I’m having issues with structure of ViewModel in Knockout.
UPDATE
END UPDATE
Overview
- I would like to return a model from MVC action and use mappings
extension for knockout to create observable from JSON to ViewModel. - Display items in table were only one field is editable and this
field is dropdown, but it’s causing all properties of item to update
values. - Post model back to MVC Action.
Description
I’m using mapping extension to map JSON model returned from MVC action to knockout model, my model looks like this:
public class SomeClassInput
{
public string Help { get; set; }
public IEnumerable<SomeClassItem> Items { get; set; }
public class SomeClassItem
{
public string Code { get; set; }
public string Location { get; set; }
public string Easting { get; set; }
public string Northing { get; set; }
public string WaterName { get; set; }
public string WfdCode { get; set; }
}
public SomeClassInput()
{
Items = new List<SomeClassItem>();
}
}
Model returned from Action, can contains default data – few items.
I’m using custom serialize settings for JSON, so all PascalCase properties names are transformed to camelCase.
binding is quite straight forward (wrapper around some sort of controller to manage few screens, and dynamic loading of views):
this.model = ko.mapping.fromJS(modelJson);
ko.applyBindings(this.model, this.view.content[0]);
Now in View I have a table:
<table class="table table-striped table-bordered" id="some-table"
data-swo-codes="@Url.Action("Codes", "Controller")"
>
<thead>
<tr>
<th>
[Actions]
</th>
<th>
Code
</th>
<th>
Location
</th>
<th>
Name of Water
</th>
<th>
WFD Code
</th>
</tr>
</thead>
<tbody data-bind="template: {name: 'rowTemplate', foreach: items}">
</tbody>
<tfoot>
<tr>
<td colspan="6">
<button class="btn" data-bind="click: table.addRow">Add new Storm Water Overflow</button>
</td>
</tr>
</tfoot>
</table>
<script type="text/html" id="rowTemplate">
<tr>
<td>
<button class="btn" data-bind="click: $parent.table.removeRow "><i class="icon-remove"></i></button>
<button class="btn" data-bind="click: $parent.table.moveUp, disable: ko.computed(function() { return $parent.table.moveUpEnabled.call($parent, $data); }, $parent) "><i class="icon-arrow-up"></i></button>
<button class="btn" data-bind="click: $parent.table.moveDown, disable: ko.computed(function() { return $parent.table.moveDownEnabled.call($parent, $data); }, $parent) "><i class="icon-arrow-down"></i></button>
</td>
<td>
<select data-bind="options: $parent.codes, optionsText: 'name', value: code, optionsCaption: 'Select Code...'"></select>
</td>
<td>
<label data-bind="visible: code, text: location" />
</td>
<td>
<label>E</label><label data-bind="visible: code, text: easting" />
<label>N</label><label data-bind="visible: code, text: northing" />
</td>
<td>
<label data-bind="visible: code, text: waterName" />
</td>
<td>
<label data-bind="visible: code, text: wfdCode" />
</td>
</tr>
</script>
this is not working example – so labels can be spans or whatever is needed.
$parent.table | table – wraps all actions around tables, like adding a row, removing, moving up and down. this works.
Only one cell will be editable – Code. Code will be a dropdown that is initialized by some ajax code in background returning all possible codes and meta-data connected to item – this call is executed before model ko.applyBindings. Result of ajax call will be looking almost like model:
[ {
name: 'some_name_of_code',
code: 'GUID',
location: 'some_place',
easting: '435',
northing: '345',
waterName: 'some_name',
wfdCode: 'some_code'
}, {
//..
}]
Whenever user select an item from Code dropdown, all the properties should be displayed and mapped to model. this.model.items[0].easting will and should return a value.
So when user will click save, I could unwrap model and post it as JSON to MVC action.
update
just in case, as all my knockouts view model are manage by controller there is a std. way of posting model back to server, fragment responsible for it:
save: function () {
var that = this,
model = ko.mapping.toJS(this.model);
delete model.help;
logger.log(this.id + ' SAVE event executing');
return $.ajax({
type: 'POST',
url: this.saveUri,
data: JSON.stringify(model),
contentType: 'application/json; charset=utf-8',
success: function() {
logger.log(that.id + ' SAVE event executed and finished with success, cleaning up dirty flag');
that.model.tracker().markCurrentStateAsClean();
}
});
}
end update
Question
I’m not sure how to achieve what I want without writing lots of custom functions that will set a data – from model returned by action to view model, and from what we have on the view to model that will be posted back to server.
Any help will be appreciated.
UPDATE
END UPDATE
Here’s the working fiddle.
http://jsfiddle.net/madcapnmckay/wgRdj/
Found a couple of things. First your initial viewmodel creation wasn’t using any mapping
configuration so this line.
Created anonymous objects instead of objects of type Item. I changed this to.
Secondly your incoming json has copies of every piece of data from the code object. In order to get the options binding to correctly select the value you would have to be referencing the same exact object instance since js will not be able to tell two objects apart even with identical values. There are a number of ways this could have been done but inorder to keep your existing json structure I simply changed the binding to refrence just the codeid instead of the whole object and made the other item values derive from that.
This has the advantage of only needing the id being passed in the json initially.
Hope this helps.