孤独プログラマー譚

孤独死が近い。

Backbone.jsでCollectionを触ってみる

Collectionを触ったことが無かったので、簡単に触っておこうと思います。

Cllectionは複数のModelを突っ込める器のようなものです。
DBに例えると、Modelがレコードだとすると、Collectionはテーブルになります。

以下、コードの全容。
タスクリストのようなアプリケーションです。
フォームにテキストを入力してボタンを押すと、リストにテキストが追加されます。
ただし、ブラウザの更新ボタンを押すとすべて初期化されます。笑

<html>
<script src="underscore-min.js"></script>
<script src="jquery.js"></script>
<script src="backbone-min.js"></script>
<script>
$(function() {
    // モデル
    var Model = Backbone.Model.extend();

    // コレクション
    var Collection = Backbone.Collection.extend({
        model: Model,
    });
    var collection = new Collection;

    // ビュー
    var View = Backbone.View.extend({
        el: 'body',
        events: {
            'click button': 'addItem',
        },
        initialize: function() {
            this.listenTo(this.collection, 'add', this.render);
        },
        addItem: function() {
            this.collection.add({ name: $('input').val() });
        },
        render: function(model) {
            $('ul').append($('<li>').text(model.get('name')));
            $('input').val('');
        },
    });
    var view = new View({ collection: collection });
});
</script>

<body>
    <input type="text">
    <button>追加</button>
    <ul>
    </ul>
</body>
</html>


var Collection = Backbone.Collection.extend({
    model: Model,
});
var collection = new Collection;

コレクションの作成。使用するモデルを指定します。

var View = Backbone.View.extend({
    el: 'body',
    events: {
        'click button': 'addItem',
    },
    initialize: function() {
        this.listenTo(this.collection, 'add', this.render);
    },
    addItem: function() {
        this.collection.add({ name: $('input').val() });
    },
    render: function(model) {
        $('ul').append($('<li>').text(model.get('name')));
        $('input').val('');
    },
});
var view = new View({ collection: collection });

ビューの作成。
コレクションにモデルが追加されると、コレクションで「add」イベントが発火されます。
ビューはaddイベントの発火を感知し、renderメソッドが実行されます。

render: function(model) {
    $('ul').append($('<li>').text(model.get('name')));
    $('input').val('');
},

このメソッドですが、自動で引数に追加されたモデルが渡されるようです。こりゃ便利だ。


Backbone.jsいいなぁ。でも古いフレームワークなので、新しいブログ記事が全く出てこない。
JavaScriptフレームワークの歴史を追うってことで、今後はReact.jsとかも触っていきたいです。

Backbone.jsで超シンプルなMVCを書く

前回はピュアなJavaScriptでシンプルなMVCを書くという趣旨でしたが…
フレームワークを使った方がシンプルに書けるのでは?」
という考えが巡り始めたので、今回は前回の内容をBackbone.jsで書き換えます。

Backbone.jsを簡単に説明すると…
日本語に訳して「背骨」なだけあって、MVCの枠組みだけを提供してくれます。
jQueryと共存できる…というか、Backbone.jsのライブラリ自体がjQueryに依存してます。
要は、大枠はBackbone.jsがやるから、細かいことはjQueryでやってくれってことです。

■ Model

var Model = Backbone.Model.extend({
    buttonStatus: true,
    changeData: function() {
        if (this.buttonStatus) {
            this.buttonStatus = false;
        } else {
            this.buttonStatus = true;
        }
        this.trigger('changingIsDone');
    }
});
var model = new Model;

まずモデル。データは全部モデルに持たせます。
モデルからDOM操作は行いません。

this.trigger('changingIsDone');

データに変更があれば、モデルの「changingIsDone」イベントが発火されます。
※HTMLの各要素から発火されるイベント(onclick, onchange等)とは別の概念と思われます。

■ View

