Compare commits
	
		
			118 Commits
		
	
	
		
			metrics-73
			...
			pete-api-p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0dff804bd3 | ||
|   | ad4c456ca2 | ||
|   | b0476edb8c | ||
|   | 8f6a287fb8 | ||
|   | 07d554d114 | ||
|   | 24d2cecdcd | ||
|   | 8efa9c6aac | ||
|   | 22e98274ca | ||
|   | 471b57f47c | ||
|   | 9c775c6715 | ||
|   | 1f09b7b0ac | ||
|   | b0cf9bbd29 | ||
|   | 4a1ca25e17 | ||
|   | cc97e567b6 | ||
|   | 53f03cddb7 | ||
|   | da1e6750a0 | ||
|   | 3258342783 | ||
|   | 73b7365ae2 | ||
|   | 32a42bd679 | ||
|   | 324c2cac03 | ||
|   | 42ac657105 | ||
|   | d30532a8bc | ||
|   | 936079da92 | ||
|   | 4e89ffbe07 | ||
|   | f9ed73c55e | ||
|   | bf3b964ad2 | ||
|   | 55ae755522 | ||
|   | d522570c0b | ||
|   | d72aaf54ca | ||
|   | 8f94751a35 | ||
|   | dfb0a570a3 | ||
|   | 5d06979866 | ||
|   | 8b51ae32d2 | ||
|   | ecb37c54be | ||
|   | 43492d31ba | ||
|   | 0e1df444df | ||
|   | f2c040367b | ||
|   | 0ff360ced3 | ||
|   | fc08df4f6f | ||
|   | c5de90a951 | ||
|   | ea0b86fe72 | ||
|   | d789e91c18 | ||
|   | f7ba24c0b6 | ||
|   | 02ec6db104 | ||
|   | a3a9393d1b | ||
|   | 217e697079 | ||
|   | 82b6166408 | ||
|   | 03ab3bddc4 | ||
|   | abd5913f02 | ||
|   | 107ecfe687 | ||
|   | 4a8222a152 | ||
|   | 7ee8d0a3f7 | ||
|   | dc2b3e85cc | ||
|   | d4b15525ca | ||
|   | cbd9509260 | ||
|   | c5ab6c6c97 | ||
|   | cd84a017b8 | ||
|   | d39dea971f | ||
|   | 4f293f22a6 | ||
|   | cebf9f73da | ||
|   | 37e6b5a352 | ||
|   | ece8f7fded | ||
|   | 3b0a3733b4 | ||
|   | baab0be5af | ||
|   | d14a2a6366 | ||
|   | 377d533ec7 | ||
|   | a4c5854561 | ||
|   | 5296255fa6 | ||
|   | 5d084c2618 | ||
|   | 5208631528 | ||
|   | a56edb9ff4 | ||
|   | 7da1a218ba | ||
|   | a4eb9d6a94 | ||
|   | 20f1dcef45 | ||
|   | 4983d35ca6 | ||
|   | 7171fd01e3 | ||
|   | 0957fbc6f9 | ||
|   | bdbb045005 | ||
|   | 318df9878d | ||
|   | 406a31889e | ||
|   | 9e4e3e9c43 | ||
|   | bd7cb98a4c | ||
|   | 0d419fa5fd | ||
|   | 5ee5e7fea1 | ||
|   | 66b1a92554 | ||
|   | c23c2b84bf | ||
|   | 0c2285719e | ||
|   | fd92c5f970 | ||
|   | 938c266b4e | ||
|   | 9e6e33983b | ||
|   | 40895ec1b9 | ||
|   | 43db52fd70 | ||
|   | 75d6803c9f | ||
|   | ed679756b3 | ||
|   | dd66cb60d8 | ||
|   | d5283d57e4 | ||
|   | 7c140c06dc | ||
|   | f9ff9921a9 | ||
|   | cdac0ad67f | ||
|   | 5d771edcf7 | ||
|   | c4f1c4ad1f | ||
|   | 14b8e02f27 | ||
|   | b383921f2a | ||
|   | a509dfb840 | ||
|   | b7a44a7557 | ||
|   | 2d305415b3 | ||
|   | 18167eddf8 | ||
|   | f302bd6cb2 | ||
|   | 071a908c10 | ||
|   | 273cf1c14f | ||
|   | 303e870b0d | ||
|   | b42ccebd5a | ||
|   | a444fc01ad | ||
|   | a126e43286 | ||
|   | 32fc50bbd3 | ||
|   | 4adb075a2b | ||
|   | d8b1e570d9 | ||
|   | 5033e2cdbb | 
							
								
								
									
										62
									
								
								LICENSES.md
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								LICENSES.md
									
									
									
									
									
								
							| @@ -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) 2009–2015 | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -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" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										180
									
								
								docs/src/design/proposals/PublicAPIProposal.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								docs/src/design/proposals/PublicAPIProposal.md
									
									
									
									
									
										Normal 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` | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -57,8 +57,17 @@ define([ | ||||
|                     }, | ||||
|                     "telemetry": { | ||||
|                         "source": "eventGenerator", | ||||
|                         "domains": [ | ||||
|                             { | ||||
|                                 "key": "time", | ||||
|                                 "name": "Time", | ||||
|                                 "format": "utc" | ||||
|                             } | ||||
|                         ], | ||||
|                         "ranges": [ | ||||
|                             { | ||||
|                                 "key": "message", | ||||
|                                 "name": "Message", | ||||
|                                 "format": "string" | ||||
|                             } | ||||
|                         ] | ||||
|   | ||||
| @@ -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(); | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -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": [ | ||||
|   | ||||
| @@ -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(); | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -60,7 +60,8 @@ var gulp = require('gulp'), | ||||
|             singleRun: true | ||||
|         }, | ||||
|         sass: { | ||||
|             includePaths: bourbon.includePaths | ||||
|             includePaths: bourbon.includePaths, | ||||
|             sourceComments: true | ||||
|         }, | ||||
|         replace: { | ||||
|             variables: { | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
							
								
								
									
										8
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.js
									
									
									
									
									
								
							| @@ -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', | ||||
|   | ||||
| @@ -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": { | ||||
|   | ||||
| @@ -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 }"> | ||||
|   | ||||
| @@ -29,5 +29,5 @@ | ||||
|  --> | ||||
| <mct-representation mct-object="domainObject" | ||||
|                     key="viewObjectTemplate || 'browse-object'" | ||||
|                     class="abs holder holder-object"> | ||||
|                     class="abs holder"> | ||||
| </mct-representation> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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) 2009–2015", | ||||
|                     "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" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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%; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -45,7 +45,6 @@ | ||||
|  | ||||
| /********************************* FORMS */ | ||||
| @import "forms/elems"; | ||||
| @import "forms/selects"; | ||||
| @import "forms/channel-selector"; | ||||
| @import "forms/datetime"; | ||||
| @import "forms/validation"; | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -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%; | ||||
| } | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								platform/commonUI/general/res/templates/tree/node.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								platform/commonUI/general/res/templates/tree/node.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <span class="tree-item menus-to-left"> | ||||
| </span> | ||||
| <span class="tree-item-subtree"> | ||||
| </span> | ||||
							
								
								
									
										2
									
								
								platform/commonUI/general/res/templates/tree/toggle.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								platform/commonUI/general/res/templates/tree/toggle.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| <span class='ui-symbol view-control flex-elem'> | ||||
| </span> | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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; | ||||
| }); | ||||
| @@ -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; | ||||
| }); | ||||
							
								
								
									
										90
									
								
								platform/commonUI/general/src/ui/TreeLabelView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								platform/commonUI/general/src/ui/TreeLabelView.js
									
									
									
									
									
										Normal 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; | ||||
| }); | ||||
							
								
								
									
										157
									
								
								platform/commonUI/general/src/ui/TreeNodeView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								platform/commonUI/general/src/ui/TreeNodeView.js
									
									
									
									
									
										Normal 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; | ||||
| }); | ||||
							
								
								
									
										141
									
								
								platform/commonUI/general/src/ui/TreeView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								platform/commonUI/general/src/ui/TreeView.js
									
									
									
									
									
										Normal 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; | ||||
| }); | ||||
							
								
								
									
										95
									
								
								platform/commonUI/general/test/directives/MCTTreeSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								platform/commonUI/general/test/directives/MCTTreeSpec.js
									
									
									
									
									
										Normal 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) | ||||
|                 ); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										313
									
								
								platform/commonUI/general/test/ui/TreeViewSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								platform/commonUI/general/test/ui/TreeViewSpec.js
									
									
									
									
									
										Normal 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(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
| @@ -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... | ||||
|   | ||||
| @@ -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(' ')); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -248,7 +248,7 @@ define([ | ||||
|                             "property": "name", | ||||
|                             "pattern": "\\S+", | ||||
|                             "required": true, | ||||
|                             "cssclass": "l-med" | ||||
|                             "cssclass": "l-input-lg" | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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 | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -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> | ||||
| @@ -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> | ||||
|  | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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 | ||||
|          */ | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|  | ||||
| 	} | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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"]); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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]; | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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", | ||||
|   | ||||
| @@ -49,7 +49,8 @@ define([ | ||||
|                             "name": "URL", | ||||
|                             "control": "textfield", | ||||
|                             "pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$", | ||||
|                             "required": true | ||||
|                             "required": true, | ||||
|                             "cssclass": "l-input-lg" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|   | ||||
| @@ -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> | ||||
| @@ -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' | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|   | ||||
| @@ -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 | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -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> | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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) | ||||
|                 ); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,2 +0,0 @@ | ||||
| This is a placeholder implementation of a Scrolling List view | ||||
| which is compatible with realtime-only data. | ||||
| @@ -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" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -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> | ||||
|  | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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> | ||||
|  | ||||
|  | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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; | ||||
|  | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -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(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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"]); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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]; | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -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": [ | ||||
|   | ||||
| @@ -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> | ||||
							
								
								
									
										9
									
								
								platform/features/table/res/templates/rt-table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								platform/features/table/res/templates/rt-table.html
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|                 } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| ); | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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]; | ||||
|             }); | ||||
|         }; | ||||
|   | ||||
| @@ -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: "=?" | ||||
|                 }, | ||||
|             }; | ||||
|         } | ||||
|   | ||||
| @@ -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(); | ||||
|                 }); | ||||
|  | ||||
|   | ||||
| @@ -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
		Reference in New Issue
	
	Block a user