@qooxdoo/framework
Version:
The JS Framework for Coders
572 lines (483 loc) • 19.8 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2007-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Fabian Jakobs (fjakobs)
* Sebastian Werner (wpbasti)
************************************************************************ */
/**
* Wrapper for browser generic event handling.
*
* Supported events differ from target to target. Generally the handlers
* in {@link qx.event.handler} defines the available features.
*
* @require(qx.event.Manager)
* @require(qx.dom.Node)
* @require(qx.lang.Function)
*/
qx.Class.define("qx.event.Registration",
{
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/**
* Static list of all instantiated event managers. The key is the qooxdoo
* hash value of the corresponding window
*/
__managers : {},
/**
* Get an instance of the event manager, which can handle events for the
* given target.
*
* @param target {Object} Any valid event target
* @return {qx.event.Manager} The event manger for the target.
*/
getManager : function(target)
{
if (target == null)
{
if (qx.core.Environment.get("qx.debug"))
{
qx.log.Logger.error("qx.event.Registration.getManager(null) was called!");
qx.log.Logger.trace(this);
}
target = window;
}
else if (target.nodeType)
{
target = qx.dom.Node.getWindow(target);
}
else if (!qx.dom.Node.isWindow(target))
{
target = window;
}
var hash = target.$$hash || qx.core.ObjectRegistry.toHashCode(target);
var manager = this.__managers[hash];
if (!manager)
{
manager = new qx.event.Manager(target, this);
this.__managers[hash] = manager;
}
return manager;
},
/**
* Removes a manager for a specific window from the list.
*
* Normally only used when the manager gets disposed through
* an unload event of the attached window.
*
* @param mgr {qx.event.Manager} The manager to remove
*/
removeManager : function(mgr)
{
var id = mgr.getWindowId();
delete this.__managers[id];
},
/**
* Add an event listener to a DOM target. The event listener is passed an
* instance of {@link qx.event.type.Event} containing all relevant information
* about the event as parameter.
*
* @param target {Object} Any valid event target
* @param type {String} Name of the event e.g. "click", "keydown", ...
* @param listener {Function} Event listener function
* @param self {Object ? null} Reference to the 'this' variable inside
* the event listener. When not given, the corresponding dispatcher
* usually falls back to a default, which is the target
* by convention. Note this is not a strict requirement, i.e.
* custom dispatchers can follow a different strategy.
* @param capture {Boolean} Whether to attach the event to the
* capturing phase or the bubbling phase of the event. The default is
* to attach the event handler to the bubbling phase.
* @return {var} An opaque id, which can be used to remove the event listener
* using the {@link #removeListenerById} method.
*/
addListener : function(target, type, listener, self, capture) {
return this.getManager(target).addListener(target, type, listener, self, capture);
},
/**
* Remove an event listener from an event target.
*
* Note: All registered event listeners will automatically at page unload
* so it is not necessary to detach events in the destructor.
*
* @param target {Object} The event target
* @param type {String} Name of the event
* @param listener {Function} The pointer to the event listener
* @param self {Object ? null} Reference to the 'this' variable inside
* the event listener.
* @param capture {Boolean} Whether to remove the event listener of
* the bubbling or of the capturing phase.
* @return {Boolean} Whether the event was removed. Return <code>false</code> if
* the event was already removed before.
*/
removeListener : function(target, type, listener, self, capture) {
return this.getManager(target).removeListener(target, type, listener, self, capture);
},
/**
* Removes an event listener from an event target by an id returned by
* {@link #addListener}
*
* @param target {Object} The event target
* @param id {var} The id returned by {@link #addListener}
* @return {Boolean} Whether the event was removed. Return <code>false</code> if
* the event was already removed before.
*/
removeListenerById : function(target, id) {
return this.getManager(target).removeListenerById(target, id);
},
/**
* Remove all event listeners, which are attached to the given event target.
*
* @param target {Object} The event target to remove all event listeners from.
* @return {Boolean} Whether the events were existant and were removed successfully.
*/
removeAllListeners : function(target) {
return this.getManager(target).removeAllListeners(target);
},
/**
* Internal helper for deleting the listeners map used during shutdown.
*
* @param target {Object} The event target to delete the internal map for
* all event listeners.
*
* @internal
*/
deleteAllListeners : function(target) {
var targetKey = target.$$hash;
if (targetKey) {
this.getManager(target).deleteAllListeners(targetKey);
}
},
/**
* Check whether there are one or more listeners for an event type
* registered at the target.
*
* @param target {Object} Any valid event target
* @param type {String} The event type
* @param capture {Boolean ? false} Whether to check for listeners of
* the bubbling or of the capturing phase.
* @return {Boolean} Whether the target has event listeners of the given type.
*/
hasListener : function(target, type, capture) {
return this.getManager(target).hasListener(target, type, capture);
},
/**
* Returns a serialized array of all events attached on the given target.
*
* @param target {Object} Any valid event target
* @return {Map[]} Array of maps where everyone contains the keys:
* <code>handler</code>, <code>self</code>, <code>type</code> and <code>capture</code>.
*/
serializeListeners : function(target) {
return this.getManager(target).serializeListeners(target);
},
/**
* Get an event instance of the given class, which can be dispatched using
* an event manager. The created events must be initialized using
* {@link qx.event.type.Event#init}.
*
* @param type {String} The type of the event to create
* @param clazz {Object?qx.event.type.Event} The event class to use
* @param args {Array?null} Array which will be passed to
* the event's init method.
* @return {qx.event.type.Event} An instance of the given class.
*/
createEvent : function(type, clazz, args)
{
if (qx.core.Environment.get("qx.debug"))
{
if (arguments.length > 1 && clazz === undefined) {
throw new Error("Create event of type " + type + " with undefined class. Please use null to explicit fallback to default event type!");
}
}
// Fallback to default
if (clazz == null) {
clazz = qx.event.type.Event;
}
var obj = qx.event.Pool.getInstance().getObject(clazz);
// Initialize with given arguments
args ? obj.init.apply(obj, args) : obj.init();
// Setup the type
// Note: Native event may setup this later or using init() above
// using the native information.
if (type) {
obj.setType(type);
}
return obj;
},
/**
* Dispatch an event object on the given target.
*
* It is normally better to use {@link #fireEvent} because it uses
* the event pooling and is quite handy otherwise as well. After dispatching
* the event object will be pooled for later reuse or disposed.
*
* @param target {Object} Any valid event target
* @param event {qx.event.type.Event} The event object to dispatch. The event
* object must be obtained using {@link #createEvent} and initialized
* using {@link qx.event.type.Event#init}.
* @return {Boolean|qx.Promise} whether the event default was prevented or not.
* Returns true, when the event was NOT prevented.
*/
dispatchEvent : function(target, event) {
return this.getManager(target).dispatchEvent(target, event);
},
/**
* Create an event object and dispatch it on the given target.
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {Event} the event
* @see #createEvent
*/
__fireEvent : function(target, type, clazz, args)
{
if (qx.core.Environment.get("qx.debug"))
{
if (arguments.length > 2 && clazz === undefined && args !== undefined) {
throw new Error("Create event of type " + type + " with undefined class. Please use null to explicit fallback to default event type!");
}
var msg = "Could not fire event '" + type + "' on target '" + (target ? target.classname : "undefined") +"': ";
qx.core.Assert.assertNotUndefined(target, msg + "Invalid event target.");
qx.core.Assert.assertNotNull(target, msg + "Invalid event target.");
}
var evt = this.createEvent(type, clazz||null, args);
this.getManager(target).dispatchEvent(target, evt);
return evt;
},
/**
* Create an event object and dispatch it on the given target.
*
* Note about Promises in v6.0: this method has changed to return either a boolean (true if the
* event was prevented) or a promise which will evaluate to the same thing; this is
* because events are now asynchronous and preventDefault is inherently synchronous.
* However, although this changing in the return type is conspicuous it does not necessarily
* introduce a backwards compatibility issue because the "truthy" nature of the return
* is preserved. Code which needs to take care of asynchronous issues will need to change,
* but that was necessary anyway, and it is rare to use the return value of this method (only
* one class in Qooxdoo used it).
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {Boolean|qx.Promise} whether the event default was prevented or not.
* Returns true, when the event was NOT prevented.
* @see #createEvent
*/
fireEvent : function(target, type, clazz, args)
{
if (qx.core.Environment.get("qx.debug"))
{
if (arguments.length > 2 && clazz === undefined && args !== undefined) {
throw new Error("Create event of type " + type + " with undefined class. Please use null to explicit fallback to default event type!");
}
var msg = "Could not fire event '" + type + "' on target '" + (target ? target.classname : "undefined") +"': ";
qx.core.Assert.assertNotUndefined(target, msg + "Invalid event target.");
qx.core.Assert.assertNotNull(target, msg + "Invalid event target.");
}
var evt = this.createEvent(type, clazz||null, args);
var tracker = {};
var self = this;
qx.event.Utils.then(tracker, function() {
return self.getManager(target).dispatchEvent(target, evt);
});
return qx.event.Utils.then(tracker, function() {
return !evt.getDefaultPrevented();
});
},
/**
* Create an event object and dispatch it on the given target; equivalent to fireEvent, except that it
* always returns a promise
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {qx.Promise} a promise aggregated from the event handlers;
* if the default was prevented, the promise is rejected
* @see #createEvent
*/
fireEventAsync : function(target, type, clazz, args)
{
if (qx.core.Environment.get("qx.promise")) {
return qx.Promise.resolve(this.fireEvent(target, type, clazz, args));
} else {
throw new Error(this.classname + ".fireNonBubblingEventAsync not supported because qx.promise==false");
}
},
/**
* Create an event object and dispatch it on the given target.
* The event dispatched with this method does never bubble! Use only if you
* are sure that bubbling is not required.
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {Event} the event
* @see #createEvent
*/
__fireNonBubblingEvent : function(target, type, clazz, args)
{
if (qx.core.Environment.get("qx.debug"))
{
if (arguments.length > 2 && clazz === undefined && args !== undefined) {
throw new Error("Create event of type " + type + " with undefined class. Please use null to explicit fallback to default event type!");
}
}
var mgr = this.getManager(target);
if (!mgr.hasListener(target, type, false)) {
return null;
}
var evt = this.createEvent(type, clazz||null, args);
mgr.dispatchEvent(target, evt);
return evt;
},
/**
* Create an event object and dispatch it on the given target.
* The event dispatched with this method does never bubble! Use only if you
* are sure that bubbling is not required.
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {Boolean} whether the event default was prevented or not.
* Returns true, when the event was NOT prevented.
* @see #createEvent
*/
fireNonBubblingEvent : function(target, type, clazz, args)
{
var evt = this.__fireNonBubblingEvent.apply(this, arguments);
if (evt === null) {
return true;
}
return !evt.getDefaultPrevented();
},
/**
* Create an event object and dispatch it on the given target.
* The event dispatched with this method does never bubble! Use only if you
* are sure that bubbling is not required.
*
* @param target {Object} Any valid event target
* @param type {String} Event type to fire
* @param clazz {Class?qx.event.type.Event} The event class
* @param args {Array?null} Arguments, which will be passed to
* the event's init method.
* @return {qx.Promise} a promise aggregated from the event handlers;
* if the default was prevented, the promise is rejected
* @see #createEvent
*/
fireNonBubblingEventAsync : qx.core.Environment.select("qx.promise", {
"true": function(target, type, clazz, args) {
var evt = this.__fireNonBubblingEvent.apply(this, arguments);
if (evt === null) {
return qx.Promise.resolve(true);
}
return evt.promise();
},
"false": function() {
throw new Error(this.classname + ".fireNonBubblingEventAsync not supported because qx.promise==false");
}
}),
/*
---------------------------------------------------------------------------
EVENT HANDLER/DISPATCHER PRIORITY
---------------------------------------------------------------------------
*/
/** @type {Integer} Highest priority. Used by handlers and dispatchers. */
PRIORITY_FIRST : -32000,
/** @type {Integer} Default priority. Used by handlers and dispatchers. */
PRIORITY_NORMAL : 0,
/** @type {Integer} Lowest priority. Used by handlers and dispatchers. */
PRIORITY_LAST : 32000,
/*
---------------------------------------------------------------------------
EVENT HANDLER REGISTRATION
---------------------------------------------------------------------------
*/
/** @type {Array} Contains all known event handlers */
__handlers : [],
/**
* Register an event handler.
*
* @param handler {qx.event.IEventHandler} Event handler to add
* @throws {Error} if the handler does not have the IEventHandler interface.
*/
addHandler : function(handler)
{
if (qx.core.Environment.get("qx.debug")) {
qx.core.Assert.assertInterface(handler, qx.event.IEventHandler, "Invalid event handler.");
}
// Append to list
this.__handlers.push(handler);
// Re-sort list
this.__handlers.sort(function(a, b) {
return a.PRIORITY - b.PRIORITY;
});
},
/**
* Get a list of registered event handlers.
*
* @return {qx.event.IEventHandler[]} registered event handlers
*/
getHandlers : function() {
return this.__handlers;
},
/*
---------------------------------------------------------------------------
EVENT DISPATCHER REGISTRATION
---------------------------------------------------------------------------
*/
/** @type {Array} Contains all known event dispatchers */
__dispatchers : [],
/**
* Register an event dispatcher.
*
* @param dispatcher {qx.event.IEventDispatcher} Event dispatcher to add
* @param priority {Integer} One of
* {@link qx.event.Registration#PRIORITY_FIRST},
* {@link qx.event.Registration#PRIORITY_NORMAL}
* or {@link qx.event.Registration#PRIORITY_LAST}.
* @throws {Error} if the dispatcher does not have the IEventHandler interface.
*/
addDispatcher : function(dispatcher, priority)
{
if (qx.core.Environment.get("qx.debug")) {
qx.core.Assert.assertInterface(dispatcher, qx.event.IEventDispatcher, "Invalid event dispatcher!");
}
// Append to list
this.__dispatchers.push(dispatcher);
// Re-sort list
this.__dispatchers.sort(function(a, b) {
return a.PRIORITY - b.PRIORITY;
});
},
/**
* Get a list of registered event dispatchers.
*
* @return {qx.event.IEventDispatcher[]} all registered event dispatcher
*/
getDispatchers : function() {
return this.__dispatchers;
}
}
});