Created
October 21, 2011 01:31
-
-
Save willbailey/1302885 to your computer and use it in GitHub Desktop.
connections
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ### Events | |
// Event mixin copied from Backbone | |
var Events = { | |
bind: function(evt, callback) { | |
var events = this._events = this._events || {}; | |
var callbacks = events[evt] = events[evt] || []; | |
callbacks.push(callback); | |
}, | |
unbind: function(evt, callback) { | |
if (!this._events) return; | |
if (!evt && !callback) { | |
this._events = {}; | |
} else if (!callback) { | |
delete this._events[evt]; | |
} else { | |
var callbacks = this._events[evt] || []; | |
for (var i = 0, len = callbacks.length; i < len; i++) { | |
callback === callbacks[i] && callbacks.splice(i,1); | |
} | |
} | |
}, | |
trigger: function(evt) { | |
var i, len, events = this._events || {}, | |
callbacks = events[evt] = events[evt] || []; | |
for (i = 0, len = callbacks.length; i < len; i++) { | |
callbacks[i].apply(this, Array.prototype.slice.call(arguments, 1)); | |
} | |
if (!events['all']) return; | |
callbacks = events['all']; | |
for (i = 0, len = callbacks.length; i < len; i++) { | |
callbacks[i].apply(this, Array.prototype.slice.call(arguments)); | |
} | |
} | |
}; | |
// ### Utilities | |
// Extend an object with properties of the passed in object[s] | |
var extend = function(obj) { | |
var extensions = Array.prototype.slice.call(arguments, 1); | |
for (var i = 0, len = extensions.length; i < len; i++) { | |
var extension = extensions[i]; | |
for (var key in extension) { | |
if (extension.hasOwnProperty(key)) { | |
obj[key] = extension[key]; | |
} | |
} | |
} | |
}; | |
// bind a function to an execution context | |
// TODO: use native bind if possible | |
var bind = function(func, context) { | |
var args = Array.prototype.splice.call(arguments, 2); | |
return function() { | |
func.apply(context, args.concat(Array.prototype.slice.call(arguments))); | |
}; | |
}; | |
// TODO: detect if an object is a DOM node | |
var isElement = function(obj) { | |
return ( | |
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 | |
typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string"); | |
}; | |
// ### Connectable wraps an object so it can be connected | |
// You can specify a list of transforms per property to apply | |
// in the format: | |
// { | |
// property: function(inValue) {return inValue.toLowerCase()} | |
// } | |
// You can also specify a list of before and after hooks to be fired | |
// before and after the property is changed. | |
Connectable = function(obj, options) { | |
this.obj = isElement(obj) ? this.nodeProxy(obj) : obj; | |
this._transforms = options.transforms || {}; | |
this._befores = options.befores || {}; | |
this._afters = options.afters || {}; | |
}; | |
extend(Connectable.prototype, Events, { | |
// TODO: if we get a dom node we'll create a proxy for it that observers the | |
// DOM events it fires and updates a proxy object that can be connected | |
// directly. | |
nodeProxy: function(element) { | |
// TODO: observe dom node for state changes | |
var listener = function() { | |
}; | |
element.addEventListener('keypress', bind(listener, this)); | |
element.addEventListener('change', bind(listener, this)); | |
return obj; | |
}, | |
// Update a property of the object and fire an event to notify any connections | |
// If the silent flag is passed, no event is fired. | |
set: function(key, value, silent) { | |
var priorValue = this.obj[key]; | |
if (priorValue !== value && !silent) { | |
this._beforeChange(); | |
this.obj[key] = this._transform(value, key); | |
this._afterChange(); | |
this.trigger('change', {property: key, priorValue: priorValue}); | |
} | |
}, | |
// Get a property of the wrapped object. | |
get: function(key) { | |
return this.obj[key]; | |
}, | |
// Invoke any transforms on the changed key | |
_transform: function(value, key) { | |
var transformer = this._transformers[key]; | |
if (transformer) { | |
return transformer(value); | |
} else { | |
return value; | |
} | |
}, | |
// fire before change hooks | |
_beforeChange: function(value, key) { | |
this._executeHooks(value, key, this._befores); | |
}, | |
// fire after change hooks | |
_afterChange: function(value, key) { | |
this._executeHooks(value, key, this._afters); | |
}, | |
_executeHooks: function(value, key, hooks) { | |
var keyHook = hooks[key]; | |
var allHook = hooks['all']; | |
keyHook && keyHook(value); | |
allHook && allHook(value, key); | |
} | |
}); | |
// ### Connection | |
// The connection class maintains a link between two objects referred to as | |
// the left and right object. When a change event fires on one of the connected | |
// objects the connection syncs the state on the connected objects. | |
// | |
// The objects passed into the connection are wrapped in the Connectable | |
// interface to facilitate the generation of events and synchronization of | |
// state. | |
Connection = function(left, right) { | |
this.left = left.isConnectable ? left : new Connectable(left); | |
this.right = right.isConnectable ? right : new Connectable(right); | |
this.left.bind('change', bind(this.leftChange, this)); | |
this.right.bind('change', bind(this.rightChange, this)); | |
}; | |
extend(Connection.prototype, Events, { | |
leftChange: function(data) { | |
this.sync(this.right, this.left, data.property); | |
}, | |
rightChange: function(data) { | |
this.sync(this.left, this.right, data.property); | |
}, | |
sync: function(target, source, property) { | |
target.set(property, source.get(property), true); | |
} | |
}); | |
// TODO: deal with collections and arrays |
Thanks Jan!...was just a sketch of an idea I had for data binding I was showing Jordan.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I don't know if you intend to use this, but while reading it I noticed two things:
Line 19 has a subtle bug (at least I think it does): If two same callbacks are in the array one after the other only the first will be deleted, just use
for (var i = array.length; i--; ) { ... }
instead.Line 28 and 33: the result of Array.prototype.slice.call can be cached.
Line