react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / browser / eventPlugins / ResponderEventPlugin.js
81155 views/**1* Copyright 2013-2014, 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 ResponderEventPlugin9*/1011"use strict";1213var EventConstants = require('EventConstants');14var EventPluginUtils = require('EventPluginUtils');15var EventPropagators = require('EventPropagators');16var SyntheticEvent = require('SyntheticEvent');1718var accumulateInto = require('accumulateInto');19var keyOf = require('keyOf');2021var isStartish = EventPluginUtils.isStartish;22var isMoveish = EventPluginUtils.isMoveish;23var isEndish = EventPluginUtils.isEndish;24var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;25var hasDispatches = EventPluginUtils.hasDispatches;26var executeDispatchesInOrderStopAtTrue =27EventPluginUtils.executeDispatchesInOrderStopAtTrue;2829/**30* ID of element that should respond to touch/move types of interactions, as31* indicated explicitly by relevant callbacks.32*/33var responderID = null;34var isPressing = false;3536var eventTypes = {37/**38* On a `touchStart`/`mouseDown`, is it desired that this element become the39* responder?40*/41startShouldSetResponder: {42phasedRegistrationNames: {43bubbled: keyOf({onStartShouldSetResponder: null}),44captured: keyOf({onStartShouldSetResponderCapture: null})45}46},4748/**49* On a `scroll`, is it desired that this element become the responder? This50* is usually not needed, but should be used to retroactively infer that a51* `touchStart` had occured during momentum scroll. During a momentum scroll,52* a touch start will be immediately followed by a scroll event if the view is53* currently scrolling.54*/55scrollShouldSetResponder: {56phasedRegistrationNames: {57bubbled: keyOf({onScrollShouldSetResponder: null}),58captured: keyOf({onScrollShouldSetResponderCapture: null})59}60},6162/**63* On a `touchMove`/`mouseMove`, is it desired that this element become the64* responder?65*/66moveShouldSetResponder: {67phasedRegistrationNames: {68bubbled: keyOf({onMoveShouldSetResponder: null}),69captured: keyOf({onMoveShouldSetResponderCapture: null})70}71},7273/**74* Direct responder events dispatched directly to responder. Do not bubble.75*/76responderMove: {registrationName: keyOf({onResponderMove: null})},77responderRelease: {registrationName: keyOf({onResponderRelease: null})},78responderTerminationRequest: {79registrationName: keyOf({onResponderTerminationRequest: null})80},81responderGrant: {registrationName: keyOf({onResponderGrant: null})},82responderReject: {registrationName: keyOf({onResponderReject: null})},83responderTerminate: {registrationName: keyOf({onResponderTerminate: null})}84};8586/**87* Performs negotiation between any existing/current responder, checks to see if88* any new entity is interested in becoming responder, performs that handshake89* and returns any events that must be emitted to notify the relevant parties.90*91* A note about event ordering in the `EventPluginHub`.92*93* Suppose plugins are injected in the following order:94*95* `[R, S, C]`96*97* To help illustrate the example, assume `S` is `SimpleEventPlugin` (for98* `onClick` etc) and `R` is `ResponderEventPlugin`.99*100* "Deferred-Dispatched Events":101*102* - The current event plugin system will traverse the list of injected plugins,103* in order, and extract events by collecting the plugin's return value of104* `extractEvents()`.105* - These events that are returned from `extractEvents` are "deferred106* dispatched events".107* - When returned from `extractEvents`, deferred-dispatched events contain an108* "accumulation" of deferred dispatches.109* - These deferred dispatches are accumulated/collected before they are110* returned, but processed at a later time by the `EventPluginHub` (hence the111* name deferred).112*113* In the process of returning their deferred-dispatched events, event plugins114* themselves can dispatch events on-demand without returning them from115* `extractEvents`. Plugins might want to do this, so that they can use event116* dispatching as a tool that helps them decide which events should be extracted117* in the first place.118*119* "On-Demand-Dispatched Events":120*121* - On-demand-dispatched events are not returned from `extractEvents`.122* - On-demand-dispatched events are dispatched during the process of returning123* the deferred-dispatched events.124* - They should not have side effects.125* - They should be avoided, and/or eventually be replaced with another126* abstraction that allows event plugins to perform multiple "rounds" of event127* extraction.128*129* Therefore, the sequence of event dispatches becomes:130*131* - `R`s on-demand events (if any) (dispatched by `R` on-demand)132* - `S`s on-demand events (if any) (dispatched by `S` on-demand)133* - `C`s on-demand events (if any) (dispatched by `C` on-demand)134* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)135* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)136* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)137*138* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`139* on-demand dispatch returns `true` (and some other details are satisfied) the140* `onResponderGrant` deferred dispatched event is returned from141* `extractEvents`. The sequence of dispatch executions in this case142* will appear as follows:143*144* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)145* - `touchStartCapture` (`EventPluginHub` dispatches as usual)146* - `touchStart` (`EventPluginHub` dispatches as usual)147* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)148*149* @param {string} topLevelType Record from `EventConstants`.150* @param {string} topLevelTargetID ID of deepest React rendered element.151* @param {object} nativeEvent Native browser event.152* @return {*} An accumulation of synthetic events.153*/154function setResponderAndExtractTransfer(155topLevelType,156topLevelTargetID,157nativeEvent) {158var shouldSetEventType =159isStartish(topLevelType) ? eventTypes.startShouldSetResponder :160isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :161eventTypes.scrollShouldSetResponder;162163var bubbleShouldSetFrom = responderID || topLevelTargetID;164var shouldSetEvent = SyntheticEvent.getPooled(165shouldSetEventType,166bubbleShouldSetFrom,167nativeEvent168);169EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);170var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);171if (!shouldSetEvent.isPersistent()) {172shouldSetEvent.constructor.release(shouldSetEvent);173}174175if (!wantsResponderID || wantsResponderID === responderID) {176return null;177}178var extracted;179var grantEvent = SyntheticEvent.getPooled(180eventTypes.responderGrant,181wantsResponderID,182nativeEvent183);184185EventPropagators.accumulateDirectDispatches(grantEvent);186if (responderID) {187var terminationRequestEvent = SyntheticEvent.getPooled(188eventTypes.responderTerminationRequest,189responderID,190nativeEvent191);192EventPropagators.accumulateDirectDispatches(terminationRequestEvent);193var shouldSwitch = !hasDispatches(terminationRequestEvent) ||194executeDirectDispatch(terminationRequestEvent);195if (!terminationRequestEvent.isPersistent()) {196terminationRequestEvent.constructor.release(terminationRequestEvent);197}198199if (shouldSwitch) {200var terminateType = eventTypes.responderTerminate;201var terminateEvent = SyntheticEvent.getPooled(202terminateType,203responderID,204nativeEvent205);206EventPropagators.accumulateDirectDispatches(terminateEvent);207extracted = accumulateInto(extracted, [grantEvent, terminateEvent]);208responderID = wantsResponderID;209} else {210var rejectEvent = SyntheticEvent.getPooled(211eventTypes.responderReject,212wantsResponderID,213nativeEvent214);215EventPropagators.accumulateDirectDispatches(rejectEvent);216extracted = accumulateInto(extracted, rejectEvent);217}218} else {219extracted = accumulateInto(extracted, grantEvent);220responderID = wantsResponderID;221}222return extracted;223}224225/**226* A transfer is a negotiation between a currently set responder and the next227* element to claim responder status. Any start event could trigger a transfer228* of responderID. Any move event could trigger a transfer, so long as there is229* currently a responder set (in other words as long as the user is pressing230* down).231*232* @param {string} topLevelType Record from `EventConstants`.233* @return {boolean} True if a transfer of responder could possibly occur.234*/235function canTriggerTransfer(topLevelType) {236return topLevelType === EventConstants.topLevelTypes.topScroll ||237isStartish(topLevelType) ||238(isPressing && isMoveish(topLevelType));239}240241/**242* Event plugin for formalizing the negotiation between claiming locks on243* receiving touches.244*/245var ResponderEventPlugin = {246247getResponderID: function() {248return responderID;249},250251eventTypes: eventTypes,252253/**254* @param {string} topLevelType Record from `EventConstants`.255* @param {DOMEventTarget} topLevelTarget The listening component root node.256* @param {string} topLevelTargetID ID of `topLevelTarget`.257* @param {object} nativeEvent Native browser event.258* @return {*} An accumulation of synthetic events.259* @see {EventPluginHub.extractEvents}260*/261extractEvents: function(262topLevelType,263topLevelTarget,264topLevelTargetID,265nativeEvent) {266var extracted;267// Must have missed an end event - reset the state here.268if (responderID && isStartish(topLevelType)) {269responderID = null;270}271if (isStartish(topLevelType)) {272isPressing = true;273} else if (isEndish(topLevelType)) {274isPressing = false;275}276if (canTriggerTransfer(topLevelType)) {277var transfer = setResponderAndExtractTransfer(278topLevelType,279topLevelTargetID,280nativeEvent281);282if (transfer) {283extracted = accumulateInto(extracted, transfer);284}285}286// Now that we know the responder is set correctly, we can dispatch287// responder type events (directly to the responder).288var type = isMoveish(topLevelType) ? eventTypes.responderMove :289isEndish(topLevelType) ? eventTypes.responderRelease :290isStartish(topLevelType) ? eventTypes.responderStart : null;291if (type) {292var gesture = SyntheticEvent.getPooled(293type,294responderID || '',295nativeEvent296);297EventPropagators.accumulateDirectDispatches(gesture);298extracted = accumulateInto(extracted, gesture);299}300if (type === eventTypes.responderRelease) {301responderID = null;302}303return extracted;304}305306};307308module.exports = ResponderEventPlugin;309310311