Compare commits
32 Commits
api-type-d
...
api-toolba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ff2b507e3 | ||
|
|
dde7de01e2 | ||
|
|
b55668426d | ||
|
|
5b656faa9d | ||
|
|
8d2c489fa9 | ||
|
|
4366b0870d | ||
|
|
47a543beb7 | ||
|
|
06f87c1472 | ||
|
|
c9c41cdcc8 | ||
|
|
14a56ea17e | ||
|
|
b2e7db71cc | ||
|
|
d51e6bfd92 | ||
|
|
d475d767d5 | ||
|
|
a63e053399 | ||
|
|
370b515c23 | ||
|
|
4a50f325cb | ||
|
|
dbe6a4efc1 | ||
|
|
13920d8802 | ||
|
|
b6a8c514aa | ||
|
|
e4a4704baa | ||
|
|
be0029e59a | ||
|
|
9cb273ef0a | ||
|
|
eec9b1cf4c | ||
|
|
1f96e84542 | ||
|
|
c289a27305 | ||
|
|
c944080790 | ||
|
|
96316de6e4 | ||
|
|
2240a87ddc | ||
|
|
d891affe48 | ||
|
|
21a618d1ce | ||
|
|
5de7a96ccc | ||
|
|
09a833f524 |
@@ -19,6 +19,7 @@
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter3": "^1.2.0"
|
||||
"eventemitter3": "^1.2.0",
|
||||
"lodash": "3.10.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,13 @@
|
||||
<script type="text/javascript">
|
||||
require(['main'], function (mct) {
|
||||
require([
|
||||
'./tutorials/grootprovider/groots',
|
||||
'./tutorials/todo/todo',
|
||||
'./tutorials/todo/bundle',
|
||||
'./example/imagery/bundle',
|
||||
'./example/eventGenerator/bundle',
|
||||
'./example/generator/bundle'
|
||||
], function (todoPlugin) {
|
||||
'./example/generator/bundle',
|
||||
], function (grootify, todoPlugin) {
|
||||
grootify(mct);
|
||||
todoPlugin(mct);
|
||||
mct.start();
|
||||
})
|
||||
@@ -44,6 +45,7 @@
|
||||
</script>
|
||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/openmct.css">
|
||||
<link rel="stylesheet" href="tutorials/todo/todo.css">
|
||||
<link rel="icon" type="image/png" href="platform/commonUI/general/res/images/favicons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="platform/commonUI/general/res/images/favicons/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="platform/commonUI/general/res/images/favicons/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
3
main.js
3
main.js
@@ -35,7 +35,8 @@ requirejs.config({
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
"zepto": "bower_components/zepto/zepto.min"
|
||||
"zepto": "bower_components/zepto/zepto.min",
|
||||
"lodash": "bower_components/lodash/lodash"
|
||||
},
|
||||
"shim": {
|
||||
"angular": {
|
||||
|
||||
102
src/MCT.js
102
src/MCT.js
@@ -1,8 +1,21 @@
|
||||
define([
|
||||
'EventEmitter',
|
||||
'legacyRegistry',
|
||||
'./api/api'
|
||||
], function (EventEmitter, legacyRegistry, api) {
|
||||
'uuid',
|
||||
'./api/api',
|
||||
'text!./adapter/templates/edit-object-replacement.html',
|
||||
'./ui/Dialog',
|
||||
'./api/events/Events',
|
||||
'./api/objects/bundle'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
uuid,
|
||||
api,
|
||||
editObjectTemplate,
|
||||
Dialog,
|
||||
Events
|
||||
) {
|
||||
function MCT() {
|
||||
EventEmitter.call(this);
|
||||
this.legacyBundle = { extensions: {} };
|
||||
@@ -15,36 +28,68 @@ define([
|
||||
});
|
||||
MCT.prototype.MCT = MCT;
|
||||
|
||||
MCT.prototype.type = function (key, type) {
|
||||
var legacyDef = type.toLegacyDefinition();
|
||||
legacyDef.key = key;
|
||||
this.legacyBundle.extensions.types =
|
||||
this.legacyBundle.extensions.types || [];
|
||||
this.legacyBundle.extensions.types.push(legacyDef);
|
||||
MCT.prototype.legacyExtension = function (category, extension) {
|
||||
this.legacyBundle.extensions[category] =
|
||||
this.legacyBundle.extensions[category] || [];
|
||||
this.legacyBundle.extensions[category].push(extension);
|
||||
};
|
||||
|
||||
var viewFactory = type.view(this.regions.main);
|
||||
if (viewFactory) {
|
||||
var viewKey = key + "." + this.regions.main;
|
||||
MCT.prototype.view = function (region, factory) {
|
||||
var viewKey = region + uuid();
|
||||
var adaptedViewKey = "adapted-view-" + region;
|
||||
|
||||
this.legacyBundle.extensions.views =
|
||||
this.legacyBundle.extensions.views || [];
|
||||
this.legacyBundle.extensions.views.push({
|
||||
this.legacyExtension(
|
||||
region === this.regions.main ? 'views' : 'representations',
|
||||
{
|
||||
name: "A view",
|
||||
key: "adapted-view",
|
||||
template: '<mct-view key="\'' +
|
||||
key: adaptedViewKey,
|
||||
editable: true,
|
||||
template: '<mct-view region="\'' +
|
||||
region +
|
||||
'\'" ' +
|
||||
'key="\'' +
|
||||
viewKey +
|
||||
'\'" ' +
|
||||
'mct-object="domainObject">' +
|
||||
'</mct-view>'
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.legacyBundle.extensions.newViews =
|
||||
this.legacyBundle.extensions.newViews || [];
|
||||
this.legacyBundle.extensions.newViews.push({
|
||||
factory: viewFactory,
|
||||
key: viewKey
|
||||
});
|
||||
}
|
||||
this.legacyExtension('policies', {
|
||||
category: "view",
|
||||
implementation: function Policy() {
|
||||
this.allow = function (view, domainObject) {
|
||||
if (view.key === adaptedViewKey) {
|
||||
return !!factory(domainObject);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.legacyExtension('newViews', {
|
||||
factory: factory,
|
||||
region: region,
|
||||
key: viewKey
|
||||
});
|
||||
};
|
||||
|
||||
MCT.prototype.type = function (key, type) {
|
||||
var legacyDef = type.toLegacyDefinition();
|
||||
legacyDef.key = key;
|
||||
type.key = key;
|
||||
|
||||
this.legacyExtension('types', legacyDef);
|
||||
this.legacyExtension('representations', {
|
||||
key: "edit-object",
|
||||
priority: "preferred",
|
||||
template: editObjectTemplate,
|
||||
type: key
|
||||
});
|
||||
};
|
||||
|
||||
MCT.prototype.dialog = function (view, title) {
|
||||
return new Dialog(view, title).show();
|
||||
};
|
||||
|
||||
MCT.prototype.start = function () {
|
||||
@@ -53,9 +98,12 @@ define([
|
||||
};
|
||||
|
||||
MCT.prototype.regions = {
|
||||
main: "MAIN"
|
||||
main: "MAIN",
|
||||
toolbar: "TOOLBAR"
|
||||
};
|
||||
|
||||
MCT.prototype.events = new Events();
|
||||
|
||||
MCT.prototype.verbs = {
|
||||
mutate: function (domainObject, mutator) {
|
||||
return domainObject.useCapability('mutation', mutator)
|
||||
@@ -63,6 +111,10 @@ define([
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
return persistence.persist();
|
||||
});
|
||||
},
|
||||
observe: function (domainObject, callback) {
|
||||
var mutation = domainObject.getCapability('mutation');
|
||||
return mutation.listen(callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,27 +3,22 @@ define(['angular'], function (angular) {
|
||||
var factories = {};
|
||||
|
||||
newViews.forEach(function (newView) {
|
||||
factories[newView.key] = newView.factory;
|
||||
factories[newView.region] = factories[newView.region] || {};
|
||||
factories[newView.region][newView.key] = newView.factory;
|
||||
});
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function (scope, element, attrs) {
|
||||
var key = undefined;
|
||||
var mctObject = undefined;
|
||||
var key, mctObject, region;
|
||||
|
||||
function maybeShow() {
|
||||
if (!factories[key]) {
|
||||
return;
|
||||
}
|
||||
if (!mctObject) {
|
||||
if (!factories[region] || !factories[region][key] || !mctObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = factories[key](mctObject);
|
||||
var elements = view.elements();
|
||||
element.empty();
|
||||
element.append(elements);
|
||||
var view = factories[region][key](mctObject);
|
||||
view.show(element[0]);
|
||||
}
|
||||
|
||||
function setKey(k) {
|
||||
@@ -36,12 +31,19 @@ define(['angular'], function (angular) {
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
function setRegion(r) {
|
||||
region = r;
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
scope.$watch('key', setKey);
|
||||
scope.$watch('region', setRegion);
|
||||
scope.$watch('mctObject', setObject);
|
||||
|
||||
},
|
||||
scope: {
|
||||
key: "=",
|
||||
region: "=",
|
||||
mctObject: "="
|
||||
}
|
||||
};
|
||||
|
||||
46
src/adapter/templates/edit-object-replacement.html
Normal file
46
src/adapter/templates/edit-object-replacement.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
|
||||
<div mct-before-unload="EditObjectController.getUnloadWarning()"
|
||||
class="holder flex-elem l-flex-row object-browse-bar ">
|
||||
<div class="items-select left flex-elem l-flex-row grows">
|
||||
<mct-representation key="'back-arrow'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem l-back"></mct-representation>
|
||||
<mct-representation key="'object-header'"
|
||||
mct-object="domainObject"
|
||||
class="l-flex-row flex-elem grows object-header">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<mct-representation key="'switcher'"
|
||||
mct-object="domainObject"
|
||||
ng-model="representation">
|
||||
</mct-representation>
|
||||
<!-- Temporarily, on mobile, the action buttons are hidden-->
|
||||
<mct-representation key="'action-group'"
|
||||
mct-object="domainObject"
|
||||
parameters="{ category: 'view-control' }"
|
||||
class="mobile-hide">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-representation key="'adapted-view-TOOLBAR'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem grows">
|
||||
</mct-representation>
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
</mct-representation>
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
</mct-representation>
|
||||
</div><!--/ l-object-wrapper-inner -->
|
||||
</div>
|
||||
</div>
|
||||
183
src/api/TimeConductor.js
Normal file
183
src/api/TimeConductor.js
Normal file
@@ -0,0 +1,183 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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(['EventEmitter'], function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* The public API for setting and querying time conductor state. The
|
||||
* time conductor is the means by which the temporal bounds of a view
|
||||
* are controlled. Time-sensitive views will typically respond to
|
||||
* changes to bounds or other properties of the time conductor and
|
||||
* update the data displayed based on the time conductor state.
|
||||
*
|
||||
* The TimeConductor extends the EventEmitter class. A number of events are
|
||||
* fired when properties of the time conductor change, which are
|
||||
* documented below.
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductor() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
//The Time System
|
||||
this.system = undefined;
|
||||
//The Time Of Interest
|
||||
this.toi = undefined;
|
||||
|
||||
this.boundsVal = {
|
||||
start: undefined,
|
||||
end: undefined
|
||||
};
|
||||
|
||||
//Default to fixed mode
|
||||
this.followMode = false;
|
||||
}
|
||||
|
||||
TimeConductor.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Validate the given bounds. This can be used for pre-validation of
|
||||
* bounds, for example by views validating user inputs.
|
||||
* @param bounds The start and end time of the conductor.
|
||||
* @returns {string | true} A validation error, or true if valid
|
||||
*/
|
||||
TimeConductor.prototype.validateBounds = function (bounds) {
|
||||
if ((bounds.start === undefined) ||
|
||||
(bounds.end === undefined) ||
|
||||
isNaN(bounds.start) ||
|
||||
isNaN(bounds.end)
|
||||
) {
|
||||
return "Start and end must be specified as integer values";
|
||||
} else if (bounds.start > bounds.end) {
|
||||
return "Specified start date exceeds end bound";
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function throwOnError(validationResult) {
|
||||
if (validationResult !== true) {
|
||||
throw new Error(validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the follow mode of the time conductor. In follow mode the
|
||||
* time conductor ticks, regularly updating the bounds from a timing
|
||||
* source appropriate to the selected time system and mode of the time
|
||||
* conductor.
|
||||
* @fires TimeConductor#follow
|
||||
* @param {boolean} followMode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
TimeConductor.prototype.follow = function (followMode) {
|
||||
if (arguments.length > 0) {
|
||||
this.followMode = followMode;
|
||||
/**
|
||||
* @event TimeConductor#follow The TimeConductor has toggled
|
||||
* into or out of follow mode.
|
||||
* @property {boolean} followMode true if follow mode is
|
||||
* enabled, otherwise false.
|
||||
*/
|
||||
this.emit('follow', this.followMode);
|
||||
}
|
||||
return this.followMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} TimeConductorBounds
|
||||
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
|
||||
* @property {number} end The end time displayed by the time conductor in ms since epoch.
|
||||
*/
|
||||
/**
|
||||
* Get or set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires TimeConductor#bounds
|
||||
* @returns {TimeConductorBounds}
|
||||
*/
|
||||
TimeConductor.prototype.bounds = function (newBounds) {
|
||||
if (arguments.length > 0) {
|
||||
throwOnError(this.validateBounds(newBounds));
|
||||
this.boundsVal = newBounds;
|
||||
/**
|
||||
* @event TimeConductor#bounds The start time, end time, or
|
||||
* both have been updated
|
||||
* @property {TimeConductorBounds} bounds
|
||||
*/
|
||||
this.emit('bounds', this.boundsVal);
|
||||
}
|
||||
return this.boundsVal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the time system of the TimeConductor. Time systems determine
|
||||
* units, epoch, and other aspects of time representation. When changing
|
||||
* the time system in use, new valid bounds must also be provided.
|
||||
* @param {TimeSystem} newTimeSystem
|
||||
* @param {TimeConductorBounds} bounds
|
||||
* @fires TimeConductor#timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
*/
|
||||
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
|
||||
if (arguments.length >= 2) {
|
||||
this.system = newTimeSystem;
|
||||
/**
|
||||
* @event TimeConductor#timeSystem The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit('timeSystem', this.system);
|
||||
// Do something with bounds here. Try and convert between
|
||||
// time systems? Or just set defaults when time system changes?
|
||||
// eg.
|
||||
this.bounds(bounds);
|
||||
} else if (arguments.length === 1) {
|
||||
throw new Error('Must set bounds when changing time system');
|
||||
}
|
||||
return this.system;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the Time of Interest. The Time of Interest is the temporal
|
||||
* focus of the current view. It can be manipulated by the user from the
|
||||
* time conductor or from other views.
|
||||
* @fires TimeConductor#timeOfInterest
|
||||
* @param newTOI
|
||||
* @returns {number} the current time of interest
|
||||
*/
|
||||
TimeConductor.prototype.timeOfInterest = function (newTOI) {
|
||||
if (arguments.length > 0) {
|
||||
this.toi = newTOI;
|
||||
/**
|
||||
* @event TimeConductor#timeOfInterest The Time of Interest has moved.
|
||||
* @property {number} Current time of interest
|
||||
*/
|
||||
this.emit('timeOfInterest', this.toi);
|
||||
}
|
||||
return this.toi;
|
||||
};
|
||||
|
||||
return TimeConductor;
|
||||
});
|
||||
110
src/api/TimeConductorSpec.js
Normal file
110
src/api/TimeConductorSpec.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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(['./TimeConductor'], function (TimeConductor) {
|
||||
describe("The Time Conductor", function () {
|
||||
var tc,
|
||||
timeSystem,
|
||||
bounds,
|
||||
eventListener,
|
||||
toi,
|
||||
follow;
|
||||
|
||||
beforeEach(function () {
|
||||
tc = new TimeConductor();
|
||||
timeSystem = {};
|
||||
bounds = {start: 0, end: 0};
|
||||
eventListener = jasmine.createSpy("eventListener");
|
||||
toi = 111;
|
||||
follow = true;
|
||||
});
|
||||
|
||||
it("Supports setting and querying of time of interest and and follow mode", function () {
|
||||
expect(tc.timeOfInterest()).not.toBe(toi);
|
||||
tc.timeOfInterest(toi);
|
||||
expect(tc.timeOfInterest()).toBe(toi);
|
||||
|
||||
expect(tc.follow()).not.toBe(follow);
|
||||
tc.follow(follow);
|
||||
expect(tc.follow()).toBe(follow);
|
||||
});
|
||||
|
||||
it("Allows setting of valid bounds", function () {
|
||||
bounds = {start: 0, end: 1};
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
|
||||
expect(tc.bounds()).toBe(bounds);
|
||||
});
|
||||
|
||||
it("Disallows setting of invalid bounds", function () {
|
||||
bounds = {start: 1, end: 0};
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
|
||||
bounds = {start: 1};
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
});
|
||||
|
||||
it("Allows setting of time system with bounds", function () {
|
||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
|
||||
expect(tc.timeSystem()).toBe(timeSystem);
|
||||
});
|
||||
|
||||
it("Disallows setting of time system without bounds", function () {
|
||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
|
||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||
});
|
||||
|
||||
it("Emits an event when time system changes", function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
tc.on("timeSystem", eventListener);
|
||||
tc.timeSystem(timeSystem, bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(timeSystem);
|
||||
});
|
||||
|
||||
it("Emits an event when time of interest changes", function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
tc.on("timeOfInterest", eventListener);
|
||||
tc.timeOfInterest(toi);
|
||||
expect(eventListener).toHaveBeenCalledWith(toi);
|
||||
});
|
||||
|
||||
it("Emits an event when bounds change", function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
tc.on("bounds", eventListener);
|
||||
tc.bounds(bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(bounds);
|
||||
});
|
||||
|
||||
it("Emits an event when follow mode changes", function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
tc.on("follow", eventListener);
|
||||
tc.follow(follow);
|
||||
expect(eventListener).toHaveBeenCalledWith(follow);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -15,14 +15,16 @@ define(function () {
|
||||
*/
|
||||
function Type(definition) {
|
||||
this.definition = definition;
|
||||
this.views = {};
|
||||
}
|
||||
|
||||
Type.prototype.view = function (region, factory) {
|
||||
if (arguments.length > 1) {
|
||||
this.views[region] = factory;
|
||||
}
|
||||
return this.views[region];
|
||||
/**
|
||||
* Check if a domain object is an instance of this type.
|
||||
* @param domainObject
|
||||
* @returns {boolean} true if the domain object is of this type
|
||||
*/
|
||||
Type.prototype.check = function (domainObject) {
|
||||
// Depends on assignment from MCT.
|
||||
return domainObject.getModel().type === this.key;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
define(function () {
|
||||
function View() {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
View.prototype = Object.create(EventEmitter.prototype);
|
||||
/**
|
||||
* Show this view in the specified container. If this view is already
|
||||
* showing elsewhere, it will be removed from that location.
|
||||
*
|
||||
* @param {HTMLElement} container the element to populate
|
||||
*/
|
||||
View.prototype.show = function (container) {
|
||||
};
|
||||
|
||||
['elements', 'model'].forEach(function (method) {
|
||||
View.prototype[method] = function (value) {
|
||||
this.viewState =
|
||||
this.viewState || { elements: [], model: undefined };
|
||||
if (arguments.length > 0) {
|
||||
this.viewState[method] = value;
|
||||
this.emit(method, value);
|
||||
}
|
||||
return this.viewState[method];
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Release any resources associated with this view.
|
||||
*
|
||||
* Subclasses should override this method to release any resources
|
||||
* they obtained during a `show` call.
|
||||
*/
|
||||
View.prototype.destroy = function () {
|
||||
};
|
||||
|
||||
return View;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
define([
|
||||
'./Type',
|
||||
'./View'
|
||||
'./TimeConductor',
|
||||
'./View',
|
||||
'./objects/ObjectAPI'
|
||||
], function (
|
||||
Type,
|
||||
View
|
||||
TimeConductor,
|
||||
View,
|
||||
ObjectAPI
|
||||
) {
|
||||
return {
|
||||
Type: Type,
|
||||
View: View
|
||||
TimeConductor: new TimeConductor(),
|
||||
View: View,
|
||||
Objects: ObjectAPI
|
||||
};
|
||||
});
|
||||
|
||||
39
src/api/events/EventDecorator.js
Normal file
39
src/api/events/EventDecorator.js
Normal file
@@ -0,0 +1,39 @@
|
||||
define([],
|
||||
function () {
|
||||
function EventDecorator(mct, domainObject) {
|
||||
if (domainObject._proxied){
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
Object.defineProperty(domainObject, "_proxied", {value: true, writable: false, enumerable: false, readable: true});
|
||||
|
||||
var handler = function (path){
|
||||
return {
|
||||
'set': function (target, name, value) {
|
||||
target[name] = value;
|
||||
mct.events.mutation(domainObject).emit([path, name].join("."), value);
|
||||
mct.events.mutation(domainObject).emit("any", value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
function decorateObject(object, property, path) {
|
||||
// Decorate object with a proxy
|
||||
var value = object[property] = new Proxy(object[property], handler(path));
|
||||
|
||||
// Enumerate properties and decorate any sub objects with
|
||||
// proxies
|
||||
Object.keys(object[property]).filter(function (key){
|
||||
return typeof(value[key]) === "object";
|
||||
}).forEach(function (key) {
|
||||
decorateObject(object[property], key, [path, key].join("."));
|
||||
});
|
||||
}
|
||||
|
||||
decorateObject(domainObject, "model", "model");
|
||||
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
return EventDecorator;
|
||||
});
|
||||
31
src/api/events/Events.js
Normal file
31
src/api/events/Events.js
Normal file
@@ -0,0 +1,31 @@
|
||||
define(
|
||||
[
|
||||
"EventEmitter"
|
||||
],
|
||||
function (EventEmitter) {
|
||||
function Events() {
|
||||
this.eventEmitter = new EventEmitter();
|
||||
}
|
||||
|
||||
Events.prototype.mutation = function (domainObject) {
|
||||
var eventEmitter = this.eventEmitter;
|
||||
|
||||
function qualifiedEventName(eventName) {
|
||||
return ['mutation', domainObject.getId(), eventName].join(':');
|
||||
}
|
||||
|
||||
return {
|
||||
on: function(eventName, callback) {
|
||||
return eventEmitter.on(qualifiedEventName(eventName), callback);
|
||||
},
|
||||
emit: function(eventName) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
args.unshift(qualifiedEventName(eventName));
|
||||
|
||||
return eventEmitter.emit.apply(eventEmitter, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Events;
|
||||
});
|
||||
87
src/api/events/README.md
Normal file
87
src/api/events/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
* Can't use defineProperty. Will not trigger on properties added to model.
|
||||
Can only use Proxy or setter
|
||||
|
||||
/**
|
||||
* Mutate function on domainObject
|
||||
*/
|
||||
|
||||
domainObject.mutate("property", function (currentVal) {
|
||||
return "12";
|
||||
});
|
||||
|
||||
//No way of mutating multiple properties at once.
|
||||
// Removes need to calculate deltas to trigger
|
||||
/*domainObject.mutate(function (domainObject) {
|
||||
object.foo = "bar";
|
||||
return object;
|
||||
});*/
|
||||
|
||||
//Mutating an array property
|
||||
domainObject.mutate("arrayProperty", function (array) {
|
||||
array.push("foo");
|
||||
array[2] = "bar";
|
||||
//mutate function can calculate delta to decide
|
||||
// whether to trigger update
|
||||
return object;
|
||||
});
|
||||
|
||||
domainObject.mutate("property.nestedProperty", function(nestedValue){
|
||||
return "someNewNestedValue"
|
||||
});
|
||||
|
||||
/*
|
||||
Problem here - no way to prevent arbitrary mutation
|
||||
of the object from within the mutate function.
|
||||
*/
|
||||
domainObject.mutate("property.nestedProperty", function(nestedObject){
|
||||
nestedObject.someprop = "someVal";
|
||||
nestedObject.something = {};
|
||||
nestedObject.something.else = "Hi";
|
||||
return nestedObject;
|
||||
});
|
||||
|
||||
/**
|
||||
* Hybrid setter / mutator approach
|
||||
*/
|
||||
domainObject.mutate("property", "value");
|
||||
domainObject.mutate("property.nestedProperty", "value");
|
||||
domainObject.mutate("arrayProperty", function(array){
|
||||
array.push("foo");
|
||||
array[1] = "bar";
|
||||
});
|
||||
// OR
|
||||
// Mutate the array outside and use the same approach
|
||||
domainObject.mutate("arrayProperty", arrayVal);
|
||||
/**
|
||||
* Setter function on domainObject. I think this is
|
||||
* out because of the difficulty in support Arrays
|
||||
*/
|
||||
//Setters are out because of arrays I think
|
||||
domainObject.set("property", value);
|
||||
domainObject.set("property.nestedProperty", value);
|
||||
|
||||
domainObject.on("configuration.layout");
|
||||
domainObject.on("configuration.layout.positions");
|
||||
|
||||
// The following would trigger configuration.views
|
||||
// watcher, but not configuration.views.view1
|
||||
domainObject.set("configuration.layout", {"positions": [{"123-1234-1232-123": [100, 200]}]});
|
||||
|
||||
// Don't think this will work, it's an unnatural way
|
||||
// of using js.
|
||||
|
||||
//Using a setter for everything requires unnatural
|
||||
// use of javascript
|
||||
domainObject.set("configuration", {})
|
||||
.set("layout", {})
|
||||
.set("positions", [{"123-1234-1232-123": [100, 200]}]);
|
||||
|
||||
// VanillaJS
|
||||
domainObject.model.configuration = {};
|
||||
domainObject.model.configuration.layout = {};
|
||||
domainObject.model.configuration.positions = [{"123-1234-1232-123": [100, 200]}];
|
||||
|
||||
// Layout does exist
|
||||
domainObject.set("configuration")
|
||||
.set("layout")
|
||||
.set("positions", [{"123-1234-1232-123": [100, 200]}]);
|
||||
70
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
70
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
@@ -0,0 +1,70 @@
|
||||
define([
|
||||
'./object-utils',
|
||||
'./ObjectAPI'
|
||||
], function (
|
||||
utils,
|
||||
ObjectAPI
|
||||
) {
|
||||
function ObjectServiceProvider(objectService, instantiate) {
|
||||
this.objectService = objectService;
|
||||
this.instantiate = instantiate;
|
||||
}
|
||||
|
||||
ObjectServiceProvider.prototype.save = function (object) {
|
||||
var key = object.key,
|
||||
keyString = utils.makeKeyString(key),
|
||||
newObject = this.instantiate(utils.toOldFormat(object), keyString);
|
||||
|
||||
return object.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return utils.toNewFormat(object, key);
|
||||
});
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.delete = function (object) {
|
||||
// TODO!
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.get = function (key) {
|
||||
var keyString = utils.makeKeyString(key);
|
||||
return this.objectService.getObjects([keyString])
|
||||
.then(function (results) {
|
||||
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
|
||||
return utils.toNewFormat(model, key);
|
||||
});
|
||||
};
|
||||
|
||||
// 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.
|
||||
function LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) {
|
||||
this.getObjects = function (keys) {
|
||||
var results = {},
|
||||
promises = keys.map(function (keyString) {
|
||||
var key = utils.parseKeyString(keyString);
|
||||
return ObjectAPI.get(key)
|
||||
.then(function (object) {
|
||||
object = utils.toOldFormat(object)
|
||||
results[keyString] = instantiate(object, keyString);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(function () {
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
ObjectAPI._supersecretSetFallbackProvider(
|
||||
new ObjectServiceProvider(objectService, instantiate)
|
||||
);
|
||||
|
||||
ROOTS.forEach(function (r) {
|
||||
ObjectAPI.addRoot(utils.parseKeyString(r.id));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return LegacyObjectAPIInterceptor;
|
||||
});
|
||||
92
src/api/objects/ObjectAPI.js
Normal file
92
src/api/objects/ObjectAPI.js
Normal file
@@ -0,0 +1,92 @@
|
||||
define([
|
||||
'lodash',
|
||||
'./object-utils'
|
||||
], function (
|
||||
_,
|
||||
utils
|
||||
) {
|
||||
|
||||
/**
|
||||
Object API. Intercepts the existing object API while also exposing
|
||||
A new Object API.
|
||||
|
||||
MCT.objects.get('mine')
|
||||
.then(function (root) {
|
||||
console.log(root);
|
||||
MCT.objects.getComposition(root)
|
||||
.then(function (composition) {
|
||||
console.log(composition)
|
||||
})
|
||||
});
|
||||
*/
|
||||
|
||||
var Objects = {},
|
||||
ROOT_REGISTRY = [],
|
||||
PROVIDER_REGISTRY = {},
|
||||
FALLBACK_PROVIDER;
|
||||
|
||||
Objects._supersecretSetFallbackProvider = function (p) {
|
||||
FALLBACK_PROVIDER = p;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Root provider is hardcoded in; can't be skipped.
|
||||
var RootProvider = {
|
||||
'get': function () {
|
||||
return Promise.resolve({
|
||||
name: 'The root object',
|
||||
type: 'root',
|
||||
composition: ROOT_REGISTRY
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve the provider for a given key.
|
||||
function getProvider(key) {
|
||||
if (key.identifier === 'ROOT') {
|
||||
return RootProvider;
|
||||
}
|
||||
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
|
||||
};
|
||||
|
||||
Objects.addProvider = function (namespace, provider) {
|
||||
PROVIDER_REGISTRY[namespace] = provider;
|
||||
};
|
||||
|
||||
[
|
||||
'save',
|
||||
'delete',
|
||||
'get'
|
||||
].forEach(function (method) {
|
||||
Objects[method] = function () {
|
||||
var key = arguments[0],
|
||||
provider = getProvider(key);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error('No Provider Matched');
|
||||
}
|
||||
|
||||
if (!provider[method]) {
|
||||
throw new Error('Provider does not support [' + method + '].');
|
||||
}
|
||||
|
||||
return provider[method].apply(provider, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Objects.addRoot = function (key) {
|
||||
ROOT_REGISTRY.unshift(key);
|
||||
};
|
||||
|
||||
Objects.removeRoot = function (key) {
|
||||
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
|
||||
return (
|
||||
k.identifier !== key.identifier ||
|
||||
k.namespace !== key.namespace
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return Objects;
|
||||
});
|
||||
100
src/api/objects/README.md
Normal file
100
src/api/objects/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Object API - Overview
|
||||
|
||||
The object API provides methods for fetching domain objects.
|
||||
|
||||
# Keys
|
||||
Keys are a composite identifier that is used to create and persist objects. Ex:
|
||||
```javascript
|
||||
{
|
||||
namespace: 'elastic',
|
||||
identifier: 'myIdentifier'
|
||||
}
|
||||
```
|
||||
|
||||
In old MCT days, we called this an "id", and we encoded it in a single string.
|
||||
The above key would encode into the identifier, `elastic:myIdentifier`.
|
||||
|
||||
When interacting with the API you will be dealing with key objects.
|
||||
|
||||
# Configuring the Object API
|
||||
|
||||
The following methods should be used before calling run. They allow you to
|
||||
configure the persistence space of MCT.
|
||||
|
||||
* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's
|
||||
key.
|
||||
* `MCT.objects.removeRoot(key)` -- Remove a "ROOT" from Open MCT by key.
|
||||
* `MCT.objects.addProvider(namespace, provider)` -- register an object provider
|
||||
for a specific namespace. See below for documentation on the provider
|
||||
interface.
|
||||
|
||||
# Using the object API
|
||||
|
||||
The object API provides methods for getting, saving, and deleting objects.
|
||||
|
||||
* MCT.objects.get(key) -> returns promise for an object
|
||||
* MCT.objects.save(object) -> returns promise that is resolved when object
|
||||
has been saved
|
||||
* MCT.objects.delete(object) -> returns promise that is resolved when object has
|
||||
been deleted
|
||||
|
||||
## Configuration Example: Adding a groot
|
||||
|
||||
The following example adds a new root object for groot and populates it with
|
||||
some pieces of groot.
|
||||
|
||||
```javascript
|
||||
|
||||
var ROOT_KEY = {
|
||||
namespace: 'groot',
|
||||
identifier: 'groot'
|
||||
};
|
||||
|
||||
var GROOT_ROOT = {
|
||||
name: 'I am groot',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'arms'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'legs'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'torso'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var GrootProvider = {
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve(GROOT_ROOT);
|
||||
}
|
||||
return Promise.resolve({
|
||||
name: 'Groot\'s ' + key.identifier
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
mct.Objects.addRoot(ROOT_KEY);
|
||||
|
||||
mct.Objects.addProvider('groot', GrootProvider);
|
||||
|
||||
```
|
||||
|
||||
### Making a custom provider:
|
||||
|
||||
All methods on the provider interface are optional, so you do not need
|
||||
to modify them.
|
||||
|
||||
* `provider.get(key)` -> promise for a domain object.
|
||||
* `provider.save(domainObject)` -> returns promise that is fulfilled when object
|
||||
has been saved.
|
||||
* `provider.delete(domainObject)` -> returns promise that is fulfilled when
|
||||
object has been deleted.
|
||||
|
||||
|
||||
49
src/api/objects/bundle.js
Normal file
49
src/api/objects/bundle.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./LegacyObjectAPIInterceptor',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
LegacyObjectAPIInterceptor,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('src/api/objects', {
|
||||
name: 'Object API',
|
||||
description: 'The public Objects API',
|
||||
extensions: {
|
||||
components: [
|
||||
{
|
||||
provides: "objectService",
|
||||
type: "decorator",
|
||||
priority: "mandatory",
|
||||
implementation: LegacyObjectAPIInterceptor,
|
||||
depends: [
|
||||
"roots[]",
|
||||
"instantiate"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
67
src/api/objects/object-utils.js
Normal file
67
src/api/objects/object-utils.js
Normal file
@@ -0,0 +1,67 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
// take a key string and turn it into a key object
|
||||
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
|
||||
var parseKeyString = function (key) {
|
||||
if (typeof key === 'object') {
|
||||
return key;
|
||||
}
|
||||
var namespace = '',
|
||||
identifier = key;
|
||||
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
|
||||
if (key[i] === ":" && !escaped) {
|
||||
namespace = key.slice(0, i);
|
||||
identifier = key.slice(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
namespace: namespace,
|
||||
identifier: identifier
|
||||
};
|
||||
};
|
||||
|
||||
// take a key and turn it into a key string
|
||||
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
|
||||
var makeKeyString = function (key) {
|
||||
if (typeof key === 'string') {
|
||||
return key;
|
||||
}
|
||||
if (!key.namespace) {
|
||||
return key.identifier;
|
||||
}
|
||||
return [
|
||||
key.namespace.replace(':', '\\:'),
|
||||
key.identifier.replace(':', '\\:')
|
||||
].join(':');
|
||||
};
|
||||
|
||||
// Converts composition to use key strings instead of keys
|
||||
var toOldFormat = function (model) {
|
||||
delete model.key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(makeKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
// converts composition to use keys instead of key strings
|
||||
var toNewFormat = function (model, key) {
|
||||
model.key = key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(parseKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
return {
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
parseKeyString: parseKeyString
|
||||
};
|
||||
});
|
||||
43
src/ui/Dialog.js
Normal file
43
src/ui/Dialog.js
Normal file
@@ -0,0 +1,43 @@
|
||||
define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
|
||||
function Dialog(view, title) {
|
||||
this.view = view;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
Dialog.prototype.show = function () {
|
||||
var $body = $('body');
|
||||
var $dialog = $(dialogTemplate);
|
||||
var $contents = $dialog.find('.contents .editor');
|
||||
var $close = $dialog.find('.close');
|
||||
|
||||
var $ok = $dialog.find('.ok');
|
||||
var $cancel = $dialog.find('.cancel');
|
||||
|
||||
var view = this.view;
|
||||
|
||||
function dismiss() {
|
||||
$dialog.remove();
|
||||
view.destroy();
|
||||
}
|
||||
|
||||
if (this.title) {
|
||||
$dialog.find('.title').text(this.title);
|
||||
}
|
||||
|
||||
$body.append($dialog);
|
||||
this.view.show($contents[0]);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
$ok.on('click', resolve);
|
||||
$ok.on('click', dismiss);
|
||||
|
||||
$cancel.on('click', reject);
|
||||
$cancel.on('click', dismiss);
|
||||
|
||||
$close.on('click', reject);
|
||||
$close.on('click', dismiss);
|
||||
});
|
||||
};
|
||||
|
||||
return Dialog;
|
||||
});
|
||||
21
src/ui/dialog.html
Normal file
21
src/ui/dialog.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="abs overlay">
|
||||
<div class="abs blocker"></div>
|
||||
<div class="abs holder">
|
||||
<a class="clk-icon icon ui-symbol close">x</a>
|
||||
<div class="abs contents">
|
||||
<div class="abs top-bar">
|
||||
<div class="title"></div>
|
||||
<div class="hint"></div>
|
||||
</div>
|
||||
<div class='abs editor'>
|
||||
</div>
|
||||
<div class="abs bottom-bar">
|
||||
<a class='s-btn major ok'>OK</a>
|
||||
<a class='s-btn cancel'>Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
44
tutorials/grootprovider/groots.js
Normal file
44
tutorials/grootprovider/groots.js
Normal file
@@ -0,0 +1,44 @@
|
||||
define(function () {
|
||||
return function grootPlugin(mct) {
|
||||
var ROOT_KEY = {
|
||||
namespace: 'groot',
|
||||
identifier: 'groot'
|
||||
};
|
||||
|
||||
var GROOT_ROOT = {
|
||||
name: 'I am groot',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'arms'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'legs'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'torso'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var GrootProvider = {
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve(GROOT_ROOT);
|
||||
}
|
||||
return Promise.resolve({
|
||||
name: 'Groot\'s ' + key.identifier
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
mct.Objects.addRoot(ROOT_KEY);
|
||||
|
||||
mct.Objects.addProvider('groot', GrootProvider);
|
||||
|
||||
return mct;
|
||||
};
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
define([
|
||||
'legacyRegistry',
|
||||
'./src/controllers/TodoController'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
TodoController
|
||||
) {
|
||||
legacyRegistry.register("tutorials/todo", {
|
||||
"name": "To-do Plugin",
|
||||
"description": "Allows creating and editing to-do lists.",
|
||||
"extensions": {
|
||||
"stylesheets": [
|
||||
{
|
||||
"stylesheetUrl": "css/todo.css"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
<div ng-controller="TodoController" class="example-todo">
|
||||
<div class="example-button-group">
|
||||
<a ng-class="{ selected: checkVisibility(true) }"
|
||||
ng-click="setVisibility(true)">All</a>
|
||||
<a ng-class="{ selected: checkVisibility(false, false) }"
|
||||
ng-click="setVisibility(false, false)">Incomplete</a>
|
||||
<a ng-class="{ selected: checkVisibility(false, true) }"
|
||||
ng-click="setVisibility(false, true)">Complete</a>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li ng-repeat="task in model.tasks"
|
||||
ng-class="{ 'example-task-completed': task.completed }"
|
||||
ng-if="showTask(task)">
|
||||
|
||||
<input type="checkbox"
|
||||
ng-checked="task.completed"
|
||||
ng-click="toggleCompletion($index)">
|
||||
<span ng-click="selectTask($index)"
|
||||
ng-class="{ selected: isSelected($index) }"
|
||||
class="example-task-description">
|
||||
{{task.description}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="model.tasks.length < 1" class="example-message">
|
||||
There are no tasks to show.
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,97 +0,0 @@
|
||||
define(function () {
|
||||
// Form to display when adding new tasks
|
||||
var NEW_TASK_FORM = {
|
||||
name: "Add a Task",
|
||||
sections: [{
|
||||
rows: [{
|
||||
name: 'Description',
|
||||
key: 'description',
|
||||
control: 'textfield',
|
||||
required: true
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
function TodoController($scope, dialogService) {
|
||||
var showAll = true,
|
||||
showCompleted;
|
||||
|
||||
// Persist changes made to a domain object's model
|
||||
function persist() {
|
||||
var persistence =
|
||||
$scope.domainObject.getCapability('persistence');
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
// Remove a task
|
||||
function removeTaskAtIndex(taskIndex) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.tasks.splice(taskIndex, 1);
|
||||
});
|
||||
persist();
|
||||
}
|
||||
|
||||
// Add a task
|
||||
function addNewTask(task) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.tasks.push(task);
|
||||
});
|
||||
persist();
|
||||
}
|
||||
|
||||
// Change which tasks are visible
|
||||
$scope.setVisibility = function (all, completed) {
|
||||
showAll = all;
|
||||
showCompleted = completed;
|
||||
};
|
||||
|
||||
// Check if current visibility settings match
|
||||
$scope.checkVisibility = function (all, completed) {
|
||||
return showAll ? all : (completed === showCompleted);
|
||||
};
|
||||
|
||||
// Toggle the completion state of a task
|
||||
$scope.toggleCompletion = function (taskIndex) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
var task = model.tasks[taskIndex];
|
||||
task.completed = !task.completed;
|
||||
});
|
||||
persist();
|
||||
};
|
||||
|
||||
// Check whether a task should be visible
|
||||
$scope.showTask = function (task) {
|
||||
return showAll || (showCompleted === !!(task.completed));
|
||||
};
|
||||
|
||||
// Handle selection state in edit mode
|
||||
if ($scope.selection) {
|
||||
// Expose the ability to select tasks
|
||||
$scope.selectTask = function (taskIndex) {
|
||||
$scope.selection.select({
|
||||
removeTask: function () {
|
||||
removeTaskAtIndex(taskIndex);
|
||||
$scope.selection.deselect();
|
||||
},
|
||||
taskIndex: taskIndex
|
||||
});
|
||||
};
|
||||
|
||||
// Expose a check for current selection state
|
||||
$scope.isSelected = function (taskIndex) {
|
||||
return ($scope.selection.get() || {}).taskIndex ===
|
||||
taskIndex;
|
||||
};
|
||||
|
||||
// Expose a view-level selection proxy
|
||||
$scope.selection.proxy({
|
||||
addTask: function () {
|
||||
dialogService.getUserInput(NEW_TASK_FORM, {})
|
||||
.then(addNewTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return TodoController;
|
||||
});
|
||||
3
tutorials/todo/todo-dialog.html
Normal file
3
tutorials/todo/todo-dialog.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<label>Description:
|
||||
<input type="text" class="description">
|
||||
</label>
|
||||
9
tutorials/todo/todo-toolbar.html
Normal file
9
tutorials/todo/todo-toolbar.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="tool-bar btn-bar contents abs">
|
||||
<a class="s-btn labeled example-add">
|
||||
<span class="ui-symbol icon">+</span>
|
||||
<span class="title-label">Add Task</span>
|
||||
</a>
|
||||
<a class="s-btn example-remove">
|
||||
<span class="ui-symbol icon">Z</span>
|
||||
</a>
|
||||
</div>
|
||||
29
tutorials/todo/todo.css
Normal file
29
tutorials/todo/todo.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.example-todo div.example-button-group {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.example-todo .example-button-group a {
|
||||
padding: 3px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.example-todo .example-button-group a.selected {
|
||||
border: 1px gray solid;
|
||||
border-radius: 3px;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.example-todo .example-task-completed .example-task-description {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.example-todo .example-task-description.selected {
|
||||
background: #46A;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.example-todo .example-message {
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
define([
|
||||
"text!./todo.html",
|
||||
"text!./todo-task.html",
|
||||
"text!./todo-toolbar.html",
|
||||
"text!./todo-dialog.html",
|
||||
"../../src/api/events/EventDecorator",
|
||||
"zepto"
|
||||
], function (todoTemplate, taskTemplate, $) {
|
||||
], function (todoTemplate, taskTemplate, toolbarTemplate, dialogTemplate, eventDecorator, $) {
|
||||
/**
|
||||
* @param {mct.MCT} mct
|
||||
*/
|
||||
@@ -22,23 +25,38 @@ define([
|
||||
});
|
||||
|
||||
function TodoView(domainObject) {
|
||||
mct.View.apply(this);
|
||||
this.domainObject = domainObject;
|
||||
this.filterValue = "all";
|
||||
this.elements($(todoTemplate));
|
||||
|
||||
var $els = $(this.elements());
|
||||
this.$buttons = {
|
||||
all: $els.find('.example-todo-button-all'),
|
||||
incomplete: $els.find('.example-todo-button-incomplete'),
|
||||
complete: $els.find('.example-todo-button-complete')
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
this.on('model', this.render.bind(this));
|
||||
this.model(domainObject);
|
||||
}
|
||||
|
||||
TodoView.prototype = Object.create(mct.View.prototype);
|
||||
TodoView.prototype.show = function (container) {
|
||||
this.destroy();
|
||||
|
||||
this.$els = $(todoTemplate);
|
||||
this.$buttons = {
|
||||
all: this.$els.find('.example-todo-button-all'),
|
||||
incomplete: this.$els.find('.example-todo-button-incomplete'),
|
||||
complete: this.$els.find('.example-todo-button-complete')
|
||||
};
|
||||
|
||||
$(container).empty().append(this.$els);
|
||||
|
||||
this.initialize();
|
||||
this.render();
|
||||
|
||||
mct.verbs.observe(this.domainObject, this.render.bind(this));
|
||||
|
||||
mct.events.mutation(this.domainObject).on("*", function (value) {
|
||||
console.log("model changed");
|
||||
});
|
||||
};
|
||||
|
||||
TodoView.prototype.destroy = function () {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
this.unlisten = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
TodoView.prototype.setFilter = function (value) {
|
||||
this.filterValue = value;
|
||||
@@ -52,8 +70,8 @@ define([
|
||||
};
|
||||
|
||||
TodoView.prototype.render = function () {
|
||||
var $els = $(this.elements());
|
||||
var domainObject = this.model();
|
||||
var $els = this.$els;
|
||||
var domainObject = this.domainObject;
|
||||
var tasks = domainObject.getModel().tasks;
|
||||
var $message = $els.find('.example-message');
|
||||
var $list = $els.find('.example-todo-task-list');
|
||||
@@ -70,38 +88,82 @@ define([
|
||||
}
|
||||
};
|
||||
var filterValue = this.filterValue;
|
||||
var model = domainObject.model;
|
||||
|
||||
Object.keys($buttons).forEach(function (k) {
|
||||
$buttons[k].toggleClass('selected', filterValue === k);
|
||||
});
|
||||
tasks = tasks.filter(filters[filterValue]);
|
||||
|
||||
$list.empty();
|
||||
tasks.forEach(function (task, index) {
|
||||
var $taskEls = $(taskTemplate);
|
||||
var $checkbox = $taskEls.find('.example-task-checked');
|
||||
$checkbox.prop('checked', task.completed);
|
||||
$taskEls.find('.example-task-description')
|
||||
.text(task.description);
|
||||
|
||||
$checkbox.on('change', function () {
|
||||
var checked = !!$checkbox.prop('checked');
|
||||
mct.verbs.mutate(domainObject, function (model) {
|
||||
model.tasks[index].completed = checked;
|
||||
function renderTasks() {
|
||||
$list.empty();
|
||||
domainObject.getModel().tasks.forEach(function (task, index) {
|
||||
var $taskEls = $(taskTemplate);
|
||||
var $checkbox = $taskEls.find('.example-task-checked');
|
||||
$checkbox.prop('checked', task.completed);
|
||||
$taskEls.find('.example-task-description')
|
||||
.text(task.description);
|
||||
$checkbox.on('change', function () {
|
||||
var checked = !!$checkbox.prop('checked');
|
||||
domainObject.getModel().tasks[index].completed = checked;
|
||||
});
|
||||
});
|
||||
|
||||
$list.append($taskEls);
|
||||
});
|
||||
$list.append($taskEls);
|
||||
});
|
||||
}
|
||||
|
||||
renderTasks();
|
||||
|
||||
$message.toggle(tasks.length < 1);
|
||||
|
||||
mct.events.mutation(domainObject).on("model.tasks.length", renderTasks);
|
||||
};
|
||||
|
||||
todoType.view(mct.regions.main, function (domainObject) {
|
||||
return new TodoView(domainObject);
|
||||
});
|
||||
function TodoToolbarView(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
TodoToolbarView.prototype.show = function (container) {
|
||||
var $els = $(toolbarTemplate);
|
||||
var $add = $els.find('a.example-add');
|
||||
var $remove = $els.find('a.example-remove');
|
||||
var domainObject = this.domainObject;
|
||||
|
||||
$(container).append($els);
|
||||
|
||||
$add.on('click', 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();
|
||||
domainObject.getModel().tasks.push({ description: description });
|
||||
/*mct.verbs.mutate(domainObject, function (model) {
|
||||
model.tasks.push({ description: description });
|
||||
console.log(model);
|
||||
});*/
|
||||
});
|
||||
});
|
||||
$remove.on('click', window.alert.bind(window, "Remove!"));
|
||||
};
|
||||
|
||||
TodoToolbarView.prototype.destroy = function () {
|
||||
|
||||
};
|
||||
|
||||
mct.type('example.todo', todoType);
|
||||
mct.view(mct.regions.main, function (domainObject) {
|
||||
return todoType.check(domainObject) && new TodoView(eventDecorator(mct, domainObject));
|
||||
});
|
||||
mct.view(mct.regions.toolbar, function (domainObject) {
|
||||
domainObject = eventDecorator(mct, domainObject);
|
||||
return todoType.check(domainObject) && new TodoToolbarView(domainObject);
|
||||
});
|
||||
|
||||
return mct;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user