问题描述
我正在生成一个基于 ko observable 的 datalist
选项.
I'm generating a datalist
options based on a ko observable.
function Company(company) {
this.id = company.Id;
this.name = company.Name;
this.state = company.State.name;
}
var self = this;
self.companies = ko.observable();
$.get('...', {}, function(result) {
ko.utils.arrayForEach(result, function(company) {
self.companies.push(new Company(ko.toJS(company)));
});
});
HTML:
<input type="text" data-bind="value: selectedCompany" list="companiesList" />
<datalist id="companiesList" data-bind="foreach: companies">
<option data-bind="value: name, text: state"></option>
</datalist>
好的,这就是交易:在 Company
函数中,我存储了该公司的 Id.我想做的事?能让我将 Id 值链接到 selectedCompany 变量而不是名称的东西?我想显示 name 属性,但我需要存储 Id.
Ok, here's the deal: in the Company
function, I store the Id from that company. What I want to do? Something that able me to link the Id value to selectedCompany variable instead the name? I want to display the name property, but I need to store the Id.
有没有办法做到这一点?谢谢大家!
Is there a way to do this?Thank you all!
推荐答案
由于目前没有 HTML5 的 Knockout 绑定,我做了一个.
Since there is currently no Knockout binding for HTML5 <datalist>
, I made one.
我试图从 select
绑定中尽可能多地借用,因此支持 options
、optionsText
和 选项值
.您可以通过 value
指定目标 observable.
I tried to borrow as much as I could from the select
binding, so there is support for options
, optionsText
and optionsValue
. You can specify a target observable via value
.
ko.bindingHandlers.datalist = (function () {
function getVal(rawItem, prop) {
var item = ko.unwrap(rawItem);
return item && prop ? ko.unwrap(item[prop]) : item;
}
function findItem(options, prop, ref) {
return ko.utils.arrayFirst(options, function (item) {
return ref === getVal(item, prop);
});
}
return {
init: function (element, valueAccessor, allBindingsAccessor) {
var setup = valueAccessor(),
textProperty = ko.unwrap(setup.optionsText),
valueProperty = ko.unwrap(setup.optionsValue),
dataItems = ko.unwrap(setup.options),
myValue = setup.value,
koValue = allBindingsAccessor().value,
datalist = document.createElement("DATALIST");
// create an associated <datalist> element
datalist.id = element.getAttribute("list");
document.body.appendChild(datalist);
// when the value is changed, write to the associated myValue observable
function onNewValue(newVal) {
var dataItems = ko.unwrap(setup.options),
selectedItem = findItem(dataItems, textProperty, newVal),
newValue = selectedItem ? getVal(selectedItem, valueProperty) : void 0;
if (ko.isWriteableObservable(myValue)) {
myValue(newValue);
}
}
// listen for value changes
// - either via KO's value binding (preferred) or the change event
if (ko.isSubscribable(koValue)) {
koValue.subscribe(onNewValue);
} else {
ko.utils.registerEventHandler(element, "change", function () {
onNewValue(this.value);
});
}
// init the element's value
// - either via the myValue observable (preferred) or KO's value binding
if (ko.isObservable(myValue) && myValue()) {
element.value = getVal(findItem(dataItems, valueProperty, myValue()), textProperty);
} else if (ko.isObservable(koValue) && koValue()) {
onNewValue(koValue());
}
},
update: function (element, valueAccessor) {
var setup = valueAccessor(),
datalist = element.list,
dataItems = ko.unwrap(setup.options),
textProperty = ko.unwrap(setup.optionsText);
// rebuild list of options when an underlying observable changes
datalist.innerHTML = "";
ko.utils.arrayForEach(dataItems, function (item) {
var option = document.createElement("OPTION");
option.value = getVal(item, textProperty);
datalist.appendChild(option);
});
ko.utils.triggerEvent(element, "change");
}
};
})();
我希望我大部分都做对了.欢迎提出更正和改进建议.
I hope I got most of this right. Corrections and improvement suggestions are welcome.
你会像这样使用它:
<input list="company" data-bind="datalist: {
options: companies,
optionsValue: 'id',
optionsText: 'name',
value: selectedCompany
}" />
注意事项.
- 无需在 HTML 中创建单独的
.自定义绑定会为您完成这项工作.
- 但是,您必须提供
元素的
list=""
属性.到目前为止,我找不到在 JavaScript 中动态设置它的方法. value
是可选的,但如果你提供它,它必须是一个可观察的.- 您仍然可以在
datalist
之外使用原始的value
绑定.它将包含显示的任何文本(没有意外).但是,写入内置值也会更新
datalist
值,反之亦然. datalist
值具有优先权,将在视图模型初始化时覆盖内置值.之后他们会保持同步.options
应该是一个对象数组(普通的或可观察的)——在这种情况下是公司).optionsText
和optionsValue
是应该映射到选项属性的字符串.- 您不必使用
optionsValue
— 在这种情况下,整个对象(单个公司)将存储在value
中.我宁愿只存储 ID. - 目前设置对
change
事件做出反应.这意味着您的视图模型在您离开输入字段之前不会更新. - 在 Chrome 32、YMMV 上测试.正如我所说,欢迎提出意见和更正.
- There is no need to create a separate
<datalist>
in the HTML. The custom binding does that for you. - You must, however, supply the
list=""
attribute of the<input>
element. I found no way of dynamically setting that in JavaScript so far. value
is optional, but if you supply it, it must be an observable.- You can still use the original
value
binding outside ofdatalist
. It will contain whatever text the<input>
displays (no surprises there). However, writing to the built-in value also updates thedatalist
value, and the other way around. - The
datalist
value has precedence and will overwrite the built-in value upon view model init. After that they stay in sync. options
should be an array (plain old or observable) of objects — companies in this case).optionsText
andoptionsValue
are strings that should map to properties on your options.- You do not have to use
optionsValue
— in that case the entire object (a single company) would be stored invalue
. I would prefer that over storing only the ID. - Currently the set-up reacts to the
change
event. That means your view model won't update until you leave the input field. - Tested on Chrome 32, YMMV. As I said, comments and corrections are welcome.
下面是一个演示,复制自原始fiddle.
Below is a demo, copied from the original fiddle.
function main() {
function Company(company) {
this.id = company.Id;
this.name = company.Name;
this.state = company.State.name;
}
function ViewModel(sampleData) {
var self = this;
self.companies = ko.observableArray();
ko.utils.arrayForEach(sampleData, function (companyData) {
self.companies.push(new Company(companyData));
});
// KO's "value" binding _can_ supply a start value
self.val = ko.observable("Microsoft");
// ... but it is overridden by datalist's "value" binding - in any case you can use both
self.selectedCompany = ko.observable(1);
}
// -----------------------------------------------------------------------------------
ko.applyBindings(new ViewModel([{
Id: 1,
Name: "Apple",
State: {
name: "California"
}
}, {
Id: 2,
Name: "Microsoft",
State: {
name: "Washington"
}
}, {
Id: 3,
Name: "IBM",
State: {
name: "New York"
}
}]));
}
// -----------------------------------------------------------------------------------
ko.bindingHandlers.datalist = (function () {
function getVal(rawItem, prop) {
var item = ko.unwrap(rawItem);
return item && prop ? ko.unwrap(item[prop]) : item;
}
function findItem(options, prop, ref) {
return ko.utils.arrayFirst(options, function (item) {
return ref === getVal(item, prop);
});
}
return {
init: function (element, valueAccessor, allBindingsAccessor) {
var setup = valueAccessor(),
textProperty = ko.unwrap(setup.optionsText),
valueProperty = ko.unwrap(setup.optionsValue),
dataItems = ko.unwrap(setup.options),
myValue = setup.value,
koValue = allBindingsAccessor().value,
datalist = document.createElement("DATALIST");
// create an associated <datalist> element
datalist.id = element.getAttribute("list");
document.body.appendChild(datalist);
// when the value is changed, write to the associated myValue observable
function onNewValue(newVal) {
var dataItems = ko.unwrap(setup.options),
selectedItem = findItem(dataItems, textProperty, newVal),
newValue = selectedItem ? getVal(selectedItem, valueProperty) : void 0;
if (ko.isWriteableObservable(myValue)) {
myValue(newValue);
}
}
// listen for value changes
// - either via KO's value binding (preferred) or the change event
if (ko.isSubscribable(koValue)) {
koValue.subscribe(onNewValue);
} else {
ko.utils.registerEventHandler(element, "change", function () {
onNewValue(this.value);
});
}
// init the element's value
// - either via the myValue observable (preferred) or KO's value binding
if (ko.isObservable(myValue) && myValue()) {
element.value = getVal(findItem(dataItems, valueProperty, myValue()), textProperty);
} else if (ko.isObservable(koValue) && koValue()) {
onNewValue(koValue());
}
},
update: function (element, valueAccessor) {
var setup = valueAccessor(),
datalist = element.list,
dataItems = ko.unwrap(setup.options),
textProperty = ko.unwrap(setup.optionsText);
// rebuild list of options when an underlying observable changes
datalist.innerHTML = "";
ko.utils.arrayForEach(dataItems, function (item) {
var option = document.createElement("OPTION");
option.value = getVal(item, textProperty);
datalist.appendChild(option);
});
ko.utils.triggerEvent(element, "change");
}
};
})();
main();
.hint {
color: silver;
font-size: 80%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<input list="company" type="text" placeholder="Select a company..." data-bind="value: val, datalist: {
options: companies,
optionsValue: 'id', /* try removing 'optionsValue' and see what happens to the view model */
optionsText: 'name',
value: selectedCompany
}" />
<span class="hint">(note the "change" event occurs after the field loses focus!)</span>
<hr />
<p>View Model:</p>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
这篇关于KnockoutJS - 将值从输入链接到数据列表值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!