delegate.js revision 426d8b6487a18aa68071eecf43ed7bc4f58f36d4
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore/**
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * Adds event delegation support to the library.
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore *
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,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith selectorTest = Y.Selector.test,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith detachCategories = Y.Env.evt.handles;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithfunction delegate(type, fn, el, filter) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith var args = toArray(arguments, 0, true),
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith query = isString(el) ? el : null,
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith typeBits = type.split(/\|/),
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith synth, container, categories, cat, handle;
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (typeBits.length > 1) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith cat = typeBits.shift();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith type = typeBits.shift();
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith synth = Y.Node.DOM_EVENTS[type];
43c15c82a8f3e7361f944d6daa02f1885c88ebf0Adam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (YLang.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
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (isString(filter)) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith filter = Y.delegate.compileFilter(filter);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith handle = Y.on.apply(Y, args);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith handle.sub.getCurrentTarget = filter;
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith handle.sub._notify = Y.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
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
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith var currentTarget = this.getCurrentTarget.apply(this, args),
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith originalEvent = args[0],
14017741ef334485c65013d129608513161db7c2Luke Smith container = originalEvent.currentTarget,
14017741ef334485c65013d129608513161db7c2Luke Smith i, ret, target;
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (currentTarget) {
14017741ef334485c65013d129608513161db7c2Luke Smith // Support multiple matches up the the container subtree
14017741ef334485c65013d129608513161db7c2Luke Smith currentTarget = toArray(currentTarget);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
14017741ef334485c65013d129608513161db7c2Luke Smith for (i = currentTarget.length - 1; i >= 0; --i) {
14017741ef334485c65013d129608513161db7c2Luke Smith target = currentTarget[i];
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
14017741ef334485c65013d129608513161db7c2Luke Smith // New facade to avoid corrupting facade sent to direct subs
14017741ef334485c65013d129608513161db7c2Luke Smith args[0] = new Y.DOMEventFacade(originalEvent, target, ce);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
14017741ef334485c65013d129608513161db7c2Luke Smith args[0].container = container;
14017741ef334485c65013d129608513161db7c2Luke Smith
14017741ef334485c65013d129608513161db7c2Luke Smith thisObj = this.context || target;
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
14017741ef334485c65013d129608513161db7c2Luke Smith ret = this.fn.apply(thisObj, 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
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithdelegate.compileFilter = Y.cached(function (selector) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return function (e) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith var container = e.currentTarget._node,
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith target = e.target._node,
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith match = [];
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith while (target !== container) {
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith if (selectorTest(target, selector, container)) {
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith match.push(Y.one(target));
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith target = target.parentNode;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
a5f89af11a2a0cd671fb5d37a001dfc929eba3b1Todd Kloots
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith if (match.length <= 1) {
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith match = match[0]; // single match or undefined
14017741ef334485c65013d129608513161db7c2Luke Smith }
14017741ef334485c65013d129608513161db7c2Luke Smith
8177882b1cec452d6822b48b9aa69007dcdf3a28Luke Smith return match;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith };
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke 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;