delegate.js revision 0c4bb3413c8172f27fcebdf7242f5798d026064b
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
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithvar toArray = Y.Array,
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith YLang = Y.Lang,
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith isString = YLang.isString,
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith selectorTest = Y.Selector.test;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smithfunction delegate(type, fn, el, filter) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (!type || !fn || !el || !filter) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith Y.log("delegate requires a type, callback function, target, and filter", "warn");
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith var args = toArray(arguments, 0, true),
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith synth = Y.Node.DOM_EVENTS[type],
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith query = isString(el) ? el : null,
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith container, handle;
43c15c82a8f3e7361f944d6daa02f1885c88ebf0Adam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (YLang.isObject(synth) && synth.delegate) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return synth.delegate.apply(synth, arguments);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
edac37f62b5675c375833896993369855211cbfdTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith container = (query) ? Y.Selector.query(query, null, true) : el;
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (!container && isString(el)) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith handle = Y.on('available', function () {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith Y.mix(handle, Y.delegate.apply(Y, args), true);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }, el);
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return handle;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (container) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith args.splice(2, 2, container); // remove the filter
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (isString(filter)) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith filter = Y.delegate.compileFilter(filter);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith handle = Y.on.apply(Y, args);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith handle.sub.getCurrentTarget = filter;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith handle.sub._notify = Y.delegate.notifySub;
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return handle;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
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) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith args = 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) {
14017741ef334485c65013d129608513161db7c2Luke Smith var descendantOfSelector = selector.replace(/,/g, ' *,') + ' *';
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;