UNPKG

react-relay

Version:

A framework for building data-driven React applications.

308 lines (256 loc) • 9.98 kB
/** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule observeAllRelayQueryData * @typechecks * */ 'use strict'; var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default']; var _slicedToArray = require('babel-runtime/helpers/sliced-to-array')['default']; var _Object$keys = require('babel-runtime/core-js/object/keys')['default']; var emptyFunction = require('fbjs/lib/emptyFunction'); var filterExclusiveKeys = require('./filterExclusiveKeys'); var forEachObject = require('fbjs/lib/forEachObject'); var invariant = require('fbjs/lib/invariant'); var observeRelayQueryData = require('./observeRelayQueryData'); var DATAID_REMOVED = {}; function observeAllRelayQueryData(store, queryNode, dataIDs, options) { return new RelayQueryMultipleDataObservable(function (dataID) { return observeRelayQueryData(store, queryNode, dataID, options); }, dataIDs); } var RelayQueryMultipleDataObservable = (function () { function RelayQueryMultipleDataObservable(observeRelayQueryData, dataIDs) { _classCallCheck(this, RelayQueryMultipleDataObservable); this._activeSubscriptions = 0; this._dataIDs = _Object$keys(toObject(dataIDs)); this._lastError = null; this._observeRelayQueryData = observeRelayQueryData; this._observers = null; this._shouldExecuteCallbacks = false; this._subscribeCalls = []; this._subscriptions = {}; this._wrappedData = {}; } /** * Returns a new object with the keys in the same order as they appear in * `reference`. */ RelayQueryMultipleDataObservable.prototype.subscribe = function subscribe(callbacks) { var _this = this; // An error occurred earlier, it is no longer possible to subscribe to this // observer if (this._lastError) { callbacks.onError(this._lastError); return { dispose: emptyFunction }; } // Only create observers on the first subscribe call if (!this._observers) { this._setupObservers(this._dataIDs); } // List of indices of where in the list of subscription per dataID this // subscription is var dataIDToSubscriptionIndex = {}; this._addSubscriptions(this._dataIDs, dataIDToSubscriptionIndex, callbacks); // An error occurred while creating the subscriptions, rolling back if (this._lastError) { callbacks.onError(this._lastError); this._disposeSubscriptions(dataIDToSubscriptionIndex); return { dispose: emptyFunction }; } this._subscribeCalls.push({ callbacks: callbacks, dataIDToSubscriptionIndex: dataIDToSubscriptionIndex }); callbacks.onNext(unwrapData(this._wrappedData)); var index = this._subscribeCalls.length - 1; var isDisposed = false; this._activeSubscriptions++; return { dispose: function dispose() { !!isDisposed ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayObserver.dispose(): Subscription was already disposed.') : invariant(false) : undefined; isDisposed = true; _this._activeSubscriptions--; _this._disposeSubscriptions(dataIDToSubscriptionIndex); _this._subscribeCalls[index] = null; if (!_this._activeSubscriptions) { _this._observers = null; _this._subscribeCalls = []; _this._subscriptions = {}; _this._wrappedData = {}; } } }; }; /** * Changes the observed dataIDs to the given dataIDs, the order of the new * dataIDs is kept. */ RelayQueryMultipleDataObservable.prototype.setDataIDs = function setDataIDs(dataIDs) { var _this2 = this; !!this._lastError ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayObserver.setDataIDs(): Unable to update records on a defunct ' + 'observer.') : invariant(false) : undefined; var dataIDSet = toObject(dataIDs); this._dataIDs = _Object$keys(dataIDSet); var _filterExclusiveKeys = filterExclusiveKeys(this._observers, dataIDSet); var _filterExclusiveKeys2 = _slicedToArray(_filterExclusiveKeys, 2); var removedDataIDs = _filterExclusiveKeys2[0]; var addedDataIDs = _filterExclusiveKeys2[1]; // Unsubscribe subscriptions for removed data IDs removedDataIDs.forEach(function (dataID) { var subscriptions = _this2._subscriptions[dataID]; if (subscriptions) { subscriptions.forEach(function (subscription) { subscription && subscription.dispose(); _this2._wrappedData[dataID] = DATAID_REMOVED; }); _this2._subscriptions[dataID] = null; } }); this._setupObservers(addedDataIDs); this._subscribeCalls.forEach(function (call) { // Add the dataIDs to any previously attached callbacks call && _this2._addSubscriptions(addedDataIDs, call.dataIDToSubscriptionIndex); }); // All subscriptions have been added and data has been ordered, invoke // callback on all subscriptions if (this._lastError) { this._callOnError(); } else { this._wrappedData = reorderObjectKeys(this._dataIDs, this._wrappedData); this._callOnNext(); } }; /** * Adds subscriptions for dataIDs that were added after the initial call to * `subscribe`. */ RelayQueryMultipleDataObservable.prototype._addSubscriptions = function _addSubscriptions(dataIDs, indices) { var _this3 = this; this._shouldExecuteCallbacks = false; dataIDs.forEach(function (dataID) { if (_this3._observers) { var observer = _this3._observers[dataID]; if (observer) { var subscriptions = _this3._subscriptions[dataID] || (_this3._subscriptions[dataID] = []); // The index the subscription will be stored at in the array. indices[dataID] = subscriptions.length; subscriptions.push(observer.subscribe({ onCompleted: function onCompleted() { return _this3._handleCompleted(dataID); }, onError: function onError(error) { return _this3._handleError(dataID, error); }, onNext: function onNext(data) { return _this3._handleNext(dataID, data); } })); } } }); this._shouldExecuteCallbacks = true; }; /** * Calls `onError` on all subscriptions but only if `_shouldExecuteCallbacks` * is `true`. This is handy to prevent excessive calls of `onError` when * observed DataIDs change */ RelayQueryMultipleDataObservable.prototype._callOnError = function _callOnError() { var _this4 = this; this._shouldExecuteCallbacks && this._subscribeCalls.forEach(function (call) { call && _this4._lastError && call.callbacks.onError(_this4._lastError); }); }; /** * Calls `onNext` on all subscriptions but only if `_shouldExecuteCallbacks` * is `true`. This is handy to prevent excessive calls of `onNext` when * observed DataIDs change */ RelayQueryMultipleDataObservable.prototype._callOnNext = function _callOnNext() { var _this5 = this; this._shouldExecuteCallbacks && this._subscribeCalls.forEach(function (call) { if (call) { call.callbacks.onNext(unwrapData(_this5._wrappedData)); } }); }; /** * Remove a set of subscriptions based on their dataID */ RelayQueryMultipleDataObservable.prototype._disposeSubscriptions = function _disposeSubscriptions(indices) { var _this6 = this; forEachObject(indices, function (index, dataID) { var subscriptions = _this6._subscriptions[dataID]; if (subscriptions && subscriptions[index]) { subscriptions[index].dispose(); subscriptions[index] = null; } }); }; RelayQueryMultipleDataObservable.prototype._handleCompleted = function _handleCompleted(dataID) { this._subscribeCalls.forEach(function (call) { call && call.callbacks.onCompleted(); }); }; /** * Notify all subscribers that an error occurred */ RelayQueryMultipleDataObservable.prototype._handleError = function _handleError(dataID, error) { this._lastError = error; this._callOnError(); }; RelayQueryMultipleDataObservable.prototype._handleNext = function _handleNext(dataID, data) { this._wrappedData[dataID] = data; this._callOnNext(); }; /** * Creates observers for the given dataIDs, if an observer for the given * dataID already exists nothing will be done for this dataID */ RelayQueryMultipleDataObservable.prototype._setupObservers = function _setupObservers(dataIDs) { var _this7 = this; if (!this._observers) { this._observers = {}; } dataIDs.forEach(function (dataID) { var observer = _this7._observeRelayQueryData(dataID); // Additional check if dataIDToObserver exists for Flow if (_this7._observers) { _this7._observers[dataID] = observer; } }); }; return RelayQueryMultipleDataObservable; })(); function reorderObjectKeys(reference, input) { var orderedInput = {}; reference.forEach(function (key) { !input.hasOwnProperty(key) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayObserver.setDataIDs(): Expected object to have key `%s`.', key) : invariant(false) : undefined; orderedInput[key] = input[key]; }); return orderedInput; } function toObject(dataIDs) { var dataIDSet = {}; dataIDs.forEach(function (dataID) { dataIDSet[dataID] = null; }); return dataIDSet; } function unwrapData(wrappedData) { var unwrappedData = []; forEachObject(wrappedData, function (data) { if (data !== DATAID_REMOVED) { unwrappedData.push(data); } }); return unwrappedData; } module.exports = observeAllRelayQueryData;