フロントエンドの JavaScript をきれいに書ける。
Size (min) | Data Binding | Components | Testing Support | |
---|---|---|---|---|
Backbone.js | 6.3 kb | |||
Angular.js | 81 kb | x | x | x |
Ember.js | 56 kb | x | x | x |
Backbone.js は圧倒的に軽量。機能は少ない。
フェラーリと自転車を比べるようなもの・・・。
Backbone なしでできないことが、Backbone でできるようになるわけではない。
Backbone.js が依存しているユーティリティライブラリ。豊富なコレクション操作など、かゆいところに手が届く。
_.each([1, 2, 3], function (num, i) { console.log(num); });
_.map([1, 2, 3], function (num) { return num * num; });
// [1, 4, 9]
_.groupBy([18, 23, 34, 30, 12, 43], function (age) {
return Math.floor(age / 10) * 10;
});
// { 10: [18, 12], 20: [23], 30: [34, 30], 40: [43] }
テンプレート関数もあり。
var compiled = _.template('Hello, <%= name %>!');
var result = compiled({ name: 'World' });
// Hello, World!
Object にイベント機能を付加する。
on
でイベントリスナーを登録。off
で解除。trigger
でイベントを発火。引数も渡せる。var obj = _.extend({}, Backbone.Events);
obj.on('greeting', function (message) {
console.log([message, message, message].join(' ') + '!');
});
obj.trigger('greeting', 'hello');
// hello hello hello!
obj.off('greeting');
obj.trigger('greeting', 'ciao');
// ...
主従が逆のメソッドも。メモリーリーク対策にも。
listenTo
でイベントリスナーを登録。stopListening
で解除。var another = _.extend({
reverse: function (message) {
console.log(message.split('').reverse().join(''));
}
}, Backbone.Events);
another.listenTo(obj, 'greeting', another.reverse); // bind いらず!
// obj.on('greeting', _.bind(another.reverse, another));
obj.trigger('greeting', 'Bonjour');
// ruojnoB
another.stopListening();
// obj から another.reverse への参照を能動的に消すことができる。
get()
/set()
でデータの出し入れ。change
イベントを発火する。var Background = Backbone.Model.extend({
promptColor: function () {
var cssColor = prompt('CSS 色を入力してください。');
this.set('color', cssColor);
},
turnBlack: function () {
this.set('color', '#000');
}
});
var bg = new Background();
bg.on('change:color', function (model, color) {
$('section').css('backgroundColor', color);
});
$('#prompt-color').on('click', _.bind(bg.promptColor, bg));
$('#turn-black').on('click', _.bind(bg.turnBlack, bg));
Model の集合を管理する。追加、削除時にイベントを発火。
var Book = Backbone.Model.extend({});
var Library = Backbone.Collection.extend({
model: Book
});
var books = new Library();
books.on('add', function (book) {
console.log('新しく「' + book.get('title') + '」が到着しました。');
});
books.add([
{ title: '羅生門 2', author: '芥川龍之介', published: false },
{ title: '坊ちゃん', author: '夏目漱石', published: true }
]);
// 新しく「羅生門 2」が到着しました。
// 新しく「坊ちゃん」が到着しました。
Underscore.js の collection メソッドが利用可能。.
var authors = books.map(function (book) {
return book.get('author');
});
// ["芥川龍之介", "夏目漱石"]
var publishedBooks = books.filter(function (book) {
return book.get('published') === true;
});
// '「坊ちゃん」だけ'
Model/Collection をバックエンドと同期する仕組み。Backbone.sync
を書き換えれば、REST API や local storage など、好きなバックエンドを選択できる。
var book = new Backbone.Model({
title: 'The Rough Riders',
author: 'Theodore Roosevelt'
});
book.save();
// create: {"title":"The Rough Riders","author":"Theodore Roosevelt"}
book.save({author: 'Teddy'});
// update: {"title":"The Rough Riders","author":"Teddy"}
コードというより、規約。
var DocumentView = Backbone.View.extend({
tagName: 'li',
className: 'document-row',
events: {
'click .icon' : 'open',
'click .button.edit' : 'openEditDialog',
'click .button.delete': 'destroy'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
}
render: function() { /* ... */ });
});
var doc = new Document({ title: 'Hello, World!', content: '...' });
var docView = new DocumentView({ model: doc });
まずは Underscore.js 標準のものが簡単。JST や Hanlderbars.js もよく使われる。
<script type="text/template" id="book-template">
<div class="book">
<span class="book-name"><%= title %></span>
by <span class="book-author"><%= author %></span>
</div>
</script>
var BookView = Backbone.View.extend({
render: function () {
var template = $('#book-template').html();
this.$el.html(template(this.model.attributes));
return this;
}
});
jQuery のセレクタとコールバックの羅列。何がしたいのかよくわからない・・・。
// Foo
$('.foo').on('click', function (e) { /* ... */ });
$('.foo bar').on('click', function (e) { /* ... */ });
$('.foo bar').on('mouseover', function (e) { /* ... */ });
// Baz
$('.baz').on('click', function (e) { /* ... */ });
部品ごとにコードを一カ所に集め、それぞれ Backbone.View
を継承したクラスにすると・・・。
var FooView = Backbone.View.extend({
events: {
'click' : 'doSomething',
'click .bar' : 'toggleBarState',
'mouseover .bar' : 'makeBarRed'
},
doSomething: function (e) { /* ... */ },
toggleBarState: function (e) { /* ... */ },
makeBarRed: function (e) { /* ... */ }
});
var BazView = Backbone.View.extend({
events: {
'click' : 'postData'
},
postData: function (e) { /* ... */ }
});
var fooView = new FooView({ el: $('.foo') });
var bazView = new BazView({ el: $('.baz') });
jQuery セレクタ/コールバックの羅列に比べて、どこで何をしているかわかりやすくなる。
events
を使うと、担当する DOM 要素とその子要素へのイベントハンドラーが簡単に登録できる。this
もバインド済。
var FooView = Backbone.View.extend({
greet: 'Hello!'
events: {
'mouseover' : 'smile',
'click .hello-button' : 'sayHello'
},
smile: function () {
},
sayHello: function () {
// this は View のインスタンス!
console.log(this.greet);
}
});
jQuery だけでも、委譲はできる。
.hello-button
は最初からある必要はない。親要素にイベントリスナーを仕掛けておき、子から bubbling してくるイベントを監視。
$('.foo').on('click', '.hello-button', function (e) {
var buttonText = $(e.currentTarget).text();
alert(buttonText + ' was clicked.');
});
$('<button />').addClass('hello-button').text('Hello!').appendTo('.foo');
$('<button />').addClass('hello-button').text('Hi!').appendTo('.foo');
古い IE で submit
など、できないイベントもあり。
グローバルの $
を使うと、View の中だけでなく、画面中どこでも操作できてしまう。 this.$el
/this.$
で、this.el
またはその子供だけを操作するようにすると、責任範囲が明確になる。
// View のメソッドの中で・・・
this.$el.css('color', '#f00');
// $(this.el).css('color', '#f00'); と同じ
this.$('li').hide();
// $(this.el).find('li').hide(); と同じ。
Model
を経由する。View
から 子 View
のメソッドを直接呼ぶ。View
が子 View
のイベントを監視する。View
同士を Mediator
でつなぐ。Router
を使う。URL とアクションをマッピング。
var Router = Backbone.Router.extend({
routes: {
'help': 'help', // #help
'search/:query': 'search', // #search/kiwis
'search/:query/p:page': 'search' // #search/kiwis/p7
},
help: function() { /* ... */ },
search: function(query, page) { /* ... */ }
});
var router = new Router();
Backbone.history.start(); // { pushStat: true } なら進む・戻る
router.navigate('help', {trigger: true});
既存システムでクライアントサイドに Model
を導入するには、けっこうな変更が必要。REST API を用意して、クライアントでテンプレート使ってレンダリングして・・・。
Router
も Single Page Application でないとあまり・・・。
まずは View
から使ってみましょう。