diff --git a/index.html b/index.html index d8297d3d4a..685c9c20d8 100644 --- a/index.html +++ b/index.html @@ -35,8 +35,8 @@ './example/imagery/bundle', './example/eventGenerator/bundle', './example/generator/bundle' - ], function (TodoPlugin) { - mct.install(TodoPlugin); + ], function (todoPlugin) { + mct.install(todoPlugin); mct.run(); }) }); diff --git a/src/MCT.js b/src/MCT.js index 8d44db17a0..18c46acb5c 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -4,7 +4,6 @@ define([ 'uuid', './api/api', 'text!./adapter/templates/edit-object-replacement.html', - './ui/Dialog', './Selection', './api/objects/object-utils' ], function ( @@ -13,13 +12,21 @@ define([ uuid, api, editObjectTemplate, - Dialog, Selection, objectUtils ) { function MCT() { EventEmitter.call(this); - this.legacyBundle = { extensions: {} }; + this.legacyBundle = { extensions: { + services: [ + { + key: "mct", + implementation: function () { + return this; + }.bind(this) + } + ] + } }; this.selection = new Selection(); this.on('navigation', this.selection.clear.bind(this.selection)); @@ -94,13 +101,6 @@ define([ region: region, key: viewKey }); - - this.legacyExtension('services', { - key: 'PublicAPI', - implementation: function () { - return this; - }.bind(this) - }); }; MCT.prototype.type = function (key, type) { @@ -117,10 +117,6 @@ define([ }); }; - MCT.prototype.dialog = function (view, title) { - return new Dialog(view, title).show(); - }; - MCT.prototype.start = function () { this.legacyExtension('runs', { depends: ['navigationService'], @@ -147,6 +143,7 @@ define([ MCT.prototype.regions = { main: "MAIN", + properties: "PROPERTIES", toolbar: "TOOLBAR" }; diff --git a/src/adapter/actions/ActionDialogDecorator.js b/src/adapter/actions/ActionDialogDecorator.js new file mode 100644 index 0000000000..60720085af --- /dev/null +++ b/src/adapter/actions/ActionDialogDecorator.js @@ -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; +}); diff --git a/src/adapter/bundle.js b/src/adapter/bundle.js index 30c18594ec..fcd9a3d380 100644 --- a/src/adapter/bundle.js +++ b/src/adapter/bundle.js @@ -1,13 +1,17 @@ define([ 'legacyRegistry', + './actions/ActionDialogDecorator', './directives/MCTView', './services/Instantiate', - './capabilities/APICapabilityDecorator' + './capabilities/APICapabilityDecorator', + './policies/AdapterCompositionPolicy' ], function ( legacyRegistry, + ActionDialogDecorator, MCTView, Instantiate, - APICapabilityDecorator + APICapabilityDecorator, + AdapterCompositionPolicy ) { legacyRegistry.register('src/adapter', { "extensions": { @@ -17,7 +21,7 @@ define([ implementation: MCTView, depends: [ "newViews[]", - "PublicAPI" + "mct" ] } ], @@ -41,6 +45,19 @@ define([ depends: [ "$injector" ] + }, + { + type: "decorator", + provides: "actionService", + implementation: ActionDialogDecorator, + depends: [ "mct", "newViews[]" ] + } + ], + policies: [ + { + category: "composition", + implementation: AdapterCompositionPolicy, + depends: [ "mct" ] } ] } diff --git a/src/adapter/policies/AdapterCompositionPolicy.js b/src/adapter/policies/AdapterCompositionPolicy.js new file mode 100644 index 0000000000..4cad9ce674 --- /dev/null +++ b/src/adapter/policies/AdapterCompositionPolicy.js @@ -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; +}); diff --git a/src/api/api.js b/src/api/api.js index 4c221c3f13..911a52669f 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -3,19 +3,22 @@ define([ './TimeConductor', './View', './objects/ObjectAPI', - './composition/CompositionAPI' + './composition/CompositionAPI', + './ui/Dialog' ], function ( Type, TimeConductor, View, ObjectAPI, - CompositionAPI + CompositionAPI, + Dialog ) { return { Type: Type, TimeConductor: new TimeConductor(), View: View, Objects: ObjectAPI, - Composition: CompositionAPI + Composition: CompositionAPI, + Dialog: Dialog }; }); diff --git a/src/api/composition/CompositionCollection.js b/src/api/composition/CompositionCollection.js index 9ab216e2e3..591085a629 100644 --- a/src/api/composition/CompositionCollection.js +++ b/src/api/composition/CompositionCollection.js @@ -52,6 +52,9 @@ define([ if (!this._children) { 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 (skipMutate) { return; // don't add twice, don't error. @@ -94,6 +97,10 @@ define([ } }; + CompositionCollection.prototype.canContain = function (domainObject) { + return this.provider.canContain(this.domainObject, domainObject); + }; + CompositionCollection.prototype.destroy = function () { if (this.provider.off) { this.provider.off( diff --git a/src/api/composition/DefaultCompositionProvider.js b/src/api/composition/DefaultCompositionProvider.js index d5cfb610cb..d935bf7a54 100644 --- a/src/api/composition/DefaultCompositionProvider.js +++ b/src/api/composition/DefaultCompositionProvider.js @@ -59,6 +59,10 @@ define([ ); }; + DefaultCompositionProvider.prototype.canContain = function (domainObject, child) { + return true; + }; + DefaultCompositionProvider.prototype.remove = function (domainObject, child) { // TODO: this needs to be synchronized via mutation var index = domainObject.composition.indexOf(child); diff --git a/src/ui/Dialog.js b/src/api/ui/Dialog.js similarity index 50% rename from src/ui/Dialog.js rename to src/api/ui/Dialog.js index 9f462fb444..c8371cb67e 100644 --- a/src/ui/Dialog.js +++ b/src/api/ui/Dialog.js @@ -2,9 +2,15 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) { function Dialog(view, title) { this.view = view; this.title = title; + this.showing = false; + this.enabledState = true; } Dialog.prototype.show = function () { + if (this.showing) { + throw new Error("Dialog already showing."); + } + var $body = $('body'); var $dialog = $(dialogTemplate); var $contents = $dialog.find('.contents .editor'); @@ -13,31 +19,45 @@ define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) { 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]); + 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) { $ok.on('click', resolve); - $ok.on('click', dismiss); - $cancel.on('click', reject); - $cancel.on('click', dismiss); - $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; }); diff --git a/src/ui/dialog.html b/src/api/ui/dialog.html similarity index 100% rename from src/ui/dialog.html rename to src/api/ui/dialog.html diff --git a/tutorials/todo/todo.js b/tutorials/todo/todo.js index 4b3be4cfbe..9077683223 100644 --- a/tutorials/todo/todo.js +++ b/tutorials/todo/todo.js @@ -31,8 +31,27 @@ define([ this.setTaskStatus = this.setTaskStatus.bind(this); this.selectTask = this.selectTask.bind(this); +<<<<<<< HEAD this.mutableObject = mct.Objects.getMutable(domainObject); this.mutableObject.on('tasks', this.updateTasks.bind(this)); +======= + //If anything on object changes, re-render view + this.mutableObject.on("*", this.objectChanged); + }; + + TodoView.prototype.show = function (container) { + var self = this; + this.destroy(); + + self.$els = $(todoTemplate); + self.$buttons = { + all: self.$els.find('.example-todo-button-all'), + incomplete: self.$els.find('.example-todo-button-incomplete'), + complete: self.$els.find('.example-todo-button-complete') + }; + + $(container).empty().append(self.$els); +>>>>>>> origin/api-tutorials this.$el = $(todoTemplate); this.$emptyMessage = this.$el.find('.example-no-tasks'); @@ -41,6 +60,7 @@ define([ this.$el.on('change', 'li', this.setTaskStatus.bind(this)); this.$el.on('click', '.example-task-description', this.selectTask.bind(this)); +<<<<<<< HEAD this.updateSelection = this.updateSelection.bind(this); mct.selection.on('change', this.updateSelection); } @@ -48,6 +68,12 @@ define([ 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 () { @@ -166,19 +192,58 @@ define([ }; TodoToolbarView.prototype.show = function (container) { - $(container).empty().append(this.$el); + var self = this; + this.destroy(); + this.$els = $(toolbarTemplate); this.render(); + $(container).append(this.$els); }; TodoToolbarView.prototype.render = function () { - this.$remove.toggle(this.selection >= 0); + var self = this; + var $els = this.$els; + + self.mutableObject = mct.Objects.getMutable(this.domainObject); + + var $add = $els.find('a.example-add'); + var $remove = $els.find('a.example-remove'); + + $add.on('click', function () { + var $dialog = $(dialogTemplate), + view = { + show: function (container) { + $(container).append($dialog); + }, + destroy: function () {} + }; + + new mct.Dialog(view, "Add a Task").show().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); }; - TodoToolbarView.prototype.handleSelectionChange = function (selection) { - if (selection && selection.length) { - this.selection = selection[0].index; - } else { - this.selection = -1; + TodoToolbarView.prototype.handleSelectionChange = function () { + var selected = mct.selection.selected(); + if (this.$remove) { + this.$remove.toggle(selected.length > 0); } this.render(); };