次のようなknockout.jsテンプレートがあるとします。
<script type="text/html" id="mytemplate">
<label for="inputId">Label for input</label>
<input type="text" id="inputId" data-bind="value: inputValue"/>
</script>
このテンプレートをページの複数の場所にレンダリングすると、同じIDの複数の入力(および同じfor値の複数のラベル)が表示され、悪い結果になります。特に、IDに依存するすべてのコードが正しく機能しない可能性があります(私の場合、同じIDを持つ複数の入力によって混乱するjquery.infieldlabelプラグインを使用しています)。この問題を解決する方法は、テンプレートにバインドするモデルに一意のid属性を追加することです。
<script type="text/html" id="mytemplate">
<label data-bind="attr: {for: id}>Label for input</label>
<input type="text" data-bind="attr: {id: id}, value: inputValue"/>
</script>
これは機能しますが、他の用途には使用されないこの人工ID属性をモデルに含める必要があるため、あまりエレガントではありません。ここにもっと良い解決策があるのだろうか。
フィールドがバインドされる順序に依存しない別の方法は、データ自体にid
プロパティをバインドに設定させることです。これは、監視可能である必要があります。
ko.bindingHandlers.uniqueId = {
init: function(element, valueAccessor) {
var value = valueAccessor();
value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
element.id = value.id;
},
counter: 0,
prefix: "unique"
};
ko.bindingHandlers.uniqueFor = {
init: function(element, valueAccessor) {
var value = valueAccessor();
value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
element.setAttribute("for", value.id);
}
};
次のように使用します。
<ul data-bind="foreach: items">
<li>
<label data-bind="uniqueFor: name">Before</label>
<input data-bind="uniqueId: name, value: name" />
<label data-bind="uniqueFor: name">After</label>
</li>
</ul>
サンプル: http://jsfiddle.net/rniemeyer/JjBhY/
Observable関数にプロパティを追加することの良い点は、プロパティをJSONに変換してサーバーに送り返すと、observableがラップされていない値に変わるため、自然に消えてしまうことです。
私は過去にこのようなことをしました:
ko.bindingHandlers.uniqueId = {
init: function(element) {
element.id = ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
},
counter: 0,
prefix: "unique"
};
ko.bindingHandlers.uniqueFor = {
init: function(element, valueAccessor) {
var after = ko.bindingHandlers.uniqueId.counter + (ko.utils.unwrapObservable(valueAccessor()) === "after" ? 0 : 1);
element.setAttribute("for", ko.bindingHandlers.uniqueId.prefix + after);
}
};
次のように使用します。
<ul data-bind="foreach: items">
<li>
<label data-bind="uniqueFor: 'before'">Before</label>
<input data-bind="uniqueId: true, value: name" />
<label data-bind="uniqueFor: 'after'">After</label>
</li>
</ul>
したがって、バインディング自体の状態を維持して、ko.bindingHandlers.uniqueId.counter
をインクリメントします。次に、uniqueFor
バインディングは、正しいIDを取得する方法を知るために、フィールドの前か後かを知る必要があります。
ここのサンプル: http://jsfiddle.net/rniemeyer/8KJD3/
ラベルがフィールドの近くにない場合(おそらくテーブルの別々の行で各ラベルの前にバインドされた複数の入力)、別の戦略を検討する必要があります。
選択した回答に返信できませんが、入力値ごとに複数の一意のIDをサポートするコードの拡張バージョンがあります。それは私のブログ http://drewp.quickwitretort.com/2012/09/18/ にあり、ここで繰り返されます:
ko.bindingHandlers.uniqueId = {
/*
data-bind="uniqueId: $data" to stick a new id on $data and
use it as the html id of the element.
data-which="foo" (optional) adds foo to the id, to separate
it from other ids made from this same $data.
*/
counter: 0,
_ensureId: function (value, element) {
if (value.id === undefined) {
value.id = "elem" + (++ko.bindingHandlers.uniqueId.counter);
}
var id = value.id, which = element.getAttribute("data-which");
if (which) {
id += "-" + which;
}
return id;
},
init: function(element, valueAccessor) {
var value = valueAccessor();
element.id = ko.bindingHandlers.uniqueId._ensureId(value, element);
},
};
ko.bindingHandlers.uniqueFor = {
/*
data-bind="uniqueFor: $data" works like uniqueId above, and
adds a for="the-new-id" attr to this element.
data-which="foo" (optional) works like it does with uniqueId.
*/
init: function(element, valueAccessor) {
element.setAttribute(
"for", ko.bindingHandlers.uniqueId._ensureId(valueAccessor(), element));
}
};
これで、自動IDを持つ1つのレコードに複数のラベル付きチェックボックスを設定できます。
<li data-bind="foreach: channel">
<input type="checkbox" data-which="mute" data-bind="uniqueId: $data, checked: mute">
<label data-which="mute" data-bind="uniqueFor: $data">Mute</label>
<input type="checkbox" data-which="solo" data-bind="uniqueId: $data, checked: solo">
<label data-which="solo" data-bind="uniqueFor: $data">Solo</label>
</li>