月: 2015年1月

AngularJSでng-repeartを使って動的に入力フォームを作成した時のバリデーション

AngularJSでng-repeartを使って動的に入力フォームを作成した時に
requiredなどのバリデーションが上手く動かなかったのでそのときの対処方法。

あと、plunker使ってみました。

■通常のエラーチェックの例
http://plnkr.co/edit/AXxoyf2ZU5ti7WQDXW70?p=preview

問題が起きたケースはinputタグなどをng-repeartを使って
複数作成した場合にでした。
たとえば以下のような記述です。
※実際にはモーダル画面の中でinputタグの動的追加みたいなことをしてました。

<form name="mainForm">
	<div ng-repeat="title in titles">
		<input type="text "name="title{{ $index }}" ng-model="titles[$index]" required />
		<span ng-show="mainForm.title{{ $index }}.$error.required">Title required!</span>
	</div>
</form>

ただ自分の場合はこれだと値を空にしてもエラーメッセージが表示されませんでした。
ググってみると

Dynamic validation and name in a form with AngularJS
http://stackoverflow.com/questions/14378401/dynamic-validation-and-name-in-a-form-with-angularjs

上記のようなページが見つかり、上記ページ中の↓のように「ng-form」を
利用することで正常にエラーが表示されるようになりました。
formタグの入れ子の利用ということかな?
これでわざわざnameを変えなくてもバリデーションがかけられます。

<form name="outerForm">
<div ng-repeat="item in items">
   <ng-form name="innerForm">
      <input type="text" name="foo" ng-model="item.foo" />
      <span ng-show="innerForm.foo.$error.required">required</span>
   </ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>

ng-form

formディレクティブの入れ子のエイリアスです。 HTMLはフォーム要素の入れ子を許可していません。 例えば、コントロールのサブグループのみの検証が必要なケースで、入れ子のフォームは便利です。

■ng-repeatを使ったフォームのエラーチェック例
http://plnkr.co/edit/1cccXO6UmCi4K6it78Zj?p=preview

 

AngularJS読書メモ

AngularJS読書メモ

娘に歯が生え始めました!

ディレクティブとかサービスとかどこに何を書けばいいのかよくわかってないので、
本で学習。

コントローラはスコープをセットアップする関数。
コントローラで$scopeのメソッドを定義する際に複雑な
ロジックを記述してしまうのではなく、プレゼンテーション
ロジックはフィルターやディレクティブに、ビジネスロジック
はサービスに分離して記述し、コントローラはあくまでも
スコープのセットアップ処理だけに徹するのがいい。

プレゼンテーションロジックを実装する:ディレクティブ、フィルター
ビジネスロジックや共通処理を実装する:サービス
これらを繋ぎ合わせる:コントローラ、スコープ
その他:DI、モジュール管理機能

読んだからといって出来てるわけではないです。。

AngularJSでDragAndDropとSortableとModalを組み合わせてみた

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()">&times;</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()">&times;</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