366 lines
8.8 KiB
JavaScript
Executable File
366 lines
8.8 KiB
JavaScript
Executable File
/*
|
|
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
|
|
Code licensed under the BSD License:
|
|
http://developer.yahoo.net/yui/license.txt
|
|
version: 3.0.0
|
|
build: 1549
|
|
*/
|
|
YUI.add('event-delegate', function(Y) {
|
|
|
|
/**
|
|
* Adds event delegation support to the library.
|
|
*
|
|
* @module event
|
|
* @submodule event-delegate
|
|
*/
|
|
|
|
var Event = Y.Event,
|
|
Lang = Y.Lang,
|
|
|
|
delegates = {},
|
|
|
|
specialTypes = {
|
|
mouseenter: "mouseover",
|
|
mouseleave: "mouseout"
|
|
},
|
|
|
|
resolveTextNode = function(n) {
|
|
try {
|
|
if (n && 3 == n.nodeType) {
|
|
return n.parentNode;
|
|
}
|
|
} catch(e) { }
|
|
return n;
|
|
},
|
|
|
|
delegateHandler = function(delegateKey, e, el) {
|
|
|
|
var target = resolveTextNode((e.target || e.srcElement)),
|
|
tests = delegates[delegateKey],
|
|
spec,
|
|
ename,
|
|
matched,
|
|
fn,
|
|
ev;
|
|
|
|
|
|
var getMatch = function(el, selector, container) {
|
|
|
|
var returnVal;
|
|
|
|
if (!el || el === container) {
|
|
returnVal = false;
|
|
}
|
|
else {
|
|
returnVal = Y.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
|
|
}
|
|
|
|
return returnVal;
|
|
|
|
};
|
|
|
|
|
|
for (spec in tests) {
|
|
|
|
if (tests.hasOwnProperty(spec)) {
|
|
|
|
ename = tests[spec];
|
|
fn = tests.fn;
|
|
matched = null;
|
|
|
|
|
|
if (Y.Selector.test(target, spec, el)) {
|
|
matched = target;
|
|
}
|
|
else if (Y.Selector.test(target, ((spec.replace(/,/gi, " *,")) + " *"), el)) {
|
|
|
|
// The target is a descendant of an element matching
|
|
// the selector, so crawl up to find the ancestor that
|
|
// matches the selector
|
|
|
|
matched = getMatch(target, spec, el);
|
|
|
|
}
|
|
|
|
|
|
if (matched) {
|
|
|
|
if (!ev) {
|
|
ev = new Y.DOMEventFacade(e, el);
|
|
ev.container = ev.currentTarget;
|
|
}
|
|
|
|
ev.currentTarget = Y.Node.get(matched);
|
|
|
|
Y.publish(ename, {
|
|
contextFn: function() {
|
|
return ev.currentTarget;
|
|
}
|
|
});
|
|
|
|
if (fn) {
|
|
fn(ev, ename);
|
|
}
|
|
else {
|
|
Y.fire(ename, ev);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
attach = function (type, key, element) {
|
|
|
|
var focusMethods = {
|
|
focus: Event._attachFocus,
|
|
blur: Event._attachBlur
|
|
},
|
|
|
|
attachFn = focusMethods[type],
|
|
|
|
args = [type,
|
|
function (e) {
|
|
delegateHandler(key, (e || window.event), element);
|
|
},
|
|
element];
|
|
|
|
|
|
if (attachFn) {
|
|
return attachFn(args, { capture: true, facade: false });
|
|
}
|
|
else {
|
|
return Event._attach(args, { facade: false });
|
|
}
|
|
|
|
},
|
|
|
|
sanitize = Y.cached(function(str) {
|
|
return str.replace(/[|,:]/g, '~');
|
|
});
|
|
|
|
/**
|
|
* Sets up event delegation on a container element. The delegated event
|
|
* will use a supplied selector to test if the target or one of the
|
|
* descendants of the target match it. The supplied callback function
|
|
* will only be executed if a match was encountered, and, in fact,
|
|
* will be executed for each element that matches if you supply an
|
|
* ambiguous selector.
|
|
*
|
|
* The event object for the delegated event is supplied to the callback
|
|
* function. It is modified slightly in order to support all properties
|
|
* that may be needed for event delegation. 'currentTarget' is set to
|
|
* the element that matched the delegation specifcation. 'container' is
|
|
* set to the element that the listener is bound to (this normally would
|
|
* be the 'currentTarget').
|
|
*
|
|
* @event delegate
|
|
* @param type {string} 'delegate'
|
|
* @param fn {function} the callback function to execute. This function
|
|
* will be provided the event object for the delegated event.
|
|
* @param el {string|node} the element that is the delegation container
|
|
* @param delegateType {string} the event type to delegate
|
|
* @param spec {string} a selector that must match the target of the
|
|
* event.
|
|
* @param context optional argument that specifies what 'this' refers to.
|
|
* @param args* 0..n additional arguments to pass on to the callback function.
|
|
* These arguments will be added after the event object.
|
|
* @return {EventHandle} the detach handle
|
|
* @for YUI
|
|
* @deprecated use Y.delegate
|
|
*/
|
|
Y.Env.evt.plugins.delegate = {
|
|
|
|
on: function(type, fn, el, delegateType, spec) {
|
|
|
|
|
|
var args = Y.Array(arguments, 0, true);
|
|
|
|
args.splice(3, 1);
|
|
|
|
args[0] = delegateType;
|
|
|
|
return Y.delegate.apply(Y, args);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets up event delegation on a container element. The delegated event
|
|
* will use a supplied selector to test if the target or one of the
|
|
* descendants of the target match it. The supplied callback function
|
|
* will only be executed if a match was encountered, and, in fact,
|
|
* will be executed for each element that matches if you supply an
|
|
* ambiguous selector.
|
|
*
|
|
* The event object for the delegated event is supplied to the callback
|
|
* function. It is modified slightly in order to support all properties
|
|
* that may be needed for event delegation. 'currentTarget' is set to
|
|
* the element that matched the delegation specifcation. 'container' is
|
|
* set to the element that the listener is bound to (this normally would
|
|
* be the 'currentTarget').
|
|
*
|
|
* @method delegate
|
|
* @param type {string} the event type to delegate
|
|
* @param fn {function} the callback function to execute. This function
|
|
* will be provided the event object for the delegated event.
|
|
* @param el {string|node} the element that is the delegation container
|
|
* @param spec {string} a selector that must match the target of the
|
|
* event.
|
|
* @param context optional argument that specifies what 'this' refers to.
|
|
* @param args* 0..n additional arguments to pass on to the callback function.
|
|
* These arguments will be added after the event object.
|
|
* @return {EventHandle} the detach handle
|
|
* @for YUI
|
|
*/
|
|
Event.delegate = function (type, fn, el, spec) {
|
|
|
|
if (!spec) {
|
|
return false;
|
|
}
|
|
|
|
|
|
var args = Y.Array(arguments, 0, true),
|
|
element = el, // HTML element serving as the delegation container
|
|
availHandle;
|
|
|
|
|
|
if (Lang.isString(el)) {
|
|
|
|
// Y.Selector.query returns an array of matches unless specified
|
|
// to return just the first match. Since the primary use case for
|
|
// event delegation is to use a single event handler on a container,
|
|
// Y.delegate doesn't currently support being able to bind a
|
|
// single listener to multiple containers.
|
|
|
|
element = Y.Selector.query(el, null, true);
|
|
|
|
if (!element) { // Not found, check using onAvailable
|
|
|
|
availHandle = Event.onAvailable(el, function() {
|
|
|
|
availHandle.handle = Event.delegate.apply(Event, args);
|
|
|
|
}, Event, true, false);
|
|
|
|
return availHandle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
element = Y.Node.getDOMNode(element);
|
|
|
|
|
|
var guid = Y.stamp(element),
|
|
|
|
// The Custom Event for the delegation spec
|
|
ename = 'delegate:' + guid + type + sanitize(spec),
|
|
|
|
// The key to the listener for the event type and container
|
|
delegateKey = type + guid,
|
|
|
|
delegate = delegates[delegateKey],
|
|
|
|
domEventHandle,
|
|
|
|
ceHandle,
|
|
|
|
listeners;
|
|
|
|
|
|
if (!delegate) {
|
|
|
|
delegate = {};
|
|
|
|
if (specialTypes[type]) {
|
|
|
|
if (!Event._fireMouseEnter) {
|
|
return false;
|
|
}
|
|
|
|
type = specialTypes[type];
|
|
delegate.fn = Event._fireMouseEnter;
|
|
|
|
}
|
|
|
|
// Create the DOM Event wrapper that will fire the Custom Event
|
|
|
|
domEventHandle = attach(type, delegateKey, element);
|
|
|
|
|
|
// Hook into the _delete method for the Custom Event wrapper of this
|
|
// DOM Event in order to clean up the 'delegates' map and unsubscribe
|
|
// the associated Custom Event listeners fired by this DOM event
|
|
// listener if/when the user calls "purgeElement" OR removes all
|
|
// listeners of the Custom Event.
|
|
|
|
Y.after(function (sub) {
|
|
|
|
if (domEventHandle.sub == sub) {
|
|
|
|
// Delete this event from the map of known delegates
|
|
delete delegates[delegateKey];
|
|
|
|
|
|
// Unsubscribe all listeners of the Custom Event fired
|
|
// by this DOM event.
|
|
Y.detachAll(ename);
|
|
|
|
}
|
|
|
|
}, domEventHandle.evt, "_delete");
|
|
|
|
delegate.handle = domEventHandle;
|
|
|
|
delegates[delegateKey] = delegate;
|
|
|
|
}
|
|
|
|
|
|
listeners = delegate.listeners;
|
|
|
|
delegate.listeners = listeners ? (listeners + 1) : 1;
|
|
delegate[spec] = ename;
|
|
|
|
|
|
args[0] = ename;
|
|
|
|
// Remove element, delegation spec
|
|
args.splice(2, 2);
|
|
|
|
|
|
// Subscribe to the Custom Event for the delegation spec
|
|
|
|
ceHandle = Y.on.apply(Y, args);
|
|
|
|
|
|
// Hook into the detach method of the handle in order to clean up the
|
|
// 'delegates' map and remove the associated DOM event handler
|
|
// responsible for firing this Custom Event if all listener for this
|
|
// event have been removed.
|
|
|
|
Y.after(function () {
|
|
|
|
delegate.listeners = (delegate.listeners - 1);
|
|
|
|
if (delegate.listeners === 0) {
|
|
delegate.handle.detach();
|
|
}
|
|
|
|
}, ceHandle, "detach");
|
|
|
|
return ceHandle;
|
|
|
|
};
|
|
|
|
Y.delegate = Event.delegate;
|
|
|
|
|
|
}, '3.0.0' ,{requires:['node-base']});
|