クライアントでバリデーション

前回作った新規入力フォームにバリデーション機能を付けてみたので、コツっぽいものをメモ。

ポイント的には

  • フォームやフォーム部品には名前(name属性)を付ける。
  • $errorや$validの評価はエレメントにつけた名前で行う。

ぐらいでしょうか。

フォームの入力要素にAngularJSから提供されてるバリデーション用のdirectiveを記述しとくとバリデートしてくれます。

たとえばinputには ng-required,ng-minlength,ng-maxlength,ng-pattern が使えるようです。詳しくはリファレンス見ましょうってことですかね。

View

入力フォーム用のViewはこんな風にしました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!doctype html>
<html ng-app="myApp">
  <head>
    <script src="@Url.Content("~/Scripts/angular.min.js")"></script>
・・・中略・・・
    <style type="text/css">
      /*invalidな時のスタイル。編集後のみ効くようにng-dirtyを付ける*/
      input.ng-invalid.ng-dirty { border: 1px solid red !important; }
    </style>
  </head>
  <body>
    <div ng-controller="TasksController">
・・・中略・・・
        <hr>
        <form class="form-horizontal" role="form" ng-model='newTask' novalidate name="newTaskForm">
            <div class="form-group">
                <div ng-controller="DatepickerCtrl">
                    <label for="newDueDate" class="col-sm-2 control-label">締切日</label>
                    <div class="col-sm-6">
                        <p class="input-group">
                            <input type="text" class="form-control" ng-model="newTask.DueDate" id="newDueDate"
                                datepicker-popup="yyyy-MM-dd" is-open="opened" datepicker-options="dateOptions" date-disabled="disabled(date, mode)"
                                ng-required="true"  />
                            <span class="input-group-btn">
                                <button class="btn btn-default" ng-click="open($event)">
                                <i class="glyphicon glyphicon-calendar"></i></button>
                            </span>
                        </p>
                    </div>
                </div>
            </div>

            <div class="form-group">
                <label for="newDescription" class="col-sm-2 control-label">詳細</label>
                <div class="col-sm-10">
                    <input type="text" ng-model="newTask.Description" placeholder="Input Description" id="newDescription" name="txtNewDescription"class="form-control"
                    ng-required="true" ng-maxlength="50"
                    />
                    <span ng-show="newTaskForm.txtNewDescription.$error.maxlength">too long</span>
                    <span ng-hide="newTaskForm.txtNewDescription.$error.maxlength">{{50-newTask.Description.length}}</span>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button ng-click="add(newTask,newTaskForm)" class="btn btn-primary"  ng-disabled="!newTaskForm.$valid">追加</button>
                </div>
            </div>
            {{newTask}}
        </form>
    </div>
    <hr>
・・・中略・・・
  </body>
</html>

8行目:エラーがある要素にはng-invalidってクラスが付与されるっぽいので、スタイルを設定。

15行目:fromにはnameとnovalidateを設定します。

23,37行目:バリデーション用のdirectiveです。必須と長さ制限。

39,40行目:フォーム要素(modelでは無い)を評価してエラーメッセージとか出してます。

45行目:ng-disabled="!newTaskForm.$valid"でフォームが正しくない時はサブミットできなくしてます。

controller

コントローラは、新規追加後にフォームをクリアする処理を追加しました。

1
2
3
4
5
6
7
$scope.add = function (task, newForm) {  //追加処理
            aTasks.save({ Description: task.Description, DueDate: task.DueDate }, function (saved_object) {
                $scope.Tasks.push(saved_object);    //成功ハンドラの第一引数にWebAPIの返り値が入るっぽい。
                $scope.newTask = {};    //入力内容初期化
                newForm.$setPristine(); //フォームもリセット
            });
        };

実行するとこんな感じです。

枠が赤くなって、エラーメッセージ(too long)が出て追加ボタンが押せなくなってます。

ほぼコードを書くことなく動いちゃうところが凄いですな。

サーバ側バリデーション

バリデーションというかエラーハンドリングです。

変な入力があったらexceptionをthrowして、クライアントで表示するようにしてみます。

controller(サーバ)

WebAPIのコントローラにバリデーションロジックを追加します。

ロジック的には新規作成の時にDescriptionに”ぬるぽ”が含まれてたら”ガッ”例外を発生させるというあほらしいものです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// POST api/tasks   新規作成
        public Models.Task Post(Models.Task value) {
            if (value.Description.Contains("ぬるぽ")) {
                throw new ApplicationException("ガッ");
            }
            using (var db = new Models.TaskContext()) {
                db.Tasks.Add(value);
                db.SaveChanges();
                return value;   //保存された値を返す
            }
        }

controller(AngularJS)

クライアント側のコントローラにAJAX呼び出しの際のエラーハンドラを追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$scope.add = function (task, newForm) {  //追加処理
            aTasks.save({ Description: task.Description, DueDate: task.DueDate }, function (saved_object) {
                $scope.Tasks.push(saved_object);    //成功ハンドラの第一引数にWebAPIの返り値が入るっぽい。
                $scope.newTask = {};    //入力内容初期化
                newForm.$setPristine(); //フォームもリセット
            },
            function (httpResponse) {  //エラーハンドら
                $scope.DebugObj = httpResponse;
                alert(httpResponse.data.ExceptionMessage);
            });
        };

いじょうですけど。