自定义选择列表文本绑定无法使用Knockoutjs中第一个数组项的编辑器



我有一个只读电话号码表。电话号码有一个号码和一种类型(例如移动电话或家庭电话)。每个电话号码都有一个"编辑"按钮,单击该按钮后,您可以编辑电话号码并在模式对话框中键入。我将Knockoutjs用于只读表和编辑器。该表绑定到PhoneVMs的observableArray,编辑器在单个PhoneVM上工作。因为我希望用户在应用他们的任何更改之前必须在模态上单击"确定",所以模态在所选PhoneVM的副本上工作,当他们单击"OK"时,它会替换表绑定到的observableArray中最初单击的PhoneVM。这一切都很好。

现在我需要允许列表中的第一个电话在只读表的同一页上进行编辑(没有模态)。这个想法是为了更容易在工作流程的早期输入第一部手机。所以你可以在页面上输入你的手机,它会自动出现在下面的只读列表中,你也可以像往常一样在模态中编辑它。我原以为淘汰赛会让这件事变得容易,但我遇到了一个障碍。从这一点来看,仅仅展示一个出错的例子会更容易。在这把小提琴里做以下动作:https://jsfiddle.net/ph4mhsof/

  1. 在文本框中编辑电话号码和制表符。请注意,所有手机列表中的第一部手机也会更新
  2. 更改下拉列表中的电话类型。请注意,"所有电话"表中的"类型ID"one_answers"类型名称"都相应地发生了更改
  3. 单击删除第一部手机。第二部手机成为新的第一部手机
  4. 在文本框中编辑电话号码和制表符。请注意,"所有电话"列表中的第一个电话会按预期更新
  5. 更改下拉列表中的电话类型。请注意,只有"所有电话"列表中的"类型ID"会更新。类型名称不会更新

我使用自定义绑定将类型名称绑定到select的文本。该绑定的init函数中的valueAccessor似乎必须专门指向原始的第一个PhoneVM的PhoneTypeName属性,但我需要它指向firstPhone计算的属性的PhoneTypeName。有什么办法解决这个问题吗?

原始jsfiddle的副本:

