Subscribed unsubscribe Subscribe Subscribe

@kyanny's blog

Write down what I learnt. Opinions are my own.

Backbone.Validation の valid/invalid コールバック関数から Marionette.View の ui を触るときの注意点

  • フォームと確認画面でそれぞれ Marionette.ItemView を使い、ボタンクリックで切り替える
  • view のインスタンスはフォーム・確認画面それぞれを表示する際に新しく new して show する(古い view インスタンスは破棄されることを期待)
  • model は事前にインスタンスを作っておいて view を new するとき使い回す
  • Backbone.Validation を使っていてフォーム画面で入力値チェックに使う

みたいなコードを書いたところ、入力して送信ボタン押す -> 確認画面で「フォーム画面に戻る」ボタン押す -> もう一度送信ボタン押す -> Bacbone.Validation.bind の引数で定義できる valid コールバック関数のなかで view.ui が jQuery オブジェクトになっておらず(ui.foo が '.foo' みたいな文字列のまま) ui.foo.show() とかが死ぬ、ということがあった。

kyanny/backbone-marionette-stickit-validation-example · GitHub

原因は model から古い view への参照が残ってしまっていたことで、 Backbone.Validation.bind すると view インスタンスと関連づけられている model インスタンスの associatedViews に view インスタンスが突っ込まれる。この古い view インスタンスは Marionette の show で別の view インスタンスが表示されるときに destroy され、その際 unbindUIElements されるが、 associatedViews からは消えないので valid コールバックがそれを受け取ってしまう。

今回の実装では古い view インスタンスは都度破棄されて新しい view インスタンスに入れ替わることを期待しており、新しい view インスタンスが show されるときに bindUIElements もされるんだから this.ui が jQuery オブジェクトになってないはずがない...と悩んでしまった。

view の onDestroy で Backbone.Validation.unbind(this) してやれば associatedViews から古い view インスタンスが消えるので、 associatedViews には常に最新の(生きている) view インスタンス一個だけが存在することになり、 this.ui も期待どおりに使える。

コミットログも参照。


f:id:a666666:20150503044129p:plain フォーム画面で送信ボタン押す -> 確認画面で戻るボタン押す -> フォーム画面でもう一度送信ボタンを押したところ。 this.ui.alert が jQuery オブジェクトのつもりで触って例外が出る。この時点で model.associatedViews には view インスタンスが二つある(view5 と view11)

f:id:a666666:20150503044139p:plain 例外が出たところ。

f:id:a666666:20150503044149p:plain 最初にフォーム画面が表示された時点。 associatedViews は view5 一個のみ。

f:id:a666666:20150503044200p:plain onDestroy でちゃんと unbind するように修正したあとで、送信 -> 戻る してフォーム画面が再表示された状態。 associatedViews は view11 一個のみ(view5 への参照は残っていない)


Marionette の bindUIElements とかにブレークポイントを張って追ったりと見当違いな調べ方をしていたが bind という名称から連想して閃いて Backbone.Validation のソースを読みつつ追ったらあっさり原因がわかった。ソースを読むいい機会になった。ただし元々これで困っていた仕事のコードのほうは、 this.ui を valid/invalid コールバックでしか使わないのに列挙する数が多いのでなんか定義が重すぎて邪魔かなぁ、と思い view.$el.find("[name=#{attr}]") で触ることにしてやり過ごしてしまった。