Config
Table of Contents

Method

Function#observes( )

Defines the list of properties (on the same object) or paths (relative to this object) that this method is interested in. Whenever one of these properties changes, the method will automatically be called.

Parameters

var_args...String All arguments are treated as the names of properties this method should observe.

Returns

Function Returns self.

"use strict";

( function ( NS, undefined  ) {

var meta = NS.meta;

var setupObserver = function ( metadata, method ) {
 var observes = this.observedProperties,
   observers = metadata.observers,
   l = observes.length,
   key, keyObservers, pathObservers;

 while ( l-- ) {
   key = observes[l];
   if ( key.indexOf( '.' ) === -1 ) {
     keyObservers = observers[ key ];
     if ( !observers.hasOwnProperty( key ) ) {
       keyObservers = observers[ key ] = keyObservers ?
         keyObservers.slice() : [];
     }
     keyObservers.push({ object: null, method: method });
   } else {
     if ( !pathObservers ) {
       pathObservers = metadata.pathObservers;
       if ( !metadata.hasOwnProperty( 'pathObservers' ) ) {
         pathObservers =
           metadata.pathObservers = Object.create( pathObservers );
       }
       // There can't be any existing path observers for this method,
       // as we're only just adding it (and if we're overriding a
       // previous method, we should have removed all of their path
       // observers first anyway).
       pathObservers = pathObservers[ method ] = [];
       metadata.inits.Observers =
         ( metadata.inits.Observers || 0 ) + 1;
     }
     pathObservers.push( key );
   }
 }
};

var teardownObserver = function ( metadata, method ) {
 var observes = this.observedProperties,
   observers = metadata.observers,
   l = observes.length,
   key, keyObservers, observer, j, pathObservers;

 while ( l-- ) {
   key = observes[l];
   if ( key.indexOf( '.' ) === -1 ) {
     keyObservers = observers[ key ];
     if ( !observers.hasOwnProperty( key ) ) {
       keyObservers = observers[ key ] = keyObservers.slice();
     }
     j = keyObservers.length;
     while ( j-- ) {
       observer = keyObservers[j];
       if ( observer.object === null &&
           observer.method === method ) {
         keyObservers.splice( j, 1 );
         break;
       }
     }
   } else {
     if ( !pathObservers ) {
       pathObservers = metadata.pathObservers;
       if ( !metadata.hasOwnProperty( 'pathObservers' ) ) {
         pathObservers =
           metadata.pathObservers = Object.create( pathObservers );
       }
       // We want to remove all path observers. Can't just delete
       // though, as it may defined on the prototype object.
       pathObservers[ method ] = null;
       metadata.inits.Observers -= 1;
     }
   }
 }
};

Function.implement({
 observes: function () {
   var properties = ( this.observedProperties ||
     ( this.observedProperties = [] ) ),
     l = arguments.length;
   while ( l-- ) {
     properties.push( arguments[l] );
   }
   this.__setupProperty__ = setupObserver;
   this.__teardownProperty__ = teardownObserver;
   return this;
 }
});

Private Method

O.ObservableProps-_setupTeardownPaths( obj, method )

Adds or removes path observers for methods on an object.

Parameters

objObject The object to setup/teardown path observers for.
methodString Either 'addObserverForPath' or 'removeObserverForPath'
var _setupTeardownPaths = function ( obj, method ) {
 var pathObservers = meta( obj ).pathObservers,
   key, paths, l;
 for ( key in pathObservers ) {
   paths = pathObservers[ key ];
   if ( paths ) {
     l = paths.length;
     while ( l-- ) {
       obj[ method ]( paths[l], obj, key );
     }
   }
 }
};

Private Method

O.ObservableProps-_notifyObserversOfKey( that, metadata, key, oldValue, newValue )

Notifies any observers of a particular key and also removes old path observers and adds them to the new object.

Parameters

thatO.ObservableProps The object on which the property has changed.
metadataObject The metadata for this object.
keyString The name of the property whose observers need to be notified.
oldValue* The old value for the property.
newValue* The new value for the property.
var _notifyObserversOfKey =
   function ( that, metadata, key, oldValue, newValue ) {
 var observers = metadata.observers[ key ],
   isInitialised = metadata.isInitialised,
   haveCheckedForNew = false,
   observer, object, method, path, l;
 if ( observers && ( l = observers.length ) ) {
   // Remember, observers may be removed (or possibly added, but that's
   // less likely) during the iterations. Clone array before iterating
   // to avoid the problem.
   observers = observers.slice();
   while ( l-- ) {
     observer = observers[l];
     object = observer.object || that;
     method = observer.method;
     // During initialisation, this method is only called when a
     // binding syncs. We want to give the illusion of the bound
     // properties being present on the object from the beginning, so
     // they can be used interchangably with non-bound properties, so
     // suppress notification of observers. However, if there is
     // another binding that is bound to this one, we need to notify
     // that to ensure it syncs the correct initial value.
     // We also need to set up any path observers correctly.
     if ( isInitialised ) {
       if ( path = observer.path ) {
         // If it's a computed property we don't really want to call
         // it unless it's needed; could be expensive.
         if ( newValue === undefined && !haveCheckedForNew ) {
           newValue = /^\d+$/.test( key ) ?
             that.getObjectAt( parseInt( key, 10 ) ) :
             that.get( key );
           haveCheckedForNew = true;
         }
         // Either value could be null
         if ( oldValue ) {
           oldValue.removeObserverForPath( path, object, method );
         }
         if ( newValue ) {
           newValue.addObserverForPath( path, object, method );
         }
         object[ method ]( that, key,
           oldValue && oldValue.getFromPath( path ),
           newValue && newValue.getFromPath( path ) );
       } else {
         object[ method ]( that, key, oldValue, newValue );
       }
     } else {
       // Setup path observers on initial value.
       if ( newValue && ( path = observer.path ) ) {
         newValue.addObserverForPath( path, object, method );
       }
       // Sync binding immediately
       if ( object instanceof NS.Binding ) {
         object[ method ]();
         object.sync();
       }
     }
   }
 }
};

Private Method

O.ObservableProps-_notifyGenericObservers( that, metadata, changed )

Notifies any observers interested (registered as observing key '*') that at least one property has changed on this object.

Parameters

thatO.ObservableProps The object on which the property has changed.
metadataObject The metadata for this object.
changedObject A map of property names to another object. This object has an oldValue and possibly a newValue property.
var _notifyGenericObservers = function ( that, metadata, changed ) {
 var observers = metadata.observers[ '*' ],
   observer, l;
 if ( observers ) {
   l = observers.length;
   while ( l-- ) {
     observer = observers[l];
     ( observer.object || that )[ observer.method ]( that, changed );
   }
 }
};

Mixin

O.ObservableProps

The O.ObservableProps mixin adds support for key-value observing to another class. Public properties should only be accessed and modified via the get/set methods inherited from O.ComputedProps.

NS.ObservableProps = {

Method

O.Observable#initObservers()

Initialises any observed paths on the object (observed keys do not require initialisation. You should never call this directly, but rather iterate through the keys of O.meta( this ).inits, calling this[ 'init' + key ]() for all keys which map to truthy values.

initObservers: function () {
   _setupTeardownPaths( this, 'addObserverForPath' );
 },

Method

O.Observable#destroyObservers()

Removes any observed paths from the object (observed keys do not require destruction. You should never call this directly, but rather iterate through the keys of O.meta( this ).inits, calling this[ 'destroy' + key ]() for all keys which map to a truthy value.

destroyObservers: function () {
   _setupTeardownPaths( this, 'removeObserverForPath' );
 },

Method

O.ObservableProps#hasObservers()

Returns true if any property on the object is currently being observed by another object.

Returns

Boolean Does the object have any observers?

hasObservers: function () {
   var observers = meta( this ).observers,
     key, keyObservers, l, object;
   for ( key in observers ) {
     keyObservers = observers[ key ];
     l = keyObservers.length;
     while ( l-- ) {
       object = keyObservers[l].object;
       if ( object && object !== this ) {
         return true;
       }
     }
   }
   return false;
 },

Method

O.ObservableProps#beginPropertyChanges()

Call this before changing a set of properties (and then call <endPropertyChanges> afterwards) to ensure that if a dependent property changes more than once, observers of that property will only be notified once of the change. No observer will be called until the matching <endPropertyChanges> call is made.

Returns

O.ObservableProps Returns self.

beginPropertyChanges: function () {
   meta( this ).depth += 1;
   return this;
 },

Method

O.ObservableProps#endPropertyChanges()

Call this after changing a set of properties (having called <beginPropertyChanges> before) to ensure that if a dependent property changes more than once, observers of that property will only be notified once of the change.

Returns

O.ObservableProps Returns self.

endPropertyChanges: function () {
   var metadata = meta( this ),
     changed, key;
   if ( metadata.depth === 1 ) {
     // Notify observers.
     while ( changed = metadata.changed ) {
       metadata.changed = null;
       for ( key in changed ) {
         _notifyObserversOfKey( this, metadata,
           key, changed[ key ].oldValue, changed[ key ].newValue );
       }
       // Notify observers interested in any property change
       if ( metadata.observers[ '*' ] ) {
         _notifyGenericObservers( this, metadata, changed );
       }
     }
   }
   // Only decrement here so that any further property changes that happen
   // whilst we are notifying of the previous ones are queued up and then
   // distributed in the next loop.
   metadata.depth -= 1;
   return this;
 },

Method

O.ObservableProps#propertyDidChange( key, oldValue, newValue )

Overrides the method in O.ComputedProps. Invalidates any cached values depending on the property and notifies any observers about the change. Will also notify any observers of dependent values about the change.

Parameters

keyString The name of the property which has changed.
oldValue* The old value for the property.
newValue* Optional The new value for the property. Only there if it's not a computed property.

Returns

O.ObservableProps Returns self.

propertyDidChange: function ( key, oldValue, newValue ) {
   var metadata = meta( this ),
     isInitialised = metadata.isInitialised,
     dependents = isInitialised ?
       this.propertiesDependentOnKey( key ) : [],
     l = dependents.length,
     depth = metadata.depth,
     hasGenericObservers = metadata.observers[ '*' ],
     fastPath = !l && !depth && !hasGenericObservers,
     changed = fastPath ? null : metadata.changed || {},
     cache = metadata.cache,
     prop;

   if ( fastPath ) {
     _notifyObserversOfKey( this, metadata, key, oldValue, newValue );
   } else {
     while ( l-- ) {
       prop = dependents[l];
       if ( !changed[ prop ] ) {
         changed[ prop ] = {
           oldValue: cache[ prop ]
         };
       }
       delete cache[ prop ];
     }

     changed[ key ] = {
       oldValue: changed[ key ] ? changed[ key ].oldValue : oldValue,
       newValue: newValue
     };

     if ( metadata.depth ) {
       metadata.changed = changed;
     } else {
       // Notify observers of dependent keys.
       for ( prop in changed ) {
         _notifyObserversOfKey( this, metadata, prop,
           changed[ prop ].oldValue, changed[ prop ].newValue );
       }

       // Notify observers interested in any property change
       if ( isInitialised && hasGenericObservers ) {
         _notifyGenericObservers( this, metadata, changed );
       }
     }
   }

   return this;
 },

Method

O.ObservableProps#addObserverForKey( key, object, method )

Registers an object and a method to be called on that object whenever a particular key changes in value. The method will be called with the following parameters: obj, key, oldValue, newValue. If it is a computed property the oldValue and newValue arguments may not be present. You can also observe '*' to be notified of any changes to the object; in this case the observer will only be supplied with the first argument: this object.

Parameters

keyString The property to observer.
objectObject The object on which to call the callback method.
methodString The name of the callback method.

Returns

O.ObservableProps Returns self.

addObserverForKey: function ( key, object, method ) {
   var observers = meta( this ).observers,
     keyObservers = observers[ key ];
   if ( !observers.hasOwnProperty( key ) ) {
     keyObservers = observers[ key ] = keyObservers ?
       keyObservers.slice() : [];
   }
   keyObservers.push({ object: object, method: method });
   return this;
 },

Method

O.ObservableProps#removeObserverForKey( key, object, method )

Removes an object/method pair from the list of those to be called when the property changes. Must use identical arguments to a previous call to <addObserverForKey>.

Parameters

keyString The property which is being observed.
objectObject The object which is observing it.
methodString The name of the callback method on the observer object.

Returns

O.ObservableProps Returns self.

removeObserverForKey: function ( key, object, method ) {
   var observers = meta( this ).observers,
     keyObservers = observers[ key ],
     observer, l;
   if ( keyObservers ) {
     l = keyObservers.length;
     while ( l-- ) {
       observer = keyObservers[l];
       if ( observer.object === object &&
           observer.method === method ) {
         keyObservers.splice( l, 1 );
         break;
       }
     }
     if ( !keyObservers.length ) {
       delete observers[ key ];
     }
   }
   return this;
 },

Method

O.ObservableProps#addObserverForPath( path, object, method )

Registers an object and a method to be called on that object whenever any property in a given path string changes. Note, this path is live, in that if you observe foo.bar.x and bar changes, you will receive a callback, and the observer will be deregistered from the old bar, and registered on the new one.

Parameters

pathString The path to observe.
objectObject The object on which to call the callback method.
methodString The name of the callback method.

Returns

O.ObservableProps Returns self.

addObserverForPath: function ( path, object, method ) {
   var nextDot = path.indexOf( '.' );
   if ( nextDot === -1 ) {
     this.addObserverForKey( path, object, method );
   }
   else {
     var key = path.slice( 0, nextDot ),
       value = this.get( key ),
       restOfPath = path.slice( nextDot + 1 ),
       observers = meta( this ).observers,
       keyObservers = observers[ key ];
     if ( !observers.hasOwnProperty( key ) ) {
       keyObservers = observers[ key ] = keyObservers ?
         keyObservers.slice() : [];
     }

     keyObservers.push({
       path: restOfPath,
       object: object,
       method: method
     });
     if ( value && !( value instanceof NS.Binding ) ) {
       value.addObserverForPath( restOfPath, object, method );
     }
   }
   return this;
 },

Method

O.ObservableProps#removeObserverForPath( path, object, method )

Removes an observer for a path added with .

Parameters

pathString The path which is being observed.
objectObject The object which is observing it.
methodString The name of the callback method on the observer object.

Returns

O.ObservableProps Returns self.

removeObserverForPath: function ( path, object, method ) {
   var nextDot = path.indexOf( '.' );
   if ( nextDot === -1 ) {
     this.removeObserverForKey( path, object, method );
   }
   else {
     var key = path.slice( 0, nextDot ),
       value = this.get( key ),
       restOfPath = path.slice( nextDot + 1 ),
       observers = meta( this ).observers[ key ],
       observer, l;

     if ( observers ) {
       l = observers.length;
       while ( l-- ) {
         observer = observers[l];
         if ( observer.path === restOfPath &&
            observer.object === object &&
            observer.method === method) {
             observers.splice( l, 1 );
             break;
         }
       }
     }
     if ( value ) {
       value.removeObserverForPath( restOfPath, object, method );
     }
   }
   return this;
 }
};

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