* Upgrades lodash * Replaces some usage of lodash with native functions. * Adds linting to catch cases where native functions could be used instead of lodash functions * Renamed testTools to testUtils Co-authored-by: Joshi <simplyrender@gmail.com> Co-authored-by: David Tsay <david.e.tsay@nasa.gov> Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com> Co-authored-by: Andrew Henry <akhenry@gmail.com>
312 lines
11 KiB
JavaScript
312 lines
11 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
* Administration. All rights reserved.
|
|
*
|
|
* Open MCT is 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.
|
|
*
|
|
* Open MCT includes source code licensed under additional open source
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
* this source code distribution or the Licensing information page available
|
|
* at runtime from the About dialog for additional information.
|
|
*****************************************************************************/
|
|
|
|
define([
|
|
'lodash',
|
|
'objectUtils'
|
|
], function (
|
|
_,
|
|
objectUtils
|
|
) {
|
|
/**
|
|
* A CompositionProvider provides the underlying implementation of
|
|
* composition-related behavior for certain types of domain object.
|
|
*
|
|
* By default, a composition provider will not support composition
|
|
* modification. You can add support for mutation of composition by
|
|
* defining `add` and/or `remove` methods.
|
|
*
|
|
* If the composition of an object can change over time-- perhaps via
|
|
* server updates or mutation via the add/remove methods, then one must
|
|
* trigger events as necessary.
|
|
*
|
|
* @interface CompositionProvider
|
|
* @memberof module:openmct
|
|
*/
|
|
|
|
function DefaultCompositionProvider(publicAPI, compositionAPI) {
|
|
this.publicAPI = publicAPI;
|
|
this.listeningTo = {};
|
|
this.onMutation = this.onMutation.bind(this);
|
|
|
|
this.cannotContainItself = this.cannotContainItself.bind(this);
|
|
|
|
compositionAPI.addPolicy(this.cannotContainItself);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
DefaultCompositionProvider.prototype.cannotContainItself = function (parent, child) {
|
|
return !(parent.identifier.namespace === child.identifier.namespace &&
|
|
parent.identifier.key === child.identifier.key);
|
|
}
|
|
|
|
/**
|
|
* Check if this provider should be used to load composition for a
|
|
* particular domain object.
|
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
|
* to check
|
|
* @returns {boolean} true if this provider can provide
|
|
* composition for a given domain object
|
|
* @memberof module:openmct.CompositionProvider#
|
|
* @method appliesTo
|
|
*/
|
|
DefaultCompositionProvider.prototype.appliesTo = function (domainObject) {
|
|
return !!domainObject.composition;
|
|
};
|
|
|
|
/**
|
|
* Load any domain objects contained in the composition of this domain
|
|
* object.
|
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
|
* for which to load composition
|
|
* @returns {Promise.<Array.<module:openmct.Identifier>>} a promise for
|
|
* the Identifiers in this composition
|
|
* @memberof module:openmct.CompositionProvider#
|
|
* @method load
|
|
*/
|
|
DefaultCompositionProvider.prototype.load = function (domainObject) {
|
|
return Promise.all(domainObject.composition);
|
|
};
|
|
|
|
/**
|
|
* Attach listeners for changes to the composition of a given domain object.
|
|
* Supports `add` and `remove` events.
|
|
*
|
|
* @param {module:openmct.DomainObject} domainObject to listen to
|
|
* @param String event the event to bind to, either `add` or `remove`.
|
|
* @param Function callback callback to invoke when event is triggered.
|
|
* @param [context] context to use when invoking callback.
|
|
*/
|
|
DefaultCompositionProvider.prototype.on = function (
|
|
domainObject,
|
|
event,
|
|
callback,
|
|
context
|
|
) {
|
|
this.establishTopicListener();
|
|
|
|
var keyString = objectUtils.makeKeyString(domainObject.identifier);
|
|
var objectListeners = this.listeningTo[keyString];
|
|
|
|
if (!objectListeners) {
|
|
objectListeners = this.listeningTo[keyString] = {
|
|
add: [],
|
|
remove: [],
|
|
reorder: [],
|
|
composition: [].slice.apply(domainObject.composition)
|
|
};
|
|
}
|
|
|
|
objectListeners[event].push({
|
|
callback: callback,
|
|
context: context
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Remove a listener that was previously added for a given domain object.
|
|
* event name, callback, and context must be the same as when the listener
|
|
* was originally attached.
|
|
*
|
|
* @param {module:openmct.DomainObject} domainObject to remove listener for
|
|
* @param String event event to stop listening to: `add` or `remove`.
|
|
* @param Function callback callback to remove.
|
|
* @param [context] context of callback to remove.
|
|
*/
|
|
DefaultCompositionProvider.prototype.off = function (
|
|
domainObject,
|
|
event,
|
|
callback,
|
|
context
|
|
) {
|
|
var keyString = objectUtils.makeKeyString(domainObject.identifier);
|
|
var objectListeners = this.listeningTo[keyString];
|
|
|
|
var index = objectListeners[event].findIndex(l => {
|
|
return l.callback === callback && l.context === context;
|
|
});
|
|
|
|
objectListeners[event].splice(index, 1);
|
|
if (!objectListeners.add.length && !objectListeners.remove.length && !objectListeners.reorder.length) {
|
|
delete this.listeningTo[keyString];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove a domain object from another domain object's composition.
|
|
*
|
|
* This method is optional; if not present, adding to a domain object's
|
|
* composition using this provider will be disallowed.
|
|
*
|
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
|
* which should have its composition modified
|
|
* @param {module:openmct.DomainObject} child the domain object to remove
|
|
* @memberof module:openmct.CompositionProvider#
|
|
* @method remove
|
|
*/
|
|
DefaultCompositionProvider.prototype.remove = function (domainObject, childId) {
|
|
let composition = domainObject.composition.filter(function (child) {
|
|
return !(childId.namespace === child.namespace &&
|
|
childId.key === child.key);
|
|
});
|
|
|
|
this.publicAPI.objects.mutate(domainObject, 'composition', composition);
|
|
};
|
|
|
|
/**
|
|
* Add a domain object to another domain object's composition.
|
|
*
|
|
* This method is optional; if not present, adding to a domain object's
|
|
* composition using this provider will be disallowed.
|
|
*
|
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
|
* which should have its composition modified
|
|
* @param {module:openmct.DomainObject} child the domain object to add
|
|
* @memberof module:openmct.CompositionProvider#
|
|
* @method add
|
|
*/
|
|
DefaultCompositionProvider.prototype.add = function (parent, childId) {
|
|
if (!this.includes(parent, childId)) {
|
|
parent.composition.push(childId);
|
|
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
|
|
}
|
|
};
|
|
/**
|
|
* @private
|
|
*/
|
|
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
|
|
return parent.composition.some(composee =>
|
|
this.publicAPI.objects.areIdsEqual(composee, childId));
|
|
};
|
|
|
|
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
|
|
let newComposition = domainObject.composition.slice();
|
|
let removeId = oldIndex > newIndex ? oldIndex + 1 : oldIndex;
|
|
let insertPosition = oldIndex < newIndex ? newIndex + 1 : newIndex;
|
|
//Insert object in new position
|
|
newComposition.splice(insertPosition, 0, domainObject.composition[oldIndex]);
|
|
newComposition.splice(removeId, 1);
|
|
|
|
let reorderPlan = [{
|
|
oldIndex,
|
|
newIndex
|
|
}];
|
|
|
|
if (oldIndex > newIndex) {
|
|
for (let i = newIndex; i < oldIndex; i++) {
|
|
reorderPlan.push({
|
|
oldIndex: i,
|
|
newIndex: i + 1
|
|
});
|
|
}
|
|
} else {
|
|
for (let i = oldIndex + 1; i <= newIndex; i++) {
|
|
reorderPlan.push({
|
|
oldIndex: i,
|
|
newIndex: i - 1
|
|
});
|
|
}
|
|
}
|
|
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
|
|
|
|
let id = objectUtils.makeKeyString(domainObject.identifier);
|
|
var listeners = this.listeningTo[id];
|
|
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
listeners.reorder.forEach(notify);
|
|
|
|
function notify(listener) {
|
|
if (listener.context) {
|
|
listener.callback.call(listener.context, reorderPlan);
|
|
} else {
|
|
listener.callback(reorderPlan);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Listens on general mutation topic, using injector to fetch to avoid
|
|
* circular dependencies.
|
|
*
|
|
* @private
|
|
*/
|
|
DefaultCompositionProvider.prototype.establishTopicListener = function () {
|
|
if (this.topicListener) {
|
|
return;
|
|
}
|
|
this.publicAPI.objects.eventEmitter.on('mutation', this.onMutation);
|
|
this.topicListener = () => {
|
|
this.publicAPI.objects.eventEmitter.off('mutation', this.onMutation)
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handles mutation events. If there are active listeners for the mutated
|
|
* object, detects changes to composition and triggers necessary events.
|
|
*
|
|
* @private
|
|
*/
|
|
DefaultCompositionProvider.prototype.onMutation = function (oldDomainObject) {
|
|
var id = objectUtils.makeKeyString(oldDomainObject.identifier);
|
|
var listeners = this.listeningTo[id];
|
|
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
var oldComposition = listeners.composition.map(objectUtils.makeKeyString);
|
|
var newComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
|
|
|
var added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
|
|
var removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
|
|
|
|
function notify(value) {
|
|
return function (listener) {
|
|
if (listener.context) {
|
|
listener.callback.call(listener.context, value);
|
|
} else {
|
|
listener.callback(value);
|
|
}
|
|
};
|
|
}
|
|
|
|
listeners.composition = newComposition.map(objectUtils.parseKeyString);
|
|
|
|
added.forEach(function (addedChild) {
|
|
listeners.add.forEach(notify(addedChild));
|
|
});
|
|
|
|
removed.forEach(function (removedChild) {
|
|
listeners.remove.forEach(notify(removedChild));
|
|
});
|
|
|
|
|
|
};
|
|
|
|
return DefaultCompositionProvider;
|
|
});
|