lxyuma BLOG

開発関係のメモ

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に分けるべき。