1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('ext/function');
  9 sc_require('system/object');
 10 
 11 //@if(debug)
 12 /**
 13   Debug parameter you can turn on.  This will log all bindings that fire to
 14   the console.  This should be disabled in production code.  Note that you
 15   can also enable this from the console or temporarily.
 16 
 17   @type Boolean
 18 */
 19 SC.LOG_BINDINGS = NO;
 20 
 21 /**
 22   Performance parameter.  This will benchmark the time spent firing each
 23   binding.
 24 
 25   @type Boolean
 26 */
 27 SC.BENCHMARK_BINDING_NOTIFICATIONS = NO;
 28 
 29 /**
 30   Performance parameter.  This will benchmark the time spend configuring each
 31   binding.
 32 
 33   @type Boolean
 34 */
 35 SC.BENCHMARK_BINDING_SETUP = NO;
 36 //@endif
 37 
 38 /**
 39   Default placeholder for multiple values in bindings.
 40 
 41   @type String
 42 */
 43 SC.MULTIPLE_PLACEHOLDER = '@@MULT@@';
 44 
 45 /**
 46   Default placeholder for null values in bindings.
 47 
 48   @type String
 49 */
 50 SC.NULL_PLACEHOLDER = '@@NULL@@';
 51 
 52 /**
 53   Default placeholder for empty values in bindings.
 54 
 55   @type String
 56 */
 57 SC.EMPTY_PLACEHOLDER = '@@EMPTY@@';
 58 
 59 
 60 /**
 61   @class
 62 
 63   A binding simply connects the properties of two objects so that whenever the
 64   value of one property changes, the other property will be changed also.  You
 65   do not usually work with Binding objects directly but instead describe
 66   bindings in your class definition using something like:
 67 
 68         valueBinding: "MyApp.someController.title"
 69 
 70   This will create a binding from "MyApp.someController.title" to the "value"
 71   property of your object instance automatically.  Now the two values will be
 72   kept in sync.
 73 
 74   One-Way Bindings
 75   ===
 76 
 77   By default, bindings are set up as two-way. In cases where you only need the
 78   binding to function in one direction, for example if a value from a controller
 79   is bound into a read-only LabelView, then for performance reasons you should
 80   use a one-way binding. To do this, call the very useful `oneWay` helper:
 81 
 82       valueBinding: SC.Binding.oneWay('MyApp.someController.title')
 83 
 84   or:
 85 
 86       valueBinding: SC.Binding.from('MyApp.someController.title').oneWay()
 87 
 88   This way if the value of MyApp.someController.title changes, your object's
 89   `value` will also update. Since `value` will never update on its own, this will
 90   avoid the setup time required to plumb the binding in the other direction,
 91   nearly doubling performance for this binding.
 92 
 93   Transforms
 94   ===
 95 
 96   In addition to synchronizing values, bindings can also perform some basic
 97   transforms on values.  These transforms can help to make sure the data fed
 98   into one object always meets the expectations of that object, regardless of
 99   what the other object outputs.
100 
101   To customize a binding, you can use one of the many helper methods defined
102   on SC.Binding. For example:
103 
104         valueBinding: SC.Binding.single("MyApp.someController.title")
105 
106   This will create a binding just like the example above, except that now the
107   binding will convert the value of MyApp.someController.title to a single
108   object (removing any arrays) before applying it to the "value" property of
109   your object.
110 
111   You can also chain helper methods to build custom bindings like so:
112 
113         valueBinding: SC.Binding.single("MyApp.someController.title").notEmpty(null,"(EMPTY)")
114 
115   This will force the value of MyApp.someController.title to be a single value
116   and then check to see if the value is "empty" (null, undefined, empty array,
117   or an empty string).  If it is empty, the value will be set to the string
118   "(EMPTY)".
119 
120   The following transform helper methods are included: `noError`, `single`, `notEmpty`,
121   `notNull`, `multiple`, `bool`, `not`, `isNull`, `and` (two values only), `or` (two
122   values only), and `equalTo`. See each method's documentation for a full description.
123 
124   (Note that transforms are only applied in the forward direction (the 'to' side); values
125   are applied untransformed to the 'from' side. If the 'from' object has validation
126   needs, it should own and apply them itself, for example via a read/write calculated
127   property.)
128 
129   Adding Custom Transforms
130   ===
131 
132   In addition to using the standard helpers provided by SproutCore, you can
133   also defined your own custom transform functions which will be used to
134   convert the value.  To do this, just define your transform function and add
135   it to the binding with the transform() helper.  The following example will
136   not allow Integers less than ten.  Note that it checks the value of the
137   bindings and allows all other values to pass:
138 
139         valueBinding: SC.Binding.transform(function (value, binding) {
140           return ((SC.typeOf(value) === SC.T_NUMBER) && (value < 10)) ? 10 : value;
141         }).from("MyApp.someController.value")
142 
143   If you would like to instead use this transform on a number of bindings,
144   you can also optionally add your own helper method to SC.Binding.  This
145   method should simply return the value of this.transform(). The example
146   below adds a new helper called notLessThan() which will limit the value to
147   be not less than the passed minimum:
148 
149       SC.Binding.notLessThan = function (minValue) {
150         return this.transform(function (value, binding) {
151           return ((SC.typeOf(value) === SC.T_NUMBER) && (value < minValue)) ? minValue : value;
152         });
153       };
154 
155   You could specify this in your core.js file, for example.  Then anywhere in
156   your application you can use it to define bindings like so:
157 
158         valueBinding: SC.Binding.from("MyApp.someController.value").notLessThan(10)
159 
160   Also, remember that helpers are chained so you can use your helper along with
161   any other helpers.  The example below will create a one way binding that
162   does not allow empty values or values less than 10:
163 
164         valueBinding: SC.Binding.oneWay("MyApp.someController.value").notEmpty().notLessThan(10)
165 
166   Note that the built in helper methods all allow you to pass a "from"
167   property path so you don't have to use the from() helper to set the path.
168   You can do the same thing with your own helper methods if you like, but it
169   is not required.
170 
171   Creating Custom Binding Templates
172   ===
173 
174   Another way you can customize bindings is to create a binding template.  A
175   template is simply a binding that is already partially or completely
176   configured.  You can specify this template anywhere in your app and then use
177   it instead of designating your own custom bindings.  This is a bit faster on
178   app startup but it is mostly useful in making your code less verbose.
179 
180   For example, let's say you will be frequently creating one way, not empty
181   bindings that allow values greater than 10 throughout your app.  You could
182   create a binding template in your core.js like this:
183 
184         MyApp.LimitBinding = SC.Binding.oneWay().notEmpty().notLessThan(10);
185 
186   Then anywhere you want to use this binding, just refer to the template like
187   so:
188 
189         valueBinding: MyApp.LimitBinding.beget("MyApp.someController.value")
190 
191   Note that when you use binding templates, it is very important that you
192   always start by using beget() to extend the template.  If you do not do
193   this, you will end up using the same binding instance throughout your app
194   which will lead to erratic behavior.
195 
196   How to Manually Activate a Binding
197   ===
198 
199   All of the examples above show you how to configure a custom binding, but
200   the result of these customizations will be a binding template, not a fully
201   active binding.  The binding will actually become active only when you
202   instantiate the object the binding belongs to.  It is useful however, to
203   understand what actually happens when the binding is activated. (Of course
204   you should always use the highest-level APIs available, even if you understand
205   how it works underneath; unless you have specific needs, you should rely on
206   the convenience `fooBinding` format.)
207 
208   For a binding to function it must have at least a "from" property and a "to"
209   property.  The from property path points to the object/key that you want to
210   bind from while the to path points to the object/key you want to bind to.
211 
212   When you define a custom binding, you are usually describing the property
213   you want to bind from (such as "MyApp.someController.value" in the examples
214   above).  When your object is created, it will automatically assign the value
215   you want to bind "to" based on the name of your binding key.  In the
216   examples above, during init, SproutCore objects will effectively call
217   something like this on your binding:
218 
219         binding = this.valueBinding.beget().to("value", this);
220 
221   This creates a new binding instance based on the template you provide, and
222   sets the to path to the "value" property of the new object.  Now that the
223   binding is fully configured with a "from" and a "to", it simply needs to be
224   connected to become active.  This is done through the connect() method:
225 
226         binding.connect();
227 
228   Now that the binding is connected, it will observe both the from and to side
229   and relay changes.
230 
231   If you ever needed to do so (you almost never will, but it is useful to
232   understand this anyway), you could manually create an active binding by
233   doing the following:
234 
235         SC.Binding.from("MyApp.someController.value")
236          .to("MyApp.anotherObject.value")
237          .connect();
238 
239   You could also use the bind() helper method provided by SC.Object. (This is
240   the same method used by SC.Object.init() to setup your bindings):
241 
242         MyApp.anotherObject.bind("value", "MyApp.someController.value");
243 
244   Both of these code fragments have the same effect as doing the most friendly
245   form of binding creation like so:
246 
247 
248         MyApp.anotherObject = SC.Object.create({
249           valueBinding: "MyApp.someController.value",
250 
251           // OTHER CODE FOR THIS OBJECT...
252 
253         });
254 
255   SproutCore's built in binding creation method make it easy to automatically
256   create bindings for you. If you need further documentation on SC.Binding's inner
257   workings, see the private method documentation in the source code.
258 
259   @since SproutCore 1.0
260 */
261 SC.Binding = /** @scope SC.Binding.prototype */{
262 
263   /**
264     Quack.
265   */
266   isBinding: YES,
267 
268   /** @private
269     This is the core method you use to create a new binding instance.  The
270     binding instance will have the receiver instance as its parent which means
271     any configuration you have there will be inherited.
272 
273     The returned instance will also have its parentBinding property set to the
274     receiver.
275 
276     @param {String} [fromPath]
277     @returns {SC.Binding} new binding instance
278   */
279   beget: function (fromPath) {
280     var ret = SC.beget(this);
281     ret.parentBinding = this;
282 
283     // Mix adapters must be recreated on beget.
284     if (ret._MixAdapter) {
285       ret._mixAdapter = ret._MixAdapter.create(ret._mixAdapterHash);
286       ret = ret.from('aggregateProperty', ret._mixAdapter).oneWay();
287     }
288 
289     // Enables duplicate API calls for SC.Binding.beget and SC.Binding.from
290     if (fromPath !== undefined) ret = ret.from(fromPath);
291     return ret;
292   },
293 
294   /** @private
295     Returns a builder function for compatibility.
296   */
297   builder: function () {
298     var binding = this,
299         ret = function (fromProperty) { return binding.beget().from(fromProperty); };
300     ret.beget = function () { return binding.beget(); };
301     return ret;
302   },
303 
304   /**
305     This will set "from" property path to the specified value.  It will not
306     attempt to resolve this property path to an actual object/property tuple
307     until you connect the binding.
308 
309     The binding will search for the property path starting at the root level
310     unless you specify an alternate root object as the second parameter to this
311     method.  Alternatively, you can begin your property path with either "." or
312     "*", which will use the root object of the to side be default.  This special
313     behavior is used to support the high-level API provided by SC.Object.
314 
315     @param {String|Tuple} propertyPath A property path or tuple
316     @param {Object} [root] root object to use when resolving the path.
317     @returns {SC.Binding} this
318   */
319   from: function (propertyPath, root) {
320 
321     // if the propertyPath is null/undefined, return this.  This allows the
322     // method to be called from other methods when the fromPath might be
323     // optional. (cf single(), multiple())
324     if (!propertyPath) return this;
325 
326     // beget if needed.
327     var binding = (this === SC.Binding) ? this.beget() : this;
328     binding._fromPropertyPath = propertyPath;
329     binding._fromRoot = root;
330     binding._fromTuple = null;
331     return binding;
332   },
333 
334   /**
335     This will set the "to" property path to the specified value.  It will not
336     attempt to reoslve this property path to an actual object/property tuple
337     until you connect the binding.
338 
339     If you are using the convenience format `fooBinding`, for example
340     `isVisibleBinding`, you do not need to call this method, as the `to` property
341     path will be generated for you when its object is created.
342 
343     @param {String|Tuple} propertyPath A property path or tuple
344     @param {Object} [root] root object to use when resolving the path.
345     @returns {SC.Binding} this
346   */
347   to: function (propertyPath, root) {
348     // beget if needed.
349     var binding = (this === SC.Binding) ? this.beget() : this;
350     binding._toPropertyPath = propertyPath;
351     binding._toRoot = root;
352     binding._toTuple = null; // clear out any existing one.
353     return binding;
354   },
355 
356   /**
357     Attempts to connect this binding instance so that it can receive and relay
358     changes.  This method will raise an exception if you have not set the
359     from/to properties yet.
360 
361     @returns {SC.Binding} this
362   */
363   connect: function () {
364     // If the binding is already connected, do nothing.
365     if (this.isConnected) return this;
366     this.isConnected = YES;
367     this._connectionPending = YES; // its connected but not really...
368     this._syncOnConnect = YES;
369 
370     SC.Binding._connectQueue.add(this);
371 
372     if (!SC.RunLoop.isRunLoopInProgress()) {
373       this._scheduleSync();
374     }
375 
376     return this;
377   },
378 
379   /** @private
380     Actually connects the binding.  This is done at the end of the runloop
381     to give you time to setup your entire object graph before the bindings
382     try to activate.
383   */
384   _connect: function () {
385     if (!this._connectionPending) return; //nothing to do
386     this._connectionPending = NO;
387 
388     var path, root;
389 
390     //@if(debug)
391     var bench = SC.BENCHMARK_BINDING_SETUP;
392     if (bench) SC.Benchmark.start("SC.Binding.connect()");
393     //@endif
394 
395     // try to connect the from side.
396     // as a special behavior, if the from property path begins with either a
397     // . or * and the fromRoot is null, use the toRoot instead.  This allows
398     // for support for the SC.Object shorthand:
399     //
400     // contentBinding: "*owner.value"
401     //
402     path = this._fromPropertyPath;
403     root = this._fromRoot;
404 
405     if (typeof path === "string") {
406 
407       // if the first character is a '.', this is a static path.  make the
408       // toRoot the default root.
409       if (path.indexOf('.') === 0) {
410         path = path.slice(1);
411         if (!root) root = this._toRoot;
412 
413       // if the first character is a '*', then setup a tuple since this is a
414       // chained path.
415       } else if (path.indexOf('*') === 0) {
416         path = [this._fromRoot || this._toRoot, path.slice(1)];
417         root = null;
418       }
419     }
420     this._fromObserverData = [path, this, this.fromPropertyDidChange, root];
421     SC.Observers.addObserver.apply(SC.Observers, this._fromObserverData);
422 
423     // try to connect the to side
424     if (!this._oneWay) {
425       path = this._toPropertyPath;
426       root = this._toRoot;
427       this._toObserverData = [path, this, this.toPropertyDidChange, root];
428       SC.Observers.addObserver.apply(SC.Observers, this._toObserverData);
429     }
430 
431     //@if(debug)
432     if (bench) SC.Benchmark.end("SC.Binding.connect()");
433     //@endif
434 
435     // now try to sync if needed
436     if (this._syncOnConnect) {
437       this._syncOnConnect = NO;
438       //@if(debug)
439       if (bench) SC.Benchmark.start("SC.Binding.connect().sync");
440       //@endif
441       this.sync();
442       //@if(debug)
443       if (bench) SC.Benchmark.end("SC.Binding.connect().sync");
444       //@endif
445     }
446   },
447 
448   /**
449     Disconnects the binding instance.  Changes will no longer be relayed.  You
450     will not usually need to call this method.
451 
452     @returns {SC.Binding} this
453   */
454   disconnect: function () {
455     if (!this.isConnected) return this; // nothing to do.
456 
457     // if connection is still pending, just cancel
458     if (this._connectionPending) {
459       this._connectionPending = NO;
460 
461       SC.Binding._connectQueue.remove(this);
462     // connection is completed, disconnect.
463     } else {
464       SC.Observers.removeObserver.apply(SC.Observers, this._fromObserverData);
465       if (!this._oneWay) {
466         SC.Observers.removeObserver.apply(SC.Observers, this._toObserverData);
467       }
468 
469       // Remove ourselves from the change queue (if we are in it).
470       SC.Binding._changeQueue.remove(this);
471     }
472 
473     this.isConnected = NO;
474     return this;
475   },
476 
477   /** @private
478     Indicates when the binding has been destroyed.
479 
480     @type Boolean
481     @default NO
482   */
483   isDestroyed: NO,
484 
485   /** @private
486     Disconnects the binding and removes all properties and external references. Called by
487     either binding target object when destroyed.
488 
489     @private
490   */
491   destroy: function () {
492     // If we're already destroyed, there's nothing to do.
493     if (this.isDestroyed) return;
494 
495     // Mark it destroyed.
496     this.isDestroyed = YES;
497 
498     // Clean up the mix adapter, if any. (See adapter methods.)
499     if (this._mixAdapter) {
500       this._mixAdapter.destroy();
501       this._mixAdapter = null;
502       this._MixAdapter = null;
503       this._mixAdapterHash = null;
504     }
505 
506     // Disconnect the binding.
507     this.disconnect();
508 
509     // Aggressively null out internal properties.
510     this._bindingSource = null;
511     this._toRoot = this._toTarget = null;
512     this._fromRoot = this._fromTarget = null;
513     this._toObserverData = this._fromObserverData = null;
514   },
515 
516   /** @private
517     Invoked whenever the value of the "from" property changes.  This will mark
518     the binding as dirty if the value has changed.
519 
520     @param {Object} target The object that contains the key
521     @param {String} key The name of the property which changed
522   */
523   fromPropertyDidChange: function (target, key) {
524     var v = target ? target.get(key) : null;
525 
526     // In rare circumstances, getting a property can result in observers firing,
527     // which may in turn run code that disconnects the binding. The cause of
528     // this pattern has been difficult to determine and so until a concrete test
529     // scenario and a lower level fix can be found, show a warning and ignore
530     // the update.
531     if (!this.isConnected) {
532       //@if(debug)
533       SC.Logger.warn("Developer Warning: A binding attempted to update after it was disconnected. The update will be ignored for binding: %@".fmt(this._fromPropertyPath, this._fromTarget, this));
534       //@endif
535 
536       // Break early.
537       return;
538     }
539 
540     // if the new value is different from the current binding value, then
541     // schedule to register an update.
542     if (v !== this._bindingValue || key === '[]') {
543 
544       this._setBindingValue(target, key);
545       SC.Binding._changeQueue.add(this); // save for later.
546 
547       this._scheduleSync();
548     }
549   },
550 
551   /** @private
552     Invoked whenever the value of the "to" property changes.  This will mark the
553     binding as dirty only if:
554 
555     - the binding is not one way
556     - the value does not match the stored transformedBindingValue
557 
558     if the value does not match the transformedBindingValue, then it will
559     become the new bindingValue.
560 
561     @param {Object} target The object that contains the key
562     @param {String} key The name of the property which changed
563   */
564   toPropertyDidChange: function (target, key) {
565     if (this._oneWay) return; // nothing to do
566 
567     var v = target.get(key);
568 
569     // In rare circumstances, getting a property can result in observers firing,
570     // which may in turn run code that disconnects the binding. The cause of
571     // this pattern has been difficult to determine and so until a concrete test
572     // scenario and a lower level fix can be found, show a warning and ignore
573     // the update.
574     if (!this.isConnected) {
575       //@if(debug)
576       SC.Logger.warn("Developer Warning: A binding attempted to update after it was disconnected. The update will be ignored for binding: %@".fmt(this));
577       //@endif
578 
579       // Break early.
580       return;
581     }
582 
583     // if the new value is different from the current binding value, then
584     // schedule to register an update.
585     if (v !== this._transformedBindingValue) {
586       this._setBindingValue(target, key);
587       SC.Binding._changeQueue.add(this); // save for later.
588 
589       this._scheduleSync();
590     }
591   },
592 
593   /** @private */
594   _scheduleSync: function () {
595     if (SC.RunLoop.isRunLoopInProgress() || SC.Binding._syncScheduled) { return; }
596     SC.Binding._syncScheduled = YES;
597     setTimeout(function () { SC.run(); SC.Binding._syncScheduled = NO; }, 1);
598   },
599 
600   /** @private
601     Saves the source location for the binding value.  This will be used later
602     to actually update the binding value.
603   */
604   _setBindingValue: function (source, key) {
605     this._bindingSource = source;
606     this._bindingKey    = key;
607   },
608 
609   /** @private
610     Updates the binding value from the current binding source if needed.  This
611     should be called just before using this._bindingValue.
612   */
613   _computeBindingValue: function () {
614     var source = this._bindingSource,
615         key    = this._bindingKey,
616         v;
617 
618     this._bindingValue = v = (source ? source.getPath(key) : null);
619     this._transformedBindingValue = this._computeTransformedValue(v);
620   },
621 
622   /** @private
623     Applies transforms to the value and returns the transfomed value.
624     @param {*} value Binding value to transform
625     @returns {*} Transformed value
626   */
627   _computeTransformedValue: function (value) {
628     var transforms = this._transforms,
629         idx,
630         len,
631         transform;
632 
633     if (transforms) {
634       len = transforms.length;
635       for (idx = 0; idx < len; idx++) {
636         transform = transforms[idx];
637         value = transform(value, this);
638       }
639     }
640 
641     // if error objects are not allowed, and the value is an error, then
642     // change it to null.
643     if (this._noError && SC.typeOf(value) === SC.T_ERROR) { value = null; }
644 
645     return value;
646   },
647 
648   _connectQueue: SC.CoreSet.create(),
649   _alternateConnectQueue: SC.CoreSet.create(),
650   _changeQueue: SC.CoreSet.create(),
651   _alternateChangeQueue: SC.CoreSet.create(),
652 
653   /** @private
654     Call this method on SC.Binding to flush all bindings with changes pending.
655 
656     @returns {Boolean} YES if changes were flushed.
657   */
658   flushPendingChanges: function () {
659 
660     // don't allow flushing more than one at a time
661     if (this._isFlushing) return NO;
662     this._isFlushing = YES;
663     SC.Observers.suspendPropertyObserving();
664 
665     var didFlush = NO,
666         // connect any bindings
667         queue, binding;
668 
669     while ((queue = this._connectQueue).length > 0) {
670       this._connectQueue = this._alternateConnectQueue;
671       this._alternateConnectQueue = queue;
672       while ((binding = queue.pop())) { binding._connect(); }
673     }
674 
675     // loop through the changed queue...
676     while ((queue = this._changeQueue).length > 0) {
677       //@if(debug)
678       if (SC.LOG_BINDINGS) SC.Logger.log("Begin: Trigger changed bindings");
679       //@endif
680 
681       didFlush = YES;
682 
683       // first, swap the change queues.  This way any binding changes that
684       // happen while we flush the current queue can be queued up.
685       this._changeQueue = this._alternateChangeQueue;
686       this._alternateChangeQueue = queue;
687 
688       // next, apply any bindings in the current queue.  This may cause
689       // additional bindings to trigger, which will end up in the new active
690       // queue.
691       while ((binding = queue.pop())) { binding.applyBindingValue(); }
692 
693       // now loop back and see if there are additional changes pending in the
694       // active queue.  Repeat this until all bindings that need to trigger
695       // have triggered.
696       //@if(debug)
697       if (SC.LOG_BINDINGS) SC.Logger.log("End: Trigger changed bindings");
698       //@endif
699     }
700 
701     // clean up
702     this._isFlushing = NO;
703     SC.Observers.resumePropertyObserving();
704 
705     return didFlush;
706   },
707 
708   /** @private
709     This method is called at the end of the Run Loop to relay the changed
710     binding value from one side to the other.
711   */
712   applyBindingValue: function () {
713     // compute the binding targets if needed.
714     this._computeBindingTargets();
715     this._computeBindingValue();
716 
717     var v = this._bindingValue,
718         tv = this._transformedBindingValue;
719 
720     //@if(debug)
721     var bench = SC.BENCHMARK_BINDING_NOTIFICATIONS,
722       log = SC.LOG_BINDINGS;
723     //@endif
724 
725     // the from property value will always be the binding value, update if
726     // needed.
727     if (!this._oneWay && this._fromTarget) {
728       //@if(debug)
729       if (log) SC.Logger.log("%@: %@ -> %@".fmt(this, v, tv));
730       if (bench) SC.Benchmark.start(this.toString() + "->");
731       //@endif
732 
733       this._fromTarget.setPathIfChanged(this._fromPropertyKey, v);
734 
735       //@if(debug)
736       if (bench) SC.Benchmark.end(this.toString() + "->");
737       //@endif
738     }
739 
740     // update the to value with the transformed value if needed.
741     if (this._toTarget) {
742 
743       //@if(debug)
744       if (log) SC.Logger.log("%@: %@ <- %@".fmt(this, v, tv));
745       if (bench) SC.Benchmark.start(this.toString() + "<-");
746       //@endif
747 
748       this._toTarget.setPathIfChanged(this._toPropertyKey, tv);
749 
750       //@if(debug)
751       if (bench) SC.Benchmark.start(this.toString() + "<-");
752       //@endif
753     }
754   },
755 
756   /**
757     Calling this method on a binding will cause it to check the value of the
758     from side of the binding matches the current expected value of the
759     binding. If not, it will relay the change as if the from side's value has
760     just changed.
761 
762     This method is useful when you are dynamically connecting bindings to a
763     network of objects that may have already been initialized. Otherwise you
764     should not need to call this method.
765   */
766   sync: function () {
767     var target,
768         key,
769         v,
770         tv;
771 
772     // do nothing if not connected
773     if (!this.isConnected) return this;
774 
775     // connection is pending, just note that we should sync also
776     if (this._connectionPending) {
777       this._syncOnConnect = YES;
778 
779     // we are connected, go ahead and sync
780     } else {
781       this._computeBindingTargets();
782       target = this._fromTarget;
783       key = this._fromPropertyKey;
784       if (!target || !key) return this; // nothing to do
785 
786       // Let's check for whether target is a valid observable with getPath.
787       // Common cases might have it be a Window or a DOM object.
788       //
789       // If we have a target, it is ready, but if it is invalid, that is WRONG.
790       if (!target.isObservable) {
791         //@if(debug)
792         // Provide some developer support.
793         if (target === window) {
794           var msg = "Developer Warning: You are attempting to bind \"%{to_root}\"'s '%{to_property}' property to the non-observable 'window.%{key}'. It's likely that you've specified a local binding path without prepending a period. For example, you may have `%{to_property}Binding: '%{key}'` instead of `%{to_property}Binding: '.%{key}'`.";
795           msg = msg.fmt({
796             to_root: (this._toRoot || 'object').toString(),
797             to_property: this._toPropertyPath,
798             key: key
799           });
800           SC.Logger.warn(msg);
801         } else {
802           SC.Logger.warn("Developer Warning: Cannot bind \"%@\"'s '%@' property to property '%@' on non-observable '%@'".fmt((this._toRoot || 'object').toString(), this._toPropertyPath, key, target));
803         }
804         //@endif
805         return this;
806       }
807 
808       // get the new value
809       v = target.getPath(key);
810       tv = this._computeTransformedValue(v);
811 
812       // if the new value is different from the current binding value, then
813       // schedule to register an update.
814       if (v !== this._bindingValue || tv !== this._transformedBindingValue || key === '[]') {
815         this._setBindingValue(target, key);
816         SC.Binding._changeQueue.add(this); // save for later.
817       }
818     }
819 
820     return this;
821   },
822 
823   /** @private
824     set if you call sync() when the binding connection is still pending.
825    */
826   _syncOnConnect: NO,
827 
828   /** @private */
829   _computeBindingTargets: function () {
830     var path, root, tuple;
831 
832     if (!this._fromTarget) {
833       // if the fromPropertyPath begins with a . or * then we may use the
834       // toRoot as the root object.  Similar code exists in connect() so if
835       // you make a change to one be sure to update the other.
836       path = this._fromPropertyPath;
837       root = this._fromRoot;
838       if (typeof path === "string") {
839 
840         // static path beginning with the toRoot
841         if (path.indexOf('.') === 0) {
842           path = path.slice(1); // remove the .
843           if (!root) root = this._toRoot; // use the toRoot optionally
844 
845         // chained path beginning with toRoot.  Setup a tuple
846         } else if (path.indexOf('*') === 0) {
847           path = [root || this._toRoot, path.slice(1)];
848           root = null;
849         }
850       }
851 
852       tuple = SC.tupleForPropertyPath(path, root);
853       if (tuple) {
854         this._fromTarget = tuple[0];
855         this._fromPropertyKey = tuple[1];
856       }
857     }
858 
859     if (!this._toTarget) {
860       path = this._toPropertyPath;
861       root = this._toRoot;
862       tuple = SC.tupleForPropertyPath(path, root);
863       if (tuple) {
864         this._toTarget = tuple[0];
865         this._toPropertyKey = tuple[1];
866         // Hook up _mixAdapter if needed (see adapter methods).
867         if (this._mixAdapter) {
868           this._mixAdapter.set('localObject', this._toTarget);
869         }
870       }
871     }
872   },
873 
874   // -------------------------------
875   // Helper Methods
876   //
877 
878   /**
879     Configures the binding as one way.  A one-way binding will relay changes
880     on the "from" side to the "to" side, but not the other way around.  This
881     means that if you change the "to" side directly, the "from" side will not
882     be updated, and may have a different value.
883 
884     @param {String} [fromPath] from path to connect.
885     @param {Boolean} [aFlag] Pass NO to set the binding back to two-way
886     @returns {SC.Binding} this
887   */
888   oneWay: function (fromPath, aFlag) {
889 
890     // If fromPath is a bool but aFlag is undefined, swap.
891     if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
892       aFlag = fromPath;
893       fromPath = null;
894     }
895 
896     // beget if needed.
897     var binding = this.from(fromPath);
898     if (binding === SC.Binding) binding = binding.beget();
899     binding._oneWay = (aFlag === undefined) ? YES : aFlag;
900 
901     return binding;
902   },
903 
904   /**
905     Adds the specified transform function to the array of transform functions.
906 
907     The function you pass must have the following signature:
908 
909           function (value) {};
910 
911     or:
912 
913           function (value, binding) {};
914 
915     It must return either the transformed value or an error object.
916 
917     Transform functions are chained, so they are called in order.  If you are
918     extending a binding and want to reset its transforms, you can call
919     resetTransform() first.
920 
921     @param {Function} transformFunc the transform function.
922     @returns {SC.Binding} this
923   */
924   transform: function (transformFunc) {
925     var binding = (this === SC.Binding) ? this.beget() : this;
926     var t = binding._transforms;
927 
928     // clone the transform array if this comes from the parent
929     if (t && (t === binding.parentBinding._transforms)) {
930       t = binding._transforms = t.slice();
931     }
932 
933     // create the transform array if needed.
934     if (!t) t = binding._transforms = [];
935 
936     // add the transform function
937     t.push(transformFunc);
938     return binding;
939   },
940 
941   /**
942     Resets the transforms for the binding.  After calling this method the
943     binding will no longer transform values.  You can then add new transforms
944     as needed.
945 
946     @returns {SC.Binding} this
947   */
948   resetTransforms: function () {
949     var binding = (this === SC.Binding) ? this.beget() : this;
950     binding._transforms = null;
951     return binding;
952   },
953 
954   /**
955     Adds a transform to convert the value to a bool value.  If the value is
956     an array it will return YES if array is not empty.  If the value is a string
957     it will return YES if the string is not empty.
958 
959     @param {String} [fromPath]
960     @returns {SC.Binding} this
961   */
962   bool: function (fromPath) {
963     return this.from(fromPath).transform(function (v) {
964       var t = SC.typeOf(v);
965       if (t === SC.T_ERROR) return v;
966       return (t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v;
967     });
968   },
969 
970   /**
971     Adds a transform that will *always* return an integer Number value. Null and undefined values will
972     return 0 while String values will be transformed using the parseInt method (according to the
973     radix) and Boolean values will be 1 or 0 if true or false accordingly. Other edge cases like NaN
974     or other non-Numbers will also return 0.
975 
976     Example results,
977 
978     - null => 0
979     - undefined => 0
980     - '123' => 123
981     - true => 1
982     - {} => 0
983 
984     @param {String} fromPathOrRadix from path or the radix for the parsing or null for 10
985     @param {String} radix the radix for the parsing or null for 10
986     @returns {SC.Binding} this
987   */
988   integer: function (fromPathOrRadix, radix) {
989     // Normalize arguments.
990     if (radix === undefined) {
991       radix = fromPathOrRadix;
992       fromPathOrRadix = null;
993     }
994 
995     // Use base 10 by default.
996     if (radix === undefined) radix = 10;
997 
998     return this.from(fromPathOrRadix).transform(function (value) {
999 
1000       // Null or undefined will be converted to 0.
1001       if (SC.none(value)) {
1002         value = 0;
1003 
1004       // String values will be converted to integer Numbers using parseInt with the given radix.
1005       } else if (typeof value === SC.T_STRING) {
1006         value = window.parseInt(value, radix);
1007 
1008       // Boolean values will be converted to 0 or 1 accordingly.
1009       } else if (typeof value === SC.T_BOOL) {
1010         value = value ? 1 : 0;
1011       }
1012 
1013       // All other non-Number values will be converted to 0 (this includes bad String parses above).
1014       if (typeof value !== SC.T_NUMBER || isNaN(value)) {
1015         value = 0;
1016       }
1017 
1018       return value;
1019     });
1020   },
1021 
1022   /**
1023     Adds a transform that will return YES if the value is null or undefined, NO otherwise.
1024 
1025     @param {String} [fromPath]
1026     @returns {SC.Binding} this
1027   */
1028   isNull: function (fromPath) {
1029     return this.from(fromPath).transform(function (v) {
1030       var t = SC.typeOf(v);
1031       return (t === SC.T_ERROR) ? v : SC.none(v);
1032     });
1033   },
1034 
1035   /**
1036     Specifies that the binding should not return error objects.  If the value
1037     of a binding is an Error object, it will be transformed to a null value
1038     instead.
1039 
1040     Note that this is not a transform function since it will be called at the
1041     end of the transform chain.
1042 
1043     @param {String} [fromPath] from path to connect.
1044     @param {Boolean} [aFlag] Pass NO to allow error objects again.
1045     @returns {SC.Binding} this
1046   */
1047   noError: function (fromPath, aFlag) {
1048     // If fromPath is a bool but aFlag is undefined, swap.
1049     if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
1050       aFlag = fromPath;
1051       fromPath = null;
1052     }
1053 
1054     // beget if needed.
1055     var binding = this.from(fromPath);
1056     if (binding === SC.Binding) binding = binding.beget();
1057     binding._noError = (aFlag === undefined) ? YES : aFlag;
1058 
1059     return binding;
1060   },
1061 
1062   /**
1063     Adds a transform to convert the value to the inverse of a bool value.  This
1064     uses the same transform as bool() but inverts it.
1065 
1066     @param {String} [fromPath]
1067     @returns {SC.Binding} this
1068   */
1069   not: function (fromPath) {
1070     return this.from(fromPath).transform(function (v) {
1071       var t = SC.typeOf(v);
1072       if (t === SC.T_ERROR) return v;
1073       return !((t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v);
1074     });
1075   },
1076 
1077   /**
1078     Adds a transform that will convert the passed value to an array.  If
1079     the value is null or undefined, it will be converted to an empty array.
1080 
1081     @param {String} [fromPath]
1082     @returns {SC.Binding} this
1083   */
1084   multiple: function (fromPath) {
1085     return this.from(fromPath).transform(function (value) {
1086       /*jshint eqnull:true*/
1087       if (!SC.isArray(value)) value = (value == null) ? [] : [value];
1088       return value;
1089     });
1090   },
1091 
1092   /**
1093     Adds a transform that will return the placeholder value if the value is
1094     null, undefined, an empty array or an empty string.  See also notNull().
1095 
1096     @param {String} fromPath from path or null
1097     @param {Object} [placeholder]
1098     @returns {SC.Binding} this
1099   */
1100   notEmpty: function (fromPath, placeholder) {
1101     if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER;
1102     return this.from(fromPath).transform(function (value, isForward) {
1103       if (SC.none(value) || (value === '') || (SC.isArray(value) && (value.get ? value.get('length') : value.length) === 0)) {
1104         value = placeholder;
1105       }
1106       return value;
1107     });
1108   },
1109 
1110   /**
1111     Adds a transform that will return the placeholder value if the value is
1112     null or undefined.  Otherwise it will pass through untouched.  See also notEmpty().
1113 
1114     @param {String} fromPath from path or null
1115     @param {Object} [placeholder]
1116     @returns {SC.Binding} this
1117   */
1118   notNull: function (fromPath, placeholder) {
1119     if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER;
1120     return this.from(fromPath).transform(function (value, isForward) {
1121       if (SC.none(value)) value = placeholder;
1122       return value;
1123     });
1124   },
1125 
1126   /**
1127     Adds a transform to the chain that will allow only single values to pass.
1128     This will allow single values, nulls, and error values to pass through.  If
1129     you pass an array, it will be mapped as so:
1130 
1131           [] => null
1132           [a] => a
1133           [a,b,c] => Multiple Placeholder
1134 
1135     You can pass in an optional multiple placeholder or it will use the
1136     default.
1137 
1138     Note that this transform will only happen on forwarded valued.  Reverse
1139     values are send unchanged.
1140 
1141     @param {String} fromPath from path or null
1142     @param {Object} [placeholder] placeholder value.
1143     @returns {SC.Binding} this
1144   */
1145   single: function (fromPath, placeholder) {
1146     if (placeholder === undefined) {
1147       placeholder = SC.MULTIPLE_PLACEHOLDER;
1148     }
1149     return this.from(fromPath).transform(function (value, isForward) {
1150       if (value && value.isEnumerable) {
1151         var len = value.get('length');
1152         value = (len > 1) ? placeholder : (len <= 0) ? null : value.firstObject();
1153       }
1154       return value;
1155     });
1156   },
1157 
1158   /**
1159     Adds a transform that will *always* return a String value. Null and undefined values will return
1160     an empty string while all other non-String values will be transformed using the toString method.
1161 
1162     Example results,
1163 
1164     - null => ''
1165     - undefined => ''
1166     - 123 => '123'
1167     - true => 'true'
1168     - {} => '[object Object]' (i.e. x = {}; return x.toString())
1169 
1170     @param {String} fromPath from path or null
1171     @returns {SC.Binding} this
1172   */
1173   string: function (fromPath) {
1174     return this.from(fromPath).transform(function (value) {
1175 
1176       // Null or undefined will be converted to an empty string.
1177       if (SC.none(value)) {
1178         value = '';
1179 
1180       // Non-string values will be converted to strings using `toString`.
1181       } else if (typeof value !== SC.T_STRING && value.toString) {
1182         value = value.toString();
1183       }
1184 
1185       return value;
1186     });
1187   },
1188 
1189   /* @private Used by mix adapter bindings. */
1190   _sc_mixAdapterBinding: function (adapterClass) {
1191     var paths = [];
1192 
1193     //@if(debug)
1194     // Add some developer support to prevent improper use.
1195     if (arguments.length < 3 ) {
1196       SC.Logger.warn('Developer Warning: Invalid mix binding, it should have at least two target paths');
1197     }
1198     //@endif
1199 
1200     // If either path is local, remove any * chains and append the localObject path to it.
1201     for (var i = 1; i < arguments.length; i++) {
1202       var path = arguments[i];
1203 
1204       if (path.indexOf('*') === 0 || path.indexOf('.') === 0) {
1205         path = path.slice(1).replace(/\*/g, '.');
1206         path = '*localObject.' + path;
1207       }
1208       paths.push( path );
1209     }
1210 
1211     // Gets the adapter class and instantiates a nice copy.
1212     var adapterHash = {
1213       localObject: null,
1214     };
1215 
1216     // create the oneWay bindings pointing to the real data sources.
1217     // for naming use a hardcoded convention 'value' + index of the property/path.
1218     // of course, these properties are internal so we are not concerned by the naming convention
1219     for (i = 0; i < paths.length; ++i) {
1220       var key = 'value' + i;
1221       adapterHash[key + 'Binding'] = SC.Binding.oneWay(paths[i]);
1222     }
1223 
1224     var adapter = adapterClass.create(adapterHash);
1225 
1226     // Creates and populates the return binding.
1227     var ret = this.from('aggregateProperty', adapter).oneWay();
1228 
1229     // This is all needed later on by beget, which must create a new adapter instance
1230     // or risk bad behavior.
1231     ret._MixAdapter = adapterClass;
1232     ret._mixAdapterHash = adapterHash;
1233     ret._mixAdapter = adapter;
1234 
1235     // On our way.
1236     return ret;
1237   },
1238 
1239   /** @private */
1240   _sc_mixImpl: function(paths, mixFunction) {
1241     var len = paths.length,
1242         properties = [];
1243 
1244     //@if(debug)
1245     // Add some developer support to prevent improper use.
1246     if (SC.none(mixFunction) || SC.typeOf(mixFunction) !== SC.T_FUNCTION ) {
1247       SC.Logger.error('Developer Error: Invalid mix binding, the last argument must be a function.');
1248     }
1249     //@endif
1250 
1251     // Create the adapter class that eventually will contain bindings pointing to all values that will be processed
1252     // by mixFunction. The effective aggregation is done by another property that depends on all these local properties
1253     // and is invalidated whenever they change.
1254     // First of all, create the list of the property names that the aggregate property depends on.
1255     // The names of these dynamically created properties are matching the pattern
1256     // mentioned above (into _sc_mixAdapterBinding): 'value' + index of the property/path
1257     for (var i = 0; i < len; ++i) {
1258       properties.push('value' + i);
1259     }
1260 
1261     // Create a proxy SC.Object which will be bound to the each of the paths and contain a computed
1262     // property that will be dependent on all of the bound properties. The computed property will
1263     // return the result of the mix function.
1264     var adapter = SC.Object.extend({
1265       // Use SC.Function.property to be able to pass an array as arguments to .property
1266       aggregateProperty: SC.Function.property(function() {
1267         // Get an array of current values that will be passed to the mix function.
1268         var values = properties.map(function (name) {
1269                                       return this.get(name);
1270                                     }, this);
1271 
1272         // Call the mixFunction providing an array containing all current source property values.
1273         return mixFunction.apply(null, values);
1274       }, properties).cacheable()
1275     });
1276 
1277     return this._sc_mixAdapterBinding.apply(this, [adapter].concat(paths));
1278   },
1279 
1280   /**
1281     Adds a transform that returns the logical 'AND' of all the values at the provided paths. This is
1282     a quick and useful way to bind a `Boolean` property to two or more other `Boolean` properties.
1283 
1284     For example, imagine that we wanted to only enable a deletion button when an item in a list
1285     is selected *and* the current user is allowed to delete items. If these two values are set
1286     on controllers respectively at `MyApp.itemsController.hasSelection` and
1287     `MyApp.userController.canDelete`. We could do the following,
1288 
1289         deleteButton: SC.ButtonView.design({
1290 
1291           // Action & target for the button.
1292           action: 'deleteSelectedItem',
1293           target: MyApp.statechart,
1294 
1295           // Whether the list has a selection or not.
1296           listHasSelectionBinding: SC.Binding.oneWay('MyApp.itemsController.hasSelection'),
1297 
1298           // Whether the user can delete items or not.
1299           userCanDeleteBinding: SC.Binding.oneWay('MyApp.userController.canDelete'),
1300 
1301           // Note: Only enable when the list has a selection and the user is allowed!
1302           isEnabled: function () {
1303             return this.get('listHasSelection') && this.get('userCanDelete');
1304           }.property('listHasSelection', 'userCanDelete').cacheable()
1305 
1306         })
1307 
1308     However, this would be much simpler to write by using the `and` binding transform like so,
1309 
1310         deleteButton: SC.ButtonView.design({
1311 
1312           // Action & target for the button.
1313           action: 'deleteSelectedItem',
1314           target: MyApp.statechart,
1315 
1316           // Note: Only enable when the list has a selection and the user is allowed!
1317           isEnabledBinding: SC.Binding.and('MyApp.itemsController.hasSelection', 'MyApp.userController.canDelete')
1318 
1319         })
1320 
1321     *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1322 
1323     @param {String...} the property paths of source values that will be provided to the AND transform.
1324   */
1325   and: function () {
1326     // Fast copy.
1327     var len = arguments.length,
1328         paths = new Array(len);
1329     for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1330 
1331     // Create a new mix implementation for the AND function.
1332     return this._sc_mixImpl(paths, function() {
1333       var result = true;
1334 
1335       for (i = 0; result && (i < arguments.length); i++) { // Bails early if any value is false.
1336         result = result && arguments[i];
1337       }
1338 
1339       return result;
1340     });
1341   },
1342 
1343   /**
1344     Adds a transform that returns the logical 'OR' of all the values at the provided paths. This is
1345     a quick and useful way to bind a `Boolean` property to two or more other `Boolean` properties.
1346 
1347     For example, imagine that we wanted to show a button when one or both of two values are present.
1348     If these two values are set on controllers respectively at `MyApp.profileController.hasDisplayName` and
1349     `MyApp.profileController.hasFullName`. We could do the following,
1350 
1351         saveButton: SC.ButtonView.design({
1352 
1353           // Action & target for the button.
1354           action: 'saveProfile',
1355           target: MyApp.statechart,
1356 
1357           // Whether the profile has a displayName or not.
1358           profileHasDisplayNameBinding: SC.Binding.oneWay('MyApp.profileController.hasDisplayName'),
1359 
1360           // Whether the profile has a fullName or not.
1361           profileHasFullNameBinding: SC.Binding.oneWay('MyApp.profileController.hasFullName'),
1362 
1363           // Note: Only show when the profile has a displayName or a fullName or both!
1364           isVisible: function () {
1365             return this.get('profileHasDisplayName') || this.get('profileHasFullName');
1366           }.property('profileHasDisplayName', 'profileHasFullName').cacheable()
1367 
1368         })
1369 
1370     However, this would be much simpler to write by using the `or` binding transform like so,
1371 
1372         saveButton: SC.ButtonView.design({
1373 
1374           // Action & target for the button.
1375           action: 'saveProfile',
1376           target: MyApp.statechart,
1377 
1378           // Note: Only show when the profile has a displayName or a fullName or both!
1379           isVisibleBinding: SC.Binding.or('MyApp.profileController.hasDisplayName', 'MyApp.profileController.hasFullName')
1380 
1381         })
1382 
1383     *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1384 
1385     @param {String...} the paths of source values that will be provided to the OR sequence.
1386   */
1387   or: function () {
1388     // Fast copy.
1389     var len = arguments.length,
1390         paths = new Array(len);
1391     for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1392 
1393     // Create a new mix implementation for the OR function.
1394     return this._sc_mixImpl( paths, function() {
1395       var result = false;
1396       for (i = 0; !result && (i < arguments.length); i++) { // Bails early if any value is true.
1397         result = result || arguments[i];
1398       }
1399 
1400       return result;
1401     });
1402   },
1403 
1404   /**
1405     Adds a transform that aggregates through a given function the values at the provided paths. The
1406     given function is called whenever any of the values are updated. This is a quick way to
1407     aggregate multiple properties into a single property value.
1408 
1409     For example, to concatenate two properties 'MyApp.groupController.name' and
1410     'MyApp.userController.fullName', we could do the following,
1411 
1412         currentGroupUserLabel: SC.LabelView.extend({
1413 
1414           // The group name (may be null).
1415           groupNameBinding: SC.Binding.oneWay('MyApp.groupController.name'),
1416 
1417           // The user full name (may be null).
1418           userFullNameBinding: SC.Binding.oneWay('MyApp.userController.fullName'),
1419 
1420           // Ex. Returns one of "", "Selected Group", or "Selected Group: Selected User"
1421           value: function () {
1422             var groupName = this.get('groupName'),
1423                 userFullName = this.get('userFullName');
1424 
1425             if (SC.none(userFullName)) {
1426               if (SC.none(groupName)) {
1427                 return ''; // No group and no user.
1428               } else {
1429                 return groupName; // Just a group.
1430               }
1431             } else {
1432               return '%@: %@'.fmt(groupName, userFullName); // Group and user.
1433             }
1434           }.property('groupName', 'userFullName').cacheable()
1435 
1436         })
1437 
1438     However, this is simpler (ex. 86 fewer characters) to write by using the `mix` binding transform like so,
1439 
1440         currentGroupUserLabel: SC.LabelView.extend({
1441 
1442           // Ex. Returns one of "", "Selected Group", or "Selected Group: Selected User"
1443           valueBinding: SC.Binding.mix(
1444             'MyApp.groupController.name', // The group name (may be null).
1445             'MyApp.userController.fullName', // The user full name (may be null).
1446 
1447             // Aggregate function. The arguments match the bound property values above.
1448             function (groupName, userFullName) {
1449               if (SC.none(userFullName)) {
1450                 if (SC.none(groupName)) {
1451                   return ''; // No group and no user.
1452                 } else {
1453                   return groupName; // Just a group.
1454                 }
1455               } else {
1456                 return '%@: %@'.fmt(groupName, userFullName); // Group and user.
1457               }
1458             })
1459 
1460         })
1461 
1462     *Note:* the number of parameters of `mixFunction` should match the number of paths provided.
1463     *Note:* the transform acts strictly as a one-way binding, working only in the one direction.
1464 
1465     @param {String...} the paths of source values that will be provided to the aggregate function.
1466     @param {Function} mixFunction the function that aggregates the values
1467   */
1468   mix: function() {
1469     var len = arguments.length - 1,
1470         paths = new Array(len);
1471 
1472     // Fast copy. The function is the last argument.
1473     for (var i = 0; i < len; i++) { paths[i] = arguments[i]; }
1474 
1475     return this._sc_mixImpl(paths, arguments[len]);
1476   },
1477 
1478   /**
1479     Adds a transform that will return YES if the value is equal to equalValue, NO otherwise.
1480 
1481       isVisibleBinding: SC.Binding.oneWay("MyApp.someController.title").equalTo(comparisonValue)
1482 
1483     Or:
1484 
1485       isVisibleBinding: SC.Binding.equalTo("MyApp.someController.title", comparisonValue)
1486 
1487     @param {String} fromPath from path or null
1488     @param {Object} equalValue the value to compare with
1489     @returns {SC.Binding} this
1490   */
1491   equalTo: function(fromPath, equalValue) {
1492     // Normalize arguments.
1493     if (equalValue === undefined) {
1494       equalValue = fromPath;
1495       fromPath = null;
1496     }
1497 
1498     return this.from(fromPath).transform(function(value, binding) {
1499        return value === equalValue;
1500      });
1501   },
1502 
1503   /** @private */
1504   toString: function () {
1505     var from = this._fromRoot ? "<%@>:%@".fmt(this._fromRoot, this._fromPropertyPath) : this._fromPropertyPath;
1506 
1507     var to = this._toRoot ? "<%@>:%@".fmt(this._toRoot, this._toPropertyPath) : this._toPropertyPath;
1508 
1509     var oneWay = this._oneWay ? '[oneWay]' : '';
1510     return "SC.Binding%@(%@ -> %@)%@".fmt(SC.guidFor(this), from, to, oneWay);
1511   }
1512 };
1513 
1514 /**
1515   Shorthand method to define a binding.  This is the same as calling:
1516 
1517         SC.binding(path) = SC.Binding.from(path)
1518 */
1519 SC.binding = function (path, root) { return SC.Binding.from(path, root); };
1520