0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TodoMVCのBackbone.js版はどのようにtodoを新規作成して描画しているのか

Last updated at Posted at 2015-12-15

様々なJavaScriptフレームワークを使って、同じTodoアプリを作る企画TodoMVCをご存知でしょうか?今回はBackBone.js版のソースコードを読み解いていきます。全てを追うのではなく、タスク名を入力して新規タスクがどのように作られるかを掴んでいきましょう。

アプリのメインはどこにある?

まずjs/app.jsを見てください。記述やっていることはapp.AppViewのインスタンスの生成です。これがこのアプリのメインとなります。

/*global $ */
/*jshint unused:false */
var app = app || {};
var ENTER_KEY = 13;
var ESC_KEY = 27;

$(function () {
    'use strict';

    // kick things off by creating the `App`
    new app.AppView();
});

app-view.jsにメインのViewであるapp.AppViewが宣言されています。このViewは画面上はどこを指すかというと次の画像の青い部分です。

todomvc_backbonejs_01_150911.jpg

これはapp.AppViewelプロパティで指定されています。

app.AppView = Backbone.View.extend({
    el: '.todoapp',
});

後はこのViewを追えば流れがわかります。見て行きましょう。

EnterでTodo作成

下記画像の赤枠のテキストエディタにタスク名を入力後、Enterを押すとtodoが登録されます。

todomvc_backbonejs_02_150911.jpg

この赤枠はもともとindex.htmlに書き込まれたもので動的に生成はされていません。この赤枠の.new-todo要素でキー入力が行われた場合に実行される処理はeventsプロパティを見ればわかります。

app.AppView = Backbone.View.extend({
    el: '.todoapp',
    events: {
        'keypress .new-todo': 'createOnEnter',
    }
});

crateOnEnterという関数が実行されると定義されています。イベントハンドラとしてcreateOnEnterを渡しているということです。ちなみにEnter以外のキー入力が行われた場合もイベントが発生するので、createOnEnterは実行されます。

app.AppView = Backbone.View.extend({
    el: '.todoapp',
    events: {
        'keypress .new-todo': 'createOnEnter',
    },
    createOnEnter: function (e) {
        if (e.which === ENTER_KEY && this.$input.val().trim()) {
            app.todos.create(this.newAttributes());
            this.$input.val('');
        }
    }
});

イベントハンドラcreateOnEnterでは、まず入力されたキーがENTERかを判断します。変数ETNER_KEYには13という数値が入っています。キー入力は数値で判断されるのです。そしてEnterキーなら次は入力された文字列をトリムします。trim()は文字列の前後から空白やタブ、改行文字を削除します。この時、次のように書けばいいのではと思うかもしれません。

if (e.which === ENTER_KEY) {
    this.$input.val().trim();
    app.todos.create(this.newAttributes());
    this.$input.val('');
}

なぜifの条件にtrimをつなげているかというと、トリムした結果が空文字の場合はifの中を実行しないためです。テキストフィールドに空白やタブしか入力されなかった時に、空文字だけになります。この場合はタスク名が入力されていないので、todoを作成する必要はないですよね。

ifの中身の2行でtodosコレクションにtodoを追加して、テキストフィールドを空にリセットしています。createは要素に追加して、saveを実行しています。今回はリソースがlocalStorageが選択されているので、APIにPOSTリクエストを送るのではなく、localStorageに保存ということになります。

app.AppView = Backbone.View.extend({
    el: '.todoapp',
    events: {
        'keypress .new-todo': 'createOnEnter',
    },
    newAttributes: function () {
        return {
            title: this.$input.val().trim(),
            order: app.todos.nextOrder(),
            completed: false
        };
    },
    createOnEnter: function (e) {
        if (e.which === ENTER_KEY && this.$input.val().trim()) {
            app.todos.create(this.newAttributes());
            this.$input.val('');
        }
    }
});

createにはハッシュで保存するtodoの値を渡すことが出来ます。新規todoのハッシュはnewAttributesというメソッドで返すようにしています。titleは入力されたタスク名。completedは未完了のfalse。orderには既存todoの数 + 1が入ります。

ここまでの流れを図にすると次のようになります。

todomvc_backbonejs_03_150911.jpg

新規TodoのHTML要素を組み立てる

app.todos.create(this.newAttributes());todoaddsaveを実行しています。app.todosaddイベントが発生した場合は、this.addOneが実行するように定義されています。

app.AppView = Backbone.View.extend({
    initialize: function () {
        this.listenTo(app.todos, 'add', this.addOne);
    }
});

addOneの中身はシンプルです。app.TodoViewを作成してレンダリングしたものを、要素.todo-listに追加しています。これで新規作成されたtodoがブラウザに表示されます。

app.AppView = Backbone.View.extend({
    initialize: function () {
        this.$list = $('.todo-list');
        this.listenTo(app.todos, 'add', this.addOne);
    },
    addOne: function (todo) {
        var view = new app.TodoView({ model: todo });
        this.$list.append(view.render().el);
    }
});

new app.TodoView({ model: todo })は引数でmodeltodoを指定しています。これでViewとModelが結びつきます。app.TodoViewのソースではmodelプロパティの定義はされていません。これは外部から``new app.TodoView()で生成されるときに先ほどのように、{ model: myModel }`とハッシュでmodelを差し替えれるようになっています。こうすることで他のモデルを`app.TodoView`で使うことができます。ただし、`app.TodoView`には`this.model.get('completed')`などと、`completed`というプロパティがmodelに設定されていることが期待されています。なので必要な仕様を実装したModelでないと、`app.TodoView`は機能しません。

作成されたViewはthis.$list.append(view.render().el);render()でHTML要素を組み立てて、elでその要素を取得しています。render()はBackbone.jsで用意された何もしないメソッドです。なのでrenderという名前にしなくても構いません。自分でHTMLを組み立てる処理を実装したメソッド用意すればいいのです。Backbone.jsはただrenderというメソッド名は使うのはどう?と提案しているに過ぎないのです。

全体の流れを図にすると次のようになります。

todomvc_backbonejs_04_150911.jpg

todoのデータはどのコンストラクタに渡すのか?

app.TodoというModelを作成したので、new app.Todo({title: "test"))のようにModelに渡します。CollectionやViewには渡しません。CollectionであるTodosにはdefaultsinitializeもありません。app.TodoViewはrenderで結びつけたmodelから値を取得してレンダリングを行っています。

app.TodoView = Backbone.View.extend({
    tagName:  'li',
    render: function () {
        if (this.model.changed.id !== undefined) {
            return;
        }

        this.$el.html(this.template(this.model.toJSON()));
        this.$el.toggleClass('completed', this.model.get('completed'));
        this.toggleVisible();
        this.$input = this.$('.edit');
        return this;
    }
});
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?