Backbone.js Tips And Patterns | Smashing Coding を読みました。
ほとんどは特に真新しくもない当たり前な内容なんですが、 "BROADCAST CUSTOM ERROR EVENT" はいいなと思ったので紹介します。
このパターンは Model で複数の属性のバリデーションを行いたい場面で使うものです。
RETURN AN ERROR OBJECT
まず、普通な validate
の実装です。
validate: function (attrs) {
var errors = [];
if (_.isUndefined(attrs.name)) {
errors.push({
attr: 'name',
message: 'Name is required'
});
}
if (errors.length) return errors;
}
まず Backbone の基本事項として下記を再確認してください:
-
validate
メソッドが真を返すとinvalid
イベントが発生する - 返り値が
invalid
イベントのコールバックに渡される
で、上記のコードサンプルは errors
という配列を作っています。なんでわざわざこんなことをしているのかというと、コールバックに複数の属性の情報を渡すには配列などを利用するしか無いわけです。そして View のコールバックでその配列の値をパースします。
このパターンは validate
メソッドで複数の属性を処理するときによく使われます。前述のサイトでは "RETURN AN ERROR OBJECT" という名前で紹介されています。
この方法で一応複数属性のバリデーションはできるんですが、レンダラが肥大化してしてしまいがち、という問題があります。
initialize: function () {
this.listenTo(this.model, 'invalid', this.renderMessages);
},
renderMessages: function (errors) {
_.each(errors, function (error) {
if (error.attr === 'name') {
this.$('.name-message').html(error.message).show();
}
}, this);
}
ここでは renderMessages
メソッドを定義しています。このメソッドでやるのがエラーメッセージをまとめてリストにして表示する、とかなら単純で大きな問題にはなりませんが、ここでやっているように属性毎にレンダリングする方法を変えたい、とかだと属性が増える度にどんどん肥大化していきます。
そのような問題に対して、個々の属性毎にレンダラを定義して、下記のようにリファクタリングすることもよく行います。
renderMessages: function (errors) {
_.each(errors, function (error) {
if (error.attr === 'name') {
this.renderNameMessage(error.message);
}
}, this);
},
renderNameMessage: function (message) {
this.$('.name-message').html(message).show();
}
BROADCAST CUSTOM ERROR EVENT
この個別レンダラに対応するカスタムイベントを発行するのが "BROADCAST CUSTOM ERROR EVENT" です。
validate: function (attrs) {
if (_.isUndefined(attrs.name)) {
this.trigger('invalid:name', 'Name is required');
}
// 注意:バグがある。詳しくは後述
}
このように invalid:ATTRIBUTE
となるようなイベントを発行します。対応する View では個別レンダラをコールバックに設定します。
initialize: function () {
this.listenTo(this.model, 'invalid:name', this.renderNameMessage);
}
この方法のよいところは、 View で Model の validate
メソッドの返り値の実装を知らなくてもよい点です。例えば今回の場合でいえば、 attr
と message
というプロパティを持つオブジェクトの配列が返される、ということを View が知っていなければなりません。しかし、カスタムイベントでメッセージを渡す方式であれば、属性名の種類さえ知っていればよいので View と Model の結合度はより小さくなります。
2つを組み合わせるのがオススメ
ただ、引用しているブログ記事には重大なバグがあって、それは何かというと、 validate
メソッドが何も返さないのでバリデーションに通ったとみなされてしまう、という点です。なので一つでもバリデーションに失敗したら true
を返すようにする、などの修正が必要です。
ですが、オススメしたいのは "BROADCAST CUSTOM ERROR EVENT" と "RETURN AN ERROR OBJECT" を組み合わせる方式です。
validate: function () {
var errors = [];
if (_.isUndefined(attrs.name)) {
errors.push({
attr: 'name',
message: 'Name is required'
});
this.trigger('invalid:name', 'Name is required');
}
if (errors.length) return errors;
}
何故組み合わせるべきだと思うのか。それは change
と change:ATTRIBUTE
イベントの構造と似せることが可能だからです。
change
と change:ATTRIBUTE
イベントは属性が変更されたときに発生します。複数の属性が変更されたとき、属性毎に change:ATTRIBUTE
イベントが発生した後に change
イベントが発生します。個別対応したければ change:ATTRIBUTE
を監視し、一括で問題無いなら change
を使う、という使い分けがされます。
それと同じことが invalid
と invalid:ATTRIBUTE
でできるようになるわけです。インタフェースが似ているので使い勝手がよさそうです。