Compare commits

...

118 Commits

Author SHA1 Message Date
Pete Richards
0dff804bd3 [Proposal] Public API proposal for context
Provides context for registration and other proposals we have in the pipeline.

Intended to be a starting point, is non-exhaustive.
2016-03-21 19:06:50 -07:00
Andrew Henry
ad4c456ca2 Merge pull request #763 from nasa/restrict-requirejs-version
[Package] Specify require minor version
2016-03-17 14:38:02 -07:00
Pete Richards
b0476edb8c [Package] Specify require minor version
Specify requirejs minor version of 2.1, as 2.2 is not compatible
with karma-requirejs.

Fixes build problems that appeared after latest requirejs version bump.
2016-03-17 14:28:22 -07:00
Pete Richards
8f6a287fb8 Merge remote-tracking branch 'origin/tree-refresh-745' 2016-03-17 11:40:53 -07:00
Victor Woeltjen
07d554d114 Merge pull request #759 from nasa/700a
Review and integrate 700a
2016-03-17 11:02:22 -07:00
Andrew Henry
24d2cecdcd Merge pull request #761 from nasa/open757
[Tables] #757 Added max row limit to streaming tables
2016-03-17 10:19:04 -07:00
Henry
8efa9c6aac [Tables] #757 Fixed code style issues 2016-03-17 10:09:59 -07:00
Henry
22e98274ca [Tables] #757 Added max row limit to streaming tables 2016-03-16 18:59:52 -07:00
Charles Hacskaylo
471b57f47c [Frontend] CSS tweaks
#750
Moved l-plot-resource cursor: pointer
to a more specific selector;
Added user-select: none to l-header;
Fixed s-status-editing styling so
that tabular labels only appear draggable
while editing;
2016-03-16 18:19:44 -07:00
Charles Hacskaylo
9c775c6715 [Frontend] Changed cursor property
#755
Changed s-timeline-gantt.mid to
use cursor: move instead of
ew-resize; Need to verify on Linux;
2016-03-16 17:36:07 -07:00
Charles Hacskaylo
1f09b7b0ac [Frontend] Fix for scrollbar flicker
#748
Was able to observe problem in Mac
Chrome. Moved body, html overflow: hidden
into startup-base.scss and change seems
to have fixed the problem;
2016-03-16 17:34:28 -07:00
Andrew Henry
b0cf9bbd29 Merge pull request #758 from nasa/error-message-753
[Tree] Don't assume context capability is present
2016-03-16 15:33:59 -07:00
Andrew Henry
4a1ca25e17 Merge pull request #754 from nasa/tree-status-styling-749
[Tree] Add status classes during editing
2016-03-16 15:26:40 -07:00
Charles Hacskaylo
cc97e567b6 [Frontend] Further style cleanups
#689
#740
Tweaked scrollbar thumb color;
Fixed color of icons in s-menu-btn
menus, particularly for Snow theme;
Removed commented code;
2016-03-16 15:17:08 -07:00
Charles Hacskaylo
53f03cddb7 [Frontend] Further cleanups to form elements
#689
Removed commented code;
2016-03-16 14:49:37 -07:00
Charles Hacskaylo
da1e6750a0 [Frontend] Removed reset.css and refs to modernizr
#700
Removed reset.css; Removed refs to
modernizr from licenses and readme.md;
2016-03-16 14:44:01 -07:00
Charles Hacskaylo
3258342783 [Frontend] Further cleanups to form elements
#700
#689
#740
Removed _selects.scss and moved classes
into _controls.scss;
Reorg'd _controls;
Moved classes into _elems.scss;
Added and fixed cssclass defs in multiple
bundle.js files to allow better field widths
in overlay dialogs;
Fixed overlay custom scrollbar colors;
Fixed alignment of required glyphs in forms;
2016-03-16 14:40:06 -07:00
Victor Woeltjen
73b7365ae2 [Tree] Don't assume context capability is present
Addresses #753 (newly-created objects may not have context,
causing errors when these are encountered by TreeNodeView)
2016-03-16 14:10:58 -07:00
Charles Hacskaylo
32a42bd679 [Frontend] CSS tweaks to fix required glyph positioning
#700
CSS modded to allow better cross-browser
positioning of 'required' form-row glyphs;
Other elem's CSS adjusted as a result;
2016-03-16 09:43:59 -07:00
Victor Woeltjen
324c2cac03 [Tree] Refresh properly on mutation
...by removing the incorrect expectation that a domain object
(and not just its model) will be passed in when mutation occurs.
Addresses #745.
2016-03-15 12:23:46 -07:00
Victor Woeltjen
42ac657105 [Tree] Add status classes during editing
Addresses #749
2016-03-15 12:16:58 -07:00
Pete Richards
d30532a8bc Merge remote-tracking branch 'origin/open742' 2016-03-14 16:37:58 -07:00
Pete Richards
936079da92 Merge remote-tracking branch 'origin/tree-732' 2016-03-14 16:15:05 -07:00
Andrew Henry
4e89ffbe07 Merge pull request #735 from nasa/open729
Review and integrate open729
2016-03-14 16:12:18 -07:00
Henry
f9ed73c55e [Table] #742 Fixed digest-related bug in RTTelemetryController
Fixed failing test
2016-03-14 16:00:36 -07:00
Charles Hacskaylo
bf3b964ad2 Removed commented code
#729
#735
2016-03-14 15:45:10 -07:00
Victor Woeltjen
55ae755522 [Tree] Fix error in test case
Correctly expect both arguments to $watch
2016-03-14 15:04:58 -07:00
Victor Woeltjen
d522570c0b [Tree] Add spec for mct-tree 2016-03-14 14:54:57 -07:00
Victor Woeltjen
d72aaf54ca [Tree] Test inner tree creation 2016-03-14 14:40:46 -07:00
Victor Woeltjen
8f94751a35 [Tree] Add capabilities to child objects for testing 2016-03-14 14:14:34 -07:00
Andrew Henry
dfb0a570a3 Merge pull request #728 from nasa/warp135b
[Timelines] Export as CSV
2016-03-14 13:20:28 -07:00
Pete Richards
5d06979866 Merge remote-tracking branch 'origin/open707' 2016-03-14 13:05:59 -07:00
Victor Woeltjen
8b51ae32d2 [Tree] Begin testing selection change 2016-03-14 13:04:46 -07:00
Victor Woeltjen
ecb37c54be [Tree] Add TreeView test cases 2016-03-14 12:52:33 -07:00
Victor Woeltjen
43492d31ba [Tree] Begin writing spec for TreeView 2016-03-14 12:22:09 -07:00
Victor Woeltjen
0e1df444df [Tree] Update test configuration
...to account for new dependency on Zepto (and its necessary
shim.)
2016-03-14 12:21:58 -07:00
Victor Woeltjen
f2c040367b [Timeline] Rename TimelineCSVExporter
...to TimelineColumnizer, clarifying its role/responsibilities in the
context of the CSV export task;
https://github.com/nasa/openmctweb/pull/728#discussion_r56031331
2016-03-14 12:00:31 -07:00
Victor Woeltjen
0ff360ced3 [Timelines] Remove excessive ternaries
...to improve readability of logic to determine which columns
are needed for a given group of domain objects for CSV export,
as requested during code review,
https://github.com/nasa/openmctweb/pull/728#discussion_r55910477
2016-03-14 11:53:28 -07:00
Henry
fc08df4f6f [Tables] #733 Made code style changes for conformance with style guide 2016-03-14 11:25:26 -07:00
Henry
c5de90a951 [Tables] Changed the way that new rows are added to table
Fixed failing tests
2016-03-13 20:15:47 -07:00
Henry
ea0b86fe72 [Tables] Fixed issue with historical tables composed of multiple objects 2016-03-13 19:12:55 -07:00
Victor Woeltjen
d789e91c18 [Tree] Add wait spinner 2016-03-11 18:00:58 -08:00
Victor Woeltjen
f7ba24c0b6 [Tree] Enable gestures on labels 2016-03-11 17:43:18 -08:00
Victor Woeltjen
02ec6db104 [Tree] Trigger digest on tree selection change 2016-03-11 17:37:11 -08:00
Victor Woeltjen
a3a9393d1b [Tree] Change selection on click 2016-03-11 17:32:57 -08:00
Victor Woeltjen
217e697079 [Tree] Display selection state 2016-03-11 17:26:00 -08:00
Victor Woeltjen
82b6166408 [Tree] Begin wiring in selection 2016-03-11 17:09:17 -08:00
Victor Woeltjen
03ab3bddc4 [Tree] Begin handling selection state 2016-03-11 17:00:55 -08:00
Andrew Henry
abd5913f02 Merge pull request #727 from nasa/open377
[Search] Index directly on mutation
2016-03-11 15:39:43 -08:00
Victor Woeltjen
107ecfe687 [Search] Don't index objects being edited
https://github.com/nasa/openmctweb/pull/727#issuecomment-195570183
2016-03-11 14:55:16 -08:00
Victor Woeltjen
4a8222a152 [Tree] Update labels on mutation 2016-03-11 14:47:50 -08:00
Victor Woeltjen
7ee8d0a3f7 [Tree] Display tree correctly 2016-03-11 14:40:04 -08:00
Victor Woeltjen
dc2b3e85cc [Tree] Show tree with toggle 2016-03-11 14:33:05 -08:00
Victor Woeltjen
d4b15525ca [Tree] Begin using mct-tree for tree representation 2016-03-11 14:23:08 -08:00
Victor Woeltjen
cbd9509260 [Tree] Use TreeView from mct-tree 2016-03-11 14:13:14 -08:00
Victor Woeltjen
c5ab6c6c97 [Tree] Implement label for tree 2016-03-11 14:10:30 -08:00
Andrew Henry
cd84a017b8 Merge pull request #723 from nasa/open672
[Build] Enforce code coverage threshold
2016-03-11 13:34:26 -08:00
Andrew Henry
d39dea971f Merge pull request #721 from nasa/open716
[Info Bubble] Fix bug with arrow positioning
2016-03-11 13:13:49 -08:00
Victor Woeltjen
4f293f22a6 [Tree] Add Zepto dependency
...to support implementation of a jQuery-less tree.
2016-03-11 12:38:40 -08:00
Victor Woeltjen
cebf9f73da [Tree] Begin adding tree label 2016-03-11 12:29:24 -08:00
Victor Woeltjen
37e6b5a352 [Tree] Continue breaking apart tree view 2016-03-11 11:39:10 -08:00
Victor Woeltjen
ece8f7fded [Tree] Begin separating out TreeView 2016-03-11 11:12:43 -08:00
Victor Woeltjen
3b0a3733b4 [Tree] Begin adding controller for refactored tree 2016-03-11 10:23:06 -08:00
Victor Woeltjen
baab0be5af [Tree] Begin adding mct-tree directive
...to reduce watch counts associated with the tree;
#732 and #315
2016-03-11 10:04:33 -08:00
Victor Woeltjen
d14a2a6366 Merge pull request #736 from nasa/open718
Review and integrate open718
2016-03-11 09:52:18 -08:00
Charles Hacskaylo
377d533ec7 [Frontend] Fixed markup
open #718
Removed class from markup;
2016-03-10 18:11:10 -08:00
Charles Hacskaylo
a4c5854561 [Frontend] Bug fixing
open #729
open #498
Fixed markup to use proper CSS classes
to allow tree items in Inspector Elements to
ellipsize properly, and to apply scroll regions
to the proper elements;
2016-03-10 14:33:01 -08:00
Victor Woeltjen
5296255fa6 [Representation] Test for false-positives on refresh
#732
2016-03-10 14:26:20 -08:00
Victor Woeltjen
5d084c2618 [Representation] Fix unchanged check
Fix logic in unchanged check; compare booleans to detect if
they have changed, instead of just anding them. Partially
addresses #732
2016-03-10 14:21:20 -08:00
Henry
5208631528 [Tables] #707 removed redundant bundles 2016-03-10 13:55:59 -08:00
Henry
a56edb9ff4 [table] #707 Fixed sorting on insert for numbers
Added metadata to event generator
2016-03-10 13:39:16 -08:00
Henry
7da1a218ba [Tables] #707 Added auto-scroll, addressed race condition in Sinewave and event telemetry providers
Fixed issue with visible padding row

Incremental improvements

Added tests

Added tests for sorted insert, and fixed lint errors
2016-03-10 13:21:57 -08:00
Henry
a4eb9d6a94 [Tables] #707 Supporting realtime telemetry tables
Added real-time table type
2016-03-10 13:21:57 -08:00
Henry
20f1dcef45 Fixed scrolling 2016-03-10 13:21:01 -08:00
Charles Hacskaylo
4983d35ca6 [Frontend] Cleanups to Inspector config elements
open #729
Spacing tweaks; set PlotOptionsForm.js > Autoscale
to not use control-first layout;
Checked in snow theme;
2016-03-10 13:18:57 -08:00
Charles Hacskaylo
7171fd01e3 [Frontend] Cleanups to Inspector config elements
open #729
In-progress
Moved CSS def to be more globally applicable;
Simplified layout obj props location display;
2016-03-10 11:07:26 -08:00
Charles Hacskaylo
0957fbc6f9 [Frontend] Cleanups to Inspector config elements
open #729
In-progress
Moved reduced-min-width into _inspector.scss;
Removed <style> defs in markup files;
2016-03-10 10:50:00 -08:00
Charles Hacskaylo
bdbb045005 [Frontend] Cleanups to Inspector config elements
open #729
In-progress!
Markup and CSS mods continued;
Config params added to PlotOptionsForm.js to
allow control-first layout per form row;
Changed titles of Inspector parts;
2016-03-10 10:31:05 -08:00
Charles Hacskaylo
318df9878d [Frontend] Cleanups to Inspector config elements
open #729
In-progress!
Markup and CSS modified to cleanup control layout
and styling in Plot and Table config options;
gulpfile.js modified to include SASS source line numbers
in rendered CSS;
2016-03-09 17:22:41 -08:00
Victor Woeltjen
406a31889e [Timeline] Add specs for remaining columns 2016-03-08 12:34:08 -08:00
Victor Woeltjen
9e4e3e9c43 [Timeline] Add JSDoc
Add JSDoc to classes implemented Export Timeline as CSV
2016-03-08 11:59:53 -08:00
Victor Woeltjen
bd7cb98a4c [Timeline] Add missing semicolon
...to pass code style checks
2016-03-08 11:40:54 -08:00
Victor Woeltjen
0d419fa5fd [Timeline] Test TimelineTraverser 2016-03-08 11:30:26 -08:00
Victor Woeltjen
5ee5e7fea1 [Timeline] Add capabilities to test inputs 2016-03-08 10:47:01 -08:00
Victor Woeltjen
66b1a92554 [Timeline] Add test cases for TimelineTraverser 2016-03-08 10:43:13 -08:00
Victor Woeltjen
c23c2b84bf [Timeline] Test inclusion of timespan properties
...in prepared data for CSV export
2016-03-08 10:31:31 -08:00
Victor Woeltjen
0c2285719e [Timeline] Test inclusion of metadata headers 2016-03-08 10:27:50 -08:00
Victor Woeltjen
fd92c5f970 [Timeline] Add more tests for rows 2016-03-08 10:22:46 -08:00
Victor Woeltjen
938c266b4e [Timeline] Add Id column to CSV export 2016-03-08 10:20:25 -08:00
Victor Woeltjen
9e6e33983b [Timeline] Add more objects to test case 2016-03-08 10:17:10 -08:00
Victor Woeltjen
40895ec1b9 [Timelines] Begin spec for exporter 2016-03-08 10:07:21 -08:00
Victor Woeltjen
43db52fd70 [Timeline] Simplify interface
...such that responsibility for knowing exportService interface
is more localized.
2016-03-08 10:04:09 -08:00
Victor Woeltjen
75d6803c9f [Timeline] Test mode columns in CSV Export 2016-03-08 09:59:54 -08:00
Victor Woeltjen
ed679756b3 [Timelines] Test metadata columns in CSV Export
...and additionally refactor to run in a test environment
(don't use Array.prototype.find)
2016-03-08 09:56:21 -08:00
Victor Woeltjen
dd66cb60d8 [Timeline] Test export task 2016-03-08 09:47:09 -08:00
Victor Woeltjen
d5283d57e4 [Timeline] Add missing semicolon 2016-03-07 16:35:40 -08:00
Victor Woeltjen
7c140c06dc [Timeline] Test CSV Export action itself 2016-03-07 16:33:03 -08:00
Victor Woeltjen
f9ff9921a9 [Timelines] Continue testing CSV Export
...with initial test cases for the action itself.
2016-03-07 16:22:39 -08:00
Victor Woeltjen
cdac0ad67f [Timeline] Add test cases for CSV Export 2016-03-07 16:17:19 -08:00
Victor Woeltjen
5d771edcf7 [Timeline] Begin testing CSV Export 2016-03-07 16:13:23 -08:00
Victor Woeltjen
c4f1c4ad1f [Timeline] Show progress notification during export 2016-03-07 16:09:23 -08:00
Victor Woeltjen
14b8e02f27 [Timeline] Format exported start/end times 2016-03-07 16:02:33 -08:00
Victor Woeltjen
b383921f2a [Timeline] Export start/end to CSV 2016-03-07 16:00:13 -08:00
Victor Woeltjen
a509dfb840 [Timelines] Remove unused TaskService
https://developer.nasa.gov/mct/warp/issues/135
2016-03-07 15:30:57 -08:00
Victor Woeltjen
b7a44a7557 Merge branch 'master' into warp135b 2016-03-07 15:21:28 -08:00
Victor Woeltjen
2d305415b3 [Search] Index directly on mutation
Pass model directly when indexing is triggered via object mutation,
to avoid issuing an extra, unnecessary request to the server.

Additionally supports indexing of objects which have been created
but not yet persisted.

Addresses #377.
2016-03-07 14:46:36 -08:00
Victor Woeltjen
18167eddf8 [Build] Enforce code coverage threshold
Addresses #672
2016-03-03 17:14:04 -08:00
Victor Woeltjen
f302bd6cb2 [Info Bubble] Test arrow class application
#716
2016-03-03 16:17:01 -08:00
Victor Woeltjen
071a908c10 [Info Bubble] Don't clobber arrow classes
#716
2016-03-03 15:58:28 -08:00
Victor Woeltjen
273cf1c14f [Timeline] Fix columns exported 2016-02-08 18:07:03 -08:00
Victor Woeltjen
303e870b0d Merge branch 'open649' into warp135 2016-02-08 17:42:30 -08:00
Victor Woeltjen
b42ccebd5a [Timeline] Fix imports, dependencies
Fix imports and dependencies needed by Export Timeline as CSV
2016-02-08 17:42:26 -08:00
Victor Woeltjen
a444fc01ad [Timeline] Fix column module definitions 2016-02-08 17:34:10 -08:00
Victor Woeltjen
a126e43286 [Timelines] Handle columns during CSV export 2016-02-08 16:11:00 -08:00
Victor Woeltjen
32fc50bbd3 [Timelines] Begin sketching in taskService
...to separate out immediate commonality with other long-running
actions which need to show a blocking progress dialog.
2016-02-08 15:04:39 -08:00
Victor Woeltjen
4adb075a2b [Timelines] Separate out timeline traversal
...from rest of CSV export.
2016-02-08 13:59:34 -08:00
Victor Woeltjen
d8b1e570d9 [CSV Export] Begin implementing
Begin implementing initial step, wherein timelines composition
is traversed to build up a list of objects to export.
2016-02-08 13:36:25 -08:00
Victor Woeltjen
5033e2cdbb [Timeline] Begin adding Export as CSV
Sketch in solution for
https://developer.nasa.gov/mct/warp/issues/135
2016-02-08 13:04:41 -08:00
130 changed files with 3907 additions and 4205 deletions

View File

@@ -309,30 +309,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
---
### Modernizr
#### Info
* Link: http://modernizr.com
* Version: 2.6.2
* Author: Faruk Ateş
* Description: Browser/device capability finding
#### License
Copyright (c) 20092015
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
### Normalize.css
#### Info
@@ -476,6 +452,44 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
---
### Zepto
#### Info
* Link: http://zeptojs.com/
* Version: 1.1.6
* Authors: Thomas Fuchs
* Description: DOM manipulation
#### License
Copyright (c) 2010-2016 Thomas Fuchs
http://zeptojs.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
### Json.NET
#### Info

View File

@@ -17,6 +17,7 @@
"screenfull": "^3.0.0",
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
"FileSaver.js": "^0.0.2"
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6"
}
}

