選択されると、要素を作るUIが作りたいとします。本来は、その要素数分、ボタンを並べれば良いのですが、スペースの関係でボタンを隠しておきたい場合があります。CSSをがんばるのもあれなので、コンボボックスにしておいて、それが選択されたらボタンが押されたようにふるまうようにしてみようと思います。
例えば、シムシティで、序盤のチュートリアルからよく使う「住宅地区」「商業地区」「工業地区」「警察」「消防」「発電所」「道路」あたりは専用のボタンを置いておくとして、ちょっと大きくならないと使わない娯楽施設系も全部表示されていると邪魔ですよね。そういうあまり使わない建物を隠しておく感じのUIです。
だめな例
<option>
タグのselected
属性が選択されている<option>
を表すので、先頭要素だけ常にtrueならいけるでしょうか?シムシティを例にあげといてなんですが、スーパーファミコン時代のしかやったことがなくて詳しくないので名前の一覧はここを参照しました。
const labels = [
'(娯楽施設建造)',
'ホテル', '円形劇場', 'エキスポセンター', 'グローブ座', 'スタジアム',
'シドニー・オペラハウス', '観覧車', '相撲会館', 'オスロ・オペラハウス'];
return m('select', {
onchange: m.withAttr('selectedIndex', ctrl.selected),
}, labels.map((label, index) => {
return m("option", {selected: index === 0}, label);
}));
コレではダメです。というのも、表示後にユーザが操作すると、実際のDOMの選択状態が変更されるだけで、仮想DOM上はMithrilが「先頭要素が選択されている」と思い続けている状態になります。そのため、再更新が走ったとしても、「変更がない」と判断されてしまいます。
OKな例
次のような関数を作り、実DOM上でインデックスをリセットすればOKです。
function resetIndex(element, isInitialized) {
if (isInitialized) {
window.requestAnimationFrame(() => {
element.selectedIndex = 0;
});
}
}
const labels = [
'(娯楽施設建造)',
'ホテル', '円形劇場', 'エキスポセンター', 'グローブ座', 'スタジアム',
'シドニー・オペラハウス', '観覧車', '相撲会館', 'オスロ・オペラハウス'];
return m('select', {
onchange: m.withAttr('selectedIndex', ctrl.selected),
config: resetIndex
}, labels.map((label, index) => {
return m("option", label);
}));
これはバグではなくて規定の動作
さて、これはバグでしょうか?そうではありません。Mithrilはある程度の実DOM上だけの状態は仮想DOMと切り離してそのまま放置しています。そうでなければ、テキスト入力フォームが毎回再生成されてしまいます。IMEがONの状態で入力中のテキストなどはJavaScriptで取得したり復元したりはできないため、仮想DOMの再生成が厳しく行われると困ったことになってしまいます。そのため、これはこれでいいのです。