/****************************************************************************** This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. *****************************************************************************/ /** Binding Binds the properties on objects together so that they are always in sync. Usage: Binding.bind(object, property, object2, property2); object.set('property', 'value'); object2.get('property2'); // returns 'value'; */ var Binding = function() { var BINDINGS = [], ID_COUNTER = 0, TRANSACTION_ID = 0; /** Bind @public @param object source @param string sourceProperty @param object target @param string targetProperty @param boolean bidirectional optional default true */ function bind(source, sourceProperty, target, targetProperty, bidirectional) { Binding.Casting.cast(source); Binding.Casting.cast(target); makeBindable(source).add(sourceProperty, target, targetProperty); if (bidirectional !== false) { makeBindable(target).add(targetProperty, source, sourceProperty); } trigger(source, sourceProperty); } /** makeBindable @private @param object object */ function makeBindable(object) { if (typeof object._BINDING_ID === "undefined" || ! BINDINGS[object._BINDING_ID]) { object._BINDING_ID = ++ID_COUNTER; BINDINGS[object._BINDING_ID] = new Binding.Object(object); } return BINDINGS[object._BINDING_ID]; } /** trigger If this is the initial trigger called by a bound source object, a new transaction id is generated. @protected @param object source @param string property @param number transactionId optional */ function trigger(source, property, transactionId) { if (BINDINGS[source._BINDING_ID]) { if (typeof transactionId === "undefined") { transactionId = ++TRANSACTION_ID; source._LAST_TRANSACTION = transactionId; } BINDINGS[source._BINDING_ID].trigger(property, transactionId); } } return { bind: bind, trigger: trigger } }(); /** Binding.Object Stores the bindings for an object in arrays based on the bound property */ Binding.Object = function(object) { this.object = object; this.bindings = {}; } Binding.Object.prototype = { /** add @public @param string property @param object target @param string targetProperty */ add : function(property, target, targetProperty) { if (! this.bindings[property]) { this.bindings[property] = []; } this.bindings[property].push({ target: target, property: targetProperty }); }, /** trigger @public @param string property @param number transactionId */ trigger : function(property, transactionId) { if (this.bindings[property]) { var binding; for (var i = 0, j = this.bindings[property].length; i < j; i++) { binding = this.bindings[property][i]; if (binding.target._LAST_TRANSACTION !== transactionId) { binding.target._LAST_TRANSACTION = transactionId; binding.target.set(binding.property, this.object.get(property), transactionId); } } } } } /** Binding.Casting Utility for making objects bindable. Adds `get` and `set` methods, and includes event listeners for basic HTML form UI elements. Usage: Binding.Casting.cast(object); */ Binding.Casting = function() { /** cast @public @param object object */ function cast(object) { if (object._BINDABLE) { return; } // adds a generic getter method object.get = function(property) { return get(this, property); }; // adds a generic setter method object.set = function(property, value, transactionId) { return set(this, property, value, transactionId) }; if (object.nodeName && Elements[object.nodeName]) { castElement(object); } object._BINDABLE = true; } /** castElement @private @param object object */ function castElement(object) { // look through the Element definitions based on nodeName if (typeof Elements[object.nodeName] === "function") { Elements[object.nodeName](object); } else { // look through the Element definitions based on the values of an attribute for (var attribute in Elements[object.nodeName]) { for (var subkey in Elements[object.nodeName][attribute]) { if (object[attribute] === subkey) { Elements[object.nodeName][attribute][subkey](object); } } } } } /** get @private @param object object @param string property */ function get(object, property) { return object[property]; } /** set @private @param object object @param string property */ function set(object, property, value, transactionId) { object[property] = value; Binding.trigger(object, property, transactionId); return object; } var Elements = { INPUT : { type : { // adds event to track changes in value text : function(object) { object.addEventListener("keyup", function(event) { Binding.trigger(object, "value"); }, false); }, // adds mouse event to track changes in value checkbox : function(object) { object.addEventListener("click", function(event) { Binding.trigger(object, "checked"); }, false); } } }, SELECT : function(object) { // implements getter method for `value` object.get = function(property) { if (property === "value") { return this.options[object.selectedIndex].value; } else { return get(this, property); } }; // implements setter method for `value` object.set = function(property, value, transactionId) { if (property === "value") { for (var i = 0, j = this.options.length; i < j; i++) { if (this.options[i].value === value) { this.selectedIndex = i; } } } return set(this, property, value, transactionId); } // adds event to track changes in value object.addEventListener("change", function(event) { object._value = object.options[object.selectedIndex].value; Binding.trigger(object, "value"); }, false); }, // adds keyboard event to track changes in value TEXTAREA : function(object) { object.addEventListener("keyup", function(event) { Binding.trigger(object, "value"); }, false); } }; return { cast: cast } }();