Merge pull request #1028 from nasa/api-view

[API Prototype] Support imperative view registration
This commit is contained in:
Victor Woeltjen 2016-07-01 10:09:29 -07:00 committed by GitHub
commit 14a56ea17e
11 changed files with 278 additions and 47 deletions

View File

@ -65,6 +65,8 @@ define([
'legacyRegistry',
'./src/MCT',
'./src/adapter/bundle',
'./platform/framework/bundle',
'./platform/core/bundle',
'./platform/representation/bundle',

View File

@ -1,11 +1,13 @@
define([
'EventEmitter',
'legacyRegistry',
'uuid',
'./api/api',
'./api/objects/bundle'
], function (
EventEmitter,
legacyRegistry,
uuid,
api
) {
function MCT() {
@ -18,6 +20,45 @@ define([
Object.keys(api).forEach(function (k) {
MCT.prototype[k] = api[k];
});
MCT.prototype.MCT = MCT;
MCT.prototype.view = function (region, factory) {
var viewKey = region + uuid();
var adaptedViewKey = "adapted-view-" + viewKey;
this.legacyBundle.extensions.views =
this.legacyBundle.extensions.views || [];
this.legacyBundle.extensions.views.push({
name: "A view",
key: adaptedViewKey,
template: '<mct-view key="\'' +
viewKey +
'\'" ' +
'mct-object="domainObject">' +
'</mct-view>'
});
this.legacyBundle.extensions.policies =
this.legacyBundle.extensions.policies || [];
this.legacyBundle.extensions.policies.push({
category: "view",
implementation: function Policy() {
this.allow = function (view, domainObject) {
if (view.key === adaptedViewKey) {
return !!factory(domainObject);
}
return true;
};
}
});
this.legacyBundle.extensions.newViews =
this.legacyBundle.extensions.newViews || [];
this.legacyBundle.extensions.newViews.push({
factory: factory,
key: viewKey
});
};
MCT.prototype.type = function (key, type) {
var legacyDef = type.toLegacyDefinition();
@ -25,6 +66,8 @@ define([
this.legacyBundle.extensions.types =
this.legacyBundle.extensions.types || [];
this.legacyBundle.extensions.types.push(legacyDef);
type.key = key;
};
MCT.prototype.start = function () {
@ -32,5 +75,19 @@ define([
this.emit('start');
};
MCT.prototype.regions = {
main: "MAIN"
};
MCT.prototype.verbs = {
mutate: function (domainObject, mutator) {
return domainObject.useCapability('mutation', mutator)
.then(function () {
var persistence = domainObject.getCapability('persistence');
return persistence.persist();
});
}
};
return MCT;
});

16
src/adapter/bundle.js Normal file
View File

@ -0,0 +1,16 @@
define([
'legacyRegistry',
'./directives/MCTView'
], function (legacyRegistry, MCTView) {
legacyRegistry.register('src/adapter', {
"extensions": {
"directives": [
{
key: "mctView",
implementation: MCTView,
depends: ["newViews[]"]
}
]
}
});
});

View File

@ -0,0 +1,49 @@
define(['angular'], function (angular) {
function MCTView(newViews) {
var factories = {};
newViews.forEach(function (newView) {
factories[newView.key] = newView.factory;
});
return {
restrict: 'E',
link: function (scope, element, attrs) {
var key = undefined;
var mctObject = undefined;
function maybeShow() {
if (!factories[key]) {
return;
}
if (!mctObject) {
return;
}
var view = factories[key](mctObject);
view.show(element[0]);
}
function setKey(k) {
key = k;
maybeShow();
}
function setObject(obj) {
mctObject = obj;
maybeShow();
}
scope.$watch('key', setKey);
scope.$watch('mctObject', setObject);
},
scope: {
key: "=",
mctObject: "="
}
};
}
return MCTView;
});

View File

@ -17,6 +17,16 @@ define(function () {
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
* legacy bundle format.

24
src/api/View.js Normal file
View 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;
});

View File

@ -1,12 +1,15 @@
define([
'./Type',
'./View',
'./objects/ObjectAPI'
], function (
Type,
View,
ObjectAPI
) {
return {
Type: Type,
View: View,
Objects: ObjectAPI
};
});

View File

@ -9,51 +9,11 @@ define([
"name": "To-do Plugin",
"description": "Allows creating and editing to-do lists.",
"extensions": {
"views": [
"stylesheets": [
{
"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"
}
]
}
]
}
"stylesheetUrl": "css/todo.css"
}
],
"controllers": [
{
"key": "TodoController",
"implementation": TodoController,
"depends": [ "$scope", "dialogService" ]
}
],
"stylesheets": [
{
"stylesheetUrl": "css/todo.css"
}
]
}
});
]
}
});
});

View File

@ -0,0 +1,5 @@
<li>
<input type="checkbox" class="example-task-checked">
<span class="example-task-description">
</span>
</li>

14
tutorials/todo/todo.html Normal file
View 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>

View File

@ -1,4 +1,11 @@
define(function () {
define([
"text!./todo.html",
"text!./todo-task.html",
"zepto"
], function (todoTemplate, taskTemplate, $) {
/**
* @param {mct.MCT} mct
*/
return function todoPlugin(mct) {
var todoType = new mct.Type({
metadata: {
@ -7,12 +14,96 @@ define(function () {
description: "A list of things that need to be done."
},
initialize: function (model) {
model.tasks = [];
model.tasks = [
{ description: "This is a task." }
];
},
creatable: true
});
function TodoView(domainObject) {
this.domainObject = domainObject;
this.filterValue = "all";
}
TodoView.prototype.show = function (container) {
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();
};
TodoView.prototype.destroy = function () {
};
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;
Object.keys($buttons).forEach(function (k) {
$buttons[k].toggleClass('selected', filterValue === k);
});
tasks = tasks.filter(filters[filterValue]);
$list.empty();
tasks.forEach(function (task, index) {
var $taskEls = $(taskTemplate);
var $checkbox = $taskEls.find('.example-task-checked');
$checkbox.prop('checked', task.completed);
$taskEls.find('.example-task-description')
.text(task.description);
$checkbox.on('change', function () {
var checked = !!$checkbox.prop('checked');
mct.verbs.mutate(domainObject, function (model) {
model.tasks[index].completed = checked;
});
});
$list.append($taskEls);
});
$message.toggle(tasks.length < 1);
};
mct.type('example.todo', todoType);
mct.view(mct.regions.main, function (domainObject) {
return todoType.check(domainObject) && new TodoView(domainObject);
});
return mct;
};