Knockout:更改选择列表中的选项而不清除视图模型中的值

Dan*_*Coy 3 javascript data-binding cascadingdropdown viewmodel knockout.js

我有一个基于Knockout JS的问题,带有一些级联选项列表,并切换出与它们相关的底层"活动"对象.

我创建了一个jsfiddle来演示这个问题.

我有一个用户界面,其中用户正在编辑主"标题"记录并添加/删除/编辑子记录.有一个用于编辑儿童记录的中心区域.想法是单击表中的子记录,这将成为正在中间区域编辑的记录.

我遇到的问题是由于第二个下拉列表中的事物列表根据第一个下拉列表而变化.在活动记录更改之前,这很好.如果类别因活动记录更改而更改,则"事物"列表也会更改.此时,清除新活动子记录中选择的"事物"(第二个下拉列表).

我假设新活动记录的值发生了变化,但是因为它没有出现在旧列表中(如果类别已更改),则会被清除.然后更改项目本身列表(包括适当的值),但此时该值已从视图模型中消失.

(我意识到这是一个冗长的解释,希望jsfiddle明确表示)

如何更改下拉列表中的项目列表以及视图模型中的选定值,同时不会丢失所选值?

HTML:

<label>Some header field</label>
<input type="text" id="txtSomeHeaderField" data-bind="value: HeaderField" />

<fieldset>
    <legend>Active Child Record</legend>

    <label>Category</label>
    <select id="ddlCategory" data-bind="options: categories, value: ActiveChildRecord().Category, optionsCaption:'Select category...'" ></select>

    <label>Things</label>
    <select id="ddlThings" data-bind="options: ThingsList, value: ActiveChildRecord().Favorite, optionsCaption:'Select favorite thing...'" ></select>
</fieldset>

<button data-bind="click: AddChildRecord" >Add a child record</button>

<table id="tblChildRecords" border>
    <thead>
        <tr>
            <th>Category</th>
            <th>Favorite Thing</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: ChildRecords">
        <tr data-bind="click: ChildRecordClicked, css: {activeRow: ActiveChildRecord() === $data}" >
            <td data-bind="text: Category"></td>
            <td data-bind="text: Favorite"></td>
        </tr>
    </tbody>
</table>

<p>Steps to reproduce problem:</p>
<ol>
    <li>Click "Add child record"</li>
    <li>Click on that row to make it the "active" record</li>
    <li>Choose category "Pets" and thing "Dog"</li>
    <li>Click "Add child record"</li>
    <li>Click on the new row to make it the "active" record</li>
    <li>Choose category "Colours" and thing "Blue"</li>
    <li>Now click back on the first row... <strong>"Dog" disappears!</strong></li>
</ol>
Run Code Online (Sandbox Code Playgroud)

使用Javascript:

var categories = ["Pets", "Colours", "Foods"];

var MyViewModel = function(){
    var _this = this;

    this.HeaderField = ko.observable("this value is unimportant");
    this.ChildRecords = ko.observableArray([]);
    this.ActiveChildRecord = ko.observable({ Category: ko.observable("-"), Favorite: ko.observable("-")});    
    this.ThingsList = ko.observableArray();

    this.AddChildRecord = function(){
        _this.ChildRecords.push({ Category: ko.observable("-"), Favorite: ko.observable("-")});
    }

    this.ChildRecordClicked = function(childRecord){
        _this.ActiveChildRecord(childRecord);
    }

    this.RefreshThingsList = ko.computed(function(){
        var strActiveCategory = _this.ActiveChildRecord().Category();
        switch(strActiveCategory){
            case "Pets": _this.ThingsList(["Dog", "Cat", "Fish"]); break;      
            case "Colours": _this.ThingsList(["Red", "Green", "Blue", "Orange"]); break;
            case "Foods": _this.ThingsList(["Apple", "Orange", "Strawberry"]); break;
        }        

    });
}

ko.applyBindings(MyViewModel);
Run Code Online (Sandbox Code Playgroud)

Kyl*_*avy 5

Knockout的valueAllowUnset绑定可能是一种更清洁的方法.

http://jsfiddle.net/5mpwx501/8/

<select id="ddlCategory" data-bind="options: categories, value: ActiveChildRecord().Category, valueAllowUnset: true, optionsCaption:'Select category...'" ></select>
<select id="ddlThings" data-bind="options: ThingsList, value: ActiveChildRecord().Favorite, valueAllowUnset: true, optionsCaption:'Select favorite thing...'" ></select>
Run Code Online (Sandbox Code Playgroud)

@super cool是100%正确的,但是未定义的原因是当你单击pet行时ActiveChildRecord会改变,但是这个计算的函数还没有执行,所以你有一个很小的时间帧,其中Dog是收藏夹,但是选项仍然是颜色.由于Dog不是一个选项,因此下拉列表会将ActiveChildRecord上的Favorite属性设置为undefined.

我会使用valueAllowUnset绑定.基本上它告诉下拉列表,如果没有匹配,请不要将我的值设置为undefined,而是等待因为选项可能正在更新.

使用此绑定的一个很好的副作用是,当您添加新的子记录时,它不会复制上一行.它会自然地为您重置选择.