Populating dropdownlist using knockout observable array - javascript

I am trying to bind 2 drop down lists to knockout observable arrays.The condition is that the first drop down list has to get populated first.The second drop down list is dependent on the first drop down list.Hence I am subscribing to the first drop down list to populate the second drop down list.
To try and achieve this,I have written the following code
HTML is
<div class="form-group">
<label class="col-sm-2 control-label labelfont">Certification:</label>
<div class="col-sm-6">
<select class="form-control" id="certification" name="certification" data-bind="value: certification, options: certificationArray, optionsText: 'Certification', optionsValue: 'CertificationID', optionsCaption: 'Select a Certification'">
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label labelfont">Specialization:</label>
<div class="col-sm-6">
<select class="form-control" id="specialization" name="specialization" data-bind="value: specialization, options: specializationArray,optionsText:'Specialization',optionsValue:'SpecializationId', optionsCaption: 'Select a Specialization'">
</select>
</div>
</div>
The view model is
self.specializationArray = ko.observableArray([]);
self.certificationArray = ko.observableArray([getCertifications()]);
self.certification = ko.observable("");
self.specialization = ko.observable("");
self.certification.subscribe(function () {
self.specializationArray([]);
getSpecializations();
}
});
The functions to get the respective certifications and speciaizations are
var getCertifications = function () {
$.getJSON("/Provider/GetCertifications", function (data) {
return data;
});
});
};
var getSpecializations = function () {
$.getJSON("/Provider/GetSpecializations/", { certificationID: $("#certification").val() }, function (data) {
self.specializationArray(data)
})
}
The JSON response looks like this
and the JSON looks like this
Could someone please guide me in the right direction,I am completely new to knockout and JSON.I am at my wit's end here.So,any help would be greatly appreciated.Thanks in advance.

In KnockOut, the values of the arrays are separate from the options the dropdowns get. You wouldn't change the observableArray, but the value observable itself. Better, though, would be to use a ko.computed field to hold the value of the 2nd dropdown.
I don't really understand why you think your dropdowns must get bound in a certain order, though. I'm not exactly sure how the Certifications and Specializations have to do with each other from your question. You aren't really doing anything in self.certification.subscribe(), like there was any dependency when filling in the Specialization. The JSON you supplied in the Certifications also does not have any related Specialization foreign key. But if your JSON for Certification looked like this:
[{"CertificationID": 1, "Certification": "M.B.B.S.", "SpecializationID": "7"},{"CertificationID": 2, "Certification": "M.D.", "SpecializationID": "3"}, ...]
while the JSON for Specialization looked like this...
[{"SpecializationID": 1, "Specialization": "Cardiology"},
{"SpecializationID": 2, "Specialization": "Dentistry"},
{"SpecializationID": 3, "Specialization": "General Practioner"}, ...
{"SpecializationID": 7, "Specialization": "Surgery"}
]
I believe something like this should work:
self.specialization = ko.computed(function() {
return ko.utils.arrayFirst(getCertifications(), function(certs) {
return certs.ID == self.SpecializationID();
}).SpecializationID;
});
But since I don't see where you have any dependencies, here, why not just fill them in, directly:
self.certificationArray = ko.observableArray(getCertifications());
self.specializationArray = ko.observableArray(getSpecializations());
self.certification = ko.observable(vm.certification);
self.specialization = ko.observable(vm.specialization);
And you set those values using the viewmodel returned from the dataset, which should be ID fields in your database table (CertificationID, SpecializationID).

Related

Angular: How to re-bind to previously selected object when list changes

