delegate.js revision 0211eb547f9f237c08793afee4008af3e8bd3da9
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore/**
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * Adds event delegation support to the library.
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove *
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * @module event
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * @submodule event-delegate
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore */
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smithvar toArray = Y.Array,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith YLang = Y.Lang,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith isString = YLang.isString,
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith isObject = YLang.isObject,
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith isArray = YLang.isArray,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith selectorTest = Y.Selector.test,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith detachCategories = Y.Env.evt.handles;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith/**
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * <p>Sets up event delegation on a container element. The delegated event
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * will use a supplied selector or filtering function to test if the event
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * references at least one node that should trigger the subscription
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * callback.</p>
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith *
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * <p>Selector string filters will trigger the callback if the event originated
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * from a node that matches it or is contained in a node that matches it.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * Function filters are called for each Node up the parent axis to the
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * subscribing container node, and receive at each level the Node and the event
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * object. The function should return true (or a truthy value) if that Node
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * should trigger the subscription callback. Note, it is possible for filters
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * to match multiple Nodes for a single event. In this case, the delegate
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * callback will be executed for each matching Node.</p>
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith *
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * <p>For each matching Node, the callback will be executed with its 'this'
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * object set to the Node matched by the filter (unless a specific context was
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * provided during subscription), and the provided event's
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * <code>currentTarget</code> will also be set to the matching Node. The
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * containing Node from which the subscription was originally made can be
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * referenced as <code>e.container</code>.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith *
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @method delegate
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param type {String} the event type to delegate
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param fn {Function} the callback function to execute. This function
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * will be provided the event object for the delegated event.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param el {String|node} the element that is the delegation container
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param spec {string|Function} a selector that must match the target of the
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * event or a function to test target and its parents for a match
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param context optional argument that specifies what 'this' refers to.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @param args* 0..n additional arguments to pass on to the callback function.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * These arguments will be added after the event object.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * @return {EventHandle} the detach handle
f60d4516da25663a6679c02654d8c2da2701657bLuke Smith * @static
f60d4516da25663a6679c02654d8c2da2701657bLuke Smith * @for Event
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith */
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithfunction delegate(type, fn, el, filter) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith var args = toArray(arguments, 0, true),
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith query = isString(el) ? el : null,
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith typeBits, synth, container, categories, cat, i, len, handles, handle;
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove // Support Y.delegate({ click: fnA, key: fnB }, el, filter, ...);
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove // and Y.delegate(['click', 'key'], fn, el, filter, ...);
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith if (isObject(type)) {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith handles = [];
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith if (isArray(type)) {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith for (i = 0, len = type.length; i < len; ++i) {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith args[0] = type[i];
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith handles.push(Y.delegate.apply(Y, args));
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith }
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith } else {
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove // Y.delegate({'click', fn}, el, filter) =>
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove // Y.delegate('click', fn, el, filter)
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith args.unshift(null); // one arg becomes two; need to make space
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith for (i in type) {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith if (type.hasOwnProperty(i)) {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith args[0] = i;
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith args[1] = type[i];
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith handles.push(Y.delegate.apply(Y, args));
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith }
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith }
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith }
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith return new Y.EventHandle(handles);
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith }
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith typeBits = type.split(/\|/);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (typeBits.length > 1) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith cat = typeBits.shift();
2f788db79418bb9fff1a97f29f12dad42f4909efLuke Smith args[0] = type = typeBits.shift();
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith synth = Y.Node.DOM_EVENTS[type];
43c15c82a8f3e7361f944d6daa02f1885c88ebf0Adam Moore
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith if (isObject(synth) && synth.delegate) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith handle = synth.delegate.apply(synth, arguments);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
edac37f62b5675c375833896993369855211cbfdTodd Kloots
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (!handle) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (!type || !fn || !el || !filter) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith Y.log("delegate requires type, callback, parent, & filter", "warn");
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith return;
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith container = (query) ? Y.Selector.query(query, null, true) : el;
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (!container && isString(el)) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith handle = Y.on('available', function () {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith Y.mix(handle, Y.delegate.apply(Y, args), true);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }, el);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (!handle && container) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith args.splice(2, 2, container); // remove the filter
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith handle = Y.Event._attach(args, { facade: false });
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith handle.sub.filter = filter;
63d012ee193ba8c768b2a2aade99081422759213Luke Smith handle.sub._notify = delegate.notifySub;
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (handle && cat) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith categories = detachCategories[cat] || (detachCategories[cat] = {});
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith categories = categories[type] || (categories[type] = []);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith categories.push(handle);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith return handle;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith}
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith/**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithOverrides the <code>_notify</code> method on the normal DOM subscription to
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithinject the filtering logic and only proceed in the case of a match.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithThis method is hosted as a private property of the `delegate` method
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith(e.g. `Y.delegate.notifySub`)
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@method notifySub
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param thisObj {Object} default 'this' object for the callback
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param args {Array} arguments passed to the event's <code>fire()</code>
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param ce {CustomEvent} the custom event managing the DOM subscriptions for
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith the subscribed event on the subscribing node.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@return {Boolean} false if the event was stopped
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@private
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@static
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@since 3.2.0
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith**/
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithdelegate.notifySub = function (thisObj, args, ce) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith // Preserve args for other subscribers
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith args = args.slice();
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (this.args) {
426d8b6487a18aa68071eecf43ed7bc4f58f36d4Luke Smith args.push.apply(args, this.args);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith // Only notify subs if the event occurred on a targeted element
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith var currentTarget = delegate._applyFilter(this.filter, args, ce),
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith //container = e.currentTarget,
851a447adf0cc52bb286ca6e8d71c3f6e088030dLuke Smith e, i, len, ret;
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (currentTarget) {
14017741ef334485c65013d129608513161db7c2Luke Smith // Support multiple matches up the the container subtree
14017741ef334485c65013d129608513161db7c2Luke Smith currentTarget = toArray(currentTarget);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // The second arg is the currentTarget, but we'll be reusing this
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // facade, replacing the currentTarget for each use, so it doesn't
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // matter what element we seed it with.
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith e.container = Y.one(ce.el);
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove
851a447adf0cc52bb286ca6e8d71c3f6e088030dLuke Smith for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith e.currentTarget = Y.one(currentTarget[i]);
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith ret = this.fn.apply(this.context || e.currentTarget, args);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
0c4bb3413c8172f27fcebdf7242f5798d026064bLuke Smith if (ret === false) { // stop further notifications
0c4bb3413c8172f27fcebdf7242f5798d026064bLuke Smith break;
14017741ef334485c65013d129608513161db7c2Luke Smith }
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
14017741ef334485c65013d129608513161db7c2Luke Smith return ret;
14017741ef334485c65013d129608513161db7c2Luke Smith }
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith};
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith/**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithCompiles a selector string into a filter function to identify whether
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithNodes along the parent axis of an event's target should trigger event
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithnotification.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithThis function is memoized, so previously compiled filter functions are
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithreturned if the same selector string is provided.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithThis function may be useful when defining synthetic events for delegate
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithhandling.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithHosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@method compileFilter
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param selector {String} the selector string to base the filtration on
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@return {Function}
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@since 3.2.0
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@static
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith**/
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithdelegate.compileFilter = Y.cached(function (selector) {
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith return function (target, e) {
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith return selectorTest(target._node, selector,
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith (e.currentTarget === e.target) ? null : e.currentTarget._node);
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith };
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith});
a5f89af11a2a0cd671fb5d37a001dfc929eba3b1Todd Kloots
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith/**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithWalks up the parent axis of an event's target, and tests each element
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithagainst a supplied filter function. If any Nodes, including the container,
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smithsatisfy the filter, the delegated callback will be triggered for each.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke SmithHosted as a protected property of the `delegate` method (e.g.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith`Y.delegate._applyFilter`).
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@method _applyFilter
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param filter {Function} boolean function to test for inclusion in event
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith notification
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param args {Array} the arguments that would be passed to subscribers
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@param ce {CustomEvent} the DOM event wrapper
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith@protected
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith**/
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smithdelegate._applyFilter = function (filter, args, ce) {
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith var e = args[0],
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith container = ce.el, // facadeless events in IE, have no e.currentTarget
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith target = e.target || e.srcElement,
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith match = [],
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith isContainer = false;
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // Resolve text nodes to their containing element
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith if (target.nodeType === 3) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith target = target.parentNode;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith }
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // passing target as the first arg rather than leaving well enough alone
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // making 'this' in the filter function refer to the target. This is to
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // support bound filter functions.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith args.unshift(target);
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith if (isString(filter)) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith while (target) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith isContainer = (target === container);
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith if (selectorTest(target, filter, (isContainer ? null: container))) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith match.push(target);
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith }
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith if (isContainer) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith break;
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith }
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith target = target.parentNode;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith }
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith } else {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // filter functions are implementer code and should receive wrappers
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith args[0] = Y.one(target);
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith args[1] = new Y.DOMEventFacade(e, container, ce);
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith while (target) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // filter(target, e, extra args...) - this === target
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith if (filter.apply(args[0], args)) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith match.push(target);
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith }
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith if (target === container) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith break;
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith }
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith target = target.parentNode;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith args[0] = Y.one(target);
14017741ef334485c65013d129608513161db7c2Luke Smith }
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith args[1] = e; // restore the raw DOM event
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith }
14017741ef334485c65013d129608513161db7c2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith if (match.length <= 1) {
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith match = match[0]; // single match or undefined
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith }
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // remove the target
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith args.shift();
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith return match;
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith};
aadc0b0e666b9b335884a2437510798ae8949343Adam Moore
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots/**
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * Sets up event delegation on a container element. The delegated event
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * will use a supplied filter to test if the callback should be executed.
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * This filter can be either a selector string or a function that returns
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * a Node to use as the currentTarget for the event.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots *
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * The event object for the delegated event is supplied to the callback
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * function. It is modified slightly in order to support all properties
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * that may be needed for event delegation. 'currentTarget' is set to
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * the element that matched the selector string filter or the Node returned
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * from the filter function. 'container' is set to the element that the
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * listener is delegated from (this normally would be the 'currentTarget').
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith *
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * Filter functions will be called with the arguments that would be passed to
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * the callback function, including the event object as the first parameter.
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * The function should return false (or a falsey value) if the success criteria
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * aren't met, and the Node to use as the event's currentTarget and 'this'
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * object if they are.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots *
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @method delegate
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param type {string} the event type to delegate
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param fn {function} the callback function to execute. This function
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * will be provided the event object for the delegated event.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param el {string|node} the element that is the delegation container
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @param filter {string|function} a selector that must match the target of the
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * event or a function that returns a Node or false.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param context optional argument that specifies what 'this' refers to.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param args* 0..n additional arguments to pass on to the callback function.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * These arguments will be added after the event object.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @return {EventHandle} the detach handle
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @for YUI
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots */
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke SmithY.delegate = Y.Event.delegate = delegate;