yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

git, git-flowの補完とプロンプトにブランチ名を表示する

JS界隈じゃないけどTips的な記事を。
今どきGitを使わないで開発することはないと思うので、
開発しやすいように補完を入れましょう。
それとgit-flowなど、たくさんのブランチを切り替えて開発するときに
便利なのでプロンプトにブランチ名を表示しましょう。

続きを読む

node.jsでRedisを使ってみる【実践編】

久しぶりの更新。
node.js + Redisでチャット作ろうとして挫折してました。orz

作ったもの

githubに置いておきました。
yussk/mychat · GitHub

動かし方

cloneして、redis起動して、依存モジュールをインストールすれば動きます。

$ git clone git@github.com:yussk/mychat.git  // forkしてからcloneでもいいかもしれません
...

$ redis-server  // redis起動。別ウインドウでやるのがおすすめ
...

$ cd mychat
$ npm install  // package.jsonから依存モジュールを持ってくる
$ node app.js  // チャット起動

これでブラウザを立ちあげて localhost:3000 にアクセスするとチャットできます。

続きを読む

node.jsでRedisを使ってみる【準備編】

久しぶりにnode.jsです。
普通に動くAPIを用意したいので、ストレージを選定する必要があります。

候補としては

の3つがあったのですが、MySQLは結構つかったことがあるので
あまり使ったことのないRedisにしました。
機会があればMongoDBも扱いたいです。

続きを読む

Backbone.jsに入門してみる【Model+Collection+View連携編】

今回はModel, Collection, Viewの連携をしてみます。

設計

電話帳をずっと例にしてきたので今回も電話帳で。

電話帳は複数の電話情報の集合なので、

  • 電話情報(Model)
  • 電話帳(Collection)

と整理することができます。
これらを表示するために、

  • 1件あたりの電話情報を表示するView
  • 電話情報の集合をラップするようなView

が必要になります。

CollectionにModelが追加されたら
Viewの方にも自動的に反映されるようにしたいところです。


実装

前に作ったコードを使いながら書いてみました。

htmlの部分はこんな感じ。

<!-- 電話情報が個々に埋め込まれる -->
<div id="addressBook"></div>

<!-- jsRenderのテンプレート -->
<script id="address-template" type="text/x-jsrender">
  <div class="address">
    <ul>
      <li>{{>name}}</li>
      <li>{{>tel}}</li>
    </ul>
  </div>
</script>

<!-- js読み込み -->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="javascripts/lib/jsrender.min.js"></script>
<script src="javascripts/address.js"></script>


Backboneで書いた部分はこんな感じ。

// 電話情報を管理するModel
var Address = Backbone.Model.extend({
    defaults: function(){
        return {
            language: 'jp'
        };
    },
    initialize: function(){
        _.bindAll(this);
        this.on('initialize', this.setRegisterDate);
        this.trigger('initialize');
    },
    setRegisterDate: function(){
        this.set('registerDate', new Date());
    }
});

// 電話情報をまとめて管理するCollection
var AddressBook = Backbone.Collection.extend({
    model: Address,
    comparator: 'name',
    initialize: function(){
        _.bindAll(this);
        this.on('change', this.cbChange);
    },
    cbChange: function(model, collection){
        var index = this.indexOf(model);
    }
});

// Model用のView
var AddressView = Backbone.View.extend({
    initialize: function(){
        _.bindAll(this);
    },
    render: function(){
        var json = this.model.attributes;
        var html = $.templates(AddressView.tmplate).render(json);
        $(this.el).html(html);
        return this;
    }
}, {
    tmplate: $('#address-template').html()
});

// Collection用のView
var AddressBookView = Backbone.View.extend({
    el: '#addressBook',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.collection, 'add', this.addAddressView);
    },
    addAddressView: function(model){
        var addressView = new AddressView({ model: model });
        this.$el.append(addressView.render().el);
    }
});


// インスタンスを作って電話情報を追加する
var addressBook = new AddressBook();
var addressBookView = new AddressBookView({
    collection: addressBook
});

addressBook.add([
    { name: 'yutapon', tel: '090-xxxx-xxxx' },
    { name: 'hogepon', tel: '080-xxxx-xxxx' }
]);

 

実行結果

CollectionにModelが追加されると
<div id="addressBook">の中身がこのようになります。

<div id="addressBook">
    <div>
        <div class="address">
            <ul>
                <li>yutapon</li>
                <li>090-xxxx-xxxx</li>
            </ul>
        </div>
    </div>
    <div>
        <div class="address">
            <ul>
                <li>hogepon</li>
                <li>080-xxxx-xxxx</li>
            </ul>
        </div>
    </div>
</div>

 

連携部分について

AddressView

まずはModel用のViewについて見てみます。

var AddressView = Backbone.View.extend({
    initialize: function(){
        _.bindAll(this);
    },
    render: function(){
        var json = this.model.attributes;  // 【1】
        var html = $.templates(AddressView.tmplate).render(json); // 【2】
        $(this.el).html(html);  // 【3】
        return this;  // 【4】
    }
}, {
    tmplate: $('#address-template').html()
});

render()によってModelのHTMLを生成しています。
【1】ではこのViewのインスタンスの属性modelからattributesを取得しています。
attributesというのは属性値のことなので、model.get('hoge')などで取得できる値のことになります。
【2】ではjsRenderのテンプレートに変数を埋め込み、コンパイルしてHTMLを取得しています。
【3】では【2】で生成したHTMLをthis.elに埋め込んでいます。
この時、AddressViewにはel属性が無いので、デフォルトの<div></div>に埋め込まれます。
【4】ではAddressViewのインスタンスをリターンしています。

AddressBookView

つぎに、Collection用のViewについて見てみます。

var AddressBookView = Backbone.View.extend({
    el: '#addressBook',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.collection, 'add', this.addAddressView); // 【1】
    },
    addAddressView: function(model){  // 【2】
        var addressView = new AddressView({ model: model }); // 【3】
        this.$el.append(addressView.render().el);  // 【4】
    }
});

【1】ではコレクションのaddイベントを購読して、
addイベントが起きたらaddAddressViewメソッドを実行するようにしています。
【2】は【1】で設定されたメソッドです。コレクションに追加されたモデルは
addイベントの引数として取得できます。
【3】ではコレクションに追加されたモデルをViewのコンストラクタにセットし、
Viewのインスタンスを生成しています。
【4】ではaddressViewのrenderメソッドを実行した後にel属性を取得し、
それを#addressBookのセレクタにマッチするDOM上に追加しています。

アプリケーション実行部分

今までのは処理のフローを定義したビジネスロジック部分でした。
実際にデータの入出力をしてアプリケーションとして動作させる部分は
次の部分です。

// インスタンスを作って電話情報を追加する
var addressBook = new AddressBook();  // Modelのインスタンス生成
var addressBookView = new AddressBookView({ // Collectionのインスタンス生成
    collection: addressBook  // addressBookのCollectionであることを指定
});

addressBook.add([  // addメソッドでModelを追加する
    { name: 'yutapon', tel: '090-xxxx-xxxx' },  // addressBookのコンストラクタ引数に渡される
    { name: 'hogepon', tel: '080-xxxx-xxxx' }
]);


addressBook.add()が起点となり、Modelが生成されCollectionにaddイベントが発火し、
それをCollection用のViewが検出して、Model用のViewのrenderメソッドを走らせ
DOMに追加するといった一連の流れが始まります。


おわりに

現状ではaddイベントにしか対応していないですが、
本来ならばremoveイベントにも対応するべきでしょう。

また、addressBook.add()の引数に指定している配列を
サーバのAPIから受け取ったデータにすれば、動的にViewを変化させることができます。

そしてBackbone.jsではCollection.fetch()などで実現できます。

次回、サーバ連携編といきたいところですが、
肝心のサーバができていないので久しぶりにnode.js回をやります。

そういえばRouter編もまだやってませんが近いうちに。

Backbone.jsに入門してみる【View編】

今回はBackbone.Viewを取り扱います。
この記事を書く前にいろいろ試してみたのですが、
なかなか難しく理解が甘いので2回やります。

Backbone.Viewとは

MVCモデルでいうVCがBackbone.Viewになります。
サーバーサイドのMVCフレームワークを使ってきた人には
ビューなのにコントローラ?だと思いますが、Backbone.Viewでは
コントローラメソッドビューメソッドの二つを意識して使い分けます。
意識して、なので決まりがあるわけではありません。
慣れないうちはModelに書くべきロジックもViewに書いてしまいがちですが、
できればそれぞれの責務に応じて分離を心掛けましょう。


Backbone.Viewの使い方

Backbone.Viewを拡張して使います。
el(element)という属性にセレクタを渡すと、$elという属性にelの
jQueryオブジェクトが自動的にキャッシュされます。
従ってelに指定する時点でDOM上に該当する要素が存在しなければなりません。
また、Viewは単一の要素を管理するものなのでセレクタは唯一に絞れるものを渡します。

// elに指定した要素が既にあることが前提
var MyView = Backbone.View.extend({
    el: '#hoge'
});
var view = new MyView();


まだDOM上にない要素を扱いたい場合は
tagName, className, id, attributesなどを指定します。
コレクションと連携するときに使えそうですね。

