Marionette.js(Backbone.js)のチュートリアル with yeoman その3(Marionette.View実装編)
チュートリアルの続き。
さんざん伸ばした挙げ句、ようやくここからが、本番。
MarionetteのViewを使う。
また、長い記事になったので、先にソースあげておく。
これから作るもの
前回、新規作成formを作ったので、
新規作成したtodoを表示するViewを作る。
- liタグで、一つずつのTaskを表示する
- ulタグで、それらを束ねる
liタグ用のView(1つ1つのTask)
generatorをまた使う。
$ yo marionette:itemview task --create-all
--create-allも付けると、templateも作ってくれる。
出来上がったソースに対して
- tagName: liを紐づける
liをクリックしたらfinished:trueにして、線を引きたいので、
- clickされたら、finished:true/falseを切り替える
- model変更したら、finishedに応じたtrue/falseを切り替える
この辺りはいつものbackboneのソース。
define([ 'backbone', 'hbs!tmpl/item/task_tmpl' ], function( Backbone, TaskTmpl ) { 'use strict'; /* Return a ItemView class definition */ return Backbone.Marionette.ItemView.extend({ tagName: "li", initialize: function() { this.changeFinishedStyle(); this.model.on('sync', this.changeFinishedStyle, this); }, template: TaskTmpl, /* Ui events hash */ events: { "click" : "onClickTask" }, onClickTask: function(event) { if(this.model.get('finished')){ this.model.save({"finished": false}) } else { this.model.save({"finished" : true}); } }, changeFinishedStyle: function() { if(this.model.get('finished')){ this.$el.attr('style', 'text-decoration: line-through;') } else { this.$el.attr('style', 'text-decoration: none;') } }, }); });
あわせて、templateもいじっておく。
//templates/item/task_tmpl.hbs'
<span>{{title}}</span>
ul側(親側)のView
次に、collectionViewを準備する。
$ yo marionette:collectionview tasks
出来たソースにitemviewとして、
- tagName:ulタグを指定
- さっき作ったviewを指定する.
define([ 'backbone', 'views/item/task' ], function( Backbone, Task ) { 'use strict'; /* Return a ItemView class definition */ return Backbone.Marionette.CollectionView.extend({ itemView: Task, tagName: "ul" }); });
めっちゃ少なくて済む。
よく書くcollectionに同期してrenderとか書かなくて済むとこんだけで済む。
collectionの追加
またまたgenerator使う
$ yo marinoette:collection tasks
url/modelだけ、指定しておこう。
url: "/api/v1/tasks", model: Task,
htmlの準備
ここでsubmitしたtasksを表示するためのdiv領域を準備する
※この下に、ul > liを広げて行く。まあ、ここをulにしても良いけど、まあ、areaという事で...
diff --git a/app/index.html b/app/index.html @@ -29,6 +29,8 @@ </div> + <div id="tasks"> + </div> <!-- build:js scripts/main.js -->
次に、application.jsでこのdiv領域にtasksViewを紐づける。
- /app/scripts/application.js
App.addRegions({ newTodo : "#new-task", tasks : "#tasks" }); App.addInitializer(function() { this.collection = new TasksCollection(); this.collection.fetch(); App.newTodo.attachView(new NewTaskView({el: App.newTodo.el, collection: this.collection})); App.tasks.show(new TasksCollectionView({collection: this.collection})); }); App.addInitializer( function () { Communicator.mediator.trigger("APP:START"); });
えーっと、ここまでで、多分、その1で保存したデータが初期表示されてるはず。
form送信viewと繋げる
form送信用のviewからは、taskモデルをsaveしてcollectionに追加する
define([ 'backbone', 'communicator', 'models/task' ], function(Backbone, Communicator, Task){ 'use strict'; return Backbone.View.extend({ events: { "keypress": "postTask" }, postTask: function(event){ if(event.keyCode == 13){ var task = new Task({title: this.$el.val()}); task.save(); this.collection.add(task); this.$el.val(''); } } }); });
ここまでで、多分、formに入れると追加/保存が出来てるはず。多分。
filtering
ここからrouterを使って、all/finishedだけを絞り込む画面を作って行く。
画面filter用のaタグを追記する
<div class="hero-unit"> <input type="TEXT" id="new-task" placeholder="ここに何か書いてEnter"></input> <div> <a href="#all">all</a> | <a href="#finished">finished</a> </div> </div>
router
ここから、routerを作る。
$ yo marionette:router tasks
defaultで出てくるのが、Backboneのrouter。
大規模になってからAppRouter使えという事であえて生routerなのだろう。
せっかくなので、今回は、AppRouterに上書きして、Controllerと繋ぐ。
define([ 'backbone' ], function(Backbone){ 'use strict'; return Backbone.Marionette.AppRouter.extend({ /* Backbone routes hash */ appRoutes: { "all" : "all", "finished" : "finished" }, }); });
controller
AppRouterはcontrollerを使うので、controllerを作る
$ yo marionette:controller tasks
allの時は、全部fetch
finishedの時は、finished:trueの物だけfetch。
define([ 'backbone' ], function( Backbone ) { 'use strict'; return Backbone.Marionette.Controller.extend({ initialize: function( options ) { this.collection = options.collection }, all : function(){ this.collection.fetch(); }, finished : function(){ this.collection.fetch({data:{conditions:'{"finished":true}'}}); } }); });
最後に、Appにrouter追加する
App.addInitializer( function () { new TasksRouter({controller: new TasksController({collection: App.collection })}); Backbone.history.start(); });
これで、動いた?
github
ここに、置いた
https://github.com/lxyuma/todo-mario
marionetteのsampleも少ないので、参考迄に。
※marionetteのgeneratorの作者もsampleあるので、参考にすると良い
思った事。
こうして見ると、やっぱり
- CollectionViewのソースの少なさが嬉しい。
生Backboneだと、色々書き方もあったりするので、
そこに時間かかったりするのが、marionetteだと基本的な汎用的な所は書かないで済んだ。
- ItemViewのrender
render書かなくて済む=marionetteのtemplate * renderの仕組みに沿う=迷い無い。
renderの中、ごちゃごちゃ書き換える必要が無いのが良い
- Applicationの中のregion manager
el要素をあえて、Backbone.Viewも外出しにしたので、
こうしてみると、とりあえず、Applicationのregionを見ると、
どこにmarionette適用しているのかが、ぱっと見で判然。素晴らしい。
ViewでもAreaに埋め込んだ方が良いと思う。
- AppRouter
今回のような小さなアプリはBackbone.routerをそのまま使っても良い。
沢山routesが増えてきたら、AppRouterとControllerに分けて、
更にもっと増えたら、複数のAppRouterとControllerに分けるべき。