View File

@@ -0,0 +1,180 @@
# Public API
We need to focus on the API as a whole, so that we can design and refactor it before 1.0. We also need to figure out what is stable and work to implementing it in a straightforward fashion. Ideally, we can create an abstraction between the public API and the implementation to allow us to refactor the underlying implementation without breaking the API contract.
Conceptual weight is a big risk, so I advocate for focusing our API on specific concepts-- telemetry, views, domain objects, actions -- and not intermediate abstract concepts like composite providers, type registeres, etc. We will reuse patterns in our system so as to reward users who are getting deep, but we should not force them to learn the patterns first.
At the same time, I think we should formalize a basic plugin API that allows for code to be bundled and installed in the platform. Plugins are allowed to use any part of the public API to implement their features, and can register extensions at any of the documented extension points.
With the introduction of a plugin API, we should also start reorganizing platform code. I think we should organize code into two categories:
* the core platform, which provides implements the Public API and takes care of a lot of "behind the scenes" functionality.
* plugins-- many of which have previously been packaged as part of the core api. This includes examples, our various view types, our persistence adapters, etc.
We don't have to split the repository, but we should move plugins to their own set of subfolders. It would be beneficial to start thinking about how plugins are versioned and distributed so that we can start developing stories for plugin reuse across missions.
Summary:
1. The Platform should have a stable, clear, and easy to use Public API, with specific extension points.
2. The Platform can implement this Public API however it would like, so long as good development practices are followed.
3. The Platform will expose a simple method for bundling functionality into Plugins, which can be installed, uninstalled, and shared. However, these plugins may only use the Public API exposed by the Platform.
## Proposal: Plugin API
Plugins are very simple: They are a function which is invoked with a single object, an instance of the MCT application. They can directly attach functionality using the MCT public API, and they can observe the `start` event to execute code when the application starts.
Here's a pseudo plugin:
```javascript
var GenericSeachIndexer = function (mct) {
var worker = new SearchWorker(mct);
var provider = new GenericSearchProvider(mct, worker);
mct.search.registerProvider(provider);
mct.on('start', function () {
worker.startIndexing();
});
};
mct.install(GenericSearchIndexer);
mct.start();
```
Note that nothing prevents using the Public API without going through the plugin interface-- this is not bad! Allowing developers to directly attach functionality without writing a plugin is a great hello-world tutorial. Plugins can come later.
## Proposal: Internal Architecture
In the post-angular world, we should look to requirejs, browserify, or es6 modules as our primary method of separating concerns.
One benefit of these module systems is that they treat modules as singletons by default, so multiple pieces of code which depend upon a module will receive the same instance of that module. This means that modules can depend on other modules, which sometimes makes sense. Platform developers are expected to use their best judgement to separate concerns.
At the same time, we need a basic framework for taking a distinct set of modules and assembling the public API.
I would propose the following simple application builder framework as a starting point:
```javascript
define('Application', function () {
function Application() {
this._plugins = [];
}
Application.prototype = Object.create(EventEmitter.prototype);
Application.prototype.install = function (plugin) {
this._plugins.push(plugin);
};
Application.prototype.uninstall = function (plugin) {
this._plugins.splice(this._plugins.indexOf(plugin), 1);
};
Application.prototype.start = function () {
this._plugins.forEach(function (plugin) {
plugin(this);
}, this);
this.emit('before:start');
this.emit('start');
};
return Application;
});
define('MCT', [
'Application',
'modules/persistence',
'modules/objects' // etc
], function (
Application,
persistenceModule,
objectModule
) {
var mct = new Application();
var modules = [
persistenceModule,
objectModule
];
// modules are functions that are passed a reference to the app.
// they attach their public API to the application.
modules.forEach(function (module)) {
module(app);
};
// after all modules have registered their public apis, they can initialize
// to use public interfaces of other modules. Potential options:
// * setting sensible defaults using app.config
// * attaching handlers to app.bus
modules.forEach(function (module) {
if (module.initialize) {
module.initialize(app);
}
});
// Calling mct.start will emit a `before:start` event and then a `start`
// event.
// `before:start` is a good time to read finalized config from app.config
// `start` is a good time to kick off any runtime function, such as reading
// the path from the url and navigating to a page.
// at this point, any configuration changes will have been made and
// internal modules should use `app.config` to read these configurations.
// It is worth discussing whether app.config can be modified after starting
// the application.
return mct;
});
```
## Proposal: API 1.0
The external API of MCT 1.0 has not been decided, but I would use this opportunity to start proposing what that API would look like so that we can work towards it.
This is not an exhaustive list, it comes off the top of my head.
Generally, if we don't need it, we should remove it. We can always add to this API, but we cannot remove.
* `MCT.routes`
something for determining how navigation is handled. Need to handle selection of views for specific regions for specific modes. For instance, what shows in inspector in edit mode. May not be a public API.
* `MCT.actions`
* `MCT.actions.registerAction(action)`
* `MCT.actions.getActions(object)` -> `Action[]`
* `MCT.actions.registerPolicy(ActionPolicy)`
* `MCT.views` -- manages "main view" views.
* `MCT.views.registerView('key', view)` is key based registration a good idea? I'm not sure.
* `MCT.views.registerPolicy(viewPolicy)` for controlling which views apply to which object. could be a method of views.
* `MCT.telemetry`
* `MCT.telemetry.requestTelemetry(object, options)` -> `promise`
* `MCT.telemetry.subscribe(object, callback, options)` -> `unsubscribe` function
* `MCT.telemetry.registerProvider(provider)`
* `MCT.telemetry.registerLimitEvaluator(limitEvaluator)`
* `MCT.telemetry.getLimitEvaluator(object)`
* `MCT.persistence`
* `MCT.persistence.registerProvider(provider)`
* `MCT.persistence.get('key')`
* `MCT.persistence.set('key', obj)`
* etc.
* MCT.objects
* `MCT.objects.registerType('key', TypeDefinition)`
* `MCT.objects.registerPolicy(compositionPolicy)`
* `MCT.objects.registerProvider(objectProvider)` -- would like to combine model/object/persistence some how.
* MCT.timeService
* `MCT.time.registerFormat('key', TimeFormat)`
* `MCT.time.getFormat('key')` -> `timeFormat`
* `MCT.bus` specifically for "optional dependencies" and loose coupling for app-wide concerns. Using this assumes that you handle the case of a missing dependency. Also enables app-wide event configuration.
* `MCT.bus.register('key', handler)`
* `MCT.bus.request('key', [arguments...])` -> `undefined` if no handler, `{...}` if handler.
* `MCT.bus.request('conductor.bounds')` -> `{start: ..., end: ...}` or `undefined` if disabled
* `MCT.bus.request('conductor.domain')` -> `{key: 'scet', label: 'SCET'}` or `undefined` if disabled
* `MCT.bus.on('user.logout', function () {})`
* `MCT.bus.on('conductor.bounds.change', reloadTelemetry)`
* `MCT.bus.on('mutation', indexModel)`
* `MCT.config` basic key-value store for app wide config. Assume that it won't change after application has started.
`MCT.config.get('key')` -> `value`
`MCT.config.set('key', value)`
`MCT.config.set('theme', 'snow')`
`MCT.config.get('theme')` -> `'snow'`
`MCT.config.get('POLLING_INTERVAL')` -> `30`

View File

