Created
February 10, 2011 18:24
-
-
Save timruffles/821039 to your computer and use it in GitHub Desktop.
Takes the sweet dojo event functions - dojo.connect, dojo.publish/subscribe, dojo.hitch - and pulls them out. Relies on a few underscore.js methods at the mo, could easily be removed
This file contains 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
var dojo = d = {} | |
// this file courtesy of the TurboAjax Group, licensed under a Dojo CLA | |
dojo.hitch = function(/*Object*/scope, /*Function|String*/method /*,...*/){ | |
// summary: | |
// Returns a function that will only ever execute in the a given scope. | |
// This allows for easy use of object member functions | |
// in callbacks and other places in which the "this" keyword may | |
// otherwise not reference the expected scope. | |
// Any number of default positional arguments may be passed as parameters | |
// beyond "method". | |
// Each of these values will be used to "placehold" (similar to curry) | |
// for the hitched function. | |
// scope: | |
// The scope to use when method executes. If method is a string, | |
// scope is also the object containing method. | |
// method: | |
// A function to be hitched to scope, or the name of the method in | |
// scope to be hitched. | |
// example: | |
// | dojo.hitch(foo, "bar")(); | |
// runs foo.bar() in the scope of foo | |
// example: | |
// | dojo.hitch(foo, myFunction); | |
// returns a function that runs myFunction in the scope of foo | |
// example: | |
// Expansion on the default positional arguments passed along from | |
// hitch. Passed args are mixed first, additional args after. | |
// | var foo = { bar: function(a, b, c){ console.log(a, b, c); } }; | |
// | var fn = dojo.hitch(foo, "bar", 1, 2); | |
// | fn(3); // logs "1, 2, 3" | |
// example: | |
// | var foo = { bar: 2 }; | |
// | dojo.hitch(foo, function(){ this.bar = 10; })(); | |
// execute an anonymous function in scope of foo | |
if(arguments.length > 2){ | |
return d._hitchArgs.apply(d, arguments); // Function | |
} | |
if(!method){ | |
method = scope; | |
scope = null; | |
} | |
if(_.isString(method)){ | |
scope = scope || d.global; | |
if(!scope[method]){ throw(['dojo.hitch: scope["', method, '"] is null (scope="', scope, '")'].join('')); } | |
return function(){ return scope[method].apply(scope, arguments || []); }; // Function | |
} | |
return !scope ? method : function(){ return method.apply(scope, arguments || []); }; // Function | |
} | |
// low-level delegation machinery | |
dojo._listener = { | |
// create a dispatcher function | |
getDispatcher: function(){ | |
// following comments pulled out-of-line to prevent cloning them | |
// in the returned function. | |
// - indices (i) that are really in the array of listeners (ls) will | |
// not be in Array.prototype. This is the 'sparse array' trick | |
// that keeps us safe from libs that take liberties with built-in | |
// objects | |
// - listener is invoked with current scope (this) | |
return function(){ | |
var ap=Array.prototype, c=arguments.callee, ls=c._listeners, t=c.target; | |
// return value comes from original target function | |
var r = t && t.apply(this, arguments); | |
// make local copy of listener array so it is immutable during processing | |
var i, lls; | |
lls = [].concat(ls); | |
// invoke listeners after target function | |
for(i in lls){ | |
if(!(i in ap)){ | |
lls[i].apply(this, arguments); | |
} | |
} | |
// return value comes from original target function | |
return r; | |
}; | |
}, | |
// add a listener to an object | |
add: function(/*Object*/ source, /*String*/ method, /*Function*/ listener){ | |
// Whenever 'method' is invoked, 'listener' will have the same scope. | |
// Trying to supporting a context object for the listener led to | |
// complexity. | |
// Non trivial to provide 'once' functionality here | |
// because listener could be the result of a dojo.hitch call, | |
// in which case two references to the same hitch target would not | |
// be equivalent. | |
source = source || dojo.global; | |
// The source method is either null, a dispatcher, or some other function | |
var f = source[method]; | |
// Ensure a dispatcher | |
if(!f || !f._listeners){ | |
var d = dojo._listener.getDispatcher(); | |
// original target function is special | |
d.target = f; | |
// dispatcher holds a list of listeners | |
d._listeners = []; | |
// redirect source to dispatcher | |
f = source[method] = d; | |
} | |
// The contract is that a handle is returned that can | |
// identify this listener for disconnect. | |
// | |
// The type of the handle is private. Here is it implemented as Integer. | |
// DOM event code has this same contract but handle is Function | |
// in non-IE browsers. | |
// | |
// We could have separate lists of before and after listeners. | |
return f._listeners.push(listener); /*Handle*/ | |
}, | |
// remove a listener from an object | |
remove: function(/*Object*/ source, /*String*/ method, /*Handle*/ handle){ | |
var f = (source || dojo.global)[method]; | |
// remember that handle is the index+1 (0 is not a valid handle) | |
if(f && f._listeners && handle--){ | |
delete f._listeners[handle]; | |
} | |
} | |
}; | |
// Multiple delegation for arbitrary methods. | |
// This unit knows nothing about DOM, but we include DOM aware documentation | |
// and dontFix argument here to help the autodocs. Actual DOM aware code is in | |
// event.js. | |
dojo.connect = function(/*Object|null*/ obj, | |
/*String*/ event, | |
/*Object|null*/ context, | |
/*String|Function*/ method, | |
/*Boolean?*/ dontFix){ | |
// summary: | |
// `dojo.connect` is the core event handling and delegation method in | |
// Dojo. It allows one function to "listen in" on the execution of | |
// any other, triggering the second whenever the first is called. Many | |
// listeners may be attached to a function, and source functions may | |
// be either regular function calls or DOM events. | |
// | |
// description: | |
// Connects listeners to actions, so that after event fires, a | |
// listener is called with the same arguments passed to the original | |
// function. | |
// | |
// Since `dojo.connect` allows the source of events to be either a | |
// "regular" JavaScript function or a DOM event, it provides a uniform | |
// interface for listening to all the types of events that an | |
// application is likely to deal with though a single, unified | |
// interface. DOM programmers may want to think of it as | |
// "addEventListener for everything and anything". | |
// | |
// When setting up a connection, the `event` parameter must be a | |
// string that is the name of the method/event to be listened for. If | |
// `obj` is null, `dojo.global` is assumed, meaning that connections | |
// to global methods are supported but also that you may inadvertently | |
// connect to a global by passing an incorrect object name or invalid | |
// reference. | |
// | |
// `dojo.connect` generally is forgiving. If you pass the name of a | |
// function or method that does not yet exist on `obj`, connect will | |
// not fail, but will instead set up a stub method. Similarly, null | |
// arguments may simply be omitted such that fewer than 4 arguments | |
// may be required to set up a connection See the examples for details. | |
// | |
// The return value is a handle that is needed to | |
// remove this connection with `dojo.disconnect`. | |
// | |
// obj: | |
// The source object for the event function. | |
// Defaults to `dojo.global` if null. | |
// If obj is a DOM node, the connection is delegated | |
// to the DOM event manager (unless dontFix is true). | |
// | |
// event: | |
// String name of the event function in obj. | |
// I.e. identifies a property `obj[event]`. | |
// | |
// context: | |
// The object that method will receive as "this". | |
// | |
// If context is null and method is a function, then method | |
// inherits the context of event. | |
// | |
// If method is a string then context must be the source | |
// object object for method (context[method]). If context is null, | |
// dojo.global is used. | |
// | |
// method: | |
// A function reference, or name of a function in context. | |
// The function identified by method fires after event does. | |
// method receives the same arguments as the event. | |
// See context argument comments for information on method's scope. | |
// | |
// dontFix: | |
// If obj is a DOM node, set dontFix to true to prevent delegation | |
// of this connection to the DOM event manager. | |
// | |
// example: | |
// When obj.onchange(), do ui.update(): | |
// | dojo.connect(obj, "onchange", ui, "update"); | |
// | dojo.connect(obj, "onchange", ui, ui.update); // same | |
// | |
// example: | |
// Using return value for disconnect: | |
// | var link = dojo.connect(obj, "onchange", ui, "update"); | |
// | ... | |
// | dojo.disconnect(link); | |
// | |
// example: | |
// When onglobalevent executes, watcher.handler is invoked: | |
// | dojo.connect(null, "onglobalevent", watcher, "handler"); | |
// | |
// example: | |
// When ob.onCustomEvent executes, customEventHandler is invoked: | |
// | dojo.connect(ob, "onCustomEvent", null, "customEventHandler"); | |
// | dojo.connect(ob, "onCustomEvent", "customEventHandler"); // same | |
// | |
// example: | |
// When ob.onCustomEvent executes, customEventHandler is invoked | |
// with the same scope (this): | |
// | dojo.connect(ob, "onCustomEvent", null, customEventHandler); | |
// | dojo.connect(ob, "onCustomEvent", customEventHandler); // same | |
// | |
// example: | |
// When globalEvent executes, globalHandler is invoked | |
// with the same scope (this): | |
// | dojo.connect(null, "globalEvent", null, globalHandler); | |
// | dojo.connect("globalEvent", globalHandler); // same | |
// normalize arguments | |
var a=arguments, args=[], i=0; | |
// if a[0] is a String, obj was omitted | |
args.push(_.isString(a[0]) ? null : a[i++], a[i++]); | |
// if the arg-after-next is a String or Function, context was NOT omitted | |
var a1 = a[i+1]; | |
args.push(_.isString(a1)||_.isFunction(a1) ? a[i++] : null, a[i++]); | |
// absorb any additional arguments | |
for(var l=a.length; i<l; i++){ args.push(a[i]); } | |
// do the actual work | |
return dojo._connect.apply(this, args); /*Handle*/ | |
} | |
// used by non-browser hostenvs. always overriden by event.js | |
dojo._connect = function(obj, event, context, method){ | |
var l=dojo._listener, h=l.add(obj, event, dojo.hitch(context, method)); | |
return [obj, event, h, l]; // Handle | |
} | |
dojo.disconnect = function(/*Handle*/ handle){ | |
// summary: | |
// Remove a link created by dojo.connect. | |
// description: | |
// Removes the connection between event and the method referenced by handle. | |
// handle: | |
// the return value of the dojo.connect call that created the connection. | |
if(handle && handle[0] !== undefined){ | |
dojo._disconnect.apply(this, handle); | |
// let's not keep this reference | |
delete handle[0]; | |
} | |
} | |
dojo._disconnect = function(obj, event, handle, listener){ | |
listener.remove(obj, event, handle); | |
} | |
// topic publish/subscribe | |
dojo._topics = {}; | |
dojo.subscribe = function(/*String*/ topic, /*Object|null*/ context, /*String|Function*/ method){ | |
// summary: | |
// Attach a listener to a named topic. The listener function is invoked whenever the | |
// named topic is published (see: dojo.publish). | |
// Returns a handle which is needed to unsubscribe this listener. | |
// context: | |
// Scope in which method will be invoked, or null for default scope. | |
// method: | |
// The name of a function in context, or a function reference. This is the function that | |
// is invoked when topic is published. | |
// example: | |
// | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); }); | |
// | dojo.publish("alerts", [ "read this", "hello world" ]); | |
// support for 2 argument invocation (omitting context) depends on hitch | |
return [topic, dojo._listener.add(dojo._topics, topic, dojo.hitch(context, method))]; /*Handle*/ | |
} | |
dojo.unsubscribe = function(/*Handle*/ handle){ | |
// summary: | |
// Remove a topic listener. | |
// handle: | |
// The handle returned from a call to subscribe. | |
// example: | |
// | var alerter = dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); }; | |
// | ... | |
// | dojo.unsubscribe(alerter); | |
if(handle){ | |
dojo._listener.remove(dojo._topics, handle[0], handle[1]); | |
} | |
} | |
dojo.publish = function(/*String*/ topic, /*Array*/ args){ | |
// summary: | |
// Invoke all listener method subscribed to topic. | |
// topic: | |
// The name of the topic to publish. | |
// args: | |
// An array of arguments. The arguments will be applied | |
// to each topic subscriber (as first class parameters, via apply). | |
// example: | |
// | dojo.subscribe("alerts", null, function(caption, message){ alert(caption + "\n" + message); }; | |
// | dojo.publish("alerts", [ "read this", "hello world" ]); | |
// Note that args is an array, which is more efficient vs variable length | |
// argument list. Ideally, var args would be implemented via Array | |
// throughout the APIs. | |
var f = dojo._topics[topic]; | |
if(f){ | |
f.apply(this, args||[]); | |
} | |
} | |
dojo.connectPublisher = function( /*String*/ topic, | |
/*Object|null*/ obj, | |
/*String*/ event){ | |
// summary: | |
// Ensure that every time obj.event() is called, a message is published | |
// on the topic. Returns a handle which can be passed to | |
// dojo.disconnect() to disable subsequent automatic publication on | |
// the topic. | |
// topic: | |
// The name of the topic to publish. | |
// obj: | |
// The source object for the event function. Defaults to dojo.global | |
// if null. | |
// event: | |
// The name of the event function in obj. | |
// I.e. identifies a property obj[event]. | |
// example: | |
// | dojo.connectPublisher("/ajax/start", dojo, "xhrGet"); | |
var pf = function(){ dojo.publish(topic, arguments); } | |
return event ? dojo.connect(obj, event, pf) : dojo.connect(obj, pf); //Handle | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment