@samitouri / QOS-React-2 / commits / 32423a83fc
Fork 0

The library for web and native user interfaces.

Injectable DOMProperty configs, and add back ID attribute

https://github.com/facebook/react/pull/141

Pete Hunt committed Jul 2, 2013 at 18:26 UTC 32423a83fc9c9c8dc1eac8f769a581e09a2ba74f
6 files changed +345 -175
src/core/ReactDefaultInjection.js
+6 -7
@@ -22,6 +22,9 @@ var ReactDOM = require('ReactDOM');
22 var ReactDOMForm = require('ReactDOMForm');
23 var ReactDOMTextarea = require('ReactDOMTextarea');
24
25 +var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
26 +var DOMProperty = require('DOMProperty');
27 +
28 var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
29 var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
30 var ChangeEventPlugin = require('ChangeEventPlugin');
@@ -37,7 +40,7 @@ function inject() {
40 EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
41
42 /**
40 - * Two important event plugins included by default (without having to require
43 + * Some important event plugins included by default (without having to require
44 * them).
45 */
46 EventPluginHub.injection.injectEventPluginsByName({
@@ -46,17 +49,13 @@ function inject() {
49 'ChangeEventPlugin': ChangeEventPlugin
50 });
51
49 - /**
50 - * This is a bit of a hack. We need to override the <form> element to be a
51 - * composite component because IE8 does not bubble or capture submit to the
52 - * top level. In order to make this work with our dependency graph we need to
53 - * inject it here.
54 - */
52 ReactDOM.injection.injectComponentClasses({
53 form: ReactDOMForm,
54 // TODO: Inject `ReactDOMInput`.
55 textarea: ReactDOMTextarea
56 });
57 +
58 + DOMProperty.injection.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
59 }
60
61 module.exports = {
src/core/__tests__/ReactNativeComponent-test.js
+4 -1
@@ -193,6 +193,9 @@ describe('ReactNativeComponent', function() {
193 beforeEach(function() {
194 require('mock-modules').dumpCache();
195
196 + var ReactDefaultInjection = require('ReactDefaultInjection');
197 + ReactDefaultInjection.inject();
198 +
199 var mixInto = require('mixInto');
200 var ReactNativeComponent = require('ReactNativeComponent');
201
@@ -217,7 +220,7 @@ describe('ReactNativeComponent', function() {
220 });
221 });
222
220 - it("should handle className", function() {
223 + it("should generate the correct markup with className", function() {
224 expect(genMarkup({ className: 'a' })).toHaveAttribute('class', 'a');
225 expect(genMarkup({ className: 'a b' })).toHaveAttribute('class', 'a b');
226 expect(genMarkup({ className: '' })).toHaveAttribute('class', '');
src/dom/DOMProperty.js
+109 -164
@@ -23,6 +23,101 @@
23
24 var invariant = require('invariant');
25
26 +var DOMPropertyInjection = {
27 + /**
28 + * Mapping from normalized, camelcased property names to a configuration that
29 + * specifies how the associated DOM property should be accessed or rendered.
30 + */
31 + MUST_USE_ATTRIBUTE: 0x1,
32 + MUST_USE_PROPERTY: 0x2,
33 + HAS_BOOLEAN_VALUE: 0x4,
34 + HAS_SIDE_EFFECTS: 0x8,
35 +
36 + /**
37 + * Inject some specialized knowledge about the DOM. This takes a config object
38 + * with the following properties:
39 + *
40 + * isCustomAttribute: function that given an attribute name will return true
41 + * if it can be inserted into the DOM verbatim. Useful for data-* or aria-*
42 + * attributes where it's impossible to enumerate all of the possible
43 + * attribute names,
44 + *
45 + * Properties: object mapping DOM property name to one of the
46 + * DOMPropertyInjection constants or null. If your attribute isn't in here,
47 + * it won't get written to the DOM.
48 + *
49 + * DOMAttributeNames: object mapping React attribute name to the DOM
50 + * attribute name. Attribute names not specified use the **lowercase**
51 + * normalized name.
52 + *
53 + * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
54 + * Property names not specified use the normalized name.
55 + *
56 + * DOMMutationMethods: Properties that require special mutation methods. If
57 + * `value` is undefined, the mutation method should unset the property.
58 + *
59 + * @param {object} domPropertyConfig the config as described above.
60 + */
61 + injectDOMPropertyConfig: function(domPropertyConfig) {
62 + var Properties = domPropertyConfig.Properties || {};
63 + var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
64 + var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {};
65 + var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
66 +
67 + if (domPropertyConfig.isCustomAttribute) {
68 + DOMProperty._isCustomAttributeFunctions.push(
69 + domPropertyConfig.isCustomAttribute
70 + );
71 + }
72 +
73 + for (var propName in Properties) {
74 + invariant(
75 + !DOMProperty.isStandardName[propName],
76 + 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' +
77 + '\'%s\' which has already been injected. You may be accidentally ' +
78 + 'injecting the same DOM property config twice, or you may be ' +
79 + 'injecting two configs that have conflicting property names.',
80 + propName
81 + );
82 +
83 + DOMProperty.isStandardName[propName] = true;
84 +
85 + DOMProperty.getAttributeName[propName] =
86 + DOMAttributeNames[propName] || propName.toLowerCase();
87 +
88 + DOMProperty.getPropertyName[propName] =
89 + DOMPropertyNames[propName] || propName;
90 +
91 + var mutationMethod = DOMMutationMethods[propName];
92 + if (mutationMethod) {
93 + DOMProperty.getMutationMethod[propName] = mutationMethod;
94 + }
95 +
96 + var propConfig = Properties[propName];
97 + DOMProperty.mustUseAttribute[propName] =
98 + propConfig & DOMPropertyInjection.MUST_USE_ATTRIBUTE;
99 + DOMProperty.mustUseProperty[propName] =
100 + propConfig & DOMPropertyInjection.MUST_USE_PROPERTY;
101 + DOMProperty.hasBooleanValue[propName] =
102 + propConfig & DOMPropertyInjection.HAS_BOOLEAN_VALUE;
103 + DOMProperty.hasSideEffects[propName] =
104 + propConfig & DOMPropertyInjection.HAS_SIDE_EFFECTS;
105 +
106 + invariant(
107 + !DOMProperty.mustUseAttribute[propName] ||
108 + !DOMProperty.mustUseProperty[propName],
109 + 'DOMProperty: Cannot use require using both attribute and property: %s',
110 + propName
111 + );
112 + invariant(
113 + DOMProperty.mustUseProperty[propName] ||
114 + !DOMProperty.hasSideEffects[propName],
115 + 'DOMProperty: Properties that have side effects must use property: %s',
116 + propName
117 + );
118 + }
119 + }
120 +};
121 var defaultValueCache = {};
122
123 /**
@@ -94,13 +189,22 @@ var DOMProperty = {
189 */
190 hasSideEffects: {},
191
192 + /**
193 + * All of the isCustomAttribute() functions that have been injected.
194 + */
195 + _isCustomAttributeFunctions: [],
196 +
197 /**
198 * Checks whether a property name is a custom attribute.
199 * @method
200 */
101 - isCustomAttribute: RegExp.prototype.test.bind(
102 - /^(data|aria)-[a-z_][a-z\d_.\-]*$/
103 - ),
201 + isCustomAttribute: function(attributeName) {
202 + return DOMProperty._isCustomAttributeFunctions.some(
203 + function(isCustomAttributeFn) {
204 + return isCustomAttributeFn.call(null, attributeName);
205 + }
206 + );
207 + },
208
209 /**
210 * Returns the default property value for a DOM property (i.e., not an
@@ -121,168 +225,9 @@ var DOMProperty = {
225 nodeDefaults[prop] = testElement[prop];
226 }
227 return nodeDefaults[prop];
124 - }
125 -};
126 -
127 -/**
128 - * Mapping from normalized, camelcased property names to a configuration that
129 - * specifies how the associated DOM property should be accessed or rendered.
130 - */
131 -var MustUseAttribute = 0x1;
132 -var MustUseProperty = 0x2;
133 -var HasBooleanValue = 0x4;
134 -var HasSideEffects = 0x8;
228 + },
229
136 -var Properties = {
137 - /**
138 - * Standard Properties
139 - */
140 - accept: null,
141 - action: null,
142 - ajaxify: MustUseAttribute,
143 - allowFullScreen: MustUseAttribute | HasBooleanValue,
144 - alt: null,
145 - autoComplete: null,
146 - autoplay: HasBooleanValue,
147 - cellPadding: null,
148 - cellSpacing: null,
149 - checked: MustUseProperty | HasBooleanValue,
150 - className: MustUseProperty,
151 - colSpan: null,
152 - contentEditable: null,
153 - controls: MustUseProperty | HasBooleanValue,
154 - data: null, // For `<object />` acts as `src`.
155 - dir: null,
156 - disabled: MustUseProperty | HasBooleanValue,
157 - draggable: null,
158 - enctype: null,
159 - height: MustUseAttribute,
160 - href: null,
161 - htmlFor: null,
162 - max: null,
163 - method: null,
164 - min: null,
165 - multiple: MustUseProperty | HasBooleanValue,
166 - name: null,
167 - poster: null,
168 - preload: null,
169 - placeholder: null,
170 - rel: null,
171 - required: HasBooleanValue,
172 - role: MustUseAttribute,
173 - scrollLeft: MustUseProperty,
174 - scrollTop: MustUseProperty,
175 - selected: MustUseProperty | HasBooleanValue,
176 - spellCheck: null,
177 - src: null,
178 - step: null,
179 - style: null,
180 - tabIndex: null,
181 - target: null,
182 - title: null,
183 - type: null,
184 - value: MustUseProperty | HasSideEffects,
185 - width: MustUseAttribute,
186 - wmode: MustUseAttribute,
187 - /**
188 - * SVG Properties
189 - */
190 - cx: MustUseProperty,
191 - cy: MustUseProperty,
192 - d: MustUseProperty,
193 - fill: MustUseProperty,
194 - fx: MustUseProperty,
195 - fy: MustUseProperty,
196 - points: MustUseProperty,
197 - r: MustUseProperty,
198 - stroke: MustUseProperty,
199 - strokeLinecap: MustUseProperty,
200 - strokeWidth: MustUseProperty,
201 - transform: MustUseProperty,
202 - x: MustUseProperty,
203 - x1: MustUseProperty,
204 - x2: MustUseProperty,
205 - version: MustUseProperty,
206 - viewBox: MustUseProperty,
207 - y: MustUseProperty,
208 - y1: MustUseProperty,
209 - y2: MustUseProperty,
210 - spreadMethod: MustUseProperty,
211 - offset: MustUseProperty,
212 - stopColor: MustUseProperty,
213 - stopOpacity: MustUseProperty,
214 - gradientUnits: MustUseProperty,
215 - gradientTransform: MustUseProperty
230 + injection: DOMPropertyInjection
231 };
232
218 -/**
219 - * Attribute names not specified use the **lowercase** normalized name.
220 - */
221 -var DOMAttributeNames = {
222 - className: 'class',
223 - htmlFor: 'for',
224 - strokeLinecap: 'stroke-linecap',
225 - strokeWidth: 'stroke-width',
226 - stopColor: 'stop-color',
227 - stopOpacity: 'stop-opacity'
228 -};
229 -
230 -/**
231 - * Property names not specified use the normalized name.
232 - */
233 -var DOMPropertyNames = {
234 - autoComplete: 'autocomplete',
235 - spellCheck: 'spellcheck'
236 -};
237 -
238 -/**
239 - * Properties that require special mutation methods. If `value` is undefined,
240 - * the mutation method should unset the property.
241 - */
242 -var DOMMutationMethods = {
243 - /**
244 - * Setting `className` to null may cause it to be set to the string "null".
245 - *
246 - * @param {DOMElement} node
247 - * @param {*} value
248 - */
249 - className: function(node, value) {
250 - node.className = value || '';
251 - }
252 -};
253 -
254 -for (var propName in Properties) {
255 - DOMProperty.isStandardName[propName] = true;
256 -
257 - DOMProperty.getAttributeName[propName] =
258 - DOMAttributeNames[propName] || propName.toLowerCase();
259 -
260 - DOMProperty.getPropertyName[propName] =
261 - DOMPropertyNames[propName] || propName;
262 -
263 - var mutationMethod = DOMMutationMethods[propName];
264 - if (mutationMethod) {
265 - DOMProperty.getMutationMethod[propName] = mutationMethod;
266 - }
267 -
268 - var propConfig = Properties[propName];
269 - DOMProperty.mustUseAttribute[propName] = propConfig & MustUseAttribute;
270 - DOMProperty.mustUseProperty[propName] = propConfig & MustUseProperty;
271 - DOMProperty.hasBooleanValue[propName] = propConfig & HasBooleanValue;
272 - DOMProperty.hasSideEffects[propName] = propConfig & HasSideEffects;
273 -
274 - invariant(
275 - !DOMProperty.mustUseAttribute[propName] ||
276 - !DOMProperty.mustUseProperty[propName],
277 - 'DOMProperty: Cannot use require using both attribute and property: %s',
278 - propName
279 - );
280 - invariant(
281 - DOMProperty.mustUseProperty[propName] ||
282 - !DOMProperty.hasSideEffects[propName],
283 - 'DOMProperty: Properties that have side effects must use property: %s',
284 - propName
285 - );
286 -}
287 -
233 module.exports = DOMProperty;
src/dom/DefaultDOMPropertyConfig.js new
+139
@@ -0,0 +1,139 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule DefaultDOMPropertyConfig
17 + */
18 +
19 +"use strict";
20 +
21 +var DOMProperty = require('DOMProperty');
22 +
23 +var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
24 +var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY;
25 +var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE;
26 +var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS;
27 +
28 +var DefaultDOMPropertyConfig = {
29 + isCustomAttribute: RegExp.prototype.test.bind(
30 + /^(data|aria)-[a-z_][a-z\d_.\-]*$/
31 + ),
32 + Properties: {
33 + /**
34 + * Standard Properties
35 + */
36 + accept: null,
37 + action: null,
38 + ajaxify: MUST_USE_ATTRIBUTE,
39 + allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
40 + alt: null,
41 + autoComplete: null,
42 + autoplay: HAS_BOOLEAN_VALUE,
43 + cellPadding: null,
44 + cellSpacing: null,
45 + checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
46 + className: MUST_USE_PROPERTY,
47 + colSpan: null,
48 + contentEditable: null,
49 + controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
50 + data: null, // For `<object />` acts as `src`.
51 + dir: null,
52 + disabled: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
53 + draggable: null,
54 + enctype: null,
55 + height: MUST_USE_ATTRIBUTE,
56 + href: null,
57 + htmlFor: null,
58 + id: MUST_USE_PROPERTY,
59 + max: null,
60 + method: null,
61 + min: null,
62 + multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
63 + name: null,
64 + poster: null,
65 + preload: null,
66 + placeholder: null,
67 + rel: null,
68 + required: HAS_BOOLEAN_VALUE,
69 + role: MUST_USE_ATTRIBUTE,
70 + scrollLeft: MUST_USE_PROPERTY,
71 + scrollTop: MUST_USE_PROPERTY,
72 + selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
73 + spellCheck: null,
74 + src: null,
75 + step: null,
76 + style: null,
77 + tabIndex: null,
78 + target: null,
79 + title: null,
80 + type: null,
81 + value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
82 + width: MUST_USE_ATTRIBUTE,
83 + wmode: MUST_USE_ATTRIBUTE,
84 + /**
85 + * SVG Properties
86 + */
87 + cx: MUST_USE_PROPERTY,
88 + cy: MUST_USE_PROPERTY,
89 + d: MUST_USE_PROPERTY,
90 + fill: MUST_USE_PROPERTY,
91 + fx: MUST_USE_PROPERTY,
92 + fy: MUST_USE_PROPERTY,
93 + points: MUST_USE_PROPERTY,
94 + r: MUST_USE_PROPERTY,
95 + stroke: MUST_USE_PROPERTY,
96 + strokeLinecap: MUST_USE_PROPERTY,
97 + strokeWidth: MUST_USE_PROPERTY,
98 + transform: MUST_USE_PROPERTY,
99 + x: MUST_USE_PROPERTY,
100 + x1: MUST_USE_PROPERTY,
101 + x2: MUST_USE_PROPERTY,
102 + version: MUST_USE_PROPERTY,
103 + viewBox: MUST_USE_PROPERTY,
104 + y: MUST_USE_PROPERTY,
105 + y1: MUST_USE_PROPERTY,
106 + y2: MUST_USE_PROPERTY,
107 + spreadMethod: MUST_USE_PROPERTY,
108 + offset: MUST_USE_PROPERTY,
109 + stopColor: MUST_USE_PROPERTY,
110 + stopOpacity: MUST_USE_PROPERTY,
111 + gradientUnits: MUST_USE_PROPERTY,
112 + gradientTransform: MUST_USE_PROPERTY
113 + },
114 + DOMAttributeNames: {
115 + className: 'class',
116 + htmlFor: 'for',
117 + strokeLinecap: 'stroke-linecap',
118 + strokeWidth: 'stroke-width',
119 + stopColor: 'stop-color',
120 + stopOpacity: 'stop-opacity'
121 + },
122 + DOMPropertyNames: {
123 + autoComplete: 'autocomplete',
124 + spellCheck: 'spellcheck'
125 + },
126 + DOMMutationMethods: {
127 + /**
128 + * Setting `className` to null may cause it to be set to the string "null".
129 + *
130 + * @param {DOMElement} node
131 + * @param {*} value
132 + */
133 + className: function(node, value) {
134 + node.className = value || '';
135 + }
136 + }
137 +};
138 +
139 +module.exports = DefaultDOMPropertyConfig;
src/dom/__tests__/DOMPropertyOperations-test.js
+65
@@ -21,10 +21,15 @@
21
22 describe('DOMPropertyOperations', function() {
23 var DOMPropertyOperations;
24 + var DOMProperty;
25
26 beforeEach(function() {
27 require('mock-modules').dumpCache();
28 + var ReactDefaultInjection = require('ReactDefaultInjection');
29 + ReactDefaultInjection.inject();
30 +
31 DOMPropertyOperations = require('DOMPropertyOperations');
32 + DOMProperty = require('DOMProperty');
33 });
34
35 describe('createMarkupForProperty', function() {
@@ -46,6 +51,13 @@ describe('DOMPropertyOperations', function() {
51 )).toBe('');
52 });
53
54 + it('should work with the id attribute', function() {
55 + expect(DOMPropertyOperations.createMarkupForProperty(
56 + 'id',
57 + 'simple'
58 + )).toBe('id="simple"');
59 + });
60 +
61 it('should create markup for boolean properties', function() {
62 expect(DOMPropertyOperations.createMarkupForProperty(
63 'checked',
@@ -126,4 +138,57 @@ describe('DOMPropertyOperations', function() {
138 });
139
140 });
141 +
142 + describe('injectDOMPropertyConfig', function() {
143 + it('should support custom attributes', function() {
144 + // foobar does not exist yet
145 + expect(DOMPropertyOperations.createMarkupForProperty(
146 + 'foobar',
147 + 'simple'
148 + )).toBe(null);
149 +
150 + // foo-* does not exist yet
151 + expect(DOMPropertyOperations.createMarkupForProperty(
152 + 'foo-xyz',
153 + 'simple'
154 + )).toBe(null);
155 +
156 + // inject foobar DOM property
157 + DOMProperty.injection.injectDOMPropertyConfig({
158 + isCustomAttribute: function(name) {
159 + return name.indexOf('foo-') === 0;
160 + },
161 + Properties: {foobar: null}
162 + });
163 +
164 + // Ensure old attributes still work
165 + expect(DOMPropertyOperations.createMarkupForProperty(
166 + 'name',
167 + 'simple'
168 + )).toBe('name="simple"');
169 + expect(DOMPropertyOperations.createMarkupForProperty(
170 + 'data-name',
171 + 'simple'
172 + )).toBe('data-name="simple"');
173 +
174 + // foobar should work
175 + expect(DOMPropertyOperations.createMarkupForProperty(
176 + 'foobar',
177 + 'simple'
178 + )).toBe('foobar="simple"');
179 +
180 + // foo-* should work
181 + expect(DOMPropertyOperations.createMarkupForProperty(
182 + 'foo-xyz',
183 + 'simple'
184 + )).toBe('foo-xyz="simple"');
185 +
186 + // It should complain about double injections.
187 + expect(function() {
188 + DOMProperty.injection.injectDOMPropertyConfig(
189 + {Properties: {foobar: null}}
190 + );
191 + }).toThrow();
192 + });
193 + });
194 });
src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js
+22 -3
@@ -26,23 +26,42 @@ describe('AnalyticsEventPlugin', function() {
26 var EventPluginHub;
27 var EventPluginRegistry;
28 var React;
29 - var ReactDefaultInjection;
29 var ReactEventEmitter;
30 var ReactEventTopLevelCallback;
31 var ReactTestUtils;
32
33 + var DefaultEventPluginOrder;
34 + var EnterLeaveEventPlugin;
35 + var ChangeEventPlugin;
36 + var ReactInstanceHandles;
37 + var SimpleEventPlugin;
38 +
39 beforeEach(function() {
40 AnalyticsEventPluginFactory = require('AnalyticsEventPluginFactory');
41 EventPluginHub = require('EventPluginHub');
42 EventPluginRegistry = require('EventPluginRegistry');
43 React = require('React');
39 - ReactDefaultInjection = require('ReactDefaultInjection');
44 ReactEventEmitter = require('ReactEventEmitter');
45 ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
46 ReactTestUtils = require('ReactTestUtils');
47
48 EventPluginRegistry._resetEventPlugins();
45 - ReactDefaultInjection.inject();
49 +
50 + // Re-inject default events system after resetting.
51 + DefaultEventPluginOrder = require('DefaultEventPluginOrder');
52 + EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
53 + ChangeEventPlugin = require('ChangeEventPlugin');
54 + ReactInstanceHandles = require('ReactInstanceHandles');
55 + SimpleEventPlugin = require('SimpleEventPlugin');
56 +
57 + EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
58 + EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
59 +
60 + EventPluginHub.injection.injectEventPluginsByName({
61 + 'SimpleEventPlugin': SimpleEventPlugin,
62 + 'EnterLeaveEventPlugin': EnterLeaveEventPlugin,
63 + 'ChangeEventPlugin': ChangeEventPlugin
64 + });
65
66 ReactEventEmitter.ensureListening(false, ReactEventTopLevelCallback);
67 });