@@ -57,8 +57,17 @@ define([
},
"telemetry": {
"source": "eventGenerator",
"domains": [
{
"key": "time",
"name": "Time",
"format": "utc"
}
],
"ranges": [
{
"key": "message",
"name": "Message",
"format": "string"
}
]

View File

@@ -37,7 +37,8 @@ define(
var
subscriptions = [],
genInterval = 1000,
startTime = Date.now();
generating = false,
id = Math.random() * 100000;
//
function matchesSource(request) {
@@ -79,11 +80,13 @@ define(
}
function startGenerating() {
generating = true;
$timeout(function () {
//console.log("startGenerating... " + Date.now());
handleSubscriptions();
if (subscriptions.length > 0) {
if (generating && subscriptions.length > 0) {
startGenerating();
} else {
generating = false;
}
}, genInterval);
}
@@ -93,7 +96,6 @@ define(
callback: callback,
requests: requests
};
function unsubscribe() {
subscriptions = subscriptions.filter(function (s) {
return s !== subscription;
@@ -101,8 +103,7 @@ define(
}
subscriptions.push(subscription);
if (subscriptions.length === 1) {
if (!generating) {
startGenerating();
}

View File

@@ -126,7 +126,7 @@ define([
{
"name": "Period",
"control": "textfield",
"cssclass": "l-small l-numeric",
"cssclass": "l-input-sm l-numeric",
"key": "period",
"required": true,
"property": [

View File

@@ -34,7 +34,8 @@ define(
* @constructor
*/
function SinewaveTelemetryProvider($q, $timeout) {
var subscriptions = [];
var subscriptions = [],
generating = false;
//
function matchesSource(request) {
@@ -75,10 +76,13 @@ define(
}
function startGenerating() {
generating = true;
$timeout(function () {
handleSubscriptions();
if (subscriptions.length > 0) {
if (generating && subscriptions.length > 0) {
startGenerating();
} else {
generating = false;
}
}, 1000);
}
@@ -97,7 +101,7 @@ define(
subscriptions.push(subscription);
if (subscriptions.length === 1) {
if (!generating) {
startGenerating();
}

View File

@@ -60,7 +60,8 @@ var gulp = require('gulp'),
singleRun: true
},
sass: {
includePaths: bourbon.includePaths
includePaths: bourbon.includePaths,
sourceComments: true
},
replace: {
variables: {

View File

@@ -81,7 +81,12 @@ module.exports = function(config) {
coverageReporter: {
dir: process.env.CIRCLE_ARTIFACTS ?
process.env.CIRCLE_ARTIFACTS + '/coverage' :
"dist/coverage"
"dist/coverage",
check: {
global: {
lines: 80
}
}
},
// HTML test reporting.

View File

@@ -33,7 +33,8 @@ requirejs.config({
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid"
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min"
},
"shim": {
"angular": {
@@ -44,6 +45,9 @@ requirejs.config({
},
"moment-duration-format": {
"deps": [ "moment" ]
},
"zepto": {
"exports": "Zepto"
}
}
});
@@ -70,12 +74,10 @@ define([
'./platform/exporters/bundle',
'./platform/telemetry/bundle',
'./platform/features/clock/bundle',
'./platform/features/events/bundle',
'./platform/features/imagery/bundle',
'./platform/features/layout/bundle',
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/scrolling/bundle',
'./platform/features/timeline/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',

View File

@@ -38,7 +38,7 @@
"moment": "^2.11.1",
"node-bourbon": "^4.2.3",
"phantomjs-prebuilt": "^2.1.0",
"requirejs": "^2.1.17",
"requirejs": "2.1.x",
"split": "^1.0.0"
},
"scripts": {

View File

@@ -32,6 +32,7 @@
</li>
<li ng-if="contextutalParents.length > 0">
<em class="t-inspector-part-header" title="The location of this linked object.">Location</em>
<div ng-if="primaryParents.length > 0" class="section-header">This Object</div>
<span class="inspector-location"
ng-repeat="parent in contextutalParents"
ng-class="{ last:($index + 1) === contextualParents.length }">
@@ -44,7 +45,7 @@
</span>
</li>
<li ng-if="primaryParents.length > 0">
<em class="t-inspector-part-header" title="The location of the original object that this was linked from.">Original Location</em>
<div class="section-header">Object's Original</div>
<span class="inspector-location"
ng-repeat="parent in primaryParents"
ng-class="{ last:($index + 1) === primaryParents.length }">

View File

@@ -29,5 +29,5 @@
-->
<mct-representation mct-object="domainObject"
key="viewObjectTemplate || 'browse-object'"
class="abs holder holder-object">
class="abs holder">
</mct-representation>

View File

@@ -19,16 +19,19 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="ElementsController">
<div ng-controller="ElementsController" class="flex-elem l-flex-col holder grows">
<mct-include key="'input-filter'"
class="flex-elem holder"
ng-model="filterBy">
</mct-include>
<div class="current-elements abs" style="height: 100%;">
<div class="flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="containedObject in composition | filter:searchText">
<span class="tree-item">
<mct-representation key="'label'" mct-object="containedObject">
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="containedObject">
</mct-representation>
</span>
</li>

View File

@@ -49,6 +49,7 @@ define([
"./src/directives/MCTScroll",
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
@@ -97,6 +98,7 @@ define([
MCTScroll,
MCTSplitPane,
MCTSplitter,
MCTTree,
bottombarTemplate,
actionButtonTemplate,
inputFilterTemplate,
@@ -175,10 +177,6 @@ define([
{
"stylesheetUrl": "css/normalize.min.css",
"priority": "mandatory"
},
{
"stylesheetUrl": "css/reset.css",
"priority": "mandatory"
}
],
"templates": [
@@ -389,6 +387,11 @@ define([
{
"key": "mctSplitter",
"implementation": MCTSplitter
},
{
"key": "mctTree",
"implementation": MCTTree,
"depends": [ '$parse', 'gestureService' ]
}
],
"constants": [
@@ -516,16 +519,6 @@ define([
}
],
"licenses": [
{
"name": "Modernizr",
"version": "2.6.2",
"description": "Browser/device capability finding",
"author": "Faruk Ateş",
"website": "http://modernizr.com",
"copyright": "Copyright (c) 20092015",
"license": "license-mit",
"link": "http://modernizr.com/license/"
},
{
"name": "Normalize.css",
"version": "1.1.2",
@@ -535,6 +528,16 @@ define([
"copyright": "Copyright (c) Nicolas Gallagher and Jonathan Neal",
"license": "license-mit",
"link": "https://github.com/necolas/normalize.css/blob/v1.1.2/LICENSE.md"
},
{
"name": "Zepto",
"version": "1.1.6",
"description": "DOM manipulation",
"author": "Thomas Fuchs",
"website": "http://zeptojs.com/",
"copyright": "Copyright (c) 2010-2016 Thomas Fuchs",
"license": "license-mit",
"link": "https://github.com/madrobby/zepto/blob/master/MIT-LICENSE"
}
]
}

View File

@@ -53,7 +53,6 @@ body, html {
font-weight: 200;
height: 100%;
width: 100%;
overflow: hidden;
}
em {
@@ -85,6 +84,8 @@ p {
margin-bottom: $interiorMarginLg;
}
ol, ul { padding-left: 0; }
mct-container {
display: block;
}

View File

@@ -61,9 +61,24 @@
.l-inspector-part {
box-sizing: border-box;
padding-right: $interiorMargin;
.form {
.tree .form {
margin-left: $treeVCW + $interiorMarginLg;
margin-bottom: $interiorMarginLg;
}
.section-header {
background: none;
color: $colorInspectorPropName;
border-radius: unset;
font-size: inherit;
padding: $interiorMarginSm 0;
}
mct-form:not(:last-child) .form {
border-bottom: 1px solid $colorInspectorSectionHeaderBg;
}
.form {
margin-bottom: $interiorMarginSm;
padding-bottom: $interiorMarginLg;
.form-section {
margin-bottom: 0;
&:not(.first) {
@@ -72,7 +87,14 @@
.form-row {
@include align-items(center);
border: none;
padding: 0;
padding: $interiorMarginSm 0;
.label {
min-width: 80px;
}
input[type='text'],
input[type='search'] {
width: 100%;
}
}
}
}

View File

@@ -45,7 +45,6 @@
/********************************* FORMS */
@import "forms/elems";
@import "forms/selects";
@import "forms/channel-selector";
@import "forms/datetime";
@import "forms/validation";

View File

@@ -74,6 +74,12 @@
.l-composite-control {
vertical-align: middle;
&:not(.l-inline) {
margin-bottom: $interiorMargin;
}
&.l-inline {
display: inline-block;
}
&.l-checkbox {
.composite-control-label {
line-height: 18px;
@@ -108,12 +114,14 @@
font-size: 0.7rem;
}
/******************************************************** CUSTOM CHECKBOXES */
label.checkbox.custom,
label.radio.custom {
$bg: pullForward($colorBodyBg, 10%);
$d: $formRowCtrlsH;
cursor: pointer;
display: inline-block;
line-height: 120%;
margin-right: $interiorMargin * 4;
padding-left: $d + $interiorMargin;
position: relative;
@@ -161,7 +169,40 @@ label.radio.custom {
label.checkbox.custom input:checked ~ em:before { content: "\32"; }
label.radio.custom input:checked ~ em:before { content: "\e619"; }
.s-menu-btn label.checkbox.custom {
margin-left: 5px;
}
.item .checkbox {
&.checked label {
box-shadow: none;
border-bottom: none;
}
}
label.form-control.checkbox {
input {
margin-right: $interiorMargin;
vertical-align: top;
}
}
/******************************************************** INPUTS */
input[type="text"],
input[type="search"] {
@include nice-input();
&.numeric {
text-align: right;
}
}
.l-input-lg input[type="text"] { width: 100% !important; }
.l-input-med input[type="text"] { width: 200px !important; }
.l-input-sm input[type="text"] { width: 50px !important; }
.l-numeric input[type="text"] { text-align: right; }
.input-labeled {
// Used in toolbar
margin-left: $interiorMargin;
label {
display: inline-block;
@@ -175,28 +216,36 @@ label.radio.custom input:checked ~ em:before { content: "\e619"; }
}
}
.s-menu-btn label.checkbox.custom {
margin-left: 5px;
}
.item .checkbox {
&.checked label {
box-shadow: none;
border-bottom: none;
/******************************************************** SELECTS */
.select {
@include btnSubtle($colorSelectBg);
@if $shdwBtns != none {
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
}
}
.context-available,
.s-icon-btn {
$c: $colorKey;
color: $c;
&:hover {
color: lighten($c, 10%);
padding: 0 $interiorMargin;
overflow: hidden;
position: relative;
line-height: $formInputH;
select {
@include appearance(none);
box-sizing: border-box;
background: none;
color: $colorSelectFg;
cursor: pointer;
border: none !important;
padding: 4px 25px 2px 0px;
width: 130%;
option {
margin: $interiorMargin 0; // Firefox
}
}
&:after {
@include contextArrow();
pointer-events: none;
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
position: absolute;
right: $interiorMargin; top: 0;
}
}
.view-switcher {
@include trans-prop-nice-fade($controlFadeMs);
}
/******************************************************** OBJECT-HEADER */
@@ -330,7 +379,6 @@ body.desktop .object-header {
}
/******************************************************** SLIDERS */
.slider {
$knobH: 100%;
.slot {
@@ -424,7 +472,7 @@ body.desktop .object-header {
border-top: 1px solid $colorInteriorBorder
}
.l-time-selects {
line-height: $formInputH;
line-height: inherit;
}
}
@@ -478,11 +526,31 @@ body.desktop .object-header {
}
}
/******************************************************** BROWSER ELEMENTS */
/******************************************************** TEXTAREA */
textarea {
@include nice-textarea($colorInputBg, $colorInputFg);
position: absolute;
height: 100%;
width: 100%;
}
/******************************************************** MISC */
.context-available,
.s-icon-btn {
$c: $colorKey;
color: $c;
&:hover {
color: lighten($c, 10%);
}
}
.view-switcher {
@include trans-prop-nice-fade($controlFadeMs);
}
/******************************************************** BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {
border-radius: 2px;
box-sizing: border-box;
box-shadow: inset $scrollbarTrackShdw;
background-color: $scrollbarTrackColorBg;
@@ -491,15 +559,15 @@ body.desktop {
}
::-webkit-scrollbar-thumb {
$bg: $scrollbarThumbColor;
$hc: $scrollbarThumbColorHov;
$gr: 5%;
@include background-image(linear-gradient(lighten($bg, $gr), $bg 20px));
border-radius: 2px;
box-sizing: border-box;
&:hover {
@include background-image(linear-gradient(lighten($hc, $gr), $hc 20px));
}
background: $scrollbarThumbColor;
&:hover { background: $scrollbarThumbColorHov; }
}
.overlay ::-webkit-scrollbar-thumb {
$lr: 15%;
background: $scrollbarThumbColorOverlay;
&:hover { background: $scrollbarThumbColorOverlayHov; }
}
::-webkit-scrollbar-corner {

View File

@@ -51,9 +51,6 @@
.title-label {
font-size: 1rem;
}
//&:after {
// color: rgba($colorInvokeMenu, 0.5);
//}
}
.menu {
@@ -113,10 +110,10 @@
.menu,
.context-menu,
.super-menu {
.super-menu,
.s-menu-btn .menu {
pointer-events: auto;
ul li {
//padding-left: 25px;
a {
color: $colorMenuFg;
}
@@ -126,9 +123,6 @@
.type-icon {
left: $interiorMargin;
}
&:hover .icon {
//color: lighten($colorMenuIc, 5%);
}
}
}
@@ -146,7 +140,7 @@
height: $d;
width: $d;
&:before {
font-size: 7px !important;// $d/2;
font-size: 7px !important;
height: $d;
width: $d;
line-height: $d;
@@ -173,7 +167,6 @@
.pane {
box-sizing: border-box;
&.left {
//@include test();
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
@@ -190,7 +183,6 @@
}
}
&.right {
//@include test(red);
left: auto;
right: 0;
padding: $interiorMargin * 5;
@@ -216,7 +208,6 @@
margin-bottom: 0.5em;
}
&.description {
//color: lighten($colorMenuBg, 30%);
color: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
@@ -258,4 +249,4 @@
left: auto;
right: 0;
width: auto;
}
}

View File

@@ -20,13 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
$c: lighten($colorBodyFg, 20%);
color: $c;
font-size: 0.8em;
padding: $formTBPad $formLRPad;
text-transform: uppercase;
text-transform: uppercase;
}
.form {
@@ -37,12 +31,20 @@
margin-bottom: $interiorMarginLg * 2;
}
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
$c: lighten($colorBodyFg, 20%);
color: $c;
font-size: 0.8em;
padding: $formTBPad $formLRPad;
}
.form-row {
$m: $interiorMargin;
box-sizing: border-box;
@include clearfix;
border-top: 1px solid $colorFormLines;
margin-top: $m;
padding: $formTBPad 0;
position: relative;
&.first {
@@ -52,10 +54,7 @@
>.label,
>.controls {
box-sizing: border-box;
@include clearfix;
font-size: 0.8rem;
line-height: $formInputH;
min-height: $formInputH;
}
>.label {
@@ -83,19 +82,6 @@
margin-right: 5px;
}
}
.l-med input[type="text"] {
width: 200px;
}
.l-small input[type="text"] {
width: 50px;
}
.l-numeric input[type="text"] {
text-align: right;
}
.select {
margin-right: $interiorMargin;
}
@@ -124,25 +110,23 @@
}
}
.l-controls-first {
.form .form-row {
margin-top: $interiorMarginSm;
>.label,
>.controls {
line-height: inherit;
min-height: inherit;;
}
>.label {
@include flex(1 1 auto);
min-width: 0;
width: auto;
order: 2;
}
>.controls {
@include flex(0 0 auto);
margin-right: $interiorMargin;
order: 1;
}
.l-controls-first .form .form-row,
.form .form-row.l-controls-first {
>.label,
>.controls {
line-height: inherit;
min-height: inherit;;
}
>.label {
@include flex(1 1 auto);
min-width: 0;
width: auto;
order: 2;
}
>.controls {
@include flex(0 0 auto);
margin-right: $interiorMargin;
order: 1;
}
}
@@ -155,13 +139,6 @@
}
}
label.form-control.checkbox {
input {
margin-right: $interiorMargin;
vertical-align: top;
}
}
.hint,
.s-hint {
font-size: 0.9em;
@@ -181,19 +158,4 @@ label.form-control.checkbox {
color: lighten($colorFormInvalid, 30%);
padding: $interiorMargin;
}
}
input[type="text"],
input[type="search"] {
@include nice-input();
&.numeric {
text-align: right;
}
}
textarea {
@include nice-textarea($colorInputBg, $colorInputFg);
position: absolute;
height: 100%;
width: 100%;
}

View File

@@ -23,9 +23,13 @@
> .label {
padding-right: $reqSymbolM; // Keep room for validation element
&::after {
float: right;
position: absolute;
right: $interiorMargin;
font-family: symbolsfont;
font-size: $reqSymbolFontSize;
height: 100%;
line-height: 200%;
}
}
&.invalid,

View File

@@ -35,7 +35,6 @@
z-index: 100;
}
> .holder {
//$i: 15%;
@include containerSubtle($colorOvrBg, $colorOvrFg);
border-radius: $basicCr * 3;
color: $colorOvrFg;
@@ -57,15 +56,8 @@
right: $m;
bottom: $m;
left: $m;
//.top-bar,
//.editor,
//.bottom-bar {
// @include absPosDefault();
//}
}
}
.title {
@include ellipsize();
font-size: 1.2em;
@@ -88,7 +80,7 @@
left: 0;
right: 0;
overflow: auto;
.field.l-med {
.field.l-input-med {
input[type='text'] {
width: 100%;
}
@@ -120,7 +112,6 @@
bottom: 0;
left: 0;
overflow: visible;
//font-size: 1em;
height: $ovrFooterH;
}
@@ -132,11 +123,14 @@
margin: .5em 0;
width: 100%;
}
.select {
box-shadow: $shdwBtnsOverlay;
}
}
.t-dialog-sm .overlay > .holder {
// Used for blocker and in-progress dialogs, modal alerts, etc.
//@include test(red);
$h: 225px;
min-height: $h;
height: $h;

View File

@@ -26,6 +26,10 @@
top: $m; right: $m * 1.25; bottom: $m; left: $m * 1.25;
}
body, html {
overflow: hidden;
}
.l-splash-holder {
// Main outer holder.
@include transition-property(opacity);

View File

@@ -40,7 +40,7 @@
<mct-representation
key="'edit-elements'"
mct-object="domainObject"
class="flex-elem holder grows vscroll current-elements">
class="flex-elem l-flex-col holder grows current-elements">
</mct-representation>
</div>
</div>

View File

@@ -19,18 +19,6 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<ul class="tree">
<li ng-if="!composition">
<span class="tree-item">
<span class="icon wait-spinner"></span>
<span class="title-label">Loading...</span>
</span>
</li>
<li ng-repeat="child in composition">
<mct-representation key="'tree-node'"
mct-object="child"
parameters="parameters"
ng-model="ngModel">
</mct-representation>
</li>
</ul>
<mct-tree mct-object="domainObject" mct-model="ngModel.selectedObject">
</mct-tree>

View File

@@ -0,0 +1,4 @@
<span class="tree-item menus-to-left">
</span>
<span class="tree-item-subtree">
</span>

View File

@@ -0,0 +1,2 @@
<span class='ui-symbol view-control flex-elem'>
</span>

View File

@@ -0,0 +1,8 @@
<span class="rep-object-label">
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem">
<div class="t-item-icon-glyph"></div>
</div>
<div class='t-title-label flex-elem grows'></div>
</div>
</span>

View File

@@ -19,11 +19,9 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="w1">
<div class="w2"
ng-controller="RTEventListController as rtevent">
<mct-rt-data-table headers="rtevent.headers()" rows="rtevent.rows()" ascending-scroll="true"></mct-rt-data-table>
</div>
</div>
<li>
<span class="tree-item">
<span class="icon wait-spinner"></span>
<span class="title-label">Loading...</span>
</span>
</li>

View File

@@ -19,40 +19,36 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/*global define*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/NameColumn"],
function (NameColumn) {
"use strict";
describe("A name column", function () {
var mockDomainObject,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel" ]
);
mockDomainObject.getModel.andReturn({
name: "Test object name"
define([
'angular',
'../ui/TreeView'
], function (angular, TreeView) {
function MCTTree($parse, gestureService) {
function link(scope, element, attrs) {
var treeView = new TreeView(gestureService),
expr = $parse(attrs.mctModel),
unobserve = treeView.observe(function (domainObject) {
if (domainObject !== expr(scope.$parent)) {
expr.assign(scope.$parent, domainObject);
scope.$apply();
}
});
column = new NameColumn();
});
it("reports a column header", function () {
expect(column.getTitle()).toEqual("Name");
});
element.append(angular.element(treeView.elements()));
it("looks up name from an object's model", function () {
expect(column.getValue(mockDomainObject).text)
.toEqual("Test object name");
});
scope.$parent.$watch(attrs.mctModel, treeView.value.bind(treeView));
scope.$watch('mctObject', treeView.model.bind(treeView));
scope.$on('$destroy', unobserve);
}
});
return {
restrict: "E",
link: link,
scope: { mctObject: "=" }
};
}
);
return MCTTree;
});

View File

@@ -22,43 +22,44 @@
/*global define*/
define([
"./src/ScrollingListController",
"text!./res/templates/scrolling.html",
'legacyRegistry'
], function (
ScrollingListController,
scrollingTemplate,
legacyRegistry
) {
"use strict";
'zepto',
'text!../../res/templates/tree/toggle.html'
], function ($, toggleTemplate) {
function ToggleView(state) {
this.expanded = !!state;
this.callbacks = [];
this.el = $(toggleTemplate);
this.el.on('click', function () {
this.value(!this.expanded);
}.bind(this));
}
legacyRegistry.register("platform/features/scrolling", {
"name": "Scrolling Lists",
"description": "Time-ordered list of latest data.",
"extensions": {
"views": [
{
"key": "scrolling",
"name": "Scrolling",
"glyph": "5",
"description": "Scrolling list of data values.",
"template": scrollingTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "ScrollingListController",
"implementation": ScrollingListController,
"depends": [
"$scope",
"telemetryFormatter"
]
}
]
ToggleView.prototype.value = function (state) {
this.expanded = state;
if (state) {
this.el.addClass('expanded');
} else {
this.el.removeClass('expanded');
}
});
this.callbacks.forEach(function (callback) {
callback(state);
});
};
ToggleView.prototype.observe = function (callback) {
this.callbacks.push(callback);
return function () {
this.callbacks = this.callbacks.filter(function (c) {
return c !== callback;
});
}.bind(this);
};
ToggleView.prototype.elements = function () {
return this.el;
};
return ToggleView;
});

View File

@@ -0,0 +1,90 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
'zepto',
'text!../../res/templates/tree/tree-label.html'
], function ($, labelTemplate) {
'use strict';
function TreeLabelView(gestureService) {
this.el = $(labelTemplate);
this.gestureService = gestureService;
}
function getGlyph(domainObject) {
var type = domainObject.getCapability('type');
return type.getGlyph();
}
function isLink(domainObject) {
var location = domainObject.getCapability('location');
return location.isLink();
}
TreeLabelView.prototype.updateView = function (domainObject) {
var titleEl = this.el.find('.t-title-label'),
glyphEl = this.el.find('.t-item-icon-glyph'),
iconEl = this.el.find('.t-item-icon');
titleEl.text(domainObject ? domainObject.getModel().name : "");
glyphEl.text(domainObject ? getGlyph(domainObject) : "");
if (domainObject && isLink(domainObject)) {
iconEl.addClass('l-icon-link');
} else {
iconEl.removeClass('l-icon-link');
}
};
TreeLabelView.prototype.model = function (domainObject) {
if (this.unlisten) {
this.unlisten();
delete this.unlisten;
}
if (this.activeGestures) {
this.activeGestures.destroy();
delete this.activeGestures;
}
this.updateView(domainObject);
if (domainObject) {
this.unlisten = domainObject.getCapability('mutation')
.listen(this.updateView.bind(this, domainObject));
this.activeGestures = this.gestureService.attachGestures(
this.elements(),
domainObject,
[ 'info', 'menu', 'drag' ]
);
}
};
TreeLabelView.prototype.elements = function () {
return this.el;
};
return TreeLabelView;
});

View File

@@ -0,0 +1,157 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
'zepto',
'text!../../res/templates/tree/node.html',
'./ToggleView',
'./TreeLabelView'
], function ($, nodeTemplate, ToggleView, TreeLabelView) {
'use strict';
function TreeNodeView(gestureService, subtreeFactory, selectFn) {
this.li = $('<li>');
this.statusClasses = [];
this.toggleView = new ToggleView(false);
this.toggleView.observe(function (state) {
if (state) {
if (!this.subtreeView) {
this.subtreeView = subtreeFactory();
this.subtreeView.model(this.activeObject);
this.li.find('.tree-item-subtree').eq(0)
.append($(this.subtreeView.elements()));
}
$(this.subtreeView.elements()).removeClass('hidden');
} else if (this.subtreeView) {
$(this.subtreeView.elements()).addClass('hidden');
}
}.bind(this));
this.labelView = new TreeLabelView(gestureService);
$(this.labelView.elements()).on('click', function () {
selectFn(this.activeObject);
}.bind(this));
this.li.append($(nodeTemplate));
this.li.find('span').eq(0)
.append($(this.toggleView.elements()))
.append($(this.labelView.elements()));
this.model(undefined);
}
TreeNodeView.prototype.updateStatusClasses = function (statuses) {
this.statusClasses.forEach(function (statusClass) {
this.li.removeClass(statusClass);
}.bind(this));
this.statusClasses = statuses.map(function (status) {
return 's-status-' + status;
});
this.statusClasses.forEach(function (statusClass) {
this.li.addClass(statusClass);
}.bind(this));
};
TreeNodeView.prototype.model = function (domainObject) {
if (this.unlisten) {
this.unlisten();
}
this.activeObject = domainObject;
if (domainObject && domainObject.hasCapability('composition')) {
$(this.toggleView.elements()).addClass('has-children');
} else {
$(this.toggleView.elements()).removeClass('has-children');
}
if (domainObject && domainObject.hasCapability('status')) {
this.unlisten = domainObject.getCapability('status')
.listen(this.updateStatusClasses.bind(this));
this.updateStatusClasses(
domainObject.getCapability('status').list()
);
}
this.labelView.model(domainObject);
if (this.subtreeView) {
this.subtreeView.model(domainObject);
}
};
function getIdPath(domainObject) {
var context = domainObject && domainObject.getCapability('context');
function getId(domainObject) {
return domainObject.getId();
}
return context ? context.getPath().map(getId) : [];
}
TreeNodeView.prototype.value = function (domainObject) {
var activeIdPath = getIdPath(this.activeObject),
selectedIdPath = getIdPath(domainObject);
if (this.onSelectionPath) {
this.li.find('.tree-item').eq(0).removeClass('selected');
if (this.subtreeView) {
this.subtreeView.value(undefined);
}
}
this.onSelectionPath =
!!domainObject &&
!!this.activeObject &&
(activeIdPath.length <= selectedIdPath.length) &&
activeIdPath.every(function (id, index) {
return selectedIdPath[index] === id;
});
if (this.onSelectionPath) {
if (activeIdPath.length === selectedIdPath.length) {
this.li.find('.tree-item').eq(0).addClass('selected');
} else {
// Expand to reveal the selection
this.toggleView.value(true);
this.subtreeView.value(domainObject);
}
}
};
/**
*
* @returns {HTMLElement[]}
*/
TreeNodeView.prototype.elements = function () {
return this.li;
};
return TreeNodeView;
});

View File

@@ -0,0 +1,141 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
'zepto',
'./TreeNodeView',
'text!../../res/templates/tree/wait-node.html'
], function ($, TreeNodeView, spinnerTemplate) {
'use strict';
function TreeView(gestureService, selectFn) {
this.ul = $('<ul class="tree"></ul>');
this.nodeViews = [];
this.callbacks = [];
this.selectFn = selectFn || this.value.bind(this);
this.gestureService = gestureService;
this.pending = false;
}
TreeView.prototype.newTreeView = function () {
return new TreeView(this.gestureService, this.selectFn);
};
TreeView.prototype.setSize = function (sz) {
var nodeView;
while (this.nodeViews.length < sz) {
nodeView = new TreeNodeView(
this.gestureService,
this.newTreeView.bind(this),
this.selectFn
);
this.nodeViews.push(nodeView);
this.ul.append($(nodeView.elements()));
}
while (this.nodeViews.length > sz) {
nodeView = this.nodeViews.pop();
$(nodeView.elements()).remove();
}
};
TreeView.prototype.loadComposition = function () {
var self = this,
domainObject = this.activeObject;
function addNode(domainObject, index) {
self.nodeViews[index].model(domainObject);
}
function addNodes(domainObjects) {
if (self.pending) {
self.pending = false;
self.nodeViews = [];
self.ul.empty();
}
if (domainObject === self.activeObject) {
self.setSize(domainObjects.length);
domainObjects.forEach(addNode);
self.updateNodeViewSelection();
}
}
domainObject.useCapability('composition')
.then(addNodes);
};
TreeView.prototype.model = function (domainObject) {
if (this.unlisten) {
this.unlisten();
}
this.activeObject = domainObject;
this.ul.empty();
if (domainObject && domainObject.hasCapability('composition')) {
this.pending = true;
this.ul.append($(spinnerTemplate));
this.unlisten = domainObject.getCapability('mutation')
.listen(this.loadComposition.bind(this));
this.loadComposition(domainObject);
} else {
this.setSize(0);
}
};
TreeView.prototype.updateNodeViewSelection = function () {
this.nodeViews.forEach(function (nodeView) {
nodeView.value(this.selectedObject);
}.bind(this));
};
TreeView.prototype.value = function (domainObject) {
this.selectedObject = domainObject;
this.updateNodeViewSelection();
this.callbacks.forEach(function (callback) {
callback(domainObject);
});
};
TreeView.prototype.observe = function (callback) {
this.callbacks.push(callback);
return function () {
this.callbacks = this.callbacks.filter(function (c) {
return c !== callback;
});
}.bind(this);
};
/**
*
* @returns {HTMLElement[]}
*/
TreeView.prototype.elements = function () {
return this.ul;
};
return TreeView;
});

View File

@@ -0,0 +1,95 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,beforeEach,jasmine,it,expect*/
define([
'../../src/directives/MCTTree'
], function (MCTTree) {
describe("The mct-tree directive", function () {
var mockParse,
mockGestureService,
mockExpr,
mctTree;
beforeEach(function () {
mockGestureService = jasmine.createSpyObj(
'gestureService',
[ 'attachGestures' ]
);
mockParse = jasmine.createSpy('$parse');
mockExpr = jasmine.createSpy('expr');
mockExpr.assign = jasmine.createSpy('assign');
mockParse.andReturn(mockExpr);
mctTree = new MCTTree(mockParse, mockGestureService);
});
it("is applicable as an element", function () {
expect(mctTree.restrict).toEqual("E");
});
it("two-way binds to mctObject", function () {
expect(mctTree.scope).toEqual({ mctObject: "=" });
});
describe("link", function () {
var mockScope,
mockElement,
testAttrs;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
mockElement = jasmine.createSpyObj('element', ['append']);
testAttrs = { mctModel: "some-expression" };
mockScope.$parent =
jasmine.createSpyObj('$scope', ['$watch', '$on']);
mctTree.link(mockScope, mockElement, testAttrs);
});
it("populates the mct-tree element", function () {
expect(mockElement.append).toHaveBeenCalled();
});
it("watches for mct-model's expression in the parent", function () {
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
testAttrs.mctModel,
jasmine.any(Function)
);
});
it("watches for changes to mct-object", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"mctObject",
jasmine.any(Function)
);
});
it("listens for the $destroy event", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
"$destroy",
jasmine.any(Function)
);
});
});
});
});

View File

@@ -0,0 +1,313 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,beforeEach,jasmine,it,expect*/
define([
'../../src/ui/TreeView',
'zepto'
], function (TreeView, $) {
'use strict';
describe("TreeView", function () {
var mockGestureService,
mockGestureHandle,
mockDomainObject,
mockMutation,
mockUnlisten,
testCapabilities,
treeView;
function makeMockDomainObject(id, model, capabilities) {
var mockDomainObject = jasmine.createSpyObj(
'domainObject-' + id,
[
'getId',
'getModel',
'getCapability',
'hasCapability',
'useCapability'
]
);
mockDomainObject.getId.andReturn(id);
mockDomainObject.getModel.andReturn(model);
mockDomainObject.hasCapability.andCallFake(function (c) {
return !!(capabilities[c]);
});
mockDomainObject.getCapability.andCallFake(function (c) {
return capabilities[c];
});
mockDomainObject.useCapability.andCallFake(function (c) {
return capabilities[c] && capabilities[c].invoke();
});
return mockDomainObject;
}
beforeEach(function () {
mockGestureService = jasmine.createSpyObj(
'gestureService',
[ 'attachGestures' ]
);
mockGestureHandle = jasmine.createSpyObj('gestures', ['destroy']);
mockGestureService.attachGestures.andReturn(mockGestureHandle);
mockMutation = jasmine.createSpyObj('mutation', ['listen']);
mockUnlisten = jasmine.createSpy('unlisten');
mockMutation.listen.andReturn(mockUnlisten);
testCapabilities = { mutation: mockMutation };
mockDomainObject =
makeMockDomainObject('parent', {}, testCapabilities);
treeView = new TreeView(mockGestureService);
});
describe("elements", function () {
var elements;
beforeEach(function () {
elements = treeView.elements();
});
it("is an unordered list", function () {
expect(elements[0].tagName.toLowerCase())
.toEqual('ul');
});
});
describe("model", function () {
var mockComposition;
function makeGenericCapabilities() {
var mockContext =
jasmine.createSpyObj('context', [ 'getPath' ]),
mockType =
jasmine.createSpyObj('type', [ 'getGlyph' ]),
mockLocation =
jasmine.createSpyObj('location', [ 'isLink' ]),
mockMutation =
jasmine.createSpyObj('mutation', [ 'listen' ]),
mockStatus =
jasmine.createSpyObj('status', [ 'listen', 'list' ]);
mockStatus.list.andReturn([]);
return {
context: mockContext,
type: mockType,
mutation: mockMutation,
location: mockLocation,
status: mockStatus
};
}
function waitForCompositionCallback() {
var calledBack = false;
testCapabilities.composition.invoke().then(function (c) {
calledBack = true;
});
waitsFor(function () {
return calledBack;
});
}
beforeEach(function () {
mockComposition = ['a', 'b', 'c'].map(function (id) {
var testCapabilities = makeGenericCapabilities(),
mockChild =
makeMockDomainObject(id, {}, testCapabilities);
testCapabilities.context.getPath
.andReturn([mockDomainObject, mockChild]);
return mockChild;
});
testCapabilities.composition =
jasmine.createSpyObj('composition', ['invoke']);
testCapabilities.composition.invoke
.andReturn(Promise.resolve(mockComposition));
treeView.model(mockDomainObject);
waitForCompositionCallback();
});
it("adds one node per composition element", function () {
expect(treeView.elements()[0].childElementCount)
.toEqual(mockComposition.length);
});
it("listens for mutation", function () {
expect(testCapabilities.mutation.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
describe("when mutation occurs", function () {
beforeEach(function () {
mockComposition.pop();
testCapabilities.mutation.listen
.mostRecentCall.args[0](mockDomainObject.getModel());
waitForCompositionCallback();
});
it("continues to show one node per composition element", function () {
expect(treeView.elements()[0].childElementCount)
.toEqual(mockComposition.length);
});
});
describe("when replaced with a non-compositional domain object", function () {
beforeEach(function () {
delete testCapabilities.composition;
treeView.model(mockDomainObject);
});
it("stops listening for mutation", function () {
expect(mockUnlisten).toHaveBeenCalled();
});
it("removes all tree nodes", function () {
expect(treeView.elements()[0].childElementCount)
.toEqual(0);
});
});
describe("when selection state changes", function () {
var selectionIndex = 1;
beforeEach(function () {
treeView.value(mockComposition[selectionIndex]);
});
it("communicates selection state to an appropriate node", function () {
var selected = $(treeView.elements()[0]).find('.selected');
expect(selected.length).toEqual(1);
});
});
describe("when a context-less object is selected", function () {
beforeEach(function () {
var testCapabilities = makeGenericCapabilities(),
mockDomainObject =
makeMockDomainObject('xyz', {}, testCapabilities);
delete testCapabilities.context;
treeView.value(mockDomainObject);
});
it("clears all selection state", function () {
var selected = $(treeView.elements()[0]).find('.selected');
expect(selected.length).toEqual(0);
});
});
describe("when children contain children", function () {
beforeEach(function () {
var newCapabilities = makeGenericCapabilities(),
gcCapabilities = makeGenericCapabilities(),
mockNewChild =
makeMockDomainObject('d', {}, newCapabilities),
mockGrandchild =
makeMockDomainObject('gc', {}, gcCapabilities),
calledBackInner = false;
newCapabilities.composition =
jasmine.createSpyObj('composition', [ 'invoke' ]);
newCapabilities.composition.invoke
.andReturn(Promise.resolve([mockGrandchild]));
mockComposition.push(mockNewChild);
newCapabilities.context.getPath.andReturn([
mockDomainObject,
mockNewChild
]);
gcCapabilities.context.getPath.andReturn([
mockDomainObject,
mockNewChild,
mockGrandchild
]);
testCapabilities.mutation.listen
.mostRecentCall.args[0](mockDomainObject);
waitForCompositionCallback();
runs(function () {
// Select the innermost object to force expansion,
// such that we can verify the subtree is present.
treeView.value(mockGrandchild);
newCapabilities.composition.invoke().then(function () {
calledBackInner = true;
});
});
waitsFor(function () {
return calledBackInner;
});
});
it("creates inner trees", function () {
expect($(treeView.elements()[0]).find('ul').length)
.toEqual(1);
});
});
describe("when status changes", function () {
var testStatuses;
beforeEach(function () {
var mockStatus = mockComposition[1].getCapability('status');
testStatuses = [ 'foo' ];
mockStatus.list.andReturn(testStatuses);
mockStatus.listen.mostRecentCall.args[0](testStatuses);
});
it("reflects the status change in the tree", function () {
expect($(treeView.elements()).find('.s-status-foo').length)
.toEqual(1);
});
});
});
describe("observe", function () {
var mockCallback,
unobserve;
beforeEach(function () {
mockCallback = jasmine.createSpy('callback');
unobserve = treeView.observe(mockCallback);
});
it("notifies listeners when value is changed", function () {
treeView.value(mockDomainObject);
expect(mockCallback).toHaveBeenCalledWith(mockDomainObject);
});
it("does not notify listeners when deactivated", function () {
unobserve();
treeView.value(mockDomainObject);
expect(mockCallback).not.toHaveBeenCalled();
});
});
});
});

View File

@@ -85,7 +85,6 @@ define(
popup.goesUp() ? 'arw-btm' : 'arw-top',
popup.goesLeft() ? 'arw-right' : 'arw-left'
].join(' ');
scope.bubbleLayout = 'arw-top arw-left';
// Create the info bubble, now that we know how to
// point the arrow...

View File

@@ -109,6 +109,35 @@ define(
);
});
[ false, true ].forEach(function (goesLeft) {
[ false, true].forEach(function (goesUp) {
var vertical = goesUp ? "up" : "down",
horizontal = goesLeft ? "left" : "right",
location = [ vertical, horizontal].join('-');
describe("when bubble goes " + location, function () {
var expectedLocation = [
goesUp ? "bottom" : "top",
goesLeft ? "right" : "left"
].join('-');
beforeEach(function () {
mockPopup.goesUp.andReturn(goesUp);
mockPopup.goesDown.andReturn(!goesUp);
mockPopup.goesLeft.andReturn(goesLeft);
mockPopup.goesRight.andReturn(!goesLeft);
service.display('', '', {}, [ 10, 10 ]);
});
it("positions the arrow in the " + expectedLocation, function () {
expect(mockScope.bubbleLayout).toEqual([
goesUp ? "arw-btm" : "arw-top",
goesLeft ? "arw-right" : "arw-left"
].join(' '));
});
});
});
});
});
}
);

View File

@@ -27,6 +27,7 @@ $colorBtnIcon: $colorKey;
$colorInvokeMenu: #fff;
$contrastInvokeMenuPercent: 20%;
$shdwBtns: rgba(black, 0.2) 0 1px 2px;
$shdwBtnsOverlay: rgba(black, 0.5) 0 1px 5px;
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.1);
$sliderColorRange: rgba($sliderColorBase, 0.3);
@@ -173,6 +174,8 @@ $scrollbarTrackShdw: rgba(#000, 0.7) 0 1px 5px;
$scrollbarTrackColorBg: rgba(#000, 0.4);
$scrollbarThumbColor: lighten($colorBodyBg, 10%);
$scrollbarThumbColorHov: lighten($scrollbarThumbColor, 2%);
$scrollbarThumbColorOverlay: lighten($colorOvrBg, 10%);
$scrollbarThumbColorOverlayHov: lighten($scrollbarThumbColorOverlay, 2%);
// Splitter
$splitterD: 25px; // splitterD and HandleD should both be odd, or even

View File

@@ -27,6 +27,7 @@ $colorBtnIcon: #eee;
$colorInvokeMenu: #000;
$contrastInvokeMenuPercent: 40%;
$shdwBtns: none;
$shdwBtnsOverlay: none;
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.07);
$sliderColorRange: rgba($sliderColorBase, 0.2);
@@ -170,9 +171,11 @@ $shdwItemTreeIcon: none;
// Scrollbar
$scrollbarTrackSize: 10px;
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
$scrollbarTrackColorBg: rgba(#000, 0.1);
$scrollbarThumbColor: darken($colorBodyBg, 50%);//
$scrollbarTrackColorBg: rgba(#000, 0.2);
$scrollbarThumbColor: darken($colorBodyBg, 50%);
$scrollbarThumbColorHov: $colorKey;
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
// Splitter
$splitterD: 24px;

View File

@@ -248,7 +248,7 @@ define([
"property": "name",
"pattern": "\\S+",
"required": true,
"cssclass": "l-med"
"cssclass": "l-input-lg"
}
]
},

View File

@@ -182,7 +182,8 @@ define([
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
]
],
"cssclass": "l-inline"
},
{
"control": "select",
@@ -195,7 +196,8 @@ define([
"value": "clock24",
"name": "24hr"
}
]
],
"cssclass": "l-inline"
}
]
}
@@ -223,6 +225,7 @@ define([
{
"key": "timerFormat",
"control": "select",
"name": "Display Format",
"options": [
{
"value": "long",

View File

@@ -1,83 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
"./src/EventListController",
"./src/directives/MCTDataTable",
"./src/policies/MessagesViewPolicy",
"text!./res/templates/messages.html",
'legacyRegistry'
], function (
EventListController,
MCTDataTable,
MessagesViewPolicy,
messagesTemplate,
legacyRegistry
) {
"use strict";
legacyRegistry.register("platform/features/events", {
"name": "Event Messages",
"description": "List of time-ordered event messages",
"extensions": {
"views": [
{
"key": "messages",
"name": "Messages",
"glyph": "5",
"description": "Scrolling list of messages.",
"template": messagesTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "EventListController",
"implementation": EventListController,
"depends": [
"$scope",
"telemetryFormatter"
]
}
],
"directives": [
{
"key": "mctDataTable",
"implementation": MCTDataTable,
"depends": [
"$window"
]
}
],
"policies": [
{
"category": "view",
"implementation": MessagesViewPolicy
}
]
}
});
});

View File

@@ -1,37 +0,0 @@
<!--
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.
-->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>

View File

@@ -1,29 +0,0 @@
<!--
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.
-->
<div class="w1" ng-controller="TelemetryController as telemetry">
<div class="w2"
ng-controller="EventListController">
<mct-data-table headers="headers" rows="rows" ascending-scroll="true"></mct-data-table>
</div>
</div>

View File

@@ -1,63 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/events
* @constructor
* @implements {platform/features/events.EventsColumn}
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(domainMetadata, telemetryFormatter) {
this.domainMetadata = domainMetadata;
this.telemetryFormatter = telemetryFormatter;
}
DomainColumn.prototype.getTitle = function () {
return this.domainMetadata.name;
};
DomainColumn.prototype.getValue = function (domainObject, data, index) {
var domainKey = this.domainMetadata.key;
return this.telemetryFormatter.formatDomainValue(
data.getDomainValue(index, domainKey)
);
};
return DomainColumn;
}
);

View File

@@ -1,164 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
/*
* Module defining EventListController.
* Created by chacskaylo on 06/18/2015.
* Modified by shale on 06/23/2015.
*/
/**
* This bundle implements the "Events" view of string telemetry.
* @namespace platform/features/events
*/
define(
["./DomainColumn", "./RangeColumn", "./EventListPopulator"],
function (DomainColumn, RangeColumn, EventListPopulator) {
"use strict";
var ROW_COUNT = 100;
/**
* The EventListController is responsible for populating
* the contents of the event list view.
* @memberof platform/features/events
* @constructor
*/
function EventListController($scope, formatter) {
var populator;
// Get a set of populated, ready-to-display rows for the
// latest data values.
function getRows(telemetry) {
var datas = telemetry.getResponse(),
objects = telemetry.getTelemetryObjects();
return populator.getRows(datas, objects, ROW_COUNT);
}
// Update the contents
function updateRows() {
var telemetry = $scope.telemetry;
$scope.rows = telemetry ? getRows(telemetry) : [];
}
// Set up columns based on telemetry metadata. This will
// include one column for each domain and range type, as
// well as a column for the domain object name.
function setupColumns(telemetry) {
var domainKeys = {},
rangeKeys = {},
columns = [],
metadata;
// Add a domain to the set of columns, if a domain
// with the same key has not yet been inclued.
function addDomain(domain) {
var key = domain.key;
if (key && !domainKeys[key]) {
domainKeys[key] = true;
columns.push(new DomainColumn(domain, formatter));
}
}
// Add a range col to the set of columns, if a range
// with the same key has not yet been included.
function addRange(range) {
var key = range.key;
if (key && !rangeKeys[key]) {
rangeKeys[key] = true;
columns.push(new RangeColumn(range, formatter));
}
}
// We cannot proceed if the telemetry controller
// is not available; clear all rows/columns.
if (!telemetry) {
columns = [];
$scope.rows = [];
$scope.headers = [];
return;
}
// Add domain, range, event msg columns
metadata = telemetry.getMetadata();
(metadata || []).forEach(function (metadata) {
(metadata.domains || []).forEach(addDomain);
});
(metadata || []).forEach(function (metadata) {
(metadata.ranges || []).forEach(addRange);
});
// Add default domain and range columns if none
// were described in metadata.
if (Object.keys(domainKeys).length < 1) {
columns.push(new DomainColumn({name: "Time"}, formatter));
}
if (Object.keys(rangeKeys).length < 1) {
columns.push(new RangeColumn({name: "Message"}, formatter));
}
// We have all columns now; use them to initializer
// the populator, which will use them to generate
// actual rows and headers.
populator = new EventListPopulator(columns);
// Initialize headers
$scope.headers = populator.getHeaders();
// Fill in the contents of the rows.
updateRows();
}
$scope.$on("telemetryUpdate", updateRows);
$scope.$watch("telemetry", setupColumns);
}
return EventListController;
/**
* A description of how to display a certain column of data in an
* Events view.
* @interface platform/features/events.EventColumn
* @private
*/
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @method platform/features/events.EventColumn#getTitle
*/
/**
* Get the text to display inside a row under this
* column.
* @param {DomainObject} domainObject the domain object associated
* with this row
* @param {TelemetrySeries} series the telemetry data associated
* with this row
* @param {number} index the index of the telemetry datum associated
* with this row
* @returns {string} the text to display
* @method platform/features/events.EventColumn#getValue
*/
}
);

View File

@@ -1,165 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* The EventListPopulator is responsible for filling in the
* values which should appear within columns of a event list
* view, based on received telemetry data.
* @constructor
* @memberof platform/features/events
* @param {Column[]} columns the columns to be populated
*/
function EventListPopulator(columns) {
this.columns = columns;
}
/*
* Look up the most recent values from a set of data objects.
* Returns an array of objects in the order in which data
* should be displayed; each element is an object with
* two properties:
*
* * objectIndex: The index of the domain object associated
* with the data point to be displayed in that
* row.
* * pointIndex: The index of the data point itself, within
* its data set.
*
* @param {Array<Telemetry>} datas an array of the most recent
* data objects; expected to be in the same order
* as the domain objects provided at constructor
* @param {number} count the number of rows to provide
*/
function getLatestDataValues(datas, count) {
var latest = [],
candidate,
candidateTime,
used = datas.map(function () { return 0; });
// This algorithm is O(nk) for n rows and k telemetry elements;
// one O(k) linear search for a max is made for each of n rows.
// This could be done in O(n lg k + k lg k), using a priority
// queue (where priority is max-finding) containing k initial
// values. For n rows, pop the max from the queue and replenish
// the queue with a value from the data at the same
// objectIndex, if available.
// But k is small, so this might not give an observable
// improvement in performance.
// Find the most recent unused data point (this will be used
// in a loop to find and the N most recent data points)
function findCandidate(data, i) {
var nextTime,
pointCount = data.getPointCount(),
pointIndex = pointCount - used[i] - 1;
if (data && pointIndex >= 0) {
nextTime = data.getDomainValue(pointIndex);
if (nextTime > candidateTime) {
candidateTime = nextTime;
candidate = {
objectIndex: i,
pointIndex: pointIndex
};
}
}
}
// Assemble a list of the most recent data points
while (latest.length < count) {
// Reset variables pre-search
candidateTime = Number.NEGATIVE_INFINITY;
candidate = undefined;
// Linear search for most recent
datas.forEach(findCandidate);
if (candidate) {
// Record this data point - it is the most recent
latest.push(candidate);
// Track the data points used so we can look farther back
// in the data set on the next iteration
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
} else {
// Ran out of candidates; not enough data points
// available to fill all rows.
break;
}
}
return latest;
}
/**
* Get the text which should appear in headers for the
* provided columns.
* @memberof platform/features/events.EventListPopulator
* @returns {string[]} column headers
*/
EventListPopulator.prototype.getHeaders = function () {
return this.columns.map(function (column) {
return column.getTitle();
});
};
/**
* Get the contents of rows for the event list view.
* @param {TelemetrySeries[]} datas the data sets
* @param {DomainObject[]} objects the domain objects which
* provided the data sets; these should match
* index-to-index with the `datas` argument
* @param {number} count the number of rows to populate
* @memberof platform/features/events.EventListPopulator
* @returns {string[][]} an array of rows, each of which
* is an array of values which should appear
* in that row
*/
EventListPopulator.prototype.getRows = function (datas, objects, count) {
var values = getLatestDataValues(datas, count),
columns = this.columns;
// Each value will become a row, which will contain
// some value in each column (rendering by the
// column object itself)
// Additionally, we want to display the rows in reverse
// order. (i.e. from the top to the bottom of the page)
return values.map(function (value) {
return columns.map(function (column) {
return column.getValue(
objects[value.objectIndex],
datas[value.objectIndex],
value.pointIndex
);
});
}).reverse();
};
return EventListPopulator;
}
);

View File

@@ -1,62 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the ScrollingListController.
*
* @memberof platform/features/events
* @constructor
* @implements {platform/features/events.EventsColumn}
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn(rangeMetadata, telemetryFormatter) {
this.rangeMetadata = rangeMetadata;
this.telemetryFormatter = telemetryFormatter;
}
RangeColumn.prototype.getTitle = function () {
return this.rangeMetadata.name;
};
RangeColumn.prototype.getValue = function (domainObject, data, index) {
var rangeKey = this.rangeMetadata.key;
return this.telemetryFormatter.formatRangeValue(
data.getRangeValue(index, rangeKey)
);
};
return RangeColumn;
}
);

View File

@@ -1,74 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining MCTDataTable. Created by shale on 06/22/2015.
*/
define(
['text!../../res/templates/mct-data-table.html'],
function (dataTableTemplate) {
"use strict";
function MCTDataTable($window) {
return {
restrict: "E",
template: dataTableTemplate,
scope: {
headers: "=",
rows: "=",
ascendingScroll: "="
},
link: function ($scope, $element) {
var currentHeight,
previousHeight,
scrollParent;
// If the scroll is set to ascending, we want to
// check when elements are added to the table, and move the scroll
// bar accordingly.
// (When viewing at the bottom of the page, the scroll bar will
// stay at the bottom despite additions to the table)
if ($scope.ascendingScroll) {
$scope.$watch("rows", function () {
// Wait until the page as been repainted (otherwise the
// height will always be zero)
$window.requestAnimationFrame(function () {
previousHeight = currentHeight;
// The height of the table body
currentHeight = $element[0].firstElementChild.firstElementChild.nextElementSibling.clientHeight;
// One of the parents is a div that has vscroll
scrollParent = $element[0].parentElement.parentElement.parentElement.parentElement.parentElement;
// Move the scrollbar down the amount that the height has changed
scrollParent.scrollTop = scrollParent.scrollTop + (currentHeight - previousHeight);
});
});
}
}
};
}
return MCTDataTable;
}
);

View File

@@ -1,67 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
/**
* Module defining MessagesViewPolicy. Created by shale on 06/24/2015.
*/
define(
[],
function () {
"use strict";
/**
* Policy controlling when the Messages view should be avaliable.
* @memberof platform/features/events
* @constructor
* @implements {Policy.<View, DomainObject>}
*/
function MessagesViewPolicy() {}
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
MessagesViewPolicy.prototype.allow = function (view, domainObject) {
// This policy only applies for the Messages view
if (view.key === 'messages') {
// The Messages view is allowed only if the domain
// object has string telemetry
if (!hasStringTelemetry(domainObject)) {
return false;
}
}
// Like all policies, allow by default.
return true;
};
return MessagesViewPolicy;
}
);

View File

@@ -1,84 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
"use strict";
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("An event list domain column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getDomainValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(testMetadata, mockFormatter);
});
it("reports a column header from domain metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
it("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42))
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,110 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../src/EventListController"],
function (EventListController) {
"use strict";
describe("The event list controller", function () {
var mockScope,
mockTelemetry,
testMetadata,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetry = jasmine.createSpyObj(
"telemetryController",
[ "getResponse", "getMetadata", "getTelemetryObjects" ]
);
testMetadata = [
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d1", name: "D1" }
],
ranges: [
{ key: "r0", name: "R0" },
{ key: "r1", name: "R1" }
]
},
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d2", name: "D2" }
],
ranges: [
{ key: "r0", name: "R0" }
]
}
];
mockTelemetry.getMetadata.andReturn(testMetadata);
mockTelemetry.getResponse.andReturn([]);
mockTelemetry.getTelemetryObjects.andReturn([]);
mockScope.telemetry = mockTelemetry;
controller = new EventListController(mockScope);
});
it("listens for telemetry data updates", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
"telemetryUpdate",
jasmine.any(Function)
);
});
it("watches for telemetry controller changes", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"telemetry",
jasmine.any(Function)
);
});
it("provides a column for each unique domain and range", function () {
// Should have five columns based on metadata above,
// (d0, d1, d2, r0, r1)
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["D0", "D1", "D2", "R0", "R1"]);
});
it("does not throw if telemetry controller is undefined", function () {
// Just a general robustness check
mockScope.telemetry = undefined;
expect(mockScope.$watch.mostRecentCall.args[1])
.not.toThrow();
});
it("provides default columns if domain/range metadata is unavailable", function () {
mockTelemetry.getMetadata.andReturn([]);
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Time", "Message"]);
});
});
}
);

View File

@@ -1,103 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../src/EventListPopulator"],
function (EventListPopulator) {
"use strict";
describe("The event list populator", function () {
var mockColumns,
mockDatas,
mockDomainObjects,
populator;
function makeMockColumn(name, index) {
var mockColumn = jasmine.createSpyObj(
"column" + index,
[ "getTitle", "getValue" ]
);
mockColumn.getTitle.andReturn(name);
mockColumn.getValue.andCallFake(function (obj, data, i) {
return data.getDomainValue(i);
});
return mockColumn;
}
function makeMockData(bias, index) {
var mockData = jasmine.createSpyObj(
"data" + index,
[ "getDomainValue", "getPointCount" ]
);
mockData.getPointCount.andReturn(1000);
mockData.getDomainValue.andCallFake(function (i) {
return i + bias;
});
return mockData;
}
function makeMockDomainObject(name, index) {
var mockDomainObject = jasmine.createSpyObj(
"domainObject" + index,
[ "getId", "getModel" ]
);
return mockDomainObject;
}
beforeEach(function () {
mockColumns = ["A", "B", "C", "D"].map(makeMockColumn);
mockDatas = [ 10, 0, 3 ].map(makeMockData);
mockDomainObjects = ["A", "B", "C"].map(makeMockDomainObject);
populator = new EventListPopulator(mockColumns);
});
it("returns column headers", function () {
expect(populator.getHeaders()).toEqual(["A", "B", "C", "D"]);
});
it("provides rows on request, with all columns in each row", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84);
expect(rows.length).toEqual(84);
rows.forEach(function (row) {
expect(row.length).toEqual(4); // number of columns
});
});
it("returns rows in most-recent-last order", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84),
previous = Number.NEGATIVE_INFINITY;
// Should always be most-recent-last
rows.forEach(function (row) {
expect(row[0]).not.toBeLessThan(previous);
previous = row[0];
});
});
});
}
);

View File

@@ -1,81 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
"use strict";
var TEST_RANGE_VALUE = "some formatted range value";
describe("An event list range column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getRangeValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn(testMetadata, mockFormatter);
});
it("reports a column header from range metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getRangeValue)
.toHaveBeenCalledWith(42, "testKey");
});
it("formats range values as time", function () {
mockDataSet.getRangeValue.andReturn(123.45678);
expect(column.getValue(undefined, mockDataSet, 42))
.toEqual(TEST_RANGE_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(123.45678);
expect(mockFormatter.formatDomainValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,81 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../../src/policies/MessagesViewPolicy"],
function (MessagesViewPolicy) {
"use strict";
describe("The messages view policy", function () {
var mockDomainObject,
mockTelemetry,
telemetryType,
testType,
testView,
testMetadata,
policy;
beforeEach(function () {
testView = { key: "string" };
testMetadata = {};
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getModel', 'getCapability']
);
mockTelemetry = jasmine.createSpyObj(
'telemetry',
['getMetadata']
);
mockDomainObject.getModel.andCallFake(function (c) {
return {type: testType};
});
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'telemetry' ? mockTelemetry : undefined;
});
mockTelemetry.getMetadata.andReturn(testMetadata);
policy = new MessagesViewPolicy();
});
it("disallows the message view for objects without string telemetry", function () {
testMetadata.ranges = [ { format: 'notString' } ];
expect(policy.allow({ key: 'messages' }, mockDomainObject)).toBeFalsy();
});
it("allows the message view for objects with string telemetry", function () {
testMetadata.ranges = [ { format: 'string' } ];
expect(policy.allow({ key: 'messages' }, mockDomainObject)).toBeTruthy();
});
it("returns true when the current view is not the Messages view", function () {
expect(policy.allow({ key: 'notMessages' }, mockDomainObject)).toBeTruthy();
});
});
}
);

View File

@@ -278,12 +278,12 @@ define([
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
}
],
"key": "layoutGrid",

View File

@@ -49,7 +49,8 @@ define([
"name": "URL",
"control": "textfield",
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
"required": true
"required": true,
"cssclass": "l-input-lg"
}
]
}

View File

@@ -19,58 +19,52 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<style>
.l-inspect .l-inspector-part .no-margin .form {
margin-left: 0;
}
.reduced-min-width .form .form-row > .label {
min-width: 80px;
}
</style>
<div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<em class="t-inspector-part-header" title="Display properties for this object">Plot Options</em>
<mct-form
ng-model="configuration.plot.xAxis"
structure="xAxisForm"
name="xAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
<mct-form
ng-model="configuration.plot.yAxis"
structure="yAxisForm"
name="yAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="child in children">
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span class="tree-item menus-to-left">
<span
class='ui-symbol view-control flex-elem has-children'
ng-class="{ expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()">
<div class="form">
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="child in children">
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span class="tree-item menus-to-left">
<span
class='ui-symbol view-control flex-elem has-children'
ng-class="{ expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()">
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="child">
</mct-representation>
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="child">
</mct-representation>
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row no-validate">
</mct-form>
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row l-controls-first no-validate">
</mct-form>
</span>
</li>
</li>
</ul>
</ul>
</ul>
</div>
</div>

View File

@@ -48,9 +48,9 @@ define(
'control': 'select',
'key': 'key',
'options': [
{'name':'scet', 'value': 'scet'},
{'name':'sclk', 'value': 'sclk'},
{'name':'lst', 'value': 'lst'}
{'name':'SCET', 'value': 'scet'},
{'name':'SCLK', 'value': 'sclk'},
{'name':'LST', 'value': 'lst'}
]
}
]
@@ -64,6 +64,16 @@ define(
// itself.
'name': 'y-axis',
'rows': [
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name':'EU', 'value': 'eu'},
{'name':'DN', 'value': 'dn'},
{'name':'Status', 'value': 'status'}
]
},
{
'name': 'Autoscale',
'control': 'checkbox',
@@ -73,23 +83,15 @@ define(
'name': 'Min',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]'
'pattern': '[0-9]',
'inputsize' : 'sm'
},
{
'name': 'Max',
'control': 'textfield',
'key': 'max',
'pattern': '[0-9]'
},
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name':'eu', 'value': 'eu'},
{'name':'dn', 'value': 'dn'},
{'name':'status', 'value': 'status'}
]
'pattern': '[0-9]',
'inputsize' : 'sm'
}
]
}]
@@ -110,7 +112,8 @@ define(
{
'name': 'Markers',
'control': 'checkbox',
'key': 'markers'
'key': 'markers',
'layout': 'control-first'
}
]
},
@@ -120,19 +123,22 @@ define(
'name': 'No Line',
'control': 'radio',
'key': 'lineType',
'value': 'noLine'
'value': 'noLine',
'layout': 'control-first'
},
{
'name': 'Step Line',
'control': 'radio',
'key': 'lineType',
'value': 'stepLine'
'value': 'stepLine',
'layout': 'control-first'
},
{
'name': 'Linear Line',
'control': 'radio',
'key': 'lineType',
'value': 'linearLine'
'value': 'linearLine',
'layout': 'control-first'
}
]
}

