Skip to content

Instantly share code, notes, and snippets.

@smonn
Last active August 29, 2015 14:09
Show Gist options
  • Save smonn/bf5e2bf80de37347d9b5 to your computer and use it in GitHub Desktop.
Save smonn/bf5e2bf80de37347d9b5 to your computer and use it in GitHub Desktop.
knockout.js binding handler for http://www.datatables.net
/*global jQuery: false, ko: false */
(function ($, ko) {
'use strict';
// based on the work by http://chadmullins.com/
// some caveats to be aware of:
// - x-editables: when clicking a sortable column the editable-unsaved css
// class gets removed since each row is re-rendered
// - data-bind on <tr/> is not supported (yet)
// - avoid data-bind on thead, tbody, etc, it won't work
ko.bindingHandlers.dataTable = {
// used for keeping track of table initialization
initializedKey: '__ko_bindingHandlers_dataTable_initialized',
// used for caching a row
rowIndexKey: '__ko_bindingHandler_dataTable_rowIndexKey',
// init method...
init: function (element, valueAccessor) {
var initializedKey = ko.bindingHandlers.dataTable.initializedKey,
rowIndexKey = ko.bindingHandlers.dataTable.rowIndexKey,
result = {
controlsDescendantBindings: true
},
rawData = [],
oldRowCallback,
column,
options,
dataSource,
dataTable,
il,
i;
// already initialized
if ($.data(element, initializedKey) === true) {
return result;
}
// get the data table configuration
options = ko.utils.unwrapObservable(valueAccessor());
if (!options.columns) {
throw 'Must provide the columns option.';
}
// convert the source data observable into a simple array
// and handle changes to the source data
if (options.data && ko.isObservable(options.data) && $.type(options.data()) === 'array') {
dataSource = options.data;
options.data = dataSource();
// cannot use subscribeArrayChanged since the source observable
// might be a computed observable
dataSource.subscribe(function (prevValue) {
// store the previous value for later comparison
rawData = prevValue.slice(0);
}, undefined, 'beforeChange');
dataSource.subscribe(function (newValue) {
var diff = ko.utils.compareArrays(rawData, newValue),
data,
row;
// remove/add rows according to the changes
for (i = 0, il = diff.length; i < il; i += 1) {
data = diff[i];
switch (data.status) {
case 'deleted':
row = data.value[rowIndexKey] && data.value[rowIndexKey].row;
if (row) {
dataTable.row(row).remove().draw();
}
break;
case 'added':
dataTable.row.add(data.value).draw();
break;
}
}
});
} else {
throw 'The data source must be an observable and evaluate to an array.';
}
// setup rendering with the required row callback
if (options.rowTemplate) {
oldRowCallback = options.rowCallback;
options.rowCallback = function (row, data) {
var updateCell = function (columnIndex) {
return function () {
var rowIndex = dataTable.row(row).index();
dataTable.cell(rowIndex, columnIndex).invalidate();
};
};
if (typeof oldRowCallback === 'function') {
// usually not required to provide a custom callback
// for ko bindings since they're handled by this
// binding handler internally
oldRowCallback.apply(this, Array.prototype.slice.call(arguments));
}
// overwrite the row content with our own template
ko.renderTemplate(options.rowTemplate, data, null, row, 'replaceChildren');
// in order to keep the datatable data in sync with the
// source data, we must invalidate the related cell data as
// it gets updated, we do this on a cell-by-cell basis
for (i = 0, il = options.columns.length; i < il; i += 1) {
column = options.columns[i];
if (column.data && data[column.data] && ko.isObservable(data[column.data])) {
data[column.data].subscribe(updateCell(i));
}
}
// cache to row for later use
data[rowIndexKey] = function () {
return;
};
data[rowIndexKey].row = row;
return row;
};
} else {
throw 'No row template was provided.';
}
// initialize the DataTable!
dataTable = $(element).DataTable(options);
$.data(element, initializedKey, true);
return result;
}
};
}(jQuery, ko));
<!-- include knockout.js and the dataTable binding handler for this to work -->
<table data-bind="dataTable: { data: people, columns: [ { data: 'name' }, { data: 'age' } ], rowTemplate: 'row-template' }">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
</table>
<script id="row-template" type="text/html">
<td><span data-bind="text: name"></span></td>
<td><span data-bind="text: age"></span></td>
</script>
<script>
var viewModel = {
// source data must be an observable!
people: ko.observableArray([
{ name: 'Bob', age: 45 },
{ name: 'Albert', age: 39 }
])
};
ko.applyBindings(viewModel);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment