Config
Table of Contents

Class

O.LiveQuery

Extends
O.Object
Includes
O.ObserverableRange, O.Enumerable

A LiveQuery instance can be treated as an observable array which automatically updates its contents to reflect a certain query on the store. A query consists of a particular type, a filter function and a sort order. Normally you will not create a LiveQuery instance yourself but get it by retrieving the query from the store.

"use strict";

( function ( NS ) {

var Status = NS.Status,
 READY = Status.READY,
 DESTROYED = Status.DESTROYED;

var numerically = function ( a, b ) {
 return a - b;
};

var LiveQuery = NS.Class({

 Extends: NS.Object,

 Mixin: [ NS.ObservableRange, NS.Enumerable ],

Property

O.LiveQuery#id

  • String

A unique identifier for this query.

id: function () {
   return NS.guid( this );
 }.property().nocache(),

Private Property

O.LiveQuery#_filter

  • (Function|null)
  • private

The function to filter data objects with.

Private Property

O.LiveQuery#_sort

  • (Function|null)
  • private

The function to sort the data objects with.

Property

O.LiveQuery#store

The store to query for records.

Property

O.LiveQuery#Type

The Record class constructor function for the type of the instances to include in this query.

Property

O.LiveQuery#status

Status of the query: READY|DESTROYED

Constructor

O.LiveQuery( mixin )

The following properties should be configured:

storeO.Store The store to query for records.
TypeO.Class The constructor for the record type this query is a collection of.
filterFunction Optional If supplied, only records which this function returns a truthy value for are included in the results.
sort{(String|String[]|Function)} Optional The records in the local query are sorted according to this named property. If an array is supplied, in the case of a tie the next property in the array will be consulted. If a function is supplied, this is used as the sort function directly on the records. If nothing is supplied, the results are not guaranteed to be in any particular order.

Parameters

mixinObject The properties for the query.
init: function ( mixin ) {
   var Type = mixin.Type,
     sort = mixin.sort,
     store = mixin.store || this.store,
     results;

   if ( sort && !( sort instanceof Function ) ) {
     sort = mixin.sort = NS.sortByProperties( sort );
   }
   results = store.findAll( Type, mixin.filter, sort );

   this._storeKeys = results;
   this._sort = results.sortFn;
   this._filter = results.filterFn;

   this.status = store.getTypeStatus( Type ) & READY;

   this.length = results.length;

   LiveQuery.parent.init.call( this, mixin );

   store.addQuery( this );
 },

Method

O.LiveQuery#destroy()

Call this method when you have finished with a local query to ensure it does not continue monitoring the store for changes and can be garbage collected.

destroy: function () {
   this.set( 'status', DESTROYED );
   this.get( 'store' ).removeQuery( this );
   LiveQuery.parent.destroy.call( this );
 },

Method

O.LiveQuery#is( status )

Checks whether the query has a particular status.

Parameters

statusO.Status The status to check.

Returns

Boolean True if the record has the queried status.

is: function ( status ) {
   return !!( this.get( 'status' ) & status );
 },

Property

O.LiveQuery#[]

  • Array

A standard array of record objects for the records in this query.

'[]': function () {
   var store = this.get( 'store' ),
     Type = this.get( 'Type' );
   return this._storeKeys.map( function ( storeKey ) {
     return store.materialiseRecord( storeKey, Type );
   });
 }.property().nocache(),

Property

O.LiveQuery#length

  • Number

The number of records in the query.

Method

O.LiveQuery#indexOfId( id, from, callback )

Finds the index of an id in the query. If the id is not found, the index returned will be -1.

Parameters

idString The record id to find.
fromNumber The first index to start the search from. Specify 0 to search the whole list.
callbackFunction Optional A callback to make with the id. For compatibility with .

Returns

Number The index of the id, or -1 if not found.

indexOfId: function ( id, from, callback ) {
   var record = this.get( 'store' ).getRecord( this.get( 'Type' ), id ),
     index = -1,
     storeKey;

   if ( record.is( READY ) ) {
     storeKey = record.get( 'storeKey' );
     index = this._storeKeys.indexOf( storeKey, from );
   }
   if ( callback ) {
     callback( index );
   }
   return index;
 },

Method

O.LiveQuery#getObjectAt( index )

Returns the record at the index given in the query.

Parameters

indexNumber The index of the record to return.

Returns

O.Record The record at index i in this array.

getObjectAt: function ( index ) {
   var storeKey = this._storeKeys[ index ],
     record;
   if ( storeKey ) {
     record = this.get( 'store' )
            .materialiseRecord( storeKey, this.get( 'Type' ) );
   }
   return record;
 },

Method

O.LiveQuery#refresh( force )

Asks the store to refresh the data for the type used in this query.

Parameters

forceBoolean Optional If true, the store will refresh the data even if it thinks it is up to date.

Returns

O.LiveQuery Returns self.

refresh: function ( force ) {
   this.get( 'store' ).fetchAll( this.get( 'Type' ), force );
   return this;
 },

Method

O.LiveQuery#reset()

Recalculate the set of matching results.

Returns

O.LiveQuery Returns self.

reset: function () {
   var oldStoreKeys = this._storeKeys,
     storeKeys = this.get( 'store' ).findAll(
       this.get( 'Type' ), this.filter, this.sort ),
     maxLength = Math.max( storeKeys.length, oldStoreKeys.length );

   this._storeKeys = storeKeys;

   return this
     .beginPropertyChanges()
       .set( 'length', storeKeys.length )
       .rangeDidChange( 0, maxLength )
     .endPropertyChanges()
     .fire( 'query:reset' );
 },

Method

O.LiveQuery#storeDidChangeRecords( storeKeysOfChanged )

Callback made by the store when there are changes that affect the query. The query calculate the changes to make and fire any necessary observers/events.

Parameters

storeKeysOfChangedArray List of store keys that have changed which are of the type included in this query.

Returns

Boolean Was there a change in the query results?

storeDidChangeRecords: function ( storeKeysOfChanged ) {
   var filter = this._filter,
     sort = this._sort,
     storeKeys = this._storeKeys,
     added = [], addedIndexes = [],
     removed = [], removedIndexes = [],
     oldLength = this.get( 'length' ),
     store = this.get( 'store' ),
     storeKeyToId = function ( storeKey ) {
       return store.getIdFromStoreKey( storeKey ) ||
         ( '#' + storeKey );
     },
     i, l, storeKey, index, shouldBeInQuery,
     newStoreKeys, oi, ri, ai, a, b,
     addedLength, removedLength, newLength, maxLength;

   // 1. Find indexes of removed and ids of added
   // If it's changed, it's added to both.
   l = storeKeysOfChanged.length;
   while ( l-- ) {
     storeKey = storeKeysOfChanged[l];
     index = storeKeys.indexOf( storeKey );
     shouldBeInQuery = ( store.getStatus( storeKey ) & READY ) &&
       ( !filter || filter( storeKey ) );
     // If in query
     if ( index > -1 ) {
       // And should be in query
       if ( shouldBeInQuery ) {
         // If there's a sort
         if ( sort ) {
           removedIndexes.push( index );
           added.push( storeKey );
         }
       }
       // And shouldn't be in query
       else {
         removedIndexes.push( index );
       }
     }
     // If not in query
     else {
       // But should be
       if ( shouldBeInQuery ) {
         added.push( storeKey );
       }
     }
   }

   removedLength = removedIndexes.length;
   addedLength = added.length;

   // 2. Sort removed indexes and find removed ids.
   if ( removedLength ) {
     removedIndexes.sort( numerically );
     for ( i = 0; i < removedLength; i += 1 ) {
       removed[i] = storeKeys[ removedIndexes[i] ];
     }
   }

   // 3. Construct new array of store keys by merging sorted arrays
   if ( addedLength || removedLength ) {
     if ( addedLength && sort ) {
       added.sort( sort );
     }
     newLength = oldLength - removedLength + addedLength;
     newStoreKeys = new Array( newLength );
     for ( i = 0, oi = 0, ri = 0, ai = 0; i < newLength; i += 1 ) {
       while ( ri < removedLength && oi === removedIndexes[ ri ] ) {
         ri += 1;
         oi += 1;
       }
       if ( sort && oi < oldLength && ai < addedLength ) {
         a = storeKeys[ oi ];
         b = added[ ai ];
         if ( sort( a, b ) < 0 ) {
           newStoreKeys[i] = a;
           oi += 1;
         } else {
           newStoreKeys[i] = b;
           addedIndexes[ ai ] = i;
           ai += 1;
         }
       } else if ( oi < oldLength ) {
         newStoreKeys[i] = storeKeys[ oi ];
         oi += 1;
       } else {
         newStoreKeys[i] = added[ ai ];
         addedIndexes[ ai ] = i;
         ai += 1;
       }
     }
   }

   // 4. Sort added/addedIndexes arrays by index
   if ( addedLength ) {
     addedIndexes.sort( numerically );
     for ( i = 0; i < addedLength; i += 1 ) {
       added[i] = newStoreKeys[ addedIndexes[i] ];
     }
   }

   // 5. Check if there are any redundant entries in the added/removed
   // lists
   l = Math.min( addedLength, removedLength );
   while ( l-- ) {
     if ( added[l] === removed[l] &&
         addedIndexes[l] === removedIndexes[l] ) {
       added.splice( l, 1 );
       addedIndexes.splice( l, 1 );
       removed.splice( l, 1 );
       removedIndexes.splice( l, 1 );
       addedLength -= 1;
       removedLength -= 1;
     }
   }

   // 6. If there was an actual change, notify observers.
   if ( addedLength || removedLength ) {
     this._storeKeys = newStoreKeys;
     maxLength = Math.max( newLength, oldLength );
     this.beginPropertyChanges()
       .set( 'length', newLength )
       .rangeDidChange(
         Math.min(
           addedLength ? addedIndexes[0] : maxLength,
           removedLength ? removedIndexes[0] : maxLength
         ),
         newLength === oldLength ?
           Math.max(
             addedLength ?
               addedIndexes[ addedLength - 1 ] : 0,
             removedLength ?
               removedIndexes[ removedLength - 1 ] : 0
           ) + 1 :
           maxLength
       )
       .endPropertyChanges()
       .fire( 'query:updated', {
         removed: removed.map( storeKeyToId ),
         removedIndexes: removedIndexes,
         added: added.map( storeKeyToId ),
         addedIndexes: addedIndexes
       });
     return true;
   }
   return false;
 },

Method

O.LiveQuery#getIdsForObjectsInRange( start, end, callback )

Get a callback with an array of the id properties for all objects in the range given.

Parameters

startNumber The index of the first object to get an id for.
endNumber One past the index of the last object to get an id for.
callbackFunction This will be called with the array of ids as the first argument, the index of the first returned result as the second argument, and one past the index of the last result as the third argument.

Returns

Boolean Always false. Represents whether the data is still loading (i.e. whether the callback has yet to be fired).

getIdsForObjectsInRange: function ( start, end, callback ) {
   start = Math.max( 0, start );
   end = Math.min( this.get( 'length' ), end );
   var store = this.get( 'store' );
   callback( this._storeKeys.slice( start, end )
                .map( function ( storeKey ) {
     return store.getIdFromStoreKey( storeKey ) || ( '#' + storeKey );
   }), start, end );
   return false;
 },

Method

O.LiveQuery#getIdsForAllObjects( callback )

Get a callback with an array of the id properties for all objects in the array.

Parameters

callbackFunction This will be called with the array of ids as the first argument, the index of the first returned result as the second argument, and one past the index of the last result as the third argument.

Returns

Boolean Always false. Represents whether the data is still loading (i.e. whether the callback has yet to be fired).

getIdsForAllObjects: function ( callback ) {
   // 0x7fffffff is the largest positive signed 32-bit number.
   return this.getIdsForObjectsInRange( 0, 0x7fffffff, callback );
 }
});

NS.LiveQuery = LiveQuery;

}( O ) );
Animation
Application
Core
DataStore
DOM
DragDrop
Foundation
IO
Localisation
Selection
Parser
TimeZones
Storage
Touch
CollectionViews
UA
ContainerViews
ControlViews
PanelViews
View