var MyView = Backbone.View.extend({
    tagName: 'div',
    className: 'fuga',
    id: function(){ return _.uniqueId('hoge'); },
    attributes: { 'data-piyo': 'bar' }
});
var view = new MyView();
console.log(view);  // -> <div id="hoge8" class="fuga" data-piyo="bar"></div>

 

コントローラメソッド

DOMからイベントを受け取り、ModelやCollectionに処理を委ねることのみを
行うメソッドをコントローラメソッドと呼んでいます。

var MyView = Backbone.View.extend({
    el: '#hoge',
    events: {
        'click #huga': 'click'    // #hoge以下にある#hugaがクリックされたらclickメソッドを実行する
    },
    initialize: function(){
        _.bindAll(this);
    },
    click: function(e){
        this.model.trigger('alert');  // Modelのalertイベントを発火
    }
});
var view = new MyView({ model: new MyModel });  // MyModelの実装は省略

clickメソッドのようなものをコントローラメソッドと呼びます。
単に自身のmodelに処理を任せているだけですね。
また、eventsに設定したセレクタの評価はelに設定した要素以下から行なわれます。
従って普通に$()で検索するよりは速くなります。
よくeventsに指定したメソッドが動かない場合がありますが、それはelの範囲外にある
要素を指定しているのが原因だったりするので注意です。

ビューメソッド

DOMの整形や出力まわりを担当するメソッドです。
DOMにアクセスして値を取得したり、差し替えたりなどはビューメソッドになります。
Backbone.Viewでいうと、renderメソッドがビューメソッドの代表です。

var MyView = Backbone.View.extend({
    el: '#hoge',
    events: {
        'click #huga': 'click'
    },
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        this.$el.append('<p>fuga</p>');  // モデルの属性が変わる度にfugaを追記
        return this;
    },
    click: function(e){
        this.model.trigger('alert');
    }
});
var view = new MyView({ model: new MyModel });  // MyModelの実装は省略

Backbone.Viewにもrenderメソッドがありますが、それは単にthisを返すだけの
メソッドなので、オーバーライドして使います。
今回の例ではel属性に設定されたセレクタにp要素を追加するだけですが、
テンプレートエンジンと連携する場合や、Collectionと連携する場合などは
もっと複雑な記述になるはずです。


jsRenderと連携してみる

Backbone.Viewとテンプレートエンジンとの連携をやってみます。
いろいろなテンプレートがありますが、今回は使い慣れた jsRender を使います。
BorisMoore/jsrender · GitHub

jsRenderを使うにはjQueryを読み込んだ後にjsRenderのファイルを読み込みます。

<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="js/lib/jsrender.min.js"></script><!-- 追記 -->
<script src="js/myapp.js"></script>

これでjsRenderが使えるようになりました。
Backbone.Viewのrenderで使ってみます。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "message": "fuga" };
        var html = $.templates('<p>{{>message}}</p>').render(json);
        this.$el.append(html);  // モデルの属性が変わる度にfugaを追記
        return this;
    }
});

今回はp要素一行程度だからソース上に書けましたが、
もっと複雑なテンプレートを扱いたい場合はHTML上に記述して、
それをセレクタで取得すると良いです。

<!-- htmlに追記 -->
<script id="hoge-template" type="text/x-jsrender">
  <div>
    <ul>
      <li>{{>title}}</li>
      <li>{{>message}}</li>
    </ul>
  </div>
</script>

上記のテンプレートを取得するように変更します。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "title": "hoge", "message": "fuga" };
        var template = $('#hoge-template').html();  // セレクタで取得
        var html = $.templates(template).render(json);
        this.$el.append(html);  // モデルの属性が変わる度にfugaを追記
        return this;
    }
});

このままでも動きますが、renderが呼び出される度に
テンプレートを取得しに行くので、一回のみ取得するように変更します。
こういう場合はBackbone.View.extendの第二引数に指定すると
クラスプロパティになることを利用します。

var MyView = Backbone.View.extend({
    el: '#hoge',
    initialize: function(){
        _.bindAll(this);
        this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
        var json = { "title": "hoge", "message": "fuga" };
        // クラスプロパティから取得するように変更
        var html = $.templates(MyView.template).render(json);
        this.$el.append(html);
        return this;
    }
}, {  // 第二引数に指定したオブジェクトがクラスプロパティになる
    template: $('#hoge-template').html()
});

これでrenderが呼ばれる度にtemplateを探しにいかなくなりました。


おわりに

今回はBackbone.Viewの使い方とjsRenderの導入で終わってしまいました。
今回はjsRenderを使いましたが、もっとパフォーマンスの良いものがあったりするので、
興味があればいろいろ試してみるのがいいです。
Hadlebars vs. Hogan vs. Mustache vs. jsRender vs Dust · jsPerf

次回はViewとModel, Collectionの連携を行います。