Reputation: 18155
I have seen some solutions elsewhere for this problem, but have not yet found how to make this work in my specific circumstances.
An observableArray populates an html list. Inside the list, a user can edit one of the values for a particular row. The item to be edited in the row is itself an observableArray that populates a dropdown list. The array for the drop down list is populated via an ajax call when an edit button on the row is clicked (edit button not shown in code below).
I have this all working fine, except for one important issue: as of yet, the value in the dropdown list is not being pre-populated.
Below is a gross mockup of what the real code is doing.
If I call selectedTeamType
as selectedTeamType()
in the html, then the intial value is populated, but further changes to the select box are not registered. If I call selectedTeamType
as just selectedTeamType
, then the intial value is not set properly, but further changes to the select box are registered.
HTML
<table>
<tr> <!-- ko foreach: my.TeamViewModel.Teams -->
<td data-bind="text: TeamID"></td>
<td data-bind="text: TeamText"></td>
<td> <!-- the TeamType will be editable, so we provide a dropdown list, but default to the current value -->
<select data-bind="options: my.TeamViewModel.TeamTypes,
optionsText: 'TeamTypeText',
optionsValue: 'TeamTypeID',
value: selectedTeamType"></select>
</td>
</tr> <!-- /ko -->
</table>
JAVASCRIPT
var my = my || {};
$(function () {
my.TeamModel = function () {
var self = this;
self.TeamID = ko.observable();
self.TeamText = ko.observable();
self.TeamTypeID = ko.observable();
self.selectedTeam = ko.observable();
self.Edit = function () {
my.TeamViewModel.load_TeamTypes();
};
};
my.TeamTypesModel = function () {
var self = this;
self.TeamTypeID = ko.observable();
self.TeamTypeText = ko.observable();
};
my.TeamViewModel = function () {
var Teams = ko.observableArray([]),
TeamTypes = ko.observableArray([]),
load_TeamTypes = function () {
$.ajax({
// only part of ajax call displayed here for sake of brevity...
$.each(data, function (i, d) {
TeamTypes.push(new my.TeamTypesModel()
.TeamTypeID(d.TeamTypeID)
.TeamTypeText(d.TeamTypeText)
);
});
});
},
load_Teams = function () {
$.ajax({
// only part of ajax call displayed here for sake of brevity...
$.each(data, function (i, d) {
Teams.push(new my.TeamModel()
.TeamID(d.TeamID)
.TeamText(d.TeamText)
);
});
}
});
};
return {
Teams: Teams,
TeamTypes: TeamTypes,
load_TeamTypes: load_TeamTypes
};
} ();
my.TeamViewModel.load_Teams();
ko.applyBindings(my.TeamViewModel);
});
Upvotes: 0
Views: 2334
Reputation: 47350
Is selectedTeamType
set before the my.TeamViewModel.TeamTypes
array is populated? If so, this could be why its initial value is not being set. I don't see anywhere in the javascript you posted where you are setting the initial value of selectedTeamType
.
Update
Your answer to your own question confirmed my suspicion above. Is there a reason why you need to load the team types dropdown every time a user goes to edit a team? If the select options are the same for every team, why not just load it once?
Example:
my.TeamViewModel.load_Teams();
my.TeamViewModel.load_TeamTypes();
ko.applyBindings(my.TeamViewModel);
This way, the options list should already be loaded by the time a user clicks the edit button. It could save your client/server some sweat too, if a fresh team types options list is not needed for every team in the list.
If you want to guarantee that both $.ajax
calls will complete before you do anything, I suggest looking into jQuery's $.Deferred
method and promise API. When you have more than 1 $.ajax
call, and you need to perform an action only after both have succeeded, you can do something like this:
var defer_Teams = $.Deferred();
var defer_TeamTypes = $.Deferred();
$.ajax({ // initiate the server call to get teams
url: '/teams',
success: function(data) {
defer_Teams.resolve(data); // tell the promise API this is ready
}
});
$.ajax({ // initiate the server call to get team types
url: '/team-types',
success: function(data) {
defer_TeamTypes.resolve(data); // tell the promise API this is ready
}
});
$.when(defer_Teams, defer_TeamTypes).then(function(teamData, teamTypeData) {
$.each(teamTypeData, function(i, d) {
// populate team types array
// could also do this with ko.mapping instead of $.each
})
$.each(teamData, function(i, d) {
// populate teams array
// could also do this with ko.mapping instead of $.each
})
});
Say your /teams call returns fast, but your /team-types call lags. With the above, the teams array would not be populated until both calls finish. And you really do need that types call to finish before the user can click the edit button and get the selected value pre-populated.
Upvotes: 2
Reputation: 18155
The apparent fix involved two steps:
First, pass a reference to the current item and its current TeamType
in the Edit
function.
self.Edit = function () {
var that = this;
var _TeamType = self.TeamTypeID();
my.TeamViewModel.load_TeamTypes(that, _TeamType);
};
Then, and most importantly, pass those references to the function that loads the TeamTypes,
and **set the selectedTeamType AFTER the TeamTypes array has loaded; in this case, the selectedTeamType is set in the complete
function of jQuery's ajax
.
load_TeamTypes = function (that, _TeamType) {
$.ajax({
// only part of ajax call displayed here for sake of brevity...
$.each(data, function (i, d) {
TeamTypes.push(new my.TeamTypesModel()
.TeamTypeID(d.TeamTypeID)
.TeamTypeText(d.TeamTypeText)
);
});
//...ajax continued
complete: function () {
that.selectedTeamType(_TeamType);
}
});
},
Upvotes: 1
Reputation: 1228
Have you tried changing your constructor to take in d like so
my.TeamTypesModel = function (data) {
var self = this;
self.TeamTypeID = ko.observable(data.TeamTypeId);
self.TeamTypeText = ko.observable(data.TeamTypeText);
};
Upvotes: 0