AngularJSでDragAndDropとSortableとModalを組み合わせてみた
あけましておめでとうございます。2015年もぼちぼちブログにメモしていきます。
前回に引き続き、AngularJS。
リストの並べ替えとドラッグ&ドロップでの
リスト要素追加、リスト要素の削除とリスト要素の内容変更を
モーダルウィンドウで行うを組み合わせてみました。
あと一応タッチデバイスでも動くように「jQuery UI Touch Punch」
も読み込んでます。
まだAngularJSがよくわかってないので書き方がおかしい
かもしれませんが晒してみます。
わかった範囲でコメントも記載しています。
おかしなところがあったら指摘をお願いします。。
■index.html
<!DOCTYPE html> <html ng-app="myapp"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>AngularJs drag and drop and sortable sample</title> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.0/css/bootstrap.min.css"/> <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script> <script src="jquery.ui.touch-punch.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.1.3/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.min.js"></script> <script src="app.js"></script> </head> <body ng-controller="myCtrl"> <div class="container" style="margin-top:20px;"> <h1>AngularJs drag and drop and sortable sample.</h1> <div class="row"> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> drag要素 </div> <div class="list-group" ng-model="items"> <div my-draggable="#sortable" class="list-group-item" ng-repeat="item in items">{{item.title}}</div> </div> </div> </div> <div class="col-md-8"> <div class="panel panel-default"> <div class="panel-heading"> sortable要素 </div> <div class="list-group" ng-model="blocks" my-sortable id="sortable"> <div class="list-group-item" ng-repeat="block in blocks"> <div>■{{block.title}}</div> <button class="btn btn-primary sortable-handle" >並び替え</button> <button class="btn btn-success" my-modal-edit>編集</button> <button class="btn btn-danger" my-modal-delete>削除</button> </div> </div> </div> </div> </div> <!-- デバッグ用エリア --> <div class="row"> <div class="col-md-4"> <pre> {{items | json }} </pre> </div> <div class="col-md-8"> <pre> {{blocks | json }} </pre> </div> </div> </div> <!-- Modal for edit --> <script type="text/ng-template" id="T_editForm"> <div class="modal-header"> <button type="button" class="close" ng-click="$dismiss()">×</button> <h3>要素編集モーダル</h3> </div> <div class="modal-body"> <form role="form"> <div class="form-group"> <label>title</label> <input ng-model="editData.title" type="text" class="form-control" placeholder="タイトル" /> </div> </form> <div ng-show="error"> {{error}} </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-success" my-update>編集</button> </div> </script><!-- Modal for edit--> <!-- Modal for delete --> <script type="text/ng-template" id="T_deleteForm"> <div class="modal-header"> <button type="button" class="close" ng-click="$dismiss()">×</button> <h3>要素削除モーダル</h3> </div> <div class="modal-body"> <div> タイトル:{{block.title}}を削除します。 </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-success" ng-click="$close()">削除する</button> </div> </script><!-- Modal for delete--> </body> </html>
■app.js
var app = angular.module('myapp', ['ui.bootstrap']); app.controller('myCtrl', function($scope, $modal) { // drag元データ $scope.items = [ {'title' : 'テキストA'}, {'title' : 'テキストB'}, {'title' : 'テキストC'}, {'title' : 'テキストD'}, {'title' : 'テキストE'}, {'title' : 'テキストF'}, {'title' : 'テキストG'}, {'title' : 'テキストH'}, {'title' : 'テキストI'}, ]; // sortable対象データ -> 初回は本来はサーバから取得する $scope.blocks = [ {'title' : 'デフォルトA'}, {'title' : 'デフォルトB'}, {'title' : 'デフォルトC'}, {'title' : 'デフォルトD'}, {'title' : 'デフォルトE'}, {'title' : 'デフォルトF'}, {'title' : 'デフォルトG'}, {'title' : 'デフォルトH'}, {'title' : 'デフォルトI'}, {'title' : 'デフォルトJ'}, ]; // 編修のモーダルインスタンスを保持する用 $scope.editModalInstance = null; // sort処理 // $on -> $emitや$broadcastで送信したイベントを受け取るメソッド $scope.$on('my-sorted',function(event,val){ $scope.blocks.splice(val.to, 0, $scope.blocks.splice(val.from, 1)[0]); }) // drag処理 $scope.$on('my-created',function(event,val){ $scope.blocks.splice(val.to, 0,{title:'#'+($scope.blocks.length+1)+': '+val.title}); }) // delete処理 $scope.$on('my-delete',function(event,val){ $scope.blocks.splice(val, 1); }) // blockの更新 $scope.$on('my-update',function(event,val){ $scope.blocks.splice(val.index, 1, val.data); // 差し替え }) }); // sortableのディレクティブ app.directive('mySortable',function(){ return { // scope -> scope, // el -> ディレクティブが適用された箇所の要素, // attrs -> ディレクティブが適用された要素の属性 link:function(scope,el,attrs){ // jQuery UI sortableのオプション // http://www.jqref.net/ui/interactions/sortable.php el.sortable({ revert: true, axis: 'y', cancel: "", // 指定しない -> 参考: http://kamegu.hateblo.jp/entry/jquery-ui/sortable-handle handle: ".sortable-handle" }); el.disableSelection(); // deactivate は、アイテムがドラッグ終了した時に呼び出されます。 // event - イベントオブジェクト // ui - ui オブジェクト // item - ドラッグ開始したアイテムの jQuery オブジェクト el.on( "sortdeactivate", function( event, ui ) { // scope -> 要素に結び付いている$scopeを取得する dragされてきた場合は$indexはundefined var from = angular.element(ui.item).scope().$index; //console.log('from -> ' + from); // jQueryオブジェクト内で、引数で指定されたエレメントのインデックス番号を返す。インデックスは、ゼロから始まる連番。 // もし渡されたエレメントがjQueryオブジェクト内に存在しない場合、戻り値には-1が返る。 var to = el.children().index(ui.item); //console.log('to -> ' + to); if(to>=0){ // $apply -> AngularJS管理外のイベントハンドラの中で$applyメソッドを実行すると、強制的に実行できる。 scope.$apply(function(){ if(from>=0){ // fromがあるからソート処理 $emit -> 派生元のスコープに対してイベントを送信するメソッド scope.$emit('my-sorted', {from:from,to:to}); // イベント呼び出し }else{ // fromが無いから追加処理 scope.$emit('my-created', {to:to, title:ui.item.text()}); // イベント呼び出し ui.item.remove(); } }) } } ); } } }) // draggableのディレクティブ app.directive('myDraggable',function(){ return { link:function(scope,el,attrs){ // jQuery UI draggableのオプション // http://www.jqref.net/ui/interactions/draggable.php el.draggable({ connectToSortable: attrs.myDraggable, // connectToSortable を指定すると、sortable にコネクト(項目を追加)することができます。 helper: "clone", revert: "invalid" }); el.disableSelection(); // jQuery UI マッチした要素集合のテキスト選択を無効にします。 } } }) // 削除のモーダルを開くディレクティブ app.directive('myModalDelete', function($modal){ return { scope: false, // falseを指定すると、そのディレクティブを利用している箇所のスコープがディレクティブのスコープとして扱える link:function(scope,el,attr){ el.on("click", function( event ){ scope.$apply(function(){ var modalInstance = $modal.open({ templateUrl: "T_deleteForm", scope: scope // 呼び出されたときのスコープをそのまま渡しているのでテンプレート側でblockが使える }); // modalInstanceからのコールバックを受ける modalInstance.result.then(function() { //console.log("モーダル側でcloseが呼ばれた!"); scope.$emit('my-delete', scope.$index); }, function() { //console.log("モーダル側でdismissが呼ばれた!"); }); }) }); } } }) // 編集のモーダルを開くディレクティブ app.directive('myModalEdit', function($modal){ return { scope: false, // falseを指定すると、そのディレクティブを利用している箇所のスコープがディレクティブのスコープとして扱える link:function(scope,el,attr){ el.on("click", function( event ){ scope.$apply(function(){ scope.editData = angular.copy(scope.block); // リアルタイムに編集してしまわないようにコピーする // 外部の操作で閉じられるようにモーダルのインスタンスを親スコープに保存しておく scope.$parent.editModalInstance = $modal.open({ templateUrl: "T_editForm", scope: scope // 呼び出されたときのスコープをそのまま渡しているのでテンプレート側でblockが使える }); // modalInstanceからのコールバックを受ける scope.editModalInstance.result.then(function() { //console.log("モーダル側でcloseが呼ばれた!"); }, function() { //console.log("モーダル側でdismissが呼ばれた!"); }); }) }); } } }) // 編集でデータの更新 app.directive('myUpdate', function(){ return { scope: false, // falseを指定すると、そのディレクティブを利用している箇所のスコープがディレクティブのスコープとして扱える link:function(scope,el,attr){ el.on("click", function( event ){ scope.$apply(function(){ // エラーチェック -> サービスに切り出すほうがいいのかも。 if(!scope.editData.title){ scope.error = 'タイトルは必須です。'; return; } // データの更新 scope.$emit('my-update', {index:scope.$index, data:scope.editData}); scope.editData = null; // 念の為消しておく // モーダル閉じる scope.editModalInstance.close(); }) }); } } })
参考サイト
■Drag object into sortable list – AngularJS
http://stackoverflow.com/questions/18835619/drag-object-into-sortable-list-angularjs
⇒回答者のデモ
http://plnkr.co/edit/aSOlqR0UwBOXgpQSFKOH?p=preview
■Angular.js入門 (5)ディレクティブ その2
http://qiita.com/_rdtr/items/590ccb7be16dc8ed9be3
■AngularJS 製アプリで jQuery を使いたい
http://dev.classmethod.jp/client-side/javascript/angularjsandjquery/
■積極的に利用したい AngularJS グローバル API
http://dev.classmethod.jp/client-side/javascript/angularjsglobalapis/
■AngularJSではじめるHTML5開発 – Part8 モーダルダイアログによる新規レコード作成フォーム
http://www.nkjmkzk.net/?p=5401
■AngularJSでBootstrapのModalを出す方法メモ
http://otiai10.hatenablog.com/entry/2014/10/30/000537
■AngularJSで HTML5的なアラートウィンドウを表示する
http://www.walbrix.com/jp/blog/2014-01-angularjs-simplemodal.html
■jQuery UI Touch Punch
https://github.com/furf/jquery-ui-touch-punch
■AngularJSで、動的に入力項目数を設定する
http://diaryruru.blog.fc2.com/blog-entry-64.html