function phoneListVM() {
var _self = this;
this.phones = ko.observableArray([
new phoneVM(1, "Mobile", "123-234-3456"),
new phoneVM(2, "Home", "654-343-3211")
]);
this.firstPhone = ko.computed(function() {
return _self.phones()[0];
});
}
function phoneVM(typeID, typeName, Number) {
this.PhoneTypeID = ko.observable(typeID);
this.PhoneTypeName = ko.observable(typeName);
this.PhoneNumber1 = ko.observable(Number);
}
ko.bindingHandlers.selectedTextValue = {
init: function(element, valueAccessor) {
var value = valueAccessor();
$(element).change(function() {
value($("option:selected", this).text());
});
},
update: function(element, valueAccessor) {}
};
$(document).ready(function() {
var phoneList = new phoneListVM()
ko.applyBindings(phoneList);
$("button").click(function() {
phoneList.phones.shift();
});
});
.editor{
background-color:rgba(200,200,250, 0.2);
border: 1px solid rgba(0,0,0, 0.2);
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h4>
Edit First Phone
</h4>
<div class="editor">
<p>Phone Number:
<input data-bind="value: firstPhone().PhoneNumber1" />
</p>
<p>Phone Type:
<select data-bind="value:firstPhone().PhoneTypeID, selectedTextValue:firstPhone().PhoneTypeName">
<option value="0"></option>
<option value="1">Mobile</option>
<option value="2">Home</option>
</select>
</p>
</div>
<h4>
All Phones
</h4>
<table>
<thead>
<tr>
<th>Type ID</th>
<th>Type Name</th>
<th>Number</th>
</tr>
</thead>
<tbody data-bind="foreach:phones">
<tr>
<td><span data-bind="text:PhoneTypeID"></span></td>
<td><span data-bind="text:PhoneTypeName"></span></td>
<td><span data-bind="text:PhoneNumber1"></span></td>
</tr>
</tbody>
</table>
<button type="button">
Remove First Phone
</button>

在我看来,自定义绑定有些过头了。我已经用一些方法和专门选择的可观察对象更新了你的代码,这样你就可以随时知道选择了哪部手机。

如果这就是你想要的,请告诉我。

function phoneListVM() {
var _self = this;
this.phones = ko.observableArray([
	// Removed typeName
new phoneVM(1, "123-234-3456"),
new phoneVM(2, "654-343-3211")
]);

// Observable to see which phone is currently selected
this.SelectedPhone = ko.observable(_self.phones().length > 0 ? _self.phones()[0] : '');

// Allow editing whichever phone they want
this.EditPhone = function(obj) {
_self.SelectedPhone(obj);
};

// Remove first phone and check if there are any more phones, if so add it to the selected phone
this.RemoveFirstPhone = function() {
var firstPhone = _self.phones()[0];
if(firstPhone) {
_self.phones.remove(firstPhone);
_self.SelectedPhone(_self.phones().length > 0 ? _self.phones()[0] : '');
}
}
}
// Removed typeName and made it computed. Could be replaced with some lodash _.find if you are storing an array of types in the global space
function phoneVM(typeID, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
this.PhoneTypeName = ko.computed(function() {
switch (self.PhoneTypeID().toString()) {
case '1':
return 'Mobile';
break;
case '2':
return 'Home';
break;
}
});
}
$(document).ready(function() {
var phoneList = new phoneListVM()
ko.applyBindings(phoneList);
});
.editor {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h4>
Edit First Phone
</h4>
<div class="editor" data-bind="with: SelectedPhone, visible: SelectedPhone()">
<p>Phone Number:
<input data-bind="value: PhoneNumber1" />
</p>
<p>Phone Type:
<select data-bind="value:PhoneTypeID">
<option value="0"></option>
<option value="1">Mobile</option>
<option value="2">Home</option>
</select>
</p>
</div>
<h4>
All Phones
</h4>
<table>
<thead>
<tr>
<th>Type ID</th>
<th>Type Name</th>
<th>Number</th>
</tr>
</thead>
<tbody data-bind="foreach:phones">
<tr>
<td><span data-bind="text:PhoneTypeID"></span></td>
<td><span data-bind="text:PhoneTypeName()"></span></td>
<td><span data-bind="text:PhoneNumber1"></span></td>
<td>
<button data-bind="click: $root.EditPhone">
Edit
</button>
</td>
</tr>
</tbody>
</table>
<button type="button" data-bind="click: RemoveFirstPhone, visible: phones().length > 0">
Remove First Phone
</button>

我同意另外两个答案,即customBinding是过度工作。但正如你所说,你不能轻易地更改代码,我将向你展示你的问题。

在自定义绑定声明中,您刚刚定义了init函数,但将update函数留空。这就是问题所在。当您对选择框进行更改时,change事件会被激发,但没有事件处理程序,因此不会发生任何事情。

事实上,您的自定义绑定已成功地将change事件处理程序添加到选择框中。这就是为什么在删除第一个电话号码之前,一切都很好。但当您删除第一个手机号码时,事件处理程序会被删除,因为您只能添加一个事件处理程序来选择change事件的框。

解决方案是:init函数留空,并将当前所有init函数内容移动到update函数,如下所示。

ko.bindingHandlers.selectedTextValue = {
init: function(element, valueAccessor) {
},
update: function(element, valueAccessor) {
var value = valueAccessor();
$(element).change(function() {
value($("option:selected", this).text());
});
}
};

你可能想得太多了。没有必要创建自定义活页夹。你可以使用淘汰赛的选项绑定。

// create an array
var phoneTypes = [{
text: "Mobile",
value: 1
}, {
text: "Home",
value: 2
}];
function phoneListVM() {
var _self = this;
// this will be bound to the dropdown
_self.phoneTypes = ko.observableArray(phoneTypes)
this.phones = ko.observableArray([
new phoneVM(1, "Mobile", "123-234-3456"),
new phoneVM(2, "Home", "654-343-3211")
]);
this.firstPhone = ko.computed(function() {
return _self.phones()[0];
});
}
function phoneVM(typeID, typeName, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
// get the value from the phonetypes array using the PhoneTypeID
self.PhoneTypeName = ko.computed(function() {
var type = phoneTypes.filter(function(a) {
return a.value === self.PhoneTypeID()
});
return type.length > 0 ? type[0].text : undefined;
})
}

并将HTML更改为:

<select data-bind="options: phoneTypes,
optionsText: 'text',
optionsValue: 'value',
optionsCaption: 'Choose',
value: firstPhone().PhoneTypeID">
</select>

您可以在knockout中将复杂对象作为选定值。因此,您可以在phoneVM中有一个PhoneType属性,并将PhoneType的2个属性绑定到text

这是一个更新的小提琴


我不太明白为什么只允许编辑第一个选项,或者用户如何编辑第二个选项。但是,您可以看看如何使项目列表中的每个项目都可编辑。


评论后更新:

即使select不是通过敲除的绑定创建的,您仍然不需要自定义绑定。您可以像我之前建议的那样,将PhoneTypeName设置为computed属性,并基于PhoneTypeIDoptions获取text

function phoneVM(typeID, typeName, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
self.PhoneTypeName = ko.computed(function() {
var type = $('#select option[value='+self.PhoneTypeID() +']');
return type.length > 0 ? type.text() : undefined;
});
}

这是一个更新的小提琴

更改事件没有被激发的原因可能是添加事件的element现在已从DOM中删除。