mirror of
https://github.com/nasa/openmct.git
synced 2024-12-24 07:16:39 +00:00
Merge remote-tracking branch 'origin/api-tutorials' into api-1110
Conflicts: src/MCT.js src/api/composition/CompositionCollection.js src/api/composition/DefaultCompositionProvider.js src/api/objects/MutableObject.js
This commit is contained in:
commit
6264ab75f3
@ -31,10 +31,12 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
require(['main'], function (mct) {
|
require(['main'], function (mct) {
|
||||||
require([
|
require([
|
||||||
|
'./tutorials/todo/todo',
|
||||||
'./example/imagery/bundle',
|
'./example/imagery/bundle',
|
||||||
'./example/eventGenerator/bundle',
|
'./example/eventGenerator/bundle',
|
||||||
'./example/generator/bundle',
|
'./example/generator/bundle'
|
||||||
], function () {
|
], function (todoPlugin) {
|
||||||
|
mct.install(todoPlugin);
|
||||||
mct.run();
|
mct.run();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
33
src/MCT.js
33
src/MCT.js
@ -4,7 +4,6 @@ define([
|
|||||||
'uuid',
|
'uuid',
|
||||||
'./api/api',
|
'./api/api',
|
||||||
'text!./adapter/templates/edit-object-replacement.html',
|
'text!./adapter/templates/edit-object-replacement.html',
|
||||||
'./ui/Dialog',
|
|
||||||
'./Selection',
|
'./Selection',
|
||||||
'./api/objects/object-utils',
|
'./api/objects/object-utils',
|
||||||
'./api/TimeConductor'
|
'./api/TimeConductor'
|
||||||
@ -14,7 +13,6 @@ define([
|
|||||||
uuid,
|
uuid,
|
||||||
api,
|
api,
|
||||||
editObjectTemplate,
|
editObjectTemplate,
|
||||||
Dialog,
|
|
||||||
Selection,
|
Selection,
|
||||||
objectUtils,
|
objectUtils,
|
||||||
TimeConductor
|
TimeConductor
|
||||||
@ -29,7 +27,16 @@ define([
|
|||||||
*/
|
*/
|
||||||
function MCT() {
|
function MCT() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
this.legacyBundle = { extensions: {} };
|
this.legacyBundle = { extensions: {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
key: "mct",
|
||||||
|
implementation: function () {
|
||||||
|
return this;
|
||||||
|
}.bind(this)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -133,13 +140,6 @@ define([
|
|||||||
region: region,
|
region: region,
|
||||||
key: viewKey
|
key: viewKey
|
||||||
});
|
});
|
||||||
|
|
||||||
this.legacyExtension('services', {
|
|
||||||
key: 'PublicAPI',
|
|
||||||
implementation: function () {
|
|
||||||
return this;
|
|
||||||
}.bind(this)
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,18 +163,6 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a dialog.
|
|
||||||
* @param view
|
|
||||||
* @param title
|
|
||||||
* @returns {Promise}
|
|
||||||
* @method dialog
|
|
||||||
* #memberof module:openmct.MCT#
|
|
||||||
*/
|
|
||||||
MCT.prototype.dialog = function (view, title) {
|
|
||||||
return new Dialog(view, title).show();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start running Open MCT. This should be called only after any plugins
|
* Start running Open MCT. This should be called only after any plugins
|
||||||
* have been installed.
|
* have been installed.
|
||||||
@ -216,6 +204,7 @@ define([
|
|||||||
|
|
||||||
MCT.prototype.regions = {
|
MCT.prototype.regions = {
|
||||||
main: "MAIN",
|
main: "MAIN",
|
||||||
|
properties: "PROPERTIES",
|
||||||
toolbar: "TOOLBAR"
|
toolbar: "TOOLBAR"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
*/
|
*/
|
||||||
Selection.prototype.select = function (value) {
|
Selection.prototype.select = function (value) {
|
||||||
this.selectedValues.push(value);
|
this.selectedValues.push(value);
|
||||||
this.emit('change');
|
this.emit('change', this.selectedValues);
|
||||||
return this.deselect.bind(this, value);
|
return this.deselect.bind(this, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
this.selectedValues = this.selectedValues.filter(function (v) {
|
this.selectedValues = this.selectedValues.filter(function (v) {
|
||||||
return v !== value;
|
return v !== value;
|
||||||
});
|
});
|
||||||
this.emit('change');
|
this.emit('change', this.selectedValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,7 +65,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
*/
|
*/
|
||||||
Selection.prototype.clear = function () {
|
Selection.prototype.clear = function () {
|
||||||
this.selectedValues = [];
|
this.selectedValues = [];
|
||||||
this.emit('change');
|
this.emit('change', this.selectedValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
return Selection;
|
return Selection;
|
||||||
|
44
src/adapter/actions/ActionDialogDecorator.js
Normal file
44
src/adapter/actions/ActionDialogDecorator.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
define([
|
||||||
|
'../../api/objects/object-utils'
|
||||||
|
], function (objectUtils) {
|
||||||
|
function ActionDialogDecorator(mct, newViews, actionService) {
|
||||||
|
this.actionService = actionService;
|
||||||
|
this.mct = mct;
|
||||||
|
this.definitions = newViews.filter(function (newView) {
|
||||||
|
return newView.region === mct.regions.properties;
|
||||||
|
}).map(function (newView) {
|
||||||
|
return newView.factory;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionDialogDecorator.prototype.getActions = function (context) {
|
||||||
|
var mct = this.mct;
|
||||||
|
var definitions = this.definitions;
|
||||||
|
|
||||||
|
return this.actionService.getActions(context).map(function (action) {
|
||||||
|
if (action.dialogService) {
|
||||||
|
var domainObject = objectUtils.toNewFormat(
|
||||||
|
context.domainObject.getModel(),
|
||||||
|
objectUtils.parseKeyString(context.domainObject.getId())
|
||||||
|
);
|
||||||
|
|
||||||
|
definitions = definitions.filter(function (definition) {
|
||||||
|
return definition.canView(domainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (definitions.length > 0) {
|
||||||
|
action.dialogService = Object.create(action.dialogService);
|
||||||
|
action.dialogService.getUserInput = function (form, value) {
|
||||||
|
return new mct.Dialog(
|
||||||
|
definitions[0].view(context.domainObject),
|
||||||
|
form.title
|
||||||
|
).show();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return ActionDialogDecorator;
|
||||||
|
});
|
@ -1,13 +1,17 @@
|
|||||||
define([
|
define([
|
||||||
'legacyRegistry',
|
'legacyRegistry',
|
||||||
|
'./actions/ActionDialogDecorator',
|
||||||
'./directives/MCTView',
|
'./directives/MCTView',
|
||||||
'./services/Instantiate',
|
'./services/Instantiate',
|
||||||
'./capabilities/APICapabilityDecorator'
|
'./capabilities/APICapabilityDecorator',
|
||||||
|
'./policies/AdapterCompositionPolicy'
|
||||||
], function (
|
], function (
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
|
ActionDialogDecorator,
|
||||||
MCTView,
|
MCTView,
|
||||||
Instantiate,
|
Instantiate,
|
||||||
APICapabilityDecorator
|
APICapabilityDecorator,
|
||||||
|
AdapterCompositionPolicy
|
||||||
) {
|
) {
|
||||||
legacyRegistry.register('src/adapter', {
|
legacyRegistry.register('src/adapter', {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -17,7 +21,7 @@ define([
|
|||||||
implementation: MCTView,
|
implementation: MCTView,
|
||||||
depends: [
|
depends: [
|
||||||
"newViews[]",
|
"newViews[]",
|
||||||
"PublicAPI"
|
"mct"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -41,6 +45,19 @@ define([
|
|||||||
depends: [
|
depends: [
|
||||||
"$injector"
|
"$injector"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "decorator",
|
||||||
|
provides: "actionService",
|
||||||
|
implementation: ActionDialogDecorator,
|
||||||
|
depends: [ "mct", "newViews[]" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
policies: [
|
||||||
|
{
|
||||||
|
category: "composition",
|
||||||
|
implementation: AdapterCompositionPolicy,
|
||||||
|
depends: [ "mct" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
26
src/adapter/policies/AdapterCompositionPolicy.js
Normal file
26
src/adapter/policies/AdapterCompositionPolicy.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
define([], function () {
|
||||||
|
function AdapterCompositionPolicy(mct) {
|
||||||
|
this.mct = mct;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdapterCompositionPolicy.prototype.allow = function (
|
||||||
|
containerType,
|
||||||
|
childType
|
||||||
|
) {
|
||||||
|
var containerObject = containerType.getInitialModel();
|
||||||
|
var childObject = childType.getInitialModel();
|
||||||
|
|
||||||
|
containerObject.type = containerType.getKey();
|
||||||
|
childObject.type = childType.getKey();
|
||||||
|
|
||||||
|
var composition = this.mct.Composition(containerObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
return composition.canContain(childObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return AdapterCompositionPolicy;
|
||||||
|
});
|
@ -3,18 +3,21 @@ define([
|
|||||||
'./TimeConductor',
|
'./TimeConductor',
|
||||||
'./View',
|
'./View',
|
||||||
'./objects/ObjectAPI',
|
'./objects/ObjectAPI',
|
||||||
'./composition/CompositionAPI'
|
'./composition/CompositionAPI',
|
||||||
|
'./ui/Dialog'
|
||||||
], function (
|
], function (
|
||||||
Type,
|
Type,
|
||||||
TimeConductor,
|
TimeConductor,
|
||||||
View,
|
View,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
CompositionAPI
|
CompositionAPI,
|
||||||
|
Dialog
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
Type: Type,
|
Type: Type,
|
||||||
View: View,
|
View: View,
|
||||||
Objects: ObjectAPI,
|
Objects: ObjectAPI,
|
||||||
Composition: CompositionAPI
|
Composition: CompositionAPI,
|
||||||
|
Dialog: Dialog
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -103,6 +103,9 @@ define([
|
|||||||
if (!this._children) {
|
if (!this._children) {
|
||||||
throw new Error("Must load composition before you can add!");
|
throw new Error("Must load composition before you can add!");
|
||||||
}
|
}
|
||||||
|
if (!this.canContain(child)) {
|
||||||
|
throw new Error("This object cannot contain that object.");
|
||||||
|
}
|
||||||
if (this.contains(child)) {
|
if (this.contains(child)) {
|
||||||
if (skipMutate) {
|
if (skipMutate) {
|
||||||
return; // don't add twice, don't error.
|
return; // don't add twice, don't error.
|
||||||
@ -165,6 +168,17 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this composition can contain this domain object.
|
||||||
|
* @name canContain
|
||||||
|
* @memberof module:openmct.CompositionCollection
|
||||||
|
* @param {module:openmct.DomainObject} the domain object to contain
|
||||||
|
* @returns {boolean} true if containment is allowed
|
||||||
|
*/
|
||||||
|
CompositionCollection.prototype.canContain = function (domainObject) {
|
||||||
|
return this.provider.canContain(this.domainObject, domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop using this composition collection. This will release any resources
|
* Stop using this composition collection. This will release any resources
|
||||||
* associated with this collection.
|
* associated with this collection.
|
||||||
|
@ -87,6 +87,17 @@ define([
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if one domain object can contain another.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* which will act as the container
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to be
|
||||||
|
* contained
|
||||||
|
* @returns {boolean} true if this is allowed
|
||||||
|
*/
|
||||||
|
DefaultCompositionProvider.prototype.canContain = function (domainObject, child) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a domain object from another domain object's composition.
|
* Remove a domain object from another domain object's composition.
|
||||||
|
@ -1,15 +1,54 @@
|
|||||||
define([
|
define([
|
||||||
'./object-utils',
|
'./object-utils',
|
||||||
'./ObjectAPI'
|
'./ObjectAPI',
|
||||||
|
'./objectEventEmitter'
|
||||||
], function (
|
], function (
|
||||||
utils,
|
utils,
|
||||||
ObjectAPI
|
ObjectAPI,
|
||||||
|
objectEventEmitter
|
||||||
) {
|
) {
|
||||||
function ObjectServiceProvider(objectService, instantiate) {
|
function ObjectServiceProvider(objectService, instantiate, topic) {
|
||||||
this.objectService = objectService;
|
this.objectService = objectService;
|
||||||
this.instantiate = instantiate;
|
this.instantiate = instantiate;
|
||||||
|
|
||||||
|
this.generalTopic = topic('mutation');
|
||||||
|
this.bridgeEventBuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges old and new style mutation events to provide compatibility between the two APIs
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ObjectServiceProvider.prototype.bridgeEventBuses = function () {
|
||||||
|
var removeGeneralTopicListener;
|
||||||
|
|
||||||
|
var handleMutation = function (newStyleObject) {
|
||||||
|
var keyString = utils.makeKeyString(newStyleObject.key);
|
||||||
|
var oldStyleObject = this.instantiate(utils.toOldFormat(newStyleObject), keyString);
|
||||||
|
|
||||||
|
// Don't trigger self
|
||||||
|
removeGeneralTopicListener();
|
||||||
|
|
||||||
|
oldStyleObject.getCapability('mutation').mutate(function () {
|
||||||
|
return utils.toOldFormat(newStyleObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var handleLegacyMutation = function (legacyObject){
|
||||||
|
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
||||||
|
|
||||||
|
//Don't trigger self
|
||||||
|
objectEventEmitter.off('mutation', handleMutation);
|
||||||
|
objectEventEmitter.emit(newStyleObject.key.identifier + ":*", newStyleObject);
|
||||||
|
objectEventEmitter.on('mutation', handleMutation);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
objectEventEmitter.on('mutation', handleMutation);
|
||||||
|
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||||
|
};
|
||||||
|
|
||||||
ObjectServiceProvider.prototype.save = function (object) {
|
ObjectServiceProvider.prototype.save = function (object) {
|
||||||
var key = object.key,
|
var key = object.key,
|
||||||
keyString = utils.makeKeyString(key),
|
keyString = utils.makeKeyString(key),
|
||||||
@ -37,7 +76,7 @@ define([
|
|||||||
|
|
||||||
// Injects new object API as a decorator so that it hijacks all requests.
|
// Injects new object API as a decorator so that it hijacks all requests.
|
||||||
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
||||||
function LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) {
|
function LegacyObjectAPIInterceptor(ROOTS, instantiate, topic, objectService) {
|
||||||
this.getObjects = function (keys) {
|
this.getObjects = function (keys) {
|
||||||
var results = {},
|
var results = {},
|
||||||
promises = keys.map(function (keyString) {
|
promises = keys.map(function (keyString) {
|
||||||
@ -56,7 +95,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
ObjectAPI._supersecretSetFallbackProvider(
|
ObjectAPI._supersecretSetFallbackProvider(
|
||||||
new ObjectServiceProvider(objectService, instantiate)
|
new ObjectServiceProvider(objectService, instantiate, topic)
|
||||||
);
|
);
|
||||||
|
|
||||||
ROOTS.forEach(function (r) {
|
ROOTS.forEach(function (r) {
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
define([
|
define([
|
||||||
'lodash'
|
'lodash',
|
||||||
|
'./objectEventEmitter'
|
||||||
], function (
|
], function (
|
||||||
_
|
_,
|
||||||
|
objectEventEmitter
|
||||||
) {
|
) {
|
||||||
|
var ANY_OBJECT_EVENT = "mutation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods for observing and modifying the state of a domain
|
* The MutableObject wraps a DomainObject and provides getters and
|
||||||
* object.
|
* setters for
|
||||||
*
|
|
||||||
* @param eventEmitter
|
* @param eventEmitter
|
||||||
* @param object
|
* @param object
|
||||||
* @interface MutableObject
|
* @interface MutableObject
|
||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
function MutableObject(eventEmitter, object) {
|
function MutableObject(object) {
|
||||||
this.eventEmitter = eventEmitter;
|
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.unlisteners = [];
|
this.unlisteners = [];
|
||||||
}
|
}
|
||||||
@ -38,8 +40,8 @@ define([
|
|||||||
*/
|
*/
|
||||||
MutableObject.prototype.on = function(path, callback) {
|
MutableObject.prototype.on = function(path, callback) {
|
||||||
var fullPath = qualifiedEventName(this.object, path);
|
var fullPath = qualifiedEventName(this.object, path);
|
||||||
this.eventEmitter.on(fullPath, callback);
|
objectEventEmitter.on(fullPath, callback);
|
||||||
this.unlisteners.push(this.eventEmitter.off.bind(this.eventEmitter, fullPath, callback));
|
this.unlisteners.push(objectEventEmitter.off.bind(objectEventEmitter, fullPath, callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,15 +54,15 @@ define([
|
|||||||
MutableObject.prototype.set = function (path, value) {
|
MutableObject.prototype.set = function (path, value) {
|
||||||
|
|
||||||
_.set(this.object, path, value);
|
_.set(this.object, path, value);
|
||||||
|
_.set(this.object, 'modified', Date.now());
|
||||||
|
|
||||||
//Emit event specific to property
|
//Emit event specific to property
|
||||||
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
objectEventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||||
//Emit wildcare event
|
//Emit wildcare event
|
||||||
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
objectEventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||||
};
|
|
||||||
|
|
||||||
MutableObject.prototype.get = function (path) {
|
//Emit a general "any object" event
|
||||||
return _.get(this.object, path);
|
objectEventEmitter.emit(ANY_OBJECT_EVENT, this.object);
|
||||||
};
|
};
|
||||||
|
|
||||||
return MutableObject;
|
return MutableObject;
|
||||||
|
@ -60,25 +60,25 @@ define(['./MutableObject'], function (MutableObject) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Supports getting and setting of object properties', function () {
|
it('Supports getting and setting of object properties', function () {
|
||||||
expect(mutableObject.get('stringProperty')).toEqual('stringValue');
|
expect(domainObject.stringProperty).toEqual('stringValue');
|
||||||
mutableObject.set('stringProperty', 'updated');
|
mutableObject.set('stringProperty', 'updated');
|
||||||
expect(mutableObject.get('stringProperty')).toEqual('updated');
|
expect(domainObject.stringProperty).toEqual('updated');
|
||||||
|
|
||||||
var newArrayProperty = [];
|
var newArrayProperty = [];
|
||||||
expect(mutableObject.get('arrayProperty')).toEqual(arrayProperty);
|
expect(domainObject.arrayProperty).toEqual(arrayProperty);
|
||||||
mutableObject.set('arrayProperty', newArrayProperty);
|
mutableObject.set('arrayProperty', newArrayProperty);
|
||||||
expect(mutableObject.get('arrayProperty')).toEqual(newArrayProperty);
|
expect(domainObject.arrayProperty).toEqual(newArrayProperty);
|
||||||
|
|
||||||
var newObjectProperty = [];
|
var newObjectProperty = [];
|
||||||
expect(mutableObject.get('objectProperty')).toEqual(objectProperty);
|
expect(domainObject.objectProperty).toEqual(objectProperty);
|
||||||
mutableObject.set('objectProperty', newObjectProperty);
|
mutableObject.set('objectProperty', newObjectProperty);
|
||||||
expect(mutableObject.get('objectProperty')).toEqual(newObjectProperty);
|
expect(domainObject.objectProperty).toEqual(newObjectProperty);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Supports getting and setting of nested properties', function () {
|
it('Supports getting and setting of nested properties', function () {
|
||||||
expect(mutableObject.get('objectProperty')).toEqual(objectProperty);
|
expect(domainObject.objectProperty).toEqual(objectProperty);
|
||||||
expect(mutableObject.get('objectProperty.prop1')).toEqual(objectProperty.prop1);
|
expect(domainObject.objectProperty.prop1).toEqual(objectProperty.prop1);
|
||||||
expect(mutableObject.get('objectProperty.prop3.propA')).toEqual(objectProperty.prop3.propA);
|
expect(domainObject.objectProperty.prop3.propA).toEqual(objectProperty.prop3.propA);
|
||||||
|
|
||||||
mutableObject.set('objectProperty.prop1', 'new-prop-1');
|
mutableObject.set('objectProperty.prop1', 'new-prop-1');
|
||||||
expect(domainObject.objectProperty.prop1).toEqual('new-prop-1');
|
expect(domainObject.objectProperty.prop1).toEqual('new-prop-1');
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
define([
|
define([
|
||||||
'lodash',
|
'lodash',
|
||||||
'EventEmitter',
|
|
||||||
'./object-utils',
|
'./object-utils',
|
||||||
'./MutableObject'
|
'./MutableObject'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
EventEmitter,
|
|
||||||
utils,
|
utils,
|
||||||
MutableObject
|
MutableObject
|
||||||
) {
|
) {
|
||||||
@ -24,8 +22,7 @@ define([
|
|||||||
var Objects = {},
|
var Objects = {},
|
||||||
ROOT_REGISTRY = [],
|
ROOT_REGISTRY = [],
|
||||||
PROVIDER_REGISTRY = {},
|
PROVIDER_REGISTRY = {},
|
||||||
FALLBACK_PROVIDER,
|
FALLBACK_PROVIDER;
|
||||||
eventEmitter = new EventEmitter();
|
|
||||||
|
|
||||||
Objects._supersecretSetFallbackProvider = function (p) {
|
Objects._supersecretSetFallbackProvider = function (p) {
|
||||||
FALLBACK_PROVIDER = p;
|
FALLBACK_PROVIDER = p;
|
||||||
@ -162,7 +159,7 @@ define([
|
|||||||
* @memberof module:openmct.ObjectAPI#
|
* @memberof module:openmct.ObjectAPI#
|
||||||
*/
|
*/
|
||||||
Objects.getMutable = function (object) {
|
Objects.getMutable = function (object) {
|
||||||
return new MutableObject(eventEmitter, object);
|
return new MutableObject(object);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +40,8 @@ define([
|
|||||||
implementation: LegacyObjectAPIInterceptor,
|
implementation: LegacyObjectAPIInterceptor,
|
||||||
depends: [
|
depends: [
|
||||||
"roots[]",
|
"roots[]",
|
||||||
"instantiate"
|
"instantiate",
|
||||||
|
"topic"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
10
src/api/objects/objectEventEmitter.js
Normal file
10
src/api/objects/objectEventEmitter.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
define([
|
||||||
|
"EventEmitter"
|
||||||
|
], function (
|
||||||
|
EventEmitter
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Provides a singleton event bus for sharing between objects.
|
||||||
|
*/
|
||||||
|
return new EventEmitter();
|
||||||
|
});
|
@ -10,6 +10,8 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
|
|||||||
function Dialog(view, title) {
|
function Dialog(view, title) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.showing = false;
|
||||||
|
this.enabledState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,6 +22,10 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
|
|||||||
* @memberof module:openmct.Dialog#
|
* @memberof module:openmct.Dialog#
|
||||||
*/
|
*/
|
||||||
Dialog.prototype.show = function () {
|
Dialog.prototype.show = function () {
|
||||||
|
if (this.showing) {
|
||||||
|
throw new Error("Dialog already showing.");
|
||||||
|
}
|
||||||
|
|
||||||
var $body = $('body');
|
var $body = $('body');
|
||||||
var $dialog = $(dialogTemplate);
|
var $dialog = $(dialogTemplate);
|
||||||
var $contents = $dialog.find('.contents .editor');
|
var $contents = $dialog.find('.contents .editor');
|
||||||
@ -28,31 +34,45 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
|
|||||||
var $ok = $dialog.find('.ok');
|
var $ok = $dialog.find('.ok');
|
||||||
var $cancel = $dialog.find('.cancel');
|
var $cancel = $dialog.find('.cancel');
|
||||||
|
|
||||||
var view = this.view;
|
|
||||||
|
|
||||||
function dismiss() {
|
|
||||||
$dialog.remove();
|
|
||||||
view.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.title) {
|
if (this.title) {
|
||||||
$dialog.find('.title').text(this.title);
|
$dialog.find('.title').text(this.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
$body.append($dialog);
|
$body.append($dialog);
|
||||||
this.view.show($contents[0]);
|
this.view.show($contents[0]);
|
||||||
|
this.$dialog = $dialog;
|
||||||
|
this.$ok = $ok;
|
||||||
|
this.showing = true;
|
||||||
|
|
||||||
|
[$ok, $cancel, $close].forEach(function ($button) {
|
||||||
|
$button.on('click', this.hide.bind(this));
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
$ok.on('click', resolve);
|
$ok.on('click', resolve);
|
||||||
$ok.on('click', dismiss);
|
|
||||||
|
|
||||||
$cancel.on('click', reject);
|
$cancel.on('click', reject);
|
||||||
$cancel.on('click', dismiss);
|
|
||||||
|
|
||||||
$close.on('click', reject);
|
$close.on('click', reject);
|
||||||
$close.on('click', dismiss);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Dialog.prototype.hide = function () {
|
||||||
|
if (!this.showing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$dialog.remove();
|
||||||
|
this.view.destroy();
|
||||||
|
this.showing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialog.prototype.enabled = function (state) {
|
||||||
|
if (state !== undefined) {
|
||||||
|
this.enabledState = state;
|
||||||
|
if (this.showing) {
|
||||||
|
this.$ok.toggleClass('disabled', !state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.enabledState;
|
||||||
|
};
|
||||||
|
|
||||||
return Dialog;
|
return Dialog;
|
||||||
});
|
});
|
@ -1,14 +1,14 @@
|
|||||||
<div class="example-todo">
|
<div class="example-todo">
|
||||||
<div class="example-button-group">
|
<div class="example-button-group">
|
||||||
<a class="example-todo-button-all">All</a>
|
<a class="example-todo-button" data-filter="all">All</a>
|
||||||
<a class="example-todo-button-incomplete">Incomplete</a>
|
<a class="example-todo-button" data-filter="incomplete">Incomplete</a>
|
||||||
<a class="example-todo-button-complete">Complete</a>
|
<a class="example-todo-button" data-filter="complete">Complete</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="example-todo-task-list">
|
<ul class="example-todo-task-list">
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="example-message">
|
<div class="example-no-tasks">
|
||||||
There are no tasks to show.
|
There are no tasks to show.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,21 +25,16 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
function TodoView(domainObject) {
|
function TodoView(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.tasks = domainObject.tasks;
|
||||||
this.mutableObject = undefined;
|
|
||||||
|
|
||||||
this.filterValue = "all";
|
this.filterValue = "all";
|
||||||
this.render = this.render.bind(this);
|
|
||||||
this.objectChanged = this.objectChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoView.prototype.objectChanged = function (object) {
|
this.setTaskStatus = this.setTaskStatus.bind(this);
|
||||||
if (this.mutableObject) {
|
this.selectTask = this.selectTask.bind(this);
|
||||||
this.mutableObject.stopListening();
|
|
||||||
}
|
|
||||||
this.mutableObject = mct.Objects.getMutable(object);
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
this.mutableObject = mct.Objects.getMutable(domainObject);
|
||||||
|
this.mutableObject.on('tasks', this.updateTasks.bind(this));
|
||||||
|
=======
|
||||||
//If anything on object changes, re-render view
|
//If anything on object changes, re-render view
|
||||||
this.mutableObject.on("*", this.objectChanged);
|
this.mutableObject.on("*", this.objectChanged);
|
||||||
};
|
};
|
||||||
@ -48,50 +43,79 @@ define([
|
|||||||
var self = this;
|
var self = this;
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
|
||||||
mct.Objects.get(utils.parseKeyString(self.domainObject.getId())).then(function (object) {
|
self.$els = $(todoTemplate);
|
||||||
self.$els = $(todoTemplate);
|
self.$buttons = {
|
||||||
self.$buttons = {
|
all: self.$els.find('.example-todo-button-all'),
|
||||||
all: self.$els.find('.example-todo-button-all'),
|
incomplete: self.$els.find('.example-todo-button-incomplete'),
|
||||||
incomplete: self.$els.find('.example-todo-button-incomplete'),
|
complete: self.$els.find('.example-todo-button-complete')
|
||||||
complete: self.$els.find('.example-todo-button-complete')
|
};
|
||||||
};
|
|
||||||
|
|
||||||
$(container).empty().append(self.$els);
|
$(container).empty().append(self.$els);
|
||||||
|
>>>>>>> origin/api-tutorials
|
||||||
|
|
||||||
|
this.$el = $(todoTemplate);
|
||||||
|
this.$emptyMessage = this.$el.find('.example-no-tasks');
|
||||||
|
this.$taskList = this.$el.find('.example-todo-task-list');
|
||||||
|
this.$el.on('click', '[data-filter]', this.updateFilter.bind(this));
|
||||||
|
this.$el.on('change', 'li', this.setTaskStatus.bind(this));
|
||||||
|
this.$el.on('click', '.example-task-description', this.selectTask.bind(this));
|
||||||
|
|
||||||
self.initialize();
|
<<<<<<< HEAD
|
||||||
self.objectChanged(object);
|
this.updateSelection = this.updateSelection.bind(this);
|
||||||
|
mct.selection.on('change', this.updateSelection);
|
||||||
|
}
|
||||||
|
|
||||||
mct.selection.on('change', self.render);
|
TodoView.prototype.show = function (container) {
|
||||||
});
|
$(container).empty().append(this.$el);
|
||||||
|
this.render();
|
||||||
|
=======
|
||||||
|
self.initialize();
|
||||||
|
self.objectChanged(this.domainObject);
|
||||||
|
|
||||||
|
mct.selection.on('change', self.render);
|
||||||
|
>>>>>>> origin/api-tutorials
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoView.prototype.destroy = function () {
|
TodoView.prototype.destroy = function () {
|
||||||
if (this.mutableObject) {
|
this.mutableObject.stopListening();
|
||||||
this.mutableObject.stopListening();
|
mct.selection.off('change', this.updateSelection);
|
||||||
}
|
|
||||||
mct.selection.off('change', this.render);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoView.prototype.setFilter = function (value) {
|
TodoView.prototype.updateSelection = function (selection) {
|
||||||
this.filterValue = value;
|
if (selection && selection.length) {
|
||||||
|
this.selection = selection[0].index;
|
||||||
|
} else {
|
||||||
|
this.selection = -1;
|
||||||
|
}
|
||||||
this.render();
|
this.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoView.prototype.initialize = function () {
|
TodoView.prototype.updateTasks = function (tasks) {
|
||||||
Object.keys(this.$buttons).forEach(function (k) {
|
this.tasks = tasks;
|
||||||
this.$buttons[k].on('click', this.setFilter.bind(this, k));
|
this.render();
|
||||||
}, this);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoView.prototype.render = function () {
|
TodoView.prototype.updateFilter = function (e) {
|
||||||
var $els = this.$els;
|
this.filterValue = $(e.target).data('filter');
|
||||||
var mutableObject = this.mutableObject;
|
this.render();
|
||||||
var tasks = mutableObject.get('tasks');
|
};
|
||||||
var $message = $els.find('.example-message');
|
|
||||||
var $list = $els.find('.example-todo-task-list');
|
TodoView.prototype.setTaskStatus = function (e) {
|
||||||
var $buttons = this.$buttons;
|
var $checkbox = $(e.target);
|
||||||
var filters = {
|
var taskIndex = $checkbox.data('taskIndex');
|
||||||
|
var completed = !!$checkbox.prop('checked');
|
||||||
|
this.tasks[taskIndex].completed = completed;
|
||||||
|
this.mutableObject.set('tasks[' + taskIndex + '].checked', completed);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.selectTask = function (e) {
|
||||||
|
var taskIndex = $(e.target).data('taskIndex');
|
||||||
|
mct.selection.clear();
|
||||||
|
mct.selection.select({index: taskIndex});
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.getFilteredTasks = function () {
|
||||||
|
return this.tasks.filter({
|
||||||
all: function () {
|
all: function () {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -101,94 +125,119 @@ define([
|
|||||||
complete: function (task) {
|
complete: function (task) {
|
||||||
return task.completed;
|
return task.completed;
|
||||||
}
|
}
|
||||||
};
|
}[this.filterValue]);
|
||||||
var filterValue = this.filterValue;
|
};
|
||||||
var selected = mct.selection.selected();
|
|
||||||
|
|
||||||
Object.keys($buttons).forEach(function (k) {
|
TodoView.prototype.render = function () {
|
||||||
$buttons[k].toggleClass('selected', filterValue === k);
|
var filteredTasks = this.getFilteredTasks();
|
||||||
});
|
this.$emptyMessage.toggle(filteredTasks.length === 0);
|
||||||
tasks = tasks.filter(filters[filterValue]);
|
this.$taskList.empty();
|
||||||
|
filteredTasks
|
||||||
|
.forEach(function (task) {
|
||||||
|
var $taskEl = $(taskTemplate),
|
||||||
|
taskIndex = this.tasks.indexOf(task);
|
||||||
|
$taskEl.find('.example-task-checked')
|
||||||
|
.prop('checked', task.completed)
|
||||||
|
.attr('data-task-index', taskIndex);
|
||||||
|
$taskEl.find('.example-task-description')
|
||||||
|
.text(task.description)
|
||||||
|
.toggleClass('selected', taskIndex === this.selection)
|
||||||
|
.attr('data-task-index', taskIndex);
|
||||||
|
|
||||||
$list.empty();
|
this.$taskList.append($taskEl);
|
||||||
tasks.forEach(function (task, index) {
|
}, this);
|
||||||
var $taskEls = $(taskTemplate);
|
|
||||||
var $checkbox = $taskEls.find('.example-task-checked');
|
|
||||||
var $desc = $taskEls.find('.example-task-description');
|
|
||||||
$checkbox.prop('checked', task.completed);
|
|
||||||
$desc.text(task.description);
|
|
||||||
|
|
||||||
$checkbox.on('change', function () {
|
|
||||||
var checked = !!$checkbox.prop('checked');
|
|
||||||
mutableObject.set("tasks." + index + ".completed", checked);
|
|
||||||
});
|
|
||||||
|
|
||||||
$desc.on('click', function () {
|
|
||||||
mct.selection.clear();
|
|
||||||
mct.selection.select({ index: index });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selected.length > 0 && selected[0].index === index) {
|
|
||||||
$desc.addClass('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
$list.append($taskEls);
|
|
||||||
});
|
|
||||||
|
|
||||||
$message.toggle(tasks.length < 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function TodoToolbarView(domainObject) {
|
function TodoToolbarView(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.tasks = domainObject.tasks;
|
||||||
this.mutableObject = undefined;
|
this.mutableObject = mct.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
this.handleSelectionChange = this.handleSelectionChange.bind(this);
|
this.handleSelectionChange = this.handleSelectionChange.bind(this);
|
||||||
|
|
||||||
|
this.mutableObject.on('tasks', this.updateTasks.bind(this));
|
||||||
|
mct.selection.on('change', this.handleSelectionChange);
|
||||||
|
this.$el = $(toolbarTemplate);
|
||||||
|
this.$remove = this.$el.find('.example-remove');
|
||||||
|
this.$el.on('click', '.example-add', this.addTask.bind(this));
|
||||||
|
this.$el.on('click', '.example-remove', this.removeTask.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.updateTasks = function (tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.addTask = function () {
|
||||||
|
var $dialog = $(dialogTemplate),
|
||||||
|
view = {
|
||||||
|
show: function (container) {
|
||||||
|
$(container).append($dialog);
|
||||||
|
},
|
||||||
|
destroy: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
mct.dialog(view, "Add a Task").then(function () {
|
||||||
|
var description = $dialog.find('input').val();
|
||||||
|
this.tasks.push({description: description});
|
||||||
|
this.mutableObject.set('tasks', this.tasks);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.removeTask = function () {
|
||||||
|
if (this.selection >= 0) {
|
||||||
|
this.tasks.splice(this.selection, 1);
|
||||||
|
this.mutableObject.set('tasks', this.tasks);
|
||||||
|
mct.selection.clear();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TodoToolbarView.prototype.show = function (container) {
|
TodoToolbarView.prototype.show = function (container) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
this.$els = $(toolbarTemplate);
|
||||||
|
this.render();
|
||||||
|
$(container).append(this.$els);
|
||||||
|
};
|
||||||
|
|
||||||
mct.Objects.get(utils.parseKeyString(this.domainObject.getId())).then(function (wrappedObject){
|
TodoToolbarView.prototype.render = function () {
|
||||||
|
var self = this;
|
||||||
|
var $els = this.$els;
|
||||||
|
|
||||||
self.mutableObject = mct.Objects.getMutable(wrappedObject);
|
self.mutableObject = mct.Objects.getMutable(this.domainObject);
|
||||||
|
|
||||||
var $els = $(toolbarTemplate);
|
var $add = $els.find('a.example-add');
|
||||||
var $add = $els.find('a.example-add');
|
var $remove = $els.find('a.example-remove');
|
||||||
var $remove = $els.find('a.example-remove');
|
|
||||||
|
|
||||||
$(container).append($els);
|
$add.on('click', function () {
|
||||||
|
var $dialog = $(dialogTemplate),
|
||||||
|
view = {
|
||||||
|
show: function (container) {
|
||||||
|
$(container).append($dialog);
|
||||||
|
},
|
||||||
|
destroy: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
$add.on('click', function () {
|
new mct.Dialog(view, "Add a Task").show().then(function () {
|
||||||
var $dialog = $(dialogTemplate),
|
var description = $dialog.find('input').val();
|
||||||
view = {
|
var tasks = self.mutableObject.get('tasks');
|
||||||
show: function (container) {
|
tasks.push({ description: description });
|
||||||
$(container).append($dialog);
|
self.mutableObject.set('tasks', tasks);
|
||||||
},
|
|
||||||
destroy: function () {}
|
|
||||||
};
|
|
||||||
|
|
||||||
mct.dialog(view, "Add a Task").then(function () {
|
|
||||||
var description = $dialog.find('input').val();
|
|
||||||
var tasks = self.mutableObject.get('tasks');
|
|
||||||
tasks.push({ description: description });
|
|
||||||
self.mutableObject.set('tasks', tasks);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
$remove.on('click', function () {
|
|
||||||
var index = mct.selection.selected()[0].index;
|
|
||||||
if (index !== undefined) {
|
|
||||||
var tasks = self.mutableObject.get('tasks').filter(function (t, i) {
|
|
||||||
return i !== index;
|
|
||||||
});
|
|
||||||
self.mutableObject.set("tasks", tasks);
|
|
||||||
self.mutableObject.set("selected", undefined);
|
|
||||||
mct.selection.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.$remove = $remove;
|
|
||||||
self.handleSelectionChange();
|
|
||||||
mct.selection.on('change', self.handleSelectionChange);
|
|
||||||
});
|
});
|
||||||
|
$remove.on('click', function () {
|
||||||
|
var index = mct.selection.selected()[0].index;
|
||||||
|
if (index !== undefined) {
|
||||||
|
var tasks = self.mutableObject.get('tasks').filter(function (t, i) {
|
||||||
|
return i !== index;
|
||||||
|
});
|
||||||
|
self.mutableObject.set("tasks", tasks);
|
||||||
|
self.mutableObject.set("selected", undefined);
|
||||||
|
mct.selection.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.$remove = $remove;
|
||||||
|
self.handleSelectionChange();
|
||||||
|
mct.selection.on('change', self.handleSelectionChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoToolbarView.prototype.handleSelectionChange = function () {
|
TodoToolbarView.prototype.handleSelectionChange = function () {
|
||||||
@ -196,14 +245,12 @@ define([
|
|||||||
if (this.$remove) {
|
if (this.$remove) {
|
||||||
this.$remove.toggle(selected.length > 0);
|
this.$remove.toggle(selected.length > 0);
|
||||||
}
|
}
|
||||||
|
this.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
TodoToolbarView.prototype.destroy = function () {
|
TodoToolbarView.prototype.destroy = function () {
|
||||||
mct.selection.off('change', this.handleSelectionChange);
|
mct.selection.off('change', this.handleSelectionChange);
|
||||||
this.$remove = undefined;
|
this.mutableObject.stopListening();
|
||||||
if (this.mutableObject) {
|
|
||||||
this.mutableObject.stopListening();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mct.type('example.todo', todoType);
|
mct.type('example.todo', todoType);
|
||||||
|
Loading…
Reference in New Issue
Block a user