Reputation: 1961
Here's JS code:
function ProductViewModel() {
// Init.
var self = this;
self.products = ko.observableArray();
self.singleProduct = ko.observable();
var mappedProducts;
// Initialize table here.
$.getJSON("/admin/test", function(allData) {
mappedProducts = $.map(allData, function(item) { return new Product(item);});
self.products(mappedProducts);
self.oTable = $('#products-table').dataTable( {
"aoColumns": [
{ "bSortable": false, "mDataProp": null, sDefaultContent: '' },
{"mData": "name"},
{"mData": "dealer"},
{"mData": "cost"},
{"mData": "price"},
{ "bSortable": false, sDefaultContent: '' }
],
});
});
// Here i'm using the basic switch pattern, as from KO tutorials.
// This is intended for showing a single product form.
self.edit = function(product) {
self.singleProduct(product);
}
// This is intended to hide form and show list back.
self.list = function() {
self.singleProduct(null);
}
// This is the form save handler, actually does nothing
// but switch the view back on list.
self.doEdit = function(product) {
self.list();
}
}
// My model.
function Product(item) {
this.name = ko.observable(item.name);
this.dealer = ko.observable(item.dealer);
this.cost = ko.observable(item.cost);
this.price = ko.observable(item.price);
this.picture = ko.observable();
}
Here's my markup:
<table id="products-table" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Pic</th>
<th>Name</th>
<th>Dealer</th>
<th>Cost</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: $parent.products">
<tr>
<td><span data-bind='ifnot: picture'>-</span></td>
<td><a data-bind="text: name"></a></td>
<td><span data-bind='text: dealer'></span></td>
<td><span data-bind='text: cost'></span></td>
<td><span data-bind='text: price'></span></td>
<td>
<button data-bind='click: $root.edit'><i class='icon-pencil'></i>
</button>
</td>
</tr>
</tbody>
</table>
When i do click on the edit button, triggering the $root.edit handler, a form is shown because of a
<div data-bind='with: singleProduct'>
binding i have made. Inside this binding i have a form, with a
<input data-bind="value: name" type="text" id="" placeholder="Name"
> class="col-xs-10 col-sm-5">
field.
Problem: When i edit the value in the input field, the relative row in the datatable is not updated. I have tried a basic table without the datatables plugin and it does work, meaning that if i change value, the row in the table is properly updated.
What's wrong here?
== EDIT ==
I found out that moving the bind point to the table TD fixed the problem, though still i can't figure out why.
<tr>
<td data-bind="text: name"></td>
<!-- More columns... -->
</tr>
The above code is working properly now. Why ?
== EDIT2 ==
Now that i fixed first issue, comes the second. I implemented my "save new" method like so
self.doAdd = function(product) {
$.ajax("/product/", {
data: ko.toJSON({ product: product }),
type: "post", contentType: "application/json",
success: function(result) { alert('ehh'); }
}).then(function(){
self.products.push(product); // <--- Look at this!
self.list();
});
}
The self.products.push(product); in the success handler is properly updating my products observable. Then, a new row is automatically added to my table, and this is the good news.
Bad news is that datatables controls, such search field or clickable sorting arrows, disappear as soon as i push the new product in the array. Why so!?
Upvotes: 1
Views: 2251
Reputation: 140
http://jsfiddle.net/zachpainter77/4tLabu56/
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function (node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function () {
return {
data: value.data || value,
beforeRenderAll: function (el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function (el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return { controlsDescendantBindings: true };
}
};
https://rawgit.com/zachpainter77/zach-knockout.js/master/zach-knockout.debug.js
Upvotes: 0
Reputation: 189
Did you ever resolve this?
I've been having similar issues for ages.
In the end my fix was to map all my entities using ko.mapping.fromJS(entity) - this then hooked up all the required dependencies and ensured that any changes flowed through my model.
Upvotes: 1