knockout.jsチュートリアル自分用メモ


概要

jQueryから抜け出せないレガシーなエンジニアが、knockout.jsの公式サイトのチュートリアルを実践した際の自分用メモ。
(チュートリアルでやったことをあとで俯瞰できるように、自分なりのメモとか翻訳した文章を加筆したものです。)

MVVM(Model-View-ViewModel)とはなんぞや?

まず、基本の勉強。
MVVMフレームワーク「Knockout.js」が超絶便利!!その概要と使いどころなどについて より

M(Model)

クライアントサイドのコードの内、HTMLの都合が関係ない部分(ドメイン)のロジックを実装するJavaScriptコード。
Webサービスの呼び出しや、共通して使われる汎用的なコード(ライブラリ)など。

V(View)

クライアントサイドのコードの内、UIのテンプレートを定義するHTML/CSS。

VM(ViewModel)

クライアントサイドのコードの内、UIの状態保持、および、UIから呼び出されるメソッドを定義したJavaScriptコード

公式チュートリアル実践

Tutorial: Introduction

See the Pen KOJS tutorial1 for myself by Yanchi4425 (@yanchi4425) on CodePen.

Tutorial: Working with Lists and Collections

See the Pen KOJS tutorial2 for myself by Yanchi4425 (@yanchi4425) on CodePen.

Tutorial: Single Page Application

このコードを直接貼り付けても"/mail"が無いので動きません。
動かしたい場合は、公式のチュートリアルページで実行する必要があります。

もしくは、以下に本チュートリアルの完成形があります。
http://learn.knockoutjs.com/WebmailExampleStandalone.html
http://learn.knockoutjs.com/mail?folder=inbox
のようにすると一応/mailから返されるデータを見ることができます。

spa.html

    <style>
        body {
            color: black;
        }

        .selected {
            background-color: #7f7;
        }
    </style>
    <ul class="folders" data-bind="foreach: folders">
        <!-- CSSバインディングを使用して、選択したクラスを一致するフォルダに適用したり、
            ユーザーがフォルダをクリックするたびにgoToFolderを呼び出すことができます。 -->
        <li data-bind="text: $data, css: {selected: $data == $root.chosenFolderId()}, click: $root.goToFolder"></li>
    </ul>

    <!-- step2 -->
    <!-- Mails grid -->
    <!-- withバインディングは、その中の要素をバインドするときに使用されるバインディング・コンテキストを作成します。 
        この例では、<table>内のすべてがselectedFolderDataにバインドされるため、
        selectedFolderDataを使用する必要はありません。 
        メールの前にプレフィックスとして。 
    -->
    <table class="mails" data-bind="with: chosenFolderData">
        <thead>
            <tr>
                <th>From</th>
                <th>To</th>
                <th>Subject</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: mails">
            <!-- step3 -->
            <!-- バインドを更新する必要があるため、訪問者がメールグリッドの行をクリックすると、
                ビューモデルが対応するメールをロードするようにする。
                なんで、<tr>要素のクリックバインドを追加する。 -->
            <tr data-bind="click: $root.goToMail">
                <td data-bind="text: from"></td>
                <td data-bind="text: to"></td>
                <td data-bind="text: subject"></td>
                <td data-bind="text: date"></td>
            </tr>
        </tbody>
    </table>

    <!-- step3 -->
    <!-- 選択されたメールの内容を表示する -->
    <!-- Chosen mail -->
    <div class="viewMail" data-bind="with: chosenMailData">
        <div class="mailInfo">
            <h1 data-bind="text: subject"></h1>
            <p>
                <label>From</label>:
                <span data-bind="text: from"></span>
            </p>
            <p>
                <label>To</label>:
                <span data-bind="text: to"></span>
            </p>
            <p>
                <label>Date</label>:
                <span data-bind="text: date"></span>
            </p>
        </div>
        <!-- htmlバインディングの使用に注目してください。これにより、
            メールコンテンツの改行やHTMLマークアップを画面上に表示することができます -->
        <p class="message" data-bind="html: messageContent" />
    </div>

    <script src="./jquery.min.js"></script>
    <script src="./sammy-latest.min.js"></script>
    <script src="./knockout-3.4.0.js"></script>
    <script>
        function WebMailViewModel() {
            // Data
            var self = this;
            self.folders = ['index', 'Archive', 'Sent', 'Spam'];
            self.chosenFolderId = ko.observable();
            // step2
            // メールグリッドと紐付ける
            self.chosenFolderData = ko.observable();

            // step3
            // メールデータと紐付ける
            self.chosenMailData = ko.observable();

            // ビヘイビア
            // フォルダの内容を表示
            // step4でSammy.jsを使用するため削除
            // self.goToFolder = function(folder) {
            //     // folderの中身:'index', 'Archive', 'Sent', 'Spam'のクリックされたやつ
            //     self.chosenFolderId(folder)

            //     // step3
            //     // メールの表示を停止する
            //     self.chosenMailData(null);
            //     // step2
            //     // jQueryが必要
            //     // ユーザーがフォルダに移動するたびに、Ajaxリクエストを実行してselectedFolderDataにデータを設定します。
            //     // nullが設定された場合は非表示になる。
            //     $.get('/mail', {
            //         folder: folder
            //     }, self.chosenFolderData);
            // };
            //step4 sammyjs化
            self.goToFolder = function(folder) {
                // urlの末尾に#folder名を追加する
                // location.hashはJSの標準
                location.hash = folder;
            }

            // メールの内容を表示
            // step4でSammy.jsを使用するため削除
            // self.goToMail = function(mail) {
            //     // 該当のフォルダを選択状態にする
            //     self.chosenFolderId(mail.folder);
            //     // フォルダの表示を停止する
            //     self.chosenFolderId(null);
            //     // ajaxでメールの内容を取得して表示する
            //     $.get('/mail', {
            //             mailId: mail.id
            //         },
            //         self.chosenMailData);
            // }
            // step4 sammyjs化
            self.goToMail = function(mail) {
                location.hash = mail.folder + '/' + mail.id;
            }

            // step2
            // Inboxをデフォルト表示
            // step4で削除
            // #付きのURLで外部からアクセスした際に、Inboxにリダイレクトされてしまうため。
            // かわりにSammy内で処理する
            // self.goToFolder('Inbox');

            // step4
            // Sammy.jsを使ってクライアントサイドでルーティング
            Sammy(function() {
                // フォルダの内容表示
                this.get('#:folder', function() {
                    //フォルダに移動
                    self.chosenFolderId(this.params.folder);
                    // メールの内容を非表示に
                    self.chosenMailData(null)
                        // Ajaxでデータ取得
                    $.get("/mail", {
                        mailId: this.params.mailId
                    }, self.chosenFolderData);
                });
                // メールの内容表示
                this.get("#:folder/:mailId", function() {
                    // メールの内容に移動
                    self.chosenFolderId(this.params.folder);
                    // フォルダの内容を非表示に
                    self.chosenFolderData(null);
                    // Ajaxでデータ取得
                    $.get("/mail", {
                        mailId: this.params.mailId
                    }, self.chosenMailData);
                });

                //初期表示
                this.get('', function() {
                    this.app.RunRoute('get', '#Inbox')
                });
            }).run();
        }

        ko.applyBindings(new WebMailViewModel());
    </script>

残る2つのチュートリアルはまた後日。