Config
Table of Contents

Class

O.Binding

Includes
O.ComputedProps

Bindings keep a property on one object in sync with a property on another. This may be a two way link, so a change on either updates the other, or it may only flow data in one direction. A transform may be applied to the data between instances.

To use, create a new instance then call O.Binding#from, O.Binding#to and O.Binding#connect. Connection will normally be handled by the O.BoundProps class rather than directly.

/*global Element */

"use strict";

( function ( NS, Element, undefined ) {

Private Method

O.Binding-_resolveRootAndPath( binding, direction, root, path )

When created, a binding may need to reference a path on an object that does not yet exist (it will be created later in the run loop). To allow this, a section of the path may be defined as 'static', that is to say it is resolved only once; at initialisation time. Any changes along this path; only changes starting from the final static object will be observed.

A static portion is signified by using a * as a divider instead of a .. The section before the '' is taken to be static. If no '' is present, the entire path is taken to be dynamic. For example, if the path is static.path*dynamic.path, at initialisation time, the path property of the static property of the root object will be found. After initialisation, any changes to the 'dynamic' property on this object, or the 'path' property on that object will trigger the binding.

The results are set directly on the binding object passed as the first argument, with names direction + 'Object'/'Path'.

Parameters

bindingO.Binding The binding to resolve paths for.
directionString Either 'to' or 'from'.
rootObject The object to treat as root.
pathString The path string.
var _resolveRootAndPath = function ( binding, direction, root, path ) {
 var beginObservablePath = path.lastIndexOf( '*' ) + 1,
   observablePath = path.slice( beginObservablePath ),
   staticPath = beginObservablePath ?
     path.slice( 0, beginObservablePath - 1 ) : '',
   lastDot = observablePath.lastIndexOf( '.' );

 binding[ direction + 'Object' ] =
   staticPath ? NS.getFromPath( root, staticPath ) : root;
 binding[ direction + 'Path' ] = observablePath;
 binding[ direction + 'PathBeforeKey' ] =
   ( lastDot === -1 ) ? '' : observablePath.slice( 0, lastDot );
 binding[ direction + 'Key' ] = observablePath.slice( lastDot + 1 );
};

var isNum = /^\d+$/;

Private Method

O.Binding-identity( v )

Returns the first argument. This is the default transform (has no effect).

Parameters

v* The value.

Returns

* The value v.

var identity = function ( v ) { return v; };

var Binding = NS.Class({

 __setupProperty__: function ( metadata, key ) {
   metadata.bindings[ key ] = this;
   metadata.inits.Bindings = ( metadata.inits.Bindings || 0 ) + 1;
 },
 __teardownProperty__: function ( metadata, key ) {
   metadata.bindings[ key ] = null;
   metadata.inits.Bindings -= 1;
 },

Property

O.Binding#isConnected

  • Boolean

Is the instance currently observing for changes? This property is READ ONLY.

Property

O.Binding#isNotInSync

  • Boolean

Has the data changed on the from object (or the 'to' object if two-way)? This property is READ ONLY.

Property

O.Binding#isSuspended

  • Boolean

Should the binding stop propagating changes? This property is READ ONLY.

Property

O.Binding#willSyncForward

  • Boolean

The direction to sync at the next sync. True if syncing from the 'from' object to the 'to' object, false if it's going to do the reverse.

Property

O.Binding#isTwoWay

  • Boolean

Are changes just propagated from the 'from' object to the 'to' object, or are they also sent the other way?

Property

O.Binding#queue

  • String

During which queue in the run loop should the binding sync?

Constructor

O.Binding( mixin )

Parameters

mixinObject Optional. Can set isTwoWay or the transform to use on the binding.
init: function ( mixin ) {
   this.isConnected = false;
   this.isSuspended = true;
   this.isNotInSync = true;
   this.willSyncForward = true;

   this._fromPath = null;
   this._fromRoot = null;
   this._toPath = null;
   this._toRoot = null;

   this.fromObject = null;
   this.fromPath = '';
   this.fromPathBeforeKey = '';
   this.fromKey = '';

   this.toObject = null;
   this.toPath = '';
   this.toPathBeforeKey = '';
   this.toKey = '';

   this.isTwoWay = false;
   this.transform = identity;
   this.queue = 'bindings';

   for ( var key in mixin ) {
     this[ key ] = mixin[ key ];
   }
 },

Method

O.Binding#destroy()

Disconnects binding and prevents any further value syncs.

destroy: function () {
   this.disconnect();
   // Ignore any remaining queued connect() calls.
   this.isConnected = true;
 },

Method

O.Binding#from( root, path )

Sets the path and object to observe for changes. This method has no effect if it is called after the object is connected.

Parameters

rootObject Optional The object the static path is resolved against, will be the "to" root if not supplied.
pathString Any path before a *' is resolved at connection time and then remains static. Path components after this are treated as a dynamic path to watch for changes. If there is no '*' present in the string, the entire string is taken as a dynamic path.

Returns

O.Binding Returns self.

from: function ( root, path ) {
   var rootIsPath = ( typeof root === 'string' );
   this._fromRoot = rootIsPath ? path : root;
   this._fromPath = rootIsPath ? root : path;
   return this;
 },

Method

O.Binding#to( root, path )

Sets the path and object to propagate changes to. This method has no effect if it is called after the object is connected.

Parameters

rootObject Optional The object the static path is resolved against, will be the "from" root if not supplied.
pathString Any path before a *' is resolved at connection time and then remains static. Path components after this are treated as a dynamic path to watch for changes. If there is no '*' present in the string, the entire string is taken as a dynamic path.

Returns

O.Binding Returns self.

to: function ( root, path ) {
   var rootIsPath = ( typeof root === 'string' );
   this._toRoot = rootIsPath ? path : root;
   this._toPath = rootIsPath ? root : path;
   return this;
 },

 // ------------

Property

O.Binding#fromObject

  • Object

The static object the observed path begins from.

Property

O.Binding#fromPath

  • String

The dynamic path to observe on the from object.

Property

O.Binding#fromKey

  • String

The final component of the fromPath (the property name on the final object).

Property

O.Binding#fromPathBeforeKey

  • String

The dynamic 'from' path component before the final key.

Property

O.Binding#toObject

  • Object

The static object from which the object-to-update path is resolved.

Property

O.Binding#toPath

  • String

The dynamic path to follow on the to object.

Property

O.Binding#toKey

  • String

The final component of the toPath (the property name on the final object).

Property

O.Binding#toPathBeforeKey

  • String

The dynamic 'to' path component before the final key.

// ------------

Private Property

O.Binding#_doNotDelayConnection

  • Boolean
  • private

If the to or from object cannot be resolved, should the binding delay the connection until the end of the run loop?

_doNotDelayConnection: false,

Method

O.Binding#connect()

Starts observing for changes and syncs the current value of the observed property on the from object with the bound property on the to object.

Returns

O.Binding Returns self.

connect: function () {
   if ( this.isConnected ) { return this; }

   this.isSuspended = false;

   // Resolve objects:
   _resolveRootAndPath(
     this, 'from', this._fromRoot || this._toRoot, this._fromPath );
   _resolveRootAndPath(
     this, 'to', this._toRoot || this._fromRoot, this._toPath );

   var fromObject = this.fromObject,
     toObject = this.toObject;

   if ( toObject instanceof Element ) {
     this.queue = 'render';
   }

   // Occassionally we have a binding created before the objects it
   // connects are, in which case delay connecting it a bit.
   if ( !this._doNotDelayConnection && ( !fromObject || !toObject ) ) {
     this._doNotDelayConnection = true;
     NS.RunLoop.queueFn( 'before', this.connect, this );
     return this;
   }

   fromObject.addObserverForPath( this.fromPath, this, 'fromDidChange' );

   // Grab initial value:
   this.sync();

   if ( this.isTwoWay ) {
     toObject.addObserverForPath( this.toPath, this, 'toDidChange' );
   }
   this.isConnected = true;
   return this;
 },

Method

O.Binding#disconnect()

Stops observing for changes.

Returns

O.Binding Returns self.

disconnect: function () {
   if ( !this.isConnected ) { return this; }

   this.fromObject.removeObserverForPath(
     this.fromPath, this, 'fromDidChange' );

   if ( this.isTwoWay ) {
     this.toObject.removeObserverForPath(
       this.toPath, this, 'toDidChange' );
   }

   this.isConnected = false;
   this.isSuspended = true;
   this.isNotInSync = true;
   this.willSyncForward = true;

   return this;
 },

Method

O.Binding#suspend()

Stop propagating changes. The instance will still note when the observed object changes, but will not sync this to the bound property on the to object until the O.Binding#resume method is called.

Returns

O.Binding Returns self.

suspend: function () {
   this.isSuspended = true;
   return this;
 },

Method

O.Binding#resume()

Restart propagating changes. Sync the to object if the observed property has changed.

Returns

O.Binding Returns self.

resume: function () {
   if ( this.isSuspended && this.isConnected ) {
     this.isSuspended = false;
     this.sync();
   }
   return this;
 },

 // ------------

Property

O.Binding#transform

  • Function

A function which is applied to a value coming from one object before it is set on the other object.

// ------------

Method

O.Binding#fromDidChange()

Called when the observed property on the from object changes; adds the binding to the queue to be synced at the end of the run loop.

Returns

O.Binding Returns self.

fromDidChange: function () {
   return this.needsSync( true );
 },

Method

O.Binding#toDidChange()

If the binding is two-way, this is called when the observed property on the to object changes; adds the binding to the queue to be synced at the end of the run loop.

Returns

O.Binding Returns self.

toDidChange: function () {
   return this.needsSync( false );
 },

Method

O.Binding#needsSync( direction )

Adds the binding to the queue to be synced at the end of the run loop.

Parameters

directionBoolean True if sync needed from the "from" object to the "to" object, false if the reverse.

Returns

O.Binding Returns self.

needsSync: function ( direction ) {
   var queue = this.queue,
     inQueue = this.isNotInSync;
   this.willSyncForward = direction;
   this.isNotInSync = true;
   if ( !inQueue && !this.isSuspended ) {
     if ( queue ) {
       NS.RunLoop.queueFn( queue, this.sync, this, true );
     } else {
       this.sync();
     }
   }
   return this;
 },

Method

O.Binding#sync( force )

If the observed property has changed, this method applies any transforms and propagates the data to the other object.

Parameters

forceBoolean If true, sync the binding even if it hasn't changed.

Returns

Boolean Did the binding actually make a change?

sync: function ( force ) {
   if ( !force && ( !this.isNotInSync || this.isSuspended ) ) {
     return false;
   }

   this.isNotInSync = false;

   var syncForward = this.willSyncForward,
     from = syncForward ? 'from' : 'to',
     to = syncForward ? 'to' : 'from',
     pathBeforeKey = this[ to + 'PathBeforeKey' ],
     toObject = this[ to + 'Object' ],
     key, value;

   if ( pathBeforeKey ) {
     toObject = toObject.getFromPath( pathBeforeKey );
   }
   if ( !toObject ) {
     return false;
   }

   key = this[ to + 'Key' ];
   value = this.transform.call( this,
     this[ from + 'Object' ].getFromPath( this[ from + 'Path' ] ),
     syncForward
   );
   if ( value !== undefined ) {
     if ( isNum.test( key ) ) {
       toObject.setObjectAt( +key, value );
     } else {
       toObject.set( key, value );
     }
   }
   return true;
 }
});

NS.Binding = Binding;

Function

O.bind( root, path, transform )

Convenience method. A shortcut for: new O.Binding({ transform: transform }).from( root, path );

Parameters

rootObject Optional The root object on the path to bind from. If not specified, will be the same object that the property is bound to.
pathString The path to bind from
transformFunction Optional A transform to apply.

Returns

O.Binding The new binding.

var bind = NS.bind = function ( root, path, transform ) {
 var binding = new Binding().from( root, path );
 if ( transform ) {
   binding.transform = transform;
 }
 return binding;
};

Function

O.bindTwoWay( root, path, transform )

Convenience method. A shortcut for: new O.Binding({ isTwoWay: true, transform: transform }).from( root, path );

Parameters

rootObject Optional The root object on the path to bind from. If not specified, will be the same object that the property is bound to.
pathString The path to bind from
transformFunction Optional A transform to apply.

Returns

O.Binding The new binding.

NS.bindTwoWay = function ( root, path, transform ) {
 var binding = bind( root, path, transform );
 binding.isTwoWay = true;
 return binding;
};

}( O, typeof Element !== undefined ? Element : function () {} ) );
Animation
Application
Core
DataStore
DOM
DragDrop
Foundation
IO
Localisation
Selection
Parser
TimeZones
Storage
Touch
CollectionViews
UA
ContainerViews
ControlViews
PanelViews
View