AngularJSでDragAndDropとSortableとModalを組み合わせてみた
あけましておめでとうございます。2015年もぼちぼちブログにメモしていきます。
前回に引き続き、AngularJS。
リストの並べ替えとドラッグ&ドロップでの
リスト要素追加、リスト要素の削除とリスト要素の内容変更を
モーダルウィンドウで行うを組み合わせてみました。
あと一応タッチデバイスでも動くように「jQuery UI Touch Punch」
も読み込んでます。
まだAngularJSがよくわかってないので書き方がおかしい
かもしれませんが晒してみます。
わかった範囲でコメントも記載しています。
おかしなところがあったら指摘をお願いします。。
demo
■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