var View = Backbone.View.extend({
    el: 'body',
    events: {
        'click button': 'switchButtonName',
    },
    initialize: function() {
        this.listenTo(this.model, 'changingIsDone', this.render);
        this.render();
    },
    switchButtonName: function() {
        this.model.changeData();
    },
    render: function() {
        var buttonName;
        if (this.model.buttonStatus) {
            buttonName = '乃木坂46';
        } else {
            buttonName = '欅坂46';
        }
        $('button').text(buttonName);
    },
});
var view = new View({ model: model });
events: {
    'click button': 'switchButtonName',
},

イベントハンドラを登録してます。
switchButtonNameメソッドから、モデルを操作してます。

initialize: function() {
    this.listenTo(this.model, 'chngingIsDone', this.render);
    this.render();
},

initializeメソッドはPHPの__construct()のようなものです。
listenToメソッドで、モデルの「chngingIsDone」イベントの監視を開始しました。
「chngingIsDone」イベントが発火すると、自動でrenderメソッドが実行されます。

render: function() {
    var buttonName;
    if (this.model.buttonStatus) {
        buttonName = '乃木坂46';
    } else {
        buttonName = '欅坂46';
    }
    $('button').text(buttonName);
},

renderメソッドではDOM操作を行っています。


ここで面白いのは、モデルの「changingIsDone」イベントを監視できるViewを複数追加できることです。

■ View 2

var SubView = Backbone.View.extend({
    el: 'p',
    initialize: function() {
        this.listenTo(this.model, 'changingIsDone', this.render);
        this.render();
    },
    render: function() {
        var description;
        if (this.model.buttonStatus) {
            description = '2011年に結成されました。';
        } else {
            description = '2015年に結成されました。';
        }
        $(this.el).text(description);
    },
});
var subView = new SubView({ model: model });

こちらのViewもモデルを監視しています。
つまり、モデルに変化があれば、View, SubViewの両方がレンダリングされる…ということです。

<body>
<button></button>
<p></p>
</body>

こちら、HTML。


ピュアなJavaScriptより、こちらの方がシンプルに書けました。
デメリットは、そのフレームワークの経験者でないと、システムの修正を気軽にできないことでしょうか…。

JavaScriptを超シンプルなMVCで書く

上記のようなこと、あると思います。というか、今の自分です。


サラッと見通しよく書きたいなぁ…ということで、簡単なサンプルを書いてみました。

var model = {
    flg: true, // 初期値
    change: function() {
        if(this.flg) {
            this.flg = false;
        } else {
            this.flg = true;
        }
    }
}

まずモデルは、データ層として考える。プロパティ = アプリが保持すべきデータ。
条件分岐はモデルでやる。
例えば、ファミコンのボタンが押されたら、その「ボタンを押した」という信号がモデルに渡されるイメージ。
信号をどう捌くかは、モデルに任せる。


自分が昔よくやってたのが、

var oldText = $('button').text();
var newText;
if (oldText === '乃木坂46') {
    newText = '欅坂46';
} else {
    newText = '乃木坂46';
}
$('button').text(newText);

DOMの値を直接読みにいって、それを使って諸々の処理を行う。
もうやめとこう。これからは、モデルのプロパティに状況を持たせる。DOMが持ってるデータは使わない。

var view = {
    render: function() {
        if(model.flg) {
            $('button').text('乃木坂46');
        } else {
            $('button').text('欅坂46');
        }
    }
}

ビューからモデル内のデータを読んで、レンダリングする。

$(function() {
    $('button').on('click', function() {
        model.change();
        view.render();
    });
    view.render(); // 初期表示
});

コントローラ。モデルとビューを使う。


まとめると、

(function() {
    var model = {
        flg: true,
        change: function() {
            if(this.flg) {
                this.flg = false;
            } else {
                this.flg = true;
            }
        }
    }
    var view = {
        render: function() {
            if(model.flg) {
                $('button').text('乃木坂46');
            } else {
                $('button').text('欅坂46');
            }
        }
    }
    $(function() {
        $('button').on('click', function() {
            model.change();
            view.render();
        });
        view.render();
    });
})();

無名関数で囲って、グローバル変数は作らないようにする。


これで何とか見通しよくなってくれないかなぁ…。