Reputation: 698
I'm using Knockout's "foreach" function on my MVC application's model to load a table of dropdown options. When the selected dropdown value changes, I need to track that change in the model.
I tried using the subscribe option on the element in the model, but when it changes, the function I bound to it isn't firing. I'm aware there are several ways of implementing this change, but I'd prefer to stick with this format of defining my functions as shown at the bottom of the view.
Model:
public class ProfileLookAndFeelViewModel : StandardLayoutViewModel
{
public ProfileLookAndFeelViewModel()
{
Form = new FormGroup();
PriceUOMDropdownOptions = new PriceUOM();
UOMInformation = new UOMInformationGroup();
}
public FormGroup Form { get; set; }
public PriceUOM PriceUOMDropdownOptions { get; set; }
public UOMInformationGroup UOMInformation { get; set; }
public class FormGroup
{
public bool FormValueChanged { get; set; }
}
public class PriceUOM
{
public int Id { get; set; }
public String Name { get; set; }
public string Code { get; set; }
}
public class PriceUOMOverride
{
public string ItemDescription { get; set; }
public string TemplateCode { get; set; }
public string UOMDesc { get; set; }
public List<PriceUOM> PriceUOMDropdownOptions { get; set; }
public int SelectedPriceUOM { get; set; }
public int SelectedPriceUOMOriginal { get; set; }
public bool SelectedPriceUOMChanged { get; set; }
}
public class UOMInformationGroup
{
public UOMInformationGroup()
{
UOMs = new List<PriceUOMOverride>();
}
public List<PriceUOMOverride> UOMs { get; set; }
public bool UOMsChanged { get; set; }
}
}
View:
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Description</th>
<th>Template Code</th>
<th>Default UOM</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: $root.UOMInformation.UOMs -->
<tr>
<td data-bind="text: ItemDescription"></td>
<td data-bind="text: TemplateCode"></td>
<td class="center" >
<select data-bind="options: PriceUOMDropdownOptions, optionsText: 'Code', optionsValue: 'Id', value: SelectedPriceUOM, css: { important: SelectedPriceUOMChanged() == true }"></select>
</td>
</tr>
<!-- /ko -->
</tbody>
</table>
<script type="text/javascript">
$(function () {
var vmProfileLookAndFeel = function () { var self = this; };
vmProfileLookAndFeel = ko.mapping.childrenIndependently($.parseJSON('@Html.RawJsonForKoMapping(Model)'), ["UOMInformation", "Form", "PriceUOMDropdownOptions"]);
vmProfileLookAndFeel.UOMInformation.UOMs.subscribe(function (newValue) {
vmProfileLookAndFeel.ValueChanged();
});
vmProfileLookAndFeel.ValueChanged = function () {
//Do something;
};
ko.applyBindings(vmProfileLookAndFeel);
});
</script>
I tried this suggestion but answer here isn't loading data from a MVC model but a hard coded array
Subscribe to select item from foreach loop
EDIT:
I updated the view to reflect adding the array as observable.
Here is the function that I call to map the objects
ko.mapping.childrenIndependently = function (jsObject, childrenArray) {
var mapping = {
"ignore": childrenArray
}
var vm = ko.mapping.fromJS(jsObject, mapping);
// handle children
for (var childrenArrayIndex = 0; childrenArrayIndex < childrenArray.length; childrenArrayIndex++) {
if (jsObject.hasOwnProperty(childrenArray[childrenArrayIndex])) {
// if the property is an array, create an objservable array
// and map each child
// else map the property
if ($.isArray(jsObject[childrenArray[childrenArrayIndex]])) {
vm[childrenArray[childrenArrayIndex]] = ko.observableArray();
for (var childObjectArrayIndex = 0; childObjectArrayIndex < jsObject[childrenArray[childrenArrayIndex]].length; childObjectArrayIndex++) {
vm[childrenArray[childrenArrayIndex]].push(ko.mapping.childrenIndependently(jsObject[childrenArray[childrenArrayIndex]][childObjectArrayIndex], childrenArray.slice(childrenArrayIndex)));
}
}
else {
vm[childrenArray[childrenArrayIndex]] = ko.mapping.childrenIndependently(jsObject[childrenArray[childrenArrayIndex]], childrenArray.slice(childrenArrayIndex));
}
}
}
return vm;
};
Upvotes: 0
Views: 224
Reputation: 698
For this scenario I end up adding a calculated function hidden immediately after the <select>
element that would get called each time a row is added. From there I could manipulate the object as it was being generated.
<td class="center" >
<select data-bind="options: PriceUOMDropdownOptions, optionsText: 'Code', optionsValue: 'Id', value: SelectedPriceUOM, css: { important: SelectedPriceUOMChanged() == true }"></select>
<span class="hide" data-bind="text: $root.OnTheFlyCalculations($data)()"></span>
</td>
New Function
vmProfileLookAndFeel.OnTheFlyCalculations = function (item) {
return ko.computed({
read: function () {
return item.SomethingChanged(true);
}
}, vmProfileLookAndFeel)
};
Upvotes: 0