Here is my view model.
Server side:
public class ShoppingListModel
{
public string Name { get; set; }
public List<ItemModel> Items{ get; set; }
public ShoppingListModel()
{
Items=new List<ItemModel>();
}
}
On the client side, I use knockout.mapping
.
ShoppingListModel = function(data) {
var vm = ko.mapping.fromJSON(data);
return vm;
};
To bind the server-side model to the client-side model:
@{
var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var data = new JavaScriptSerializer().Serialize(JsonConvert.SerializeObject(Model, jsonSerializerSettings));
}
@section scripts {
<script src="~/App/ShoppingListModel.js"></script>
<script>
var vm = ShoppingListModel(@Html.Raw(data));
ko.applyBindings(vm);
</script>
}
The code above:
JSON.NET
to serialize the server-side Model
into a camel cased json. Now I want to take advantage of the two-way binding.
First I tested on the Name
:
@Html.HiddenFor(model => model.Name, new { @data_bind="value:name"})
<input type="text" data-bind="value:name"/>
It went well, I was able to edit the Name
value on a text input
and persist the value into the hidden input
. The updated value could reach the POST action when the form is submitted.
Now the question: how to implement the binding on the list?
My test is to remove one item from the 'Items' list:
@Html.HiddenFor(model => model.Items, new { data_bind = "value: items" })
<tbody data-bind="foreach:items">
<tr>
<td>
<span data-bind="text:name"></span>
</td>
<td><span data-bind="text:count"></span></td>
<td>
<button class="btn btn-xs" data-bind="click:$parent.remove">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
A console.log()
tells me that the client-side model has been updated, but this time, the binding on the HiddenFor
has never worked! When the form is submitted, Items
is always null.
I guess it is reasonable because in Html:
<input type="hidden" value="xxx" />
we are expecting the value of an input to be a simple value.
I was thinking about loop through the list and data-bind from there. But it is also difficult. The knockout foreach
is on tbody
tag while a C# foreach
is placed around tr
(inside tbody
).
Then what is the correct way to bind the list?
Here is my solution based on Fabio's suggestion:
add a computed value to bind the list into a json string.
vm.itemsJson = ko.computed(function() {
return ko.toJSON(vm.items);
},this);
add a hidden input to hold the json string and post with our form.
<input name="itemsjson" type="hidden" data-bind="value:itemsJson"/>
besides
public List<ItemModel> Items{ get; set; }
Add another string property to hold the posted json.
public string ItemsJson { get; set; }
At this point, we are able to see the ItemsJson
value successfully sent to the controller action.
Since it is typed model, we are to use JSON.Net
to deserialize.
var items=JArray.Parse(model.ItemsJson);
model.Items = items.
Select(i => new ItemModel {Name = (string) i["name"], Count = (int) i["count"]})
.ToList();
return View(model);
Be sure to use JArray.Parse()
for a List
instead of JObject.Parse()
.
It works.
Let's see if there is any better way than manually parsing json string. Otherwise, I would mark Fabio's answer as our solution after this weekend.
You could use computed observables to bind the hidden field, like this:
function YourViewModel() {
var self = this;
self.items = ko.observableArray(); //fill it with your stuff;
self.valueForHiddenField = ko.computed(function() {
return ko.toJSON(self.items);
}, this); //use this observable as value of your hidden field
}
For more information http://knockoutjs.com/documentation/json-data.html
EDIT 1
You don't need to convert the json inside your controller. Instead sending a json to your server, send the collection using list of hidden fields. Like this:
<form>
<!-- ko foreach: items -->
<input type="hidden" data-bind="value: property1, attr: { name: 'Items[' + $index() + '].Property1' }">
<input type="hidden" data-bind="value: property2, attr: { name: 'Items[' + $index() + '].Property2' }">
<!-- /ko -->
</form>
Then you can send the post, and don't need to worry about the collection, it will refresh automatically when you changed the items observableArray.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.