I'm trying to execute callback on list of items to make pagination using DataTable. Now i want to execute my callback after all my items have been rendered not after each item is rendered. Following that SO question i created simple custom binding
ko.bindingHandlers.ConvertToDataTable = {
update: function (element, valueAccessor, allBindings) {
var list = ko.utils.unwrapObservable(valueAccessor()); //grab a dependency to the obs array
var tableId = allBindings().tableId;
$('#' + tableId).dataTable({
"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>"
});
}
}
My view is:
<table class="table table-striped table-bordered" data-bind="attr: { id: tableId }">
<thead>
<tr>
<td>Task Name</td>
<td>Task Description</td>
</tr>
</thead>
<tbody>
<!-- ko foreach: tasks, ConvertToDataTable: tasks, tableId: tableId -->
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: Details"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
That code works fine but with small number of items. but incase of big number of recoreds(e.g 500) the custom binding is executed before my items are fully renedered..Any Ideas?
Update
I get my data from server via AJAX, so maybe my custom binding is executed first while my observable array items
is empty
From a logical point of view, shouldn't the ConvertToDataTable
binding be on the table itself, instead on the foreach
?
Also, shouldn't you control table layout via the binding or the view model? The custom binding is a very bad place for hard-coded values.
Anyway, controlsDescendantBindings
is your friend (docs):
Custom Binding:
ko.bindingHandlers.dataTable = {
init: function () {
return { controlsDescendantBindings: true };
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var layout = valueAccessor();
ko.applyBindingsToDescendants(bindingContext, element);
$(element).dataTable({ "sDom": layout });
}
};
View Model:
{
dataTableLayout: "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
tasks: ko.observableArray([/* ... */])
}
Template:
<table data-bind="dataTable: dataTableLayout">
<thead>
<tr>
<td>Task Name</td>
<td>Task Description</td>
</tr>
</thead>
<tbody data-bind="foreach: tasks">
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: Details"></td>
</tr>
</tbody>
</table>
http://jsfiddle.net/WcaM5/
Disclaimer: I don't know exactly how jQuery DataTables work, so the sample is aircode. The point I want to make is that you can take manual control over the binding if necessary.
Here is a custom binding that I use! This covers any datatables options that you want to define in the binding...
ko.bindingHandlers.dataTablesForEach = {
page: 0,
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.unwrap(valueAccessor());
ko.unwrap(options.data);
if(options.dataTableOptions.paging){
valueAccessor().data.subscribe(function (changes) {
var table = $(element).closest('table').DataTable();
ko.bindingHandlers.dataTablesForEach.page = table.page();
table.destroy();
}, null, 'arrayChange');
}
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, allBindings, viewModel, bindingContext) {
var options = ko.unwrap(valueAccessor()),
key = 'DataTablesForEach_Initialized';
ko.unwrap(options.data);
var table;
if(!options.dataTableOptions.paging){
table = $(element).closest('table').DataTable();
table.destroy();
}
ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);
table = $(element).closest('table').DataTable(options.dataTableOptions);
if (options.dataTableOptions.paging) {
if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page == 0)
table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false);
else
table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false);
}
if (!ko.utils.domData.get(element, key) && (options.data || options.length))
ko.utils.domData.set(element, key, true);
return { controlsDescendantBindings: true };
}
};
JSFIDDLE