DataTables with Knockout Observables

Preamble

An example of how DataTables 1.10 can be used with Knockout.JS observables. The table below will automatically update as the values of the underlying data in the Knockout observableArray is updated, new rows added and old rows deleted.

Live example

ID Name Age

Initialisation code

// Helper function so we know what has changed
// http://stackoverflow.com/questions/12166982
ko.observableArray.fn.subscribeArrayChanged = function(addCallback, deleteCallback) {
    var previousValue = undefined;
    this.subscribe(function(_previousValue) {
        previousValue = _previousValue.slice(0);
    }, undefined, 'beforeChange');
    this.subscribe(function(latestValue) {
        var editScript = ko.utils.compareArrays(previousValue, latestValue);
        for (var i = 0, j = editScript.length; i < j; i++) {
            switch (editScript[i].status) {
                case "retained":
                    break;
                case "deleted":
                    if (deleteCallback)
                        deleteCallback(editScript[i].value);
                    break;
                case "added":
                    if (addCallback)
                        addCallback(editScript[i].value);
                    break;
            }
        }
        previousValue = undefined;
    });
};



// Person object
var Person = function(data, dt) {
    this.id    = data.id;
    this.first = ko.observable(data.first);
    this.last  = ko.observable(data.last);
    this.age   = ko.observable(data.age);
    this.full  = ko.computed(function() {
        return this.first() + " " + this.last();
    }, this);     

    // Subscribe a listener to the observable properties for the table
    // and invalidate the DataTables row when they change so it will redraw
	var that = this;
	$.each( [ 'first', 'last', 'age' ], function (i, prop) {
		that[ prop ].subscribe( function (val) {
			// Find the row in the DataTable and invalidate it, which will
			// cause DataTables to re-read the data
			var rowIdx = dt.column( 0 ).data().indexOf( that.id );
			dt.row( rowIdx ).invalidate();
		} );
	} );
};


// Initial data set
var data = [
    { id: 0, first: "Allan", last: "Jardine", age: 86 },
    { id: 1, first: "Bob", last: "Smith", age: 54 },
    { id: 2, first: "Jimmy", last: "Jones", age: 32 }
];


$(document).ready(function() {
	var people = ko.mapping.fromJS( [] );
	var dt = $('#example').DataTable( {
		columns: [
			{ data: 'id' },
			{ data: 'first()' },
			{ data: 'age()' }
		]
	} );

	// Update the table when the `people` array has items added or removed
	people.subscribeArrayChanged(
		function ( addedItem ) {
			dt.row.add( addedItem ).draw();
		},
		function ( deletedItem ) {
			var rowIdx = dt.column( 0 ).data().indexOf( deletedItem.id );
			dt.row( rowIdx ).remove().draw();
		}
	);

	// Convert the data set into observable objects, and will also add the
	// initial data to the table
	ko.mapping.fromJS(
		data,
		{
			key: function(data) {
				return ko.utils.unwrapObservable(data.id);        
			},
			create: function(options) {
				return new Person(options.data, dt);
			}    
		},
		people
	);


	

	// Examples:
	
	// Update a field
	people()[0].first( 'Allan3' );

	// Add an item
	people.push( new Person( {
		id: 3,
		first: "John",
		last: "Smith",
		age: 34
	}, dt ) );

	// Remove an item
	people.shift();
} );