mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 03:00:13 +00:00
Compare commits
40 Commits
api-tutori
...
api-toolba
Author | SHA1 | Date | |
---|---|---|---|
7ff2b507e3 | |||
dde7de01e2 | |||
b55668426d | |||
5b656faa9d | |||
8d2c489fa9 | |||
4366b0870d | |||
47a543beb7 | |||
06f87c1472 | |||
c9c41cdcc8 | |||
14a56ea17e | |||
b2e7db71cc | |||
d51e6bfd92 | |||
370b515c23 | |||
4a50f325cb | |||
dbe6a4efc1 | |||
13920d8802 | |||
b6a8c514aa | |||
e4a4704baa | |||
be0029e59a | |||
9cb273ef0a | |||
eec9b1cf4c | |||
1f96e84542 | |||
c289a27305 | |||
c944080790 | |||
96316de6e4 | |||
2240a87ddc | |||
d891affe48 | |||
21a618d1ce | |||
580a4e52b5 | |||
4ca2f51d5e | |||
86ac80ddbd | |||
0525ba6b0b | |||
a79e958ffc | |||
03cb0ccb57 | |||
7205faa6bb | |||
136f2ae785 | |||
a07e2fb8e5 | |||
55b531bdeb | |||
7ece5897e8 | |||
a29c7a6eab |
@ -33,7 +33,6 @@
|
|||||||
require([
|
require([
|
||||||
'./tutorials/grootprovider/groots',
|
'./tutorials/grootprovider/groots',
|
||||||
'./tutorials/todo/todo',
|
'./tutorials/todo/todo',
|
||||||
'./tutorials/todo/bundle',
|
|
||||||
'./example/imagery/bundle',
|
'./example/imagery/bundle',
|
||||||
'./example/eventGenerator/bundle',
|
'./example/eventGenerator/bundle',
|
||||||
'./example/generator/bundle',
|
'./example/generator/bundle',
|
||||||
@ -46,6 +45,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
<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="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-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-96x96.png" sizes="96x96">
|
||||||
<link rel="icon" type="image/png" href="platform/commonUI/general/res/images/favicons/favicon-16x16.png" sizes="16x16">
|
<link rel="icon" type="image/png" href="platform/commonUI/general/res/images/favicons/favicon-16x16.png" sizes="16x16">
|
||||||
|
2
main.js
2
main.js
@ -65,6 +65,8 @@ define([
|
|||||||
'legacyRegistry',
|
'legacyRegistry',
|
||||||
'./src/MCT',
|
'./src/MCT',
|
||||||
|
|
||||||
|
'./src/adapter/bundle',
|
||||||
|
|
||||||
'./platform/framework/bundle',
|
'./platform/framework/bundle',
|
||||||
'./platform/core/bundle',
|
'./platform/core/bundle',
|
||||||
'./platform/representation/bundle',
|
'./platform/representation/bundle',
|
||||||
|
94
src/MCT.js
94
src/MCT.js
@ -1,12 +1,20 @@
|
|||||||
define([
|
define([
|
||||||
'EventEmitter',
|
'EventEmitter',
|
||||||
'legacyRegistry',
|
'legacyRegistry',
|
||||||
|
'uuid',
|
||||||
'./api/api',
|
'./api/api',
|
||||||
|
'text!./adapter/templates/edit-object-replacement.html',
|
||||||
|
'./ui/Dialog',
|
||||||
|
'./api/events/Events',
|
||||||
'./api/objects/bundle'
|
'./api/objects/bundle'
|
||||||
], function (
|
], function (
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
api
|
uuid,
|
||||||
|
api,
|
||||||
|
editObjectTemplate,
|
||||||
|
Dialog,
|
||||||
|
Events
|
||||||
) {
|
) {
|
||||||
function MCT() {
|
function MCT() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
@ -18,13 +26,70 @@ define([
|
|||||||
Object.keys(api).forEach(function (k) {
|
Object.keys(api).forEach(function (k) {
|
||||||
MCT.prototype[k] = api[k];
|
MCT.prototype[k] = api[k];
|
||||||
});
|
});
|
||||||
|
MCT.prototype.MCT = MCT;
|
||||||
|
|
||||||
|
MCT.prototype.legacyExtension = function (category, extension) {
|
||||||
|
this.legacyBundle.extensions[category] =
|
||||||
|
this.legacyBundle.extensions[category] || [];
|
||||||
|
this.legacyBundle.extensions[category].push(extension);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCT.prototype.view = function (region, factory) {
|
||||||
|
var viewKey = region + uuid();
|
||||||
|
var adaptedViewKey = "adapted-view-" + region;
|
||||||
|
|
||||||
|
this.legacyExtension(
|
||||||
|
region === this.regions.main ? 'views' : 'representations',
|
||||||
|
{
|
||||||
|
name: "A view",
|
||||||
|
key: adaptedViewKey,
|
||||||
|
editable: true,
|
||||||
|
template: '<mct-view region="\'' +
|
||||||
|
region +
|
||||||
|
'\'" ' +
|
||||||
|
'key="\'' +
|
||||||
|
viewKey +
|
||||||
|
'\'" ' +
|
||||||
|
'mct-object="domainObject">' +
|
||||||
|
'</mct-view>'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
MCT.prototype.type = function (key, type) {
|
||||||
var legacyDef = type.toLegacyDefinition();
|
var legacyDef = type.toLegacyDefinition();
|
||||||
legacyDef.key = key;
|
legacyDef.key = key;
|
||||||
this.legacyBundle.extensions.types =
|
type.key = key;
|
||||||
this.legacyBundle.extensions.types || [];
|
|
||||||
this.legacyBundle.extensions.types.push(legacyDef);
|
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 () {
|
MCT.prototype.start = function () {
|
||||||
@ -32,5 +97,26 @@ define([
|
|||||||
this.emit('start');
|
this.emit('start');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCT.prototype.regions = {
|
||||||
|
main: "MAIN",
|
||||||
|
toolbar: "TOOLBAR"
|
||||||
|
};
|
||||||
|
|
||||||
|
MCT.prototype.events = new Events();
|
||||||
|
|
||||||
|
MCT.prototype.verbs = {
|
||||||
|
mutate: function (domainObject, mutator) {
|
||||||
|
return domainObject.useCapability('mutation', mutator)
|
||||||
|
.then(function () {
|
||||||
|
var persistence = domainObject.getCapability('persistence');
|
||||||
|
return persistence.persist();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
observe: function (domainObject, callback) {
|
||||||
|
var mutation = domainObject.getCapability('mutation');
|
||||||
|
return mutation.listen(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return MCT;
|
return MCT;
|
||||||
});
|
});
|
||||||
|
16
src/adapter/bundle.js
Normal file
16
src/adapter/bundle.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./directives/MCTView'
|
||||||
|
], function (legacyRegistry, MCTView) {
|
||||||
|
legacyRegistry.register('src/adapter', {
|
||||||
|
"extensions": {
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
key: "mctView",
|
||||||
|
implementation: MCTView,
|
||||||
|
depends: ["newViews[]"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
53
src/adapter/directives/MCTView.js
Normal file
53
src/adapter/directives/MCTView.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
define(['angular'], function (angular) {
|
||||||
|
function MCTView(newViews) {
|
||||||
|
var factories = {};
|
||||||
|
|
||||||
|
newViews.forEach(function (newView) {
|
||||||
|
factories[newView.region] = factories[newView.region] || {};
|
||||||
|
factories[newView.region][newView.key] = newView.factory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
link: function (scope, element, attrs) {
|
||||||
|
var key, mctObject, region;
|
||||||
|
|
||||||
|
function maybeShow() {
|
||||||
|
if (!factories[region] || !factories[region][key] || !mctObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = factories[region][key](mctObject);
|
||||||
|
view.show(element[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKey(k) {
|
||||||
|
key = k;
|
||||||
|
maybeShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setObject(obj) {
|
||||||
|
mctObject = obj;
|
||||||
|
maybeShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRegion(r) {
|
||||||
|
region = r;
|
||||||
|
maybeShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.$watch('key', setKey);
|
||||||
|
scope.$watch('region', setRegion);
|
||||||
|
scope.$watch('mctObject', setObject);
|
||||||
|
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
key: "=",
|
||||||
|
region: "=",
|
||||||
|
mctObject: "="
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTView;
|
||||||
|
});
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -17,6 +17,16 @@ define(function () {
|
|||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a definition for this type that can be registered using the
|
* Get a definition for this type that can be registered using the
|
||||||
* legacy bundle format.
|
* legacy bundle format.
|
||||||
|
24
src/api/View.js
Normal file
24
src/api/View.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
define(function () {
|
||||||
|
function View() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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([
|
define([
|
||||||
'./Type',
|
'./Type',
|
||||||
|
'./TimeConductor',
|
||||||
|
'./View',
|
||||||
'./objects/ObjectAPI'
|
'./objects/ObjectAPI'
|
||||||
], function (
|
], function (
|
||||||
Type,
|
Type,
|
||||||
|
TimeConductor,
|
||||||
|
View,
|
||||||
ObjectAPI
|
ObjectAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
Type: Type,
|
Type: Type,
|
||||||
|
TimeConductor: new TimeConductor(),
|
||||||
|
View: View,
|
||||||
Objects: ObjectAPI
|
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]}]);
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,59 +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": {
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "example.todo",
|
|
||||||
"type": "example.todo",
|
|
||||||
"glyph": "2",
|
|
||||||
"name": "List",
|
|
||||||
"templateUrl": "templates/todo.html",
|
|
||||||
"editable": true,
|
|
||||||
"toolbar": {
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"text": "Add Task",
|
|
||||||
"glyph": "+",
|
|
||||||
"method": "addTask",
|
|
||||||
"control": "button"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"glyph": "Z",
|
|
||||||
"method": "removeTask",
|
|
||||||
"control": "button"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TodoController",
|
|
||||||
"implementation": TodoController,
|
|
||||||
"depends": [ "$scope", "dialogService" ]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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>
|
5
tutorials/todo/todo-task.html
Normal file
5
tutorials/todo/todo-task.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<li>
|
||||||
|
<input type="checkbox" class="example-task-checked">
|
||||||
|
<span class="example-task-description">
|
||||||
|
</span>
|
||||||
|
</li>
|
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;
|
||||||
|
}
|
14
tutorials/todo/todo.html
Normal file
14
tutorials/todo/todo.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<div class="example-todo">
|
||||||
|
<div class="example-button-group">
|
||||||
|
<a class="example-todo-button-all">All</a>
|
||||||
|
<a class="example-todo-button-incomplete">Incomplete</a>
|
||||||
|
<a class="example-todo-button-complete">Complete</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="example-todo-task-list">
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="example-message">
|
||||||
|
There are no tasks to show.
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,4 +1,14 @@
|
|||||||
define(function () {
|
define([
|
||||||
|
"text!./todo.html",
|
||||||
|
"text!./todo-task.html",
|
||||||
|
"text!./todo-toolbar.html",
|
||||||
|
"text!./todo-dialog.html",
|
||||||
|
"../../src/api/events/EventDecorator",
|
||||||
|
"zepto"
|
||||||
|
], function (todoTemplate, taskTemplate, toolbarTemplate, dialogTemplate, eventDecorator, $) {
|
||||||
|
/**
|
||||||
|
* @param {mct.MCT} mct
|
||||||
|
*/
|
||||||
return function todoPlugin(mct) {
|
return function todoPlugin(mct) {
|
||||||
var todoType = new mct.Type({
|
var todoType = new mct.Type({
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -7,12 +17,153 @@ define(function () {
|
|||||||
description: "A list of things that need to be done."
|
description: "A list of things that need to be done."
|
||||||
},
|
},
|
||||||
initialize: function (model) {
|
initialize: function (model) {
|
||||||
model.tasks = [];
|
model.tasks = [
|
||||||
|
{ description: "This is a task." }
|
||||||
|
];
|
||||||
},
|
},
|
||||||
creatable: true
|
creatable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function TodoView(domainObject) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.filterValue = "all";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.initialize = function () {
|
||||||
|
Object.keys(this.$buttons).forEach(function (k) {
|
||||||
|
this.$buttons[k].on('click', this.setFilter.bind(this, k));
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.render = function () {
|
||||||
|
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');
|
||||||
|
var $buttons = this.$buttons;
|
||||||
|
var filters = {
|
||||||
|
all: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
incomplete: function (task) {
|
||||||
|
return !task.completed;
|
||||||
|
},
|
||||||
|
complete: function (task) {
|
||||||
|
return task.completed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTasks();
|
||||||
|
|
||||||
|
$message.toggle(tasks.length < 1);
|
||||||
|
|
||||||
|
mct.events.mutation(domainObject).on("model.tasks.length", renderTasks);
|
||||||
|
};
|
||||||
|
|
||||||
|
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.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;
|
return mct;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user