Chaplin.jsを試している

Brunchで生成されるスキャフォールドよくわからんわーっていうのはChaplin.jsがわからんわーっていうことと同義だった。

Hello Worldのサンプルostioのコードを読んでみたんだけど、いまいちわかってないところがある(HandlebarとかStylusをどこで組み込んでいるのかとか)ので、自分で一つ書いてみないとダメだなぁ。

Chaplin.jsとは

いまのとこの理解だと、Chaplin.jsというのはBackbone.jsを土台にしたフレームワーク。backbone.layoutmanagerはBackbone.jsの拡張っぽいなぁと思ったがこっちはフレームワークな感じなので規約とかお作法っぽいものが多い。

  • View,Mode,Utilsのディレクトリ構造は大体決まっている。
  • View,Modelにはbaseっていうディレクトリが掘ってあって、共通の処理を書く。ほんでもって継承する。
  • Layoutはbackbone.layoutmanagerとは異なり、Viewを継承していない。

他にドキュメントからだと、

  • Applicationクラスは単なるブートストラップ
  • mediatorがpub/subを司る
  • ControllerがModelと対応するViewをインスタンス化する

という感じで役者が増えている。

grunt-bbbでtodoリストをつくってみた

Yeomanはちょっとデカすぎるなぁってことで、Yeomanよりは小さめな感じのgrunt-bbbをいじってみた。

todomvc用のスキャフォールドが用意されているので

bbb init; bbb init:todomvc

というコマンドが使えるんだけど、これは動かない

というわけでbbbのお作法の勉強も兼ねて自分で最初から書いてみた。

mkdir bbbtodos
cd bbbtodos
bbb init
bbb init:module todo

とかこんな感じで書いていけばいい。

bbbtodos

  • backbone-localstorages.jsをvendor/jam以下に置いてしまったがまぁいいかな。
  • 実際のコードはapp/modulesに置くのがお作法らしい。bbb init:moduleもあるしな。
  • bbb serverで127.0.0.1:8000でサーバーが立ち上がるので便利
  • main.jsではrouterの設定をしている
  • app.jsではLayoutの設定とか、useLayoutといった便利関数を追加しているだけ。
  • router.jsが頑張っている

grunt-bbbとは何か?

backbone.layoutmanagerを含んだbackbone.jsのボイラープレートのフレームワーク。

backbone.layoutmanagerはPhilosophyを読めば分かるようにBackbone.Viewを拡張したものでキャッシュ機構を入れたりテンプレートをよろしく管理してくれたりとかそんな感じ。

なので、Backbone.jsで開発している感をだしつつ、弱いところを補ってくれる感じのBackbone.js++って感じのツールかな。

Asynchronous Module Definition

今年はBackbone.jsとかCSS(Stylus)なんかを頑張ろうかなぁと思っている。

理解があいまいなAMD(Asynchronous Module Definition)について調べてみた。

underscore.jsではAMDのサポートが外されている。

npm startのデフォルトはnode server.js

リンク切れてるっていう連絡をもらったので更新しておきました(2018/01/30)。


モデルにgoogle tasksのapiを使ってbackbone.jsでアプリをつくるチュートリアルを読んだ。

package.jsonのscriptsのセクションにstartがないのにnpm startでサーバーが立ち上がるのはなんでじゃろかー?って不思議だったので、ぐぐってみた。

npm startのデフォルトはnode server.jsらしい。

btask

Backbone.sync

RESTfulなAPIじゃない場合とか他のライブラリを使いまわす場合どうするのかなと思って調べてみたらBackbone.syncを上書きすればいいらしい。modelのほうはいじらないでsyncでよろしくやればいいみたい。

Backbone.sync = function(method, model, options) {
  var requestContent = {};
  options || (options = {});

  switch (model.url) {
    case 'tasks':
      requestContent.task = model.get('id');
    break;

    case 'tasklists':
      requestContent.tasklist = model.get('id');
    break;
  }

  switch (method) {
    case 'create':
      requestContent['resource'] = model.toJSON();
      request = gapi.client.tasks[model.url].insert(requestContent);
      Backbone.gapiRequest(request, method, model, options);
    break;

    case 'update':
      requestContent['resource'] = model.toJSON();
      request = gapi.client.tasks[model.url].update(requestContent);
      Backbone.gapiRequest(request, method, model, options);
    break;

    case 'delete':
      requestContent['resource'] = model.toJSON();
      request = gapi.client.tasks[model.url].delete(requestContent);
      Backbone.gapiRequest(request, method, model, options);
    break;

    case 'read':
      request = gapi.client.tasks[model.url].list(options.data);
      Backbone.gapiRequest(request, method, model, options);
    break;
  }
};

Backbone.jsのソースコードを読んだ

日本語のアノテーションがついているソースコードを読んだので、ちょっとわかりにくかったあたりをメモっておく。

コールバックリスト(107行目)

リスト構造にしてあんのかなと思ったがtailってなんだろうと思ったら最後尾を常に指すようにしているらしい。

while (event = events.shift()) {
  list = calls[event];
  node = list ? list.tail : {};
  node.next = tail = {};
  node.context = context;
  node.callback = callback;
  calls[event] = {tail: tail, next: list ? list.next : node};
}

これは最後尾のobjectのnextとtailが同じ物を指すようにしているのでさくっと最後に空のオブジェクトの参照を保持している。全てのコールバックを実行するのはnextでたどればいいようになっている。Spine.jsは普通に配列使っていたけどリストにするメリットはなんかあるのかなぁ。

実際の動きはこんな感じ。

> node = {}
{}
> node.next = tail = {}
{}
> node.context = "ctx1"
'ctx1'
> node.callback = "callback1"
'callback1'
> calls = {tail: tail, next: node}
{ tail: {},
  next: 
   { next: {},
     context: 'ctx1',
     callback: 'callback1' } }
> list = calls
{ tail: {},
  next: 
   { next: {},
     context: 'ctx1',
     callback: 'callback1' } }
> node = list.tail
{}
> node.next = tail = {}
{}
> node.context = "ctx2"
'ctx2'
> node.callback = "callback2"
'callback2'
> calls = {tail: tail, next: list.next}
{ tail: {},
  next: 
   { next: 
      { next: {},
        context: 'ctx2',
        callback: 'callback2' },
     context: 'ctx1',
     callback: 'callback1' } }

tailとnextは同じ空のオブジェクトを指しているので、常に次の参照を保持したまま大きくなっていく。

void 0 (283行目)

void 0はundefinedになる。

> void 0
undefined

success = option.success (342行目)

if (success) success(model, resp);って何やっているのかなぁと思ったのだが、opttion.successを上書きしてしまうので、元々のoption.successを残しておいてコールバックで呼び出しているだけだった。

var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
  if (!model.set(model.parse(resp, xhr), options)) return false;
  if (success) success(model, resp);
};
options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);

},

element (1190行目)

$elとelが存在する。

this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];

ステートフルJavascriptを読んであればそんなに難しくないので、コードリーディングにちょうどいいかも