diff --git a/src/browser/eventPlugins/ResponderEventPlugin.js b/src/browser/eventPlugins/ResponderEventPlugin.js index 7cc9909818..7e3b525d68 100644 --- a/src/browser/eventPlugins/ResponderEventPlugin.js +++ b/src/browser/eventPlugins/ResponderEventPlugin.js @@ -23,7 +23,7 @@ var EventPluginUtils = require('EventPluginUtils'); var EventPropagators = require('EventPropagators'); var SyntheticEvent = require('SyntheticEvent'); -var accumulate = require('accumulate'); +var accumulateInto = require('accumulateInto'); var keyOf = require('keyOf'); var isStartish = EventPluginUtils.isStartish; @@ -212,7 +212,7 @@ function setResponderAndExtractTransfer( nativeEvent ); EventPropagators.accumulateDirectDispatches(terminateEvent); - extracted = accumulate(extracted, [grantEvent, terminateEvent]); + extracted = accumulateInto(extracted, [grantEvent, terminateEvent]); responderID = wantsResponderID; } else { var rejectEvent = SyntheticEvent.getPooled( @@ -221,10 +221,10 @@ function setResponderAndExtractTransfer( nativeEvent ); EventPropagators.accumulateDirectDispatches(rejectEvent); - extracted = accumulate(extracted, rejectEvent); + extracted = accumulateInto(extracted, rejectEvent); } } else { - extracted = accumulate(extracted, grantEvent); + extracted = accumulateInto(extracted, grantEvent); responderID = wantsResponderID; } return extracted; @@ -288,7 +288,7 @@ var ResponderEventPlugin = { nativeEvent ); if (transfer) { - extracted = accumulate(extracted, transfer); + extracted = accumulateInto(extracted, transfer); } } // Now that we know the responder is set correctly, we can dispatch @@ -303,7 +303,7 @@ var ResponderEventPlugin = { nativeEvent ); EventPropagators.accumulateDirectDispatches(gesture); - extracted = accumulate(extracted, gesture); + extracted = accumulateInto(extracted, gesture); } if (type === eventTypes.responderRelease) { responderID = null; diff --git a/src/browser/ui/dom/components/LocalEventTrapMixin.js b/src/browser/ui/dom/components/LocalEventTrapMixin.js index eee6f33a1d..b2af85460e 100644 --- a/src/browser/ui/dom/components/LocalEventTrapMixin.js +++ b/src/browser/ui/dom/components/LocalEventTrapMixin.js @@ -20,7 +20,7 @@ var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); -var accumulate = require('accumulate'); +var accumulateInto = require('accumulateInto'); var forEachAccumulated = require('forEachAccumulated'); var invariant = require('invariant'); @@ -36,7 +36,8 @@ var LocalEventTrapMixin = { handlerBaseName, this.getDOMNode() ); - this._localEventListeners = accumulate(this._localEventListeners, listener); + this._localEventListeners = + accumulateInto(this._localEventListeners, listener); }, // trapCapturedEvent would look nearly identical. We don't implement that diff --git a/src/event/EventPluginHub.js b/src/event/EventPluginHub.js index 76dc8e1448..6ba02942f4 100644 --- a/src/event/EventPluginHub.js +++ b/src/event/EventPluginHub.js @@ -21,7 +21,7 @@ var EventPluginRegistry = require('EventPluginRegistry'); var EventPluginUtils = require('EventPluginUtils'); -var accumulate = require('accumulate'); +var accumulateInto = require('accumulateInto'); var forEachAccumulated = require('forEachAccumulated'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -236,7 +236,7 @@ var EventPluginHub = { nativeEvent ); if (extractedEvents) { - events = accumulate(events, extractedEvents); + events = accumulateInto(events, extractedEvents); } } } @@ -252,7 +252,7 @@ var EventPluginHub = { */ enqueueEvents: function(events) { if (events) { - eventQueue = accumulate(eventQueue, events); + eventQueue = accumulateInto(eventQueue, events); } }, diff --git a/src/event/EventPropagators.js b/src/event/EventPropagators.js index 5a739cb576..8a391ed257 100644 --- a/src/event/EventPropagators.js +++ b/src/event/EventPropagators.js @@ -21,7 +21,7 @@ var EventConstants = require('EventConstants'); var EventPluginHub = require('EventPluginHub'); -var accumulate = require('accumulate'); +var accumulateInto = require('accumulateInto'); var forEachAccumulated = require('forEachAccumulated'); var PropagationPhases = EventConstants.PropagationPhases; @@ -52,8 +52,9 @@ function accumulateDirectionalDispatches(domID, upwards, event) { var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured; var listener = listenerAtPhase(domID, event, phase); if (listener) { - event._dispatchListeners = accumulate(event._dispatchListeners, listener); - event._dispatchIDs = accumulate(event._dispatchIDs, domID); + event._dispatchListeners = + accumulateInto(event._dispatchListeners, listener); + event._dispatchIDs = accumulateInto(event._dispatchIDs, domID); } } @@ -85,8 +86,9 @@ function accumulateDispatches(id, ignoredDirection, event) { var registrationName = event.dispatchConfig.registrationName; var listener = getListener(id, registrationName); if (listener) { - event._dispatchListeners = accumulate(event._dispatchListeners, listener); - event._dispatchIDs = accumulate(event._dispatchIDs, id); + event._dispatchListeners = + accumulateInto(event._dispatchListeners, listener); + event._dispatchIDs = accumulateInto(event._dispatchIDs, id); } } } diff --git a/src/utils/__tests__/accumulateInto-test.js b/src/utils/__tests__/accumulateInto-test.js new file mode 100644 index 0000000000..44aacd6266 --- /dev/null +++ b/src/utils/__tests__/accumulateInto-test.js @@ -0,0 +1,64 @@ +/** + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @emails react-core + * @jsx React.DOM + */ + +"use strict"; + +require('mock-modules') + .dontMock('accumulateInto'); + +var accumulateInto; + +describe('accumulateInto', function() { + + beforeEach(function() { + accumulateInto = require('accumulateInto'); + }); + + it('throws if the second item is null', function() { + expect(function() { + accumulateInto([], null); + }).toThrow( + 'Invariant Violation: accumulateInto(...): Accumulated items must not ' + + 'be null or undefined.' + ); + }); + + it('returns the second item if first is null', function() { + var a = []; + expect(accumulateInto(null, a)).toBe(a); + }); + + it('merges the second into the first if first item is an array', function() { + var a = [1, 2]; + var b = [3, 4]; + accumulateInto(a, b); + expect(a).toEqual([1, 2, 3, 4]); + expect(b).toEqual([3, 4]); + var c = [1]; + accumulateInto(c, 2); + expect(c).toEqual([1, 2]); + }); + + it('returns a new array if first or both items are scalar', function() { + var a = [2]; + expect(accumulateInto(1, a)).toEqual([1, 2]); + expect(a).toEqual([2]); + expect(accumulateInto(1, 2)).toEqual([1, 2]); + }); +}); diff --git a/src/utils/accumulateInto.js b/src/utils/accumulateInto.js new file mode 100644 index 0000000000..12d3298dbe --- /dev/null +++ b/src/utils/accumulateInto.js @@ -0,0 +1,69 @@ +/** + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule accumulateInto + */ + +"use strict"; + +var invariant = require('invariant'); + +/** + * + * Accumulates items that must not be null or undefined into the first one. This + * is used to conserve memory by avoiding array allocations, and thus sacrifices + * API cleanness. Since `current` can be null before being passed in and not + * null after this function, make sure to assign it back to `current`: + * + * `a = accumulateInto(a, b);` + * + * This API should be sparingly used. Try `accumulate` for something cleaner. + * + * @return {*|array<*>} An accumulation of items. + */ + +function accumulateInto(current, next) { + invariant( + next != null, + 'accumulateInto(...): Accumulated items must not be null or undefined.' + ); + if (current == null) { + return next; + } + + // Both are not empty. Warning: Never call x.concat(y) when you are not + // certain that x is an Array (x could be a string with concat method). + var currentIsArray = Array.isArray(current); + var nextIsArray = Array.isArray(next); + + if (currentIsArray && nextIsArray) { + current.push.apply(current, next); + return current; + } + + if (currentIsArray) { + current.push(next); + return current; + } + + if (nextIsArray) { + // A bit too dangerous to mutate `next`. + return [current].concat(next); + } + + return [current, next]; +} + +module.exports = accumulateInto;