Compare commits

...

16 Commits

24 changed files with 422 additions and 280 deletions

View File

@ -215,7 +215,7 @@ define([
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name objects * @name objects
*/ */
this.objects = new api.ObjectAPI(); this.objects = new api.ObjectAPI(this.types);
/** /**
* An interface for retrieving and interpreting telemetry data associated * An interface for retrieving and interpreting telemetry data associated

View File

@ -60,7 +60,8 @@ define([
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()), var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()),
keystring = utils.makeKeyString(newStyleObject.identifier); keystring = utils.makeKeyString(newStyleObject.identifier);
this.eventEmitter.emit(keystring + ":*", newStyleObject); this.eventEmitter.emit(keystring + ':$_synchronize_model', newStyleObject);
this.eventEmitter.emit(keystring + ':*', newStyleObject);
this.eventEmitter.emit('mutation', newStyleObject); this.eventEmitter.emit('mutation', newStyleObject);
}.bind(this); }.bind(this);

View File

@ -21,9 +21,11 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'lodash' 'lodash',
'../objects/MutableDomainObject'
], function ( ], function (
_ _,
MutableDomainObject
) { ) {
/** /**
* A CompositionCollection represents the list of domain objects contained * A CompositionCollection represents the list of domain objects contained
@ -60,6 +62,17 @@ define([
}; };
this.onProviderAdd = this.onProviderAdd.bind(this); this.onProviderAdd = this.onProviderAdd.bind(this);
this.onProviderRemove = this.onProviderRemove.bind(this); this.onProviderRemove = this.onProviderRemove.bind(this);
this.mutables = {};
if (this.domainObject instanceof MutableDomainObject.default &&
this.publicAPI.objects.isMutable(this.domainObject)) {
this.returnMutables = true;
this.domainObject.$observe('$_destroy', () => {
Object.values(this.mutables).forEach(mutable => {
mutable.$destroy();
});
});
}
} }
/** /**
@ -74,9 +87,6 @@ define([
if (!this.listeners[event]) { if (!this.listeners[event]) {
throw new Error('Event not supported by composition: ' + event); throw new Error('Event not supported by composition: ' + event);
} }
if (!this.mutationListener) {
this._synchronize();
}
if (this.provider.on && this.provider.off) { if (this.provider.on && this.provider.off) {
if (event === 'add') { if (event === 'add') {
this.provider.on( this.provider.on(
@ -132,8 +142,6 @@ define([
this.listeners[event].splice(index, 1); this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) { if (this.listeners[event].length === 0) {
this._destroy();
// Remove provider listener if this is the last callback to // Remove provider listener if this is the last callback to
// be removed. // be removed.
if (this.provider.off && this.provider.on) { if (this.provider.off && this.provider.on) {
@ -182,6 +190,13 @@ define([
} }
this.provider.add(this.domainObject, child.identifier); this.provider.add(this.domainObject, child.identifier);
} else { } else {
if (this.returnMutables && this.publicAPI.objects.isMutable(child)) {
let keyString = this.publicAPI.objects.makeKeyString(child.identifier);
if (this.publicAPI.objects.isMutable(child)) {
child = this.publicAPI.objects.mutable(child);
this.mutables[keyString] = child;
}
}
this.emit('add', child); this.emit('add', child);
} }
}; };
@ -195,6 +210,7 @@ define([
* @name load * @name load
*/ */
CompositionCollection.prototype.load = function () { CompositionCollection.prototype.load = function () {
this.cleanUpMutables();
return this.provider.load(this.domainObject) return this.provider.load(this.domainObject)
.then(function (children) { .then(function (children) {
return Promise.all(children.map((c) => this.publicAPI.objects.get(c))); return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
@ -225,6 +241,13 @@ define([
if (!skipMutate) { if (!skipMutate) {
this.provider.remove(this.domainObject, child.identifier); this.provider.remove(this.domainObject, child.identifier);
} else { } else {
if (this.returnMutables && this.publicAPI.objects.isMutable(child)) {
let keyString = this.publicAPI.objects.makeKeyString(child);
if (this.mutables[keyString] !== undefined) {
this.mutables[keyString].$destroy();
delete this.mutables[keyString];
}
}
this.emit('remove', child); this.emit('remove', child);
} }
}; };
@ -271,19 +294,6 @@ define([
this.remove(child, true); this.remove(child, true);
}; };
CompositionCollection.prototype._synchronize = function () {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
});
};
CompositionCollection.prototype._destroy = function () {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
};
/** /**
* Emit events. * Emit events.
* @private * @private
@ -298,5 +308,11 @@ define([
}); });
}; };
CompositionCollection.prototype.cleanUpMutables = function () {
Object.values(this.mutables).forEach(mutable => {
mutable.$destroy();
});
}
return CompositionCollection; return CompositionCollection;
}); });

View File

@ -0,0 +1,105 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*****************************************************************************/
import _ from 'lodash';
import utils from './object-utils.js';
const ANY_OBJECT_EVENT = 'mutation';
class MutableDomainObject {
constructor(eventEmitter) {
Object.defineProperties(this, {
_eventEmitter: {
value: eventEmitter,
// Property should not be serialized
enumerable: false
},
_observers: {
value: [],
// Property should not be serialized
enumerable: false
},
isMutable: {
value: true,
// Property should not be serialized
enumerable: false
}
});
}
$observe(path, callback) {
var fullPath = qualifiedEventName(this, path);
var eventOff =
this._eventEmitter.off.bind(this._eventEmitter, fullPath, callback);
this._eventEmitter.on(fullPath, callback);
this._observers.push(eventOff);
return eventOff;
}
$set(path, value) {
_.set(this, path, value);
_.set(this, 'modified', Date.now());
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
this._eventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
//Emit a general "any object" event
this._eventEmitter.emit(ANY_OBJECT_EVENT, this);
//Emit wildcard event, with path so that callback knows what changed
this._eventEmitter.emit(qualifiedEventName(this, '*'), this, path, value);
//Emit events specific to properties affected
let parentPropertiesList = path.split('.');
for (let index = parentPropertiesList.length; index > 0; index--) {
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
this._eventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath));
}
//TODO: Emit events for listeners of child properties when parent changes.
// Do it at observer time - also register observers for parent attribute path.
}
$destroy() {
this._observers.forEach(observer => observer());
delete this._eventEmitter;
delete this._observers;
this._eventEmitter.emit(qualifiedEventName(this, '$_destroy'));
}
static createMutable(object, mutationTopic) {
let mutable = Object.create(new MutableDomainObject(mutationTopic));
Object.assign(mutable, object);
mutable.$observe('$_synchronize_model', (updatedObject) => {
let clone = JSON.parse(JSON.stringify(updatedObject));
let deleted = _.difference(Object.keys(updatedObject), Object.keys(updatedObject));
deleted.forEach((propertyName) => delete mutable[propertyName]);
Object.assign(mutable, clone);
})
return mutable;
}
}
function qualifiedEventName(object, eventName) {
var keystring = utils.makeKeyString(object.identifier);
return [keystring, eventName].join(':');
}
export default MutableDomainObject;

View File

@ -1,102 +0,0 @@
/*****************************************************************************
* 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([
'./object-utils.js',
'lodash'
], function (
utils,
_
) {
var ANY_OBJECT_EVENT = "mutation";
/**
* The MutableObject wraps a DomainObject and provides getters and
* setters for
* @param eventEmitter
* @param object
* @interface MutableObject
*/
function MutableObject(eventEmitter, object) {
this.eventEmitter = eventEmitter;
this.object = object;
this.unlisteners = [];
}
function qualifiedEventName(object, eventName) {
var keystring = utils.makeKeyString(object.identifier);
return [keystring, eventName].join(':');
}
MutableObject.prototype.stopListening = function () {
this.unlisteners.forEach(function (unlisten) {
unlisten();
});
this.unlisteners = [];
};
/**
* Observe changes to this domain object.
* @param {string} path the property to observe
* @param {Function} callback a callback to invoke when new values for
* this property are observed
* @method on
* @memberof module:openmct.MutableObject#
*/
MutableObject.prototype.on = function (path, callback) {
var fullPath = qualifiedEventName(this.object, path);
var eventOff =
this.eventEmitter.off.bind(this.eventEmitter, fullPath, callback);
this.eventEmitter.on(fullPath, callback);
this.unlisteners.push(eventOff);
};
/**
* Modify this domain object.
* @param {string} path the property to modify
* @param {*} value the new value for this property
* @method set
* @memberof module:openmct.MutableObject#
*/
MutableObject.prototype.set = function (path, value) {
_.set(this.object, path, value);
_.set(this.object, 'modified', Date.now());
var handleRecursiveMutation = function (newObject) {
this.object = newObject;
}.bind(this);
//Emit wildcard event
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
//Emit a general "any object" event
this.eventEmitter.emit(ANY_OBJECT_EVENT, this.object);
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
//Emit event specific to property
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
};
return MutableObject;
});

View File

@ -23,14 +23,14 @@
define([ define([
'lodash', 'lodash',
'./object-utils', './object-utils',
'./MutableObject', './MutableDomainObject',
'./RootRegistry', './RootRegistry',
'./RootObjectProvider', './RootObjectProvider',
'EventEmitter' 'EventEmitter'
], function ( ], function (
_, _,
utils, utils,
MutableObject, MutableDomainObject,
RootRegistry, RootRegistry,
RootObjectProvider, RootObjectProvider,
EventEmitter EventEmitter
@ -43,7 +43,8 @@ define([
* @memberof module:openmct * @memberof module:openmct
*/ */
function ObjectAPI() { function ObjectAPI(typeRegistry) {
this.typeRegistry = typeRegistry;
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.providers = {}; this.providers = {};
this.rootRegistry = new RootRegistry(); this.rootRegistry = new RootRegistry();
@ -157,6 +158,19 @@ define([
return provider.get(identifier); return provider.get(identifier);
}; };
/**
* Will fetch object, returning it as a MutableDomainObject IF the object is mutable.
*/
ObjectAPI.prototype.getAsMutable = function (identifier) {
return this.get(identifier).then((object) => {
if (this.isMutable(object)) {
return this.mutable(object);
} else {
return object;
}
});
}
ObjectAPI.prototype.delete = function () { ObjectAPI.prototype.delete = function () {
throw new Error('Delete not implemented'); throw new Error('Delete not implemented');
}; };
@ -177,6 +191,20 @@ define([
this.rootRegistry.addRoot(key); this.rootRegistry.addRoot(key);
}; };
ObjectAPI.prototype.mutable = function (object) {
if (!this.isMutable) {
throw `Error: Attempted to create mutable from immutable object ${object.name}`;
}
return MutableDomainObject.default.createMutable(object, this.eventEmitter);
}
ObjectAPI.prototype.isMutable = function (object) {
// Checking for mutability is a bit broken right now. This is an 80% solution,
// but does not work in many cases.
const type = this.typeRegistry.get(object.type);
return type && type.definition.creatable === true;
}
/** /**
* Modify a domain object. * Modify a domain object.
* @param {module:openmct.DomainObject} object the object to mutate * @param {module:openmct.DomainObject} object the object to mutate
@ -186,9 +214,17 @@ define([
* @memberof module:openmct.ObjectAPI# * @memberof module:openmct.ObjectAPI#
*/ */
ObjectAPI.prototype.mutate = function (domainObject, path, value) { ObjectAPI.prototype.mutate = function (domainObject, path, value) {
var mutableObject = if (!this.isMutable(domainObject)) {
new MutableObject(this.eventEmitter, domainObject); throw `Error: Attempted to mutate immutable object ${domainObject.name}`;
return mutableObject.set(path, value); }
console.warn('DEPRECATION WARNING: The .mutate() function in the Object API is now deprecated. Please use mutable() ');
if (domainObject instanceof MutableDomainObject.default) {
domainObject.$set(path, value);
} else {
let mutable = this.mutable(domainObject);
mutable.$set(path, value);
mutable.$destroy();
}
}; };
/** /**
@ -201,10 +237,14 @@ define([
* @memberof module:openmct.ObjectAPI# * @memberof module:openmct.ObjectAPI#
*/ */
ObjectAPI.prototype.observe = function (domainObject, path, callback) { ObjectAPI.prototype.observe = function (domainObject, path, callback) {
var mutableObject = console.warn('DEPRECATION WARNING: The .observe() function in the Object API is now deprecated. Please use mutable() ');
new MutableObject(this.eventEmitter, domainObject); if (domainObject instanceof MutableDomainObject.default) {
mutableObject.on(path, callback); return domainObject.$observe(path, callback);
return mutableObject.stopListening.bind(mutableObject); } else {
let mutable = this.mutable(domainObject);
mutable.$observe(path, callback);
return () => mutable.$destroy();
}
}; };
/** /**

View File

@ -0,0 +1,121 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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([
'./ObjectAPI'
], function (
ObjectAPI
) {
fdescribe('The Object API', function () {
describe('Mutable Object', function () {
let testObject;
let mutable;
let objectAPI;
beforeEach(function () {
objectAPI = new ObjectAPI();
testObject = {
identifier: {
namespace: 'test-namespace',
key: 'test-key'
},
otherAttribute: 'other-attribute-value',
objectAttribute: {
embeddedObject: {
embeddedKey: 'embedded-value'
}
}
};
mutable = objectAPI.mutable(testObject);
});
it('retains own properties', function () {
expect(mutable.hasOwnProperty('identifier')).toBe(true);
expect(mutable.hasOwnProperty('otherAttribute')).toBe(true);
expect(mutable.identifier).toEqual(testObject.identifier);
expect(mutable.otherAttribute).toEqual(testObject.otherAttribute);
});
it('is identical to original object when serialized', function () {
expect(JSON.stringify(mutable)).toEqual(JSON.stringify(testObject));
});
it('is identical to original object when serialized', function () {
expect(JSON.stringify(mutable)).toEqual(JSON.stringify(testObject));
});
describe('uses events', function () {
let testObjectDuplicate;
let mutableSecondInstance;
beforeEach(function () {
// Duplicate object to guarantee we are not sharing object instance, which would invalidate test
testObjectDuplicate = JSON.parse(JSON.stringify(testObject));
mutableSecondInstance = objectAPI.mutable(testObjectDuplicate);
});
it('to stay synchronized when mutated', function () {
mutable.$set('otherAttribute', 'new-attribute-value');
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
mutableSecondInstance.observe('otherAttribute', mutationCallback);
mutable.$set('otherAttribute', 'some-new-value')
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
});
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
mutableSecondInstance.observe('objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback);
mutableSecondInstance.observe('objectAttribute.embeddedObject', embeddedObjectCallback);
mutableSecondInstance.observe('objectAttribute', objectAttributeCallback);
mutable.$set('objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
}
});
});
});
});
});
})
});

View File

@ -23,7 +23,7 @@
<template> <template>
<tr @contextmenu.prevent="showContextMenu"> <tr @contextmenu.prevent="showContextMenu">
<td>{{name}}</td> <td>{{domainObject.name}}</td>
<td>{{timestamp}}</td> <td>{{timestamp}}</td>
<td :class="valueClass"> <td :class="valueClass">
{{value}} {{value}}
@ -50,7 +50,6 @@ export default {
currentObjectPath.unshift(this.domainObject); currentObjectPath.unshift(this.domainObject);
return { return {
name: this.domainObject.name,
timestamp: '---', timestamp: '---',
value: '---', value: '---',
valueClass: '', valueClass: '',
@ -70,9 +69,6 @@ export default {
this.valueClass = ''; this.valueClass = '';
} }
}, },
updateName(name){
this.name = name;
},
updateTimeSystem(timeSystem) { updateTimeSystem(timeSystem) {
this.value = '---'; this.value = '---';
this.timestamp = '---'; this.timestamp = '---';
@ -98,14 +94,6 @@ export default {
.telemetry .telemetry
.limitEvaluator(this.domainObject); .limitEvaluator(this.domainObject);
this.stopWatchingMutation = openmct
.objects
.observe(
this.domainObject,
'*',
this.updateName
);
this.openmct.time.on('timeSystem', this.updateTimeSystem); this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.timestampKey = this.openmct.time.timeSystem().key; this.timestampKey = this.openmct.time.timeSystem().key;
@ -126,7 +114,6 @@ export default {
.then((array) => this.updateValues(array[array.length - 1])); .then((array) => this.updateValues(array[array.length - 1]));
}, },
destroyed() { destroyed() {
this.stopWatchingMutation();
this.unsubscribe(); this.unsubscribe();
this.openmct.off('timeSystem', this.updateTimeSystem); this.openmct.off('timeSystem', this.updateTimeSystem);
} }

View File

@ -176,9 +176,8 @@
export default { export default {
data() { data() {
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return { return {
internalDomainObject: domainObject, internalDomainObject: this.domainObject,
initSelectIndex: undefined, initSelectIndex: undefined,
selection: [] selection: []
}; };
@ -566,9 +565,6 @@
} }
}, },
mounted() { mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
}.bind(this));
this.openmct.selection.on('change', this.setSelection); this.openmct.selection.on('change', this.setSelection);
this.initializeItems(); this.initializeItems();
this.composition = this.openmct.composition.get(this.internalDomainObject); this.composition = this.openmct.composition.get(this.internalDomainObject);
@ -580,7 +576,6 @@
this.openmct.selection.off('change', this.setSelection); this.openmct.selection.off('change', this.setSelection);
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.unlisten();
} }
} }
</script> </script>

View File

@ -113,10 +113,13 @@
} }
}, },
mounted() { mounted() {
this.openmct.objects.get(this.item.identifier) this.openmct.objects.getAsMutable(this.item.identifier)
.then(this.setObject); .then(this.setObject);
}, },
destroyed() { destroyed() {
if (this.domainObject.$destroy) {
this.domainObject.$destroy();
}
if (this.removeSelectable) { if (this.removeSelectable) {
this.removeSelectable(); this.removeSelectable();
} }

View File

@ -248,12 +248,13 @@
} }
}, },
mounted() { mounted() {
this.openmct.objects.get(this.item.identifier) this.openmct.objects.getAsMutable(this.item.identifier)
.then(this.setObject); .then(this.setObject);
this.openmct.time.on("bounds", this.refreshData); this.openmct.time.on("bounds", this.refreshData);
}, },
destroyed() { destroyed() {
this.removeSubscription(); this.removeSubscription();
this.domainObject.$destroy();
if (this.removeSelectable) { if (this.removeSelectable) {
this.removeSelectable(); this.removeSelectable();

View File

@ -152,7 +152,7 @@
}, },
getGlobalFiltersToRemove(keyString) { getGlobalFiltersToRemove(keyString) {
let filtersToRemove = new Set(); let filtersToRemove = new Set();
if (this.children[keyString]){
this.children[keyString].metadataWithFilters.forEach(metadatum => { this.children[keyString].metadataWithFilters.forEach(metadatum => {
let keepFilter = false let keepFilter = false
Object.keys(this.children).forEach(childKeyString => { Object.keys(this.children).forEach(childKeyString => {
@ -170,6 +170,7 @@
filtersToRemove.add(metadatum.key); filtersToRemove.add(metadatum.key);
} }
}); });
}
return Array.from(filtersToRemove); return Array.from(filtersToRemove);
}, },
@ -234,16 +235,14 @@
this.composition.on('add', this.addChildren); this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren); this.composition.on('remove', this.removeChildren);
this.composition.load(); this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters); this.unobserve = this.providedObject.$observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters); this.unobserveGlobalFilters = this.providedObject.$observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
}, },
beforeDestroy() { beforeDestroy() {
this.composition.off('add', this.addChildren); this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren); this.composition.off('remove', this.removeChildren);
this.unobserve(); this.unobserve();
this.unobserveGlobalFilters(); this.unobserveGlobalFilters();
this.unobserveAllMutation();
} }
} }
</script> </script>

View File

@ -629,9 +629,6 @@ export default {
return size; return size;
} }
}, },
updateDomainObject(newDomainObject) {
this.domainObject = newDomainObject;
},
moveContainer(toIndex, event) { moveContainer(toIndex, event) {
let containerId = event.dataTransfer.getData('containerid'); let containerId = event.dataTransfer.getData('containerid');
let container = this.containers.filter(c => c.id === containerId)[0]; let container = this.containers.filter(c => c.id === containerId)[0];
@ -664,14 +661,10 @@ export default {
this.composition.on('add', this.addFrame); this.composition.on('add', this.addFrame);
this.RemoveAction = new RemoveAction(this.openmct); this.RemoveAction = new RemoveAction(this.openmct);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
}, },
beforeDestroy() { beforeDestroy() {
this.composition.off('remove', this.removeChildObject); this.composition.off('remove', this.removeChildObject);
this.composition.off('add', this.addFrame); this.composition.off('add', this.addFrame);
this.unobserve();
} }
} }
</script> </script>

View File

@ -108,7 +108,7 @@ export default {
}, },
mounted() { mounted() {
if (this.frame.domainObjectIdentifier) { if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{ this.openmct.objects.getAsMutable(this.frame.domainObjectIdentifier).then((object)=>{
this.setDomainObject(object); this.setDomainObject(object);
}); });
} }
@ -116,6 +116,10 @@ export default {
this.dragGhost = document.getElementById('js-fl-drag-ghost'); this.dragGhost = document.getElementById('js-fl-drag-ghost');
}, },
beforeDestroy() { beforeDestroy() {
if (this.domainObject.$destroy) {
this.domainObject.$destroy();
}
if (this.unsubscribeSelection) { if (this.unsubscribeSelection) {
this.unsubscribeSelection(); this.unsubscribeSelection();
} }

View File

@ -34,11 +34,8 @@ define([
this.columns = {}; this.columns = {};
this.removeColumnsForObject = this.removeColumnsForObject.bind(this); this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
this.objectMutated = this.objectMutated.bind(this);
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated); this.unlistenFromMutation = domainObject.$observe('configuration', configuration => this.updateListeners(configuration));
} }
getConfiguration() { getConfiguration() {
@ -60,15 +57,8 @@ define([
* @private * @private
* @param {*} object * @param {*} object
*/ */
objectMutated(object) { updateListeners(configuration) {
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible. this.emit('change', configuration);
this.domainObject = object;
//Was it the configuration that changed?
if (object.configuration !== undefined && !_.eq(object.configuration, this.oldConfiguration)) {
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
this.emit('change', object.configuration);
}
} }
addSingleColumnForObject(telemetryObject, column, position) { addSingleColumnForObject(telemetryObject, column, position) {

View File

@ -23,11 +23,13 @@
define( define(
[ [
'EventEmitter', 'EventEmitter',
'lodash' 'lodash',
'../api/objects/MutableDomainObject.js'
], ],
function ( function (
EventEmitter, EventEmitter,
_ _,
MutableDomainObject
) { ) {
/** /**
@ -75,6 +77,10 @@ define(
this.selected = [selectable]; this.selected = [selectable];
} }
if (this.temporaryMutables) {
this.temporaryMutables.forEach(mutable => mutable.$destroy());
}
this.emit('change', this.selected); this.emit('change', this.selected);
}; };
@ -233,12 +239,6 @@ define(
element.addEventListener('click', capture, true); element.addEventListener('click', capture, true);
element.addEventListener('click', selectCapture); element.addEventListener('click', selectCapture);
if (context.item) {
var unlisten = this.openmct.objects.observe(context.item, "*", function (newItem) {
context.item = newItem;
});
}
if (select) { if (select) {
element.click(); element.click();
} }
@ -246,10 +246,6 @@ define(
return function () { return function () {
element.removeEventListener('click', capture, true); element.removeEventListener('click', capture, true);
element.removeEventListener('click', selectCapture); element.removeEventListener('click', selectCapture);
if (unlisten) {
unlisten();
}
}; };
}; };

View File

@ -68,12 +68,6 @@ export default {
}; };
}, },
mounted() { mounted() {
if (this.observedObject) {
let removeListener = this.openmct.objects.observe(this.observedObject, '*', (newObject) => {
this.observedObject = newObject;
});
this.$once('hook:destroyed', removeListener);
}
this.previewAction = new PreviewAction(this.openmct); this.previewAction = new PreviewAction(this.openmct);
}, },
computed: { computed: {

View File

@ -59,10 +59,6 @@ export default {
delete this.removeSelectable; delete this.removeSelectable;
} }
if (this.composition) {
this.composition._destroy();
}
this.openmct.objectViews.off('clearData', this.clearData); this.openmct.objectViews.off('clearData', this.clearData);
}, },
invokeEditModeHandler(editMode) { invokeEditModeHandler(editMode) {
@ -80,7 +76,6 @@ export default {
this.composition = this.openmct.composition.get(this.currentObject); this.composition = this.openmct.composition.get(this.currentObject);
if (this.composition) { if (this.composition) {
this.composition._synchronize();
this.loadComposition(); this.loadComposition();
} }
@ -130,20 +125,17 @@ export default {
delete this.removeSelectable; delete this.removeSelectable;
} }
if (this.composition) {
this.composition._destroy();
}
this.currentObject = object; this.currentObject = object;
this.composition = this.openmct.composition.get(this.currentObject);
if (this.composition) {
this.loadComposition();
}
if (currentObjectPath) { if (currentObjectPath) {
this.currentObjectPath = currentObjectPath; this.currentObjectPath = currentObjectPath;
} }
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
this.currentObject = mutatedObject;
});
this.viewKey = viewKey; this.viewKey = viewKey;
this.updateView(immediatelySelect); this.updateView(immediatelySelect);
}, },

View File

@ -118,9 +118,6 @@ export default {
} }
if (this.parentObject) { if (this.parentObject) {
this.mutationUnobserver = this.openmct.objects.observe(this.parentObject, '*', (updatedModel) => {
this.parentObject = updatedModel;
});
this.composition = this.openmct.composition.get(this.parentObject); this.composition = this.openmct.composition.get(this.parentObject);
if (this.composition) { if (this.composition) {
@ -141,8 +138,7 @@ export default {
}, },
addElement(element) { addElement(element) {
let keyString = this.openmct.objects.makeKeyString(element.identifier); let keyString = this.openmct.objects.makeKeyString(element.identifier);
this.elementsCache[keyString] = this.elementsCache[keyString] = element;
JSON.parse(JSON.stringify(element));
this.applySearch(this.currentSearch); this.applySearch(this.currentSearch);
}, },
reorderElements() { reorderElements() {
@ -182,9 +178,6 @@ export default {
this.openmct.editor.off('isEditing', this.setEditState); this.openmct.editor.off('isEditing', this.setEditState);
this.openmct.selection.off('change', this.showSelection); this.openmct.selection.off('change', this.showSelection);
if (this.mutationUnobserver) {
this.mutationUnobserver();
}
if (this.compositionUnlistener) { if (this.compositionUnlistener) {
this.compositionUnlistener(); this.compositionUnlistener();
} }

View File

@ -77,8 +77,9 @@ const PLACEHOLDER_OBJECT = {};
this.showSaveMenu = false; this.showSaveMenu = false;
}, },
updateName(event) { updateName(event) {
// TODO: handle isssues with contenteditable text escaping.
if (event.target.innerText !== this.domainObject.name && event.target.innerText.match(/\S/)) { if (event.target.innerText !== this.domainObject.name && event.target.innerText.match(/\S/)) {
this.openmct.objects.mutate(this.domainObject, 'name', event.target.innerText); this.domainObject.$set('name', event.target.innerText);
} else { } else {
event.target.innerText = this.domainObject.name; event.target.innerText = this.domainObject.name;
} }
@ -223,20 +224,7 @@ const PLACEHOLDER_OBJECT = {};
this.isEditing = isEditing; this.isEditing = isEditing;
}); });
}, },
watch: {
domainObject() {
if (this.mutationObserver) {
this.mutationObserver();
}
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
this.domainObject = domainObject;
});
}
},
beforeDestroy: function () { beforeDestroy: function () {
if (this.mutationObserver) {
this.mutationObserver();
}
document.removeEventListener('click', this.closeViewAndSaveMenu); document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway); window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
} }

View File

@ -207,6 +207,7 @@
getAllChildren() { getAllChildren() {
this.isLoading = true; this.isLoading = true;
this.openmct.objects.get('ROOT') this.openmct.objects.get('ROOT')
.then(root => this.openmct.objects.mutable(root))
.then(root => { .then(root => {
return this.openmct.composition.get(root).load() return this.openmct.composition.get(root).load()
}) })
@ -223,8 +224,18 @@
}); });
}, },
getFilteredChildren() { getFilteredChildren() {
if (this.filteredTreeItems) {
this.filteredTreeItems.forEach(filteredTreeItem => filteredTreeItem.destroy());
}
this.searchService.query(this.searchValue).then(children => { this.searchService.query(this.searchValue).then(children => {
this.filteredTreeItems = children.hits.map(child => { this.filteredTreeItems = children.hits
.map(child => {
if (this.openmct.objects.isMutable(child)) {
this.openmct.objects.mutable(child);
}
})
.map(child => {
let context = child.object.getCapability('context'), let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'), object = child.object.useCapability('adapter'),

View File

@ -66,11 +66,7 @@
// TODO: set isAlias per tree-item // TODO: set isAlias per tree-item
this.domainObject = this.node.object; this.domainObject = this.node.object;
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject;
});
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) { if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true; this.hasChildren = true;
} }
@ -82,6 +78,7 @@
if (this.composition) { if (this.composition) {
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.children.forEach(child => child.object.$destroy());
delete this.composition; delete this.composition;
} }
}, },
@ -101,6 +98,9 @@
}, },
methods: { methods: {
addChild (child) { addChild (child) {
if (this.openmct.objects.isMutable(child)) {
child = this.openmct.objects.mutable(child);
}
this.children.push({ this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier), id: this.openmct.objects.makeKeyString(child.identifier),
object: child, object: child,
@ -110,8 +110,16 @@
}, },
removeChild(identifier) { removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier); let removeId = this.openmct.objects.makeKeyString(identifier);
let removed = [];
this.children = this.children this.children = this.children
.filter(c => c.id !== removeId); .filter(c => {
if(c.id !== removeId) {
removed.push(c);
return true
}
return false;
});
removed.forEach(removedChild => removedChild.object.$destroy());
}, },
finishLoading () { finishLoading () {
this.isLoading = false; this.isLoading = false;

View File

@ -7,13 +7,14 @@ define([
return function install(openmct) { return function install(openmct) {
let navigateCall = 0; let navigateCall = 0;
let browseObject; let browseObject;
let unobserve = undefined; let mutable;
let currentObjectPath; let currentObjectPath;
openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot); openmct.router.route(/^\/browse\/?$/, navigateToFirstChildOfRoot);
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => { openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
let navigatePath = results[1]; let navigatePath = results[1];
clearMutationListeners();
navigateToPath(navigatePath, params.view); navigateToPath(navigatePath, params.view);
}); });
@ -27,10 +28,17 @@ define([
}); });
function viewObject(object, viewProvider) { function viewObject(object, viewProvider) {
if (mutable) {
mutable.$destroy();
mutable = undefined;
}
if (openmct.objects.isMutable(object)) {
mutable = openmct.objects.mutable(object);
}
currentObjectPath = openmct.router.path; currentObjectPath = openmct.router.path;
openmct.layout.$refs.browseObject.show(object, viewProvider.key, true, currentObjectPath); openmct.layout.$refs.browseObject.show(mutable || object, viewProvider.key, true, currentObjectPath);
openmct.layout.$refs.browseBar.domainObject = object; openmct.layout.$refs.browseBar.domainObject = mutable || object;
openmct.layout.$refs.browseBar.viewKey = viewProvider.key; openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
} }
@ -38,37 +46,26 @@ define([
navigateCall++; navigateCall++;
let currentNavigation = navigateCall; let currentNavigation = navigateCall;
if (unobserve) {
unobserve();
unobserve = undefined;
}
//Split path into object identifiers //Split path into object identifiers
if (!Array.isArray(path)) { if (!Array.isArray(path)) {
path = path.split('/'); path = path.split('/');
} }
return pathToObjects(path).then((objects)=>{ return pathToObjects(path).then((objects) => {
if (currentNavigation !== navigateCall) { if (currentNavigation !== navigateCall) {
return; // Prevent race. return; // Prevent race.
} }
let navigatedObject = objects[objects.length - 1];
// FIXME: this is a hack to support create action, intended to // FIXME: this is a hack to support create action, intended to
// expose the current routed path. We need to rewrite the // expose the current routed path. We need to rewrite the
// navigation service and router to expose a clear and minimal // navigation service and router to expose a clear and minimal
// API for this. // API for this.
openmct.router.path = objects.reverse(); objects = objects.reverse();
openmct.router.path = objects;
unobserve = this.openmct.objects.observe(openmct.router.path[0], '*', (newObject) => { browseObject = objects[0];
openmct.router.path[0] = newObject; openmct.layout.$refs.browseBar.domainObject = browseObject;
});
openmct.layout.$refs.browseBar.domainObject = navigatedObject; if (!browseObject) {
browseObject = navigatedObject;
if (!navigatedObject) {
openmct.layout.$refs.browseObject.clear(); openmct.layout.$refs.browseObject.clear();
return; return;
} }
@ -78,12 +75,12 @@ define([
document.title = browseObject.name; //change document title to current object in main view document.title = browseObject.name; //change document title to current object in main view
if (currentProvider && currentProvider.canView(navigatedObject)) { if (currentProvider && currentProvider.canView(browseObject)) {
viewObject(navigatedObject, currentProvider); viewObject(browseObject, currentProvider);
return; return;
} }
let defaultProvider = openmct.objectViews.get(navigatedObject)[0]; let defaultProvider = openmct.objectViews.get(browseObject)[0];
if (defaultProvider) { if (defaultProvider) {
openmct.router.updateParams({ openmct.router.updateParams({
view: defaultProvider.key view: defaultProvider.key
@ -99,7 +96,7 @@ define([
function pathToObjects(path) { function pathToObjects(path) {
return Promise.all(path.map((keyString)=>{ return Promise.all(path.map((keyString)=>{
return openmct.objects.get(keyString); return openmct.objects.getAsMutable(keyString);
})); }));
} }
@ -117,5 +114,15 @@ define([
}); });
}); });
} }
function clearMutationListeners() {
if (openmct.router.path !== undefined) {
openmct.router.path.forEach((pathObject) => {
if (pathObject.$destroy) {
pathObject.$destroy();
}
});
}
}
} }
}); });

View File

@ -81,8 +81,8 @@
} }
}, },
observeObject(domainObject, id) { observeObject(domainObject, id) {
let unobserveObject = this.openmct.objects.observe(domainObject, '*', function(newObject) { let unobserveObject = domainObject.$observe('*', function(newObject) {
this.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject)); this.domainObjectsById[id].newObject = newObject;
this.updateToolbarAfterMutation(); this.updateToolbarAfterMutation();
}.bind(this)); }.bind(this));
this.unObserveObjects.push(unobserveObject); this.unObserveObjects.push(unobserveObject);