react / react-0.13.3 / examples / basic-commonjs / node_modules / react / lib / ChangeEventPlugin.js
81143 views/**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 ChangeEventPlugin9*/1011'use strict';1213var EventConstants = require("./EventConstants");14var EventPluginHub = require("./EventPluginHub");15var EventPropagators = require("./EventPropagators");16var ExecutionEnvironment = require("./ExecutionEnvironment");17var ReactUpdates = require("./ReactUpdates");18var SyntheticEvent = require("./SyntheticEvent");1920var isEventSupported = require("./isEventSupported");21var isTextInputElement = require("./isTextInputElement");22var keyOf = require("./keyOf");2324var topLevelTypes = EventConstants.topLevelTypes;2526var eventTypes = {27change: {28phasedRegistrationNames: {29bubbled: keyOf({onChange: null}),30captured: keyOf({onChangeCapture: null})31},32dependencies: [33topLevelTypes.topBlur,34topLevelTypes.topChange,35topLevelTypes.topClick,36topLevelTypes.topFocus,37topLevelTypes.topInput,38topLevelTypes.topKeyDown,39topLevelTypes.topKeyUp,40topLevelTypes.topSelectionChange41]42}43};4445/**46* For IE shims47*/48var activeElement = null;49var activeElementID = null;50var activeElementValue = null;51var activeElementValueProp = null;5253/**54* SECTION: handle `change` event55*/56function shouldUseChangeEvent(elem) {57return (58elem.nodeName === 'SELECT' ||59(elem.nodeName === 'INPUT' && elem.type === 'file')60);61}6263var doesChangeEventBubble = false;64if (ExecutionEnvironment.canUseDOM) {65// See `handleChange` comment below66doesChangeEventBubble = isEventSupported('change') && (67(!('documentMode' in document) || document.documentMode > 8)68);69}7071function manualDispatchChangeEvent(nativeEvent) {72var event = SyntheticEvent.getPooled(73eventTypes.change,74activeElementID,75nativeEvent76);77EventPropagators.accumulateTwoPhaseDispatches(event);7879// If change and propertychange bubbled, we'd just bind to it like all the80// other events and have it go through ReactBrowserEventEmitter. Since it81// doesn't, we manually listen for the events and so we have to enqueue and82// process the abstract event manually.83//84// Batching is necessary here in order to ensure that all event handlers run85// before the next rerender (including event handlers attached to ancestor86// elements instead of directly on the input). Without this, controlled87// components don't work properly in conjunction with event bubbling because88// the component is rerendered and the value reverted before all the event89// handlers can run. See https://github.com/facebook/react/issues/708.90ReactUpdates.batchedUpdates(runEventInBatch, event);91}9293function runEventInBatch(event) {94EventPluginHub.enqueueEvents(event);95EventPluginHub.processEventQueue();96}9798function startWatchingForChangeEventIE8(target, targetID) {99activeElement = target;100activeElementID = targetID;101activeElement.attachEvent('onchange', manualDispatchChangeEvent);102}103104function stopWatchingForChangeEventIE8() {105if (!activeElement) {106return;107}108activeElement.detachEvent('onchange', manualDispatchChangeEvent);109activeElement = null;110activeElementID = null;111}112113function getTargetIDForChangeEvent(114topLevelType,115topLevelTarget,116topLevelTargetID) {117if (topLevelType === topLevelTypes.topChange) {118return topLevelTargetID;119}120}121function handleEventsForChangeEventIE8(122topLevelType,123topLevelTarget,124topLevelTargetID) {125if (topLevelType === topLevelTypes.topFocus) {126// stopWatching() should be a noop here but we call it just in case we127// missed a blur event somehow.128stopWatchingForChangeEventIE8();129startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);130} else if (topLevelType === topLevelTypes.topBlur) {131stopWatchingForChangeEventIE8();132}133}134135136/**137* SECTION: handle `input` event138*/139var isInputEventSupported = false;140if (ExecutionEnvironment.canUseDOM) {141// IE9 claims to support the input event but fails to trigger it when142// deleting text, so we ignore its input events143isInputEventSupported = isEventSupported('input') && (144(!('documentMode' in document) || document.documentMode > 9)145);146}147148/**149* (For old IE.) Replacement getter/setter for the `value` property that gets150* set on the active element.151*/152var newValueProp = {153get: function() {154return activeElementValueProp.get.call(this);155},156set: function(val) {157// Cast to a string so we can do equality checks.158activeElementValue = '' + val;159activeElementValueProp.set.call(this, val);160}161};162163/**164* (For old IE.) Starts tracking propertychange events on the passed-in element165* and override the value property so that we can distinguish user events from166* value changes in JS.167*/168function startWatchingForValueChange(target, targetID) {169activeElement = target;170activeElementID = targetID;171activeElementValue = target.value;172activeElementValueProp = Object.getOwnPropertyDescriptor(173target.constructor.prototype,174'value'175);176177Object.defineProperty(activeElement, 'value', newValueProp);178activeElement.attachEvent('onpropertychange', handlePropertyChange);179}180181/**182* (For old IE.) Removes the event listeners from the currently-tracked element,183* if any exists.184*/185function stopWatchingForValueChange() {186if (!activeElement) {187return;188}189190// delete restores the original property definition191delete activeElement.value;192activeElement.detachEvent('onpropertychange', handlePropertyChange);193194activeElement = null;195activeElementID = null;196activeElementValue = null;197activeElementValueProp = null;198}199200/**201* (For old IE.) Handles a propertychange event, sending a `change` event if202* the value of the active element has changed.203*/204function handlePropertyChange(nativeEvent) {205if (nativeEvent.propertyName !== 'value') {206return;207}208var value = nativeEvent.srcElement.value;209if (value === activeElementValue) {210return;211}212activeElementValue = value;213214manualDispatchChangeEvent(nativeEvent);215}216217/**218* If a `change` event should be fired, returns the target's ID.219*/220function getTargetIDForInputEvent(221topLevelType,222topLevelTarget,223topLevelTargetID) {224if (topLevelType === topLevelTypes.topInput) {225// In modern browsers (i.e., not IE8 or IE9), the input event is exactly226// what we want so fall through here and trigger an abstract event227return topLevelTargetID;228}229}230231// For IE8 and IE9.232function handleEventsForInputEventIE(233topLevelType,234topLevelTarget,235topLevelTargetID) {236if (topLevelType === topLevelTypes.topFocus) {237// In IE8, we can capture almost all .value changes by adding a238// propertychange handler and looking for events with propertyName239// equal to 'value'240// In IE9, propertychange fires for most input events but is buggy and241// doesn't fire when text is deleted, but conveniently, selectionchange242// appears to fire in all of the remaining cases so we catch those and243// forward the event if the value has changed244// In either case, we don't want to call the event handler if the value245// is changed from JS so we redefine a setter for `.value` that updates246// our activeElementValue variable, allowing us to ignore those changes247//248// stopWatching() should be a noop here but we call it just in case we249// missed a blur event somehow.250stopWatchingForValueChange();251startWatchingForValueChange(topLevelTarget, topLevelTargetID);252} else if (topLevelType === topLevelTypes.topBlur) {253stopWatchingForValueChange();254}255}256257// For IE8 and IE9.258function getTargetIDForInputEventIE(259topLevelType,260topLevelTarget,261topLevelTargetID) {262if (topLevelType === topLevelTypes.topSelectionChange ||263topLevelType === topLevelTypes.topKeyUp ||264topLevelType === topLevelTypes.topKeyDown) {265// On the selectionchange event, the target is just document which isn't266// helpful for us so just check activeElement instead.267//268// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire269// propertychange on the first input event after setting `value` from a270// script and fires only keydown, keypress, keyup. Catching keyup usually271// gets it and catching keydown lets us fire an event for the first272// keystroke if user does a key repeat (it'll be a little delayed: right273// before the second keystroke). Other input methods (e.g., paste) seem to274// fire selectionchange normally.275if (activeElement && activeElement.value !== activeElementValue) {276activeElementValue = activeElement.value;277return activeElementID;278}279}280}281282283/**284* SECTION: handle `click` event285*/286function shouldUseClickEvent(elem) {287// Use the `click` event to detect changes to checkbox and radio inputs.288// This approach works across all browsers, whereas `change` does not fire289// until `blur` in IE8.290return (291elem.nodeName === 'INPUT' &&292(elem.type === 'checkbox' || elem.type === 'radio')293);294}295296function getTargetIDForClickEvent(297topLevelType,298topLevelTarget,299topLevelTargetID) {300if (topLevelType === topLevelTypes.topClick) {301return topLevelTargetID;302}303}304305/**306* This plugin creates an `onChange` event that normalizes change events307* across form elements. This event fires at a time when it's possible to308* change the element's value without seeing a flicker.309*310* Supported elements are:311* - input (see `isTextInputElement`)312* - textarea313* - select314*/315var ChangeEventPlugin = {316317eventTypes: eventTypes,318319/**320* @param {string} topLevelType Record from `EventConstants`.321* @param {DOMEventTarget} topLevelTarget The listening component root node.322* @param {string} topLevelTargetID ID of `topLevelTarget`.323* @param {object} nativeEvent Native browser event.324* @return {*} An accumulation of synthetic events.325* @see {EventPluginHub.extractEvents}326*/327extractEvents: function(328topLevelType,329topLevelTarget,330topLevelTargetID,331nativeEvent) {332333var getTargetIDFunc, handleEventFunc;334if (shouldUseChangeEvent(topLevelTarget)) {335if (doesChangeEventBubble) {336getTargetIDFunc = getTargetIDForChangeEvent;337} else {338handleEventFunc = handleEventsForChangeEventIE8;339}340} else if (isTextInputElement(topLevelTarget)) {341if (isInputEventSupported) {342getTargetIDFunc = getTargetIDForInputEvent;343} else {344getTargetIDFunc = getTargetIDForInputEventIE;345handleEventFunc = handleEventsForInputEventIE;346}347} else if (shouldUseClickEvent(topLevelTarget)) {348getTargetIDFunc = getTargetIDForClickEvent;349}350351if (getTargetIDFunc) {352var targetID = getTargetIDFunc(353topLevelType,354topLevelTarget,355topLevelTargetID356);357if (targetID) {358var event = SyntheticEvent.getPooled(359eventTypes.change,360targetID,361nativeEvent362);363EventPropagators.accumulateTwoPhaseDispatches(event);364return event;365}366}367368if (handleEventFunc) {369handleEventFunc(370topLevelType,371topLevelTarget,372topLevelTargetID373);374}375}376377};378379module.exports = ChangeEventPlugin;380381382