のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

jquery.pjax x Backbone.js を試す

週末ですね。こんにちわ。

まえおき

前回のエントリで「Knockback.js試すぜ!」みたいなことを言いましたが撤回します。ドキュメント見て止めました。
理由は以下の二点

  • Backbone.js, Knockout.js知識が必要
  • 日本語での資料が少ない

Backbone.js

というわけで、まずはBackbone.jsをためそうじゃないかと。
大本の目的が、pjaxでページ毎の挙動を制御したいっていう要求があったので、じゃあBackbone.jsだろうと。

http://backbonejs.org/

こんてんつ

  • Initialize
  • Routes
  • View
  • history

Initialize

まずはどうすればいいかの話。
必要最小限では、以下の様な組み合わせるになるのかなと。

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="/plugin/underscore-1.4.4.min.js"></script>
    <script src="/plugin/backbone-0.9.10.min.js"></script>
    <script src="/plugin/bootstrap2.2.2/bootstrap.min.js"></script>
    <script src="/js/main.js"></script>

jQueryは何はともあれ必要です。
Underscore.jsはBackbone.jsの動作に必要。
Backbone.jsは本体。フルセットです。
デザインを簡略化するためにBootstrapを利用しています。
main.jsに挙動を定義していきます。

Routes

何はともあれ、まずは一番の要求であるページ毎の挙動ですよね。

var myRouter = Backbone.Router.extend({
    "routes" : {
        ''       : 'index',
        "mypage" : 'mypage',
    },
  "index" : function() {
    },
    "mypage" : function() {
    },
});

$(function() {
    var router = new myRpiter();
});

そのうちまとめるとは思いますが、Backbone.jsではオブジェクトの継承にextendメソッドを利用します。
RouterであればBackbone.Routerをextendする。引数にHASH形式でメソッドや初期値等を設定します。

"routes"

HTTP_PATHと動作するメソッドを紐付けています。
shebungを前提として作られているのか、Rootの"/"を省略しています。すなわち、

GET /mypage でアクセスが来たら、myRouter.routes.mypage が呼び出される

actions

ルーティングされて、myRouter.indexが呼び出されたとします。
その時、例えば

GET /?param=value

みたいなリクエストが着ちゃうとコケるようです。

   "routes" : {
       "?param=:value" : 'index',
   },
   "index" : function(value) {
   },

このようにして、置き換えて上げると引数で受け取れるみたいなので、そういう書き方してあげるとよいんではないかと。

instance

new で定義したクラスを呼び出してあげれば、Routingが設定され、リクエストしたパスを見て処理を行なってくれます。
but,このままではpjaxとか使えません。

PJAX

Backbone.jsのRouterにはhistory管理が同梱されていて、簡単にpjaxを実装できるそうです。

以下のような記事を参考にすれば、わりかし簡単にpjaxを実装できると思います。実際出来ました。

http://kennyj-jp.blogspot.jp/2011/07/backbonejspjax.html
http://taiju.hatenablog.com/entry/20120414/1334390163
http://taiju.hatenablog.com/entry/20120414/1334390163

だが私はそれに反論を唱える

重要なのは、Backbone.jsが提供しているのはpushStateとRouter.navigateによるイベント処理だけだということです。

if 非対応ブラウザのでアクセス
if ページのレスポンスに時間がかかる
if 完全なHTMLをレスポンスしてきた
if InternalServerError等の例外

コケたらなにも反応しないボタンになるか、あるいは自前でアレコレと例外処理を用意しなくてはなりません。


・・・・・



ヽ(´Д`ヽ)(/´Д`)/イヤァ~





僕は、jquery.pjaxに甘えたくなりました。

jquery.pjaxとBackbone.jsを連携させることは可能であるか?

https://github.com/defunkt/jquery-pjax

jquery.pjaxはjquery用に書かれたpjaxのpluginです。
pjax処理の周辺でeventをbindしてくれていてhookが非常に簡単。
また、timeoutやlocation.hrefによる遷移などのサポートも充実していて、非常に使い勝手の良いpluginです。

But

jquery.pjaxが良い物なのは、わかる。このブログ開設当初から使っていた。
Backbone.jsはjQueryを使うことに異論を唱えていない。pluginを使ったからどうだということはない。むしろ積極的に使って効率化できるんじゃないかと思う。余談だ。

では何が問題か。
jquery.pjaxもBackbone.Routerもhistoryの操作を行なっている。
重複操作になり得る可能性は無いか?

とりあえず試す

template/layout.html
<script src="/plugin/jquery.pjax.js"></script>
js/main.js
var viewPjax = Backbone.View.extend({
    "events" : {
        "pjax:send"    : 'send',
        "pjax:succes"  : 'success',
        "pjax:complete": 'complete',
        "pjax:timeout" : 'timeout',
    },
    "el" : document,
    "initialize" : function() {
        $(document).pjax('a', pjax_container);
    },
    "send" : function(e) {
        console.log('pjax send');
    },
    "success" : function() {
        cosnole.log('pjax success');
    },
    "complete" : function(e) {
        $('body').trigger('init');
        this._navigate();
        $(window).trigger('load');
    },
    "timeout" : function(e) {
        console.log('pjax timeout');
    },
    "_navigate" : function() {
        window.router.navigate(location.pathname.substr(1), true);
    },
});

$(function(){
    new viewPjax();
    window.router = new myRouter();

    Backbone.history.start({pushState : true});
});

イグザクトリー そのとおりだ

遷移するけど、バックボタンを二回押さないと戻らないという罠にはまった。
おそらくpushStateが二回走っているが為に履歴が重複登録されているんだろうと想定。

対策

試行錯誤しているうちになんか動くようになった。誰か説明してくれ。

var viewPjax = Backbone.View.extend({
    "events" : {
        "pjax:send"    : 'send',
        "pjax:succes"  : 'success',
        "pjax:complete": 'complete',
        "pjax:timeout" : 'timeout',
    },
    "el" : document,
    "initialize" : function() {
        $(document).pjax('a', pjax_container);
    },
    "send" : function(e) {
        console.log('pjax send');
    },
    "success" : function() {
        cosnole.log('pjax success');
    },
    "complete" : function(e) {
        $('body').trigger('init');
        this._navigate();
        $(window).trigger('load');
    },
    "timeout" : function(e) {
        console.log('pjax timeout');
    },
    "_navigate" : function() {
        window.router.navigate(location.pathname.substr(1), {
            "trigger" : true,
            "replace" : true,
        });
    },
});

$(function(){
    new viewPjax();
    window.router = new myRouter();

    Backbone.history.start({pushState : true});
});

まとめ

  • Backbone.jsでRouterによるページ毎の挙動実装ためした
  • Backbone.jsとjquery.pjaxによるpjax実装をためした

いまいち釈然としないものの、ひとまずよくわからないけれどpjax動いてるんだよね。釈然としない。
Backbone.jsについてはちゃんと中身を確認して把握しないと使い物にするには大変だなぁと思いつつ、ここまで実質半日程度だから比較的軽度の勉強で済む予感。大変なのはこれからか。

JSをModelとかViewとかに分離できるのはすごくありがたい。
なにせいままでイベントとロジック祭りで何処に何があるのか非常にわかりにくかった。

今後、Knockbackとかも試して行きたいが、とりあえずBackboneを使い倒せるようになろうと思う。


うおし、尻みよ尻。

2013/03/06追記

この話には続きがあります。

jquery.pjaxとBackbone.jsの並行運用は
そ ん な 簡 単 な 話 じ ゃ な か っ た
http://mizuki-r.hatenablog.com/entry/2013/03/06/093235