technoshop

杉並区和泉のソフトウェアハウス、株式会社テラソフトの技術ブログです。主に.NET MVCとKnockoutJSの情報をまとめます。

.Net MVC4 で KnockoutJS ~【応用編2】モーダルからビューモデルにデータを追加してみる

ザクです。ご無沙汰しております!仕事で更新がストップしてしまいました。

前回はモーダル上で生徒が取っている科目と成績が見れるようにしました。演習では、EnumになっているGradeの値を成績らしくアルファベットに変換する問題を出したと思います。下がその答え合わせです。

  // Translate Enum Grade to Alphabets
    ko.bindingHandlers.grade = {
        update: function (element, valueAccessor, allBindingsAccessor) {
            return ko.bindingHandlers.text.update(element, function () {
                var value = ko.utils.unwrapObservable(valueAccessor()),
                    valueToWrite = 'N/A';
                switch (value) {
                    case 0:
                        valueToWrite = "A"
                        break;
                    case 1:
                        valueToWrite = "B"
                        break;
                    case 2:
                        valueToWrite = "C"
                        break;
                }
                return valueToWrite;
            });
        }
    };

gradeカスタムバインディングを作成して、参考リンク先のCurrency辺りのコードを拝借しながらvalueAccessor経由で受け取った値の中身を確認、valueToWriteに変換しています。大したことはありませんでしたね。できましたか?

モーダル使って詳細情報を見れるようになりましたけど、普通データの修正とかもやりますね。今回は、モーダル画面上でEnrollmentモデルにデータを追加したら、表の中が自動更新されるとかやってみます。

モーダル上にEnrollmentの追加フォームを作る

データの追加にはフォームが必要ですね。モーダルのテンプレートにフォームを入れます。

<script id="enrollment" type="text/html">
<div class="modal-dialog">
    <div class="modal-content">
    <div class="modal-header">
        <h4 class="modal-title">Class Enrolled (<span data-bind="text: LastName"></span>, <span data-bind="text: FirstMidName"></span>)</h4>
    </div>
    <div class="modal-body">  
        <table class="table table-bordered table-condensed">
            <thead>
                <tr>
                    <th>Title</th>
                    <th class="text-center">Credits</th>
                    <th class="text-center">Grade</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <!-- ko foreach: Enrollments -->
                <tr>
                    <td><span data-bind="text: Title"></span></td>
                    <td class="text-center"><span data-bind="text: Credits"></span></td>
                    <td class="text-center"><span data-bind="grade: Grade"></span></td>
                    <td></td>
                </tr>
                <!-- /ko -->
                <tr>
                    <td><input data-bind="value: addtitle" /></td>
                    <td><input data-bind="value: addcredit" /></td>
                    <td><select data-bind="value: addgrade, options: gradeOptions, optionsText: 'Text', optionsValue: 'Value'"></select></td>
                    <td><button type="button" class="btn btn-mini btn-success" >Add PO</button></td>
                </tr>
            </tbody>
        </table>  
    </div>
    <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    </div>
    </div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</script>

Enrollmentsのループの下に、一行フォームを用意しました。クラス名(Title)と単位(Credits)はinputにして、Gradeは3択なのでselectにしました。selectを利用するには、選択肢になるObservableが必要です。Observableはoptionsバインディングを使ってバインドします。

Knockout : The "options" binding

とりあえずフォームができたので、ビューモデルにフォーム用のObservableを追加します。

        // Add function for Enrollment
        self.gradeOptions = ko.observableArray([{ Text: 'A', Value: '0' }, { Text: 'B', Value: '1' }, { Text: 'C', Value: '2' }]);
        self.addtitle = ko.observable();
        self.addcredit = ko.observable();
        self.addgrade = ko.observable();

追加する場所はStudentモデルの定義の一番下です。Studentの中ならどこでもいいのですが、Gitでコードを管理してたら下にアペンドしてたほうがゴチャゴチャしなくてわかりやすいかもです。フォームの各値にObservableを用意してやります。selectには選択肢になる別のObservableArrayが必要なのでgradeOptionsを作りました。中身はselectの選択肢のオブジェクト({})が入った配列([])です。オブジェクトの中は、2つのKey/Valueのペアになっているので、フォーム側のoptionTextとoptionValueに使った名前TextとValueに合わせます。これだけで、とりあえずフォームは表示できるはずです。

f:id:technoshop:20141105164133j:plain

フォームが表示されました!

フォームからObservableArrayにプッシュする

これで入力の準備は整いました。Title、Credits、Gradeを入力し、Add Gradeを押したらテーブルに一行アペンドするようにします。とりあえず、Add Gradeを押した時に呼び出されるAddEnrollファンクションを作ります。

        self.addEnroll = function () {
            var formdata = {
                "StudentId": self.StudentId(),
                "Grade": parseFloat(self.addgrade()),
                "Course": { "Title": self.addtitle(), "Credits": parseFloat(self.addcredit())}
            };
            self.Enrollments.push(formdata);
            self.addtitle('');
            self.addcredit('');
            self.addgrade('');
        }

フォーム用のObservableの下に続けて、上のコードを追加します。EnrollmentsのObsrevableArrayに差し込めるように、Enrollmentモデルに合った形にフォームのObservableをformdataとしてまとめたオブジェクトを作ります。Enrollmentモデルは、Web APIから取ってきたJsonデータの都合で、Courseオブジェクトを下にぶら下げないと入力できません。なので、TitleとCreditsはCourseオブジェクトの中に入れてやります。

formdataが準備できたらpushを使ってEnrollments(EnrollmentのObservableArray)に差し込みます。仕上げに、フォームのObservableの中身を空にしてリセットしたら完成です。

では、フォームに値を入れてAdd Enroll

f:id:technoshop:20141105172351j:plain

成績のなかったLauraさんとNinoさんに、AbenomicsとHatsunemicsを履修させて成績をつけました。Enrollmentが追加されたら、後ろの成績表の中も更新されてます。

Knockoutらしく入力規則をつける

入力ができるようになりましたけど、TitleやCreditsに値がなくてもガンガン追加できてしまいます。これらに値が入っていなかったらボタンを押せないようにしたら、空で追加は防げそうです。

Knockoutらしく入力規則を作ってみます。


        self.canAdd = ko.computed(function () {
            var yesyoucan = false;
            if (self.addtitle() && self.addcredit()) {
                yesyoucan = true;
            }
            return yesyoucan;
        });

ここで再びComputed Observableの登場です。フォームのaddtitleとaddcreditを監視するフラグをcomputedで作りました。この2つに値が入っていたらyesyoucanフラグにtrueを入れて返すようにします。次にフォームです。

<tr>
    <td><input data-bind="value: addtitle" /></td>
    <td><input data-bind="value: addcredit" /></td>
    <td><select data-bind="value: addgrade, options: gradeOptions, optionsText: 'Text', optionsValue: 'Value'"></select></td>
    <td><button type="button" class="btn btn-mini btn-success" data-bind="enable: canAdd, click: addEnroll">Add Grade</button></td>
</tr>

Add Gradeボタンに、enableバインディングを追加して、値にcanAddを入れます。これだけでTitleとCreditsの値がなければ押せないボタンの出来上がりです。

f:id:technoshop:20141105174224j:plain

Add Gradeボタンが薄い緑になって、押せなくなっています!