• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • Work with all your cloud files (Drive, Dropbox, and Slack and Gmail attachments) and documents (Google Docs, Sheets, and Notion) in one place. Try Dokkio (from the makers of PBworks) for free. Now available on the web, Mac, Windows, and as a Chrome extension!



Page history last edited by BorisMoore 10 years, 3 months ago

Observable Arrays and Observable Objects

Support for Observable Arrays and Observable Objects is provided by jquery.observable.js


It provides a way of mutating JavaScript arrays or objects in a way that raises corresponding change events, which other code may be listening for. This is key to providing reactive UI driven by data changes, and can be used in many other scenarios, such as creating changesets of data-changes to be round-tripped to the server via JSON services or similar.


Observable data is used by data linking and by JsViews in order to enable interactive data-driven views. JsViews integrates jQuery templates and data link, so that observable changes to data can automatically trigger updates in UI rendered by jQuery templates. Use of JsViews by the Grid can enable HTML rendered by the grid to update whenever the data is sorted, filtered, or mutated, without additional code. Where appropriate, the update can be incremental, without the performance hit of re-rendering the whole list of items.


Observable API


Usually you just want to make one or more observable changes to an object or an array:

$.observable( myObject ).setField( "someField", value );
$.observable( myArray ).pop();


$.observable( data ) gives you as set of mutation methods for your data - based on whether it is an array or an object.


If you want, can hold on to it, as in

var myObservableArray = $.observable( myArray );
myObservableArray.push( item ).shift();


If needed you can get your raw data item back:

myArray === myObservableArray.data(); // true


...But usually you won't bother. Just use $.observable( data ).foo() each time.


Supported mutation methods


pop, push, reverse, shift, unshift, sort, splice. (Signatures identical to standard array methods) 

move( fromIndex, numItems, toIndex)

replace( newItems )  



setField( path, value )

Note: path can be the field name, or can drill into child objects, as in setField( "address.city", "Seattle" );


Associated change events



Note: associated eventArgs provides the method name and associated data or indexes, such as:

{ change: "add", newItems: [addedItem] } 



Note: associated eventArgs provides the path and the value

{ path: "address.city", value: "Seattle" } 



You can add methods for arrays or objects by extending $.observable.array or $.observable.object...


Example (This is how the replace method was added in jquery.observable.js):

    replace: function( newItems ) {
        this.splice.apply( this, [].concat( 0, this.data().length, newItems ));
        return this;


jQuery Templates Integration

The JsViews and JsRender implementation of jQuery templates allows the following:


// Get concatenated string obtained by applying the template to the data item (if object) or items (if array).
var htmlString = $( "#myTemplate" ).render( myData );

// Insert the html into the DOM
$( "#myContainer" ).html( htmlString ); 

// Activate the rendered HTML elements
$( "#myContainer" ).dataLink( myData );


The effect of data linking (activation) is to add bindings to both the data and the rendered HTML, so that: 

  1. If myData is and array, then observable changes to the array will cause the templates engine to render incrementally into to the DOM to remain in sync with the data array
  2. If myData is an object, then declarative data linking in the template allows fields in the HTML to update when observable changes are made to the field of the data
  3. Declarative data linking also allows two-way binding to apply so user value entered in inputs etc. can immediately propagate to the underlying data
  4. The above binding applies also to nested template, arrays of data, and hierarchical data, so that values of fields on data items in an array, or modifications to nested arrays will also propagate without additional code being required 


As a result, the following code will immediately trigger incremental insertion of a new rendered item under "#myContainer" above:


// Add item and trigger any rendered templates in the DOM to insert corresponding row
$.observable( myData ).push( item );


Grid and DataSource Integration


Key changes to DataSource:

 Listen to observable changes on input data (local data source):


$.dataLink( this.options.input, function() {


Make observable changes when changing 'output' data:


this.options.source( request, function( data, totalCount ) {
    $.observable( that ).setField( "totalCount", totalCount );
    $.observable( that.options.data ).replace( data );


Key changes to Grid:

 Grid depends only on data array, not on DataSource, and responds to observable changes on that data:


refresh: function() {
    tbody.html( $.render( template, source ))
        .dataLink( source );





The grids reflect the changes to the data on paging etc., as a result of observable collection events integrated with templates (without additional code).


Demo: Grid and Observable Array



Key code:

$( "#developers-data" ).grid({
    selectMode: "single",
    columns: [ "firstName", "lastName", "country" ],
    source: developersInput

function deleteSelectedDeveloper() {
    $.observable( developersInput ).splice( index, 1 );
        developersDataGrid.select( prevIndex );


The grid reflects the changes, as a result of observable collection events.


Demo: Grid and Editable Data



Key code:

$( "#developers-data" ).grid({
    selectMode: "single",
    columns: [ "firstName", "lastName", "country" ],
    source: developersInput,
    select: onSelectChange

function onSelectChange( event, args ) {
    $( "#developerDetail" )
            $( "#detailViewTemplate" ).render( selectedItem.data )
        .dataLink( selectedItem.data );
<script id="detailViewTemplate" type="text/x-jquery-tmpl">
    <input data-jq-linkfrom="firstName" data-jq-linkto="firstName" value="${firstName}"/>


Full editing through Master-Detail UI. The grid reflects the changes, as a result of observable events.


Demo: Multiple Views



  • Both grids reflect the changes, as a result of observable events. 
  • The selection in the two grids are coupled.
  • Even after sorting/filtering/paging the first grid, coupling is maintained, without additional code. 

Full set of demos:



Follow-on work


I've been collaborating with Boris to refine $.observable in ways that are important to Grid (and to jQuery generally).  We'll post this work (including demos) to the wiki soon.


The aspects we're covering are:

  • Editing capabilities -- The $.observable API can be extended to include a means of determining what edit operations are supported by the underlying data.  The Grid control can light up a feature like "insert row" by calling $.observable(rowData).canInsert()...or "drag/drop to reorder rows" by calling $.observable(rowData).canMove().
  • Collecting data edits to submit to server -- We're looking at adding an extensibility point to $.observable that would allow a data loader/cache/store piece to subclass $.observable for a given data array or object.  One expected use is to override the editing capabilities of an observable to match what edits the data's backing store will permit (see "Editing capabilities" above).  Another expected use is to subclass in order to intercept such editing calls over $.observable, collect these data edits and submit them to a backing store.
  • Event dispatch customization --  Currently, $.observable dispatches data change events using $.triggerHandler directly.  Applications might want to customize event dispatch, for instance, to make it asynchronous, to coalesce events, to batch events.  We're looking at adding extensibility to $.observable to allow for this.


-- Brad Olenick (brado@microsoft.com)


Comments (0)

You don't have permission to comment on this page.