admin管理员组文章数量:1301590
I have a Knockout JS based problem with some cascading options lists and switching out the underlying "active" object that they relate to.
I have created a jsfiddle to demonstrate the problem.
I have a UI in which the users are editing a main "header" record and adding/removing/editing child records. There is a central area for editing child records. The idea is to click a child record in a table and this bee the record being edited in the middle area.
The problem I have is due to the fact that the list of things in the second drop-down changes depending on the first. This is fine until the active record changes. If the category changes because the active record changes then the list of "things" also changes. At this point, the chosen "thing" (2nd drop-down) on the new active child record is cleared.
I'm assuming the value on the new active record changes, but is cleared because it doesn't appear in the old list (if the category changed). The list of items themselves is then changed (including the appropriate value), but the value has already gone from the view model by this point.
(I realize that's quite a long-winded explanation, hopefuly the jsfiddle makes it clear)
How can I change the list of items in a drop-down AND the selected value in the view model without losing the selected value along the way?
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>
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 = koputed(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);
I have a Knockout JS based problem with some cascading options lists and switching out the underlying "active" object that they relate to.
I have created a jsfiddle to demonstrate the problem.
I have a UI in which the users are editing a main "header" record and adding/removing/editing child records. There is a central area for editing child records. The idea is to click a child record in a table and this bee the record being edited in the middle area.
The problem I have is due to the fact that the list of things in the second drop-down changes depending on the first. This is fine until the active record changes. If the category changes because the active record changes then the list of "things" also changes. At this point, the chosen "thing" (2nd drop-down) on the new active child record is cleared.
I'm assuming the value on the new active record changes, but is cleared because it doesn't appear in the old list (if the category changed). The list of items themselves is then changed (including the appropriate value), but the value has already gone from the view model by this point.
(I realize that's quite a long-winded explanation, hopefuly the jsfiddle makes it clear)
How can I change the list of items in a drop-down AND the selected value in the view model without losing the selected value along the way?
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>
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.puted(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);
Share
asked Dec 10, 2014 at 15:40
Dan McCoyDan McCoy
4054 silver badges18 bronze badges
1
- 1 modified your fiddle you looking for this jsfiddle/supercool/9edj1z64 – super cool Commented Dec 10, 2014 at 16:55
3 Answers
Reset to default 6Knockout's valueAllowUnset binding might be a cleaner approach.
http://jsfiddle/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>
@super cool is 100% correct, but the reason it is undefined is that the ActiveChildRecord changes when you click the pet row, but this puted function has not yet executed so you've got a small timeframe where Dog is the Favorite, but the options are still Colours. Since Dog is not an option, the dropdown will set the Favorite property on your ActiveChildRecord to undefined.
I would use the valueAllowUnset binding. Basically it tells the dropdown that if there is no match, don't set my value to undefined, but rather wait because the options might be updating.
A nice side effect of using this binding is that when you add a new child record it doesn't copy the previous row. It naturally resets the selection for you.
I've used a totally different approach, using subscriptions to updates lists and values, and an special observable to hold the edited record.
<fieldset>
<legend>Active Child Record</legend>
<label>Category</label>
<select id="ddlCategory"
data-bind="options: categories, value: category,
optionsCaption:'Select category...'" ></select>
<label>Things</label>
<select id="ddlThings"
data-bind="options: things, value: thing,
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: editedRecord() === $data}" >
<td data-bind="text: category"></td>
<td data-bind="text: thing"></td>
</tr>
</tbody>
</table>
JavaScript:
var categories = ["Pets", "Colours", "Foods"];
var MyViewModel = function(){
var _this = this;
this.categories = ko.observableArray(["Pets","Colours","Foods"]);
this.category = ko.observable();
this.category.subscribe(function(newCategory){
_this.refreshThings(newCategory);
if(editedRecord()) {
editedRecord().category(newCategory);
}
});
this.things = ko.observableArray([]);
this.thing = ko.observable();
_this.refreshThings = function(newCategory){
switch(newCategory){
case "Pets": _this.things(["Dog", "Cat", "Fish"]); break;
case "Colours": _this.things(["Red", "Green", "Blue", "Orange"]); break;
case "Foods": _this.things(["Apple", "Orange", "Strawberry"]); break;
}
};
this.thing.subscribe(function(newThing){
if(editedRecord()) {
editedRecord().thing(newThing);
}
});
this.childRecords = ko.observableArray([]);
this.editedRecord = ko.observable();
this.AddChildRecord = function(){
var newRecord = {
category: ko.observable(),
thing: ko.observable()
};
_this.childRecords.push(newRecord);
_this.editedRecord(newRecord);
_this.category('');
_this.thing('');
}
this.ChildRecordClicked = function(childRecord){
_this.editedRecord(null);
_this.category(childRecord.category())
_this.thing(childRecord.thing())
_this.editedRecord(childRecord);
}
}
ko.applyBindings(MyViewModel);
Several notes:
- a new observable, named 'editedRecord' is used. This can hold the value of the currently edited record (either new, either selected by clicking it), or a null value, if nothing should be edited (this value is set in
AddChildRecord
andChildrecordClicked
, to vaoid changes while the lists are updated) - there is an array of
categories
, acategory
observable, and a subscription that updates the list of things as well as the category property of the edited record, if present - there is an array of
things
, athing
observable, and a subscription that updates the thing property of the edited record, if present - the
addChildRecord
, creates a new, empty record, and set it as the edited record. Besides initializes both lists of categories and things - the
childRecordClick
sets the clicked record as edited record
As you can see, with this technique the bindings remain very simple, and you have full control of what's ging on in each moment.
You can use techniques similar to this one to allow cancelling of the edition and things like that. In fact, I usually edit the record in a different place, and add it, or apply its changes, once the user accepts them, allowing him to cancel.
This is your modified fiddle.
Finally, if you want to keep the slashes on unedited records, make this change:
this.AddChildRecord = function(){
_this.editedRecord(null);
var newRecord = {
category: ko.observable("-"),
thing: ko.observable("-")
};
_this.childRecords.push(newRecord);
_this.category('');
_this.thing('');
_this.editedRecord(newRecord);
}
included in this version of the fiddle, but it would be better if you applied an style so that the table cell has a minimun height, and keep it empty, like in the previous version.
well i made a small modification in your fiddle which worked perfectly .
View Model:
this.RefreshThingsList = ko.puted(function(){
var store= ActiveChildRecord().Favorite();
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;
}
alert(ActiveChildRecord().Favorite()); // debug here you get undefined on your step 7 so store the value upfront and use it .
ActiveChildRecord().Favorite(store);
});
working fiddle here
Just in case you looking for something other than this let us know .
本文标签: javascriptKnockout changing options in select list without clearing value in viewmodelStack Overflow
版权声明:本文标题:javascript - Knockout: changing options in select list without clearing value in view-model - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741666450a2391339.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论