You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
8.0 KiB
275 lines
8.0 KiB
10 years ago
|
/**
|
||
|
* Copyright 2013-2015, Facebook, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This source code is licensed under the BSD-style license found in the
|
||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
*
|
||
|
* @providesModule EventPluginHub
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var EventPluginRegistry = require("./EventPluginRegistry");
|
||
|
var EventPluginUtils = require("./EventPluginUtils");
|
||
|
|
||
|
var accumulateInto = require("./accumulateInto");
|
||
|
var forEachAccumulated = require("./forEachAccumulated");
|
||
|
var invariant = require("./invariant");
|
||
|
|
||
|
/**
|
||
|
* Internal store for event listeners
|
||
|
*/
|
||
|
var listenerBank = {};
|
||
|
|
||
|
/**
|
||
|
* Internal queue of events that have accumulated their dispatches and are
|
||
|
* waiting to have their dispatches executed.
|
||
|
*/
|
||
|
var eventQueue = null;
|
||
|
|
||
|
/**
|
||
|
* Dispatches an event and releases it back into the pool, unless persistent.
|
||
|
*
|
||
|
* @param {?object} event Synthetic event to be dispatched.
|
||
|
* @private
|
||
|
*/
|
||
|
var executeDispatchesAndRelease = function(event) {
|
||
|
if (event) {
|
||
|
var executeDispatch = EventPluginUtils.executeDispatch;
|
||
|
// Plugins can provide custom behavior when dispatching events.
|
||
|
var PluginModule = EventPluginRegistry.getPluginModuleForEvent(event);
|
||
|
if (PluginModule && PluginModule.executeDispatch) {
|
||
|
executeDispatch = PluginModule.executeDispatch;
|
||
|
}
|
||
|
EventPluginUtils.executeDispatchesInOrder(event, executeDispatch);
|
||
|
|
||
|
if (!event.isPersistent()) {
|
||
|
event.constructor.release(event);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* - `InstanceHandle`: [required] Module that performs logical traversals of DOM
|
||
|
* hierarchy given ids of the logical DOM elements involved.
|
||
|
*/
|
||
|
var InstanceHandle = null;
|
||
|
|
||
|
function validateInstanceHandle() {
|
||
|
var valid =
|
||
|
InstanceHandle &&
|
||
|
InstanceHandle.traverseTwoPhase &&
|
||
|
InstanceHandle.traverseEnterLeave;
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
valid,
|
||
|
'InstanceHandle not injected before use!'
|
||
|
) : invariant(valid));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is a unified interface for event plugins to be installed and configured.
|
||
|
*
|
||
|
* Event plugins can implement the following properties:
|
||
|
*
|
||
|
* `extractEvents` {function(string, DOMEventTarget, string, object): *}
|
||
|
* Required. When a top-level event is fired, this method is expected to
|
||
|
* extract synthetic events that will in turn be queued and dispatched.
|
||
|
*
|
||
|
* `eventTypes` {object}
|
||
|
* Optional, plugins that fire events must publish a mapping of registration
|
||
|
* names that are used to register listeners. Values of this mapping must
|
||
|
* be objects that contain `registrationName` or `phasedRegistrationNames`.
|
||
|
*
|
||
|
* `executeDispatch` {function(object, function, string)}
|
||
|
* Optional, allows plugins to override how an event gets dispatched. By
|
||
|
* default, the listener is simply invoked.
|
||
|
*
|
||
|
* Each plugin that is injected into `EventsPluginHub` is immediately operable.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
var EventPluginHub = {
|
||
|
|
||
|
/**
|
||
|
* Methods for injecting dependencies.
|
||
|
*/
|
||
|
injection: {
|
||
|
|
||
|
/**
|
||
|
* @param {object} InjectedMount
|
||
|
* @public
|
||
|
*/
|
||
|
injectMount: EventPluginUtils.injection.injectMount,
|
||
|
|
||
|
/**
|
||
|
* @param {object} InjectedInstanceHandle
|
||
|
* @public
|
||
|
*/
|
||
|
injectInstanceHandle: function(InjectedInstanceHandle) {
|
||
|
InstanceHandle = InjectedInstanceHandle;
|
||
|
if ("production" !== process.env.NODE_ENV) {
|
||
|
validateInstanceHandle();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getInstanceHandle: function() {
|
||
|
if ("production" !== process.env.NODE_ENV) {
|
||
|
validateInstanceHandle();
|
||
|
}
|
||
|
return InstanceHandle;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @param {array} InjectedEventPluginOrder
|
||
|
* @public
|
||
|
*/
|
||
|
injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
|
||
|
|
||
|
/**
|
||
|
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
|
||
|
*/
|
||
|
injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName
|
||
|
|
||
|
},
|
||
|
|
||
|
eventNameDispatchConfigs: EventPluginRegistry.eventNameDispatchConfigs,
|
||
|
|
||
|
registrationNameModules: EventPluginRegistry.registrationNameModules,
|
||
|
|
||
|
/**
|
||
|
* Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.
|
||
|
*
|
||
|
* @param {string} id ID of the DOM element.
|
||
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||
|
* @param {?function} listener The callback to store.
|
||
|
*/
|
||
|
putListener: function(id, registrationName, listener) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
!listener || typeof listener === 'function',
|
||
|
'Expected %s listener to be a function, instead got type %s',
|
||
|
registrationName, typeof listener
|
||
|
) : invariant(!listener || typeof listener === 'function'));
|
||
|
|
||
|
var bankForRegistrationName =
|
||
|
listenerBank[registrationName] || (listenerBank[registrationName] = {});
|
||
|
bankForRegistrationName[id] = listener;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @param {string} id ID of the DOM element.
|
||
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||
|
* @return {?function} The stored callback.
|
||
|
*/
|
||
|
getListener: function(id, registrationName) {
|
||
|
var bankForRegistrationName = listenerBank[registrationName];
|
||
|
return bankForRegistrationName && bankForRegistrationName[id];
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Deletes a listener from the registration bank.
|
||
|
*
|
||
|
* @param {string} id ID of the DOM element.
|
||
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||
|
*/
|
||
|
deleteListener: function(id, registrationName) {
|
||
|
var bankForRegistrationName = listenerBank[registrationName];
|
||
|
if (bankForRegistrationName) {
|
||
|
delete bankForRegistrationName[id];
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Deletes all listeners for the DOM element with the supplied ID.
|
||
|
*
|
||
|
* @param {string} id ID of the DOM element.
|
||
|
*/
|
||
|
deleteAllListeners: function(id) {
|
||
|
for (var registrationName in listenerBank) {
|
||
|
delete listenerBank[registrationName][id];
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Allows registered plugins an opportunity to extract events from top-level
|
||
|
* native browser events.
|
||
|
*
|
||
|
* @param {string} topLevelType Record from `EventConstants`.
|
||
|
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||
|
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||
|
* @param {object} nativeEvent Native browser event.
|
||
|
* @return {*} An accumulation of synthetic events.
|
||
|
* @internal
|
||
|
*/
|
||
|
extractEvents: function(
|
||
|
topLevelType,
|
||
|
topLevelTarget,
|
||
|
topLevelTargetID,
|
||
|
nativeEvent) {
|
||
|
var events;
|
||
|
var plugins = EventPluginRegistry.plugins;
|
||
|
for (var i = 0, l = plugins.length; i < l; i++) {
|
||
|
// Not every plugin in the ordering may be loaded at runtime.
|
||
|
var possiblePlugin = plugins[i];
|
||
|
if (possiblePlugin) {
|
||
|
var extractedEvents = possiblePlugin.extractEvents(
|
||
|
topLevelType,
|
||
|
topLevelTarget,
|
||
|
topLevelTargetID,
|
||
|
nativeEvent
|
||
|
);
|
||
|
if (extractedEvents) {
|
||
|
events = accumulateInto(events, extractedEvents);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return events;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Enqueues a synthetic event that should be dispatched when
|
||
|
* `processEventQueue` is invoked.
|
||
|
*
|
||
|
* @param {*} events An accumulation of synthetic events.
|
||
|
* @internal
|
||
|
*/
|
||
|
enqueueEvents: function(events) {
|
||
|
if (events) {
|
||
|
eventQueue = accumulateInto(eventQueue, events);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Dispatches all synthetic events on the event queue.
|
||
|
*
|
||
|
* @internal
|
||
|
*/
|
||
|
processEventQueue: function() {
|
||
|
// Set `eventQueue` to null before processing it so that we can tell if more
|
||
|
// events get enqueued while processing.
|
||
|
var processingEventQueue = eventQueue;
|
||
|
eventQueue = null;
|
||
|
forEachAccumulated(processingEventQueue, executeDispatchesAndRelease);
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
!eventQueue,
|
||
|
'processEventQueue(): Additional events were enqueued while processing ' +
|
||
|
'an event queue. Support for this has not yet been implemented.'
|
||
|
) : invariant(!eventQueue));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* These are needed for tests only. Do not use!
|
||
|
*/
|
||
|
__purge: function() {
|
||
|
listenerBank = {};
|
||
|
},
|
||
|
|
||
|
__getListenerBank: function() {
|
||
|
return listenerBank;
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
module.exports = EventPluginHub;
|