View File

@@ -1,84 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
"./src/RTEventListController",
"./src/directives/MCTRTDataTable",
"./src/policies/RTMessagesViewPolicy",
"text!./res/templates/rtmessages.html",
'legacyRegistry'
], function (
RTEventListController,
MCTRTDataTable,
RTMessagesViewPolicy,
rtmessagesTemplate,
legacyRegistry
) {
"use strict";
legacyRegistry.register("platform/features/rtevents", {
"name": "Event Messages",
"description": "List of time-ordered event messages",
"extensions": {
"views": [
{
"key": "rtmessages",
"name": "RT Messages",
"glyph": "5",
"description": "Scrolling list of real time messages.",
"template": rtmessagesTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "RTEventListController",
"implementation": RTEventListController,
"depends": [
"$scope",
"telemetryHandler",
"telemetryFormatter"
]
}
],
"directives": [
{
"key": "mctRtDataTable",
"implementation": MCTRTDataTable,
"depends": [
"$window"
]
}
],
"policies": [
{
"category": "view",
"implementation": RTMessagesViewPolicy
}
]
}
});
});

View File

@@ -1,37 +0,0 @@
<!--
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.
-->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>

View File

@@ -1,75 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/rtevents
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(telemetryFormatter) {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtevents.DomainColumn#
*/
getTitle: function () {
// At the moment there does not appear to be a way to get the
// column's title through metadata for real time telemetry
return "Time";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtevents.DomainColumn#
*/
getValue: function (domainObject, handle) {
return {
text: telemetryFormatter.formatDomainValue(
handle.getDomainValue(domainObject)
)
};
}
};
}
return DomainColumn;
}
);

