読者です 読者をやめる 読者になる 読者になる

@半径とことこ60分

人間の認知範囲なんてそんなもんさと、鳥が囀った

プログラマーじゃないけどLaravel4入門(8)blogをつくる

laravel4

バックエンド/ブロック新規作成、編集、削除

さて、ブロックです。想定しているのは、プロフィールやリンクなどの静的コンテンツと最新記事やカテゴリ一覧などの動的コンテンツをメインコンテンツの右、または左に表示することですが、難しそうなのは動的コンテンツですね。

まあ、何とかなるでしょう。と、相変わらず行き当たりばったりで、まずは記事やカテゴリと同様にジェネレーターを使ってコントローラー他を作ります。

$ php artisan generate:scaffold block --fields="title:string, is_published:tinyinteger:unsigned, type:tinyinteger:unsigned, order:tinyinteger:unsigned, value:text"

ところで、この JeffreyWay/Laravel-4-Generators ですが、各フィールド指定のコンマの後にスペースを入れないとエラーになります。下のようなマイグレーションができてしまいますのでご注意!

public function up()
{
  Schema::create('block', function(Blueprint $table)
  {
    $table->increments('id');
    $table->string,is_published('title')->tinyinteger()->unsigned,type()->tinyinteger()->unsigned,order()->tinyinteger()->unsigned,value()->
text();
    $table->timestamps();
  });
}

BlocksTableSeeder.php にいくつかデータが入るようコードを追加し、

$ php artisan db:seed --class=BlocksTableSeeder

でダミーデータ作成。routes.php

Route::resource('blocks', 'BlocksController');

を追加し、それぞれ backend ディレクトリ以下にコントローラー BlocksController.php と各ビューを書いていきます。

コードは、ausnichts/laravel4-blog · GitHub にあります。

f:id:ausnichts:20140908130807p:plain

で、こんな感じにできあがったんですが、表示順を自由に変更できないと意味がありませんので、どうしようかと思案の末、最近よく目にするようになったテーブルのセルやリストのアイテムをドラッグして移動する UI を調べてみることにしました。

jQuery UI の Sortable を実装する

こちらのサイトを参考させていただきました。

Sortable | jQuery UI
jQuery UI 日本語リファレンス

まず、ダウンロードページで core.js, widget.js, mouse.js, sortable.js にチェックを入れてスクリプトをダウンロードします。

ダウンロードした jquery-ui.min.js を blocks/index.blade.php に読み込み、

$('#sortable-table tbody').sortable();

と記述するだけで、行をドラッグドロップで移動できるようになります。ポイントは、セレクタを sortable-table と id 指定したテーブルの tbody とすることのようです。tbody を指定しないと thead と tbody 全体がドラッグ対象になったり、テーブル全体が対象になったりします。

さて、簡単に一覧の表示順を変更できるようになったのですが、このままではこの順序が保持されるわけではありませんので、何らかの方法で blocks テーブルに保存しなくてはなりません。

toArray  メソッド

Sortable toArray メソッドが役に立ちそうです。

$('#sortable-table tbody').sortable('toArray');

とすれば、tbody 以下の tr に指定された id がカンマ区切りの文字列で取得出来ます。ですので、各 tr に $block->id を設定しておき、toArray で取得した文字列を hidden の input 要素に設定してフォームで送るだけです。

app/views/backend/blocks/index.blade.php

@extends('backend.master')
@section('title')
  ブロック一覧 - @parent
@stop

@section('content')
  <h1 class="page-header">ブロック一覧</h1>
  {{ Notification::showAll() }}
  <div class="pull-left">
    <div class="btn-toolbar">
      <a href="{{ URL::route('backend.blocks.create') }}" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span>&nbsp;新規ブロック作成</a>
    </div>
  </div>
  <div style="clear:both"></div>
