Compare commits

...

6 Commits

Author SHA1 Message Date
9c033ad5fd WIP implemented stub for adding gestures 2017-07-17 15:58:57 -07:00
aa27fd6f9c WIP navigation using window.location is implemented. no testing done. seems crude, but works. 2017-07-14 16:12:52 -07:00
1f0a1a2bca WIP View for table exists and the ability to organize the table exists. navigating to the elements in the list does not function as well as the ability to view info and a context menu for a given item. 2017-07-14 13:44:02 -07:00
0138d9001d WIP 2017-07-13 01:19:29 -07:00
4e323ac6af WIP 2017-07-12 15:17:54 -07:00
ba98d9315c [ViewAPI] Update view API with more support
Update view provider to allow metadata definitions and to play
nicely with old style views.

Spec out some updates to ViewProviders and ViewRegistry to
support further use of views.
2017-07-05 13:56:32 -07:00
11 changed files with 433 additions and 97 deletions

View File

@ -66,6 +66,8 @@
}));
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.install(openmct.plugins.ListView(openmct));
this.openmct = openmct;
openmct.start();
});
</script>

View File

@ -106,9 +106,9 @@ define([
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name mainViews
* @name objectViews
*/
this.mainViews = new ViewRegistry();
this.objectViews = new ViewRegistry();
/**
* Registry for views which should appear in the Inspector area.
@ -255,6 +255,18 @@ define([
this.legacyExtension('types', legacyDefinition);
}.bind(this));
this.objectViews.providers.forEach(function (p) {
this.legacyExtension('views', {
key: '_vpid' + p._vpid,
vpid: p._vpid,
provider: p,
name: p.name,
cssClass: p.cssClass,
description: p.description,
template: '<mct-view mct-vpid="' + p._vpid + '"/>'
});
}, this);
legacyRegistry.register('adapter', this.legacyBundle);
legacyRegistry.enable('adapter');
/**

View File

@ -24,7 +24,6 @@ define([
'legacyRegistry',
'./actions/ActionDialogDecorator',
'./capabilities/AdapterCapability',
'./controllers/AdaptedViewController',
'./directives/MCTView',
'./services/Instantiate',
'./services/MissingModelCompatibilityDecorator',
@ -33,12 +32,10 @@ define([
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler',
'text!./templates/adapted-view-template.html'
], function (
legacyRegistry,
ActionDialogDecorator,
AdapterCapability,
AdaptedViewController,
MCTView,
Instantiate,
MissingModelCompatibilityDecorator,
@ -47,14 +44,14 @@ define([
AdaptedViewPolicy,
AlternateCompositionInitializer,
TimeSettingsURLHandler,
adaptedViewTemplate
) {
legacyRegistry.register('src/adapter', {
"extensions": {
"directives": [
{
key: "mctView",
implementation: MCTView
implementation: MCTView,
depends: ["openmct"]
}
],
capabilities: [
@ -63,16 +60,6 @@ define([
implementation: AdapterCapability
}
],
controllers: [
{
key: "AdaptedViewController",
implementation: AdaptedViewController,
depends: [
'$scope',
'openmct'
]
}
],
services: [
{
key: "instantiate",
@ -135,12 +122,6 @@ define([
depends: ["openmct", "$location", "$rootScope"]
}
],
views: [
{
key: "adapted-view",
template: adaptedViewTemplate
}
],
licenses: [
{
"name": "almond",

View File

@ -22,10 +22,12 @@
define([
'./synchronizeMutationCapability',
'./AlternateCompositionCapability'
'./AlternateCompositionCapability',
'./patchViewCapability'
], function (
synchronizeMutationCapability,
AlternateCompositionCapability
AlternateCompositionCapability,
patchViewCapability
) {
/**
@ -46,6 +48,9 @@ define([
capabilities.mutation =
synchronizeMutationCapability(capabilities.mutation);
}
if (capabilities.view) {
capabilities.view = patchViewCapability(capabilities.view);
}
if (AlternateCompositionCapability.appliesTo(model, id)) {
capabilities.composition = function (domainObject) {
return new AlternateCompositionCapability(this.$injector, domainObject);

View File

@ -20,21 +20,42 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function AdaptedViewController($scope, openmct) {
function refresh(legacyObject) {
if (!legacyObject) {
$scope.view = undefined;
return;
define([
'lodash'
], function (
_
) {
function patchViewCapability(viewConstructor) {
return function makeCapability(domainObject) {
var capability = viewConstructor(domainObject);
var oldInvoke = capability.invoke.bind(capability);
capability.invoke = function () {
var availableViews = oldInvoke();
var newDomainObject = capability
.domainObject
.useCapability('adapter');
return _(availableViews).map(function (v, i) {
var vd = {
view: v,
priority: i + 100 // arbitrary to allow new views to
// be defaults by returning priority less than 100.
};
if (v.provider) {
vd.priority = v.provider.canView(newDomainObject);
}
return vd;
})
.sortBy('priority')
.map('view')
.value();
}
return capability;
}
var domainObject = legacyObject.useCapability('adapter');
var providers = openmct.mainViews.get(domainObject);
$scope.view = providers[0] && providers[0].view(domainObject);
}
$scope.$watch('domainObject', refresh);
}
return AdaptedViewController;
return patchViewCapability;
});

View File

@ -21,18 +21,20 @@
*****************************************************************************/
define([
'angular',
'./Region'
], function (
angular,
Region
) {
function MCTView() {
function MCTView(openmct) {
return {
restrict: 'A',
restrict: 'E',
link: function (scope, element, attrs) {
var region = new Region(element[0]);
scope.$watch(attrs.mctView, region.show.bind(region));
var provider = openmct.objectViews._getByVPID(Number(attrs.mctVpid));
var view = new provider.view(scope.domainObject.useCapability('adapter'));
view.show(element[0]);
if (view.destroy) {
scope.$on('$destroy', function () {
view.destroy(element[0])
});
}
}
};
}

View File

@ -29,9 +29,9 @@ define([], function () {
view,
legacyObject
) {
if (view.key === 'adapted-view') {
if (view.hasOwnProperty('vpid')) {
var domainObject = legacyObject.useCapability('adapter');
return this.openmct.mainViews.get(domainObject).length > 0;
return view.provider.canView(domainObject);
}
return true;
};

View File

@ -0,0 +1,314 @@
/*****************************************************************************
* 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([
'zepto',
'../../../../platform/core/src/capabilities/ContextualDomainObject.js',
'../../api/objects/object-utils.js'
], function (
$,
ContextualDomainObject,
objectUtils
) {
/**
* Displays folders in a list view.
* @constructor
*/
function ListView(openmct) {
var createTable = function(container,domainObject) {
var element = document.createElement('table');
element.classList.add('list-view');
createTableHeader(element);
createTableBody(element, domainObject);
element.addEventListener("click", function(event){
var tableRef = event.currentTarget;
var headerRef = event.target;
if(headerRef.tagName ==="TH"){
sortTableByHeader(tableRef,headerRef);//current target is table element, target is th element
};
},false);
initialTableSort(element);
container.appendChild(element);
}
var createTableHeader = function (parentElement) {
var headElement = document.createElement('thead');
var rowElement = document.createElement('tr');
//populate table header with column names
[
{title:'Name', order:'asc'},
{title:'Type', order:'asc'},
{title:'Created Date', order:'desc'},
{title:'Update Date', order:'desc'}
].forEach(function (columnInfo, index){
var element = document.createElement('th');
element.classList.add('sortable');
element.setAttribute('col', index);
element.setAttribute('order', columnInfo.order);
element.appendChild(document.createTextNode(columnInfo.title));
rowElement.appendChild(element);
});
headElement.appendChild(rowElement);
parentElement.appendChild(headElement);
}
var createTableBody = function (parentElement, domainObject) {
var tbodyElement = document.createElement('tbody');
openmct.composition.get(domainObject).load().then(
function(compositions){
compositions.map(function(child){
var instantiate = this.openmct.$injector.get('instantiate');
var childKeystring = objectUtils.makeKeyString(child.identifier);
var childOldformat = objectUtils.toOldFormat(child);
var childOld = instantiate(childOldformat, childKeystring);
//can't access globalDomainObject TODO: figure out how to access it.
// var parentKeystring = this.objectUtils.makeKeyString(globalDomainObject.identifier);
// var parentOldformat = this.objectUtils.toOldFormat(globalDomainObject);
// var parentOld = instantiate(parentOldformat, parentKeystring);
//
// var contextObject = new ContextualDomainObject(childOld, parentOld);
return {
icon: childOld.getCapability('type').getCssClass(),
title: childOld.getModel().name,
type: childOld.getCapability('type').getName(),
persisted: new Date(
childOld.getModel().persisted
).toUTCString(),
modified: new Date(
childOld.getModel().modified
).toUTCString(),
asDomainObject: childOld,
location: childOld.getCapability('location'),
action: childOld.getCapability('action')
}
}).forEach(function (child){
createRow(tbodyElement, child, domainObject);
});
},
function(err){
console.log(err);
}
)
parentElement.appendChild(tbodyElement);
}
var createRow = function(parentElement, domainObject, parentDomainObject){
var rowElement = document.createElement('tr');
addIconAndTitle(rowElement, domainObject);
addType(rowElement, domainObject);
addPersistedValue(rowElement, domainObject);
addModifiedValue(rowElement, domainObject);
//TODO: implement the navigate functionality.
//currently we are using $injector for angular and getting $location.
//this allows us to get the path within the application.
//this may be something along the lines of an internal view hierarchy.
//not view hierarchy but a hierarchy of subfolder.
//what belongs to what.
rowElement.addEventListener('click',function(){
var l =openmct.$injector.get('$location');
var domainObjectId = domainObject.asDomainObject.getId();
//l.path(l.path() + '/' + domainObject.asDomainObject.getId())
var hash = window.location.hash;
var pathWithoutStuff = hash.split('?')[0];
window.location = pathWithoutStuff +'/' + domainObjectId;
// debugger;
// domainObject.action.perform('navigate');
});
openmct.gestures.contextMenu(rowElement, domainObject.asDomainObject, parentDomainObject);
openmct.gestures.info(rowElement, domainObject.asDomainObject, parentDomainObject);
parentElement.appendChild(rowElement);
}
var addIconAndTitle = function(parentElement, domainObject){
var tdElement = document.createElement('td');
var divElement = document.createElement('div')
divElement.classList.add('l-flex-row');
createIconElement(divElement, domainObject);
createTitleElement(divElement, domainObject);
tdElement.appendChild(divElement);
parentElement.appendChild(tdElement);
}
var createIconElement = function(parentElement, domainObject){
var wrapElement = document.createElement('span');
wrapElement.classList.add(...['flex-elem', 't-item-icon']);
var element = document.createElement('span')
element.classList.add(...['t-item-icon-glyph', domainObject.icon]);
wrapElement.appendChild(element);
parentElement.appendChild(wrapElement);
}
var createTitleElement = function(parentElement, domainObject){
var element = document.createElement('span');
element.classList.add(...['t-title-label', 'flex-elem', 'grows']);
element.appendChild(document.createTextNode(domainObject.title));
parentElement.appendChild(element);
}
var addType = function(parentElement, domainObject){
var element = document.createElement('td');
element.appendChild(document.createTextNode(domainObject.type));
parentElement.appendChild(element);
}
var addPersistedValue = function(parentElement, domainObject){
var element = document.createElement('td');
element.appendChild(document.createTextNode(domainObject.persisted));
parentElement.appendChild(element);
}
var addModifiedValue = function(parentElement, domainObject){
var element = document.createElement('td');
element.appendChild(document.createTextNode(domainObject.modified));
parentElement.appendChild(element);
}
// function sortTable(table, col, reverse) {
// var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
// tr = Array.prototype.slice.call(tb.rows, 0), // put rows into array
// i;
// reverse = -((+reverse) || -1);
// tr = tr.sort(function (a, b) { // sort rows
// return reverse // `-1 *` if want opposite order
// * (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
// .localeCompare(b.cells[col].textContent.trim())
// );
// });
// for(i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); // append each row in order
// debugger;
// }
//
// function makeSortable(table) {
// var th = table.tHead, i;
// th && (th = th.rows[0]) && (th = th.cells);
// if (th) i = th.length;
// else return; // if no `<thead>` then do nothing
// while (--i >= 0) (function (i) {
// var dir = 1;
// //backwards iteration through the rows. therefore time are the last two
// if(i==0||i==1){
// th[i].addEventListener('click', function () {sortTable(table, i, -1)});
// }else{
// th[i].addEventListener('click', function () {sortTable(table, i, 1)});
// }
// //th[i].addEventListener('click', function () {sortTable(table, i, (dir = 1 - dir))});
//
// }(i));
// }
// function makeAllSortable(parent) {
// parent = parent || document.body;
// var t = parent.getElementsByTagName('table'), i = t.length;
// while (--i >= 0) makeSortable(t[i]);
// }
function sortTableByHeader(table,header){
// debugger;
var col = header.getAttribute('col');
var order = header.getAttribute('order');
if(header.classList.contains('sort')){
var ascLast = header.classList.contains('asc');
clearTableHeaders(table);
header.classList.add('sort');
if(ascLast){
reverse = -1;
header.classList.add('desc');
}else{
reverse = 1;
header.classList.add('asc');
}
}else{
clearTableHeaders(table);
header.classList.add('sort');
if (order === 'asc'){
reverse = 1;
header.classList.add('asc')
}else{
reverse = -1;
header.classList.add('desc')
}
}
var body = table.tBodies[0];
var rows = Array.prototype.slice.call(body.rows, 0);
rows = rows.sort(function (a, b) { // sort rows
return reverse // `-1 *` if want opposite order
* (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
.localeCompare(b.cells[col].textContent.trim())
);
});
for(i = 0; i < rows.length; ++i) body.appendChild(rows[i]);
}
function initialTableSort(table){
var col = 0;
var reserve = 1;
table.firstElementChild.firstElementChild.firstElementChild.classList.add('sort','asc');
var body = table.tBodies[0];
var rows = Array.prototype.slice.call(body.rows, 0);
rows = rows.sort(function (a, b) { // sort rows
return reverse // `-1 *` if want opposite order
* (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
.localeCompare(b.cells[col].textContent.trim())
);
});
for(i = 0; i < rows.length; ++i) body.appendChild(rows[i]);
}
function clearTableHeaders(table){
var tableHeadersHTMLCollection = table.firstElementChild.firstElementChild.children;
var tableHeaders = Array.prototype.slice.call(tableHeadersHTMLCollection);
tableHeaders.forEach(function(headerElement, index){
//remove sort from all
headerElement.classList.remove('sort','desc','asc');
});
}
//Object defining the view
return {
name: 'listview',
cssClass: 'icon-list-view',
canView: function (d) {
return d.type === 'folder' && 150;
},
view: function (domainObject) {
return {
show: function (container) {
createTable(container, domainObject);
},
destroy: function (container) {
}
}
}
}
}
return ListView;
});

View File

@ -1,9 +1,9 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* 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 is licensed under the Apache License, Version 2.0 (the
* 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.
@ -14,32 +14,26 @@
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* 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([], function () {
function Region(element) {
this.activeView = undefined;
this.element = element;
}
Region.prototype.clear = function () {
if (this.activeView) {
this.activeView.destroy();
this.activeView = undefined;
}
define([
"./ListView.js"
], function (
ListView
) {
return function ListViewPlugin() {
return function install(openmct) {
console.log("registering listview");
openmct.objectViews.addProvider(new ListView(openmct));
// openmct.legacyExtension('listviewNEWAPI', {
// key: 'folder',
// implementation: ListView,
// depends: ['openmct']
// });
};
Region.prototype.show = function (view) {
this.clear();
this.activeView = view;
if (this.activeView) {
this.activeView.show(this.element);
}
};
return Region;
});

View File

@ -26,14 +26,16 @@ define([
'../../example/generator/plugin',
'../../platform/features/autoflow/plugin',
'./timeConductor/plugin',
'../../example/imagery/plugin'
'../../example/imagery/plugin',
'./listview/plugin'
], function (
_,
UTCTimeSystem,
GeneratorPlugin,
AutoflowPlugin,
TimeConductorPlugin,
ExampleImagery
ExampleImagery,
ListView
) {
var bundleMap = {
CouchDB: 'platform/persistence/couch',
@ -54,6 +56,8 @@ define([
plugins.UTCTimeSystem = UTCTimeSystem;
plugins.ListView = ListView;
/**
* A tabular view showing the latest values of multiple telemetry points at
* once. Formatted so that labels and values are aligned.

View File

@ -28,6 +28,7 @@ define([], function () {
* @memberof module:openmct
*/
function ViewRegistry() {
this._next_id = 0;
this.providers = [];
}
@ -40,7 +41,8 @@ define([], function () {
*/
ViewRegistry.prototype.get = function (item) {
return this.providers.filter(function (provider) {
return provider.canView(item);
return typeof provider.canView(item) !== 'undefined' &&
provider.canView(item) !== false;
});
};
@ -52,9 +54,21 @@ define([], function () {
* @memberof module:openmct.ViewRegistry#
*/
ViewRegistry.prototype.addProvider = function (provider) {
provider._vpid = this._next_id++;
this.providers.push(provider);
};
/**
* Used internally to support seamless usage of new views with old
* views.
* @private
*/
ViewRegistry.prototype._getByVPID = function (vpid) {
return this.providers.filter(function (p) {
return p._vpid === vpid;
})[0]
};
/**
* A View is used to provide displayable content, and to react to
* associated life cycle events.
@ -91,6 +105,11 @@ define([], function () {
* Exposes types of views in Open MCT.
*
* @interface ViewProvider
* @property {string} name the human-readable name of this view
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} [cssClass] the CSS class to apply to labels for this
* view (to add icons, for instance)
* @memberof module:openmct
*/
@ -107,8 +126,11 @@ define([], function () {
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be viewed
* @returns {boolean} true if this domain object can be viewed using
* this provider
* @returns {Number|boolean} if this returns `false`, then the view does
* not apply to the object. If it returns true or any number, then
* it applies to this object. If multiple views could apply
* to an object, the view that returns the lowest number will be
* the default view.
*/
/**
@ -126,27 +148,6 @@ define([], function () {
* @returns {module:openmct.View} a view of this domain object
*/
/**
* Get metadata associated with this view provider. This may be used
* to populate the user interface with options associated with this
* view provider.
*
* @method metadata
* @memberof module:openmct.ViewProvider#
* @returns {module:openmct.ViewProvider~ViewMetadata} view metadata
*/
/**
* @typedef ViewMetadata
* @memberof module:openmct.ViewProvider~
* @property {string} name the human-readable name of this view
* @property {string} key a machine-readable name for this view
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} cssClass the CSS class to apply to labels for this
* view (to add icons, for instance)
*/
return ViewRegistry;
});