View File

@@ -1,140 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
/**
* Module defining RTEventListController.
* Created by shale on 06/25/2014. Based on RT Scrolling lists.
*/
define(
["./DomainColumn", "./RangeColumn"],
function (DomainColumn, RangeColumn) {
"use strict";
var ROW_COUNT = 100;
/**
* The RTEventListController is responsible for populating
* the contents of the messages view.
* @memberof platform/features/rtevents
* @constructor
*/
function RTEventListController($scope, telemetryHandler, telemetryFormatter) {
var handle,
lastUpdated = {},
lastIds = [],
columns = [],
headers = [],
rows = [];
function getTelemetryObjects() {
//console.log("handle.getTelemetryObjects() ", handle.getTelemetryObjects());
return handle ? handle.getTelemetryObjects() : [];
}
function idsChanged(telemetryObjects) {
function mismatch(id, index) {
return id !== telemetryObjects[index].getId();
}
return lastIds.length !== telemetryObjects.length ||
lastIds.some(mismatch);
}
function setupColumns(telemetryObjects) {
var id = $scope.domainObject && $scope.domainObject.getId(),
firstId =
telemetryObjects[0] && telemetryObjects[0].getId();
columns = [];
columns.push(new DomainColumn(telemetryFormatter));
columns.push(new RangeColumn());
headers = columns.map(function (column) {
return column.getTitle();
});
}
function updateObjects(telemetryObjects) {
if (idsChanged(telemetryObjects)) {
setupColumns(telemetryObjects);
lastIds = telemetryObjects.map(function (telemetryObject) {
return telemetryObject.getId();
});
}
}
function addRow(telemetryObject) {
var id = telemetryObject.getId(),
domainValue = handle.getDomainValue(telemetryObject);
if (lastUpdated[id] !== domainValue &&
domainValue !== undefined) {
// Instead of unshift (scrolling), use push (messages)
rows.push(columns.map(function (column) {
return column.getValue(telemetryObject, handle).text;
}));
// Remove first rows when adding past the max rows limit
rows.splice(0, rows.length - ROW_COUNT);
lastUpdated[id] = domainValue;
}
}
function updateValues() {
getTelemetryObjects().forEach(addRow);
}
function releaseSubscription() {
if (handle) {
handle.unsubscribe();
}
}
function makeSubscription(domainObject) {
releaseSubscription();
rows = [];
handle = telemetryHandler.handle(
domainObject,
updateValues,
true
);
}
$scope.$on("$destroy", releaseSubscription);
$scope.$watch("domainObject", makeSubscription);
$scope.$watch(getTelemetryObjects, updateObjects);
return {
rows: function () {
return rows;
},
headers: function () {
return headers;
}
};
}
return RTEventListController;
}
);

View File

@@ -1,72 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn.
* Created by vwoeltje on 11/18/14. Modified by shale on 06/25/2015.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the RTEventListController.
*
* @memberof platform/features/rtevents
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn() {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtevents.RangeColumn#
*/
getTitle: function () {
return "Message";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtevents.RangeColumn#
*/
getValue: function (domainObject, handle) {
return {
text: handle.getRangeValue(domainObject)
};
}
};
}
return RangeColumn;
}
);

