/* 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 */ /* * DOM event listener abstraction layer * @module event * @submodule event-base */ (function() { // Unlike most of the library, this code has to be executed as soon as it is // introduced into the page -- and it should only be executed one time // regardless of the number of instances that use it. var GLOBAL_ENV = YUI.Env, C = YUI.config, D = C.doc, POLL_INTERVAL = C.pollInterval || 40, _ready = function(e) { GLOBAL_ENV._ready(); }; if (!GLOBAL_ENV._ready) { GLOBAL_ENV._ready = function() { if (!GLOBAL_ENV.DOMReady) { GLOBAL_ENV.DOMReady=true; // Remove the DOMContentLoaded (FF/Opera/Safari) if (D.removeEventListener) { D.removeEventListener("DOMContentLoaded", _ready, false); } } }; // create custom event /*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */ // Internet Explorer: use the readyState of a defered script. // This isolates what appears to be a safe moment to manipulate // the DOM prior to when the document's readyState suggests // it is safe to do so. if (navigator.userAgent.match(/MSIE/)) { if (self !== self.top) { document.onreadystatechange = function() { if (document.readyState == 'complete') { document.onreadystatechange = null; _ready(); } }; } else { GLOBAL_ENV._dri = setInterval(function() { try { // throws an error if doc is not ready document.documentElement.doScroll('left'); clearInterval(GLOBAL_ENV._dri); GLOBAL_ENV._dri = null; _ready(); } catch (ex) { } }, POLL_INTERVAL); } // FireFox, Opera, Safari 3+: These browsers provide a event for this // moment. } else { D.addEventListener("DOMContentLoaded", _ready, false); } ///////////////////////////////////////////////////////////// } })(); YUI.add('event-base', function(Y) { (function() { /* * DOM event listener abstraction layer * @module event * @submodule event-base */ var GLOBAL_ENV = YUI.Env, yready = function() { Y.fire('domready'); }; Y.publish('domready', { fireOnce: true }); if (GLOBAL_ENV.DOMReady) { // console.log('DOMReady already fired', 'info', 'event'); yready(); } else { // console.log('setting up before listener', 'info', 'event'); // console.log('env: ' + YUI.Env.windowLoaded, 'info', 'event'); Y.before(yready, GLOBAL_ENV, "_ready"); } })(); (function() { /** * Custom event engine, DOM event listener abstraction layer, synthetic DOM * events. * @module event * @submodule event-base */ /** * Wraps a DOM event, properties requiring browser abstraction are * fixed here. Provids a security layer when required. * @class DOMEventFacade * @param ev {Event} the DOM event * @param currentTarget {HTMLElement} the element the listener was attached to * @param wrapper {Event.Custom} the custom event wrapper for this DOM event */ /* * @TODO constants? LEFTBUTTON, MIDDLEBUTTON, RIGHTBUTTON, keys */ /* var whitelist = { altKey : 1, // "button" : 1, // we supply // "bubbles" : 1, // needed? // "cancelable" : 1, // needed? // "charCode" : 1, // we supply cancelBubble : 1, // "currentTarget" : 1, // we supply ctrlKey : 1, clientX : 1, // needed? clientY : 1, // needed? detail : 1, // not fully implemented // "fromElement" : 1, keyCode : 1, // "height" : 1, // needed? // "initEvent" : 1, // need the init events? // "initMouseEvent" : 1, // "initUIEvent" : 1, // "layerX" : 1, // needed? // "layerY" : 1, // needed? metaKey : 1, // "modifiers" : 1, // needed? // "offsetX" : 1, // needed? // "offsetY" : 1, // needed? // "preventDefault" : 1, // we supply // "reason" : 1, // IE proprietary // "relatedTarget" : 1, // "returnValue" : 1, // needed? shiftKey : 1, // "srcUrn" : 1, // IE proprietary // "srcElement" : 1, // "srcFilter" : 1, IE proprietary // "stopPropagation" : 1, // we supply // "target" : 1, // "timeStamp" : 1, // needed? // "toElement" : 1, type : 1, // "view" : 1, // "which" : 1, // we supply // "width" : 1, // needed? x : 1, y : 1 }, */ var ua = Y.UA, /** * webkit key remapping required for Safari < 3.1 * @property webkitKeymap * @private */ webkitKeymap = { 63232: 38, // up 63233: 40, // down 63234: 37, // left 63235: 39, // right 63276: 33, // page up 63277: 34, // page down 25: 9, // SHIFT-TAB (Safari provides a different key code in // this case, even though the shiftKey modifier is set) 63272: 46, // delete 63273: 36, // home 63275: 35 // end }, /** * Returns a wrapped node. Intended to be used on event targets, * so it will return the node's parent if the target is a text * node. * * If accessing a property of the node throws an error, this is * probably the anonymous div wrapper Gecko adds inside text * nodes. This likely will only occur when attempting to access * the relatedTarget. In this case, we now return null because * the anonymous div is completely useless and we do not know * what the related target was because we can't even get to * the element's parent node. * * @method resolve * @private */ resolve = function(n) { try { if (n && 3 == n.nodeType) { n = n.parentNode; } } catch(e) { return null; } return Y.one(n); }; // provide a single event with browser abstractions resolved // // include all properties for both browers? // include only DOM2 spec properties? // provide browser-specific facade? Y.DOMEventFacade = function(ev, currentTarget, wrapper) { wrapper = wrapper || {}; var e = ev, ot = currentTarget, d = Y.config.doc, b = d.body, x = e.pageX, y = e.pageY, c, t; this.altKey = e.altKey; this.ctrlKey = e.ctrlKey; this.metaKey = e.metaKey; this.shiftKey = e.shiftKey; this.type = e.type; this.clientX = e.clientX; this.clientY = e.clientY; ////////////////////////////////////////////////////// if (!x && 0 !== x) { x = e.clientX || 0; y = e.clientY || 0; if (ua.ie) { x += Math.max(d.documentElement.scrollLeft, b.scrollLeft); y += Math.max(d.documentElement.scrollTop, b.scrollTop); } } this._yuifacade = true; /** * The native event * @property _event */ this._event = e; /** * The X location of the event on the page (including scroll) * @property pageX * @type int */ this.pageX = x; /** * The Y location of the event on the page (including scroll) * @property pageY * @type int */ this.pageY = y; ////////////////////////////////////////////////////// c = e.keyCode || e.charCode || 0; if (ua.webkit && (c in webkitKeymap)) { c = webkitKeymap[c]; } /** * The keyCode for key events. Uses charCode if keyCode is not available * @property keyCode * @type int */ this.keyCode = c; /** * The charCode for key events. Same as keyCode * @property charCode * @type int */ this.charCode = c; ////////////////////////////////////////////////////// /** * The button that was pushed. * @property button * @type int */ this.button = e.which || e.button; /** * The button that was pushed. Same as button. * @property which * @type int */ this.which = this.button; ////////////////////////////////////////////////////// /** * Node reference for the targeted element * @propery target * @type Node */ this.target = resolve(e.target || e.srcElement); /** * Node reference for the element that the listener was attached to. * @propery currentTarget * @type Node */ this.currentTarget = resolve(ot); t = e.relatedTarget; if (!t) { if (e.type == "mouseout") { t = e.toElement; } else if (e.type == "mouseover") { t = e.fromElement; } } /** * Node reference to the relatedTarget * @propery relatedTarget * @type Node */ this.relatedTarget = resolve(t); /** * Number representing the direction and velocity of the movement of the mousewheel. * Negative is down, the higher the number, the faster. Applies to the mousewheel event. * @property wheelDelta * @type int */ if (e.type == "mousewheel" || e.type == "DOMMouseScroll") { this.wheelDelta = (e.detail) ? (e.detail * -1) : Math.round(e.wheelDelta / 80) || ((e.wheelDelta < 0) ? -1 : 1); } ////////////////////////////////////////////////////// // methods /** * Stops the propagation to the next bubble target * @method stopPropagation */ this.stopPropagation = function() { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } wrapper.stopped = 1; }; /** * Stops the propagation to the next bubble target and * prevents any additional listeners from being exectued * on the current target. * @method stopImmediatePropagation */ this.stopImmediatePropagation = function() { if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); } else { this.stopPropagation(); } wrapper.stopped = 2; }; /** * Prevents the event's default behavior * @method preventDefault * @param returnValue {string} sets the returnValue of the event to this value * (rather than the default false value). This can be used to add a customized * confirmation query to the beforeunload event). */ this.preventDefault = function(returnValue) { if (e.preventDefault) { e.preventDefault(); } e.returnValue = returnValue || false; wrapper.prevented = 1; }; /** * Stops the event propagation and prevents the default * event behavior. * @method halt * @param immediate {boolean} if true additional listeners * on the current target will not be executed */ this.halt = function(immediate) { if (immediate) { this.stopImmediatePropagation(); } else { this.stopPropagation(); } this.preventDefault(); }; }; })(); (function() { /** * DOM event listener abstraction layer * @module event * @submodule event-base */ /** * The event utility provides functions to add and remove event listeners, * event cleansing. It also tries to automatically remove listeners it * registers during the unload event. * * @class Event * @static */ Y.Env.evt.dom_wrappers = {}; Y.Env.evt.dom_map = {}; var _eventenv = Y.Env.evt, add = YUI.Env.add, remove = YUI.Env.remove, onLoad = function() { YUI.Env.windowLoaded = true; Y.Event._load(); remove(window, "load", onLoad); }, onUnload = function() { Y.Event._unload(); remove(window, "unload", onUnload); }, EVENT_READY = 'domready', COMPAT_ARG = '~yui|2|compat~', shouldIterate = function(o) { try { return (o && typeof o !== "string" && Y.Lang.isNumber(o.length) && !o.tagName && !o.alert); } catch(ex) { return false; } }, Event = function() { /** * True after the onload event has fired * @property _loadComplete * @type boolean * @static * @private */ var _loadComplete = false, /** * The number of times to poll after window.onload. This number is * increased if additional late-bound handlers are requested after * the page load. * @property _retryCount * @static * @private */ _retryCount = 0, /** * onAvailable listeners * @property _avail * @static * @private */ _avail = [], /** * Custom event wrappers for DOM events. Key is * 'event:' + Element uid stamp + event type * @property _wrappers * @type Y.Event.Custom * @static * @private */ _wrappers = _eventenv.dom_wrappers, _windowLoadKey = null, /** * Custom event wrapper map DOM events. Key is * Element uid stamp. Each item is a hash of custom event * wrappers as provided in the _wrappers collection. This * provides the infrastructure for getListeners. * @property _el_events * @static * @private */ _el_events = _eventenv.dom_map; return { /** * The number of times we should look for elements that are not * in the DOM at the time the event is requested after the document * has been loaded. The default is 1000@amp;40 ms, so it will poll * for 40 seconds or until all outstanding handlers are bound * (whichever comes first). * @property POLL_RETRYS * @type int * @static * @final */ POLL_RETRYS: 1000, /** * The poll interval in milliseconds * @property POLL_INTERVAL * @type int * @static * @final */ POLL_INTERVAL: 40, /** * addListener/removeListener can throw errors in unexpected scenarios. * These errors are suppressed, the method returns false, and this property * is set * @property lastError * @static * @type Error */ lastError: null, /** * poll handle * @property _interval * @static * @private */ _interval: null, /** * document readystate poll handle * @property _dri * @static * @private */ _dri: null, /** * True when the document is initially usable * @property DOMReady * @type boolean * @static */ DOMReady: false, /** * @method startInterval * @static * @private */ startInterval: function() { var E = Y.Event; if (!E._interval) { E._interval = setInterval(Y.bind(E._poll, E), E.POLL_INTERVAL); } }, /** * Executes the supplied callback when the item with the supplied * id is found. This is meant to be used to execute behavior as * soon as possible as the page loads. If you use this after the * initial page load it will poll for a fixed time for the element. * The number of times it will poll and the frequency are * configurable. By default it will poll for 10 seconds. * *

The callback is executed with a single parameter: * the custom object parameter, if provided.

* * @method onAvailable * * @param {string||string[]} id the id of the element, or an array * of ids to look for. * @param {function} fn what to execute when the element is found. * @param {object} p_obj an optional object to be passed back as * a parameter to fn. * @param {boolean|object} p_override If set to true, fn will execute * in the context of p_obj, if set to an object it * will execute in the context of that object * @param checkContent {boolean} check child node readiness (onContentReady) * @static * @deprecated Use Y.on("available") */ // @TODO fix arguments onAvailable: function(id, fn, p_obj, p_override, checkContent, compat) { var a = Y.Array(id), i, availHandle; for (i=0; iThe callback is executed with a single parameter: * the custom object parameter, if provided.

* * @method onContentReady * * @param {string} id the id of the element to look for. * @param {function} fn what to execute when the element is ready. * @param {object} p_obj an optional object to be passed back as * a parameter to fn. * @param {boolean|object} p_override If set to true, fn will execute * in the context of p_obj. If an object, fn will * exectute in the context of that object * * @static * @deprecated Use Y.on("contentready") */ // @TODO fix arguments onContentReady: function(id, fn, p_obj, p_override, compat) { return this.onAvailable(id, fn, p_obj, p_override, true, compat); }, /** * Adds an event listener * * @method attach * * @param {String} type The type of event to append * @param {Function} fn The method the event invokes * @param {String|HTMLElement|Array|NodeList} el An id, an element * reference, or a collection of ids and/or elements to assign the * listener to. * @param {Object} context optional context object * @param {Boolean|object} args 0..n arguments to pass to the callback * @return {EventHandle} an object to that can be used to detach the listener * * @static */ attach: function(type, fn, el, context) { return Y.Event._attach(Y.Array(arguments, 0, true)); }, _createWrapper: function (el, type, capture, compat, facade) { var ek = Y.stamp(el), key = 'event:' + ek + type, cewrapper; if (false === facade) { key += 'native'; } if (capture) { key += 'capture'; } cewrapper = _wrappers[key]; if (!cewrapper) { // create CE wrapper cewrapper = Y.publish(key, { silent: true, bubbles: false, contextFn: function() { cewrapper.nodeRef = cewrapper.nodeRef || Y.one(cewrapper.el); return cewrapper.nodeRef; } }); // for later removeListener calls cewrapper.el = el; cewrapper.key = key; cewrapper.domkey = ek; cewrapper.type = type; cewrapper.fn = function(e) { cewrapper.fire(Y.Event.getEvent(e, el, (compat || (false === facade)))); }; cewrapper.capture = capture; if (el == Y.config.win && type == "load") { // window load happens once cewrapper.fireOnce = true; _windowLoadKey = key; } _wrappers[key] = cewrapper; _el_events[ek] = _el_events[ek] || {}; _el_events[ek][key] = cewrapper; add(el, type, cewrapper.fn, capture); } return cewrapper; }, _attach: function(args, config) { var compat, E=Y.Event, handles, oEl, cewrapper, context, fireNow = false, ret, type = args[0], fn = args[1], el = args[2] || Y.config.win, facade = config && config.facade, capture = config && config.capture; if (args[args.length-1] === COMPAT_ARG) { compat = true; // trimmedArgs.pop(); } if (!fn || !fn.call) { // throw new TypeError(type + " attach call failed, callback undefined"); return false; } // The el argument can be an array of elements or element ids. if (shouldIterate(el)) { handles=[]; Y.each(el, function(v, k) { args[2] = v; handles.push(E._attach(args, config)); }); // return (handles.length === 1) ? handles[0] : handles; return new Y.EventHandle(handles); // If the el argument is a string, we assume it is // actually the id of the element. If the page is loaded // we convert el to the actual element, otherwise we // defer attaching the event until the element is // ready } else if (Y.Lang.isString(el)) { // oEl = (compat) ? Y.DOM.byId(el) : Y.Selector.query(el); if (compat) { oEl = Y.DOM.byId(el); } else { oEl = Y.Selector.query(el); switch (oEl.length) { case 0: oEl = null; break; case 1: oEl = oEl[0]; break; default: args[2] = oEl; return E._attach(args, config); } } if (oEl) { el = oEl; // Not found = defer adding the event until the element is available } else { ret = this.onAvailable(el, function() { ret.handle = E._attach(args, config); }, E, true, false, compat); return ret; } } // Element should be an html element or node if (!el) { return false; } if (Y.Node && el instanceof Y.Node) { el = Y.Node.getDOMNode(el); } cewrapper = this._createWrapper(el, type, capture, compat, facade); if (el == Y.config.win && type == "load") { // if the load is complete, fire immediately. // all subscribers, including the current one // will be notified. if (YUI.Env.windowLoaded) { fireNow = true; } } if (compat) { args.pop(); } context = args[3]; // set context to the Node if not specified // ret = cewrapper.on.apply(cewrapper, trimmedArgs); ret = cewrapper._on(fn, context, (args.length > 4) ? args.slice(4) : null); if (fireNow) { cewrapper.fire(); } return ret; }, /** * Removes an event listener. Supports the signature the event was bound * with, but the preferred way to remove listeners is using the handle * that is returned when using Y.on * * @method detach * * @param {String} type the type of event to remove. * @param {Function} fn the method the event invokes. If fn is * undefined, then all event handlers for the type of event are * removed. * @param {String|HTMLElement|Array|NodeList|EventHandle} el An * event handle, an id, an element reference, or a collection * of ids and/or elements to remove the listener from. * @return {boolean} true if the unbind was successful, false otherwise. * @static */ detach: function(type, fn, el, obj) { var args=Y.Array(arguments, 0, true), compat, i, l, ok, id, ce; if (args[args.length-1] === COMPAT_ARG) { compat = true; // args.pop(); } if (type && type.detach) { return type.detach(); } // The el argument can be a string if (typeof el == "string") { // el = (compat) ? Y.DOM.byId(el) : Y.all(el); if (compat) { el = Y.DOM.byId(el); } else { el = Y.Selector.query(el); l = el.length; if (l < 1) { el = null; } else if (l == 1) { el = el[0]; } } // return Y.Event.detach.apply(Y.Event, args); // The el argument can be an array of elements or element ids. } if (!el) { return false; } if (shouldIterate(el)) { ok = true; for (i=0, l=el.length; i 0); } // onAvailable notAvail = []; executeItem = function (el, item) { var context, ov = item.override; if (item.compat) { if (item.override) { if (ov === true) { context = item.obj; } else { context = ov; } } else { context = el; } item.fn.call(context, item.obj); } else { context = item.obj || Y.one(el); item.fn.apply(context, (Y.Lang.isArray(ov)) ? ov : []); } }; // onAvailable for (i=0,len=_avail.length; i 4 ? Y.Array(arguments, 4, true) : []; return Y.Event.onAvailable.call(Y.Event, id, fn, o, a); } }; /** * Executes the callback as soon as the specified element * is detected in the DOM with a nextSibling property * (indicating that the element's children are available) * @event contentready * @param type {string} 'contentready' * @param fn {function} the callback function to execute. * @param el {string|HTMLElement|collection} the element(s) to attach * @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 */ Y.Env.evt.plugins.contentready = { on: function(type, fn, id, o) { var a = arguments.length > 4 ? Y.Array(arguments, 4, true) : []; return Y.Event.onContentReady.call(Y.Event, id, fn, o, a); } }; }, '3.0.0' ,{requires:['event-custom-base']});