しるてく

技術的な話をします

Backbone.js × jQuery pjax

はじめに

jquery-pjaxを使っているサイトのメンテナンス性あげるために、Backbone.jsでも入れようかなあと思ったところ、両者のpushState()が被って挙動がおかしくなった。

jquery-pjax

jquery-pjaxjQueryのプラグインで、pushState×ajaxを実装したもの。
他にも、jquery-pjaxライクなプラグインはあるけど、やっぱりこれが一番使いやすい。

(function($) {
  $(document).pjax('a', '#container');
  $(document).on('pjax:success', function() {
    console.log('pjax success!');
  });
})(jQuery);

Backbone.js導入前はこんな感じに使っていた。

Backbone.js

簡単に言うと、javascriptMVCフレームワーク
jQuery($)とunderscore(_)に依存している。

にあるように、Backbone.Routerを使えばBackbone単体でpjaxは実装できる。

$(function() {
  var Router = Backbone.Router.extend({
    routes: {
      "": "index",
      "hoge":  "hoge"
    },
    initialize: function() {
      this.container = $("#container");
    },
    index: function() {
      this.container.load("/");
    },
    hoge: function() {
      this.contaier.load("/hoge");
    }
  });
  window.router = new Router();
  Backbone.history.start({pushState: true});

  $("a").click(function() {
    window.router.navigate(location.pathname.substr(), {
      trigger: true
    });
  });
})();

こんな感じ。
で、実装できるのは良いんだけど、

にも書かれているとおり、jquery-pjaxの至れり尽くせりな感じを捨ててBackboneにごにょごにょ書くのは面倒くさい。pjaxはjquery-pjaxで動かしつつ、Backbone.Routerも使いたい。

問題点

単純に、上のやつを書くだけだと、Backbone.Router.navigateとjquery-pjaxで二重にpushStateされてしまって2回戻らないと前のページに戻れない。
pop時にhistory.go()とかで2個戻るとか、replace: trueとかの組み合わせでなんとかならないかなと思ったけど、それも挙動が怪しくなる。

解決

じゃあもう、navigateでhistoryいじってるところ全部ナシにすればいいんじゃね?という考えに至り、

$(function() {
  var Pjax = Backbone.View.extend({
    el: document,
    events: {
      "pjax:complete": "complete"
    },
    initialize: function() {
      _.bindAll(this);
      this.$el.pjax("a", "#container");
    },
    navigateWithPjax: function(fragment, options) {
      // Backbone.history.navigate で必要な部分を抜き出す
      if (!options || options === true) options = {trigger: options};
      var that = Backbone.history;
      fragment = that.getFragment(fragment || '');
      if (that.history.fragment === fragment) return;
      that.history.fragment = fragment;
      if (options.trigger) that.loadUrl(fragment);
    },  
    conplete: function() {
      this.navigateWithPjax(location.pathname.substr(1), {
        trigger: true
      });
    }
  });

  var Router = Backbone.Router.extend({
    routes: {
      "": "index",
      "hoge":  "hoge"
    },
    initialize: function() {
      this.container = $("#container");
    },
    index: function() {
      this.container.load("/");
    },
    hoge: function() {
      this.contaier.load("/hoge");
    }
  });

  window.router = new Router();
  Backbone.history.start({pushState: true});
  var pjax = new Pjax();
  pjax.complete(); 
})();

やりかたとしてイケてるかは別にしてRouterの機能を使いつつjquery-pjaxで遷移するという当初の予定は達成出来たのでよしとする。

おわり

何か間違いや、より良い方法があれば教えてください。