View File

@@ -1,74 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining MCTRTDataTable. Created by shale on 06/25/2015.
*/
define(
['text!../../res/templates/mct-rt-data-table.html'],
function (dataTableTemplate) {
"use strict";
function MCTRTDataTable($window) {
return {
restrict: "E",
template: dataTableTemplate,
scope: {
headers: "=",
rows: "=",
ascendingScroll: "="
},
link: function ($scope, $element) {
var currentHeight,
previousHeight,
scrollParent;
// If the scroll is set to ascending, we want to
// check when elements are added to the table, and move the scroll
// bar accordingly.
// (When viewing at the bottom of the page, the scroll bar will
// stay at the bottom despite additions to the table)
if ($scope.ascendingScroll) {
$scope.$watchCollection("rows", function () {
// Wait until the page as been repainted (otherwise the
// height will always be zero)
$window.requestAnimationFrame(function () {
previousHeight = currentHeight;
// The height of the table body
currentHeight = $element[0].firstElementChild.firstElementChild.nextElementSibling.clientHeight;
// One of the parents is a div that has vscroll
scrollParent = $element[0].parentElement.parentElement.parentElement.parentElement.parentElement;
// Move the scrollbar down the amount that the height has changed
scrollParent.scrollTop = scrollParent.scrollTop + (currentHeight - previousHeight);
});
});
}
}
};
}
return MCTRTDataTable;
}
);

View File

@@ -1,76 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
/**
* Module defining MessagesViewPolicy. Created by shale on 06/24/2015.
*/
define(
[],
function () {
"use strict";
/**
* Policy controlling when the real time Messages view should be avaliable.
* @memberof platform/features/rtevents
* @constructor
*/
function RTMessagesViewPolicy() {
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
return {
/**
* Check whether or not a given action is allowed by this
* policy.
* @param {Action} action the action
* @param domainObject the domain object which will be viewed
* @returns {boolean} true if not disallowed
* @memberof platform/features/rtevents.RTMessagesViewPolicy#
*/
allow: function (view, domainObject) {
// This policy only applies for the RT Messages view
if (view.key === 'rtmessages') {
// The Messages view is allowed only if the domain
// object has string telemetry
if (!hasStringTelemetry(domainObject)) {
return false;
}
}
// Like all policies, allow by default.
return true;
}
};
}
return RTMessagesViewPolicy;
}
);

View File

@@ -1,88 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* RTEventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/25/2015.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
"use strict";
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A real time event list domain column", function () {
var mockDomainObject,
mockTelemetryHandler,
mockHandle,
mockFormatter,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getModel", "getCapability"]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(mockFormatter);
});
it("reports the domain column header as 'Time'", function () {
expect(column.getTitle()).toEqual("Time");
});
it("retrives data from a telemetry provider", function () {
column.getValue(mockDomainObject, mockHandle);
expect(mockHandle.getDomainValue).toHaveBeenCalled();
});
it("formats domain values as time", function () {
mockHandle.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(mockDomainObject, mockHandle).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,130 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* RTEventSpec. Created by shale on 06/25/2015.
*/
define(
["../src/RTEventListController"],
function (RTEventListController) {
"use strict";
describe("The real time event list controller", function () {
var mockDomainObject,
mockScope,
mockTelemetryHandler,
mockHandle,
mockTelemetryFormatter,
controller;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue", "getTelemetryObjects", "unsubscribe"]
);
mockTelemetryFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
controller = new RTEventListController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
mockHandle.getDomainValue.andReturn("domain value");
mockHandle.getRangeValue.andReturn("range value");
mockTelemetryHandler.handle.andReturn(mockHandle);
mockHandle.getTelemetryObjects.andReturn([mockDomainObject]);
// Subscribe to the RT telemetry
// second argument of: $scope.$watch("domainObject", makeSubscription);
mockScope.$watch.calls.forEach(function (c) {
// There are two possible calls of $watch, so we need to filter
// through the calls to get the correct kind
if (c.args[0] === 'domainObject') {
c.args[1]();
}
});
// callback, passed into telemetry handler
mockTelemetryHandler.handle.mostRecentCall.args[1]();
// Update the telemetry objects
// second argument of: $scope.$watch(getTelemetryObjects, updateObjects);
mockScope.$watch.calls.forEach(function (c) {
// There are two possible calls of $watch, so we need to filter
// through the calls to get the correct kind
if (c.args[0] !== 'domainObject') {
c.args[1]([mockDomainObject]);
}
});
});
it("provides a domain and a range column", function () {
// Should have two columns with these headers
expect(controller.headers()).toEqual(["Time", "Message"]);
});
it("listens for telemetry data updates", function () {
// Of the two possible $watch calls, this corresponds to
// $scope.$watch(getTelemetryObjects, updateObjects);
expect(mockScope.$watch).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it("makes telemetry subscriptions", function () {
// Of the two possible $watch calls, this corresponds to
// $scope.$watch("domainObject", makeSubscription);
expect(mockScope.$watch).toHaveBeenCalledWith(
"domainObject",
jasmine.any(Function)
);
});
it("releases telemetry subscriptions on destruction", function () {
// Call the second argument of
// $scope.$on("$destroy", releaseSubscription);
mockScope.$on.mostRecentCall.args[1]();
expect(mockScope.$on).toHaveBeenCalledWith(
"$destroy",
jasmine.any(Function)
);
});
});
}
);

View File

@@ -1,84 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* RTEventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/25/2015.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
"use strict";
var TEST_RANGE_VALUE = "some formatted range value";
describe("A real time event list range column", function () {
var mockDomainObject,
mockTelemetryHandler,
mockHandle,
mockFormatter,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getModel", "getCapability"]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn();
});
it("reports a range column header as 'Message'", function () {
expect(column.getTitle()).toEqual("Message");
});
it("retrives data from a telemetry provider", function () {
column.getValue(mockDomainObject, mockHandle);
expect(mockHandle.getRangeValue).toHaveBeenCalled();
});
it("does not format range values", function () {
mockHandle.getRangeValue.andReturn(123.45678);
// Does not format range value as time
expect(column.getValue(mockDomainObject, mockHandle).text)
.not.toEqual(TEST_RANGE_VALUE);
// There should be no additional formatting
// i.e. the message string stays a string
expect(column.getValue(mockDomainObject, mockHandle).text)
.toEqual(123.45678);
});
});
}
);

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/**
* RTEventSpec. Created by shale on 06/25/2015.
*/
define(
["../../src/policies/RTMessagesViewPolicy"],
function (RTMessagesViewPolicy) {
"use strict";
describe("The real time Messages view policy", function () {
var testView,
mockDomainObject,
mockTelemetry,
testMetadata,
policy;
beforeEach(function () {
testView = { key: "rtmessages" };
testMetadata = {};
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getId', 'getModel', 'getCapability']
);
mockTelemetry = jasmine.createSpyObj(
'telemetry',
['getMetadata']
);
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'telemetry' ? mockTelemetry : undefined;
});
mockTelemetry.getMetadata.andReturn(testMetadata);
policy = new RTMessagesViewPolicy();
});
it("allows the real time messages view for domain objects with string telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "string" } ];
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
});
it("disallows the real time messages view for domain objects without string telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ];
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
});
it("disallows the real time messages view for domain objects without telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "string" } ];
mockDomainObject.getCapability.andReturn(undefined);
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
});
it("allows other views", function () {
testView.key = "somethingElse";
testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ];
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
});
});
}
);

View File

@@ -1,2 +0,0 @@
This is a placeholder implementation of a Scrolling List view
which is compatible with realtime-only data.

View File

@@ -1,65 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
"./src/RTScrollingListController",
"text!./res/templates/rtscrolling.html",
'legacyRegistry'
], function (
RTScrollingListController,
rtscrollingTemplate,
legacyRegistry
) {
"use strict";
legacyRegistry.register("platform/features/rtscrolling", {
"name": "Scrolling Lists",
"description": "Time-ordered list of latest data.",
"extensions": {
"views": [
{
"key": "scrolling",
"name": "Scrolling",
"glyph": "5",
"description": "Scrolling list of data values.",
"template": rtscrollingTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "RTScrollingListController",
"implementation": RTScrollingListController,
"depends": [
"$scope",
"telemetryHandler",
"telemetryFormatter"
]
}
]
}
});
});

View File

@@ -1,51 +0,0 @@
<!--
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.
-->
<div class="w1">
<div class="w2"
ng-controller="RTScrollingListController as rtscroll">
<!-- To add filtering, add class 'filterable' to <table> and uncomment 2nd <tr> in <thead> -->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in rtscroll.headers()">
{{header}}
</th>
</tr>
<!--tr>
<th ng-repeat="header in headers">
<input type="text" />
</th>
</tr-->
</thead>
<tbody>
<tr ng-repeat="row in rtscroll.rows()">
<td ng-repeat="cell in row"
ng-class="cell.cssClass">
{{cell.text}}
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,73 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/rtscrolling
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(telemetryFormatter) {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.DomainColumn#
*/
getTitle: function () {
return "Time";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.DomainColumn#
*/
getValue: function (domainObject, handle) {
return {
text: telemetryFormatter.formatDomainValue(
handle.getDomainValue(domainObject)
)
};
}
};
}
return DomainColumn;
}
);

View File

@@ -1,66 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report the name of the domain object
* which exposed specific telemetry values.
*
* @memberof platform/features/rtscrolling
* @constructor
*/
function NameColumn() {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.NameColumn#
*/
getTitle: function () {
return "Name";
},
/**
* Get the text to display inside a row under this
* column. This returns the domain object's name.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.NameColumn#
*/
getValue: function (domainObject) {
return {
text: domainObject.getModel().name
};
}
};
}
return NameColumn;
}
);

View File

@@ -1,139 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining ListController. Created by vwoeltje on 11/18/14.
*/
define(
["./NameColumn", "./DomainColumn", "./RangeColumn"],
function (NameColumn, DomainColumn, RangeColumn) {
"use strict";
var ROW_COUNT = 100;
/**
* The RTScrollingListController is responsible for populating
* the contents of the scrolling list view.
* @memberof platform/features/rtscrolling
* @constructor
*/
function RTScrollingListController($scope, telemetryHandler, telemetryFormatter) {
var handle,
lastUpdated = {},
lastIds = [],
columns = [],
headers = [],
rows = [];
function getTelemetryObjects() {
return handle ? handle.getTelemetryObjects() : [];
}
function idsChanged(telemetryObjects) {
function mismatch(id, index) {
return id !== telemetryObjects[index].getId();
}
return lastIds.length !== telemetryObjects.length ||
lastIds.some(mismatch);
}
function setupColumns(telemetryObjects) {
var id = $scope.domainObject && $scope.domainObject.getId(),
firstId =
telemetryObjects[0] && telemetryObjects[0].getId();
columns = [];
if (telemetryObjects > 1 || id !== firstId) {
columns.push(new NameColumn());
}
columns.push(new DomainColumn(telemetryFormatter));
columns.push(new RangeColumn());
headers = columns.map(function (column) {
return column.getTitle();
});
}
function updateObjects(telemetryObjects) {
if (idsChanged(telemetryObjects)) {
setupColumns(telemetryObjects);
lastIds = telemetryObjects.map(function (telemetryObject) {
return telemetryObject.getId();
});
}
}
function addRow(telemetryObject) {
var id = telemetryObject.getId(),
domainValue = handle.getDomainValue(telemetryObject);
if (lastUpdated[id] !== domainValue &&
domainValue !== undefined) {
rows.unshift(columns.map(function (column) {
return column.getValue(telemetryObject, handle);
}));
rows.splice(ROW_COUNT, Number.MAX_VALUE);
lastUpdated[id] = domainValue;
}
}
function updateValues() {
getTelemetryObjects().forEach(addRow);
}
function releaseSubscription() {
if (handle) {
handle.unsubscribe();
}
}
function makeSubscription(domainObject) {
releaseSubscription();
rows = [];
handle = telemetryHandler.handle(
domainObject,
updateValues,
true
);
}
$scope.$on("$destroy", releaseSubscription);
$scope.$watch("domainObject", makeSubscription);
$scope.$watch(getTelemetryObjects, updateObjects);
return {
rows: function () {
return rows;
},
headers: function () {
return headers;
}
};
}
return RTScrollingListController;
}
);

View File

@@ -1,87 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the RTScrollingListController.
*
* @memberof platform/features/rtscrolling
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn() {
function findRange(domainObject) {
var telemetry = domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [{}];
return ranges[0].key;
}
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.RangeColumn#
*/
getTitle: function () {
return "Value";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.RangeColumn#
*/
getValue: function (domainObject, handle) {
var range = findRange(domainObject),
limit = domainObject.getCapability('limit'),
value = handle.getRangeValue(domainObject),
alarm = limit && limit.evaluate(
handle.getDatum(domainObject),
range
);
return {
cssClass: alarm && alarm.cssClass,
text: value
};
}
};
}
return RangeColumn;
}
);

View File

@@ -1,51 +0,0 @@
<!--
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.
-->
<div class="w1" ng-controller="TelemetryController as telemetry">
<div class="w2"
ng-controller="ScrollingListController">
<!-- To add filtering, add class 'filterable' to <table> and uncomment 2nd <tr> in <thead> -->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
<!--tr>
<th ng-repeat="header in headers">
<input type="text" />
</th>
</tr-->
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row"
ng-class="cell.cssClass">
{{cell.text}}
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,65 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,moment*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/scrolling
* @implements {platform/features/scrolling.ScrollingColumn}
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(domainMetadata, telemetryFormatter) {
this.domainMetadata = domainMetadata;
this.telemetryFormatter = telemetryFormatter;
}
DomainColumn.prototype.getTitle = function () {
return this.domainMetadata.name;
};
DomainColumn.prototype.getValue = function (domainObject, datum) {
return {
text: this.telemetryFormatter.formatDomainValue(
datum[this.domainMetadata.key],
this.domainMetadata.format
)
};
};
return DomainColumn;
}
);

View File

@@ -1,68 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the ScrollingListController.
*
* @memberof platform/features/scrolling
* @implements {platform/features/scrolling.ScrollingColumn}
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn(rangeMetadata, telemetryFormatter) {
this.rangeMetadata = rangeMetadata;
this.telemetryFormatter = telemetryFormatter;
}
RangeColumn.prototype.getTitle = function () {
return this.rangeMetadata.name;
};
RangeColumn.prototype.getValue = function (domainObject, datum) {
var range = this.rangeMetadata.key,
limit = domainObject.getCapability('limit'),
value = datum[range],
alarm = limit && limit.evaluate(datum, range);
return {
cssClass: alarm && alarm.cssClass,
text: this.telemetryFormatter.formatRangeValue(value)
};
};
return RangeColumn;
}
);

View File

@@ -1,159 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,Promise*/
/**
* This bundle implements a "Scrolling List" view of telemetry data.
* @namespace platform/features/scrolling
*/
define(
["./NameColumn", "./DomainColumn", "./RangeColumn", "./ScrollingListPopulator"],
function (NameColumn, DomainColumn, RangeColumn, ScrollingListPopulator) {
"use strict";
var ROW_COUNT = 18;
/**
* The ScrollingListController is responsible for populating
* the contents of the scrolling list view.
* @memberof platform/features/scrolling
* @constructor
*/
function ScrollingListController($scope, formatter) {
var populator = new ScrollingListPopulator([]);
// Get a set of populated, ready-to-display rows for the
// latest data values.
function getRows(telemetry) {
var datas = telemetry.getResponse(),
objects = telemetry.getTelemetryObjects();
return populator.getRows(datas, objects, ROW_COUNT);
}
// Update the contents
function updateRows() {
var telemetry = $scope.telemetry;
$scope.rows = telemetry ? getRows(telemetry) : [];
}
// Set up columns based on telemetry metadata. This will
// include one column for each domain and range type, as
// well as a column for the domain object name.
function setupColumns(metadatas) {
var domainKeys = {},
rangeKeys = {},
columns = [];
// Add a domain to the set of columns, if a domain
// with the same key has not yet been inclued.
function addDomain(domain) {
var key = domain.key;
if (key && !domainKeys[key]) {
domainKeys[key] = true;
columns.push(new DomainColumn(domain, formatter));
}
}
// Add a range to the set of columns, if a range
// with the same key has not yet been inclued.
function addRange(range) {
var key = range.key;
if (key && !rangeKeys[key]) {
rangeKeys[key] = true;
columns.push(new RangeColumn(range, formatter));
}
}
// We cannot proceed if metadata is not available;
// clear all rows/columns.
if (!Array.isArray(metadatas)) {
columns = [];
$scope.rows = [];
$scope.headers = [];
return;
}
columns = [ new NameColumn() ];
// Add domain, range columns
metadatas.forEach(function (metadata) {
(metadata.domains || []).forEach(addDomain);
});
metadatas.forEach(function (metadata) {
(metadata.ranges || []).forEach(addRange);
});
// Add default domain, range columns if none
// were described in metadata.
if (Object.keys(domainKeys).length < 1) {
columns.push(new DomainColumn({name: "Time"}, formatter));
}
if (Object.keys(rangeKeys).length < 1) {
columns.push(new RangeColumn({name: "Value"}, formatter));
}
// We have all columns now; use them to initializer
// the populator, which will use them to generate
// actual rows and headers.
populator = new ScrollingListPopulator(columns);
// Initialize headers
$scope.headers = populator.getHeaders();
// Fill in the contents of the rows.
updateRows();
}
$scope.$on("telemetryUpdate", updateRows);
$scope.$watch("telemetry.getMetadata()", setupColumns);
}
/**
* A description of how to display a certain column of data in a
* Scrolling List view.
* @interface platform/features/scrolling.ScrollingColumn
* @private
*/
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @method platform/features/scrolling.ScrollingColumn#getTitle
*/
/**
* Get the text to display inside a row under this
* column.
* @param {DomainObject} domainObject the domain object associated
* with this row
* @param {TelemetrySeries} series the telemetry data associated
* with this row
* @param {number} index the index of the telemetry datum associated
* with this row
* @returns {string} the text to display
* @method platform/features/scrolling.ScrollingColumn#getValue
*/
return ScrollingListController;
}
);

View File

