Reputation: 285
I am having difficulties with knockout. From my model, I am sending in a JSON object that contains a list of credentials. Each credential has a "Id", "Name", and "Selected" property. In my view, I have 2 lists. The first shows all credentials with "Selected" value equal to "false". The second list shows all items that have "Selected" value equal to "true".
Initially, all the items will have "Selected" value equal to false. When I double click the item in the list, the "Selected" value toggles so that it is removed from the first list and added the the second. My issue is that I can't seem to get the binding to work correctly. At first I was able to get the first list to populate correctly and I could manually enter in a value (Id) to the toggle function so that upon loading the page, whatever item I specified was listed properly on the second list. However, I was doing this incorrectly because I was changing the raw input data, not an observable array so my view wouldn't update correctly.
This is how I currently have it. As of right now when it goes into the toggleselected function, it isn't retrieving the correct credential (whereas before it was working when I used the raw, non observable data). I'm definitely missing something!
MODEL (just the one method that creates the JSON):
private void createJsonForAllCredentials()
{
List<CredentialLookupDto> credentials = proxy.Credential_RetrieveAll();
var optimizedCred = from c in credentials
select new
{
Name = c.Name,
Id = c.CredentialId,
Selected =
SelectedQualification != null && SelectedQualification.Credentials != null &&
SelectedQualification.Credentials.Any(p => p.CredentialId == c.CredentialId)
};
this.JsonAllCredentials = JsonConvert.SerializeObject(optimizedCred);
}
VIEW:
<div>
<table>
<tr>
<td class="fieldName_td">
@Html.Label("Available Credentials")
</td>
<td class="fieldData_td">
<select data-bind="foreach: $root.AllCredentials" multiple="multiple" name="ddallcredentials" id="allCredentials">
<!-- ko if: !Selected -->
<option data-bind="text:Name,value:Id"></option>
<!-- /ko -->
</select>
</td>
</tr>
</table>
</div>
<div>
<table>
<tr>
<td class="fieldName_td">
@Html.Label("Selected Credentials")
</td>
<td class="fieldData_td">
<select data-bind="foreach: $root.AllCredentials" multiple="multiple" name="ddSelectedCredentials" id="selectedCredentials">
<!-- ko if: Selected -->
<option data-bind="text:Name,value:Id"></option>
<!-- /ko -->
</select>
</td>
</tr>
</table>
</div>
}
@section scripts {
@Scripts.Render("~/Scripts/knockout-2.2.1.js", "~/jscripts/Administration/Interfaces/QualificationList.js", "~/Scripts/knockout.mapping-latest.js")
<script type="text/javascript">
$(function () {
TestNWJS.QualificationList.Init(@Html.Raw(Model.JsonAllCredentials));
})
</script>
}
VIEWMODEL:
///<reference path="~/Scripts/jquery-1.9.1.js" />
///<reference path="~/Scripts/knockout-2.2.1.js" />
///<reference path="~/Scripts/knockout.mapping-latest.js" />
var TestNWJS = TestNWJS || {};
TestNWJS.QualificationList = (function () {
//private functions
function CreateQualificationModel(allCredentialsList) {
TestNWJS.QualificationList.ViewModel = {};
TestNWJS.QualificationList.ViewModel.AllCredentials = ko.observableArray(allCredentialsList).extend({ notify: 'always' });
}
function toggleselected(array,id) {
var credential = ko.utils.arrayFirst(array, function (credential) {
var stringToInt = parseInt(id);
return credential.Id === stringToInt;
});
if (credential.Selected == false) {
credential.Selected = true;
}
else {
credential.Selected = false;
}
return credential;
}
//public function
return {
Init: function (allCredentialsList) {
CreateQualificationModel(allCredentialsList);
$("#allCredentials").live('dblclick', function (e) {
toggleselected(this.value);
});
$("#selectedCredentials").live('dblclick', function (e) {
toggleselected(this.value);
});
ko.applyBindings(TestNWJS.QualificationList.ViewModel);
}
}
})();
Again, just for clarification, the initial list loads correctly. This issue is when I want to double click an item to trigger the "toggleselected" function (the double click trigger works), the function does not work correctly. It returns a null value for "credential", thus meaning that my ko.utils.arrayFirst implementation isn't correct. There are potentially other issues related to binding as well. And finally, I was able to get everything to work correctly when I passed in the raw data "allCredentialsList" instead of "TestNWJS.QualificationList.ViewModel.AllCredentials". The reason I am doing so is because "TestNWJS.QualificationList.ViewModel.AllCredentials" is observable thus triggering a view update when a property changes (in this case the "Selected" property upon a double click).
Upvotes: 0
Views: 257
Reputation: 10976
I might miss something as I've no time right now to test it completely, but yes you're missing something.
You're trying to update your viewModel through the DOM with jQuery in the live
functions, thus bypassing the Knockout MVVM logic.
In fact Knockout also has an options
binding which is designed specifically for this kind of task.
Adapted example from the docs:
<select data-bind="options: myOptionsList,
optionsText: 'countryName',
value: myValueBinding,
optionsCaption: 'Choose...'"></select>
The value
binding keeps track of the selected option if you have a myValueBinding
observable in your viewmodel.
The options
binding is an observableArray
in your view model, from which the value
binding will be updated.
The arrayFirst
utility function returns the first match for which the function evaluates true. In your snippet this is impossible: in your toggleSelected
functions you only pass an array and no id, so it will never evaluate to true (id = undefined).
var credential = ko.utils.arrayFirst(array, function (credential) {
var stringToInt = parseInt(id);
return credential.Id === stringToInt;
});
Instead of passing this.value
to toggleSelected (which has no meaning for Knockout, unless you intend to use the DOM as data communication layer instead of your viewModel. So you should access the option's bound data with ko.dataFor()
, eg
$("#selectedCredentials").live('dblclick', function (e) {
var id = ko.dataFor(this).ID, array = ko.contextFor(this).$root.AllCredentials;
// if you have properly stored your arrays and properties, this will work:
toggleselected(array,id);
});
If you bound this live on your select
it would have been even easier:
<select data-bind="event: { dblclick: toggleSelected }"></select>
You don't even need to pass parameters in the view, because event
and data
are automatically passed as parameter. In your toggleSelected
function you'd have:
TestNWJS.QualificationList.ViewModel.toggleSelected = function(data, e) {
var id = data.ID, array = TestNWJS.QualificationList.ViewModel.AllCredentials;
// if you have properly stored your arrays and properties, this will work:
toggleselected(array,id);
});
Upvotes: 3