Componentsの使いどころ
Ractive.jsはテンプレートとその展開先が必要である関係上、必ずテンプレートの外に「囲み要素」が必要になります。
そうなるとこういった場合に問題が生じます。
<ul>
<li>
<select name="viewer">
<option value="1">表示領域Aを表示</option>
<option value="2">表示領域Bを表示</option>
<option value="3">表示領域Cを表示</option>
</select>
</li>
<li>表示領域A</li>
<li>表示領域B</li>
<li>表示領域C</li>
</ul>
セレクトボックスviewerの値の変化によって表示領域ABCの表示を切り替えたいとします。
この場合、どのようにRactiveオブジェクトを構成すればいいのでしょうか。
<li>
を囲むとこうなります。
<ul>
<div id="container">
<script id="template" type="text/ractive">
<li>
<select name="viewer">
<option value="1">表示領域Aを表示</option>
<option value="2">表示領域Bを表示</option>
<option value="3">表示領域Cを表示</option>
</select>
</li>
</sctipt>
</div>
<li>表示領域A</li>
<li>表示領域B</li>
<li>表示領域C</li>
</ul>
このテンプレートをRactiveオブジェクトに結び付けて実行するとどうなるか、もう分かると思います。
<ul>
<div id="container">
<li>
<select name="viewer">
<option value="1">表示領域Aを表示</option>
<option value="2">表示領域Bを表示</option>
<option value="3">表示領域Cを表示</option>
</select>
</li>
</div>
<li>表示領域A</li>
<li>表示領域B</li>
<li>表示領域C</li>
</ul>
<div>
が挟まってしまい絶望に包まれることになる。
ここで<ul>
全体をRactiveオブジェクトにすることで<select>
も表示領域も一括で扱う方法もありますが、そうするとDomの挙動と意味合いを明確に分離できていない、大きくて密結合なモデルになってしまいます。
そこで登場するのがComponentsの概念です。
Componentsの組み方
先ほどのテンプレートを以下の形に分割します。
セレクトボックスと表示領域という2つの概念を固まりに纏めています。
<ul id="listContainer">
<script id="listTemplate" type="text/ractive">
<li>表示領域A</li>
<li>表示領域B</li>
<li>表示領域C</li>
</script>
</ul>
<script id="selectTemplate" type="text/ractive">
<li>
<select name="viewer" value="{{selected}}">
<option value="1">表示領域Aを表示</option>
<option value="2">表示領域Bを表示</option>
<option value="3">表示領域Cを表示</option>
</select>
</li>
</script>
今回はセレクトボックスの領域をComponentsとして扱うことにします。
というわけでまずはセレクトボックスをComponentsとして定義します。
こちらの操作にはRactiveオブジェクトの拡張を定義するRactive.extend()
を利用します。
var SelectComponents = Ractive.extend({
template: '#selectTemplate',
data: {
selected: null
}
});
これでセレクトボックスをコンポーネント化できました。
コンポーネントは展開先が他のRactiveテンプレート内になるので、elの指定は必要ありません。
次に<ul>
全体をRactiveオブジェクトとして起動し、セレクトコンポーネントを埋め込みます。
var ListModel = new Ractive({
el: '#listContainer',
template: '#listTemplate',
data: {
activate: {
A: false,
B: false,
C: false
}
},
components: {
// ここに埋め込む
exselect: SelectComponents
}
});
さて後は仕上げです。
Componentsの展開先はテンプレート内に<コンポーネント名 />と記述することで定義します。
今回の場合は<exselect/>
となります。
ということで一気にモデルの関連性まで組み上げてしまいましょう。
サンプルコード
<ul id="listContainer">
<script id="listTemplate" type="text/ractive">
// Componentsを展開
<exselect />
{{#if activate.A}}
<li>表示領域A</li>
{{/if}}
{{#if activate.B}}
<li>表示領域B</li>
{{/if}}
{{#if activate.C}}
<li>表示領域C</li>
{{/if}}
</script>
</ul>
<script id="selectTemplate" type="text/ractive">
<li>
<select name="viewer" value="{{selected}}">
<option value="1">表示領域Aを表示</option>
<option value="2">表示領域Bを表示</option>
<option value="3">表示領域Cを表示</option>
</select>
</li>
</script>
var SelectComponents = Ractive.extend({
template: '#selectTemplate',
data: {
selected: null
},
onrender: function(){
this.observe('selected', function(v){
// 一旦全部リセット
this._parent.set('activate.A', false);
this._parent.set('activate.B', false);
this._parent.set('activate.C', false);
// 対応する表示領域をONに
if (v === '1') {
this._parent.set('activate.A', true);
}
else if (v === '2') {
this._parent.set('activate.B', true);
}
else if (v === '3') {
this._parent.set('activate.C', true);
}
});
}
});
var ListModel = new Ractive({
el: '#listContainer',
template: '#listTemplate',
data: {
activate: {
A: false,
B: false,
C: false
}
},
components: {
// ここに埋め込む
exselect: SelectComponents
}
});
これでセレクトボックスの選択に合わせて選択領域がそれぞれ表示されるようになります。
注目するべきはSelectComponentsのobserve内で使用しているthis._parent
です。
これで「親」であるListModelにアクセスすることが出来ます。
ただこちら、アンダーバーの付いているプロパティであることから伺うに、もしかするともっと良い親オブジェクトへのアクセス方法があるのかもしれません。
コンポーネントから親Ractiveオブジェクトへアクセスする正しい方法をご存知の方がいらっしゃったら教えてください。