/**1* Copyright 2013-2015, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule EventPluginHub9*/1011'use strict';1213var EventPluginRegistry = require("./EventPluginRegistry");14var EventPluginUtils = require("./EventPluginUtils");1516var accumulateInto = require("./accumulateInto");17var forEachAccumulated = require("./forEachAccumulated");18var invariant = require("./invariant");1920/**21* Internal store for event listeners22*/23var listenerBank = {};2425/**26* Internal queue of events that have accumulated their dispatches and are27* waiting to have their dispatches executed.28*/29var eventQueue = null;3031/**32* Dispatches an event and releases it back into the pool, unless persistent.33*34* @param {?object} event Synthetic event to be dispatched.35* @private36*/37var executeDispatchesAndRelease = function(event) {38if (event) {39var executeDispatch = EventPluginUtils.executeDispatch;40// Plugins can provide custom behavior when dispatching events.41var PluginModule = EventPluginRegistry.getPluginModuleForEvent(event);42if (PluginModule && PluginModule.executeDispatch) {43executeDispatch = PluginModule.executeDispatch;44}45EventPluginUtils.executeDispatchesInOrder(event, executeDispatch);4647if (!event.isPersistent()) {48event.constructor.release(event);49}50}51};5253/**54* - `InstanceHandle`: [required] Module that performs logical traversals of DOM55* hierarchy given ids of the logical DOM elements involved.56*/57var InstanceHandle = null;5859function validateInstanceHandle() {60var valid =61InstanceHandle &&62InstanceHandle.traverseTwoPhase &&63InstanceHandle.traverseEnterLeave;64("production" !== process.env.NODE_ENV ? invariant(65valid,66'InstanceHandle not injected before use!'67) : invariant(valid));68}6970/**71* This is a unified interface for event plugins to be installed and configured.72*73* Event plugins can implement the following properties:74*75* `extractEvents` {function(string, DOMEventTarget, string, object): *}76* Required. When a top-level event is fired, this method is expected to77* extract synthetic events that will in turn be queued and dispatched.78*79* `eventTypes` {object}80* Optional, plugins that fire events must publish a mapping of registration81* names that are used to register listeners. Values of this mapping must82* be objects that contain `registrationName` or `phasedRegistrationNames`.83*84* `executeDispatch` {function(object, function, string)}85* Optional, allows plugins to override how an event gets dispatched. By86* default, the listener is simply invoked.87*88* Each plugin that is injected into `EventsPluginHub` is immediately operable.89*90* @public91*/92var EventPluginHub = {9394/**95* Methods for injecting dependencies.96*/97injection: {9899/**100* @param {object} InjectedMount101* @public102*/103injectMount: EventPluginUtils.injection.injectMount,104105/**106* @param {object} InjectedInstanceHandle107* @public108*/109injectInstanceHandle: function(InjectedInstanceHandle) {110InstanceHandle = InjectedInstanceHandle;111if ("production" !== process.env.NODE_ENV) {112validateInstanceHandle();113}114},115116getInstanceHandle: function() {117if ("production" !== process.env.NODE_ENV) {118validateInstanceHandle();119}120return InstanceHandle;121},122123/**124* @param {array} InjectedEventPluginOrder125* @public126*/127injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,128129/**130* @param {object} injectedNamesToPlugins Map from names to plugin modules.131*/132injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName133134},135136eventNameDispatchConfigs: EventPluginRegistry.eventNameDispatchConfigs,137138registrationNameModules: EventPluginRegistry.registrationNameModules,139140/**141* Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.142*143* @param {string} id ID of the DOM element.144* @param {string} registrationName Name of listener (e.g. `onClick`).145* @param {?function} listener The callback to store.146*/147putListener: function(id, registrationName, listener) {148("production" !== process.env.NODE_ENV ? invariant(149!listener || typeof listener === 'function',150'Expected %s listener to be a function, instead got type %s',151registrationName, typeof listener152) : invariant(!listener || typeof listener === 'function'));153154var bankForRegistrationName =155listenerBank[registrationName] || (listenerBank[registrationName] = {});156bankForRegistrationName[id] = listener;157},158159/**160* @param {string} id ID of the DOM element.161* @param {string} registrationName Name of listener (e.g. `onClick`).162* @return {?function} The stored callback.163*/164getListener: function(id, registrationName) {165var bankForRegistrationName = listenerBank[registrationName];166return bankForRegistrationName && bankForRegistrationName[id];167},168169/**170* Deletes a listener from the registration bank.171*172* @param {string} id ID of the DOM element.173* @param {string} registrationName Name of listener (e.g. `onClick`).174*/175deleteListener: function(id, registrationName) {176var bankForRegistrationName = listenerBank[registrationName];177if (bankForRegistrationName) {178delete bankForRegistrationName[id];179}180},181182/**183* Deletes all listeners for the DOM element with the supplied ID.184*185* @param {string} id ID of the DOM element.186*/187deleteAllListeners: function(id) {188for (var registrationName in listenerBank) {189delete listenerBank[registrationName][id];190}191},192193/**194* Allows registered plugins an opportunity to extract events from top-level195* native browser events.196*197* @param {string} topLevelType Record from `EventConstants`.198* @param {DOMEventTarget} topLevelTarget The listening component root node.199* @param {string} topLevelTargetID ID of `topLevelTarget`.200* @param {object} nativeEvent Native browser event.201* @return {*} An accumulation of synthetic events.202* @internal203*/204extractEvents: function(205topLevelType,206topLevelTarget,207topLevelTargetID,208nativeEvent) {209var events;210var plugins = EventPluginRegistry.plugins;211for (var i = 0, l = plugins.length; i < l; i++) {212// Not every plugin in the ordering may be loaded at runtime.213var possiblePlugin = plugins[i];214if (possiblePlugin) {215var extractedEvents = possiblePlugin.extractEvents(216topLevelType,217topLevelTarget,218topLevelTargetID,219nativeEvent220);221if (extractedEvents) {222events = accumulateInto(events, extractedEvents);223}224}225}226return events;227},228229/**230* Enqueues a synthetic event that should be dispatched when231* `processEventQueue` is invoked.232*233* @param {*} events An accumulation of synthetic events.234* @internal235*/236enqueueEvents: function(events) {237if (events) {238eventQueue = accumulateInto(eventQueue, events);239}240},241242/**243* Dispatches all synthetic events on the event queue.244*245* @internal246*/247processEventQueue: function() {248// Set `eventQueue` to null before processing it so that we can tell if more249// events get enqueued while processing.250var processingEventQueue = eventQueue;251eventQueue = null;252forEachAccumulated(processingEventQueue, executeDispatchesAndRelease);253("production" !== process.env.NODE_ENV ? invariant(254!eventQueue,255'processEventQueue(): Additional events were enqueued while processing ' +256'an event queue. Support for this has not yet been implemented.'257) : invariant(!eventQueue));258},259260/**261* These are needed for tests only. Do not use!262*/263__purge: function() {264listenerBank = {};265},266267__getListenerBank: function() {268return listenerBank;269}270271};272273module.exports = EventPluginHub;274275276