By default, when you modify the source list that a select is bound to using ng-options, it clears the selected list item even if that item exists in its entirety in the new list. I figure there may be a way to tell angular that if they have the same WhateverKey property, then consider them the same item and don't de-select. Is there a way to do this?
In the example below, selecting a new customer re-binds (think: re-downloads) the list of possible locations, but the selected location ($scope.Location) still exists in the new list, and thus user experience intuition says it should be selected.
I understand the problem is that it's literally a different reference object with a different angular object key. I'm asking how to auto-rebind based on a property of the object (in this case, LocationKey) without resorting to looping through JavaScript to find the corresponding object in the new list manually.
function Ctrl($scope) {
$scope.Customers = [{CustomerKey: 1, CustomerCode: 'Customer1'},
{CustomerKey: 2, CustomerCode: 'Customer2'},
{CustomerKey: 3, CustomerCode: 'Customer3'}];
$scope.Locations = [{LocationKey: 1, LocationCode: 'Location1'},
{LocationKey: 2, LocationCode: 'Location2'},
{LocationKey: 3, LocationCode: 'Location3'}];
$scope.Customer = $scope.Customers[0];
$scope.Location = $scope.Locations[0];
$scope.SelectCustomer = function() {
$scope.Locations = angular.copy($scope.Locations);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="Ctrl">
<select ng-model="Customer"
ng-options="c as c.CustomerCode for c in Customers"
ng-change="SelectCustomer()"></select>
<select ng-model="Location"
ng-options="l as l.LocationCode for l in Locations"></select>
<div ng-bind="'Customer: ' + Customer.CustomerCode"></div>
<div ng-bind="'Location: ' + Location.LocationCode"></div>
</div>
$scope.SelectCustomer = function() {
$scope.Locations = angular.copy($scope.Locations);
$scope.Location = $scope.Locations.find(function(loc){
return loc.LocationKey === $scope.LocationKey;
}
}
Sorry for the lazy answer. Would have been a comment if you could format code properly in a comment.

Selecting Select Box Option from Controller

I did a lot of searching and tried eleventy-billion different Google search combinations, but all I can find on this issue is how to set a default option in a select box.
I have a page where an admin can select a user from a list of users, and then Angular JS grabs the user_id of that user (using ng-change), sends it to the DB via POST, and then the goal is to change the value of the other inputs to the values from the DB. I have the values, but running into a hitch when using that value to get my state select box to change to the user's state.
The JS in question:
$scope.getUserInfo = function(user_id) {
this.user_id = user_id;
$http.post("lib/scripts/managing_user.php", {func: 'retrieve_user_info', user_id: this.user_id}).
success(function(data) {
console.log(data);
$scope.is_active = data['0']['active'];
//Interacts with the ng-checked directive. It takes a bool, and data returns 0 or 1 in active.
$scope.username = data['0']['username'];
//Assigns the data value to the ng-model directive with the value username
//Have to treat data as a 2D array because that is how it is returned
$scope.email = data['0']['email'];
$scope.fName = data['0']['first_name'];
$scope.lName = data['0']['last_name'];
$scope.schoolList = data['0']['school_id']; (<-Does not work)
}).
I accomplished the same thing using jQuery before. Here is the code if it helps you see what I want to do.
if ($("#school").children("option:selected"))
$("#school").children("option:selected").attr("selected", "false");
$("#school #" + this['school_id'] + "").attr("selected", "true");
Here is the Select Box that I want changed.
<div class="row-fluid">
<span class="adduser_heading">School:</span>
<select id="school" class="adduser_input" ng-model="user.schoolList" ng-options="name.school_name for (key, name) in schoolList" ng-disabled="is_disabled" name="school" style="width: 246px;"></select>
</div>
I get the list of schools from the DB, and that populates that list. After selecting a user, I want this select box to change to that user's school. The ultimate goal is for the admin to be able to change the selected school and submit it, changing the school in the DB.
Hope I described the problem adequately. Basically: I want to select an option in a select box from the JS using Angular JS.
Edit: As per the advice of oware, I created a function that gets just the school name from the object array and returns it to $scope.user.schoolList. Unfortunately, that did not work.
$scope.user.schoolList = $scope.findInSchoolList(data['0']['school_id']);
$scope.findInSchoolList = function(school_id) {
var school_id = school_id;
var school;
school = $scope.schoolList[school_id];
school = school['school_name'];
return school;
};
And here is the format of what is returned from the DB with regards to school. I don't really want to post "data" since that has the information of an actual person. Basically, the information with regards to school is what is below.
school_id: "106"
school_name: "Central Campus High School"
Your ng-model is set to user.schoolList, while you're assigning the default value to $scope.schoolList.
It should be $scope.user.schoolList instead.
If you want to use the find function, you still need to return the right object, not just the name; and you need to fix your function. So something like this should work:
$scope.findInSchoolList = function(school_id) {
for(var i = 0; i < $scope.schoolList.length; i++) {
if ($scope.schoolList[i].school_id == school_id) {
return $scope.schoolList[i];
}
}
};
Here's a working example:
angular.module('app', [])
.controller('Ctrl', function($scope) {
var findInSchoolList = function(school_id) {
for (var i = 0; i < $scope.schoolList.length; i++) {
if ($scope.schoolList[i].school_id == school_id) {
return $scope.schoolList[i];
}
}
};
$scope.schoolList = [{
school_id: "1",
school_name: "Central Campus High School"
}, {
school_id: "106",
school_name: "Another High School"
}, {
school_id: "231",
school_name: "Yet Another High School"
}, {
school_id: "23",
school_name: "The Correct High School"
}, {
school_id: "2",
school_name: "Last High School"
}]
$scope.user = {
schoolList: findInSchoolList(23)
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="row-fluid" ng-app="app" ng-controller="Ctrl">
<span class="adduser_heading">School:</span>
<select id="school" class="adduser_input" ng-model="user.schoolList" ng-options="name.school_name for (key, name) in schoolList" ng-disabled="is_disabled" name="school" style="width: 246px;"></select>
</div>
you have to select the item from the array that populated the list, for example, if you have:
$scope.school_list = [{id:1, name:'harvard'}, {id:2, name:'other'}]
and you want to select with:
$scope.user.schoolList = {id:1, name:'harvard'}
it won't work, you have to make a fucntion that finds the element in the array and then assign it to the $scope.user.schoolList variable (that is bound to the ng-model of your list)
you have to do something like this:
$scope.user.schoolList = findInSchoolList({id:1, name:'harvard'})
and it will select the item from the select list
hope it helps

Knockout: changing options in select list without clearing value in view-model

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 become 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.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);
Knockout's valueAllowUnset binding might be a cleaner approach.
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>
#super cool is 100% correct, but the reason it is undefined is that the ActiveChildRecord changes when you click the pet row, but this computed 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 and ChildrecordClicked, to vaoid changes while the lists are updated)
there is an array of categories, a category 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, a thing 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.computed(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 .

sub ng-repeats, getting (checked) checkbox values in angular

I have some nested ng-repeats I am working with and the third level down is a grouping of checkboxes. Initially I am sent an array of options for the checkboxes so I do this:
<div class="fadingOptionsHere" ng-repeat="fading in fade.options">
<input type="checkbox" ng-model="fadingsHere" value="{{fading.id}}">{{fading.name}}
</div>
I am tyring to find a way to ONLY get the values of the selected checkboxes back. It would be super ideal if I could just replace the nested options array with an array of only the selected items, so I could just send the json object back like that.
This is the third level down of nesting so I'm having trouble tracking these guys. How would I be able to get only the selected values (as the fading.id) of the select boxes for each iteration of the ng-repeat?
I keep trying to reference the fadingsHere model with no success.
Thanks!
You can do this in this way.
In HTML do like below.
<ul>
<li data-ng-repeat="record in records">
<input type="checkbox" ng-model="selected[record.Id]"> {{record.Id}}
</li>
</ul>
And in controller you have to add selected property.
function MyCtrl($scope) {
$scope.records = [ { "Id": 1, }, { "Id": 2 }, { "Id": 3 } ];
$scope.selected = {};
$scope.ShowSelected = function() {
return $scope.selected
};
When you read the selected property you will get the selected list.
Demo
you could use checklist model directive like
<input type="checkbox" class="form-control"
checklist-model="transaction.jobData[attribute.key]"
checklist-value="checkBoxAttributes.code">

How do I keep the original value selected when transitioning to edit mode?

The select lists are not rendering with the correct option selected. I've tried this a number of different ways including a computed selected observable (this.selected = ko.computed(return parseInt(selected(), 10) == this.id; )) and find in array functions.
In production, the dataArea elements would be populated with server side data. Using the divs with "data-" attributes keeps server side and client side scripting separate (I find this helps the designers).
A record would be displayed in non edit mode first with the option to edit by clicking the edit button. In edit mode, the initial values for the record appear in input controls. You would have the option to say, choose another customer and the having the form load new associated projects. Loading a new customer would reset the project list as expected.
So while loading a new customer would work well, its the transition to editing the current values that is causing an issue. The selected project needs to appear in the drop down list. If a new customer is chosen, the list populates with new options and no defaults are required.
http://jsfiddle.net/mathewvance/ZQLRx/
* original sample (please ignore) http://jsfiddle.net/mathewvance/wAGzh/ *
Thanks.
<p>
issue: When the select options are read, the inital value gets reset to the first object in the options. How do I keep the original value selected when transitioning to edit mode?
</p>
<div>
<h2>Edit Quote '1001'</h2>
<div class="editor-row" data-bind="with: selectedCustomer">
<label>Customer</label>
<div data-bind="visible: !$root.isEditMode()">
<span data-bind="text: CompanyName"></span>
</div>
<div data-bind="visible: $root.isEditMode()">
<input type="radio" name="customerGroup" value="1" data-bind="value: id"> Company Name 1
<input type="radio" name="customerGroup" value="2" data-bind="value: id"> Company Name 2
</div>
</div>
<div class="editor-row">
<label>Project</label>
<div data-bind="visible: !isEditMode()">
<span data-bind="text: selectedProject.Name"></span>
</div>
<div data-bind="visible: isEditMode()">
<select data-bind="options: selectedCustomer().projects, optionsText: 'Name', value: selectedProject"></select>
</div>
</div>
<div>
<button data-bind="click: function() { turnOnEditMode() }">Edit</button>
<button data-bind="click: function() { turnOffEditMode() }">Cancel</button>
</div>
</div>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
function ajaxCallGetProjectsByCustomer(customerId) {
var database = '[{"CustomerId": 1, "Name":"Company Name 1", "Projects": [ { "ProjectId": "11", "Name": "project 11" }, { "ProjectId": "12", "Name": "project 12" }, { "ProjectId": "13", "Name": "project 13" }] }, {"CustomerId": 2, "Name": "Company Name 2", "Projects": [ { "ProjectId": "21", "Name": "project 21" }, { "ProjectId": "22", "Name": "project 22" }, { "ProjectId": "23", "Name": "project 23" }] }]';
var json = ko.utils.parseJson(database);
//console.log('parseJson(database) - ' + json);
//ko.utils.arrayForEach(json, function(item) {
// console.log('CustomerId: ' + item.CustomerId);
//});
return ko.utils.arrayFirst(json, function(item){
return item.CustomerId == customerId;
});
}
var Customer = function(id, name, projects) {
var self = this;
this.id = ko.observable(id);
this.CompanyName = ko.observable(name);
this.projects = ko.observableArray(ko.utils.arrayMap(projects, function(item) {
return new Project(item.ProjectId, item.Name);
}));
};
Customer.load = function(id) {
var data = ajaxCallGetProjectsByCustomer(id);
var customer = new Customer(
data.CustomerId,
data.Name,
data.Projects);
};
var Project= function(id, name) {
this.id = id;
this.Name = ko.observable(name);
};
var QuoteViewModel = function () {
var self = this;
$customerData = $('#customerData'); // data from html elements
$projectData = $('#projectData');
// intial values to display from html data
var customer = new Customer (
$customerData .attr('data-id'),
$customerData .attr('data-companyName'),
[{"ProjectId": $projectData .attr('data-id'), "Name": $projectData .attr('data-name')}]
)
this.selectedCustomer = ko.observable(customer);
this.selectedProject = ko.observable($projectData.attr('data-id'));
this.isEditMode = ko.observable(false);
this.selectedCustomer.subscribe(function(){
// todo: load customer projects from database api when editing
});
this.turnOnEditMode = function() {
var customerId = self.selectedCustomer().id();
console.log('customerId: ' + customerId);
Customer.load(customerId);
self.isEditMode(true);
};
this.turnOffEditMode = function() {
self.isEditMode(false);
};
};
var viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);
One the initial value you load
this.dongle = ko.observable($dongleData.attr('data-id'));
This would be the string value "3". Where as the dongle html select element is actually saving/expecting to retrieve the object { "Id": "3", "Name": "dongle 3" }.
Here is a working version that gets the correct initial values and allows editing.
http://jsfiddle.net/madcapnmckay/28FVr/5/
If you need to save the a specific value and not the whole dongle/widget object, you can use the optionsValue attribute to store just the id. Here is it working in the same way.
http://jsfiddle.net/madcapnmckay/VnjyT/4/
EDIT
Ok I have a working version for you. I'll try to summarize everything I changed and why.
http://jsfiddle.net/madcapnmckay/jXr8W/
To get the customer info to work
The Customer name was not stored in the ajaxCallGetProjectsByCustomer json so when you loaded a customer there was no way to determine the new name from the data received. I added a Name property to each customer in the json with name "Company Name 1" etc.
To get the projects collection to work
The problem here was as stated originally with the dongles. You initialize the selectedProject observable with $projectData.attr('data-id') which equates to string value of 13. This is incorrect as the select list is configured in such a way that it actually saves/expects to receive the project object itself. Changing this id assignment to an object assignment made the initial value of project work correctly.
var project = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == Number($projectData.attr('data-id'));
});
this.selectedProject = ko.observable(project);
FYI there was a minor error in the html, the selectedProject.Name needed to be selectedProject().Name. No big deal.
I'm sure you could have figured out those pretty easily. The next bit is where the real issue is. You reload the Customer every time the edit button is clicked. This seems strange and you may want to reconsider that approach.
However what happens is you load a customer object from the server by id. Assign it to the selectedCustomer observable, this actually works fine. But then because the drop down is bound to selectedCustomer().projects and viewModel.selectedProject it expects that selectedProject is a member of selectedCustomer().projects. In the case of objects the equality operator is assessing whether the references match and in your case they do not because the original selectedProject was destroyed with its associated customer when you overwrote the selectedCustomer value. The fact that the ids are the same is irrelevant.
I have put in place a hack to solve this.
var oldProjectId = viewModel.selectedProject().id;
viewModel.selectedCustomer(customer);
var sameProjectDifferentInstance = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == oldProjectId;
});
viewModel.selectedProject(sameProjectDifferentInstance || customer.projects()[0]);
This saves the old projectId before assigning the new customer, looks up a project object in the new customer object and assigns it or defaults to the first if not found.
I would recommend rethinking when you load objects and how you handle their lifecycle. If you hold the current objects it memory with a full list of projects included you don't need to reload them to edit, simply edit and then send the update back to the server.
You may find it easier to hold json from the server in js variables instead of html dom elements. e.g.
<script>var projectInitialData = '#Model.ProjectInitialData.toJSON()';</script>
Hope this helps.

Categories

Resources