@@ -1,189 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* The ScrollingListPopulator is responsible for filling in the
* values which should appear within columns of a scrolling list
* view, based on received telemetry data.
* @memberof platform/features/scrolling
* @constructor
* @param {Column[]} columns the columns to be populated
*/
function ScrollingListPopulator(columns) {
this.columns = columns;
}
/**
* Look up the most recent values from a set of data objects.
* Returns an array of objects in the order in which data
* should be displayed; each element is an object with
* two properties:
*
* * objectIndex: The index of the domain object associated
* with the data point to be displayed in that
* row.
* * pointIndex: The index of the data point itself, within
* its data set.
*
* @param {Array<Telemetry>} datas an array of the most recent
* data objects; expected to be in the same order
* as the domain objects provided at constructor
* @param {number} count the number of rows to provide
* @returns {Array} latest data values in display order
* @private
*/
function getLatestDataValues(datas, count) {
var latest = [],
candidate,
candidateTime,
used = datas.map(function () { return 0; });
// This algorithm is O(nk) for n rows and k telemetry elements;
// one O(k) linear search for a max is made for each of n rows.
// This could be done in O(n lg k + k lg k), using a priority
// queue (where priority is max-finding) containing k initial
// values. For n rows, pop the max from the queue and replenish
// the queue with a value from the data at the same
// objectIndex, if available.
// But k is small, so this might not give an observable
// improvement in performance.
// Find the most recent unused data point (this will be used
// in a loop to find and the N most recent data points)
function findCandidate(data, i) {
var nextTime,
pointCount = data.getPointCount(),
pointIndex = pointCount - used[i] - 1;
if (data && pointIndex >= 0) {
nextTime = data.getDomainValue(pointIndex);
if (nextTime > candidateTime) {
candidateTime = nextTime;
candidate = {
objectIndex: i,
pointIndex: pointIndex
};
}
}
}
// Assemble a list of the most recent data points
while (latest.length < count) {
// Reset variables pre-search
candidateTime = Number.NEGATIVE_INFINITY;
candidate = undefined;
// Linear search for most recent
datas.forEach(findCandidate);
if (candidate) {
// Record this data point - it is the most recent
latest.push(candidate);
// Track the data points used so we can look farther back
// in the data set on the next iteration
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
} else {
// Ran out of candidates; not enough data points
// available to fill all rows.
break;
}
}
return latest;
}
/**
* Get the text which should appear in headers for the
* provided columns.
* @returns {string[]} column headers
*/
ScrollingListPopulator.prototype.getHeaders = function () {
return this.columns.map(function (column) {
return column.getTitle();
});
};
/**
* Get the contents of rows for the scrolling list view.
* @param {TelemetrySeries[]} datas the data sets
* @param {DomainObject[]} objects the domain objects which
* provided the data sets; these should match
* index-to-index with the `datas` argument
* @param {number} count the number of rows to populate
* @returns {string[][]} an array of rows, each of which
* is an array of values which should appear
* in that row
*/
ScrollingListPopulator.prototype.getRows = function (datas, objects, count) {
var values = getLatestDataValues(datas, count),
self = this;
// From a telemetry series, retrieve a single data point
// containing all fields for domains/ranges
function makeDatum(domainObject, series, index) {
var telemetry = domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
result = {};
(metadata.domains || []).forEach(function (domain) {
result[domain.key] =
series.getDomainValue(index, domain.key);
});
(metadata.ranges || []).forEach(function (range) {
result[range.key] =
series.getRangeValue(index, range.key);
});
return result;
}
// Each value will become a row, which will contain
// some value in each column (rendering by the
// column object itself)
return values.map(function (value) {
var datum = makeDatum(
objects[value.objectIndex],
datas[value.objectIndex],
value.pointIndex
);
return self.columns.map(function (column) {
return column.getValue(
objects[value.objectIndex],
datum
);
});
});
};
return ScrollingListPopulator;
}
);

View File

@@ -1,84 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
"use strict";
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A domain column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getDomainValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(testMetadata, mockFormatter);
});
it("reports a column header from domain metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
xit("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
xit("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,76 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
"use strict";
var TEST_RANGE_VALUE = "some formatted range value";
describe("A range column", function () {
var testDatum,
testMetadata,
mockFormatter,
mockDomainObject,
column;
beforeEach(function () {
testDatum = { testKey: 123, otherKey: 456 };
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel", "getCapability" ]
);
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn(testMetadata, mockFormatter);
});
it("reports a column header from range metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("formats range values as numbers", function () {
expect(column.getValue(mockDomainObject, testDatum).text)
.toEqual(TEST_RANGE_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(testDatum.testKey);
expect(mockFormatter.formatDomainValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,110 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/ScrollingListController"],
function (ScrollingListController) {
"use strict";
describe("The scrolling list controller", function () {
var mockScope,
mockTelemetry,
testMetadata,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetry = jasmine.createSpyObj(
"telemetryController",
[ "getResponse", "getMetadata", "getTelemetryObjects" ]
);
testMetadata = [
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d1", name: "D1" }
],
ranges: [
{ key: "r0", name: "R0" },
{ key: "r1", name: "R1" }
]
},
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d2", name: "D2" }
],
ranges: [
{ key: "r0", name: "R0" }
]
}
];
mockTelemetry.getMetadata.andReturn(testMetadata);
mockTelemetry.getResponse.andReturn([]);
mockTelemetry.getTelemetryObjects.andReturn([]);
mockScope.telemetry = mockTelemetry;
controller = new ScrollingListController(mockScope);
});
it("listens for telemetry data updates", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
"telemetryUpdate",
jasmine.any(Function)
);
});
xit("watches for telemetry controller changes", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"telemetry",
jasmine.any(Function)
);
});
xit("provides a column for each name and each unique domain, range", function () {
// Should have six columns based on metadata above,
// (name, d0, d1, d2, r0, r1)
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Name", "D0", "D1", "D2", "R0", "R1"]);
});
it("does not throw if telemetry controller is undefined", function () {
// Just a general robustness check
mockScope.telemetry = undefined;
expect(mockScope.$watch.mostRecentCall.args[1])
.not.toThrow();
});
xit("provides default columns if domain/range metadata is unavailable", function () {
mockTelemetry.getMetadata.andReturn([]);
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Name", "Time", "Value"]);
});
});
}
);

View File

@@ -1,105 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/ScrollingListPopulator"],
function (ScrollingListPopulator) {
"use strict";
describe("The scrolling list populator", function () {
var mockColumns,
mockDatas,
mockDomainObjects,
populator;
function makeMockColumn(name, index) {
var mockColumn = jasmine.createSpyObj(
"column" + index,
[ "getTitle", "getValue" ]
);
mockColumn.getTitle.andReturn(name);
mockColumn.getValue.andCallFake(function (obj, data, i) {
return data.getDomainValue(i);
});
return mockColumn;
}
function makeMockData(bias, index) {
var mockData = jasmine.createSpyObj(
"data" + index,
[ "getDomainValue", "getPointCount" ]
);
mockData.getPointCount.andReturn(1000);
mockData.getDomainValue.andCallFake(function (i) {
return i + bias;
});
return mockData;
}
function makeMockDomainObject(name, index) {
var mockDomainObject = jasmine.createSpyObj(
"domainObject" + index,
[ "getId", "getModel" ]
);
return mockDomainObject;
}
beforeEach(function () {
mockColumns = ["A", "B", "C", "D"].map(makeMockColumn);
mockDatas = [ 10, 0, 3 ].map(makeMockData);
mockDomainObjects = ["A", "B", "C"].map(makeMockDomainObject);
populator = new ScrollingListPopulator(mockColumns);
});
it("returns column headers", function () {
expect(populator.getHeaders()).toEqual(["A", "B", "C", "D"]);
});
xit("provides rows on request, with all columns in each row", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84);
expect(rows.length).toEqual(84);
rows.forEach(function (row) {
expect(row.length).toEqual(4); // number of columns
});
});
xit("returns rows in reverse domain order", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84),
previous = Number.POSITIVE_INFINITY;
// Should always be most-recent-first; since the mockColumn
// returns the domain value, column contents should be
// non-increasing.
rows.forEach(function (row) {
expect(row[0]).not.toBeGreaterThan(previous);
previous = row[0];
});
});
});
}
);

View File

