mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 17:57:04 +00:00
[Autoflow] Rewrite Autoflow Tabular using new APIs (#1816)
Rewrite Autoflow tabular using Vue and all new telemetry APIs. Also adds LAD support to autoflow tabular. * [Autoflow] Add Vue dependency ...to begin refactor away from Angular, #1810 * [Autoflow] Add Vue to require config ...to support usage in refactoring Autoflow Tabular view. * [Autoflow] Sketch in new plugin registration * [Autoflow] Bring over template, without Angular * [Autoflow] Add license headers * [Autoflow] Add VueView ...to simplify addition of Vue-based views. * [Autoflow] Add Vue bindings to template * [Autoflow] Sketch in AutoflowTabularView * [Autoflow] Include title for row names * [Autoflow] Begin adding controller * [Autoflow] Sketch in controller functionality * [Autoflow] Support object filtering * [Autoflow] Unlisten from controller on destroy * [Autoflow] Track rows on an interval * [Autoflow] Support column width changes * [Autoflow] Expose new plugin through openmct.plugins * [Autoflow] Fix run-time errors instantiating view * [Autoflow] Fix row generation error * [Autoflow] Fix row formatting * [Autoflow] Utilize width * [Autoflow] Update autoflow view when filter changes * [Autoflow] Enable autoflow for telemetry panels ...in developer environment. * [Autoflow] Bind data-value for rows * [Autoflow] Include limit evaluations * [Autoflow] Rename property rows to rowCount * [Autoflow] Retain rows during update * [Autoflow] Add bindings to clear autoflow filter * [Autoflow] Show updated timestamp * [Autoflow] Remove obsolete plugin * [Autoflow] Load vue for tests * [Autoflow] Begin adding spec for autoflow tabular plugin * [Autoflow] Test plugin registration * [Autoflow] Begin spec for AutoflowTabularView * [Autoflow] Obey contract from VueView.show ...by populating a container, instead of replacing it. * [Autoflow] Begin testing behavior * [Autoflow] Get initial row heights * [Autoflow] Verify unsubscription on destroy * [Autoflow] Test column width button * [Autoflow] Simplify controller activation/destruction * [Autoflow] Verify data display * [Autoflow] Test limit display * [Autoflow] Fully initialize controller * [Autoflow] Add missing semicolon * [Autoflow] Separate out constants ...to access them from tests * [Autoflow] Use constants from spec * [Autoflow] Test autoflow behavior * [Autoflow] Refactor test case ...to support tests for composition changes * [Autoflow] Add test cases for composition change * [Autoflow] Handle composition changes * [Autoflow] Sketch in row controller https://github.com/nasa/openmct/pull/1816/files#r153015544 * [Autoflow] Integrate row controller https://github.com/nasa/openmct/pull/1816#pullrequestreview-79305103 * [Autoflow] Add tests for historical request * [Autoflow] Request historical telemetry * [Autoflow] Remove unused active flag * [Autoflow] Clarify row destruction ...to avoid problems with binding destroy * [Autoflow] Fix mistake in test * [Autoflow] Simplify waiting for view updates in test * [Autoflow] Move filtering, autoflow to view * [Autoflow] Remove unused caching * [Autoflow] Remove obsolete method reference * [Autoflow] Fix lint errors Add missing semicolon, remove unused vars * [Autoflow] Refactor test to simplify emitting events * [Autoflow] Emit add events during load for testing ...to simulate the actual behavior of this method. * [Autoflow] Provide composition in mock ...to allow constructor-time usage of dependency from controller * [Autoflow] Avoid intermittent errors ...by checking to see if tabularArea is available before accessing its clientHeight; depending on the timing of setInterval versus Vue's mount event, it may not be! * [Autoflow] Use add/remove composition events from controller ...exclusively, instead of attempting to load again and triggering an infiniute loop each time. * [Autoflow] Test that composition does not reload * [Autoflow] Expect identifiers for remove events * [Autoflow] Simplify row-matching test * [Autoflow] Combine down to a single integration test * [Autoflow] Remove possible test race condition * [Autoflow] Add JSDoc * [Autoflow] Remove excess test case ...which is no longer needed after combining behavioral tests for view into a single spec. * [Autoflow] Remove unused destroy call https://github.com/nasa/openmct/pull/1816/files#r154787335 * [Autoflow] Use requestAnimationFrame in tests ...to avoid brittle change detection. https://github.com/nasa/openmct/pull/1816/files#r154785549 * [Autoflow] Use MCT instance for spies ...such that test case becomes sensitive to API changes in MCT.
This commit is contained in:
parent
a51b9bc63f
commit
50b4d5cb28
@ -43,6 +43,9 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.ImportExport());
|
openmct.install(openmct.plugins.ImportExport());
|
||||||
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
|
type: "telemetry.panel"
|
||||||
|
}));
|
||||||
openmct.install(openmct.plugins.Conductor({
|
openmct.install(openmct.plugins.Conductor({
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,7 @@ module.exports = function(config) {
|
|||||||
files: [
|
files: [
|
||||||
{pattern: 'bower_components/**/*.js', included: false},
|
{pattern: 'bower_components/**/*.js', included: false},
|
||||||
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
||||||
|
{pattern: 'node_modules/vue/**/*.js', included: false},
|
||||||
{pattern: 'src/**/*.js', included: false},
|
{pattern: 'src/**/*.js', included: false},
|
||||||
{pattern: 'example/**/*.html', included: false},
|
{pattern: 'example/**/*.html', included: false},
|
||||||
{pattern: 'example/**/*.js', included: false},
|
{pattern: 'example/**/*.js', included: false},
|
||||||
|
@ -37,6 +37,7 @@ requirejs.config({
|
|||||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||||
"text": "bower_components/text/text",
|
"text": "bower_components/text/text",
|
||||||
"uuid": "bower_components/node-uuid/uuid",
|
"uuid": "bower_components/node-uuid/uuid",
|
||||||
|
"vue": "node_modules/vue/dist/vue.min",
|
||||||
"zepto": "bower_components/zepto/zepto.min",
|
"zepto": "bower_components/zepto/zepto.min",
|
||||||
"lodash": "bower_components/lodash/lodash",
|
"lodash": "bower_components/lodash/lodash",
|
||||||
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
"d3-time-format": "^2.0.3",
|
"d3-time-format": "^2.0.3",
|
||||||
"express": "^4.13.1",
|
"express": "^4.13.1",
|
||||||
"minimist": "^1.1.1",
|
"minimist": "^1.1.1",
|
||||||
"request": "^2.69.0"
|
"request": "^2.69.0",
|
||||||
|
"vue": "^2.5.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bower": "^1.7.7",
|
"bower": "^1.7.7",
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
define([
|
|
||||||
'text!./res/templates/autoflow-tabular.html',
|
|
||||||
'./src/AutoflowTabularController',
|
|
||||||
'./src/MCTAutoflowTable'
|
|
||||||
], function (
|
|
||||||
autoflowTabularTemplate,
|
|
||||||
AutoflowTabularController,
|
|
||||||
MCTAutoflowTable
|
|
||||||
) {
|
|
||||||
return function (options) {
|
|
||||||
return function (openmct) {
|
|
||||||
openmct.legacyRegistry.register("platform/features/autoflow", {
|
|
||||||
"name": "WARP Telemetry Adapter",
|
|
||||||
"description": "Retrieves telemetry from the WARP Server and provides related types and views.",
|
|
||||||
"resources": "res",
|
|
||||||
"extensions": {
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "autoflow",
|
|
||||||
"name": "Autoflow Tabular",
|
|
||||||
"cssClass": "icon-packet",
|
|
||||||
"description": "A tabular view of packet contents.",
|
|
||||||
"template": autoflowTabularTemplate,
|
|
||||||
"type": options && options.type,
|
|
||||||
"needs": [
|
|
||||||
"telemetry"
|
|
||||||
],
|
|
||||||
"delegation": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "AutoflowTabularController",
|
|
||||||
"implementation": AutoflowTabularController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$timeout",
|
|
||||||
"telemetrySubscriber"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctAutoflowTable",
|
|
||||||
"implementation": MCTAutoflowTable
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
openmct.legacyRegistry.enable("platform/features/autoflow");
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
|||||||
<div class="items-holder abs contents autoflow obj-value-format"
|
|
||||||
ng-controller="AutoflowTabularController as autoflow">
|
|
||||||
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
|
||||||
<mct-include key="'input-filter'"
|
|
||||||
ng-model="autoflow.filter"
|
|
||||||
class="flex-elem">
|
|
||||||
</mct-include>
|
|
||||||
<div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
|
|
||||||
<a title="Change column width"
|
|
||||||
class="s-button flex-elem icon-arrows-right-left change-column-width"
|
|
||||||
ng-click="autoflow.increaseColumnWidth()"></a>
|
|
||||||
</div>
|
|
||||||
<div class="abs t-autoflow-items l-autoflow-items"
|
|
||||||
mct-resize="autoflow.setBounds(bounds)"
|
|
||||||
mct-resize-interval="50">
|
|
||||||
<mct-autoflow-table values="autoflow.rangeValues()"
|
|
||||||
objects="autoflow.getTelemetryObjects()"
|
|
||||||
rows="autoflow.getRows()"
|
|
||||||
classes="autoflow.classes()"
|
|
||||||
updated="autoflow.updated()"
|
|
||||||
column-width="autoflow.columnWidth()"
|
|
||||||
counter="autoflow.counter()"
|
|
||||||
>
|
|
||||||
</mct-autoflow-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,169 +0,0 @@
|
|||||||
/*global angular*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The link step for the `mct-autoflow-table` directive;
|
|
||||||
* watches scope and updates the DOM appropriately.
|
|
||||||
* See documentation in `MCTAutoflowTable.js` for the rationale
|
|
||||||
* for including this directive, as well as for an explanation
|
|
||||||
* of which values are placed in scope.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Scope} scope the scope for this usage of the directive
|
|
||||||
* @param element the jqLite-wrapped element which used this directive
|
|
||||||
*/
|
|
||||||
function AutoflowTableLinker(scope, element) {
|
|
||||||
var objects, // Domain objects at last structure refresh
|
|
||||||
rows, // Number of rows from last structure refresh
|
|
||||||
priorClasses = {},
|
|
||||||
valueSpans = {}; // Span elements to put data values in
|
|
||||||
|
|
||||||
// Create a new name-value pair in the specified column
|
|
||||||
function createListItem(domainObject, ul) {
|
|
||||||
// Create a new li, and spans to go in it.
|
|
||||||
var li = angular.element('<li>'),
|
|
||||||
titleSpan = angular.element('<span>'),
|
|
||||||
valueSpan = angular.element('<span>');
|
|
||||||
|
|
||||||
// Place spans in the li, and li into the column.
|
|
||||||
// valueSpan must precede titleSpan in the DOM due to new CSS float approach
|
|
||||||
li.append(valueSpan).append(titleSpan);
|
|
||||||
ul.append(li);
|
|
||||||
|
|
||||||
// Style appropriately
|
|
||||||
li.addClass('l-autoflow-row');
|
|
||||||
titleSpan.addClass('l-autoflow-item l');
|
|
||||||
valueSpan.addClass('l-autoflow-item r l-obj-val-format');
|
|
||||||
|
|
||||||
// Set text/tooltip for the name-value row
|
|
||||||
titleSpan.text(domainObject.getModel().name);
|
|
||||||
titleSpan.attr("title", domainObject.getModel().name);
|
|
||||||
|
|
||||||
// Keep a reference to the span which will hold the
|
|
||||||
// data value, to populate in the next refreshValues call
|
|
||||||
valueSpans[domainObject.getId()] = valueSpan;
|
|
||||||
|
|
||||||
return li;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new column of name-value pairs in this table.
|
|
||||||
function createColumn(el) {
|
|
||||||
// Create a ul
|
|
||||||
var ul = angular.element('<ul>');
|
|
||||||
|
|
||||||
// Add it into the mct-autoflow-table
|
|
||||||
el.append(ul);
|
|
||||||
|
|
||||||
// Style appropriately
|
|
||||||
ul.addClass('l-autoflow-col');
|
|
||||||
|
|
||||||
// Get the current col width and apply at time of column creation
|
|
||||||
// Important to do this here, as new columns could be created after
|
|
||||||
// the user has changed the width.
|
|
||||||
ul.css('width', scope.columnWidth + 'px');
|
|
||||||
|
|
||||||
// Return it, so some li elements can be added
|
|
||||||
return ul;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the width of the columns when user clicks the resize button.
|
|
||||||
function resizeColumn() {
|
|
||||||
element.find('ul').css('width', scope.columnWidth + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild the DOM associated with this table.
|
|
||||||
function rebuild(domainObjects, rowCount) {
|
|
||||||
var activeColumn;
|
|
||||||
|
|
||||||
// Empty out our cached span elements
|
|
||||||
valueSpans = {};
|
|
||||||
|
|
||||||
// Start with an empty DOM beneath this directive
|
|
||||||
element.html("");
|
|
||||||
|
|
||||||
// Add DOM elements for each domain object being displayed
|
|
||||||
// in this table.
|
|
||||||
domainObjects.forEach(function (object, index) {
|
|
||||||
// Start a new column if we'd run out of room
|
|
||||||
if (index % rowCount === 0) {
|
|
||||||
activeColumn = createColumn(element);
|
|
||||||
}
|
|
||||||
// Add the DOM elements for that object to whichever
|
|
||||||
// column (a `ul` element) is current.
|
|
||||||
createListItem(object, activeColumn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update spans with values, as made available via the
|
|
||||||
// `values` attribute of this directive.
|
|
||||||
function refreshValues() {
|
|
||||||
// Get the available values
|
|
||||||
var values = scope.values || {},
|
|
||||||
classes = scope.classes || {};
|
|
||||||
|
|
||||||
// Populate all spans with those values (or clear
|
|
||||||
// those spans if no value is available)
|
|
||||||
(objects || []).forEach(function (object) {
|
|
||||||
var id = object.getId(),
|
|
||||||
span = valueSpans[id],
|
|
||||||
value;
|
|
||||||
|
|
||||||
if (span) {
|
|
||||||
// Look up the value...
|
|
||||||
value = values[id];
|
|
||||||
// ...and convert to empty string if it's undefined
|
|
||||||
value = value === undefined ? "" : value;
|
|
||||||
span.attr("data-value", value);
|
|
||||||
|
|
||||||
// Update the span
|
|
||||||
span.text(value);
|
|
||||||
span.attr("title", value);
|
|
||||||
span.removeClass(priorClasses[id]);
|
|
||||||
span.addClass(classes[id]);
|
|
||||||
priorClasses[id] = classes[id];
|
|
||||||
}
|
|
||||||
// Also need stale/alert/ok class
|
|
||||||
// on span
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh the DOM for this table, if necessary
|
|
||||||
function refreshStructure() {
|
|
||||||
// Only rebuild if number of rows or set of objects
|
|
||||||
// has changed; otherwise, our structure is still valid.
|
|
||||||
if (scope.objects !== objects ||
|
|
||||||
scope.rows !== rows) {
|
|
||||||
|
|
||||||
// Track those values to support future refresh checks
|
|
||||||
objects = scope.objects;
|
|
||||||
rows = scope.rows;
|
|
||||||
|
|
||||||
// Rebuild the DOM
|
|
||||||
rebuild(objects || [], rows || 1);
|
|
||||||
|
|
||||||
// Refresh all data values shown
|
|
||||||
refreshValues();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changing the domain objects in use or the number
|
|
||||||
// of rows should trigger a structure change (DOM rebuild)
|
|
||||||
scope.$watch("objects", refreshStructure);
|
|
||||||
scope.$watch("rows", refreshStructure);
|
|
||||||
|
|
||||||
// When the current column width has been changed, resize the column
|
|
||||||
scope.$watch('columnWidth', resizeColumn);
|
|
||||||
|
|
||||||
// When the last-updated time ticks,
|
|
||||||
scope.$watch("updated", refreshValues);
|
|
||||||
|
|
||||||
// Update displayed values when the counter changes.
|
|
||||||
scope.$watch("counter", refreshValues);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return AutoflowTableLinker;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,324 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
['moment'],
|
|
||||||
function (moment) {
|
|
||||||
|
|
||||||
var ROW_HEIGHT = 16,
|
|
||||||
SLIDER_HEIGHT = 10,
|
|
||||||
INITIAL_COLUMN_WIDTH = 225,
|
|
||||||
MAX_COLUMN_WIDTH = 525,
|
|
||||||
COLUMN_WIDTH_STEP = 25,
|
|
||||||
DEBOUNCE_INTERVAL = 100,
|
|
||||||
DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
|
|
||||||
NOT_UPDATED = "No updates",
|
|
||||||
EMPTY_ARRAY = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responsible for supporting the autoflow tabular view.
|
|
||||||
* Implements the all-over logic which drives that view,
|
|
||||||
* mediating between template-provided areas, the included
|
|
||||||
* `mct-autoflow-table` directive, and the underlying
|
|
||||||
* domain object model.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function AutflowTabularController(
|
|
||||||
$scope,
|
|
||||||
$timeout,
|
|
||||||
telemetrySubscriber
|
|
||||||
) {
|
|
||||||
var filterValue = "",
|
|
||||||
filterValueLowercase = "",
|
|
||||||
subscription,
|
|
||||||
filteredObjects = [],
|
|
||||||
lastUpdated = {},
|
|
||||||
updateText = NOT_UPDATED,
|
|
||||||
rangeValues = {},
|
|
||||||
classes = {},
|
|
||||||
limits = {},
|
|
||||||
updatePending = false,
|
|
||||||
lastBounce = Number.NEGATIVE_INFINITY,
|
|
||||||
columnWidth = INITIAL_COLUMN_WIDTH,
|
|
||||||
rows = 1,
|
|
||||||
counter = 0;
|
|
||||||
|
|
||||||
// Trigger an update of the displayed table by incrementing
|
|
||||||
// the counter that it watches.
|
|
||||||
function triggerDisplayUpdate() {
|
|
||||||
counter += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether or not an object's name matches the
|
|
||||||
// user-entered filter value.
|
|
||||||
function filterObject(domainObject) {
|
|
||||||
return (domainObject.getModel().name || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.indexOf(filterValueLowercase) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparator for sorting points back into packet order
|
|
||||||
function compareObject(objectA, objectB) {
|
|
||||||
var indexA = objectA.getModel().index || 0,
|
|
||||||
indexB = objectB.getModel().index || 0;
|
|
||||||
return indexA - indexB;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the list of currently-displayed objects; these
|
|
||||||
// will be the subset of currently subscribed-to objects
|
|
||||||
// which match a user-entered filter.
|
|
||||||
function doUpdateFilteredObjects() {
|
|
||||||
// Generate the list
|
|
||||||
filteredObjects = (
|
|
||||||
subscription ?
|
|
||||||
subscription.getTelemetryObjects() :
|
|
||||||
[]
|
|
||||||
).filter(filterObject).sort(compareObject);
|
|
||||||
|
|
||||||
// Clear the pending flag
|
|
||||||
updatePending = false;
|
|
||||||
|
|
||||||
// Track when this occurred, so that we can wait
|
|
||||||
// a whole before updating again.
|
|
||||||
lastBounce = Date.now();
|
|
||||||
|
|
||||||
triggerDisplayUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request an update to the list of current objects; this may
|
|
||||||
// run on a timeout to avoid excessive calls, e.g. while the user
|
|
||||||
// is typing a filter.
|
|
||||||
function updateFilteredObjects() {
|
|
||||||
// Don't do anything if an update is already scheduled
|
|
||||||
if (!updatePending) {
|
|
||||||
if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
|
|
||||||
// Update immediately if it's been long enough
|
|
||||||
doUpdateFilteredObjects();
|
|
||||||
} else {
|
|
||||||
// Otherwise, update later, and track that we have
|
|
||||||
// an update pending so that subsequent calls can
|
|
||||||
// be ignored.
|
|
||||||
updatePending = true;
|
|
||||||
$timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track the latest data values for this domain object
|
|
||||||
function recordData(telemetryObject) {
|
|
||||||
// Get latest domain/range values for this object.
|
|
||||||
var id = telemetryObject.getId(),
|
|
||||||
domainValue = subscription.getDomainValue(telemetryObject),
|
|
||||||
rangeValue = subscription.getRangeValue(telemetryObject);
|
|
||||||
|
|
||||||
// Track the most recent timestamp change observed...
|
|
||||||
if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
|
|
||||||
lastUpdated[id] = domainValue;
|
|
||||||
// ... and update the displayable text for that timestamp
|
|
||||||
updateText = isNaN(domainValue) ? "" :
|
|
||||||
moment.utc(domainValue).format(DATE_FORMAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store data values into the rangeValues structure, which
|
|
||||||
// will be used to populate the table itself.
|
|
||||||
// Note that we want full precision here.
|
|
||||||
rangeValues[id] = rangeValue;
|
|
||||||
|
|
||||||
// Update limit states as well
|
|
||||||
classes[id] = limits[id] && (limits[id].evaluate({
|
|
||||||
// This relies on external knowledge that the
|
|
||||||
// range value of a telemetry point is encoded
|
|
||||||
// in its datum as "value."
|
|
||||||
value: rangeValue
|
|
||||||
}) || {}).cssClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Look at telemetry objects from the subscription; this is watched
|
|
||||||
// to detect changes from the subscription.
|
|
||||||
function subscribedTelemetry() {
|
|
||||||
return subscription ?
|
|
||||||
subscription.getTelemetryObjects() : EMPTY_ARRAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the data values which will be used to populate the table
|
|
||||||
function updateValues() {
|
|
||||||
subscribedTelemetry().forEach(recordData);
|
|
||||||
triggerDisplayUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter-setter function for user-entered filter text.
|
|
||||||
function filter(value) {
|
|
||||||
// If value was specified, we're a setter
|
|
||||||
if (value !== undefined) {
|
|
||||||
// Store the new value
|
|
||||||
filterValue = value;
|
|
||||||
filterValueLowercase = value.toLowerCase();
|
|
||||||
// Change which objects appear in the table
|
|
||||||
updateFilteredObjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always act as a getter
|
|
||||||
return filterValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the bounds (width and height) of this view;
|
|
||||||
// called from the mct-resize directive. Recalculates how
|
|
||||||
// many rows should appear in the contained table.
|
|
||||||
function setBounds(bounds) {
|
|
||||||
var availableSpace = bounds.height - SLIDER_HEIGHT;
|
|
||||||
rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the current column width, up to the defined maximum.
|
|
||||||
// When the max is hit, roll back to the default.
|
|
||||||
function increaseColumnWidth() {
|
|
||||||
columnWidth += COLUMN_WIDTH_STEP;
|
|
||||||
// Cycle down to the initial width instead of exceeding max
|
|
||||||
columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
|
|
||||||
INITIAL_COLUMN_WIDTH : columnWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get displayable text for last-updated value
|
|
||||||
function updated() {
|
|
||||||
return updateText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe, if a subscription is active.
|
|
||||||
function releaseSubscription() {
|
|
||||||
if (subscription) {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
subscription = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update set of telemetry objects managed by this view
|
|
||||||
function updateTelemetryObjects(telemetryObjects) {
|
|
||||||
updateFilteredObjects();
|
|
||||||
limits = {};
|
|
||||||
telemetryObjects.forEach(function (telemetryObject) {
|
|
||||||
var id = telemetryObject.getId();
|
|
||||||
limits[id] = telemetryObject.getCapability('limit');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a subscription for the represented domain object.
|
|
||||||
// This will resolve capability delegation as necessary.
|
|
||||||
function makeSubscription(domainObject) {
|
|
||||||
// Unsubscribe, if there is an existing subscription
|
|
||||||
releaseSubscription();
|
|
||||||
|
|
||||||
// Clear updated timestamp
|
|
||||||
lastUpdated = {};
|
|
||||||
updateText = NOT_UPDATED;
|
|
||||||
|
|
||||||
// Create a new subscription; telemetrySubscriber gets
|
|
||||||
// to do the meaningful work here.
|
|
||||||
subscription = domainObject && telemetrySubscriber.subscribe(
|
|
||||||
domainObject,
|
|
||||||
updateValues
|
|
||||||
);
|
|
||||||
|
|
||||||
// Our set of in-view telemetry objects may have changed,
|
|
||||||
// so update the set that is being passed down to the table.
|
|
||||||
updateFilteredObjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for changes to the set of objects which have telemetry
|
|
||||||
$scope.$watch(subscribedTelemetry, updateTelemetryObjects);
|
|
||||||
|
|
||||||
// Watch for the represented domainObject (this field will
|
|
||||||
// be populated by mct-representation)
|
|
||||||
$scope.$watch("domainObject", makeSubscription);
|
|
||||||
|
|
||||||
// Make sure we unsubscribe when this view is destroyed.
|
|
||||||
$scope.$on("$destroy", releaseSubscription);
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Get the number of rows which should be shown in this table.
|
|
||||||
* @return {number} the number of rows to show
|
|
||||||
*/
|
|
||||||
getRows: function () {
|
|
||||||
return rows;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the objects which should currently be displayed in
|
|
||||||
* this table. This will be watched, so the return value
|
|
||||||
* should be stable when this list is unchanging. Only
|
|
||||||
* objects which match the user-entered filter value should
|
|
||||||
* be returned here.
|
|
||||||
* @return {DomainObject[]} the domain objects to include in
|
|
||||||
* this table.
|
|
||||||
*/
|
|
||||||
getTelemetryObjects: function () {
|
|
||||||
return filteredObjects;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Set the bounds (width/height) of this autoflow tabular view.
|
|
||||||
* The template must ensure that these bounds are tracked on
|
|
||||||
* the table area only.
|
|
||||||
* @param bounds the bounds; and object with `width` and
|
|
||||||
* `height` properties, both as numbers, in pixels.
|
|
||||||
*/
|
|
||||||
setBounds: setBounds,
|
|
||||||
/**
|
|
||||||
* Increments the width of the autoflow column.
|
|
||||||
* Setting does not yet persist.
|
|
||||||
*/
|
|
||||||
increaseColumnWidth: increaseColumnWidth,
|
|
||||||
/**
|
|
||||||
* Get-or-set the user-supplied filter value.
|
|
||||||
* @param {string} [value] the new filter value; omit to use
|
|
||||||
* as a getter
|
|
||||||
* @returns {string} the user-supplied filter value
|
|
||||||
*/
|
|
||||||
filter: filter,
|
|
||||||
/**
|
|
||||||
* Get all range values for use in this table. These will be
|
|
||||||
* returned as an object of key-value pairs, where keys are
|
|
||||||
* domain object IDs, and values are the most recently observed
|
|
||||||
* data values associated with those objects, formatted for
|
|
||||||
* display.
|
|
||||||
* @returns {object.<string,string>} most recent values
|
|
||||||
*/
|
|
||||||
rangeValues: function () {
|
|
||||||
return rangeValues;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get CSS classes to apply to specific rows, representing limit
|
|
||||||
* states and/or stale states. These are returned as key-value
|
|
||||||
* pairs where keys are domain object IDs, and values are CSS
|
|
||||||
* classes to display for domain objects with those IDs.
|
|
||||||
* @returns {object.<string,string>} CSS classes
|
|
||||||
*/
|
|
||||||
classes: function () {
|
|
||||||
return classes;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the "last updated" text for this view; this will be
|
|
||||||
* the most recent timestamp observed for any telemetry-
|
|
||||||
* providing object, formatted for display.
|
|
||||||
* @returns {string} the time of the most recent update
|
|
||||||
*/
|
|
||||||
updated: updated,
|
|
||||||
/**
|
|
||||||
* Get the current column width, in pixels.
|
|
||||||
* @returns {number} column width
|
|
||||||
*/
|
|
||||||
columnWidth: function () {
|
|
||||||
return columnWidth;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Keep a counter and increment this whenever the display
|
|
||||||
* should be updated; this will be watched by the
|
|
||||||
* `mct-autoflow-table`.
|
|
||||||
* @returns {number} a counter value
|
|
||||||
*/
|
|
||||||
counter: function () {
|
|
||||||
return counter;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return AutflowTabularController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
["./AutoflowTableLinker"],
|
|
||||||
function (AutoflowTableLinker) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `mct-autoflow-table` directive specifically supports
|
|
||||||
* autoflow tabular views; it is not intended for use outside
|
|
||||||
* of that view.
|
|
||||||
*
|
|
||||||
* This directive is responsible for creating the structure
|
|
||||||
* of the table in this view, and for updating its values.
|
|
||||||
* While this is achievable using a regular Angular template,
|
|
||||||
* this is undesirable from the perspective of performance
|
|
||||||
* due to the number of watches that can be involved for large
|
|
||||||
* tables. Instead, this directive will maintain a small number
|
|
||||||
* of watches, rebuilding table structure only when necessary,
|
|
||||||
* and updating displayed values in the more common case of
|
|
||||||
* new data arriving.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function MCTAutoflowTable() {
|
|
||||||
return {
|
|
||||||
// Only applicable at the element level
|
|
||||||
restrict: "E",
|
|
||||||
|
|
||||||
// The link function; handles DOM update/manipulation
|
|
||||||
link: AutoflowTableLinker,
|
|
||||||
|
|
||||||
// Parameters to pass from attributes into scope
|
|
||||||
scope: {
|
|
||||||
// Set of domain objects to show in the table
|
|
||||||
objects: "=",
|
|
||||||
|
|
||||||
// Values for those objects, by ID
|
|
||||||
values: "=",
|
|
||||||
|
|
||||||
// CSS classes to show for objects, by ID
|
|
||||||
classes: "=",
|
|
||||||
|
|
||||||
// Number of rows to show before autoflowing
|
|
||||||
rows: "=",
|
|
||||||
|
|
||||||
// Time of last update; watched to refresh values
|
|
||||||
updated: "=",
|
|
||||||
|
|
||||||
// Current width of the autoflow column
|
|
||||||
columnWidth: "=",
|
|
||||||
|
|
||||||
// A counter used to trigger display updates
|
|
||||||
counter: "="
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTAutoflowTable;
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,178 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
["../src/AutoflowTableLinker"],
|
|
||||||
function (AutoflowTableLinker) {
|
|
||||||
|
|
||||||
describe("The mct-autoflow-table linker", function () {
|
|
||||||
var cachedAngular,
|
|
||||||
mockAngular,
|
|
||||||
mockScope,
|
|
||||||
mockElement,
|
|
||||||
mockElements,
|
|
||||||
linker;
|
|
||||||
|
|
||||||
// Utility function to generate more mock elements
|
|
||||||
function createMockElement(html) {
|
|
||||||
var mockEl = jasmine.createSpyObj(
|
|
||||||
"element-" + html,
|
|
||||||
[
|
|
||||||
"append",
|
|
||||||
"addClass",
|
|
||||||
"removeClass",
|
|
||||||
"text",
|
|
||||||
"attr",
|
|
||||||
"html",
|
|
||||||
"css",
|
|
||||||
"find"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockEl.testHtml = html;
|
|
||||||
mockEl.append.andReturn(mockEl);
|
|
||||||
mockElements.push(mockEl);
|
|
||||||
return mockEl;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockDomainObject(id) {
|
|
||||||
var mockDomainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject-" + id,
|
|
||||||
["getId", "getModel"]
|
|
||||||
);
|
|
||||||
mockDomainObject.getId.andReturn(id);
|
|
||||||
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
|
|
||||||
return mockDomainObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fireWatch(watchExpression, value) {
|
|
||||||
mockScope.$watch.calls.forEach(function (call) {
|
|
||||||
if (call.args[0] === watchExpression) {
|
|
||||||
call.args[1](value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutoflowTableLinker accesses Angular in the global
|
|
||||||
// scope, since it is not injectable; we simulate that
|
|
||||||
// here by adding/removing it to/from the window object.
|
|
||||||
beforeEach(function () {
|
|
||||||
mockElements = [];
|
|
||||||
|
|
||||||
mockAngular = jasmine.createSpyObj("angular", ["element"]);
|
|
||||||
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
|
|
||||||
mockElement = createMockElement('<div>');
|
|
||||||
|
|
||||||
mockAngular.element.andCallFake(createMockElement);
|
|
||||||
|
|
||||||
if (window.angular !== undefined) {
|
|
||||||
cachedAngular = window.angular;
|
|
||||||
}
|
|
||||||
window.angular = mockAngular;
|
|
||||||
|
|
||||||
linker = new AutoflowTableLinker(mockScope, mockElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
if (cachedAngular !== undefined) {
|
|
||||||
window.angular = cachedAngular;
|
|
||||||
} else {
|
|
||||||
delete window.angular;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("watches for changes in inputs", function () {
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
|
||||||
"objects",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
|
||||||
"rows",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
|
||||||
"counter",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("changes structure when domain objects change", function () {
|
|
||||||
// Set up scope
|
|
||||||
mockScope.rows = 4;
|
|
||||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
||||||
.map(createMockDomainObject);
|
|
||||||
|
|
||||||
// Fire an update to the set of objects
|
|
||||||
fireWatch("objects");
|
|
||||||
|
|
||||||
// Should have rebuilt with two columns of
|
|
||||||
// four and two rows each; first, by clearing...
|
|
||||||
expect(mockElement.html).toHaveBeenCalledWith("");
|
|
||||||
|
|
||||||
// Should have appended two columns...
|
|
||||||
expect(mockElement.append.calls.length).toEqual(2);
|
|
||||||
|
|
||||||
// ...which should have received two and four rows each
|
|
||||||
expect(mockElement.append.calls[0].args[0].append.calls.length)
|
|
||||||
.toEqual(4);
|
|
||||||
expect(mockElement.append.calls[1].args[0].append.calls.length)
|
|
||||||
.toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates values", function () {
|
|
||||||
var mockSpans;
|
|
||||||
|
|
||||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
||||||
.map(createMockDomainObject);
|
|
||||||
mockScope.values = { a: 0 };
|
|
||||||
|
|
||||||
// Fire an update to the set of values
|
|
||||||
fireWatch("objects");
|
|
||||||
fireWatch("updated");
|
|
||||||
|
|
||||||
// Get all created spans
|
|
||||||
mockSpans = mockElements.filter(function (mockElem) {
|
|
||||||
return mockElem.testHtml === '<span>';
|
|
||||||
});
|
|
||||||
|
|
||||||
// First span should be a, should have gotten this value.
|
|
||||||
// This test detects, in particular, WTD-749
|
|
||||||
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
|
|
||||||
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes in column width", function () {
|
|
||||||
var mockUL = createMockElement("<ul>");
|
|
||||||
mockElement.find.andReturn(mockUL);
|
|
||||||
mockScope.columnWidth = 200;
|
|
||||||
fireWatch("columnWidth", mockScope.columnWidth);
|
|
||||||
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates CSS classes", function () {
|
|
||||||
var mockSpans;
|
|
||||||
|
|
||||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
||||||
.map(createMockDomainObject);
|
|
||||||
mockScope.values = { a: "a value to find" };
|
|
||||||
mockScope.classes = { a: 'class-a' };
|
|
||||||
|
|
||||||
// Fire an update to the set of values
|
|
||||||
fireWatch("objects");
|
|
||||||
fireWatch("updated");
|
|
||||||
|
|
||||||
// Figure out which span holds the relevant value...
|
|
||||||
mockSpans = mockElements.filter(function (mockElem) {
|
|
||||||
return mockElem.testHtml === '<span>';
|
|
||||||
}).filter(function (mockSpan) {
|
|
||||||
var attrCalls = mockSpan.attr.calls;
|
|
||||||
return attrCalls.some(function (call) {
|
|
||||||
return call.args[0] === 'title' &&
|
|
||||||
call.args[1] === mockScope.values.a;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ...and make sure it also has had its class applied
|
|
||||||
expect(mockSpans[0].addClass)
|
|
||||||
.toHaveBeenCalledWith(mockScope.classes.a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,341 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
["../src/AutoflowTabularController"],
|
|
||||||
function (AutoflowTabularController) {
|
|
||||||
|
|
||||||
describe("The autoflow tabular controller", function () {
|
|
||||||
var mockScope,
|
|
||||||
mockTimeout,
|
|
||||||
mockSubscriber,
|
|
||||||
mockDomainObject,
|
|
||||||
mockSubscription,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
// Fire watches that are registered as functions.
|
|
||||||
function fireFnWatches() {
|
|
||||||
mockScope.$watch.calls.forEach(function (call) {
|
|
||||||
if (typeof call.args[0] === 'function') {
|
|
||||||
call.args[1](call.args[0]());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$watch"]
|
|
||||||
);
|
|
||||||
mockTimeout = jasmine.createSpy("$timeout");
|
|
||||||
mockSubscriber = jasmine.createSpyObj(
|
|
||||||
"telemetrySubscriber",
|
|
||||||
["subscribe"]
|
|
||||||
);
|
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getId", "getModel", "getCapability"]
|
|
||||||
);
|
|
||||||
mockSubscription = jasmine.createSpyObj(
|
|
||||||
"subscription",
|
|
||||||
[
|
|
||||||
"unsubscribe",
|
|
||||||
"getTelemetryObjects",
|
|
||||||
"getDomainValue",
|
|
||||||
"getRangeValue"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
|
||||||
mockDomainObject.getModel.andReturn({name: "something"});
|
|
||||||
|
|
||||||
controller = new AutoflowTabularController(
|
|
||||||
mockScope,
|
|
||||||
mockTimeout,
|
|
||||||
mockSubscriber
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for the represented domain object", function () {
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
|
||||||
"domainObject",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides a getter-setter function for filtering", function () {
|
|
||||||
expect(controller.filter()).toEqual("");
|
|
||||||
controller.filter("something");
|
|
||||||
expect(controller.filter()).toEqual("something");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("tracks bounds and adjust number of rows accordingly", function () {
|
|
||||||
// Rows are 15px high, and need room for an 10px slider
|
|
||||||
controller.setBounds({ width: 700, height: 120 });
|
|
||||||
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
|
|
||||||
controller.setBounds({ width: 700, height: 240 });
|
|
||||||
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
|
|
||||||
});
|
|
||||||
|
|
||||||
it("subscribes to a represented object's telemetry", function () {
|
|
||||||
// Set up subscription, scope
|
|
||||||
mockSubscription.getTelemetryObjects
|
|
||||||
.andReturn([mockDomainObject]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
// Invoke the watcher with represented domain object
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
|
|
||||||
// Should have subscribed to it
|
|
||||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
|
||||||
mockDomainObject,
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should report objects as reported from subscription
|
|
||||||
expect(controller.getTelemetryObjects())
|
|
||||||
.toEqual([mockDomainObject]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("releases subscriptions on destroy", function () {
|
|
||||||
// Set up subscription...
|
|
||||||
mockSubscription.getTelemetryObjects
|
|
||||||
.andReturn([mockDomainObject]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
|
|
||||||
// Verify precondition
|
|
||||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Make sure we're listening for $destroy
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
"$destroy",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fire a destroy event
|
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
// Should have unsubscribed
|
|
||||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("presents latest values and latest update state", function () {
|
|
||||||
// Make sure values are available
|
|
||||||
mockSubscription.getDomainValue.andReturn(402654321123);
|
|
||||||
mockSubscription.getRangeValue.andReturn(789);
|
|
||||||
mockDomainObject.getId.andReturn('testId');
|
|
||||||
|
|
||||||
// Set up subscription...
|
|
||||||
mockSubscription.getTelemetryObjects
|
|
||||||
.andReturn([mockDomainObject]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
|
|
||||||
// Fire subscription callback
|
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
// ...and exposed the results for template to consume
|
|
||||||
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
|
|
||||||
expect(controller.rangeValues().testId).toEqual(789);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sorts domain objects by index", function () {
|
|
||||||
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
|
|
||||||
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
|
|
||||||
var mockDomainObj = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getId", "getModel"]
|
|
||||||
);
|
|
||||||
|
|
||||||
mockDomainObj.getId.andReturn(id);
|
|
||||||
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
|
|
||||||
|
|
||||||
return mockDomainObj;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose those domain objects...
|
|
||||||
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
|
|
||||||
// Fire subscription callback
|
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
// Controller should expose same objects, but sorted by index from model
|
|
||||||
expect(controller.getTelemetryObjects()).toEqual([
|
|
||||||
mockDomainObjects[3], // d, index=0
|
|
||||||
mockDomainObjects[1], // b, index=1
|
|
||||||
mockDomainObjects[0], // a, index=2
|
|
||||||
mockDomainObjects[2] // c, index=3
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses a timeout to throttle update", function () {
|
|
||||||
// Set up subscription...
|
|
||||||
mockSubscription.getTelemetryObjects
|
|
||||||
.andReturn([mockDomainObject]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
// Set the object in view; should not need a timeout
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
expect(mockTimeout.calls.length).toEqual(0);
|
|
||||||
|
|
||||||
// Next call should schedule an update on a timeout
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
expect(mockTimeout.calls.length).toEqual(1);
|
|
||||||
|
|
||||||
// ...but this last one should not, since existing
|
|
||||||
// timeout will cover it
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
expect(mockTimeout.calls.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows changing column width", function () {
|
|
||||||
var initialWidth = controller.columnWidth();
|
|
||||||
controller.increaseColumnWidth();
|
|
||||||
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("filter", function () {
|
|
||||||
var doFilter,
|
|
||||||
filteredObjects,
|
|
||||||
filteredObjectNames;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
var telemetryObjects,
|
|
||||||
updateFilteredObjects;
|
|
||||||
|
|
||||||
telemetryObjects = [
|
|
||||||
'DEF123',
|
|
||||||
'abc789',
|
|
||||||
'456abc',
|
|
||||||
'4ab3cdef',
|
|
||||||
'hjs[12].*(){}^\\'
|
|
||||||
].map(function (objectName, index) {
|
|
||||||
var mockTelemetryObject = jasmine.createSpyObj(
|
|
||||||
objectName,
|
|
||||||
["getId", "getModel"]
|
|
||||||
);
|
|
||||||
|
|
||||||
mockTelemetryObject.getId.andReturn(objectName);
|
|
||||||
mockTelemetryObject.getModel.andReturn({
|
|
||||||
name: objectName,
|
|
||||||
index: index
|
|
||||||
});
|
|
||||||
|
|
||||||
return mockTelemetryObject;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockSubscription
|
|
||||||
.getTelemetryObjects
|
|
||||||
.andReturn(telemetryObjects);
|
|
||||||
|
|
||||||
// Trigger domainObject change to create subscription.
|
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
|
||||||
|
|
||||||
updateFilteredObjects = function () {
|
|
||||||
filteredObjects = controller.getTelemetryObjects();
|
|
||||||
filteredObjectNames = filteredObjects.map(function (o) {
|
|
||||||
return o.getModel().name;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
doFilter = function (term) {
|
|
||||||
controller.filter(term);
|
|
||||||
// Filter is debounced so we have to force it to occur.
|
|
||||||
mockTimeout.mostRecentCall.args[0]();
|
|
||||||
updateFilteredObjects();
|
|
||||||
};
|
|
||||||
|
|
||||||
updateFilteredObjects();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially shows all objects", function () {
|
|
||||||
expect(filteredObjectNames).toEqual([
|
|
||||||
'DEF123',
|
|
||||||
'abc789',
|
|
||||||
'456abc',
|
|
||||||
'4ab3cdef',
|
|
||||||
'hjs[12].*(){}^\\'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("by blank string matches all objects", function () {
|
|
||||||
doFilter('');
|
|
||||||
expect(filteredObjectNames).toEqual([
|
|
||||||
'DEF123',
|
|
||||||
'abc789',
|
|
||||||
'456abc',
|
|
||||||
'4ab3cdef',
|
|
||||||
'hjs[12].*(){}^\\'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exactly matches an object name", function () {
|
|
||||||
doFilter('4ab3cdef');
|
|
||||||
expect(filteredObjectNames).toEqual(['4ab3cdef']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("partially matches object names", function () {
|
|
||||||
doFilter('abc');
|
|
||||||
expect(filteredObjectNames).toEqual([
|
|
||||||
'abc789',
|
|
||||||
'456abc'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("matches case insensitive names", function () {
|
|
||||||
doFilter('def');
|
|
||||||
expect(filteredObjectNames).toEqual([
|
|
||||||
'DEF123',
|
|
||||||
'4ab3cdef'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works as expected with special characters", function () {
|
|
||||||
doFilter('[12]');
|
|
||||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
|
||||||
doFilter('.*');
|
|
||||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
|
||||||
doFilter('.*()');
|
|
||||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
|
||||||
doFilter('.*?');
|
|
||||||
expect(filteredObjectNames).toEqual([]);
|
|
||||||
doFilter('.+');
|
|
||||||
expect(filteredObjectNames).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exposes CSS classes from limits", function () {
|
|
||||||
var id = mockDomainObject.getId(),
|
|
||||||
testClass = "some-css-class",
|
|
||||||
mockLimitCapability =
|
|
||||||
jasmine.createSpyObj('limit', ['evaluate']);
|
|
||||||
|
|
||||||
mockDomainObject.getCapability.andCallFake(function (key) {
|
|
||||||
return key === 'limit' && mockLimitCapability;
|
|
||||||
});
|
|
||||||
mockLimitCapability.evaluate
|
|
||||||
.andReturn({ cssClass: testClass });
|
|
||||||
|
|
||||||
mockSubscription.getTelemetryObjects
|
|
||||||
.andReturn([mockDomainObject]);
|
|
||||||
|
|
||||||
fireFnWatches();
|
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
expect(controller.classes()[id]).toEqual(testClass);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exposes a counter that changes with each update", function () {
|
|
||||||
var i, prior;
|
|
||||||
|
|
||||||
for (i = 0; i < 10; i += 1) {
|
|
||||||
prior = controller.counter();
|
|
||||||
expect(controller.counter()).toEqual(prior);
|
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
|
||||||
expect(controller.counter()).not.toEqual(prior);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
["../src/MCTAutoflowTable"],
|
|
||||||
function (MCTAutoflowTable) {
|
|
||||||
|
|
||||||
describe("The mct-autoflow-table directive", function () {
|
|
||||||
var mctAutoflowTable;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mctAutoflowTable = new MCTAutoflowTable();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Real functionality is contained/tested in the linker,
|
|
||||||
// so just check to make sure we're exposing the directive
|
|
||||||
// appropriately.
|
|
||||||
it("is applicable at the element level", function () {
|
|
||||||
expect(mctAutoflowTable.restrict).toEqual("E");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("two-ways binds needed scope variables", function () {
|
|
||||||
expect(mctAutoflowTable.scope).toEqual({
|
|
||||||
objects: "=",
|
|
||||||
values: "=",
|
|
||||||
rows: "=",
|
|
||||||
updated: "=",
|
|
||||||
classes: "=",
|
|
||||||
columnWidth: "=",
|
|
||||||
counter: "="
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides a link function", function () {
|
|
||||||
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
34
src/plugins/autoflow/AutoflowTabularConstants.js
Normal file
34
src/plugins/autoflow/AutoflowTabularConstants.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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 () {
|
||||||
|
/**
|
||||||
|
* Constant values used by the Autoflow Tabular View.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
ROW_HEIGHT: 16,
|
||||||
|
SLIDER_HEIGHT: 10,
|
||||||
|
INITIAL_COLUMN_WIDTH: 225,
|
||||||
|
MAX_COLUMN_WIDTH: 525,
|
||||||
|
COLUMN_WIDTH_STEP: 25
|
||||||
|
};
|
||||||
|
});
|
121
src/plugins/autoflow/AutoflowTabularController.js
Normal file
121
src/plugins/autoflow/AutoflowTabularController.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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([
|
||||||
|
'./AutoflowTabularRowController'
|
||||||
|
], function (AutoflowTabularRowController) {
|
||||||
|
/**
|
||||||
|
* Controller for an Autoflow Tabular View. Subscribes to telemetry
|
||||||
|
* associated with children of the domain object and passes that
|
||||||
|
* information on to the view.
|
||||||
|
*
|
||||||
|
* @param {DomainObject} domainObject the object being viewed
|
||||||
|
* @param {*} data the view data
|
||||||
|
* @param openmct a reference to the openmct application
|
||||||
|
*/
|
||||||
|
function AutoflowTabularController(domainObject, data, openmct) {
|
||||||
|
this.composition = openmct.composition.get(domainObject);
|
||||||
|
this.data = data;
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.rows = {};
|
||||||
|
this.controllers = {};
|
||||||
|
|
||||||
|
this.addRow = this.addRow.bind(this);
|
||||||
|
this.removeRow = this.removeRow.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the "Last Updated" value to be displayed.
|
||||||
|
* @param {String} value the value to display
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
|
||||||
|
this.data.updated = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to an `add` event from composition by adding a new row.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
AutoflowTabularController.prototype.addRow = function (childObject) {
|
||||||
|
var identifier = childObject.identifier;
|
||||||
|
var id = [identifier.namespace, identifier.key].join(":");
|
||||||
|
|
||||||
|
if (!this.rows[id]) {
|
||||||
|
this.rows[id] = {
|
||||||
|
classes: "",
|
||||||
|
name: childObject.name,
|
||||||
|
value: undefined
|
||||||
|
};
|
||||||
|
this.controllers[id] = new AutoflowTabularRowController(
|
||||||
|
childObject,
|
||||||
|
this.rows[id],
|
||||||
|
this.openmct,
|
||||||
|
this.trackLastUpdated.bind(this)
|
||||||
|
);
|
||||||
|
this.controllers[id].activate();
|
||||||
|
this.data.items.push(this.rows[id]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to an `remove` event from composition by removing any
|
||||||
|
* related row.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
AutoflowTabularController.prototype.removeRow = function (identifier) {
|
||||||
|
var id = [identifier.namespace, identifier.key].join(":");
|
||||||
|
|
||||||
|
if (this.rows[id]) {
|
||||||
|
this.data.items = this.data.items.filter(function (item) {
|
||||||
|
return item !== this.rows[id];
|
||||||
|
}.bind(this));
|
||||||
|
this.controllers[id].destroy();
|
||||||
|
delete this.controllers[id];
|
||||||
|
delete this.rows[id];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate this controller; begin listening for changes.
|
||||||
|
*/
|
||||||
|
AutoflowTabularController.prototype.activate = function () {
|
||||||
|
this.composition.on('add', this.addRow);
|
||||||
|
this.composition.on('remove', this.removeRow);
|
||||||
|
this.composition.load();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this controller; detach any associated resources.
|
||||||
|
*/
|
||||||
|
AutoflowTabularController.prototype.destroy = function () {
|
||||||
|
Object.keys(this.controllers).forEach(function (id) {
|
||||||
|
this.controllers[id].destroy();
|
||||||
|
}.bind(this));
|
||||||
|
this.controllers = {};
|
||||||
|
this.composition.off('add', this.addRow);
|
||||||
|
this.composition.off('remove', this.removeRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
return AutoflowTabularController;
|
||||||
|
});
|
60
src/plugins/autoflow/AutoflowTabularPlugin.js
Normal file
60
src/plugins/autoflow/AutoflowTabularPlugin.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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([
|
||||||
|
'./AutoflowTabularView'
|
||||||
|
], function (
|
||||||
|
AutoflowTabularView
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* This plugin provides an Autoflow Tabular View for domain objects
|
||||||
|
* in Open MCT.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {String} [options.type] the domain object type for which
|
||||||
|
* this view should be available; if omitted, this view will
|
||||||
|
* be available for all objects
|
||||||
|
*/
|
||||||
|
return function (options) {
|
||||||
|
return function (openmct) {
|
||||||
|
var views = (openmct.mainViews || openmct.objectViews);
|
||||||
|
|
||||||
|
views.addProvider({
|
||||||
|
name: "Autoflow Tabular",
|
||||||
|
key: "autoflow",
|
||||||
|
cssClass: "icon-packet",
|
||||||
|
description: "A tabular view of packet contents.",
|
||||||
|
canView: function (d) {
|
||||||
|
return !options || (options.type === d.type);
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
return new AutoflowTabularView(
|
||||||
|
domainObject,
|
||||||
|
openmct,
|
||||||
|
document
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
319
src/plugins/autoflow/AutoflowTabularPluginSpec.js
Normal file
319
src/plugins/autoflow/AutoflowTabularPluginSpec.js
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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([
|
||||||
|
'./AutoflowTabularPlugin',
|
||||||
|
'./AutoflowTabularConstants',
|
||||||
|
'../../MCT',
|
||||||
|
'zepto'
|
||||||
|
], function (AutoflowTabularPlugin, AutoflowTabularConstants, MCT, $) {
|
||||||
|
describe("AutoflowTabularPlugin", function () {
|
||||||
|
var testType;
|
||||||
|
var testObject;
|
||||||
|
var mockmct;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testType = "some-type";
|
||||||
|
testObject = { type: testType };
|
||||||
|
mockmct = new MCT();
|
||||||
|
spyOn(mockmct.composition, 'get');
|
||||||
|
spyOn(mockmct.objectViews, 'addProvider');
|
||||||
|
spyOn(mockmct.telemetry, 'getMetadata');
|
||||||
|
spyOn(mockmct.telemetry, 'getValueFormatter');
|
||||||
|
spyOn(mockmct.telemetry, 'limitEvaluator');
|
||||||
|
spyOn(mockmct.telemetry, 'request');
|
||||||
|
spyOn(mockmct.telemetry, 'subscribe');
|
||||||
|
|
||||||
|
var plugin = new AutoflowTabularPlugin({ type: testType });
|
||||||
|
plugin(mockmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("installs a view provider", function () {
|
||||||
|
expect(mockmct.objectViews.addProvider).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("installs a view provider which", function () {
|
||||||
|
var provider;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
provider =
|
||||||
|
mockmct.objectViews.addProvider.mostRecentCall.args[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies its view to the type from options", function () {
|
||||||
|
expect(provider.canView(testObject)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not apply to other types", function () {
|
||||||
|
expect(provider.canView({ type: 'foo' })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("provides a view which", function () {
|
||||||
|
var testKeys;
|
||||||
|
var testChildren;
|
||||||
|
var testContainer;
|
||||||
|
var testHistories;
|
||||||
|
var mockComposition;
|
||||||
|
var mockMetadata;
|
||||||
|
var mockEvaluator;
|
||||||
|
var mockUnsubscribes;
|
||||||
|
var callbacks;
|
||||||
|
var view;
|
||||||
|
|
||||||
|
function waitsForChange() {
|
||||||
|
var callback = jasmine.createSpy('callback');
|
||||||
|
window.requestAnimationFrame(callback);
|
||||||
|
waitsFor(function () {
|
||||||
|
return callback.calls.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitEvent(mockEmitter, type, event) {
|
||||||
|
mockEmitter.on.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === type) {
|
||||||
|
call.args[1](event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
callbacks = {};
|
||||||
|
|
||||||
|
testObject = { type: 'some-type' };
|
||||||
|
testKeys = ['abc', 'def', 'xyz'];
|
||||||
|
testChildren = testKeys.map(function (key) {
|
||||||
|
return {
|
||||||
|
identifier: { namespace: "test", key: key },
|
||||||
|
name: "Object " + key
|
||||||
|
};
|
||||||
|
});
|
||||||
|
testContainer = $('<div>')[0];
|
||||||
|
testHistories = testKeys.reduce(function (histories, key, index) {
|
||||||
|
histories[key] = { key: key, range: index + 10, domain: key + index };
|
||||||
|
return histories;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
mockComposition =
|
||||||
|
jasmine.createSpyObj('composition', ['load', 'on', 'off']);
|
||||||
|
mockMetadata =
|
||||||
|
jasmine.createSpyObj('metadata', ['valuesForHints']);
|
||||||
|
|
||||||
|
mockEvaluator = jasmine.createSpyObj('evaluator', ['evaluate']);
|
||||||
|
mockUnsubscribes = testKeys.reduce(function (map, key) {
|
||||||
|
map[key] = jasmine.createSpy('unsubscribe-' + key);
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
mockmct.composition.get.andReturn(mockComposition);
|
||||||
|
mockComposition.load.andCallFake(function () {
|
||||||
|
testChildren.forEach(emitEvent.bind(null, mockComposition, 'add'));
|
||||||
|
return Promise.resolve(testChildren);
|
||||||
|
});
|
||||||
|
|
||||||
|
mockmct.telemetry.getMetadata.andReturn(mockMetadata);
|
||||||
|
mockmct.telemetry.getValueFormatter.andCallFake(function (metadatum) {
|
||||||
|
var mockFormatter = jasmine.createSpyObj('formatter', ['format']);
|
||||||
|
mockFormatter.format.andCallFake(function (datum) {
|
||||||
|
return datum[metadatum.hint];
|
||||||
|
});
|
||||||
|
return mockFormatter;
|
||||||
|
});
|
||||||
|
mockmct.telemetry.limitEvaluator.andReturn(mockEvaluator);
|
||||||
|
mockmct.telemetry.subscribe.andCallFake(function (obj, callback) {
|
||||||
|
var key = obj.identifier.key;
|
||||||
|
callbacks[key] = callback;
|
||||||
|
return mockUnsubscribes[key];
|
||||||
|
});
|
||||||
|
mockmct.telemetry.request.andCallFake(function (obj, request) {
|
||||||
|
var key = obj.identifier.key;
|
||||||
|
return Promise.resolve([testHistories[key]]);
|
||||||
|
});
|
||||||
|
mockMetadata.valuesForHints.andCallFake(function (hints) {
|
||||||
|
return [{ hint: hints[0] }];
|
||||||
|
});
|
||||||
|
|
||||||
|
view = provider.view(testObject);
|
||||||
|
view.show(testContainer);
|
||||||
|
|
||||||
|
waitsForChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("populates its container", function () {
|
||||||
|
expect(testContainer.children.length > 0).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when rows have been populated", function () {
|
||||||
|
function rowsMatch() {
|
||||||
|
var rows = $(testContainer).find(".l-autoflow-row").length;
|
||||||
|
return rows === testChildren.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("shows one row per child object", function () {
|
||||||
|
waitsFor(rowsMatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds rows on composition change", function () {
|
||||||
|
var child = {
|
||||||
|
identifier: { namespace: "test", key: "123" },
|
||||||
|
name: "Object 123"
|
||||||
|
};
|
||||||
|
testChildren.push(child);
|
||||||
|
emitEvent(mockComposition, 'add', child);
|
||||||
|
waitsFor(rowsMatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes rows on composition change", function () {
|
||||||
|
var child = testChildren.pop();
|
||||||
|
emitEvent(mockComposition, 'remove', child.identifier);
|
||||||
|
waitsFor(rowsMatch);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes subscriptions when destroyed", function () {
|
||||||
|
testKeys.forEach(function (key) {
|
||||||
|
expect(mockUnsubscribes[key]).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
view.destroy();
|
||||||
|
testKeys.forEach(function (key) {
|
||||||
|
expect(mockUnsubscribes[key]).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a button to change column width", function () {
|
||||||
|
var initialWidth = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
|
||||||
|
var nextWidth =
|
||||||
|
initialWidth + AutoflowTabularConstants.COLUMN_WIDTH_STEP;
|
||||||
|
|
||||||
|
expect($(testContainer).find('.l-autoflow-col').css('width'))
|
||||||
|
.toEqual(initialWidth + 'px');
|
||||||
|
|
||||||
|
$(testContainer).find('.change-column-width').click();
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
var width = $(testContainer).find('.l-autoflow-col').css('width');
|
||||||
|
return width !== initialWidth + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect($(testContainer).find('.l-autoflow-col').css('width'))
|
||||||
|
.toEqual(nextWidth + 'px');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("subscribes to all child objects", function () {
|
||||||
|
testKeys.forEach(function (key) {
|
||||||
|
expect(callbacks[key]).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays historical telemetry", function () {
|
||||||
|
waitsFor(function () {
|
||||||
|
return $(testContainer).find(".l-autoflow-item").filter(".r").text() !== "";
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
testKeys.forEach(function (key, index) {
|
||||||
|
var datum = testHistories[key];
|
||||||
|
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||||
|
expect($cell.text()).toEqual(String(datum.range));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays incoming telemetry", function () {
|
||||||
|
var testData = testKeys.map(function (key, index) {
|
||||||
|
return { key: key, range: index * 100, domain: key + index };
|
||||||
|
});
|
||||||
|
|
||||||
|
testData.forEach(function (datum) {
|
||||||
|
callbacks[datum.key](datum);
|
||||||
|
});
|
||||||
|
|
||||||
|
waitsForChange();
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
testData.forEach(function (datum, index) {
|
||||||
|
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||||
|
expect($cell.text()).toEqual(String(datum.range));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates classes for limit violations", function () {
|
||||||
|
var testClass = "some-limit-violation";
|
||||||
|
mockEvaluator.evaluate.andReturn({ cssClass: testClass });
|
||||||
|
testKeys.forEach(function (key) {
|
||||||
|
callbacks[key]({ range: 'foo', domain: 'bar' });
|
||||||
|
});
|
||||||
|
|
||||||
|
waitsForChange();
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
testKeys.forEach(function (datum, index) {
|
||||||
|
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||||
|
expect($cell.hasClass(testClass)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("automatically flows to new columns", function () {
|
||||||
|
var rowHeight = AutoflowTabularConstants.ROW_HEIGHT;
|
||||||
|
var sliderHeight = AutoflowTabularConstants.SLIDER_HEIGHT;
|
||||||
|
var count = testKeys.length;
|
||||||
|
var $container = $(testContainer);
|
||||||
|
|
||||||
|
function columnsHaveAutoflowed() {
|
||||||
|
var itemsHeight = $container.find('.l-autoflow-items').height();
|
||||||
|
var availableHeight = itemsHeight - sliderHeight;
|
||||||
|
var availableRows = Math.max(Math.floor(availableHeight / rowHeight), 1);
|
||||||
|
var columns = Math.ceil(count / availableRows);
|
||||||
|
return $container.find('.l-autoflow-col').length === columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container.find('.abs').css({
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0px',
|
||||||
|
right: '0px',
|
||||||
|
top: '0px',
|
||||||
|
bottom: '0px'
|
||||||
|
});
|
||||||
|
$container.css({ position: 'absolute' });
|
||||||
|
|
||||||
|
runs($container.appendTo.bind($container, document.body));
|
||||||
|
for (var height = 0; height < rowHeight * count * 2; height += rowHeight / 2) {
|
||||||
|
runs($container.css.bind($container, 'height', height + 'px'));
|
||||||
|
waitsFor(columnsHaveAutoflowed);
|
||||||
|
}
|
||||||
|
runs($container.remove.bind($container));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads composition exactly once", function () {
|
||||||
|
var testObj = testChildren.pop();
|
||||||
|
emitEvent(mockComposition, 'remove', testObj.identifier);
|
||||||
|
testChildren.push(testObj);
|
||||||
|
emitEvent(mockComposition, 'add', testObj);
|
||||||
|
expect(mockComposition.load.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
94
src/plugins/autoflow/AutoflowTabularRowController.js
Normal file
94
src/plugins/autoflow/AutoflowTabularRowController.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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 () {
|
||||||
|
/**
|
||||||
|
* Controller for individual rows of an Autoflow Tabular View.
|
||||||
|
* Subscribes to telemetry and updates row data.
|
||||||
|
*
|
||||||
|
* @param {DomainObject} domainObject the object being viewed
|
||||||
|
* @param {*} data the view data
|
||||||
|
* @param openmct a reference to the openmct application
|
||||||
|
* @param {Function} callback a callback to invoke with "last updated" timestamps
|
||||||
|
*/
|
||||||
|
function AutoflowTabularRowController(domainObject, data, openmct, callback) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.data = data;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.ranges = this.metadata.valuesForHints(['range']);
|
||||||
|
this.domains = this.metadata.valuesForHints(['domain']);
|
||||||
|
this.rangeFormatter =
|
||||||
|
this.openmct.telemetry.getValueFormatter(this.ranges[0]);
|
||||||
|
this.domainFormatter =
|
||||||
|
this.openmct.telemetry.getValueFormatter(this.domains[0]);
|
||||||
|
this.evaluator =
|
||||||
|
this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update row to reflect incoming telemetry data.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
|
||||||
|
var violations = this.evaluator.evaluate(datum, this.ranges[0]);
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
this.data.classes = violations ? violations.cssClass : "";
|
||||||
|
this.data.value = this.rangeFormatter.format(datum);
|
||||||
|
this.callback(this.domainFormatter.format(datum));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate this controller; begin listening for changes.
|
||||||
|
*/
|
||||||
|
AutoflowTabularRowController.prototype.activate = function () {
|
||||||
|
this.unsubscribe = this.openmct.telemetry.subscribe(
|
||||||
|
this.domainObject,
|
||||||
|
this.updateRowData.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.openmct.telemetry.request(
|
||||||
|
this.domainObject,
|
||||||
|
{ size: 1 }
|
||||||
|
).then(function (history) {
|
||||||
|
if (!this.initialized && history.length > 0) {
|
||||||
|
this.updateRowData(history[history.length - 1]);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this controller; detach any associated resources.
|
||||||
|
*/
|
||||||
|
AutoflowTabularRowController.prototype.destroy = function () {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return AutoflowTabularRowController;
|
||||||
|
});
|
125
src/plugins/autoflow/AutoflowTabularView.js
Normal file
125
src/plugins/autoflow/AutoflowTabularView.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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([
|
||||||
|
'./AutoflowTabularController',
|
||||||
|
'./AutoflowTabularConstants',
|
||||||
|
'../../ui/VueView',
|
||||||
|
'text!./autoflow-tabular.html'
|
||||||
|
], function (
|
||||||
|
AutoflowTabularController,
|
||||||
|
AutoflowTabularConstants,
|
||||||
|
VueView,
|
||||||
|
autoflowTemplate
|
||||||
|
) {
|
||||||
|
var ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
|
||||||
|
var SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
|
||||||
|
var INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
|
||||||
|
var MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
|
||||||
|
var COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the Autoflow Tabular view of a domain object.
|
||||||
|
*/
|
||||||
|
function AutoflowTabularView(domainObject, openmct) {
|
||||||
|
var data = {
|
||||||
|
items: [],
|
||||||
|
columns: [],
|
||||||
|
width: INITIAL_COLUMN_WIDTH,
|
||||||
|
filter: "",
|
||||||
|
updated: "No updates",
|
||||||
|
rowCount: 1
|
||||||
|
};
|
||||||
|
var controller =
|
||||||
|
new AutoflowTabularController(domainObject, data, openmct);
|
||||||
|
var interval;
|
||||||
|
|
||||||
|
VueView.call(this, {
|
||||||
|
data: data,
|
||||||
|
methods: {
|
||||||
|
increaseColumnWidth: function () {
|
||||||
|
data.width += COLUMN_WIDTH_STEP;
|
||||||
|
data.width = data.width > MAX_COLUMN_WIDTH ?
|
||||||
|
INITIAL_COLUMN_WIDTH : data.width;
|
||||||
|
},
|
||||||
|
reflow: function () {
|
||||||
|
var column = [];
|
||||||
|
var index = 0;
|
||||||
|
var filteredItems =
|
||||||
|
data.items.filter(function (item) {
|
||||||
|
return item.name.toLowerCase()
|
||||||
|
.indexOf(data.filter.toLowerCase()) !== -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
data.columns = [];
|
||||||
|
|
||||||
|
while (index < filteredItems.length) {
|
||||||
|
if (column.length >= data.rowCount) {
|
||||||
|
data.columns.push(column);
|
||||||
|
column = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
column.push(filteredItems[index]);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.length > 0) {
|
||||||
|
data.columns.push(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
filter: 'reflow',
|
||||||
|
items: 'reflow',
|
||||||
|
rowCount: 'reflow'
|
||||||
|
},
|
||||||
|
template: autoflowTemplate,
|
||||||
|
destroyed: function () {
|
||||||
|
controller.destroy();
|
||||||
|
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
interval = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
controller.activate();
|
||||||
|
|
||||||
|
var updateRowHeight = function () {
|
||||||
|
var tabularArea = this.$refs.autoflowItems;
|
||||||
|
var height = tabularArea ? tabularArea.clientHeight : 0;
|
||||||
|
var available = height - SLIDER_HEIGHT;
|
||||||
|
var rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
|
||||||
|
data.rowCount = rows;
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
interval = setInterval(updateRowHeight, 50);
|
||||||
|
this.$nextTick(updateRowHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoflowTabularView.prototype = Object.create(VueView.prototype);
|
||||||
|
|
||||||
|
return AutoflowTabularView;
|
||||||
|
});
|
||||||
|
|
42
src/plugins/autoflow/autoflow-tabular.html
Normal file
42
src/plugins/autoflow/autoflow-tabular.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
"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 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.
|
||||||
|
-->
|
||||||
|
<div class="items-holder abs contents autoflow obj-value-format">
|
||||||
|
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
||||||
|
<span class="t-filter l-filter">
|
||||||
|
<input type="search" class="t-filter-input" v-model="filter"/>
|
||||||
|
<a v-if="filter !== ''" v-on:click="filter = ''" class="clear-icon icon-x-in-circle"></a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex-elem grows t-last-update" title="Last Update">{{updated}}</div>
|
||||||
|
<a title="Change column width"
|
||||||
|
v-on:click="increaseColumnWidth()"
|
||||||
|
class="s-button flex-elem icon-arrows-right-left change-column-width"></a>
|
||||||
|
</div>
|
||||||
|
<div class="abs t-autoflow-items l-autoflow-items" ref="autoflowItems">
|
||||||
|
<ul v-for="column in columns" class="l-autoflow-col" :style="{ width: width + 'px' }">
|
||||||
|
<li v-for="row in column" class="l-autoflow-row" >
|
||||||
|
<span :title="row.value" :data-value="row.value" :class="'l-autoflow-item r l-obj-val-format ' + row.classes">{{row.value}}</span>
|
||||||
|
<span :title="row.name" class="l-autoflow-item l">{{row.name}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -24,7 +24,7 @@ define([
|
|||||||
'lodash',
|
'lodash',
|
||||||
'./utcTimeSystem/plugin',
|
'./utcTimeSystem/plugin',
|
||||||
'../../example/generator/plugin',
|
'../../example/generator/plugin',
|
||||||
'../../platform/features/autoflow/plugin',
|
'./autoflow/AutoflowTabularPlugin',
|
||||||
'./timeConductor/plugin',
|
'./timeConductor/plugin',
|
||||||
'../../example/imagery/plugin',
|
'../../example/imagery/plugin',
|
||||||
'../../platform/import-export/bundle',
|
'../../platform/import-export/bundle',
|
||||||
|
33
src/ui/VueView.js
Normal file
33
src/ui/VueView.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, 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
|
||||||
|
* "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 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(['vue'], function (Vue) {
|
||||||
|
function VueView(options) {
|
||||||
|
var vm = new Vue(options);
|
||||||
|
this.show = function (container) {
|
||||||
|
container.appendChild(vm.$mount().$el);
|
||||||
|
};
|
||||||
|
this.destroy = vm.$destroy.bind(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return VueView;
|
||||||
|
});
|
@ -63,6 +63,7 @@ requirejs.config({
|
|||||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||||
"text": "bower_components/text/text",
|
"text": "bower_components/text/text",
|
||||||
"uuid": "bower_components/node-uuid/uuid",
|
"uuid": "bower_components/node-uuid/uuid",
|
||||||
|
"vue": "node_modules/vue/dist/vue.min",
|
||||||
"zepto": "bower_components/zepto/zepto.min",
|
"zepto": "bower_components/zepto/zepto.min",
|
||||||
"lodash": "bower_components/lodash/lodash",
|
"lodash": "bower_components/lodash/lodash",
|
||||||
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user