@if($blocks->count())
  <div class="table-responsive">
    <table id="sortable-table" class="table table-striped">
      <thead>
        <tr>
          <th><a data-toggle="modal" href="#myModalSort"><span class="glyphicon glyphicon-save" Title="Save"></span></a>
            <div class="modal fade" id="myModalSort" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
            <div class="modal-dialog">
            <div class="modal-content">
            <div class="modal-header">
              <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
              <h3 class="modal-title">ブロック表示順の保存</h3>
            </div>
            <div class="modal-body">
              <p>この順序で保存していいですか?</p>
            </div>
            <div class="modal-footer">
              {{ Form::open(array('url' => 'backend/blocks/sort', 'id' => 'formSort', 'class' => 'pull-right')) }}
              {{ Form::hidden('result', '', array('id' => 'result')) }}
              {{ Form::button('キャンセル', array('class' => 'btn btn-default', 'data-dismiss' => 'modal')) }}
              {{ Form::submit('保存', array('id' => 'submit', 'class' => 'btn btn-default')) }}
              {{ Form::close() }}
            </div>
            </div>
            </div>
            </div>
          </th>
          <th>タイトル</th>
          <th>公開</th>
          <th>タイプ</th>
          <th>作成日</th>
          <th>更新日</th>
          <th>ID</th>
          <th>削除</th>
        </tr>
      </thead>
      <tbody>
@foreach( $blocks as $block )
      <tr id="{{ $block->id }}">
        <td><span class="glyphicon glyphicon-sort" Title="Sort block"></span></td>
        <td><a href="{{ URL::route('backend.blocks.edit', array($block->id)) }}">{{ $block->title }}</a></td>
        <td>{{ ($block->is_published) ? '公開' : '非公開' }}</td>
        <td>{{ ($block->type) ? 'module' : 'content' }}</td>
        <td>{{ $block->created_at }}</td>
        <td>{{ $block->updated_at }}</td>
        <td>{{ $block->id }}</td>
        <td><a data-toggle="modal" href="#myModal{{$block->id}}"><span class="glyphicon glyphicon-remove-circle" Title="Delete block"></span></a>
          <div class="modal fade" id="myModal{{$block->id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
          <div class="modal-dialog">
          <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
            <h3 class="modal-title">ブロックの削除</h3>
           </div>
           <div class="modal-body">
             <p><span class="text-danger lead">{{ $block->title }}</span> を削除していいですか?<br>削除すると戻せません.</p>
            </div>
            <div class="modal-footer">
              {{ Form::open(array('url' => 'backend/blocks/' . $block->id, 'class' => 'pull-right')) }}
              {{ Form::hidden('_method', 'DELETE') }}
              {{ Form::button('キャンセル', array('class' => 'btn btn-default', 'data-dismiss' => 'modal')) }}
              {{ Form::submit('削除', array('class' => 'btn btn-danger')) }}
              {{ Form::close() }}
            </div>
            </div>
            </div>
            </div>
          </td>
        </tr>
@endforeach
      </tbody>
    </table>
  </div>
@else
  <div class="alert alert-danger">No results found</div>
@endif
@stop

@section('script')
@parent
  {{ HTML::script('asset/js/jquery-ui.min.js') }}
  <script type="text/javascript">
  $(function(){
    // notification slide-up out
    setTimeout(function(){
      $('.alert').slideUp();
    },3000);
    // sort order of block
    $('#sortable-table tbody').sortable();
    $('#submit').click(function() {
      var result = $('#sortable-table tbody').sortable('toArray');
      $('#result').val(result);
      $('#formSort').submit();
    });
  });
  </script>
@stop

app/controllers/backend/BlocksController.php に追加

public function sort()
{
  $result = Input::get('result');
  $arr = explode(',', $result);
  $i = 1;
  foreach ( $arr as $id )
  {
    $block = Block::find($id);
    $block->order = $i;
    $block->save();
    $i++;
  }

  Notification::success('The order of block was successfully updated');
  return Redirect::action('BlocksController@index');
}

app/routes.php のADMIN SECTIONに追加

Route::post('blocks/sort', 'BlocksController@sort');

ということで完成です。

f:id:ausnichts:20140908155234p:plain