Merge remote-tracking branch 'origin/api-tutorials' into api-1110

Conflicts:
	src/MCT.js
	src/api/composition/CompositionCollection.js
	src/api/composition/DefaultCompositionProvider.js
	src/api/objects/MutableObject.js
This commit is contained in:
Victor Woeltjen 2016-09-06 10:03:59 -07:00
commit 6264ab75f3
19 changed files with 421 additions and 199 deletions

View File

@ -31,10 +31,12 @@
<script type="text/javascript"> <script type="text/javascript">
require(['main'], function (mct) { require(['main'], function (mct) {
require([ require([
'./tutorials/todo/todo',
'./example/imagery/bundle', './example/imagery/bundle',
'./example/eventGenerator/bundle', './example/eventGenerator/bundle',
'./example/generator/bundle', './example/generator/bundle'
], function () { ], function (todoPlugin) {
mct.install(todoPlugin);
mct.run(); mct.run();
}) })
}); });

View File

@ -4,7 +4,6 @@ define([
'uuid', 'uuid',
'./api/api', './api/api',
'text!./adapter/templates/edit-object-replacement.html', 'text!./adapter/templates/edit-object-replacement.html',
'./ui/Dialog',
'./Selection', './Selection',
'./api/objects/object-utils', './api/objects/object-utils',
'./api/TimeConductor' './api/TimeConductor'
@ -14,7 +13,6 @@ define([
uuid, uuid,
api, api,
editObjectTemplate, editObjectTemplate,
Dialog,
Selection, Selection,
objectUtils, objectUtils,
TimeConductor TimeConductor
@ -29,7 +27,16 @@ define([
*/ */
function MCT() { function MCT() {
EventEmitter.call(this); EventEmitter.call(this);
this.legacyBundle = { extensions: {} }; this.legacyBundle = { extensions: {
services: [
{
key: "mct",
implementation: function () {
return this;
}.bind(this)
}
]
} };
/** /**
* *
@ -133,13 +140,6 @@ define([
region: region, region: region,
key: viewKey key: viewKey
}); });
this.legacyExtension('services', {
key: 'PublicAPI',
implementation: function () {
return this;
}.bind(this)
});
}; };
/** /**
@ -163,18 +163,6 @@ define([
}); });
}; };
/**
* Show a dialog.
* @param view
* @param title
* @returns {Promise}
* @method dialog
* #memberof module:openmct.MCT#
*/
MCT.prototype.dialog = function (view, title) {
return new Dialog(view, title).show();
};
/** /**
* Start running Open MCT. This should be called only after any plugins * Start running Open MCT. This should be called only after any plugins
* have been installed. * have been installed.
@ -216,6 +204,7 @@ define([
MCT.prototype.regions = { MCT.prototype.regions = {
main: "MAIN", main: "MAIN",
properties: "PROPERTIES",
toolbar: "TOOLBAR" toolbar: "TOOLBAR"
}; };

View File

@ -28,7 +28,7 @@ define(['EventEmitter'], function (EventEmitter) {
*/ */
Selection.prototype.select = function (value) { Selection.prototype.select = function (value) {
this.selectedValues.push(value); this.selectedValues.push(value);
this.emit('change'); this.emit('change', this.selectedValues);
return this.deselect.bind(this, value); return this.deselect.bind(this, value);
}; };
@ -43,7 +43,7 @@ define(['EventEmitter'], function (EventEmitter) {
this.selectedValues = this.selectedValues.filter(function (v) { this.selectedValues = this.selectedValues.filter(function (v) {
return v !== value; return v !== value;
}); });
this.emit('change'); this.emit('change', this.selectedValues);
}; };
/** /**
@ -65,7 +65,7 @@ define(['EventEmitter'], function (EventEmitter) {
*/ */
Selection.prototype.clear = function () { Selection.prototype.clear = function () {
this.selectedValues = []; this.selectedValues = [];
this.emit('change'); this.emit('change', this.selectedValues);
}; };
return Selection; return Selection;

View File

@ -0,0 +1,44 @@
define([
'../../api/objects/object-utils'
], function (objectUtils) {
function ActionDialogDecorator(mct, newViews, actionService) {
this.actionService = actionService;
this.mct = mct;
this.definitions = newViews.filter(function (newView) {
return newView.region === mct.regions.properties;
}).map(function (newView) {
return newView.factory;
});
}
ActionDialogDecorator.prototype.getActions = function (context) {
var mct = this.mct;
var definitions = this.definitions;
return this.actionService.getActions(context).map(function (action) {
if (action.dialogService) {
var domainObject = objectUtils.toNewFormat(
context.domainObject.getModel(),
objectUtils.parseKeyString(context.domainObject.getId())
);
definitions = definitions.filter(function (definition) {
return definition.canView(domainObject);
});
if (definitions.length > 0) {
action.dialogService = Object.create(action.dialogService);
action.dialogService.getUserInput = function (form, value) {
return new mct.Dialog(
definitions[0].view(context.domainObject),
form.title
).show();
};
}
}
return action;
});
};
return ActionDialogDecorator;
});

View File

@ -1,13 +1,17 @@
define([ define([
'legacyRegistry', 'legacyRegistry',
'./actions/ActionDialogDecorator',
'./directives/MCTView', './directives/MCTView',
'./services/Instantiate', './services/Instantiate',
'./capabilities/APICapabilityDecorator' './capabilities/APICapabilityDecorator',
'./policies/AdapterCompositionPolicy'
], function ( ], function (
legacyRegistry, legacyRegistry,
ActionDialogDecorator,
MCTView, MCTView,
Instantiate, Instantiate,
APICapabilityDecorator APICapabilityDecorator,
AdapterCompositionPolicy
) { ) {
legacyRegistry.register('src/adapter', { legacyRegistry.register('src/adapter', {
"extensions": { "extensions": {
@ -17,7 +21,7 @@ define([
implementation: MCTView, implementation: MCTView,
depends: [ depends: [
"newViews[]", "newViews[]",
"PublicAPI" "mct"
] ]
} }
], ],
@ -41,6 +45,19 @@ define([
depends: [ depends: [
"$injector" "$injector"
] ]
},
{
type: "decorator",
provides: "actionService",
implementation: ActionDialogDecorator,
depends: [ "mct", "newViews[]" ]
}
],
policies: [
{
category: "composition",
implementation: AdapterCompositionPolicy,
depends: [ "mct" ]
} }
] ]
} }

View File

@ -0,0 +1,26 @@
define([], function () {
function AdapterCompositionPolicy(mct) {
this.mct = mct;
}
AdapterCompositionPolicy.prototype.allow = function (
containerType,
childType
) {
var containerObject = containerType.getInitialModel();
var childObject = childType.getInitialModel();
containerObject.type = containerType.getKey();
childObject.type = childType.getKey();
var composition = this.mct.Composition(containerObject);
if (composition) {
return composition.canContain(childObject);
}
return true;
};
return AdapterCompositionPolicy;
});

View File

@ -3,18 +3,21 @@ define([
'./TimeConductor', './TimeConductor',
'./View', './View',
'./objects/ObjectAPI', './objects/ObjectAPI',
'./composition/CompositionAPI' './composition/CompositionAPI',
'./ui/Dialog'
], function ( ], function (
Type, Type,
TimeConductor, TimeConductor,
View, View,
ObjectAPI, ObjectAPI,
CompositionAPI CompositionAPI,
Dialog
) { ) {
return { return {
Type: Type, Type: Type,
View: View, View: View,
Objects: ObjectAPI, Objects: ObjectAPI,
Composition: CompositionAPI Composition: CompositionAPI,
Dialog: Dialog
}; };
}); });

View File

@ -103,6 +103,9 @@ define([
if (!this._children) { if (!this._children) {
throw new Error("Must load composition before you can add!"); throw new Error("Must load composition before you can add!");
} }
if (!this.canContain(child)) {
throw new Error("This object cannot contain that object.");
}
if (this.contains(child)) { if (this.contains(child)) {
if (skipMutate) { if (skipMutate) {
return; // don't add twice, don't error. return; // don't add twice, don't error.
@ -165,6 +168,17 @@ define([
} }
}; };
/**
* Check if this composition can contain this domain object.
* @name canContain
* @memberof module:openmct.CompositionCollection
* @param {module:openmct.DomainObject} the domain object to contain
* @returns {boolean} true if containment is allowed
*/
CompositionCollection.prototype.canContain = function (domainObject) {
return this.provider.canContain(this.domainObject, domainObject);
};
/** /**
* Stop using this composition collection. This will release any resources * Stop using this composition collection. This will release any resources
* associated with this collection. * associated with this collection.

View File

@ -87,6 +87,17 @@ define([
); );
}; };
/**
* Check if one domain object can contain another.
* @param {module:openmct.DomainObject} domainObject the domain object
* which will act as the container
* @param {module:openmct.DomainObject} child the domain object to be
* contained
* @returns {boolean} true if this is allowed
*/
DefaultCompositionProvider.prototype.canContain = function (domainObject, child) {
return true;
};
/** /**
* Remove a domain object from another domain object's composition. * Remove a domain object from another domain object's composition.

View File

@ -1,15 +1,54 @@
define([ define([
'./object-utils', './object-utils',
'./ObjectAPI' './ObjectAPI',
'./objectEventEmitter'
], function ( ], function (
utils, utils,
ObjectAPI ObjectAPI,
objectEventEmitter
) { ) {
function ObjectServiceProvider(objectService, instantiate) { function ObjectServiceProvider(objectService, instantiate, topic) {
this.objectService = objectService; this.objectService = objectService;
this.instantiate = instantiate; this.instantiate = instantiate;
this.generalTopic = topic('mutation');
this.bridgeEventBuses();
} }
/**
* Bridges old and new style mutation events to provide compatibility between the two APIs
* @private
*/
ObjectServiceProvider.prototype.bridgeEventBuses = function () {
var removeGeneralTopicListener;
var handleMutation = function (newStyleObject) {
var keyString = utils.makeKeyString(newStyleObject.key);
var oldStyleObject = this.instantiate(utils.toOldFormat(newStyleObject), keyString);
// Don't trigger self
removeGeneralTopicListener();
oldStyleObject.getCapability('mutation').mutate(function () {
return utils.toOldFormat(newStyleObject);
});
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
}.bind(this);
var handleLegacyMutation = function (legacyObject){
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
//Don't trigger self
objectEventEmitter.off('mutation', handleMutation);
objectEventEmitter.emit(newStyleObject.key.identifier + ":*", newStyleObject);
objectEventEmitter.on('mutation', handleMutation);
}.bind(this);
objectEventEmitter.on('mutation', handleMutation);
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
};
ObjectServiceProvider.prototype.save = function (object) { ObjectServiceProvider.prototype.save = function (object) {
var key = object.key, var key = object.key,
keyString = utils.makeKeyString(key), keyString = utils.makeKeyString(key),
@ -37,7 +76,7 @@ define([
// Injects new object API as a decorator so that it hijacks all requests. // Injects new object API as a decorator so that it hijacks all requests.
// Object providers implemented on new API should just work, old API should just work, many things may break. // Object providers implemented on new API should just work, old API should just work, many things may break.
function LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) { function LegacyObjectAPIInterceptor(ROOTS, instantiate, topic, objectService) {
this.getObjects = function (keys) { this.getObjects = function (keys) {
var results = {}, var results = {},
promises = keys.map(function (keyString) { promises = keys.map(function (keyString) {
@ -56,7 +95,7 @@ define([
}; };
ObjectAPI._supersecretSetFallbackProvider( ObjectAPI._supersecretSetFallbackProvider(
new ObjectServiceProvider(objectService, instantiate) new ObjectServiceProvider(objectService, instantiate, topic)
); );
ROOTS.forEach(function (r) { ROOTS.forEach(function (r) {

View File

@ -1,19 +1,21 @@
define([ define([
'lodash' 'lodash',
'./objectEventEmitter'
], function ( ], function (
_ _,
objectEventEmitter
) { ) {
var ANY_OBJECT_EVENT = "mutation";
/** /**
* Provides methods for observing and modifying the state of a domain * The MutableObject wraps a DomainObject and provides getters and
* object. * setters for
*
* @param eventEmitter * @param eventEmitter
* @param object * @param object
* @interface MutableObject * @interface MutableObject
* @memberof module:openmct * @memberof module:openmct
*/ */
function MutableObject(eventEmitter, object) { function MutableObject(object) {
this.eventEmitter = eventEmitter;
this.object = object; this.object = object;
this.unlisteners = []; this.unlisteners = [];
} }
@ -38,8 +40,8 @@ define([
*/ */
MutableObject.prototype.on = function(path, callback) { MutableObject.prototype.on = function(path, callback) {
var fullPath = qualifiedEventName(this.object, path); var fullPath = qualifiedEventName(this.object, path);
this.eventEmitter.on(fullPath, callback); objectEventEmitter.on(fullPath, callback);
this.unlisteners.push(this.eventEmitter.off.bind(this.eventEmitter, fullPath, callback)); this.unlisteners.push(objectEventEmitter.off.bind(objectEventEmitter, fullPath, callback));
}; };
/** /**
@ -52,15 +54,15 @@ define([
MutableObject.prototype.set = function (path, value) { MutableObject.prototype.set = function (path, value) {
_.set(this.object, path, value); _.set(this.object, path, value);
_.set(this.object, 'modified', Date.now());
//Emit event specific to property //Emit event specific to property
this.eventEmitter.emit(qualifiedEventName(this.object, path), value); objectEventEmitter.emit(qualifiedEventName(this.object, path), value);
//Emit wildcare event //Emit wildcare event
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object); objectEventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
};
MutableObject.prototype.get = function (path) { //Emit a general "any object" event
return _.get(this.object, path); objectEventEmitter.emit(ANY_OBJECT_EVENT, this.object);
}; };
return MutableObject; return MutableObject;

View File

@ -60,25 +60,25 @@ define(['./MutableObject'], function (MutableObject) {
}); });
it('Supports getting and setting of object properties', function () { it('Supports getting and setting of object properties', function () {
expect(mutableObject.get('stringProperty')).toEqual('stringValue'); expect(domainObject.stringProperty).toEqual('stringValue');
mutableObject.set('stringProperty', 'updated'); mutableObject.set('stringProperty', 'updated');
expect(mutableObject.get('stringProperty')).toEqual('updated'); expect(domainObject.stringProperty).toEqual('updated');
var newArrayProperty = []; var newArrayProperty = [];
expect(mutableObject.get('arrayProperty')).toEqual(arrayProperty); expect(domainObject.arrayProperty).toEqual(arrayProperty);
mutableObject.set('arrayProperty', newArrayProperty); mutableObject.set('arrayProperty', newArrayProperty);
expect(mutableObject.get('arrayProperty')).toEqual(newArrayProperty); expect(domainObject.arrayProperty).toEqual(newArrayProperty);
var newObjectProperty = []; var newObjectProperty = [];
expect(mutableObject.get('objectProperty')).toEqual(objectProperty); expect(domainObject.objectProperty).toEqual(objectProperty);
mutableObject.set('objectProperty', newObjectProperty); mutableObject.set('objectProperty', newObjectProperty);
expect(mutableObject.get('objectProperty')).toEqual(newObjectProperty); expect(domainObject.objectProperty).toEqual(newObjectProperty);
}); });
it('Supports getting and setting of nested properties', function () { it('Supports getting and setting of nested properties', function () {
expect(mutableObject.get('objectProperty')).toEqual(objectProperty); expect(domainObject.objectProperty).toEqual(objectProperty);
expect(mutableObject.get('objectProperty.prop1')).toEqual(objectProperty.prop1); expect(domainObject.objectProperty.prop1).toEqual(objectProperty.prop1);
expect(mutableObject.get('objectProperty.prop3.propA')).toEqual(objectProperty.prop3.propA); expect(domainObject.objectProperty.prop3.propA).toEqual(objectProperty.prop3.propA);
mutableObject.set('objectProperty.prop1', 'new-prop-1'); mutableObject.set('objectProperty.prop1', 'new-prop-1');
expect(domainObject.objectProperty.prop1).toEqual('new-prop-1'); expect(domainObject.objectProperty.prop1).toEqual('new-prop-1');

View File

@ -1,11 +1,9 @@
define([ define([
'lodash', 'lodash',
'EventEmitter',
'./object-utils', './object-utils',
'./MutableObject' './MutableObject'
], function ( ], function (
_, _,
EventEmitter,
utils, utils,
MutableObject MutableObject
) { ) {
@ -24,8 +22,7 @@ define([
var Objects = {}, var Objects = {},
ROOT_REGISTRY = [], ROOT_REGISTRY = [],
PROVIDER_REGISTRY = {}, PROVIDER_REGISTRY = {},
FALLBACK_PROVIDER, FALLBACK_PROVIDER;
eventEmitter = new EventEmitter();
Objects._supersecretSetFallbackProvider = function (p) { Objects._supersecretSetFallbackProvider = function (p) {
FALLBACK_PROVIDER = p; FALLBACK_PROVIDER = p;
@ -162,7 +159,7 @@ define([
* @memberof module:openmct.ObjectAPI# * @memberof module:openmct.ObjectAPI#
*/ */
Objects.getMutable = function (object) { Objects.getMutable = function (object) {
return new MutableObject(eventEmitter, object); return new MutableObject(object);
}; };
/** /**

View File

@ -40,7 +40,8 @@ define([
implementation: LegacyObjectAPIInterceptor, implementation: LegacyObjectAPIInterceptor,
depends: [ depends: [
"roots[]", "roots[]",
"instantiate" "instantiate",
"topic"
] ]
} }
] ]

View File

@ -0,0 +1,10 @@
define([
"EventEmitter"
], function (
EventEmitter
) {
/**
* Provides a singleton event bus for sharing between objects.
*/
return new EventEmitter();
});

View File

@ -10,6 +10,8 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
function Dialog(view, title) { function Dialog(view, title) {
this.view = view; this.view = view;
this.title = title; this.title = title;
this.showing = false;
this.enabledState = true;
} }
/** /**
@ -20,6 +22,10 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
* @memberof module:openmct.Dialog# * @memberof module:openmct.Dialog#
*/ */
Dialog.prototype.show = function () { Dialog.prototype.show = function () {
if (this.showing) {
throw new Error("Dialog already showing.");
}
var $body = $('body'); var $body = $('body');
var $dialog = $(dialogTemplate); var $dialog = $(dialogTemplate);
var $contents = $dialog.find('.contents .editor'); var $contents = $dialog.find('.contents .editor');
@ -28,31 +34,45 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
var $ok = $dialog.find('.ok'); var $ok = $dialog.find('.ok');
var $cancel = $dialog.find('.cancel'); var $cancel = $dialog.find('.cancel');
var view = this.view;
function dismiss() {
$dialog.remove();
view.destroy();
}
if (this.title) { if (this.title) {
$dialog.find('.title').text(this.title); $dialog.find('.title').text(this.title);
} }
$body.append($dialog); $body.append($dialog);
this.view.show($contents[0]); this.view.show($contents[0]);
this.$dialog = $dialog;
this.$ok = $ok;
this.showing = true;
[$ok, $cancel, $close].forEach(function ($button) {
$button.on('click', this.hide.bind(this));
}.bind(this));
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
$ok.on('click', resolve); $ok.on('click', resolve);
$ok.on('click', dismiss);
$cancel.on('click', reject); $cancel.on('click', reject);
$cancel.on('click', dismiss);
$close.on('click', reject); $close.on('click', reject);
$close.on('click', dismiss);
}); });
}; };
Dialog.prototype.hide = function () {
if (!this.showing) {
return;
}
this.$dialog.remove();
this.view.destroy();
this.showing = false;
};
Dialog.prototype.enabled = function (state) {
if (state !== undefined) {
this.enabledState = state;
if (this.showing) {
this.$ok.toggleClass('disabled', !state);
}
}
return this.enabledState;
};
return Dialog; return Dialog;
}); });

View File

@ -1,14 +1,14 @@
<div class="example-todo"> <div class="example-todo">
<div class="example-button-group"> <div class="example-button-group">
<a class="example-todo-button-all">All</a> <a class="example-todo-button" data-filter="all">All</a>
<a class="example-todo-button-incomplete">Incomplete</a> <a class="example-todo-button" data-filter="incomplete">Incomplete</a>
<a class="example-todo-button-complete">Complete</a> <a class="example-todo-button" data-filter="complete">Complete</a>
</div> </div>
<ul class="example-todo-task-list"> <ul class="example-todo-task-list">
</ul> </ul>
<div class="example-message"> <div class="example-no-tasks">
There are no tasks to show. There are no tasks to show.
</div> </div>
</div> </div>

View File

@ -25,21 +25,16 @@ define([
}); });
function TodoView(domainObject) { function TodoView(domainObject) {
this.domainObject = domainObject; this.tasks = domainObject.tasks;
this.mutableObject = undefined;
this.filterValue = "all"; this.filterValue = "all";
this.render = this.render.bind(this);
this.objectChanged = this.objectChanged.bind(this);
}
TodoView.prototype.objectChanged = function (object) { this.setTaskStatus = this.setTaskStatus.bind(this);
if (this.mutableObject) { this.selectTask = this.selectTask.bind(this);
this.mutableObject.stopListening();
}
this.mutableObject = mct.Objects.getMutable(object);
this.render();
<<<<<<< HEAD
this.mutableObject = mct.Objects.getMutable(domainObject);
this.mutableObject.on('tasks', this.updateTasks.bind(this));
=======
//If anything on object changes, re-render view //If anything on object changes, re-render view
this.mutableObject.on("*", this.objectChanged); this.mutableObject.on("*", this.objectChanged);
}; };
@ -48,50 +43,79 @@ define([
var self = this; var self = this;
this.destroy(); this.destroy();
mct.Objects.get(utils.parseKeyString(self.domainObject.getId())).then(function (object) { self.$els = $(todoTemplate);
self.$els = $(todoTemplate); self.$buttons = {
self.$buttons = { all: self.$els.find('.example-todo-button-all'),
all: self.$els.find('.example-todo-button-all'), incomplete: self.$els.find('.example-todo-button-incomplete'),
incomplete: self.$els.find('.example-todo-button-incomplete'), complete: self.$els.find('.example-todo-button-complete')
complete: self.$els.find('.example-todo-button-complete') };
};
$(container).empty().append(self.$els); $(container).empty().append(self.$els);
>>>>>>> origin/api-tutorials
this.$el = $(todoTemplate);
this.$emptyMessage = this.$el.find('.example-no-tasks');
this.$taskList = this.$el.find('.example-todo-task-list');
this.$el.on('click', '[data-filter]', this.updateFilter.bind(this));
this.$el.on('change', 'li', this.setTaskStatus.bind(this));
this.$el.on('click', '.example-task-description', this.selectTask.bind(this));
self.initialize(); <<<<<<< HEAD
self.objectChanged(object); this.updateSelection = this.updateSelection.bind(this);
mct.selection.on('change', this.updateSelection);
}
mct.selection.on('change', self.render); TodoView.prototype.show = function (container) {
}); $(container).empty().append(this.$el);
this.render();
=======
self.initialize();
self.objectChanged(this.domainObject);
mct.selection.on('change', self.render);
>>>>>>> origin/api-tutorials
}; };
TodoView.prototype.destroy = function () { TodoView.prototype.destroy = function () {
if (this.mutableObject) { this.mutableObject.stopListening();
this.mutableObject.stopListening(); mct.selection.off('change', this.updateSelection);
}
mct.selection.off('change', this.render);
}; };
TodoView.prototype.setFilter = function (value) { TodoView.prototype.updateSelection = function (selection) {
this.filterValue = value; if (selection && selection.length) {
this.selection = selection[0].index;
} else {
this.selection = -1;
}
this.render(); this.render();
}; };
TodoView.prototype.initialize = function () { TodoView.prototype.updateTasks = function (tasks) {
Object.keys(this.$buttons).forEach(function (k) { this.tasks = tasks;
this.$buttons[k].on('click', this.setFilter.bind(this, k)); this.render();
}, this);
}; };
TodoView.prototype.render = function () { TodoView.prototype.updateFilter = function (e) {
var $els = this.$els; this.filterValue = $(e.target).data('filter');
var mutableObject = this.mutableObject; this.render();
var tasks = mutableObject.get('tasks'); };
var $message = $els.find('.example-message');
var $list = $els.find('.example-todo-task-list'); TodoView.prototype.setTaskStatus = function (e) {
var $buttons = this.$buttons; var $checkbox = $(e.target);
var filters = { var taskIndex = $checkbox.data('taskIndex');
var completed = !!$checkbox.prop('checked');
this.tasks[taskIndex].completed = completed;
this.mutableObject.set('tasks[' + taskIndex + '].checked', completed);
};
TodoView.prototype.selectTask = function (e) {
var taskIndex = $(e.target).data('taskIndex');
mct.selection.clear();
mct.selection.select({index: taskIndex});
};
TodoView.prototype.getFilteredTasks = function () {
return this.tasks.filter({
all: function () { all: function () {
return true; return true;
}, },
@ -101,94 +125,119 @@ define([
complete: function (task) { complete: function (task) {
return task.completed; return task.completed;
} }
}; }[this.filterValue]);
var filterValue = this.filterValue; };
var selected = mct.selection.selected();
Object.keys($buttons).forEach(function (k) { TodoView.prototype.render = function () {
$buttons[k].toggleClass('selected', filterValue === k); var filteredTasks = this.getFilteredTasks();
}); this.$emptyMessage.toggle(filteredTasks.length === 0);
tasks = tasks.filter(filters[filterValue]); this.$taskList.empty();
filteredTasks
.forEach(function (task) {
var $taskEl = $(taskTemplate),
taskIndex = this.tasks.indexOf(task);
$taskEl.find('.example-task-checked')
.prop('checked', task.completed)
.attr('data-task-index', taskIndex);
$taskEl.find('.example-task-description')
.text(task.description)
.toggleClass('selected', taskIndex === this.selection)
.attr('data-task-index', taskIndex);
$list.empty(); this.$taskList.append($taskEl);
tasks.forEach(function (task, index) { }, this);
var $taskEls = $(taskTemplate);
var $checkbox = $taskEls.find('.example-task-checked');
var $desc = $taskEls.find('.example-task-description');
$checkbox.prop('checked', task.completed);
$desc.text(task.description);
$checkbox.on('change', function () {
var checked = !!$checkbox.prop('checked');
mutableObject.set("tasks." + index + ".completed", checked);
});
$desc.on('click', function () {
mct.selection.clear();
mct.selection.select({ index: index });
});
if (selected.length > 0 && selected[0].index === index) {
$desc.addClass('selected');
}
$list.append($taskEls);
});
$message.toggle(tasks.length < 1);
}; };
function TodoToolbarView(domainObject) { function TodoToolbarView(domainObject) {
this.domainObject = domainObject; this.tasks = domainObject.tasks;
this.mutableObject = undefined; this.mutableObject = mct.Objects.getMutable(domainObject);
this.handleSelectionChange = this.handleSelectionChange.bind(this); this.handleSelectionChange = this.handleSelectionChange.bind(this);
this.mutableObject.on('tasks', this.updateTasks.bind(this));
mct.selection.on('change', this.handleSelectionChange);
this.$el = $(toolbarTemplate);
this.$remove = this.$el.find('.example-remove');
this.$el.on('click', '.example-add', this.addTask.bind(this));
this.$el.on('click', '.example-remove', this.removeTask.bind(this));
} }
TodoToolbarView.prototype.updateTasks = function (tasks) {
this.tasks = tasks;
};
TodoToolbarView.prototype.addTask = function () {
var $dialog = $(dialogTemplate),
view = {
show: function (container) {
$(container).append($dialog);
},
destroy: function () {}
};
mct.dialog(view, "Add a Task").then(function () {
var description = $dialog.find('input').val();
this.tasks.push({description: description});
this.mutableObject.set('tasks', this.tasks);
}.bind(this));
};
TodoToolbarView.prototype.removeTask = function () {
if (this.selection >= 0) {
this.tasks.splice(this.selection, 1);
this.mutableObject.set('tasks', this.tasks);
mct.selection.clear();
this.render();
}
};
TodoToolbarView.prototype.show = function (container) { TodoToolbarView.prototype.show = function (container) {
var self = this; var self = this;
this.destroy(); this.destroy();
this.$els = $(toolbarTemplate);
this.render();
$(container).append(this.$els);
};
mct.Objects.get(utils.parseKeyString(this.domainObject.getId())).then(function (wrappedObject){ TodoToolbarView.prototype.render = function () {
var self = this;
var $els = this.$els;
self.mutableObject = mct.Objects.getMutable(wrappedObject); self.mutableObject = mct.Objects.getMutable(this.domainObject);
var $els = $(toolbarTemplate); var $add = $els.find('a.example-add');
var $add = $els.find('a.example-add'); var $remove = $els.find('a.example-remove');
var $remove = $els.find('a.example-remove');
$(container).append($els); $add.on('click', function () {
var $dialog = $(dialogTemplate),
view = {
show: function (container) {
$(container).append($dialog);
},
destroy: function () {}
};
$add.on('click', function () { new mct.Dialog(view, "Add a Task").show().then(function () {
var $dialog = $(dialogTemplate), var description = $dialog.find('input').val();
view = { var tasks = self.mutableObject.get('tasks');
show: function (container) { tasks.push({ description: description });
$(container).append($dialog); self.mutableObject.set('tasks', tasks);
},
destroy: function () {}
};
mct.dialog(view, "Add a Task").then(function () {
var description = $dialog.find('input').val();
var tasks = self.mutableObject.get('tasks');
tasks.push({ description: description });
self.mutableObject.set('tasks', tasks);
});
}); });
$remove.on('click', function () {
var index = mct.selection.selected()[0].index;
if (index !== undefined) {
var tasks = self.mutableObject.get('tasks').filter(function (t, i) {
return i !== index;
});
self.mutableObject.set("tasks", tasks);
self.mutableObject.set("selected", undefined);
mct.selection.clear();
}
});
self.$remove = $remove;
self.handleSelectionChange();
mct.selection.on('change', self.handleSelectionChange);
}); });
$remove.on('click', function () {
var index = mct.selection.selected()[0].index;
if (index !== undefined) {
var tasks = self.mutableObject.get('tasks').filter(function (t, i) {
return i !== index;
});
self.mutableObject.set("tasks", tasks);
self.mutableObject.set("selected", undefined);
mct.selection.clear();
}
});
self.$remove = $remove;
self.handleSelectionChange();
mct.selection.on('change', self.handleSelectionChange);
}; };
TodoToolbarView.prototype.handleSelectionChange = function () { TodoToolbarView.prototype.handleSelectionChange = function () {
@ -196,14 +245,12 @@ define([
if (this.$remove) { if (this.$remove) {
this.$remove.toggle(selected.length > 0); this.$remove.toggle(selected.length > 0);
} }
this.render();
}; };
TodoToolbarView.prototype.destroy = function () { TodoToolbarView.prototype.destroy = function () {
mct.selection.off('change', this.handleSelectionChange); mct.selection.off('change', this.handleSelectionChange);
this.$remove = undefined; this.mutableObject.stopListening();
if (this.mutableObject) {
this.mutableObject.stopListening();
}
}; };
mct.type('example.todo', todoType); mct.type('example.todo', todoType);