画面遷移のあるアプリケーションで、Backbone.jsを使用していると、スライドビューの自動スライドでページが飛んだり、アコーディオンのビューが広がらず閉まったままになることがあります。UIのアクションがBackbone.ModelやCollectionとひもづいている場合に発生します。最初に遷移したときは問題ないのに、再び戻ってくると発生し、画面遷移する度に症状はひどくなっていき、最終的には、スライドは最初と最後のスライドしか表示されないとかになったりします。
前提環境
- 画面遷移の度に遷移先のページに該当するBackbone.Viewを再生成
- jQuery.MobleのchangePageで画面遷移
- Backbone.Model/Collection/Events等を使って、データ変更イベントをバインドしている
- サーバサイドによるHTMLのレンダリングはせず、Ajaxでデータ取得してJSでレンダリング
解決策
Backbone.jsのバージョンが古いですが、解決策はここに書いてあります。
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
- 画面切り替えの度に、Backbone.Model/Collectionのイベントのひも付けを解除する
例えば、jQuery.Mobileのページ切り替えイベントを使用している場合ですが、
ViewControllerみたいなものを用意して、画面遷移して不要になったViewに紐づいているEventの後始末や現在アクティブなViewの保存を行うとかすればいいと思います。
コード:
Eventsの後始末を行うメソッドを定義して、アプリケーション共通で使うベースのViewに組み込みます。
ベースのコンストラクタ/関数定義
var module = {};
/*!
* Helper functions.
*/
module.Helpers = {
nl2br: function(string) {
return string && string.replace(/\r\n|\r|\n/g, '<br/>') || '';
},
zeroPadding: function(number, digits) {
var padding = '', i = 0, l = 0;
for (i = 0, l = digits - 1; i < l; i++) {
padding += '0';
}
return (padding + number).slice(-1 * digits);
},
basename: function(path, suffix) {
[… snip …]
};
/*!
* Application base view.
*/
// Backbone.Viewを直接使わず、これを使う
module.BaseView = Backbone.View.extend(_.extend({}, module.Helpers, {
// Should be override.
destruct: function() {
this.unregisterEvents();
},
// Should be override.
unregisterEvents: function() {
this.off(); // Viewにひもづけたイベントを解除する
this.undelegateEvents();
return this;
}
}));
// いらなくなったviewの後始末と現在のViewを管理するController.
module.ViewController = Backbone.Model.extend({
initialize: function() {
_.bindAll(this);
},
setView: function(view) {
if (this.has('currentView')) {
this.get('currentView').unregisterEvents();
}
this.set({currentView: view});
},
getViewElement: function() {
var view = this.get('currentView');
return view && view.$el;
},
renderView: function(options) {
this.get('currentView').render(options);
}
});
###個別のView定義
個別のViewに紐づくイベントを解除するために、"unregisterEvents"に後始末の処理を書きます。
- まず階層管理している子Viewで後始末するメソッドを先に呼び出します。
- 子Viewと親View間でバインディングしている場合は、それを解除します。
- 最後にBackbone.Model/Collectionのイベントとのバインディングを解除します。
var MainArticleView = module.BaseView.extend({
unregisterEvents: function() {
// 1. Viewを階層管理している場合、子Viewで後始末するメソッドを先に呼び出す
this.listView.unregisterEvents();
this.slideView.unregisterEvents();
// 2. このViewと子Viewとの間でバインディングしている場合は、それも解除
this.listView.off();
this.slideView.off();
// 3. 自分のを解除
this.bgmManager.off('end error', this.onRecoveryBGM);
this.collection.off('reset', this.rebuild); // Backbone.Collectionに紐づけていたイベントハンドラを解除
return module.BaseView.prototype.unregisterEvents.apply(this, arguments);
},
initialize: function(options) {
_.bindAll(this);
this.bgmManager = options.bgm;
this.listView = new module.BaseView({el: this.$('.list-a'), collection: this.collection});
this.slideView = new module.BaseView({el: this.$('.slide-b')});
},
registerEvents: function(options) {
this.bgmManager.on('end error', this.onRecoveryBGM);
this.listView.on('selected', this.onChangeSlide);
[… snip …]
}
});
jQuery Mobileでページ切り替えイベントを使ってViewを生成する場合
jQuery Mobileのページ切り替えイベントの"pagebeforeshow"でviewを生成し、"pageshow"でレンダリングするとします。生成したviewを、viewControllerにセットすると、古いviewに紐づいていたイベントが一括解除されます。
var showPage = function() {
viewController.renderView();
};
$(document).on('pagebeforeshow', '#main', funciton() {
var view = new MainArticleView({
el: $('#main article'),
collection: collection,
bgm: bgmManager
});
viewController.setView(view);
}).on('pagebeforeshow', '#config', function() {
var view = new ConfigView({
el: $('#config article')
});
viewController.setView(view);
}).on('pageshow', '#main', showPage).on('pageshow', '#config', showPage);
##参考