@@ -23,6 +23,7 @@
define([
"./src/directives/MCTTable",
"./src/controllers/RTTelemetryTableController",
"./src/controllers/TelemetryTableController",
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
@@ -30,6 +31,7 @@ define([
"legacyRegistry"
], function (
MCTTable,
RTTelemetryTableController,
TelemetryTableController,
TableOptionsController,
Region,
@@ -59,7 +61,7 @@ define([
"types": [
{
"key": "table",
"name": "Table",
"name": "Historical Telemetry Table",
"glyph": "\ue605",
"description": "A table for displaying telemetry data",
"features": "creation",
@@ -78,6 +80,30 @@ define([
"views": [
"table"
]
},
{
"key": "rttable",
"name": "Real-time Telemetry Table",
"glyph": "\ue605",
"description": "A table for displaying realtime telemetry" +
" data",
"features": "creation",
"delegates": [
"telemetry"
],
"inspector": tableInspector,
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"views": [
"rt-table",
"scrolling-table"
]
}
],
"controllers": [
@@ -86,6 +112,11 @@ define([
"implementation": TelemetryTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "RTTelemetryTableController",
"implementation": RTTelemetryTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "TableOptionsController",
"implementation": TableOptionsController,
@@ -95,7 +126,7 @@ define([
],
"views": [
{
"name": "Table",
"name": "Historical Table",
"key": "table",
"glyph": "\ue605",
"templateUrl": "templates/table.html",
@@ -104,6 +135,17 @@ define([
],
"delegation": true,
"editable": true
},
{
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue605",
"templateUrl": "templates/rt-table.html",
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
}
],
"directives": [

View File

@@ -1,10 +1,13 @@
<div class="l-view-section scrolling"
style="overflow: auto;"
ng-style="overrideRowPositioning ?
{'overflow': 'auto'} :
{'overflow': 'scroll'}"
>
<table class="filterable"
ng-style="overrideRowPositioning && {
height: totalHeight + 'px',
'table-layout': overrideRowPositioning ? 'fixed' : 'auto'
'table-layout': overrideRowPositioning ? 'fixed' : 'auto',
'max-width': totalWidth
}">
<thead>
<tr>
@@ -59,6 +62,5 @@
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,9 @@
<div ng-controller="RTTelemetryTableController">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true"
auto-scroll="autoScroll">
</mct-table>
</div>

View File

@@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="TableOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<div ng-controller="TableOptionsController" class="l-controls-first flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
<mct-form
ng-model="configuration.table.columns"
structure="columnsForm"
name="columnsFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
</div>

View File

@@ -47,26 +47,28 @@ define(
* @param metadata Metadata describing the domains and ranges available
* @returns {TableConfiguration} This object
*/
TableConfiguration.prototype.buildColumns = function(metadata) {
TableConfiguration.prototype.buildColumns = function (metadata) {
var self = this;
this.columns = [];
if (metadata) {
if (metadata.length > 1){
self.addColumn(new NameColumn(), 0);
}
metadata.forEach(function (metadatum) {
//Push domains first
(metadatum.domains || []).forEach(function (domainMetadata) {
self.addColumn(new DomainColumn(domainMetadata, self.telemetryFormatter));
self.addColumn(new DomainColumn(domainMetadata,
self.telemetryFormatter));
});
(metadatum.ranges || []).forEach(function (rangeMetadata) {
self.addColumn(new RangeColumn(rangeMetadata, self.telemetryFormatter));
self.addColumn(new RangeColumn(rangeMetadata,
self.telemetryFormatter));
});
});
if (this.columns.length > 0){
self.addColumn(new NameColumn(), 0);
}
}
return this;
};
@@ -98,7 +100,7 @@ define(
* Get a simple list of column titles
* @returns {Array} The titles of the columns
*/
TableConfiguration.prototype.getHeaders = function() {
TableConfiguration.prototype.getHeaders = function () {
var self = this;
return this.columns.map(function (column, i){
return self.getColumnTitle(column) || 'Column ' + (i + 1);
@@ -113,9 +115,9 @@ define(
* @returns {Object} Key value pairs where the key is the column
* title, and the value is the formatted value from the provided datum.
*/
TableConfiguration.prototype.getRowValues = function(telemetryObject, datum) {
TableConfiguration.prototype.getRowValues = function (telemetryObject, datum) {
var self = this;
return this.columns.reduce(function(rowObject, column, i){
return this.columns.reduce(function (rowObject, column, i){
var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1),
columnValue = column.getValue(telemetryObject, datum);
@@ -125,7 +127,9 @@ define(
// Don't replace something with nothing.
// This occurs when there are multiple columns with the
// column title
if (rowObject[columnTitle] === undefined || rowObject[columnTitle].text === undefined || rowObject[columnTitle].text.length === 0) {
if (rowObject[columnTitle] === undefined ||
rowObject[columnTitle].text === undefined ||
rowObject[columnTitle].text.length === 0) {
rowObject[columnTitle] = columnValue;
}
return rowObject;
@@ -136,7 +140,8 @@ define(
* @private
*/
TableConfiguration.prototype.defaultColumnConfiguration = function () {
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
return ((this.domainObject.getModel().configuration || {}).table ||
{}).columns || {};
};
/**
@@ -158,7 +163,7 @@ define(
* pairs where the key is the column title, and the value is a
* boolean indicating whether the column should be shown.
*/
TableConfiguration.prototype.getColumnConfiguration = function() {
TableConfiguration.prototype.getColumnConfiguration = function () {
var configuration = {},
//Use existing persisted config, or default it
defaultConfig = this.defaultColumnConfiguration();
@@ -168,8 +173,10 @@ define(
* specifying whether the column is visible or not. Default to
* existing (persisted) configuration if available
*/
this.getHeaders().forEach(function(columnTitle) {
configuration[columnTitle] = typeof defaultConfig[columnTitle] === 'undefined' ? true : defaultConfig[columnTitle];
this.getHeaders().forEach(function (columnTitle) {
configuration[columnTitle] =
typeof defaultConfig[columnTitle] === 'undefined' ? true :
defaultConfig[columnTitle];
});
return configuration;

View File

@@ -5,6 +5,15 @@ define(
function () {
"use strict";
/**
* A controller for the MCTTable directive. Populates scope with
* data used for populating, sorting, and filtering
* tables.
* @param $scope
* @param $timeout
* @param element
* @constructor
*/
function MCTTableController($scope, $timeout, element) {
var self = this;
@@ -13,6 +22,9 @@ define(
this.$timeout = $timeout;
this.maxDisplayRows = 50;
this.scrollable = element.find('div');
this.scrollable.on('scroll', this.onScroll.bind(this));
$scope.visibleRows = [];
$scope.overrideRowPositioning = false;
@@ -33,8 +45,6 @@ define(
setDefaults($scope);
element.find('div').on('scroll', this.onScroll.bind(this));
$scope.toggleSort = function (key) {
if (!$scope.enableSort) {
return;
@@ -51,22 +61,96 @@ define(
self.updateRows($scope.rows);
};
/*
* Define watches to listen for changes to headers and rows.
*/
$scope.$watchCollection('filters', function () {
self.updateRows(self.$scope.rows);
self.updateRows($scope.rows);
});
$scope.$watchCollection('headers', this.updateHeaders.bind(this));
$scope.$watchCollection('rows', this.updateRows.bind(this));
$scope.$watch('headers', this.updateHeaders.bind(this));
$scope.$watch('rows', this.updateRows.bind(this));
/*
* Listen for rows added individually (eg. for real-time tables)
*/
$scope.$on('add:row', this.newRow.bind(this));
$scope.$on('remove:row', this.removeRow.bind(this));
}
/**
* On scroll, calculate which rows indexes are visible and
* ensure that an equal number of rows are preloaded for
* scrolling in either direction.
* If auto-scroll is enabled, this function will scroll to the
* bottom of the page
* @private
*/
MCTTableController.prototype.scrollToBottom = function () {
var self = this;
//Use timeout to defer execution until next digest when any
// pending UI changes have completed, eg. a new row in the table.
if (this.$scope.autoScroll) {
this.$timeout(function (){
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
});
}
};
/**
* Handles a row add event. Rows can be added as needed using the
* `addRow` broadcast event.
* @private
*/
MCTTableController.prototype.newRow = function (event, rowIndex) {
var row = this.$scope.rows[rowIndex];
//Add row to the filtered, sorted list of all rows
if (this.filterRows([row]).length > 0) {
this.insertSorted(this.$scope.displayRows, row);
}
this.$timeout(this.setElementSizes.bind(this))
.then(this.scrollToBottom.bind(this));
};
/**
* Handles a row add event. Rows can be added as needed using the
* `addRow` broadcast event.
* @private
*/
MCTTableController.prototype.removeRow = function (event, rowIndex) {
var row = this.$scope.rows[rowIndex],
// Do a sequential search here. Only way of finding row is by
// object equality, so array is in effect unsorted.
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
if (indexInDisplayRows != -1) {
this.$scope.displayRows.splice(indexInDisplayRows, 1);
this.setVisibleRows();
}
};
/**
* @private
*/
MCTTableController.prototype.onScroll = function (event) {
//If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
if (this.scrollable[0].scrollTop <
(this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight)) {
this.$scope.autoScroll = false;
} else {
this.$scope.autoScroll = true;
}
this.setVisibleRows();
this.$scope.$digest();
};
/**
* Sets visible rows based on array
* content and current scroll state.
*/
MCTTableController.prototype.setVisibleRows = function () {
var self = this,
topScroll = event.target.scrollTop,
bottomScroll = topScroll + event.target.offsetHeight,
target = this.scrollable[0],
topScroll = target.scrollTop,
bottomScroll = topScroll + target.offsetHeight,
firstVisible,
lastVisible,
totalVisible,
@@ -74,42 +158,59 @@ define(
start,
end;
//No need to scroll
if (this.$scope.displayRows.length < this.maxDisplayRows) {
return;
}
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
//Check whether need to resynchronize visible with display
// rows (if data added)
if (this.$scope.visibleRows.length !=
this.$scope.displayRows.length){
start = 0;
end = this.$scope.displayRows.length;
} else {
//Data is in sync, and no need to calculate scroll,
// so do nothing.
return;
}
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) / this.$scope.rowHeight
//rows has exceeded display maximum, so may be necessary to
// scroll
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
totalVisible = lastVisible - firstVisible;
numberOffscreen = this.maxDisplayRows - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(this.maxDisplayRows,
this.$scope.displayRows.length);
} else if (end >= this.$scope.displayRows.length) {
end = this.$scope.displayRows.length;
start = end - this.maxDisplayRows + 1;
}
if (this.$scope.visibleRows[0] &&
this.$scope.visibleRows[0].rowIndex === start &&
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
.rowIndex === end) {
return; // don't update if no changes are required.
}
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) / this.$scope.rowHeight
);
totalVisible = lastVisible - firstVisible;
numberOffscreen = this.maxDisplayRows - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = this.$scope.visibleRows.length - 1;
} else if (end >= this.$scope.displayRows.length) {
end = this.$scope.displayRows.length - 1;
start = end - this.maxDisplayRows + 1;
}
if (this.$scope.visibleRows[0].rowIndex === start &&
this.$scope.visibleRows[this.$scope.visibleRows.length-1]
.rowIndex === end) {
return; // don't update if no changes are required.
}
//Set visible rows from display rows, based on calculated offset.
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
.map(function(row, i) {
.map(function (row, i) {
return {
rowIndex: start + i,
offsetY: ((start + i) * self.$scope.rowHeight) +
@@ -117,8 +218,6 @@ define(
contents: row
};
});
this.$scope.$digest();
};
/**
@@ -136,10 +235,11 @@ define(
this.$scope.filters = {};
}
// Reset column sort information unless the new headers
// contain the column current sorted on.
if (this.$scope.enableSort && newHeaders.indexOf(this.$scope.sortColumn) === -1) {
this.$scope.sortColumn = undefined;
this.$scope.sortDirection = undefined;
// contain the column currently sorted on.
if (this.$scope.enableSort &&
newHeaders.indexOf(this.$scope.sortColumn) === -1) {
this.$scope.sortColumn = undefined;
this.$scope.sortDirection = undefined;
}
this.updateRows(this.$scope.rows);
};
@@ -155,80 +255,142 @@ define(
firstRow = tbody.find('tr'),
column = firstRow.find('td'),
headerHeight = thead.prop('offsetHeight'),
//row height is hard-coded for now.
rowHeight = 20,
overallHeight = headerHeight + (rowHeight * (this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
columnWidth,
tableWidth = 0,
overallHeight = headerHeight + (rowHeight *
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
this.$scope.columnWidths = [];
while (column.length) {
columnWidth = column.prop('offsetWidth');
this.$scope.columnWidths.push(column.prop('offsetWidth'));
tableWidth += columnWidth;
column = column.next();
}
this.$scope.headerHeight = headerHeight;
this.$scope.rowHeight = rowHeight;
this.$scope.totalHeight = overallHeight;
this.setVisibleRows();
this.$scope.visibleRows = this.$scope.displayRows.slice(0, this.maxDisplayRows).map(function(row, i) {
return {
rowIndex: i,
offsetY: (i * self.$scope.rowHeight) + self.$scope.headerHeight,
contents: row
};
});
if (tableWidth > 0) {
this.$scope.totalWidth = tableWidth + 'px';
} else {
this.$scope.totalWidth = 'none';
}
this.$scope.overrideRowPositioning = true;
};
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1,
self = this,
sortKey = this.$scope.sortColumn;
function binarySearch(searchArray, searchElement, min, max){
var sampleAt = Math.floor((max - min) / 2) + min,
valA,
valB;
if (max < min) {
return min; // Element is not in array, min gives direction
}
valA = isNaN(searchElement[sortKey].text) ?
searchElement[sortKey].text :
parseFloat(searchElement[sortKey].text);
valB = isNaN(searchArray[sampleAt][sortKey].text) ?
searchArray[sampleAt][sortKey].text :
parseFloat(searchArray[sampleAt][sortKey].text);
switch(self.sortComparator(valA, valB)) {
case -1:
return binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
}
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
//No sorting applied, push it on the end.
index = array.length;
} else {
//Sort is enabled, perform binary search to find insertion point
index = binarySearch(array, element, 0, array.length - 1);
}
if (index === -1){
array.unshift(element);
} else if (index === array.length){
array.push(element);
} else {
array.splice(index, 0, element);
}
};
/**
* Compare two variables, returning a number that represents
* which is larger. Similar to the default array sort
* comparator, but does not coerce all values to string before
* conversion. Strings are lowercased before comparison.
*
* @private
*/
MCTTableController.prototype.sortComparator = function (a, b) {
var result = 0,
sortDirectionMultiplier;
if (typeof a === "string" && typeof b === "string") {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (a < b) {
result = -1;
}
if (a > b) {
result = 1;
}
if (this.$scope.sortDirection === 'asc') {
sortDirectionMultiplier = 1;
} else if (this.$scope.sortDirection === 'desc') {
sortDirectionMultiplier = -1;
}
return result * sortDirectionMultiplier;
};
/**
* Returns a new array which is a result of applying the sort
* criteria defined in $scope.
*
* Does not modify the array that was passed in.
*/
MCTTableController.prototype.sortRows = function(rowsToSort) {
/**
* Compare two variables, returning a number that represents
* which is larger. Similar to the default array sort
* comparator, but does not coerce all values to string before
* conversion. Strings are lowercased before comparison.
*/
function genericComparator(a, b) {
if (typeof a === "string" && typeof b === "string") {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
MCTTableController.prototype.sortRows = function (rowsToSort) {
var self = this,
sortKey = this.$scope.sortColumn;
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
return rowsToSort;
}
var sortKey = this.$scope.sortColumn,
sortDirectionMultiplier;
if (this.$scope.sortDirection === 'asc') {
sortDirectionMultiplier = 1;
} else if (this.$scope.sortDirection === 'desc') {
sortDirectionMultiplier = -1;
}
return rowsToSort.slice(0).sort(function(a, b) {
return rowsToSort.sort(function (a, b) {
//If the values to compare can be compared as
// numbers, do so. String comparison of number
// values can cause inconsistencies
var valA = isNaN(a[sortKey].text) ? a[sortKey].text : parseFloat(a[sortKey].text),
valB = isNaN(b[sortKey].text) ? b[sortKey].text : parseFloat(b[sortKey].text);
var valA = isNaN(a[sortKey].text) ? a[sortKey].text :
parseFloat(a[sortKey].text),
valB = isNaN(b[sortKey].text) ? b[sortKey].text :
parseFloat(b[sortKey].text);
return genericComparator(valA, valB) *
sortDirectionMultiplier;
return self.sortComparator(valA, valB);
});
};
@@ -238,7 +400,7 @@ define(
* pre-calculate optimal column sizes without having to render
* every row.
*/
MCTTableController.prototype.findLargestRow = function(rows) {
MCTTableController.prototype.findLargestRow = function (rows) {
var largestRow = rows.reduce(function (largestRow, row) {
Object.keys(row).forEach(function (key) {
var currentColumn = row[key].text,
@@ -259,10 +421,11 @@ define(
return largestRow;
}, JSON.parse(JSON.stringify(rows[0] || {})));
largestRow = JSON.parse(JSON.stringify(largestRow));
// Pad with characters to accomodate variable-width fonts,
// and remove characters that would allow word-wrapping.
largestRow = JSON.parse(JSON.stringify(largestRow));
Object.keys(largestRow).forEach(function(key) {
Object.keys(largestRow).forEach(function (key) {
var padCharacters,
i;
@@ -277,8 +440,15 @@ define(
return largestRow;
};
/**
* Calculates the widest row in the table, pads that row, and adds
* it to the table. Allows the table to size itself, then uses this
* as basis for column dimensions.
* @private
*/
MCTTableController.prototype.resize = function (){
var largestRow = this.findLargestRow(this.$scope.displayRows);
var largestRow = this.findLargestRow(this.$scope.displayRows),
self = this;
this.$scope.visibleRows = [
{
rowIndex: 0,
@@ -287,7 +457,28 @@ define(
}
];
this.$timeout(this.setElementSizes.bind(this));
//Wait a timeout to allow digest of previous change to visible
// rows to happen.
this.$timeout(function () {
//Remove temporary padding row used for setting column widths
self.$scope.visibleRows = [];
self.setElementSizes();
});
};
/**
* @priate
*/
MCTTableController.prototype.filterAndSort = function (rows) {
var displayRows = rows;
if (this.$scope.enableFilter) {
displayRows = this.filterRows(displayRows);
}
if (this.$scope.enableSort) {
displayRows = this.sortRows(displayRows.slice(0));
}
this.$scope.displayRows = displayRows;
};
/**
@@ -295,29 +486,27 @@ define(
* will be sorted before display.
*/
MCTTableController.prototype.updateRows = function (newRows) {
var displayRows = newRows;
//Reset visible rows because new row data available.
this.$scope.visibleRows = [];
this.$scope.overrideRowPositioning = false;
//Nothing to show because no columns visible
if (!this.$scope.displayHeaders) {
return;
}
if (this.$scope.enableFilter) {
displayRows = this.filterRows(displayRows);
}
if (this.$scope.enableSort) {
displayRows = this.sortRows(displayRows);
}
this.$scope.displayRows = displayRows;
this.filterAndSort(newRows || []);
this.resize();
};
/**
* Filter rows.
* Applies user defined filters to rows. These filters are based on
* the text entered in the search areas in each column.
* @param rowsToFilter {Object[]} The rows to apply filters to
* @returns {Object[]} A filtered copy of the supplied rows
*/
MCTTableController.prototype.filterRows = function(rowsToFilter) {
MCTTableController.prototype.filterRows = function (rowsToFilter) {
var filters = {},
self = this;
@@ -325,7 +514,7 @@ define(
* Returns true if row matches all filters.
*/
function matchRow(filters, row) {
return Object.keys(filters).every(function(key) {
return Object.keys(filters).every(function (key) {
if (!row[key]) {
return false;
}
@@ -338,7 +527,7 @@ define(
return rowsToFilter;
}
Object.keys(this.$scope.filters).forEach(function(key) {
Object.keys(this.$scope.filters).forEach(function (key) {
if (!self.$scope.filters[key]) {
return;
}

View File

@@ -0,0 +1,123 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define(
[
'./TelemetryTableController'
],
function (TableController) {
"use strict";
/**
* Extends TelemetryTableController and adds real-time streaming
* support.
* @memberof platform/features/table
* @param $scope
* @param telemetryHandler
* @param telemetryFormatter
* @constructor
*/
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
$scope.autoScroll = false;
this.maxRows = 100000;
/*
* Determine if auto-scroll should be enabled. Is enabled
* automatically when telemetry type is string
*/
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
$scope.$watch('domainObject', function (domainObject) {
//When a domain object becomes available, check whether the
// view should auto-scroll to the bottom.
if (domainObject && hasStringTelemetry(domainObject)){
$scope.autoScroll = true;
}
});
}
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
/**
Override the subscribe function defined on the parent controller in
order to handle realtime telemetry instead of historical.
*/
RTTelemetryTableController.prototype.subscribe = function () {
var self = this;
self.$scope.rows = undefined;
(this.subscriptions || []).forEach(function (unsubscribe){
unsubscribe();
});
if (this.handle) {
this.handle.unsubscribe();
}
function updateData(){
var datum,
row;
self.handle.getTelemetryObjects().forEach(function (telemetryObject){
datum = self.handle.getDatum(telemetryObject);
if (datum) {
row = self.table.getRowValues(telemetryObject, datum);
if (!self.$scope.rows){
self.$scope.rows = [row];
self.$scope.$digest();
} else {
self.$scope.rows.push(row);
if (self.$scope.rows.length > self.maxRows) {
self.$scope.$broadcast('remove:row', 0);
self.$scope.rows.shift();
}
self.$scope.$broadcast('add:row',
self.$scope.rows.length - 1);
}
}
});
}
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject,
updateData,
true // Lossless
);
this.setup();
};
return RTTelemetryTableController;
}
);

View File

@@ -58,9 +58,9 @@ define(
self.populateForm(model);
});
$scope.$watchCollection('configuration.table.columns', function(columns){
$scope.$watchCollection('configuration.table.columns', function (columns){
if (columns){
self.domainObject.useCapability('mutation', function(model) {
self.domainObject.useCapability('mutation', function (model) {
model.configuration.table.columns = columns;
});
self.domainObject.getCapability('persistence').persist();

View File

@@ -54,10 +54,11 @@ define(
this.handle = undefined;
//this.pending = false;
this.telemetryHandler = telemetryHandler;
this.table = new TableConfiguration($scope.domainObject, telemetryFormatter);
this.table = new TableConfiguration($scope.domainObject,
telemetryFormatter);
this.changeListeners = [];
$scope.rows = [];
$scope.rows = undefined;
// Subscribe to telemetry when a domain object becomes available
this.$scope.$watch('domainObject', function(domainObject){
@@ -72,21 +73,24 @@ define(
this.$scope.$on("$destroy", this.destroy.bind(this));
}
TelemetryTableController.prototype.registerChangeListeners = function() {
//Defer registration of change listeners until domain object is
// available in order to avoid race conditions
/**
* Defer registration of change listeners until domain object is
* available in order to avoid race conditions
* @private
*/
TelemetryTableController.prototype.registerChangeListeners = function () {
this.changeListeners.forEach(function (listener) {
return listener && listener();
});
this.changeListeners = [];
// When composition changes, re-subscribe to the various
// telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection('domainObject.getModel().composition', this.subscribe.bind(this)));
this.changeListeners.push(this.$scope.$watchCollection(
'domainObject.getModel().composition', this.subscribe.bind(this)));
//Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.subscribe.bind(this)));
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
this.subscribe.bind(this)));
};
/**
@@ -100,16 +104,17 @@ define(
};
/**
Create a new subscription. This is called when
Create a new subscription. This can be overridden by children to
change default behaviour (which is to retrieve historical telemetry
only).
*/
TelemetryTableController.prototype.subscribe = function() {
TelemetryTableController.prototype.subscribe = function () {
var self = this;
if (this.handle) {
this.handle.unsubscribe();
}
this.$scope.rows = [];
//Noop because not supporting realtime data right now
function noop(){
}
@@ -120,31 +125,43 @@ define(
true // Lossless
);
this.handle.request({}, this.addHistoricalData.bind(this));
this.handle.request({}).then(this.addHistoricalData.bind(this));
this.setup();
};
/**
* Add any historical data available
* Populates historical data on scope when it becomes available
* @private
*/
TelemetryTableController.prototype.addHistoricalData = function(domainObject, series) {
var i;
for (i=0; i < series.getPointCount(); i++) {
this.updateRows(domainObject, this.handle.makeDatum(domainObject, series, i));
}
TelemetryTableController.prototype.addHistoricalData = function () {
var rowData = [],
self = this;
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
var series = self.handle.getSeries(telemetryObject) || {},
pointCount = series.getPointCount ? series.getPointCount() : 0,
i = 0;
for (; i < pointCount; i++) {
rowData.push(self.table.getRowValues(telemetryObject,
self.handle.makeDatum(telemetryObject, series, i)));
}
});
this.$scope.rows = rowData;
};
/**
* Setup table columns based on domain object metadata
*/
TelemetryTableController.prototype.setup = function() {
TelemetryTableController.prototype.setup = function () {
var handle = this.handle,
table = this.table,
self = this;
if (handle) {
handle.promiseTelemetryObjects().then(function (objects) {
handle.promiseTelemetryObjects().then(function () {
table.buildColumns(handle.getMetadata());
self.filterColumns();
@@ -160,7 +177,7 @@ define(
};
/**
* Add data to rows
* @private
* @param object The object for which data is available (table may
* be composed of multiple objects)
* @param datum The data received from the telemetry source
@@ -172,6 +189,7 @@ define(
/**
* When column configuration changes, update the visible headers
* accordingly.
* @private
*/
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
if (!columnConfig){
@@ -179,7 +197,7 @@ define(
this.table.saveColumnConfiguration(columnConfig);
}
//Populate headers with visible columns (determined by configuration)
this.$scope.headers = Object.keys(columnConfig).filter(function(column) {
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
return columnConfig[column];
});
};

View File

@@ -1,20 +1,30 @@
/*global define*/
define(
["../controllers/MCTTableController"],
function (MCTTableController) {
[
"../controllers/MCTTableController",
"text!../../res/templates/mct-table.html"
],
function (MCTTableController, TableTemplate) {
"use strict";
/**
* Defines a generic 'Table' component. The table can be populated
* en-masse by setting the rows attribute, or rows can be added as
* needed via a broadcast 'addRow' event.
* @constructor
*/
function MCTTable($timeout) {
return {
restrict: "E",
templateUrl: "platform/features/table/res/templates/mct-data-table.html",
template: TableTemplate,
controller: ['$scope', '$timeout', '$element', MCTTableController],
scope: {
headers: "=",
rows: "=",
enableFilter: "=?",
enableSort: "=?"
enableSort: "=?",
autoScroll: "=?"
},
};
}

View File

@@ -120,13 +120,13 @@ define(
});
it("populates the columns attribute", function() {
expect(table.columns.length).toBe(4);
expect(table.columns.length).toBe(5);
});
it("Build columns populates columns with domains to the left", function() {
expect(table.columns[0] instanceof DomainColumn).toBeTruthy();
expect(table.columns[1] instanceof DomainColumn).toBeTruthy();
expect(table.columns[2] instanceof DomainColumn).toBeFalsy();
expect(table.columns[2] instanceof DomainColumn).toBeTruthy();
expect(table.columns[3] instanceof DomainColumn).toBeFalsy();
});
it("Produces headers for each column based on title", function() {
@@ -135,7 +135,7 @@ define(
spyOn(firstColumn, 'getTitle');
headers = table.getHeaders();
expect(headers.length).toBe(4);
expect(headers.length).toBe(5);
expect(firstColumn.getTitle).toHaveBeenCalled();
});

View File

@@ -48,6 +48,8 @@ define(
watches = {};
mockScope = jasmine.createSpyObj('scope', [
'$watch',
'$on',
'$watchCollection'
]);
mockScope.$watchCollection.andCallFake(function(event, callback) {
@@ -62,14 +64,15 @@ define(
mockScope.displayHeaders = true;
mockTimeout = jasmine.createSpy('$timeout');
mockTimeout.andReturn(promise(undefined));
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
});
it('Reacts to changes to filters, headers, and rows', function() {
expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('headers', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('rows', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('headers', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
});
describe('rows', function() {
@@ -92,6 +95,7 @@ define(
'col3': {'text': 'row3 col3'}
}
];
mockScope.rows = testRows;
});
it('Filters results based on filter input', function() {
@@ -116,6 +120,31 @@ define(
expect(mockScope.displayRows).toEqual(testRows);
});
it('Supports adding rows individually', function() {
var addRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-2].args[1],
row4 = {
'col1': {'text': 'row3 col1'},
'col2': {'text': 'ghi'},
'col3': {'text': 'row3 col3'}
};
controller.updateRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
testRows.push(row4);
addRowFunc(undefined, 3);
expect(mockScope.displayRows.length).toBe(4);
});
it('Supports removing rows individually', function() {
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
controller.updateRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
spyOn(controller, 'setVisibleRows');
//controller.setVisibleRows.andReturn(undefined);
removeRowFunc(undefined, 2);
expect(mockScope.displayRows.length).toBe(2);
expect(controller.setVisibleRows).toHaveBeenCalled();
});
describe('sorting', function() {
var sortedRows;
@@ -149,7 +178,98 @@ define(
expect(sortedRows[1].col2.text).toEqual('def');
expect(sortedRows[2].col2.text).toEqual('abc');
});
describe('Adding new rows', function() {
var row4,
row5,
row6;
beforeEach(function() {
row4 = {
'col1': {'text': 'row5 col1'},
'col2': {'text': 'xyz'},
'col3': {'text': 'row5 col3'}
};
row5 = {
'col1': {'text': 'row6 col1'},
'col2': {'text': 'aaa'},
'col3': {'text': 'row6 col3'}
};
row6 = {
'col1': {'text': 'row6 col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6 col3'}
};
});
it('Adds new rows at the correct sort position when' +
' sorted ', function() {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'desc';
mockScope.displayRows = controller.sortRows(testRows.slice(0));
mockScope.rows.push(row4);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
//Add a duplicate row
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
});
it('Adds new rows at the correct sort position when' +
' sorted and filtered', function() {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'desc';
mockScope.filters = {'col2': 'a'};//Include only
// rows with 'a'
mockScope.displayRows = controller.sortRows(testRows.slice(0));
mockScope.displayRows = controller.filterRows(testRows);
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows.length).toBe(2);
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows.length).toBe(2);
//Row was not added because does not match filter
});
it('Adds new rows at the correct sort position when' +
' not sorted ', function() {
mockScope.sortColumn = undefined;
mockScope.sortDirection = undefined;
mockScope.filters = {};
mockScope.displayRows = testRows.slice(0);
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
});
});
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More