Compare commits
	
		
			146 Commits
		
	
	
		
			testathon-
			...
			summary-wi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					00f4dcd558 | ||
| 
						 | 
					0bc05e1b9e | ||
| 
						 | 
					c931c54332 | ||
| 
						 | 
					4ec6f797e7 | ||
| 
						 | 
					c40cc3e060 | ||
| 
						 | 
					7ee4a508f6 | ||
| 
						 | 
					28da6a5106 | ||
| 
						 | 
					a4f203332a | ||
| 
						 | 
					b9a678bf9a | ||
| 
						 | 
					971eda4d88 | ||
| 
						 | 
					68e6e3c121 | ||
| 
						 | 
					7e7f39db2d | ||
| 
						 | 
					b6e0fca828 | ||
| 
						 | 
					ffc5896e5a | ||
| 
						 | 
					fd6ebd152f | ||
| 
						 | 
					7a5c1c0e1f | ||
| 
						 | 
					2f7e1e3f1a | ||
| 
						 | 
					d73746b51b | ||
| 
						 | 
					2df54af019 | ||
| 
						 | 
					586269f761 | ||
| 
						 | 
					e536ab34d7 | ||
| 
						 | 
					e15002dd72 | ||
| 
						 | 
					453cf3ad6a | ||
| 
						 | 
					5c46e48bde | ||
| 
						 | 
					868ea9362f | ||
| 
						 | 
					d69106ff2c | ||
| 
						 | 
					1658b17c56 | ||
| 
						 | 
					39cf0528ca | ||
| 
						 | 
					3d12f7312b | ||
| 
						 | 
					22481fdc31 | ||
| 
						 | 
					4a9d27dc79 | ||
| 
						 | 
					a5a9fefd40 | ||
| 
						 | 
					dae4074934 | ||
| 
						 | 
					a540a3573f | ||
| 
						 | 
					4e7fe9082c | ||
| 
						 | 
					568141bf81 | ||
| 
						 | 
					ac3ea43fe5 | ||
| 
						 | 
					e922e8d504 | ||
| 
						 | 
					650a877d2a | ||
| 
						 | 
					1202109c59 | ||
| 
						 | 
					429d7bbd57 | ||
| 
						 | 
					af749fe71b | ||
| 
						 | 
					cf64c512ce | ||
| 
						 | 
					c5bd3da44a | ||
| 
						 | 
					ff3e49e926 | ||
| 
						 | 
					e244a3e431 | ||
| 
						 | 
					c5d9fb6fd9 | ||
| 
						 | 
					4c276ab422 | ||
| 
						 | 
					bf321abae4 | ||
| 
						 | 
					7336968ef9 | ||
| 
						 | 
					d60956948b | ||
| 
						 | 
					23d5c2e1ee | ||
| 
						 | 
					2632b8891a | ||
| 
						 | 
					fff4cd9d51 | ||
| 
						 | 
					be0291cf70 | ||
| 
						 | 
					b3a6d7271d | ||
| 
						 | 
					ebeed2f236 | ||
| 
						 | 
					337c26c019 | ||
| 
						 | 
					730f363f94 | ||
| 
						 | 
					ae30e6110b | ||
| 
						 | 
					6e4bf3e45b | ||
| 
						 | 
					5465ca92f9 | ||
| 
						 | 
					e55ea41b0a | ||
| 
						 | 
					8cfb3cc689 | ||
| 
						 | 
					2d728a1362 | ||
| 
						 | 
					99333988df | ||
| 
						 | 
					de783d4286 | ||
| 
						 | 
					f6c1488ccd | ||
| 
						 | 
					26be1ecf37 | ||
| 
						 | 
					38f0f072bb | ||
| 
						 | 
					e5e969665f | ||
| 
						 | 
					ffbb662c99 | ||
| 
						 | 
					bd7b23f896 | ||
| 
						 | 
					c238def902 | ||
| 
						 | 
					2d430ece7f | ||
| 
						 | 
					c92644a661 | ||
| 
						 | 
					41ce3c04f7 | ||
| 
						 | 
					fcf77f359f | ||
| 
						 | 
					40a2737915 | ||
| 
						 | 
					216489d67f | ||
| 
						 | 
					418a393b26 | ||
| 
						 | 
					1f3d744494 | ||
| 
						 | 
					ff3f2dccba | ||
| 
						 | 
					e69973bd29 | ||
| 
						 | 
					05b352cc36 | ||
| 
						 | 
					9735548999 | ||
| 
						 | 
					f9529b1362 | ||
| 
						 | 
					c598cec702 | ||
| 
						 | 
					e9ea1c4a0f | ||
| 
						 | 
					e9238ff282 | ||
| 
						 | 
					4cebd72cba | ||
| 
						 | 
					f8a44d6e71 | ||
| 
						 | 
					d0745b300b | ||
| 
						 | 
					2a4e0a3081 | ||
| 
						 | 
					1ff19f9574 | ||
| 
						 | 
					7ef84cb50d | ||
| 
						 | 
					cd05c70d64 | ||
| 
						 | 
					568473b82f | ||
| 
						 | 
					c61b074755 | ||
| 
						 | 
					8ed66ab4ab | ||
| 
						 | 
					b2502dd998 | ||
| 
						 | 
					856eedbf9d | ||
| 
						 | 
					0c0ca6e6af | ||
| 
						 | 
					498b797e49 | ||
| 
						 | 
					02c33388ba | ||
| 
						 | 
					8a8e3cc055 | ||
| 
						 | 
					36d60b16e9 | ||
| 
						 | 
					de3114568b | ||
| 
						 | 
					eb5835faeb | ||
| 
						 | 
					ff1ddb0b79 | ||
| 
						 | 
					15b127bb2e | ||
| 
						 | 
					e4ed881f6d | ||
| 
						 | 
					7b62cf130c | ||
| 
						 | 
					72fd2e531c | ||
| 
						 | 
					4a5392ef78 | ||
| 
						 | 
					0150a708ca | ||
| 
						 | 
					eacc181d5e | ||
| 
						 | 
					405bb55881 | ||
| 
						 | 
					4a35508459 | ||
| 
						 | 
					98a9d71a2e | ||
| 
						 | 
					a1596d0b06 | ||
| 
						 | 
					4b3be4c483 | ||
| 
						 | 
					0fa8472db1 | ||
| 
						 | 
					e1e2dca1d8 | ||
| 
						 | 
					755c013ec8 | ||
| 
						 | 
					eab702b763 | ||
| 
						 | 
					d15446ac91 | ||
| 
						 | 
					500733afb2 | ||
| 
						 | 
					2aa04b0a56 | ||
| 
						 | 
					c051f342af | ||
| 
						 | 
					8aeb365f5f | ||
| 
						 | 
					827a28313d | ||
| 
						 | 
					c83de8aad2 | ||
| 
						 | 
					b55f43b8df | ||
| 
						 | 
					8466723a90 | ||
| 
						 | 
					a103b4dbff | ||
| 
						 | 
					826ac3a947 | ||
| 
						 | 
					597327f138 | ||
| 
						 | 
					bef79402ca | ||
| 
						 | 
					e68e0c381f | ||
| 
						 | 
					c73f7259c2 | ||
| 
						 | 
					4c9235ba10 | ||
| 
						 | 
					55e2a77df8 | ||
| 
						 | 
					cfbff02e7f | ||
| 
						 | 
					54980fb296 | ||
| 
						 | 
					ba98d9315c | 
@@ -59,7 +59,7 @@ define([
 | 
			
		||||
            if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
 | 
			
		||||
                workerRequest[prop] = domainObject.telemetry[prop];
 | 
			
		||||
            }
 | 
			
		||||
            if (request.hasOwnProperty(prop)) {
 | 
			
		||||
            if (request && request.hasOwnProperty(prop)) {
 | 
			
		||||
                workerRequest[prop] = request[prop];
 | 
			
		||||
            }
 | 
			
		||||
            if (!workerRequest[prop]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,7 @@
 | 
			
		||||
        <h2>Palettes</h2>
 | 
			
		||||
        <div class="cols cols1-1">
 | 
			
		||||
            <div class="col">
 | 
			
		||||
                <p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
 | 
			
		||||
                <p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
 | 
			
		||||
                <p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
 | 
			
		||||
@@ -129,9 +129,9 @@
 | 
			
		||||
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
 | 
			
		||||
    <span class="l-click-area" ng-click="toggle.toggle()"></span>
 | 
			
		||||
    <span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
 | 
			
		||||
    <div class="menu l-color-palette" ng-show="toggle.isActive()">
 | 
			
		||||
    <div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
 | 
			
		||||
        <div class="l-palette-row l-option-row">
 | 
			
		||||
            <div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
 | 
			
		||||
            <div class="l-palette-item s-palette-item no-selection"></div>
 | 
			
		||||
            <span class="l-palette-item-label">None</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="l-palette-row">
 | 
			
		||||
@@ -147,7 +147,7 @@
 | 
			
		||||
            <div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="l-palette-row">
 | 
			
		||||
            <div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
 | 
			
		||||
            <div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
 | 
			
		||||
            <div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
 | 
			
		||||
            <div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
 | 
			
		||||
            <div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,7 @@
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
 | 
			
		||||
    <title></title>
 | 
			
		||||
    <script src="bower_components/requirejs/require.js">
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src="bower_components/requirejs/require.js"> </script>
 | 
			
		||||
    <script>
 | 
			
		||||
        var THIRTY_MINUTES = 30 * 60 * 1000;
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +49,7 @@
 | 
			
		||||
                        name: "Fixed",
 | 
			
		||||
                        timeSystem: 'utc',
 | 
			
		||||
                        bounds: {
 | 
			
		||||
                            start: Date.now() - 30 * 60 * 1000,
 | 
			
		||||
                            start: Date.now() - THIRTY_MINUTES,
 | 
			
		||||
                            end: Date.now()
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
@@ -65,6 +64,7 @@
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }));
 | 
			
		||||
            openmct.install(openmct.plugins.SummaryWidget());
 | 
			
		||||
            openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
 | 
			
		||||
            openmct.time.timeSystem('utc');
 | 
			
		||||
            openmct.start();
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="abs top-bar">
 | 
			
		||||
    <div class="title">{{ngModel.title}}</div>
 | 
			
		||||
    <div class="dialog-title">{{ngModel.title}}</div>
 | 
			
		||||
    <div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class='abs editor'>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
<div class="l-message"
 | 
			
		||||
     ng-class="'message-severity-' + ngModel.severity">
 | 
			
		||||
    <div class="ui-symbol type-icon message-type"></div>
 | 
			
		||||
    <div class="message-contents">
 | 
			
		||||
    <div class="w-message-contents">
 | 
			
		||||
        <div class="top-bar">
 | 
			
		||||
            <div class="title">{{ngModel.title}}</div>
 | 
			
		||||
            <div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
 | 
			
		||||
        <div class="message-body">
 | 
			
		||||
            <div class="message-action">
 | 
			
		||||
                {{ngModel.actionText}}
 | 
			
		||||
@@ -25,8 +24,6 @@
 | 
			
		||||
               ng-click="ngModel.primaryOption.callback()">
 | 
			
		||||
                {{ngModel.primaryOption.label}}
 | 
			
		||||
            </a>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
<mct-container key="overlay" class="t-message-list">
 | 
			
		||||
    <div class="message-contents">
 | 
			
		||||
        <div class="abs top-bar">
 | 
			
		||||
            <div class="title">{{ngModel.dialog.title}}</div>
 | 
			
		||||
<mct-container key="overlay">
 | 
			
		||||
    <div class="t-message-list">
 | 
			
		||||
        <div class="top-bar">
 | 
			
		||||
            <div class="dialog-title">{{ngModel.dialog.title}}</div>
 | 
			
		||||
            <div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
 | 
			
		||||
                                                                                                  ngModel.dialog.messages.length == 0">s</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="abs message-body">
 | 
			
		||||
        <div class="w-messages">
 | 
			
		||||
            <mct-include
 | 
			
		||||
                    ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
 | 
			
		||||
                    key="'message'" ng-model="msg.model"></mct-include>
 | 
			
		||||
                ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
 | 
			
		||||
                key="'message'" ng-model="msg.model"></mct-include>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="abs bottom-bar">
 | 
			
		||||
        <div class="bottom-bar">
 | 
			
		||||
            <a ng-repeat="dialogAction in ngModel.dialog.actions"
 | 
			
		||||
               class="s-button major"
 | 
			
		||||
               ng-click="dialogAction.action()">
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
-->
 | 
			
		||||
<mct-container key="overlay">
 | 
			
		||||
    <div class="abs top-bar">
 | 
			
		||||
        <div class="title">{{ngModel.dialog.title}}</div>
 | 
			
		||||
        <div class="dialog-title">{{ngModel.dialog.title}}</div>
 | 
			
		||||
        <div class="hint">{{ngModel.dialog.hint}}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class='abs editor'>
 | 
			
		||||
 
 | 
			
		||||
@@ -137,6 +137,11 @@
 | 
			
		||||
        min-height: 0;
 | 
			
		||||
        &.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
 | 
			
		||||
    }
 | 
			
		||||
    &.l-flex-accordion .flex-accordion-holder {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        //overflow: hidden !important;
 | 
			
		||||
    }
 | 
			
		||||
    .flex-container { @include flex-direction(column); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -180,6 +180,20 @@ a.disabled {
 | 
			
		||||
    @include ellipsize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-selection {
 | 
			
		||||
    // aka selection = "None". Used in palettes and their menu buttons.
 | 
			
		||||
    $c: red; $s: 48%; $e: 52%;
 | 
			
		||||
    @include background-image(linear-gradient(-45deg,
 | 
			
		||||
        transparent $s - 5%,
 | 
			
		||||
        $c $s,
 | 
			
		||||
        $c $e,
 | 
			
		||||
        transparent $e + 5%
 | 
			
		||||
    ));
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-size: contain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.scrolling,
 | 
			
		||||
.scroll {
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@
 | 
			
		||||
/********************************* CONTROLS */
 | 
			
		||||
@import "controls/breadcrumb";
 | 
			
		||||
@import "controls/buttons";
 | 
			
		||||
@import "controls/color-palette";
 | 
			
		||||
@import "controls/palette";
 | 
			
		||||
@import "controls/controls";
 | 
			
		||||
@import "controls/lists";
 | 
			
		||||
@import "controls/menus";
 | 
			
		||||
@@ -80,3 +80,4 @@
 | 
			
		||||
@import "autoflow";
 | 
			
		||||
@import "features/imagery";
 | 
			
		||||
@import "features/time-display";
 | 
			
		||||
@import "widgets";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										306
									
								
								platform/commonUI/general/res/sass/_widgets.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								platform/commonUI/general/res/sass/_widgets.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/************************************************************* WIDGET OBJECT */
 | 
			
		||||
.l-summary-widget {
 | 
			
		||||
    // Widget layout classes here
 | 
			
		||||
    @include ellipsize();
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    .widget-label:before {
 | 
			
		||||
        // Widget icon
 | 
			
		||||
        font-size: 0.9em;
 | 
			
		||||
        margin-right: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-summary-widget {
 | 
			
		||||
    // Widget style classes here
 | 
			
		||||
    @include boxShdw($shdwBtns);
 | 
			
		||||
    border-radius: $basicCr;
 | 
			
		||||
    border-style: solid;
 | 
			
		||||
    border-width: 1px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
    padding: $interiorMarginLg $interiorMarginLg * 2;
 | 
			
		||||
    &[href] {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-edit-holder {
 | 
			
		||||
    // Hide edit area when in browse mode
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-rule-header {
 | 
			
		||||
    @extend .l-flex-row;
 | 
			
		||||
    @include align-items(center);
 | 
			
		||||
    margin-bottom: $interiorMargin;
 | 
			
		||||
    > .flex-elem {
 | 
			
		||||
        &:not(:first-child) {
 | 
			
		||||
            margin-left: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-rules-wrapper,
 | 
			
		||||
.widget-rule-content,
 | 
			
		||||
.w-widget-test-data-content {
 | 
			
		||||
    @include trans-prop-nice($props: (height, min-height, opacity), $dur: 250ms);
 | 
			
		||||
    min-height: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-rules-wrapper {
 | 
			
		||||
    flex: 1 1 auto !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-rule-content.expanded {
 | 
			
		||||
    overflow: visible !important;
 | 
			
		||||
    min-height: 50px;
 | 
			
		||||
    height: auto;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    pointer-events: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-widget-test-data-content {
 | 
			
		||||
    .l-enable {
 | 
			
		||||
        padding: $interiorMargin 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .w-widget-test-data-items {
 | 
			
		||||
        max-height: 20vh;
 | 
			
		||||
        overflow-y: scroll !important;
 | 
			
		||||
        padding-right: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.l-widget-thumb-wrapper,
 | 
			
		||||
.l-compact-form label {
 | 
			
		||||
    $ruleLabelW: 40%;
 | 
			
		||||
    $ruleLabelMaxW: 150px;
 | 
			
		||||
    @include display(flex);
 | 
			
		||||
    max-width: $ruleLabelMaxW;
 | 
			
		||||
    width: $ruleLabelW;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.t-message-widget-no-data {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/********************************************************** EDITING A WIDGET */
 | 
			
		||||
.s-status-editing > mct-view > .w-summary-widget {
 | 
			
		||||
    // Classes for editor layout while editing a widget
 | 
			
		||||
    // This selector is ugly and brittle, but needed to prevent interface from showing when widget is in a layout
 | 
			
		||||
    // being edited.
 | 
			
		||||
    @include absPosDefault();
 | 
			
		||||
    @extend .l-flex-col;
 | 
			
		||||
 | 
			
		||||
    > .l-summary-widget {
 | 
			
		||||
        // Main view of the summary widget
 | 
			
		||||
        // Give some airspace and center the widget in the area
 | 
			
		||||
        margin: 30px auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .widget-edit-holder {
 | 
			
		||||
        display: flex; // Overrides `display: none` during Browse mode
 | 
			
		||||
        .flex-accordion-holder {
 | 
			
		||||
            // Needed because otherwise accordion elements "creep" when contents expand and contract
 | 
			
		||||
            display: block !important;
 | 
			
		||||
        }
 | 
			
		||||
        &.expanded-widget-test-data {
 | 
			
		||||
            .w-widget-test-data-content {
 | 
			
		||||
                min-height: 50px;
 | 
			
		||||
                height: auto;
 | 
			
		||||
                opacity: 1;
 | 
			
		||||
                pointer-events: inherit;
 | 
			
		||||
            }
 | 
			
		||||
            &:not(.expanded-widget-rules) {
 | 
			
		||||
                // Test data is expanded and rules are collapsed
 | 
			
		||||
                // Make text data take up all the vertical space
 | 
			
		||||
                .flex-accordion-holder { display: flex; }
 | 
			
		||||
                .widget-test-data {
 | 
			
		||||
                    flex-grow: 999999;
 | 
			
		||||
                }
 | 
			
		||||
                .w-widget-test-data-items {
 | 
			
		||||
                    max-height: inherit;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        &.expanded-widget-rules {
 | 
			
		||||
            .widget-rules-wrapper {
 | 
			
		||||
                min-height: 50px;
 | 
			
		||||
                height: auto;
 | 
			
		||||
                opacity: 1;
 | 
			
		||||
                pointer-events: inherit;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.s-status-no-data {
 | 
			
		||||
        .widget-edit-holder {
 | 
			
		||||
            opacity: 0.3;
 | 
			
		||||
            pointer-events: none;
 | 
			
		||||
        }
 | 
			
		||||
        .t-message-widget-no-data {
 | 
			
		||||
            display: flex;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .l-compact-form {
 | 
			
		||||
        // Overrides on .l-compact-form
 | 
			
		||||
        ul {
 | 
			
		||||
            &:last-child { margin: 0; }
 | 
			
		||||
            li {
 | 
			
		||||
                @include align-items(flex-start);
 | 
			
		||||
                @include flex-wrap(nowrap);
 | 
			
		||||
                line-height: 230%; // Provide enough space when controls wrap
 | 
			
		||||
                padding: 2px 0;
 | 
			
		||||
                &:not(.widget-rule-header) {
 | 
			
		||||
                    &:not(.connects-to-previous) {
 | 
			
		||||
                        border-top: 1px solid $colorFormLines;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                &.connects-to-previous {
 | 
			
		||||
                    padding: $interiorMargin 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                > label {
 | 
			
		||||
                    display: block; // Needed to align text to right
 | 
			
		||||
                    text-align: right;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.s-widget-test-data-item {
 | 
			
		||||
            // Single line of ul li label span, etc.
 | 
			
		||||
            ul {
 | 
			
		||||
                li {
 | 
			
		||||
                    border: none !important;
 | 
			
		||||
                    > label {
 | 
			
		||||
                        display: inline-block;
 | 
			
		||||
                        width: auto;
 | 
			
		||||
                        text-align: left;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-edit-holder {
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-rules-wrapper {
 | 
			
		||||
    // Wrapper area that holds n rules
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
    padding-right: $interiorMargin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.l-widget-rule,
 | 
			
		||||
.l-widget-test-data-item {
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    margin-bottom: $interiorMarginSm;
 | 
			
		||||
    padding: $interiorMargin $interiorMarginLg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.l-widget-thumb-wrapper {
 | 
			
		||||
    @extend .l-flex-row;
 | 
			
		||||
    @include align-items(center);
 | 
			
		||||
    > span { display: block; }
 | 
			
		||||
    .grippy-holder,
 | 
			
		||||
    .view-control {
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        width: 1em;
 | 
			
		||||
        height: 1em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .widget-thumb {
 | 
			
		||||
        @include flex(1 1 auto);
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rule-title {
 | 
			
		||||
    @include flex(0 1 auto);
 | 
			
		||||
    color: pullForward($colorBodyFg, 50%);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rule-description {
 | 
			
		||||
    @include flex(1 1 auto);
 | 
			
		||||
    @include ellipsize();
 | 
			
		||||
    color: pushBack($colorBodyFg, 20%);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-widget-rule,
 | 
			
		||||
.s-widget-test-data-item {
 | 
			
		||||
    background-color: rgba($colorBodyFg, 0.1);
 | 
			
		||||
    border-radius: $basicCr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.widget-thumb {
 | 
			
		||||
    @include ellipsize();
 | 
			
		||||
    @extend .s-summary-widget;
 | 
			
		||||
    @extend .l-summary-widget;
 | 
			
		||||
    padding: $interiorMarginSm $interiorMargin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Hide and show elements in the rule-header on hover
 | 
			
		||||
.l-widget-rule,
 | 
			
		||||
.l-widget-test-data-item {
 | 
			
		||||
    .grippy,
 | 
			
		||||
    .l-rule-action-buttons-wrapper,
 | 
			
		||||
    .l-condition-action-buttons-wrapper,
 | 
			
		||||
    .l-widget-test-data-item-action-buttons-wrapper {
 | 
			
		||||
        @include trans-prop-nice($props: opacity, $dur: 500ms);
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
    }
 | 
			
		||||
    &:hover {
 | 
			
		||||
        .grippy,
 | 
			
		||||
        .l-rule-action-buttons-wrapper,
 | 
			
		||||
        .l-widget-test-data-item-action-buttons-wrapper {
 | 
			
		||||
            @include trans-prop-nice($props: opacity, $dur: 0);
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .l-rule-action-buttons-wrapper {
 | 
			
		||||
          .t-delete {
 | 
			
		||||
            margin-left: 10px;
 | 
			
		||||
          }
 | 
			
		||||
    }
 | 
			
		||||
    .t-condition {
 | 
			
		||||
        &:hover {
 | 
			
		||||
            .l-condition-action-buttons-wrapper {
 | 
			
		||||
                @include trans-prop-nice($props: opacity, $dur: 0);
 | 
			
		||||
                opacity: 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -261,7 +261,7 @@ input[type="number"] {
 | 
			
		||||
input[type="text"].lg {  width: 100% !important; }
 | 
			
		||||
.l-input-med input[type="text"],
 | 
			
		||||
input[type="text"].med { width: 200px !important; }
 | 
			
		||||
input[type="text"].sm {  width: 50px !important; }
 | 
			
		||||
input[type="text"].sm, input[type="number"].sm {  width: 50px !important; }
 | 
			
		||||
.l-numeric input[type="text"],
 | 
			
		||||
input[type="text"].numeric { text-align: right; }
 | 
			
		||||
 | 
			
		||||
@@ -317,14 +317,10 @@ input[type="text"].s-input-inline,
 | 
			
		||||
.select {
 | 
			
		||||
    @include btnSubtle($bg: $colorSelectBg);
 | 
			
		||||
    @extend .icon-arrow-down; // Context arrow
 | 
			
		||||
    @if $shdwBtns != none {
 | 
			
		||||
        margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
 | 
			
		||||
    }
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    padding: 0 $interiorMargin;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    line-height: $formInputH;
 | 
			
		||||
    select {
 | 
			
		||||
        @include appearance(none);
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
@@ -340,11 +336,13 @@ input[type="text"].s-input-inline,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    &:before {
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
        @include transform(translateY(-50%));
 | 
			
		||||
        color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
 | 
			
		||||
        display: block;
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        right: $interiorMargin; top: 0;
 | 
			
		||||
        right: $interiorMargin;
 | 
			
		||||
        top: 50%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -396,8 +394,7 @@ input[type="text"].s-input-inline,
 | 
			
		||||
    .l-elem-wrapper {
 | 
			
		||||
        mct-representation {
 | 
			
		||||
            // Holds the context-available item
 | 
			
		||||
            // Must have min-width to make flex work properly
 | 
			
		||||
            // in Safari
 | 
			
		||||
            // Must have min-width to make flex work properly in Safari
 | 
			
		||||
            min-width: 0.7em;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -563,7 +560,6 @@ input[type="text"].s-input-inline,
 | 
			
		||||
    height: $h;
 | 
			
		||||
    margin-top: 1 + floor($h/2) * -1;
 | 
			
		||||
    @include btnSubtle(pullForward($colorBtnBg, 10%));
 | 
			
		||||
    //border-radius: 50% !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin sliderKnobRound() {
 | 
			
		||||
@@ -578,7 +574,6 @@ input[type="text"].s-input-inline,
 | 
			
		||||
 | 
			
		||||
input[type="range"] {
 | 
			
		||||
    // HTML5 range inputs
 | 
			
		||||
 | 
			
		||||
    -webkit-appearance: none; /* Hides the slider so that custom slider can be made */
 | 
			
		||||
    background: transparent; /* Otherwise white in Chrome */
 | 
			
		||||
    &:focus {
 | 
			
		||||
@@ -736,6 +731,30 @@ textarea {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-switcher,
 | 
			
		||||
.t-btn-view-large {
 | 
			
		||||
    @include trans-prop-nice-fade($controlFadeMs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-control {
 | 
			
		||||
    @extend .icon-arrow-right;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-size: 0.75em;
 | 
			
		||||
    &:before {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        @include trans-prop-nice(transform, 100ms);
 | 
			
		||||
        @include transform-origin(center);
 | 
			
		||||
    }
 | 
			
		||||
    &.expanded:before {
 | 
			
		||||
        @include transform(rotate(90deg));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grippy {
 | 
			
		||||
    @extend .icon-grippy;
 | 
			
		||||
    cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************** BROWSER ELEMENTS */
 | 
			
		||||
body.desktop {
 | 
			
		||||
    ::-webkit-scrollbar {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,23 +29,27 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
        font-size: 16px; //120%;
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .title-label {
 | 
			
		||||
        margin-left: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .icon-swatch,
 | 
			
		||||
    .color-swatch {
 | 
			
		||||
        // Used in color menu buttons in toolbar
 | 
			
		||||
        $d: 10px;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        border: 1px solid rgba($colorBtnFg, 0.2);
 | 
			
		||||
        height: $d;
 | 
			
		||||
        width: $d;
 | 
			
		||||
        height: $d; width: $d;
 | 
			
		||||
        line-height: $d;
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
        margin-left: $interiorMarginSm;
 | 
			
		||||
        margin-top: -2px;
 | 
			
		||||
        &:not(.no-selection) {
 | 
			
		||||
            border-color: transparent;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:after {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/******************************************************************* STATUS BLOCK ELEMS */
 | 
			
		||||
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
 | 
			
		||||
	$bgPb: 30%;
 | 
			
		||||
	$bgPbD: 10%;
 | 
			
		||||
@@ -140,7 +140,7 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Styles for messages and message banners */
 | 
			
		||||
/******************************************************************* MESSAGE BANNERS */
 | 
			
		||||
.message {
 | 
			
		||||
	&.block {
 | 
			
		||||
		border-radius: $basicCr;
 | 
			
		||||
@@ -196,7 +196,6 @@
 | 
			
		||||
		padding: 0 $interiorMargin;
 | 
			
		||||
	}
 | 
			
		||||
    .close {
 | 
			
		||||
		//@include test(red, 0.7);
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
        font-size: 7px;
 | 
			
		||||
		width: 8px;
 | 
			
		||||
@@ -240,132 +239,147 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin messageBlock($iconW: 32px) {
 | 
			
		||||
    .type-icon.message-type {
 | 
			
		||||
/******************************************************************* MESSAGES */
 | 
			
		||||
 | 
			
		||||
/* Contexts:
 | 
			
		||||
    In .t-message-list
 | 
			
		||||
    In .overlay as a singleton
 | 
			
		||||
    Inline in the view area
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Archetypal message
 | 
			
		||||
.l-message {
 | 
			
		||||
    $iconW: 32px;
 | 
			
		||||
    @include display(flex);
 | 
			
		||||
    @include flex-direction(row);
 | 
			
		||||
    @include align-items(stretch);
 | 
			
		||||
    padding: $interiorMarginLg;
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
        // Icon
 | 
			
		||||
        @include flex(0 1 auto);
 | 
			
		||||
        @include txtShdw($shdwStatusIc);
 | 
			
		||||
        @extend .icon-bell;
 | 
			
		||||
        color: $colorStatusDefault;
 | 
			
		||||
        font-size: $iconW;
 | 
			
		||||
        padding: 1px;
 | 
			
		||||
        width: $iconW + 2;
 | 
			
		||||
        margin-right: $interiorMarginLg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .message-severity-info .type-icon.message-type {
 | 
			
		||||
    &.message-severity-info:before {
 | 
			
		||||
        @extend .icon-info;
 | 
			
		||||
        color: $colorInfo;
 | 
			
		||||
    }
 | 
			
		||||
    .message-severity-alert .type-icon.message-type {
 | 
			
		||||
        @extend .icon-bell;
 | 
			
		||||
 | 
			
		||||
    &.message-severity-alert:before {
 | 
			
		||||
        color: $colorWarningLo;
 | 
			
		||||
    }
 | 
			
		||||
    .message-severity-error .type-icon.message-type {
 | 
			
		||||
 | 
			
		||||
    &.message-severity-error:before {
 | 
			
		||||
        @extend .icon-alert-rect;
 | 
			
		||||
        color: $colorWarningHi;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
/* Paths:
 | 
			
		||||
    t-dialog | t-dialog-sm > t-message-single | t-message-list > overlay > holder > contents > l-message >
 | 
			
		||||
        message-type > (icon)
 | 
			
		||||
        message-contents >
 | 
			
		||||
        top-bar >
 | 
			
		||||
            title
 | 
			
		||||
            hint
 | 
			
		||||
        editor >
 | 
			
		||||
            (if displaying list of messages)
 | 
			
		||||
            ul > li > l-message >
 | 
			
		||||
                ... same as above
 | 
			
		||||
        bottom-bar
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
.l-message {
 | 
			
		||||
 | 
			
		||||
.w-message-contents {
 | 
			
		||||
    @include flex(1 1 auto);
 | 
			
		||||
    @include display(flex);
 | 
			
		||||
    @include flex-direction(row);
 | 
			
		||||
    @include align-items(stretch);
 | 
			
		||||
    .type-icon.message-type {
 | 
			
		||||
        @include flex(0 1 auto);
 | 
			
		||||
        position: relative;
 | 
			
		||||
    }
 | 
			
		||||
    .message-contents {
 | 
			
		||||
        @include flex(1 1 auto);
 | 
			
		||||
        margin-left: $overlayMargin;
 | 
			
		||||
        position: relative;
 | 
			
		||||
    @include flex-direction(column);
 | 
			
		||||
 | 
			
		||||
        .top-bar,
 | 
			
		||||
    > div,
 | 
			
		||||
    > span {
 | 
			
		||||
        //@include test(red);
 | 
			
		||||
        margin-bottom: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .message-body {
 | 
			
		||||
        @include flex(1 1 100%);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Singleton in an overlay dialog
 | 
			
		||||
.t-message-single .l-message,
 | 
			
		||||
.t-message-single.l-message {
 | 
			
		||||
        $iconW: 80px;
 | 
			
		||||
        @include absPosDefault();
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        &:before {
 | 
			
		||||
            font-size: $iconW;
 | 
			
		||||
            width: $iconW + 2;
 | 
			
		||||
        }
 | 
			
		||||
        .title {
 | 
			
		||||
            font-size: 1.2em;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Singleton inline in a view
 | 
			
		||||
.t-message-inline .l-message,
 | 
			
		||||
.t-message-inline.l-message {
 | 
			
		||||
    border-radius: $controlCr;
 | 
			
		||||
    &.message-severity-info { background-color: rgba($colorInfo, 0.3); }
 | 
			
		||||
    &.message-severity-alert { background-color: rgba($colorWarningLo, 0.3); }
 | 
			
		||||
    &.message-severity-error { background-color: rgba($colorWarningHi, 0.3); }
 | 
			
		||||
 | 
			
		||||
    .w-message-contents.l-message-body-only {
 | 
			
		||||
        .message-body {
 | 
			
		||||
            margin-bottom: $interiorMarginLg * 2;
 | 
			
		||||
            margin-top: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// In a list
 | 
			
		||||
.t-message-list {
 | 
			
		||||
    @include absPosDefault();
 | 
			
		||||
    @include display(flex);
 | 
			
		||||
    @include flex-direction(column);
 | 
			
		||||
 | 
			
		||||
// Message as singleton
 | 
			
		||||
.t-message-single {
 | 
			
		||||
    @include messageBlock(80px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body.desktop .t-message-single {
 | 
			
		||||
    .l-message,
 | 
			
		||||
    .bottom-bar {
 | 
			
		||||
        @include absPosDefault();
 | 
			
		||||
    > div,
 | 
			
		||||
    > span {
 | 
			
		||||
        margin-bottom: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .bottom-bar {
 | 
			
		||||
        top: auto;
 | 
			
		||||
        height: $ovrFooterH;
 | 
			
		||||
    .w-messages {
 | 
			
		||||
        @include flex(1 1 100%);
 | 
			
		||||
        overflow-y: auto;
 | 
			
		||||
        padding-right: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
    // Each message
 | 
			
		||||
    .l-message {
 | 
			
		||||
        border-radius: $controlCr;
 | 
			
		||||
        background: rgba($colorOvrFg, 0.1);
 | 
			
		||||
        margin-bottom: $interiorMargin;
 | 
			
		||||
        .hint,
 | 
			
		||||
        .bottom-bar {
 | 
			
		||||
            text-align: left;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@include phonePortrait {
 | 
			
		||||
    .t-message-single {
 | 
			
		||||
        .l-message {
 | 
			
		||||
            @include flex-direction(column);
 | 
			
		||||
            .message-contents { margin-left: 0; }
 | 
			
		||||
        }
 | 
			
		||||
        .type-icon.message-type {
 | 
			
		||||
    .t-message-single .l-message,
 | 
			
		||||
    .t-message-single.l-message {
 | 
			
		||||
        @include flex-direction(column);
 | 
			
		||||
        &:before {
 | 
			
		||||
            margin-right: 0;
 | 
			
		||||
            margin-bottom: $interiorMarginLg;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bottom-bar {
 | 
			
		||||
            text-align: center !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Messages in list
 | 
			
		||||
.t-message-list {
 | 
			
		||||
    @include messageBlock(32px);
 | 
			
		||||
 | 
			
		||||
    .message-contents {
 | 
			
		||||
        .l-message {
 | 
			
		||||
            border-radius: $controlCr;
 | 
			
		||||
            background: rgba($colorOvrFg, 0.1);
 | 
			
		||||
            margin-bottom: $interiorMargin;
 | 
			
		||||
            padding: $interiorMarginLg;
 | 
			
		||||
 | 
			
		||||
            .message-contents,
 | 
			
		||||
            .bottom-bar {
 | 
			
		||||
                position: relative;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .message-contents {
 | 
			
		||||
                font-size: 0.9em;
 | 
			
		||||
                margin-left: $interiorMarginLg;
 | 
			
		||||
                .message-action { color: pushBack($colorOvrFg, 20%); }
 | 
			
		||||
                .bottom-bar { text-align: left; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .top-bar,
 | 
			
		||||
            .message-body {
 | 
			
		||||
                margin-bottom: $interiorMarginLg;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            .s-button {
 | 
			
		||||
                display: block;
 | 
			
		||||
                width: 100%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body.desktop .t-message-list {
 | 
			
		||||
    .message-contents .l-message { margin-right: $interiorMarginLg; }
 | 
			
		||||
    .w-message-contents { padding-right: $interiorMargin; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alert elements in views
 | 
			
		||||
 
 | 
			
		||||
@@ -19,11 +19,10 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
.l-color-palette {
 | 
			
		||||
.l-palette {
 | 
			
		||||
	$d: 16px;
 | 
			
		||||
	$colorsPerRow: 10;
 | 
			
		||||
	$m: 1;
 | 
			
		||||
	$colorSelectedColor: #fff;
 | 
			
		||||
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	padding: $interiorMargin !important;
 | 
			
		||||
@@ -33,46 +32,41 @@
 | 
			
		||||
		line-height: $d;
 | 
			
		||||
		width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
 | 
			
		||||
 | 
			
		||||
        &.l-option-row {
 | 
			
		||||
            margin-bottom: $interiorMargin;
 | 
			
		||||
            .s-palette-item {
 | 
			
		||||
                border-color: $colorPaletteFg;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		.l-palette-item {
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			@include txtShdwSubtle(0.8);
 | 
			
		||||
			@include trans-prop-nice-fade(0.25s);
 | 
			
		||||
			border: 1px solid transparent;
 | 
			
		||||
			color: $colorSelectedColor;
 | 
			
		||||
			display: block;
 | 
			
		||||
			float: left;
 | 
			
		||||
			height: $d; width: $d;
 | 
			
		||||
			line-height: $d * 0.9;
 | 
			
		||||
			margin: 0 ($m * 1px) ($m * 1px) 0;
 | 
			
		||||
            position: relative;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
            &:before {
 | 
			
		||||
                // Check mark for selected items
 | 
			
		||||
                font-size: 0.8em;
 | 
			
		||||
            }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.s-palette-item {
 | 
			
		||||
            border: 1px solid transparent;
 | 
			
		||||
            color: $colorPaletteFg;
 | 
			
		||||
            text-shadow: $shdwPaletteFg;
 | 
			
		||||
            @include trans-prop-nice-fade(0.25s);
 | 
			
		||||
			&:hover {
 | 
			
		||||
				@include trans-prop-nice-fade(0);
 | 
			
		||||
				border-color: $colorSelectedColor !important;
 | 
			
		||||
				border-color: $colorPaletteSelected !important;
 | 
			
		||||
			}
 | 
			
		||||
            &.selected {
 | 
			
		||||
                border-color: $colorPaletteSelected;
 | 
			
		||||
                box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
 | 
			
		||||
            }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.l-palette-item-label {
 | 
			
		||||
			margin-left: $interiorMargin;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&.l-option-row {
 | 
			
		||||
			margin-bottom: $interiorMargin;
 | 
			
		||||
			.s-palette-item {
 | 
			
		||||
				border-color: $colorBodyFg;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,19 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
.section-header {
 | 
			
		||||
    border-radius: $basicCr;
 | 
			
		||||
    background: $colorFormSectionHeader;
 | 
			
		||||
    color: lighten($colorBodyFg, 20%);
 | 
			
		||||
    font-size: inherit;
 | 
			
		||||
    margin: $interiorMargin 0;
 | 
			
		||||
    padding: $formTBPad $formLRPad;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
    .view-control {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        width: 1em;
 | 
			
		||||
        height: 1em;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form {
 | 
			
		||||
@@ -41,15 +53,6 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .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;
 | 
			
		||||
@@ -57,9 +60,6 @@
 | 
			
		||||
        margin-bottom: $interiorMarginLg * 2;
 | 
			
		||||
		padding: $formTBPad 0;
 | 
			
		||||
		position: relative;
 | 
			
		||||
        //&ng-form {
 | 
			
		||||
        //    display: block;
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
		&.first {
 | 
			
		||||
			border-top: none;
 | 
			
		||||
@@ -171,3 +171,106 @@
 | 
			
		||||
		padding: $interiorMargin;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**************************************************************************** COMPACT FORM */
 | 
			
		||||
// ul > li > label, control
 | 
			
		||||
// Make a new UL for each form section
 | 
			
		||||
// Allow control-first, controls-below
 | 
			
		||||
// TO-DO: migrate work in branch ch-plot-styling that users .inspector-config to use classes below instead
 | 
			
		||||
 | 
			
		||||
.l-compact-form .tree ul li,
 | 
			
		||||
.l-compact-form ul li {
 | 
			
		||||
    padding: 2px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.l-compact-form {
 | 
			
		||||
    $labelW: 40%;
 | 
			
		||||
    $minW: $labelW;
 | 
			
		||||
    ul {
 | 
			
		||||
        margin-bottom: $interiorMarginLg;
 | 
			
		||||
        li {
 | 
			
		||||
            @include display(flex);
 | 
			
		||||
            @include flex-wrap(wrap);
 | 
			
		||||
            @include align-items(center);
 | 
			
		||||
            label,
 | 
			
		||||
            .control {
 | 
			
		||||
                @include display(flex);
 | 
			
		||||
            }
 | 
			
		||||
            label {
 | 
			
		||||
                line-height: inherit;
 | 
			
		||||
                width: $labelW;
 | 
			
		||||
            }
 | 
			
		||||
            .controls {
 | 
			
		||||
                @include flex-grow(1);
 | 
			
		||||
                margin-left: $interiorMargin;
 | 
			
		||||
                input[type="text"],
 | 
			
		||||
                input[type="search"],
 | 
			
		||||
                input[type="number"],
 | 
			
		||||
                .select {
 | 
			
		||||
                    height: $btnStdH;
 | 
			
		||||
                    line-height: $btnStdH;
 | 
			
		||||
                    vertical-align: middle;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                .e-control {
 | 
			
		||||
                    // Individual form controls
 | 
			
		||||
                    &:not(:first-child) {
 | 
			
		||||
                        margin-left: $interiorMarginSm;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &.connects-to-previous {
 | 
			
		||||
                padding-top: 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &.section-header {
 | 
			
		||||
                margin-top: $interiorMarginLg;
 | 
			
		||||
                border-top: 1px solid $colorFormLines;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &.controls-first {
 | 
			
		||||
                .control {
 | 
			
		||||
                    @include flex-grow(0);
 | 
			
		||||
                    margin-right: $interiorMargin;
 | 
			
		||||
                    min-width: 0;
 | 
			
		||||
                    order: 1;
 | 
			
		||||
                    width: auto;
 | 
			
		||||
                }
 | 
			
		||||
                label {
 | 
			
		||||
                    @include flex-grow(1);
 | 
			
		||||
                    order: 2;
 | 
			
		||||
                    width: auto;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            &.controls-under {
 | 
			
		||||
                display: block;
 | 
			
		||||
                .control, label {
 | 
			
		||||
                    display: block;
 | 
			
		||||
                    width: auto;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ul li {
 | 
			
		||||
                    border-top: none !important;
 | 
			
		||||
                    padding: 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .form-error {
 | 
			
		||||
        // Block element that visually flags an error and contains a message
 | 
			
		||||
        background-color: $colorFormFieldErrorBg;
 | 
			
		||||
        color: $colorFormFieldErrorFg;
 | 
			
		||||
        border-radius: $basicCr;
 | 
			
		||||
        display: block;
 | 
			
		||||
        padding: 1px 6px;
 | 
			
		||||
        &:before {
 | 
			
		||||
            content: $glyph-icon-alert-triangle;
 | 
			
		||||
            display: inline;
 | 
			
		||||
            font-family: symbolsfont;
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,7 @@
 | 
			
		||||
 | 
			
		||||
    // Dialog boxes, size constrained and centered in desktop/tablet
 | 
			
		||||
    &.l-dialog {
 | 
			
		||||
        font-size: 0.8rem;
 | 
			
		||||
        .s-button {
 | 
			
		||||
            &:not(.major) {
 | 
			
		||||
                @include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
 | 
			
		||||
@@ -125,9 +126,9 @@
 | 
			
		||||
            @include containerSubtle($colorOvrBg, $colorOvrFg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title {
 | 
			
		||||
        .dialog-title {
 | 
			
		||||
            @include ellipsize();
 | 
			
		||||
            font-size: 1.2em;
 | 
			
		||||
            font-size: 1.5em;
 | 
			
		||||
            line-height: 120%;
 | 
			
		||||
            margin-bottom: $interiorMargin;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -52,21 +52,13 @@ ul.tree {
 | 
			
		||||
 | 
			
		||||
    .view-control {
 | 
			
		||||
        color: $colorItemTreeVC;
 | 
			
		||||
        font-size: 0.75em;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        line-height: inherit;
 | 
			
		||||
        width: $treeVCW;
 | 
			
		||||
        &:before { display: none; }
 | 
			
		||||
        &.has-children {
 | 
			
		||||
            &:before {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                @include trans-prop-nice(transform, 100ms);
 | 
			
		||||
                content: "\e904";
 | 
			
		||||
                @include transform-origin(center);
 | 
			
		||||
            }
 | 
			
		||||
            &.expanded:before {
 | 
			
		||||
                @include transform(rotate(90deg));
 | 
			
		||||
            }
 | 
			
		||||
            &:before { display: block; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,8 @@
 | 
			
		||||
 | 
			
		||||
    &.t-object-type-timer,
 | 
			
		||||
    &.t-object-type-clock,
 | 
			
		||||
    &.t-object-type-hyperlink {
 | 
			
		||||
    &.t-object-type-hyperlink,
 | 
			
		||||
    &.t-object-type-summary-widget {
 | 
			
		||||
        // Hide the right side buttons for objects where they don't make sense
 | 
			
		||||
        // Note that this will hide the view Switcher button if applied
 | 
			
		||||
        // to an object that has it.
 | 
			
		||||
@@ -125,14 +126,21 @@
 | 
			
		||||
        pointer-events: none !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        /********************************************************** OBJECT TYPES */
 | 
			
		||||
    .t-object-type-hyperlink {
 | 
			
		||||
    /********************************************************** OBJECT TYPES */
 | 
			
		||||
    .t-object-type-hyperlink,
 | 
			
		||||
    .t-object-type-summary-widget {
 | 
			
		||||
        .object-holder {
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
        .w-summary-widget,
 | 
			
		||||
        .l-summary-widget,
 | 
			
		||||
        .l-hyperlink.s-button {
 | 
			
		||||
            // When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
 | 
			
		||||
            // Some object types expand to the full size of the object-holder.
 | 
			
		||||
            @extend .abs;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .l-summary-widget,
 | 
			
		||||
        .l-hyperlink.s-button {
 | 
			
		||||
            .label {
 | 
			
		||||
                @include ellipsize();
 | 
			
		||||
                @include transform(translateY(-50%));
 | 
			
		||||
 
 | 
			
		||||
@@ -243,6 +243,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
 | 
			
		||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
 | 
			
		||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
 | 
			
		||||
 | 
			
		||||
// Palettes
 | 
			
		||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
 | 
			
		||||
$colorPaletteSelected: #fff;
 | 
			
		||||
$shdwPaletteFg: black 0 0 2px;
 | 
			
		||||
$shdwPaletteSelected: inset 0 0 0 1px #000;
 | 
			
		||||
 | 
			
		||||
// About Screen
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -243,6 +243,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
 | 
			
		||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
 | 
			
		||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
 | 
			
		||||
 | 
			
		||||
// Palettes
 | 
			
		||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
 | 
			
		||||
$colorPaletteSelected: #333;
 | 
			
		||||
$shdwPaletteFg: none;
 | 
			
		||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
 | 
			
		||||
 | 
			
		||||
// About Screen
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
 | 
			
		||||
<div class="frame frame-template t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}">
 | 
			
		||||
    <div class="abs object-browse-bar l-flex-row">
 | 
			
		||||
        <div class="left flex-elem l-flex-row grows">
 | 
			
		||||
            <mct-representation
 | 
			
		||||
 
 | 
			
		||||
@@ -24,21 +24,22 @@
 | 
			
		||||
 | 
			
		||||
    <span class="l-click-area" ng-click="toggle.toggle()"></span>
 | 
			
		||||
    <span class="color-swatch"
 | 
			
		||||
          ng-class="{'no-selection':ngModel[field] === 'transparent'}"
 | 
			
		||||
          ng-style="{
 | 
			
		||||
             background: ngModel[field]
 | 
			
		||||
             'background-color': ngModel[field]
 | 
			
		||||
         }">
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="title-label" ng-if="structure.text">
 | 
			
		||||
        {{structure.text}}
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <div class="menu l-color-palette"
 | 
			
		||||
    <div class="menu l-palette l-color-palette"
 | 
			
		||||
        ng-controller="ColorController as colors"
 | 
			
		||||
        ng-show="toggle.isActive()">
 | 
			
		||||
        <div
 | 
			
		||||
            class="l-palette-row l-option-row"
 | 
			
		||||
            ng-if="!structure.mandatory">
 | 
			
		||||
            <div class="l-palette-item s-palette-item {{ngModel[field] === 'transparent' ? 'icon-check' : '' }}"
 | 
			
		||||
            <div class="l-palette-item s-palette-item no-selection {{ngModel[field] === 'transparent' ? 'selected' : '' }}"
 | 
			
		||||
                ng-click="ngModel[field] = 'transparent'">
 | 
			
		||||
            </div>
 | 
			
		||||
            <span class="l-palette-item-label">None</span>
 | 
			
		||||
@@ -46,7 +47,7 @@
 | 
			
		||||
        <div
 | 
			
		||||
            class="l-palette-row"
 | 
			
		||||
            ng-repeat="group in colors.groups()">
 | 
			
		||||
            <div class="l-palette-item s-palette-item {{ngModel[field] === color ? 'icon-check' : '' }}"
 | 
			
		||||
            <div class="l-palette-item s-palette-item {{ngModel[field] === color ? 'selected' : '' }}"
 | 
			
		||||
                ng-repeat="color in group"
 | 
			
		||||
                ng-style="{ background: color }"
 | 
			
		||||
                ng-click="ngModel[field] = color">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
        <a class="close icon-x-in-circle"></a>
 | 
			
		||||
        <div class="abs inner-holder contents">
 | 
			
		||||
            <div class="abs top-bar">
 | 
			
		||||
                <div class="title"></div>
 | 
			
		||||
                <div class="dialog-title"></div>
 | 
			
		||||
                <div class="hint"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class='abs editor'>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ define([
 | 
			
		||||
    './timeConductor/plugin',
 | 
			
		||||
    '../../example/imagery/plugin',
 | 
			
		||||
    '../../platform/import-export/bundle',
 | 
			
		||||
    './summaryWidget/plugin',
 | 
			
		||||
    './URLIndicatorPlugin/URLIndicatorPlugin'
 | 
			
		||||
], function (
 | 
			
		||||
    _,
 | 
			
		||||
@@ -37,6 +38,7 @@ define([
 | 
			
		||||
    TimeConductorPlugin,
 | 
			
		||||
    ExampleImagery,
 | 
			
		||||
    ImportExport,
 | 
			
		||||
    SummaryWidget,
 | 
			
		||||
    URLIndicatorPlugin
 | 
			
		||||
) {
 | 
			
		||||
    var bundleMap = {
 | 
			
		||||
@@ -123,6 +125,7 @@ define([
 | 
			
		||||
 | 
			
		||||
    plugins.ExampleImagery = ExampleImagery;
 | 
			
		||||
 | 
			
		||||
    plugins.SummaryWidget = SummaryWidget;
 | 
			
		||||
    plugins.URLIndicatorPlugin = URLIndicatorPlugin;
 | 
			
		||||
 | 
			
		||||
    return plugins;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								src/plugins/summaryWidget/SummaryWidgetsCompositionPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/plugins/summaryWidget/SummaryWidgetsCompositionPolicy.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        function SummaryWidgetsCompositionPolicy(openmct) {
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SummaryWidgetsCompositionPolicy.prototype.allow = function (parent, child) {
 | 
			
		||||
            var parentType = parent.getCapability('type');
 | 
			
		||||
            var newStyleChild = child.useCapability('adapter');
 | 
			
		||||
 | 
			
		||||
            if (parentType.instanceOf('summary-widget') && !this.openmct.telemetry.canProvideTelemetry(newStyleChild)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return SummaryWidgetsCompositionPolicy;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										70
									
								
								src/plugins/summaryWidget/plugin.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										70
									
								
								src/plugins/summaryWidget/plugin.js
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
define(['./src/SummaryWidget', './SummaryWidgetsCompositionPolicy'], function (SummaryWidget, SummaryWidgetsCompositionPolicy) {
 | 
			
		||||
 | 
			
		||||
    function plugin() {
 | 
			
		||||
 | 
			
		||||
        var widgetType = {
 | 
			
		||||
            name: 'Summary Widget',
 | 
			
		||||
            description: 'A compact status update for collections of telemetry-producing items',
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            cssClass: 'icon-summary-widget',
 | 
			
		||||
            initialize: function (domainObject) {
 | 
			
		||||
                domainObject.composition = [];
 | 
			
		||||
                domainObject.configuration = {};
 | 
			
		||||
                domainObject.openNewTab = 'thisTab';
 | 
			
		||||
            },
 | 
			
		||||
            form: [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "url",
 | 
			
		||||
                    "name": "URL",
 | 
			
		||||
                    "control": "textfield",
 | 
			
		||||
                    "pattern": "^(ftp|https?)\\:\\/\\/",
 | 
			
		||||
                    "required": false,
 | 
			
		||||
                    "cssClass": "l-input-lg"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "openNewTab",
 | 
			
		||||
                    "name": "Tab to Open Hyperlink",
 | 
			
		||||
                    "control": "select",
 | 
			
		||||
                    "options": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "value": "thisTab",
 | 
			
		||||
                                "name": "Open in this tab"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "value": "newTab",
 | 
			
		||||
                                "name": "Open in a new tab"
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                    "cssClass": "l-inline"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        function initViewProvider(openmct) {
 | 
			
		||||
            return {
 | 
			
		||||
                name: 'Widget View',
 | 
			
		||||
                view: function (domainObject) {
 | 
			
		||||
                    var summaryWidget = new SummaryWidget(domainObject, openmct);
 | 
			
		||||
                    return {
 | 
			
		||||
                        show: summaryWidget.show,
 | 
			
		||||
                        destroy: summaryWidget.destroy
 | 
			
		||||
                    };
 | 
			
		||||
                },
 | 
			
		||||
                canView: function (domainObject) {
 | 
			
		||||
                    return (domainObject.type === 'summary-widget');
 | 
			
		||||
                },
 | 
			
		||||
                editable: true,
 | 
			
		||||
                key: 'summaryWidgets'
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return function install(openmct) {
 | 
			
		||||
            openmct.types.addType('summary-widget', widgetType);
 | 
			
		||||
            openmct.objectViews.addProvider(initViewProvider(openmct));
 | 
			
		||||
            openmct.legacyExtension('policies', {category: 'composition',
 | 
			
		||||
                implementation: SummaryWidgetsCompositionPolicy, depends: ['openmct']});
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return plugin;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										11
									
								
								src/plugins/summaryWidget/res/conditionTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/plugins/summaryWidget/res/conditionTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
<li class="t-condition">
 | 
			
		||||
    <label class="t-condition-context">when</label>
 | 
			
		||||
    <span class="controls">
 | 
			
		||||
        <span class="t-configuration"> </span>
 | 
			
		||||
        <span class="t-value-inputs"> </span>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="flex-elem l-condition-action-buttons-wrapper">
 | 
			
		||||
        <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this condition"></a>
 | 
			
		||||
        <a class="s-icon-button icon-trash t-delete" title="Delete this condition"></a>
 | 
			
		||||
    </span>
 | 
			
		||||
</li>
 | 
			
		||||
							
								
								
									
										10
									
								
								src/plugins/summaryWidget/res/input/paletteTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/plugins/summaryWidget/res/input/paletteTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<a class="e-control s-button s-menu-button menu-element">
 | 
			
		||||
   <span class="l-click-area"></span>
 | 
			
		||||
   <span class="t-swatch"></span>
 | 
			
		||||
   <div class="menu l-palette">
 | 
			
		||||
       <div class="l-palette-row l-option-row">
 | 
			
		||||
           <div class="l-palette-item s-palette-item no-selection"></div>
 | 
			
		||||
           <span class="l-palette-item-label">None</span>
 | 
			
		||||
       </div>
 | 
			
		||||
   </div>
 | 
			
		||||
</a>
 | 
			
		||||
							
								
								
									
										4
									
								
								src/plugins/summaryWidget/res/input/selectTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/plugins/summaryWidget/res/input/selectTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
<div class="e-control select">
 | 
			
		||||
    <select>
 | 
			
		||||
    </select>
 | 
			
		||||
 </div>
 | 
			
		||||
							
								
								
									
										3
									
								
								src/plugins/summaryWidget/res/ruleImageTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/plugins/summaryWidget/res/ruleImageTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
<div class="holder widget-rules-wrapper">
 | 
			
		||||
    <div class="t-drag-rule-image l-widget-rule s-widget-rule"></div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										69
									
								
								src/plugins/summaryWidget/res/ruleTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/plugins/summaryWidget/res/ruleTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <div class="l-widget-rule s-widget-rule l-compact-form">
 | 
			
		||||
        <div class="widget-rule-header">
 | 
			
		||||
            <span class="flex-elem l-widget-thumb-wrapper">
 | 
			
		||||
                <span class="grippy-holder">
 | 
			
		||||
                    <span class="t-grippy grippy"></span>
 | 
			
		||||
                </span>
 | 
			
		||||
                <span class="view-control expanded"></span>
 | 
			
		||||
                <span class="t-widget-thumb widget-thumb">
 | 
			
		||||
                    <span class="widget-label">DEF</span>
 | 
			
		||||
                </span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="flex-elem rule-title">Default Title</span>
 | 
			
		||||
            <span class="flex-elem rule-description grows">Rule description goes here</span>
 | 
			
		||||
            <span class="flex-elem l-rule-action-buttons-wrapper">
 | 
			
		||||
                <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this rule"></a>
 | 
			
		||||
                <a class="s-icon-button icon-trash t-delete" title="Delete this rule"></a>
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="widget-rule-content expanded">
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label>Rule Name:</label>
 | 
			
		||||
                    <span class="controls">
 | 
			
		||||
                        <input class="t-rule-name-input" type="text" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="connects-to-previous">
 | 
			
		||||
                    <label>Label:</label>
 | 
			
		||||
                    <span class="controls t-label-input">
 | 
			
		||||
                        <input class="e-control t-rule-label-input" type="text" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="connects-to-previous">
 | 
			
		||||
                    <label>Message:</label>
 | 
			
		||||
                    <span class="controls">
 | 
			
		||||
                        <input type="text" class="lg s t-rule-message-input"
 | 
			
		||||
                         placeholder="Will appear as tooltip when hovering on the widget"/>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="connects-to-previous">
 | 
			
		||||
                    <label>Style:</label>
 | 
			
		||||
                    <span class="controls t-style-input">
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
            <ul class="t-widget-rule-config">
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label>Trigger when</label>
 | 
			
		||||
                    <span class="controls">
 | 
			
		||||
                        <div class="e-control select">
 | 
			
		||||
                            <select class="t-trigger">
 | 
			
		||||
                                <option value="any">any condition is met</option>
 | 
			
		||||
                                <option value="all">all conditions are met</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                         </div>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label></label>
 | 
			
		||||
                    <span class="controls">
 | 
			
		||||
                        <a class="e-control s-button labeled add-condition icon-plus">Add Condition</a>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="t-drag-indicator l-widget-rule s-widget-rule" style="opacity:0;" hidden></div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										16
									
								
								src/plugins/summaryWidget/res/testDataItemTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/plugins/summaryWidget/res/testDataItemTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
<div class="t-test-data-item l-compact-form l-widget-test-data-item s-widget-test-data-item">
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li>
 | 
			
		||||
            <label>Set </label>
 | 
			
		||||
            <span class="controls">
 | 
			
		||||
                <span class="t-configuration"></span>
 | 
			
		||||
                <span class="equal-to hidden"> equal to </span>
 | 
			
		||||
                <span class="t-value-inputs"></span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="flex-elem l-widget-test-data-item-action-buttons-wrapper">
 | 
			
		||||
                <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this test value"></a>
 | 
			
		||||
                <a class="s-icon-button icon-trash t-delete" title="Delete this test value"></a>
 | 
			
		||||
            </span>
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										15
									
								
								src/plugins/summaryWidget/res/testDataTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/plugins/summaryWidget/res/testDataTemplate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
<div class="flex-accordion-holder">
 | 
			
		||||
    <div class="flex-accordion-holder t-widget-test-data-content w-widget-test-data-content">
 | 
			
		||||
        <div class="l-enable">
 | 
			
		||||
            <label class="checkbox custom">Apply Test Values
 | 
			
		||||
                <input type="checkbox" class="t-test-data-checkbox">
 | 
			
		||||
                <em></em>
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="t-test-data-config w-widget-test-data-items">
 | 
			
		||||
            <div class="holder add-rule-button-wrapper align-right">
 | 
			
		||||
                <a id="addRule" class="e-control s-button major labeled add-test-condition icon-plus">Add Test Value</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										23
									
								
								src/plugins/summaryWidget/res/widgetTemplate.html
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								src/plugins/summaryWidget/res/widgetTemplate.html
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
<div class="w-summary-widget s-status-no-data">
 | 
			
		||||
    <a id="widget" class="t-summary-widget l-summary-widget s-summary-widget labeled">
 | 
			
		||||
        <span id="widgetLabel" class="label widget-label">Default Static Name</span>
 | 
			
		||||
    </a>
 | 
			
		||||
    <div class="holder flex-elem t-message-inline l-message message-severity-alert t-message-widget-no-data">
 | 
			
		||||
        <div class="w-message-contents l-message-body-only">
 | 
			
		||||
            <div class="message-body">
 | 
			
		||||
                You must add at least one telemetry object to edit this widget.
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="holder l-flex-col l-flex-accordion flex-elem grows widget-edit-holder expanded-widget-test-data expanded-widget-rules">
 | 
			
		||||
        <div class="section-header"><span class="view-control t-view-control-test-data expanded"></span>Test Data Values</div>
 | 
			
		||||
        <div class="widget-test-data flex-accordion-holder"></div>
 | 
			
		||||
        <div class="section-header"><span class="view-control t-view-control-rules expanded"></span>Rules</div>
 | 
			
		||||
        <div class="holder widget-rules-wrapper flex-elem expanded">
 | 
			
		||||
            <div id="ruleArea" class="widget-rules"></div>
 | 
			
		||||
            <div class="holder add-rule-button-wrapper align-right">
 | 
			
		||||
                <a id="addRule" class="s-button major labeled add-rule-button icon-plus">Add Rule</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										188
									
								
								src/plugins/summaryWidget/src/Condition.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/plugins/summaryWidget/src/Condition.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/conditionTemplate.html',
 | 
			
		||||
    './input/ObjectSelect',
 | 
			
		||||
    './input/KeySelect',
 | 
			
		||||
    './input/OperationSelect',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    conditionTemplate,
 | 
			
		||||
    ObjectSelect,
 | 
			
		||||
    KeySelect,
 | 
			
		||||
    OperationSelect,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents an individual condition for a summary widget rule. Manages the
 | 
			
		||||
     * associated inputs and view.
 | 
			
		||||
     * @param {Object} conditionConfig The configurration for this condition, consisting
 | 
			
		||||
     *                                of object, key, operation, and values fields
 | 
			
		||||
     * @param {number} index the index of this Condition object in it's parent Rule's data model,
 | 
			
		||||
     *                        to be injected into callbacks for removes
 | 
			
		||||
     * @param {ConditionManager} conditionManager A ConditionManager instance for populating
 | 
			
		||||
     *                                            selects with configuration data
 | 
			
		||||
     */
 | 
			
		||||
    function Condition(conditionConfig, index, conditionManager) {
 | 
			
		||||
        this.config = conditionConfig;
 | 
			
		||||
        this.index = index;
 | 
			
		||||
        this.conditionManager = conditionManager;
 | 
			
		||||
 | 
			
		||||
        this.domElement = $(conditionTemplate);
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['remove', 'duplicate', 'change'];
 | 
			
		||||
 | 
			
		||||
        this.deleteButton = $('.t-delete', this.domElement);
 | 
			
		||||
        this.duplicateButton = $('.t-duplicate', this.domElement);
 | 
			
		||||
 | 
			
		||||
        this.selects = {};
 | 
			
		||||
        this.valueInputs = [];
 | 
			
		||||
 | 
			
		||||
        this.remove = this.remove.bind(this);
 | 
			
		||||
        this.duplicate = this.duplicate.bind(this);
 | 
			
		||||
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for a change in one of this conditions' custom selects
 | 
			
		||||
         * @param {string} value The new value of this selects
 | 
			
		||||
         * @param {string} property The property of this condition to modify
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onSelectChange(value, property) {
 | 
			
		||||
            if (property === 'operation') {
 | 
			
		||||
                self.generateValueInputs(value);
 | 
			
		||||
            }
 | 
			
		||||
            self.eventEmitter.emit('change', {
 | 
			
		||||
                value: value,
 | 
			
		||||
                property: property,
 | 
			
		||||
                index: self.index
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for this conditions value inputs
 | 
			
		||||
         * @param {Event} event The oninput event that triggered this callback
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onValueInput(event) {
 | 
			
		||||
            var elem = event.target,
 | 
			
		||||
                value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
 | 
			
		||||
                inputIndex = self.valueInputs.indexOf(elem);
 | 
			
		||||
 | 
			
		||||
            self.eventEmitter.emit('change', {
 | 
			
		||||
                value: value,
 | 
			
		||||
                property: 'values[' + inputIndex + ']',
 | 
			
		||||
                index: self.index
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.deleteButton.on('click', this.remove);
 | 
			
		||||
        this.duplicateButton.on('click', this.duplicate);
 | 
			
		||||
 | 
			
		||||
        this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
 | 
			
		||||
            ['any', 'any telemetry'],
 | 
			
		||||
            ['all', 'all telemetry']
 | 
			
		||||
        ]);
 | 
			
		||||
        this.selects.key = new KeySelect(this.config, this.selects.object, this.conditionManager);
 | 
			
		||||
        this.selects.operation = new OperationSelect(
 | 
			
		||||
            this.config,
 | 
			
		||||
            this.selects.key,
 | 
			
		||||
            this.conditionManager,
 | 
			
		||||
            function (value) {
 | 
			
		||||
                onSelectChange(value, 'operation');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        this.selects.object.on('change', function (value) {
 | 
			
		||||
            onSelectChange(value, 'object');
 | 
			
		||||
        });
 | 
			
		||||
        this.selects.key.on('change', function (value) {
 | 
			
		||||
            onSelectChange(value, 'key');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Object.values(this.selects).forEach(function (select) {
 | 
			
		||||
            $('.t-configuration', self.domElement).append(select.getDOM());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $(this.domElement).on('input', 'input', onValueInput);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Condition.prototype.getDOM = function (container) {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this condition: supported callbacks are remove, change,
 | 
			
		||||
     * duplicate
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    Condition.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hide the appropriate inputs when this is the only condition
 | 
			
		||||
     */
 | 
			
		||||
    Condition.prototype.hideButtons = function () {
 | 
			
		||||
        this.deleteButton.hide();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove this condition from the configuration. Invokes any registered
 | 
			
		||||
     * remove callbacks
 | 
			
		||||
     */
 | 
			
		||||
    Condition.prototype.remove = function () {
 | 
			
		||||
        this.eventEmitter.emit('remove', this.index);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Make a deep clone of this condition's configuration and invoke any duplicate
 | 
			
		||||
     * callbacks with the cloned configuration and this rule's index
 | 
			
		||||
     */
 | 
			
		||||
    Condition.prototype.duplicate = function () {
 | 
			
		||||
        var sourceCondition = JSON.parse(JSON.stringify(this.config));
 | 
			
		||||
        this.eventEmitter.emit('duplicate', {
 | 
			
		||||
            sourceCondition: sourceCondition,
 | 
			
		||||
            index: this.index
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When an operation is selected, create the appropriate value inputs
 | 
			
		||||
     * and add them to the view
 | 
			
		||||
     * @param {string} operation The key of currently selected operation
 | 
			
		||||
     */
 | 
			
		||||
    Condition.prototype.generateValueInputs = function (operation) {
 | 
			
		||||
        var evaluator = this.conditionManager.getEvaluator(),
 | 
			
		||||
            inputArea = $('.t-value-inputs', this.domElement),
 | 
			
		||||
            inputCount,
 | 
			
		||||
            inputType,
 | 
			
		||||
            newInput,
 | 
			
		||||
            index = 0;
 | 
			
		||||
 | 
			
		||||
        inputArea.html('');
 | 
			
		||||
        this.valueInputs = [];
 | 
			
		||||
 | 
			
		||||
        if (evaluator.getInputCount(operation)) {
 | 
			
		||||
            inputCount = evaluator.getInputCount(operation);
 | 
			
		||||
            inputType = evaluator.getInputType(operation);
 | 
			
		||||
            while (index < inputCount) {
 | 
			
		||||
                if (!this.config.values[index]) {
 | 
			
		||||
                    this.config.values[index] = (inputType === 'number' ? 0 : '');
 | 
			
		||||
                }
 | 
			
		||||
                newInput = $('<input class="sm" type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
 | 
			
		||||
                this.valueInputs.push(newInput.get(0));
 | 
			
		||||
                inputArea.append(newInput);
 | 
			
		||||
                index += 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Condition;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										438
									
								
								src/plugins/summaryWidget/src/ConditionEvaluator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								src/plugins/summaryWidget/src/ConditionEvaluator.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,438 @@
 | 
			
		||||
define([], function () {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Responsible for maintaining the possible operations for conditions
 | 
			
		||||
     * in this widget, and evaluating the boolean value of conditions passed as
 | 
			
		||||
     * input.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} subscriptionCache A cache consisting of the latest available
 | 
			
		||||
     *                                   data for any telemetry sources in the widget's
 | 
			
		||||
     *                                   composition.
 | 
			
		||||
     * @param {Object} compositionObjs The current set of composition objects to
 | 
			
		||||
     *                                 evaluate for 'any' and 'all' conditions
 | 
			
		||||
     */
 | 
			
		||||
    function ConditionEvaluator(subscriptionCache, compositionObjs) {
 | 
			
		||||
        this.subscriptionCache = subscriptionCache;
 | 
			
		||||
        this.compositionObjs = compositionObjs;
 | 
			
		||||
 | 
			
		||||
        this.testCache = {};
 | 
			
		||||
        this.useTestCache = false;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Maps value types to HTML input field types. These
 | 
			
		||||
         * type of inputs will be generated by conditions expecting this data type
 | 
			
		||||
         */
 | 
			
		||||
        this.inputTypes = {
 | 
			
		||||
            number: 'number',
 | 
			
		||||
            string: 'text'
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Functions to validate that the input to an operation is of the type
 | 
			
		||||
         * that it expects, in order to prevent unexpected behavior. Will be
 | 
			
		||||
         * invoked before the corresponding operation is executed
 | 
			
		||||
         */
 | 
			
		||||
        this.inputValidators = {
 | 
			
		||||
            number: this.validateNumberInput,
 | 
			
		||||
            string: this.validateStringInput
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A library of operations supported by this rule evaluator. Each operation
 | 
			
		||||
         * consists of the following fields:
 | 
			
		||||
         * operation: a function with boolean return type to be invoked when this
 | 
			
		||||
         *            operation is used. Will be called with an array of inputs
 | 
			
		||||
         *            where input [0] is the telemetry value and input [1..n] are
 | 
			
		||||
         *            any comparison values
 | 
			
		||||
         * text: a human-readable description of this operation to populate selects
 | 
			
		||||
         * appliesTo: an array of identifiers for types that operation may be used on
 | 
			
		||||
         * inputCount: the number of inputs required to get any necessary comparison
 | 
			
		||||
         *             values for the operation
 | 
			
		||||
         * getDescription: A function returning a human-readable shorthand description of
 | 
			
		||||
         *                this operation to populate the 'description' field in the rule header.
 | 
			
		||||
         *                Will be invoked with an array of a condition's comparison values.
 | 
			
		||||
         */
 | 
			
		||||
        this.operations = {
 | 
			
		||||
            equalTo: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] === input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is equal to',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' == ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            notEqualTo: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] !== input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is not equal to',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' != ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            greaterThan: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] > input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is greater than',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' > ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            lessThan: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] < input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is less than',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' < ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            greaterThanOrEq: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] >= input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is greater than or equal to',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' >= ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            lessThanOrEq: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] <= input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is less than or equal to',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' <= ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            between: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] > input[1] && input[0] < input[2];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is between',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 2,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' between ' + values[0] + ' and ' + values[1];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            notBetween: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] < input[1] || input[0] > input[2];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is not between',
 | 
			
		||||
                appliesTo: ['number'],
 | 
			
		||||
                inputCount: 2,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' not between ' + values[0] + ' and ' + values[1];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            textContains: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] && input[1] && input[0].includes(input[1]);
 | 
			
		||||
                },
 | 
			
		||||
                text: 'text contains',
 | 
			
		||||
                appliesTo: ['string'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' contains ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            textDoesNotContain: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] && input[1] && !input[0].includes(input[1]);
 | 
			
		||||
                },
 | 
			
		||||
                text: 'text does not contain',
 | 
			
		||||
                appliesTo: ['string'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' does not contain ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            textStartsWith: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0].startsWith(input[1]);
 | 
			
		||||
                },
 | 
			
		||||
                text: 'text starts with',
 | 
			
		||||
                appliesTo: ['string'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' starts with ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            textEndsWith: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0].endsWith(input[1]);
 | 
			
		||||
                },
 | 
			
		||||
                text: 'text ends with',
 | 
			
		||||
                appliesTo: ['string'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' ends with ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            textIsExactly: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return input[0] === input[1];
 | 
			
		||||
                },
 | 
			
		||||
                text: 'text is exactly',
 | 
			
		||||
                appliesTo: ['string'],
 | 
			
		||||
                inputCount: 1,
 | 
			
		||||
                getDescription: function (values) {
 | 
			
		||||
                    return ' is exactly ' + values[0];
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            isUndefined: {
 | 
			
		||||
                operation: function (input) {
 | 
			
		||||
                    return typeof input[0] === 'undefined';
 | 
			
		||||
                },
 | 
			
		||||
                text: 'is undefined',
 | 
			
		||||
                appliesTo: ['string', 'number'],
 | 
			
		||||
                inputCount: 0,
 | 
			
		||||
                getDescription: function () {
 | 
			
		||||
                    return ' is undefined';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Evaluate the conditions passed in as an argument, and return the boolean
 | 
			
		||||
     * value of these conditions. Available evaluation modes are 'any', which will
 | 
			
		||||
     * return true if any of the conditions evaluates to true (i.e. logical OR); 'all',
 | 
			
		||||
     * which returns true only if all conditions evalute to true (i.e. logical AND);
 | 
			
		||||
     * or 'js', which returns the boolean value of a custom JavaScript conditional.
 | 
			
		||||
     * @param {} conditions Either an array of objects with object, key, operation,
 | 
			
		||||
     *                      and value fields, or a string representing a JavaScript
 | 
			
		||||
     *                      condition.
 | 
			
		||||
     * @param {string} mode The key of the mode to use when evaluating the conditions.
 | 
			
		||||
     * @return {boolean} The boolean value of the conditions
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.execute = function (conditions, mode) {
 | 
			
		||||
        var active = false,
 | 
			
		||||
            conditionValue,
 | 
			
		||||
            conditionDefined = false,
 | 
			
		||||
            self = this,
 | 
			
		||||
            firstRuleEvaluated = false,
 | 
			
		||||
            compositionObjs = this.compositionObjs;
 | 
			
		||||
 | 
			
		||||
        if (mode === 'js') {
 | 
			
		||||
            active = this.executeJavaScriptCondition(conditions);
 | 
			
		||||
        } else {
 | 
			
		||||
            (conditions || []).forEach(function (condition) {
 | 
			
		||||
                conditionDefined = false;
 | 
			
		||||
                if (condition.object === 'any') {
 | 
			
		||||
                    conditionValue = false;
 | 
			
		||||
                    Object.keys(compositionObjs).forEach(function (objId) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            conditionValue = conditionValue ||
 | 
			
		||||
                                self.executeCondition(objId, condition.key,
 | 
			
		||||
                                    condition.operation, condition.values);
 | 
			
		||||
                            conditionDefined = true;
 | 
			
		||||
                        } catch (e) {
 | 
			
		||||
                            //ignore a malformed condition
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (condition.object === 'all') {
 | 
			
		||||
                    conditionValue = true;
 | 
			
		||||
                    Object.keys(compositionObjs).forEach(function (objId) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            conditionValue = conditionValue &&
 | 
			
		||||
                                self.executeCondition(objId, condition.key,
 | 
			
		||||
                                    condition.operation, condition.values);
 | 
			
		||||
                            conditionDefined = true;
 | 
			
		||||
                        } catch (e) {
 | 
			
		||||
                            //ignore a malformed condition
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    try {
 | 
			
		||||
                        conditionValue = self.executeCondition(condition.object, condition.key,
 | 
			
		||||
                            condition.operation, condition.values);
 | 
			
		||||
                        conditionDefined = true;
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        //ignore malformed condition
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (conditionDefined) {
 | 
			
		||||
                    active = (mode === 'all' && !firstRuleEvaluated ? true : active);
 | 
			
		||||
                    firstRuleEvaluated = true;
 | 
			
		||||
                    if (mode === 'any') {
 | 
			
		||||
                        active = active || conditionValue;
 | 
			
		||||
                    } else if (mode === 'all') {
 | 
			
		||||
                        active = active && conditionValue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return active;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a condition defined as an object.
 | 
			
		||||
     * @param {string} object The identifier of the telemetry object to retrieve data from
 | 
			
		||||
     * @param {string} key The property of the telemetry object
 | 
			
		||||
     * @param {string} operation The key of the operation in this ConditionEvaluator to executeCondition
 | 
			
		||||
     * @param {string} values An array of comparison values to invoke the operation with
 | 
			
		||||
     * @return {boolean} The value of this condition
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.executeCondition = function (object, key, operation, values) {
 | 
			
		||||
        var cache = (this.useTestCache ? this.testCache : this.subscriptionCache),
 | 
			
		||||
            telemetryValue,
 | 
			
		||||
            op,
 | 
			
		||||
            input,
 | 
			
		||||
            validator;
 | 
			
		||||
 | 
			
		||||
        if (cache[object] && typeof cache[object][key] !== 'undefined') {
 | 
			
		||||
            telemetryValue = [cache[object][key]];
 | 
			
		||||
        }
 | 
			
		||||
        op = this.operations[operation] && this.operations[operation].operation;
 | 
			
		||||
        input = telemetryValue && telemetryValue.concat(values);
 | 
			
		||||
        validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
 | 
			
		||||
 | 
			
		||||
        if (op && input && validator) {
 | 
			
		||||
            return validator(input) && op(input);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error('Malformed condition');
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A function that returns true only if each value in its input argument is
 | 
			
		||||
     * of a numerical type
 | 
			
		||||
     * @param {[]} input An array of values
 | 
			
		||||
     * @returns {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.validateNumberInput = function (input) {
 | 
			
		||||
        var valid = true;
 | 
			
		||||
        input.forEach(function (value) {
 | 
			
		||||
            valid = valid && (typeof value === 'number');
 | 
			
		||||
        });
 | 
			
		||||
        return valid;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A function that returns true only if each value in its input argument is
 | 
			
		||||
     * a string
 | 
			
		||||
     * @param {[]} input An array of values
 | 
			
		||||
     * @returns {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.validateStringInput = function (input) {
 | 
			
		||||
        var valid = true;
 | 
			
		||||
        input.forEach(function (value) {
 | 
			
		||||
            valid = valid && (typeof value === 'string');
 | 
			
		||||
        });
 | 
			
		||||
        return valid;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the keys of operations supported by this evaluator
 | 
			
		||||
     * @return {string[]} An array of the keys of supported operations
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getOperationKeys = function () {
 | 
			
		||||
        return Object.keys(this.operations);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the human-readable text corresponding to a given operation
 | 
			
		||||
     * @param {string} key The key of the operation
 | 
			
		||||
     * @return {string} The text description of the operation
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getOperationText = function (key) {
 | 
			
		||||
        return this.operations[key].text;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true only of the given operation applies to a given type
 | 
			
		||||
     * @param {string} key The key of the operation
 | 
			
		||||
     * @param {string} type The value type to query
 | 
			
		||||
     * @returns {boolean} True if the condition applies, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.operationAppliesTo = function (key, type) {
 | 
			
		||||
        return (this.operations[key].appliesTo.includes(type));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the number of value inputs required by an operation
 | 
			
		||||
     * @param {string} key The key of the operation to query
 | 
			
		||||
     * @return {number}
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getInputCount = function (key) {
 | 
			
		||||
        if (this.operations[key]) {
 | 
			
		||||
            return this.operations[key].inputCount;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the human-readable shorthand description of the operation for a rule header
 | 
			
		||||
     * @param {string} key The key of the operation to query
 | 
			
		||||
     * @param {} values An array of values with which to invoke the getDescription function
 | 
			
		||||
     *                  of the operation
 | 
			
		||||
     * @return {string} A text description of this operation
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getOperationDescription = function (key, values) {
 | 
			
		||||
        if (this.operations[key]) {
 | 
			
		||||
            return this.operations[key].getDescription(values);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the HTML input type associated with a given operation
 | 
			
		||||
     * @param {string} key The key of the operation to query
 | 
			
		||||
     * @return {string} The key for an HTML5 input type
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getInputType = function (key) {
 | 
			
		||||
        var type;
 | 
			
		||||
        if (this.operations[key]) {
 | 
			
		||||
            type = this.operations[key].appliesTo[0];
 | 
			
		||||
        }
 | 
			
		||||
        if (this.inputTypes[type]) {
 | 
			
		||||
            return this.inputTypes[type];
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the HTML input type associated with a value type
 | 
			
		||||
     * @param {string} dataType The JavaScript value type
 | 
			
		||||
     * @return {string} The key for an HTML5 input type
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.getInputTypeById = function (dataType) {
 | 
			
		||||
        return this.inputTypes[dataType];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the test data cache used by this rule evaluator
 | 
			
		||||
     * @param {object} testCache A mock cache following the format of the real
 | 
			
		||||
     *                           subscription cache
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.setTestDataCache = function (testCache) {
 | 
			
		||||
        this.testCache = testCache;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Have this RuleEvaluator pull data values from the provided test cache
 | 
			
		||||
     * instead of its actual subscription cache when evaluating. If invoked with true,
 | 
			
		||||
     * will use the test cache; otherwise, will use the subscription cache
 | 
			
		||||
     * @param {boolean} useTestData Boolean flag
 | 
			
		||||
     */
 | 
			
		||||
    ConditionEvaluator.prototype.useTestData = function (useTestCache) {
 | 
			
		||||
        this.useTestCache = useTestCache;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return ConditionEvaluator;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										371
									
								
								src/plugins/summaryWidget/src/ConditionManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								src/plugins/summaryWidget/src/ConditionManager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,371 @@
 | 
			
		||||
define ([
 | 
			
		||||
    './ConditionEvaluator',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto',
 | 
			
		||||
    'lodash'
 | 
			
		||||
], function (
 | 
			
		||||
    ConditionEvaluator,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $,
 | 
			
		||||
    _
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provides a centralized content manager for conditions in the summary widget.
 | 
			
		||||
     * Loads and caches composition and telemetry subscriptions, and maintains a
 | 
			
		||||
     * {ConditionEvaluator} instance to handle evaluation
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} domainObject the Summary Widget domain object
 | 
			
		||||
     * @param {MCT} openmct an MCT instance
 | 
			
		||||
     */
 | 
			
		||||
    function ConditionManager(domainObject, openmct) {
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.composition = this.openmct.composition.get(this.domainObject);
 | 
			
		||||
        this.compositionObjs = {};
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['add', 'remove', 'load', 'metadata', 'receiveTelemetry'];
 | 
			
		||||
 | 
			
		||||
        this.keywordLabels = {
 | 
			
		||||
            any: 'any Telemetry',
 | 
			
		||||
            all: 'all Telemetry'
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.telemetryMetadataById = {
 | 
			
		||||
            any: {},
 | 
			
		||||
            all: {}
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.telemetryTypesById = {
 | 
			
		||||
            any: {},
 | 
			
		||||
            all: {}
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.subscriptions = {};
 | 
			
		||||
        this.subscriptionCache = {};
 | 
			
		||||
        this.loadComplete = false;
 | 
			
		||||
        this.metadataLoadComplete = false;
 | 
			
		||||
        this.evaluator = new ConditionEvaluator(this.subscriptionCache, this.compositionObjs);
 | 
			
		||||
 | 
			
		||||
        this.composition.on('add', this.onCompositionAdd, this);
 | 
			
		||||
        this.composition.on('remove', this.onCompositionRemove, this);
 | 
			
		||||
        this.composition.on('load', this.onCompositionLoad, this);
 | 
			
		||||
 | 
			
		||||
        this.composition.load();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this ConditionManager: supported callbacks are add
 | 
			
		||||
     * remove, load, metadata, and receiveTelemetry
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a set of rules, execute the conditions associated with each rule
 | 
			
		||||
     * and return the id of the last rule whose conditions evaluate to true
 | 
			
		||||
     * @param {string[]} ruleOrder An array of rule IDs indicating what order They
 | 
			
		||||
     *                             should be evaluated in
 | 
			
		||||
     * @param {Object} rules An object mapping rule IDs to rule configurations
 | 
			
		||||
     * @return {string} The ID of the rule to display on the widget
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.executeRules = function (ruleOrder, rules) {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            activeId = ruleOrder[0],
 | 
			
		||||
            rule,
 | 
			
		||||
            conditions;
 | 
			
		||||
 | 
			
		||||
        ruleOrder.forEach(function (ruleId) {
 | 
			
		||||
            rule = rules[ruleId];
 | 
			
		||||
            conditions = rule.getProperty('conditions');
 | 
			
		||||
            if (self.evaluator.execute(conditions, rule.getProperty('trigger'))) {
 | 
			
		||||
                activeId = ruleId;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return activeId;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a field to the list of all available metadata fields in the widget
 | 
			
		||||
     * @param {Object} metadatum An object representing a set of telemetry metadata
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.addGlobalMetadata = function (metadatum) {
 | 
			
		||||
        this.telemetryMetadataById.any[metadatum.key] = metadatum;
 | 
			
		||||
        this.telemetryMetadataById.all[metadatum.key] = metadatum;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a field to the list of properties for globally available metadata
 | 
			
		||||
     * @param {string} key The key for the property this type applies to
 | 
			
		||||
     * @param {string} type The type that should be associated with this property
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
 | 
			
		||||
        this.telemetryTypesById.any[key] = type;
 | 
			
		||||
        this.telemetryTypesById.all[key] = type;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a telemetry-producing domain object, associate each of it's telemetry
 | 
			
		||||
     * fields with a type, parsing from historical data.
 | 
			
		||||
     * @param {Object} object a domain object that can produce telemetry
 | 
			
		||||
     * @return {Promise} A promise that resolves when a telemetry request
 | 
			
		||||
     *                   has completed and types have been parsed
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.parsePropertyTypes = function (object) {
 | 
			
		||||
        var telemetryAPI = this.openmct.telemetry,
 | 
			
		||||
            key,
 | 
			
		||||
            type,
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        self.telemetryTypesById[object.identifier.key] = {};
 | 
			
		||||
        return telemetryAPI.request(object, {}).then(function (telemetry) {
 | 
			
		||||
            Object.entries(telemetry[telemetry.length - 1]).forEach(function (telem) {
 | 
			
		||||
                key = telem[0];
 | 
			
		||||
                type = typeof telem[1];
 | 
			
		||||
                self.telemetryTypesById[object.identifier.key][key] = type;
 | 
			
		||||
                self.subscriptionCache[object.identifier.key][key] = telem[1];
 | 
			
		||||
                self.addGlobalPropertyType(key, type);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse types of telemetry fields from all composition objects; used internally
 | 
			
		||||
     * to perform a block types load once initial composition load has completed
 | 
			
		||||
     * @return {Promise} A promise that resolves when all metadata has been loaded
 | 
			
		||||
     *                   and property types parsed
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.parseAllPropertyTypes = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            index = 0,
 | 
			
		||||
            objs = Object.values(self.compositionObjs),
 | 
			
		||||
            promise = new Promise(function (resolve, reject) {
 | 
			
		||||
                if (objs.length === 0) {
 | 
			
		||||
                    resolve();
 | 
			
		||||
                }
 | 
			
		||||
                objs.forEach(function (obj) {
 | 
			
		||||
                    self.parsePropertyTypes(obj).then(function () {
 | 
			
		||||
                        if (index === objs.length - 1) {
 | 
			
		||||
                            resolve();
 | 
			
		||||
                        }
 | 
			
		||||
                        index += 1;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        return promise;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invoked when a telemtry subscription yields new data. Updates the LAD
 | 
			
		||||
     * cache and invokes any registered receiveTelemetry callbacks
 | 
			
		||||
     * @param {string} objId The key associated with the telemetry source
 | 
			
		||||
     * @param {datum} datum The new data from the telemetry source
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) {
 | 
			
		||||
        this.subscriptionCache[objId] = datum;
 | 
			
		||||
        this.eventEmitter.emit('receiveTelemetry');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Event handler for an add event in this Summary Widget's composition.
 | 
			
		||||
     * Sets up subscription handlers and parses its property types.
 | 
			
		||||
     * @param {Object} obj The newly added domain object
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.onCompositionAdd = function (obj) {
 | 
			
		||||
        var compositionKeys,
 | 
			
		||||
            telemetryAPI = this.openmct.telemetry,
 | 
			
		||||
            objId = obj.identifier.key,
 | 
			
		||||
            telemetryMetadata,
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        if (telemetryAPI.canProvideTelemetry(obj)) {
 | 
			
		||||
            self.compositionObjs[objId] = obj;
 | 
			
		||||
            self.telemetryMetadataById[objId] = {};
 | 
			
		||||
 | 
			
		||||
            compositionKeys = self.domainObject.composition.map(function (object) {
 | 
			
		||||
                return object.key;
 | 
			
		||||
            });
 | 
			
		||||
            if (!compositionKeys.includes(obj.identifier.key)) {
 | 
			
		||||
                self.domainObject.composition.push(obj.identifier);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            telemetryMetadata = telemetryAPI.getMetadata(obj).values();
 | 
			
		||||
            telemetryMetadata.forEach(function (metaDatum) {
 | 
			
		||||
                self.telemetryMetadataById[objId][metaDatum.key] = metaDatum;
 | 
			
		||||
                self.addGlobalMetadata(metaDatum);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            self.subscriptionCache[objId] = {};
 | 
			
		||||
            self.subscriptions[objId] = telemetryAPI.subscribe(obj, function (datum) {
 | 
			
		||||
                self.handleSubscriptionCallback(objId, datum);
 | 
			
		||||
            }, {});
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * if this is the initial load, parsing property types will be postponed
 | 
			
		||||
             * until all composition objects have been loaded
 | 
			
		||||
             */
 | 
			
		||||
            if (self.loadComplete) {
 | 
			
		||||
                self.parsePropertyTypes(obj);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.eventEmitter.emit('add', obj);
 | 
			
		||||
 | 
			
		||||
            $('.w-summary-widget').removeClass('s-status-no-data');
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invoked on a remove event in this Summary Widget's compostion. Removes
 | 
			
		||||
     * the object from the local composition, and untracks it
 | 
			
		||||
     * @param {object} identifier The identifier of the object to be removed
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.onCompositionRemove = function (identifier) {
 | 
			
		||||
        _.remove(this.domainObject.composition, function (id) {
 | 
			
		||||
            return id.key === identifier.key;
 | 
			
		||||
        });
 | 
			
		||||
        delete this.compositionObjs[identifier.key];
 | 
			
		||||
        this.subscriptions[identifier.key](); //unsubscribe from telemetry source
 | 
			
		||||
        this.eventEmitter.emit('remove', identifier);
 | 
			
		||||
 | 
			
		||||
        if (_.isEmpty(this.compositionObjs)) {
 | 
			
		||||
            $('.w-summary-widget').addClass('s-status-no-data');
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invoked when the Summary Widget's composition finishes its initial load.
 | 
			
		||||
     * Invokes any registered load callbacks, does a block load of all metadata,
 | 
			
		||||
     * and then invokes any registered metadata load callbacks.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.onCompositionLoad = function () {
 | 
			
		||||
        var self = this;
 | 
			
		||||
        self.loadComplete = true;
 | 
			
		||||
        self.eventEmitter.emit('load');
 | 
			
		||||
        self.parseAllPropertyTypes().then(function () {
 | 
			
		||||
            self.metadataLoadComplete = true;
 | 
			
		||||
            self.eventEmitter.emit('metadata');
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the currently tracked telemetry sources
 | 
			
		||||
     * @return {Object} An object mapping object keys to domain objects
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getComposition = function () {
 | 
			
		||||
        return this.compositionObjs;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the human-readable name of a domain object from its key
 | 
			
		||||
     * @param {string} id The key of the domain object
 | 
			
		||||
     * @return {string} The human-readable name of the domain object
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getObjectName = function (id) {
 | 
			
		||||
        var name;
 | 
			
		||||
 | 
			
		||||
        if (this.keywordLabels[id]) {
 | 
			
		||||
            name = this.keywordLabels[id];
 | 
			
		||||
        } else if (this.compositionObjs[id]) {
 | 
			
		||||
            name = this.compositionObjs[id].name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return name;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the property metadata associated with a given telemetry source
 | 
			
		||||
     * @param {string} id The key associated with the domain object
 | 
			
		||||
     * @return {Object} Returns an object with fields representing each telemetry field
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getTelemetryMetadata = function (id) {
 | 
			
		||||
        return this.telemetryMetadataById[id];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the type associated with a telemtry data field of a particular domain
 | 
			
		||||
     * object
 | 
			
		||||
     * @param {string} id The key associated with the domain object
 | 
			
		||||
     * @param {string} property The telemetry field key to retrieve the type of
 | 
			
		||||
     * @return {string} The type name
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getTelemetryPropertyType = function (id, property) {
 | 
			
		||||
        if (this.telemetryTypesById[id]) {
 | 
			
		||||
            return this.telemetryTypesById[id][property];
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the human-readable name of a telemtry data field of a particular domain
 | 
			
		||||
     * object
 | 
			
		||||
     * @param {string} id The key associated with the domain object
 | 
			
		||||
     * @param {string} property The telemetry field key to retrieve the type of
 | 
			
		||||
     * @return {string} The telemetry field name
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getTelemetryPropertyName = function (id, property) {
 | 
			
		||||
        if (this.telemetryMetadataById[id] && this.telemetryMetadataById[id][property]) {
 | 
			
		||||
            return this.telemetryMetadataById[id][property].name;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the {ConditionEvaluator} instance associated with this condition
 | 
			
		||||
     * manager
 | 
			
		||||
     * @return {ConditionEvaluator}
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.getEvaluator = function () {
 | 
			
		||||
        return this.evaluator;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true if the initial compostion load has completed
 | 
			
		||||
     * @return {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.loadCompleted = function () {
 | 
			
		||||
        return this.loadComplete;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true if the initial block metadata load has completed
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.metadataLoadCompleted = function () {
 | 
			
		||||
        return this.metadataLoadComplete;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Triggers the telemetryRecieve callbacks registered to this ConditionManager,
 | 
			
		||||
     * used by the {TestDataManager} to force a rule evaluation when test data is
 | 
			
		||||
     * enabled
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.triggerTelemetryCallback = function () {
 | 
			
		||||
        this.eventEmitter.emit('receiveTelemetry');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unsubscribe from all registered telemetry sources and unregister all event
 | 
			
		||||
     * listeners registered with the Open MCT APIs
 | 
			
		||||
     */
 | 
			
		||||
    ConditionManager.prototype.destroy = function () {
 | 
			
		||||
        Object.values(this.subscriptions).forEach(function (unsubscribeFunction) {
 | 
			
		||||
            unsubscribeFunction();
 | 
			
		||||
        });
 | 
			
		||||
        this.composition.off('add', this.onCompositionAdd, this);
 | 
			
		||||
        this.composition.off('remove', this.onCompositionRemove, this);
 | 
			
		||||
        this.composition.off('load', this.onCompositionLoad, this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return ConditionManager;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										479
									
								
								src/plugins/summaryWidget/src/Rule.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								src/plugins/summaryWidget/src/Rule.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,479 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/ruleTemplate.html',
 | 
			
		||||
    './Condition',
 | 
			
		||||
    './input/ColorPalette',
 | 
			
		||||
    './input/IconPalette',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'lodash',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    ruleTemplate,
 | 
			
		||||
    Condition,
 | 
			
		||||
    ColorPalette,
 | 
			
		||||
    IconPalette,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    _,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An object representing a summary widget rule. Maintains a set of text
 | 
			
		||||
     * and css properties for output, and a set of conditions for configuring
 | 
			
		||||
     * when the rule will be applied to the summary widget.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} ruleConfig A JavaScript object representing the configuration of this rule
 | 
			
		||||
     * @param {Object} domainObject The Summary Widget domain object which contains this rule
 | 
			
		||||
     * @param {MCT} openmct An MCT instance
 | 
			
		||||
     * @param {ConditionManager} conditionManager A ConditionManager instance
 | 
			
		||||
     * @param {WidgetDnD} widgetDnD A WidgetDnD instance to handle dragging and dropping rules
 | 
			
		||||
     * @param {element} container The DOM element which cotains this summary widget
 | 
			
		||||
     */
 | 
			
		||||
    function Rule(ruleConfig, domainObject, openmct, conditionManager, widgetDnD, container) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.config = ruleConfig;
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.conditionManager = conditionManager;
 | 
			
		||||
        this.widgetDnD = widgetDnD;
 | 
			
		||||
        this.container = container;
 | 
			
		||||
 | 
			
		||||
        this.domElement = $(ruleTemplate);
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['remove', 'duplicate', 'change', 'conditionChange'];
 | 
			
		||||
        this.conditions = [];
 | 
			
		||||
        this.dragging = false;
 | 
			
		||||
 | 
			
		||||
        this.remove = this.remove.bind(this);
 | 
			
		||||
        this.duplicate = this.duplicate.bind(this);
 | 
			
		||||
 | 
			
		||||
        this.thumbnail = $('.t-widget-thumb', this.domElement);
 | 
			
		||||
        this.thumbnailLabel = $('.widget-label', this.domElement);
 | 
			
		||||
        this.title = $('.rule-title', this.domElement);
 | 
			
		||||
        this.description = $('.rule-description', this.domElement);
 | 
			
		||||
        this.trigger = $('.t-trigger', this.domElement);
 | 
			
		||||
        this.toggleConfigButton = $('.view-control', this.domElement);
 | 
			
		||||
        this.configArea = $('.widget-rule-content', this.domElement);
 | 
			
		||||
        this.grippy = $('.t-grippy', this.domElement);
 | 
			
		||||
        this.conditionArea = $('.t-widget-rule-config', this.domElement);
 | 
			
		||||
        this.jsConditionArea = $('.t-rule-js-condition-input-holder', this.domElement);
 | 
			
		||||
        this.deleteButton = $('.t-delete', this.domElement);
 | 
			
		||||
        this.duplicateButton = $('.t-duplicate', this.domElement);
 | 
			
		||||
        this.addConditionButton = $('.add-condition', this.domElement);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The text inputs for this rule: any input included in this object will
 | 
			
		||||
         * have the appropriate event handlers registered to it, and it's corresponding
 | 
			
		||||
         * field in the domain object will be updated with its value
 | 
			
		||||
         */
 | 
			
		||||
        this.textInputs = {
 | 
			
		||||
            name: $('.t-rule-name-input', this.domElement),
 | 
			
		||||
            label: $('.t-rule-label-input', this.domElement),
 | 
			
		||||
            message: $('.t-rule-message-input', this.domElement),
 | 
			
		||||
            jsCondition: $('.t-rule-js-condition-input', this.domElement)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.iconInput = new IconPalette('', container);
 | 
			
		||||
        this.colorInputs = {
 | 
			
		||||
            'background-color': new ColorPalette('icon-paint-bucket', container),
 | 
			
		||||
            'border-color': new ColorPalette('icon-line-horz', container),
 | 
			
		||||
            'color': new ColorPalette('icon-T', container)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.colorInputs.color.toggleNullOption();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An onchange event handler method for this rule's icon palettes
 | 
			
		||||
         * @param {string} icon The css class name corresponding to this icon
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onIconInput(icon) {
 | 
			
		||||
            self.config.icon = icon;
 | 
			
		||||
            self.updateDomainObject('icon', icon);
 | 
			
		||||
            self.thumbnailLabel.removeClass().addClass('label widget-label ' + icon);
 | 
			
		||||
            self.eventEmitter.emit('change');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An onchange event handler method for this rule's color palettes palettes
 | 
			
		||||
         * @param {string} color The color selected in the palette
 | 
			
		||||
         * @param {string} property The css property which this color corresponds to
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onColorInput(color, property) {
 | 
			
		||||
            self.config.style[property] = color;
 | 
			
		||||
            self.thumbnail.css(property, color);
 | 
			
		||||
            self.eventEmitter.emit('change');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Parse input text from textbox to prevent HTML Injection
 | 
			
		||||
         * @param {string} msg The text to be Parsed
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function encodeMsg(msg) {
 | 
			
		||||
            return $('<div />').text(msg).html();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An onchange event handler method for this rule's trigger key
 | 
			
		||||
         * @param {event} event The change event from this rule's select element
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onTriggerInput(event) {
 | 
			
		||||
            var elem = event.target;
 | 
			
		||||
            self.config.trigger = encodeMsg(elem.value);
 | 
			
		||||
            self.generateDescription();
 | 
			
		||||
            self.updateDomainObject();
 | 
			
		||||
            self.refreshConditions();
 | 
			
		||||
            self.eventEmitter.emit('conditionChange');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An onchange event handler method for this rule's text inputs
 | 
			
		||||
         * @param {element} elem The input element that generated the event
 | 
			
		||||
         * @param {string} inputKey The field of this rule's configuration to update
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onTextInput(elem, inputKey) {
 | 
			
		||||
            var text = encodeMsg(elem.value);
 | 
			
		||||
            self.config[inputKey] = text;
 | 
			
		||||
            self.updateDomainObject();
 | 
			
		||||
            if (inputKey === 'name') {
 | 
			
		||||
                self.title.html(text);
 | 
			
		||||
            } else if (inputKey === 'label') {
 | 
			
		||||
                self.thumbnailLabel.html(text);
 | 
			
		||||
            }
 | 
			
		||||
            self.eventEmitter.emit('change');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An onchange event handler for a mousedown event that initiates a drag gesture
 | 
			
		||||
         * @param {event} event A mouseup event that was registered on this rule's grippy
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onDragStart(event) {
 | 
			
		||||
            $('.t-drag-indicator').each(function () {
 | 
			
		||||
                $(this).html($('.widget-rule-header', self.domElement).clone().get(0));
 | 
			
		||||
            });
 | 
			
		||||
            self.widgetDnD.setDragImage($('.widget-rule-header', self.domElement).clone().get(0));
 | 
			
		||||
            self.widgetDnD.dragStart(self.config.id);
 | 
			
		||||
            self.domElement.hide();
 | 
			
		||||
        }
 | 
			
		||||
        /**
 | 
			
		||||
         * Show or hide this rule's configuration properties
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function toggleConfig() {
 | 
			
		||||
            self.configArea.toggleClass('expanded');
 | 
			
		||||
            self.toggleConfigButton.toggleClass('expanded');
 | 
			
		||||
            self.config.expanded = !self.config.expanded;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('.t-rule-label-input', this.domElement).before(this.iconInput.getDOM());
 | 
			
		||||
        this.iconInput.set(self.config.icon);
 | 
			
		||||
        this.iconInput.on('change', function (value) {
 | 
			
		||||
            onIconInput(value);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Initialize thumbs when first loading
 | 
			
		||||
        this.thumbnailLabel.removeClass().addClass('label widget-label ' + self.config.icon);
 | 
			
		||||
        this.thumbnailLabel.html(self.config.label);
 | 
			
		||||
 | 
			
		||||
        Object.keys(this.colorInputs).forEach(function (inputKey) {
 | 
			
		||||
            var input = self.colorInputs[inputKey];
 | 
			
		||||
 | 
			
		||||
            input.set(self.config.style[inputKey]);
 | 
			
		||||
            onColorInput(self.config.style[inputKey], inputKey);
 | 
			
		||||
 | 
			
		||||
            input.on('change', function (value) {
 | 
			
		||||
                onColorInput(value, inputKey);
 | 
			
		||||
                self.updateDomainObject();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $('.t-style-input', self.domElement).append(input.getDOM());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Object.keys(this.textInputs).forEach(function (inputKey) {
 | 
			
		||||
            self.textInputs[inputKey].prop('value', self.config[inputKey] || '');
 | 
			
		||||
            self.textInputs[inputKey].on('input', function () {
 | 
			
		||||
                onTextInput(this, inputKey);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.deleteButton.on('click', this.remove);
 | 
			
		||||
        this.duplicateButton.on('click', this.duplicate);
 | 
			
		||||
        this.addConditionButton.on('click', function () {
 | 
			
		||||
            self.initCondition();
 | 
			
		||||
        });
 | 
			
		||||
        this.toggleConfigButton.on('click', toggleConfig);
 | 
			
		||||
        this.trigger.on('change', onTriggerInput);
 | 
			
		||||
 | 
			
		||||
        this.title.html(self.config.name);
 | 
			
		||||
        this.description.html(self.config.description);
 | 
			
		||||
        this.trigger.prop('value', self.config.trigger);
 | 
			
		||||
 | 
			
		||||
        this.grippy.on('mousedown', onDragStart);
 | 
			
		||||
        this.widgetDnD.on('drop', function () {
 | 
			
		||||
            this.domElement.show();
 | 
			
		||||
            $('.t-drag-indicator').hide();
 | 
			
		||||
        }, this);
 | 
			
		||||
 | 
			
		||||
        if (!this.conditionManager.loadCompleted()) {
 | 
			
		||||
            this.config.expanded = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.config.expanded) {
 | 
			
		||||
            this.configArea.removeClass('expanded');
 | 
			
		||||
            this.toggleConfigButton.removeClass('expanded');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.domainObject.configuration.ruleOrder.length === 2) {
 | 
			
		||||
            $('.t-grippy', this.domElement).hide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.refreshConditions();
 | 
			
		||||
 | 
			
		||||
        //if this is the default rule, hide elements that don't apply
 | 
			
		||||
        if (this.config.id === 'default') {
 | 
			
		||||
            $('.t-delete', this.domElement).hide();
 | 
			
		||||
            $('.t-widget-rule-config', this.domElement).hide();
 | 
			
		||||
            $('.t-grippy', this.domElement).hide();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the DOM element representing this rule
 | 
			
		||||
     * @return {Element} A DOM element
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.getDOM = function () {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unregister any event handlers registered with external sources
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.destroy = function () {
 | 
			
		||||
        Object.values(this.colorInputs).forEach(function (palette) {
 | 
			
		||||
            palette.destroy();
 | 
			
		||||
        });
 | 
			
		||||
        this.iconInput.destroy();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this rule: supported callbacks are remove, change,
 | 
			
		||||
     * conditionChange, and duplicate
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An event handler for when a condition's configuration is modified
 | 
			
		||||
     * @param {} value
 | 
			
		||||
     * @param {string} property The path in the configuration to updateDomainObject
 | 
			
		||||
     * @param {number} index The index of the condition that initiated this change
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.onConditionChange = function (event) {
 | 
			
		||||
        _.set(this.config.conditions[event.index], event.property, event.value);
 | 
			
		||||
        this.generateDescription();
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.eventEmitter.emit('conditionChange');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * During a rule drag event, show the placeholder element after this rule
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.showDragIndicator = function () {
 | 
			
		||||
        $('.t-drag-indicator').hide();
 | 
			
		||||
        $('.t-drag-indicator', this.domElement).show();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mutate thet domain object with this rule's local configuration
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.updateDomainObject = function () {
 | 
			
		||||
        this.openmct.objects.mutate(this.domainObject, 'configuration.ruleConfigById.' +
 | 
			
		||||
            this.config.id, this.config);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a property of this rule by key
 | 
			
		||||
     * @param {string} prop They property key of this rule to get
 | 
			
		||||
     * @return {} The queried property
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.getProperty = function (prop) {
 | 
			
		||||
        return this.config[prop];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove this rule from the domain object's configuration and invoke any
 | 
			
		||||
     * registered remove callbacks
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.remove = function () {
 | 
			
		||||
        var ruleOrder = this.domainObject.configuration.ruleOrder,
 | 
			
		||||
            ruleConfigById = this.domainObject.configuration.ruleConfigById,
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        ruleConfigById[self.config.id] = undefined;
 | 
			
		||||
        _.remove(ruleOrder, function (ruleId) {
 | 
			
		||||
            return ruleId === self.config.id;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.openmct.objects.mutate(this.domainObject, 'configuration.ruleConfigById', ruleConfigById);
 | 
			
		||||
        this.openmct.objects.mutate(this.domainObject, 'configuration.ruleOrder', ruleOrder);
 | 
			
		||||
        this.destroy();
 | 
			
		||||
        this.eventEmitter.emit('remove');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes a deep clone of this rule's configuration, and calls the duplicate event
 | 
			
		||||
     * callback with the cloned configuration as an argument if one has been registered
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.duplicate = function () {
 | 
			
		||||
        var sourceRule = JSON.parse(JSON.stringify(this.config));
 | 
			
		||||
        sourceRule.expanded = true;
 | 
			
		||||
        this.eventEmitter.emit('duplicate', sourceRule);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialze a new condition. If called with the sourceConfig and sourceIndex arguments,
 | 
			
		||||
     * will insert a new condition with the provided configuration after the sourceIndex
 | 
			
		||||
     * index. Otherwise, initializes a new blank rule and inserts it at the end
 | 
			
		||||
     * of the list.
 | 
			
		||||
     * @param {Object} [config] The configuration to initialize this rule from,
 | 
			
		||||
     *                          consisting of sourceCondition and index fields
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.initCondition = function (config) {
 | 
			
		||||
        var ruleConfigById = this.domainObject.configuration.ruleConfigById,
 | 
			
		||||
            newConfig,
 | 
			
		||||
            sourceIndex = config && config.index,
 | 
			
		||||
            defaultConfig = {
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        newConfig = (config !== undefined ? config.sourceCondition : defaultConfig);
 | 
			
		||||
        if (sourceIndex !== undefined) {
 | 
			
		||||
            ruleConfigById[this.config.id].conditions.splice(sourceIndex + 1, 0, newConfig);
 | 
			
		||||
        } else {
 | 
			
		||||
            ruleConfigById[this.config.id].conditions.push(newConfig);
 | 
			
		||||
        }
 | 
			
		||||
        this.domainObject.configuration.ruleConfigById = ruleConfigById;
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.refreshConditions();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build {Condition} objects from configuration and rebuild associated view
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.refreshConditions = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            $condition = null,
 | 
			
		||||
            loopCnt = 0,
 | 
			
		||||
            triggerContextStr = self.config.trigger === 'any' ? ' or ' : ' and ';
 | 
			
		||||
 | 
			
		||||
        self.conditions = [];
 | 
			
		||||
        $('.t-condition', this.domElement).remove();
 | 
			
		||||
 | 
			
		||||
        this.config.conditions.forEach(function (condition, index) {
 | 
			
		||||
            var newCondition = new Condition(condition, index, self.conditionManager);
 | 
			
		||||
            newCondition.on('remove', self.removeCondition, self);
 | 
			
		||||
            newCondition.on('duplicate', self.initCondition, self);
 | 
			
		||||
            newCondition.on('change', self.onConditionChange, self);
 | 
			
		||||
            self.conditions.push(newCondition);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (this.config.trigger === 'js') {
 | 
			
		||||
            this.jsConditionArea.show();
 | 
			
		||||
            this.addConditionButton.hide();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.jsConditionArea.hide();
 | 
			
		||||
            this.addConditionButton.show();
 | 
			
		||||
            self.conditions.forEach(function (condition) {
 | 
			
		||||
                $condition = condition.getDOM();
 | 
			
		||||
                $('li:last-of-type', self.conditionArea).before($condition);
 | 
			
		||||
                if (loopCnt > 0) {
 | 
			
		||||
                    $('.t-condition-context', $condition).html(triggerContextStr + ' when');
 | 
			
		||||
                }
 | 
			
		||||
                loopCnt++;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (self.conditions.length === 1) {
 | 
			
		||||
            self.conditions[0].hideButtons();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.generateDescription();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove a condition from this rule's configuration at the given index
 | 
			
		||||
     * @param {number} removeIndex The index of the condition to remove
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.removeCondition = function (removeIndex) {
 | 
			
		||||
        var ruleConfigById = this.domainObject.configuration.ruleConfigById,
 | 
			
		||||
            conditions = ruleConfigById[this.config.id].conditions;
 | 
			
		||||
 | 
			
		||||
        _.remove(conditions, function (condition, index) {
 | 
			
		||||
            return index === removeIndex;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.domainObject.configuration.ruleConfigById[this.config.id] = this.config;
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.refreshConditions();
 | 
			
		||||
        this.eventEmitter.emit('conditionChange');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build a human-readable description from this rule's conditions
 | 
			
		||||
     */
 | 
			
		||||
    Rule.prototype.generateDescription = function () {
 | 
			
		||||
        var description = '',
 | 
			
		||||
            manager = this.conditionManager,
 | 
			
		||||
            evaluator = manager.getEvaluator(),
 | 
			
		||||
            name,
 | 
			
		||||
            property,
 | 
			
		||||
            operation,
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        if (this.config.conditions && this.config.id !== 'default') {
 | 
			
		||||
            if (self.config.trigger === 'js') {
 | 
			
		||||
                description = 'when a custom JavaScript condition evaluates to true';
 | 
			
		||||
            } else {
 | 
			
		||||
                this.config.conditions.forEach(function (condition, index) {
 | 
			
		||||
                    name = manager.getObjectName(condition.object);
 | 
			
		||||
                    property = manager.getTelemetryPropertyName(condition.object, condition.key);
 | 
			
		||||
                    operation = evaluator.getOperationDescription(condition.operation, condition.values);
 | 
			
		||||
                    if (name || property || operation) {
 | 
			
		||||
                        description += 'when ' +
 | 
			
		||||
                            (name ? name + '\'s ' : '') +
 | 
			
		||||
                            (property ? property + ' ' : '') +
 | 
			
		||||
                            (operation ? operation + ' ' : '') +
 | 
			
		||||
                            (self.config.trigger === 'any' ? ' OR ' : ' AND ');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (description.endsWith('OR ')) {
 | 
			
		||||
            description = description.substring(0, description.length - 3);
 | 
			
		||||
        }
 | 
			
		||||
        if (description.endsWith('AND ')) {
 | 
			
		||||
            description = description.substring(0, description.length - 4);
 | 
			
		||||
        }
 | 
			
		||||
        description = (description === '' ? this.config.description : description);
 | 
			
		||||
        this.description.html(description);
 | 
			
		||||
        this.config.description = description;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Rule;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										404
									
								
								src/plugins/summaryWidget/src/SummaryWidget.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								src/plugins/summaryWidget/src/SummaryWidget.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,404 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/widgetTemplate.html',
 | 
			
		||||
    './Rule',
 | 
			
		||||
    './ConditionManager',
 | 
			
		||||
    './TestDataManager',
 | 
			
		||||
    './WidgetDnD',
 | 
			
		||||
    'lodash',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    widgetTemplate,
 | 
			
		||||
    Rule,
 | 
			
		||||
    ConditionManager,
 | 
			
		||||
    TestDataManager,
 | 
			
		||||
    WidgetDnD,
 | 
			
		||||
    _,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    //default css configuration for new rules
 | 
			
		||||
    var DEFAULT_PROPS = {
 | 
			
		||||
        'color': '#ffffff',
 | 
			
		||||
        'background-color': '#38761d',
 | 
			
		||||
        'border-color': 'rgba(0,0,0,0)'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A Summary Widget object, which allows a user to configure rules based
 | 
			
		||||
     * on telemetry producing domain objects, and update a compact display
 | 
			
		||||
     * accordingly.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} domainObject The domain Object represented by this Widget
 | 
			
		||||
     * @param {MCT} openmct An MCT instance
 | 
			
		||||
     */
 | 
			
		||||
    function SummaryWidget(domainObject, openmct) {
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.domainObject.configuration = this.domainObject.configuration || {};
 | 
			
		||||
        this.domainObject.configuration.ruleConfigById = this.domainObject.configuration.ruleConfigById || {};
 | 
			
		||||
        this.domainObject.configuration.ruleOrder = this.domainObject.configuration.ruleOrder || ['default'];
 | 
			
		||||
        this.domainObject.configuration.testDataConfig = this.domainObject.configuration.testDataConfig || [{
 | 
			
		||||
            object: '',
 | 
			
		||||
            key: '',
 | 
			
		||||
            value: ''
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
        this.activeId = 'default';
 | 
			
		||||
        this.rulesById = {};
 | 
			
		||||
        this.domElement = $(widgetTemplate);
 | 
			
		||||
        this.toggleRulesControl = $('.t-view-control-rules', this.domElement);
 | 
			
		||||
        this.toggleTestDataControl = $('.t-view-control-test-data', this.domElement);
 | 
			
		||||
        this.widgetButton = this.domElement.children('#widget');
 | 
			
		||||
        this.editing = false;
 | 
			
		||||
        this.container = '';
 | 
			
		||||
        this.editListenerUnsubscribe = $.noop;
 | 
			
		||||
 | 
			
		||||
        this.outerWrapper = $('.widget-edit-holder', this.domElement);
 | 
			
		||||
        this.ruleArea = $('#ruleArea', this.domElement);
 | 
			
		||||
        this.configAreaRules = $('.widget-rules-wrapper', this.domElement);
 | 
			
		||||
 | 
			
		||||
        this.testDataArea = $('.widget-test-data', this.domElement);
 | 
			
		||||
        this.addRuleButton = $('#addRule', this.domElement);
 | 
			
		||||
 | 
			
		||||
        this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
 | 
			
		||||
        this.testDataManager = new TestDataManager(this.domainObject, this.conditionManager, this.openmct);
 | 
			
		||||
 | 
			
		||||
        this.watchForChanges = this.watchForChanges.bind(this);
 | 
			
		||||
        this.show = this.show.bind(this);
 | 
			
		||||
        this.destroy = this.destroy.bind(this);
 | 
			
		||||
        this.addRule = this.addRule.bind(this);
 | 
			
		||||
        this.onEdit = this.onEdit.bind(this);
 | 
			
		||||
 | 
			
		||||
        this.addHyperlink(domainObject.url, domainObject.openNewTab);
 | 
			
		||||
        this.watchForChanges(openmct, domainObject);
 | 
			
		||||
 | 
			
		||||
        var id = this.domainObject.identifier.key,
 | 
			
		||||
            self = this,
 | 
			
		||||
            oldDomainObject,
 | 
			
		||||
            statusCapability;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Toggles the configuration area for test data in the view
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function toggleTestData() {
 | 
			
		||||
            self.outerWrapper.toggleClass('expanded-widget-test-data');
 | 
			
		||||
            self.toggleTestDataControl.toggleClass('expanded');
 | 
			
		||||
        }
 | 
			
		||||
        this.toggleTestDataControl.on('click', toggleTestData);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Toggles the configuration area for rules in the view
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function toggleRules() {
 | 
			
		||||
            self.outerWrapper.toggleClass('expanded-widget-rules');
 | 
			
		||||
            self.toggleRulesControl.toggleClass('expanded');
 | 
			
		||||
        }
 | 
			
		||||
        this.toggleRulesControl.on('click', toggleRules);
 | 
			
		||||
 | 
			
		||||
        openmct.$injector.get('objectService')
 | 
			
		||||
            .getObjects([id])
 | 
			
		||||
            .then(function (objs) {
 | 
			
		||||
                oldDomainObject = objs[id];
 | 
			
		||||
                statusCapability = oldDomainObject.getCapability('status');
 | 
			
		||||
                self.editListenerUnsubscribe = statusCapability.listen(self.onEdit);
 | 
			
		||||
                if (statusCapability.get('editing')) {
 | 
			
		||||
                    self.onEdit(['editing']);
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.onEdit([]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * adds or removes href to widget button and adds or removes openInNewTab
 | 
			
		||||
     * @param {string} url String that denotes the url to be opened
 | 
			
		||||
     * @param {string} openNewTab String that denotes wether to open link in new tab or not
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.addHyperlink = function (url, openNewTab) {
 | 
			
		||||
        if (url) {
 | 
			
		||||
            this.widgetButton.attr('href', url);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.widgetButton.removeAttr('href');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (openNewTab === 'newTab') {
 | 
			
		||||
            this.widgetButton.attr('target', '_blank');
 | 
			
		||||
        } else {
 | 
			
		||||
            this.widgetButton.removeAttr('target');
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * adds a listener to the object to watch for any changes made by user
 | 
			
		||||
     * only executes if changes are observed
 | 
			
		||||
     * @param {openmct} Object Instance of OpenMCT
 | 
			
		||||
     * @param {domainObject} Object instance of this object
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.watchForChanges = function (openmct, domainObject) {
 | 
			
		||||
        this.watchForChangesUnsubscribe = openmct.objects.observe(domainObject, '*', function (newDomainObject) {
 | 
			
		||||
            if (newDomainObject.url !== this.domainObject.url ||
 | 
			
		||||
                    newDomainObject.openNewTab !== this.domainObject.openNewTab) {
 | 
			
		||||
                this.addHyperlink(newDomainObject.url, newDomainObject.openNewTab);
 | 
			
		||||
            }
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds the Summary Widget's DOM, performs other necessary setup, and attaches
 | 
			
		||||
     * this Summary Widget's view to the supplied container.
 | 
			
		||||
     * @param {element} container The DOM element that will contain this Summary
 | 
			
		||||
     *                            Widget's view.
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.show = function (container) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
        this.container = container;
 | 
			
		||||
        $(container).append(this.domElement);
 | 
			
		||||
        $('.widget-test-data', this.domElement).append(this.testDataManager.getDOM());
 | 
			
		||||
        this.widgetDnD = new WidgetDnD(this.domElement, this.domainObject.configuration.ruleOrder, this.rulesById);
 | 
			
		||||
        this.initRule('default', 'Default');
 | 
			
		||||
        this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
 | 
			
		||||
            self.initRule(ruleId);
 | 
			
		||||
        });
 | 
			
		||||
        this.refreshRules();
 | 
			
		||||
        this.updateWidget();
 | 
			
		||||
        this.updateView();
 | 
			
		||||
 | 
			
		||||
        this.addRuleButton.on('click', this.addRule);
 | 
			
		||||
        this.conditionManager.on('receiveTelemetry', this.executeRules, this);
 | 
			
		||||
        this.widgetDnD.on('drop', this.reorder, this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unregister event listeners with the Open MCT APIs, unsubscribe from telemetry,
 | 
			
		||||
     * and clean up event handlers
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.destroy = function (container) {
 | 
			
		||||
        this.editListenerUnsubscribe();
 | 
			
		||||
        this.conditionManager.destroy();
 | 
			
		||||
        this.widgetDnD.destroy();
 | 
			
		||||
        this.watchForChangesUnsubscribe();
 | 
			
		||||
        Object.values(this.rulesById).forEach(function (rule) {
 | 
			
		||||
            rule.destroy();
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A callback function for the Open MCT status capability listener. If the
 | 
			
		||||
     * view representing the domain object is in edit mode, update the internal
 | 
			
		||||
     * state and widget view accordingly.
 | 
			
		||||
     * @param {string[]} status an array containing the domain object's current status
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.onEdit = function (status) {
 | 
			
		||||
        if (status && status.includes('editing')) {
 | 
			
		||||
            this.editing = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.editing = false;
 | 
			
		||||
        }
 | 
			
		||||
        this.updateView();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this view is currently in edit mode, show all rule configuration interfaces.
 | 
			
		||||
     * Otherwise, hide them.
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.updateView = function () {
 | 
			
		||||
        if (this.editing) {
 | 
			
		||||
            this.ruleArea.show();
 | 
			
		||||
            this.testDataArea.show();
 | 
			
		||||
            this.addRuleButton.show();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.ruleArea.hide();
 | 
			
		||||
            this.testDataArea.hide();
 | 
			
		||||
            this.addRuleButton.hide();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the view from the current rule configuration and order
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.refreshRules = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            ruleOrder = self.domainObject.configuration.ruleOrder,
 | 
			
		||||
            rules = self.rulesById;
 | 
			
		||||
        self.ruleArea.html('');
 | 
			
		||||
        Object.values(ruleOrder).forEach(function (ruleId) {
 | 
			
		||||
            self.ruleArea.append(rules[ruleId].getDOM());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.executeRules();
 | 
			
		||||
        this.addOrRemoveDragIndicator();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SummaryWidget.prototype.addOrRemoveDragIndicator = function () {
 | 
			
		||||
        var rules = this.domainObject.configuration.ruleOrder;
 | 
			
		||||
        var rulesById = this.rulesById;
 | 
			
		||||
 | 
			
		||||
        rules.forEach(function (ruleKey, index, array) {
 | 
			
		||||
            if (array.length > 2 && index > 0) {
 | 
			
		||||
                $('.t-grippy', rulesById[ruleKey].domElement).show();
 | 
			
		||||
            } else {
 | 
			
		||||
                $('.t-grippy', rulesById[ruleKey].domElement).hide();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the widget's appearance from the configuration of the active rule
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.updateWidget = function () {
 | 
			
		||||
        var activeRule = this.rulesById[this.activeId];
 | 
			
		||||
        this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style'));
 | 
			
		||||
        $('#widget', this.domElement).prop('title', activeRule.getProperty('message'));
 | 
			
		||||
        $('#widgetLabel', this.domElement).html(activeRule.getProperty('label'));
 | 
			
		||||
        $('#widgetLabel', this.domElement).removeClass().addClass('label widget-label ' + activeRule.getProperty('icon'));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the active rule and update the Widget's appearance.
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.executeRules = function () {
 | 
			
		||||
        this.activeId = this.conditionManager.executeRules(
 | 
			
		||||
            this.domainObject.configuration.ruleOrder,
 | 
			
		||||
            this.rulesById
 | 
			
		||||
        );
 | 
			
		||||
        this.updateWidget();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a new rule to this widget
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.addRule = function () {
 | 
			
		||||
        var ruleCount = 0,
 | 
			
		||||
            ruleId,
 | 
			
		||||
            ruleOrder = this.domainObject.configuration.ruleOrder;
 | 
			
		||||
 | 
			
		||||
        while (Object.keys(this.rulesById).includes('rule' + ruleCount)) {
 | 
			
		||||
            ruleCount++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ruleId = 'rule' + ruleCount;
 | 
			
		||||
        ruleOrder.push(ruleId);
 | 
			
		||||
        this.domainObject.configuration.ruleOrder = ruleOrder;
 | 
			
		||||
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.initRule(ruleId, 'Rule');
 | 
			
		||||
        this.refreshRules();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Duplicate an existing widget rule from its configuration and splice it in
 | 
			
		||||
     * after the rule it duplicates
 | 
			
		||||
     * @param {Object} sourceConfig The configuration properties of the rule to be
 | 
			
		||||
     *                              instantiated
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.duplicateRule = function (sourceConfig) {
 | 
			
		||||
        var ruleCount = 0,
 | 
			
		||||
            ruleId,
 | 
			
		||||
            sourceRuleId = sourceConfig.id,
 | 
			
		||||
            ruleOrder = this.domainObject.configuration.ruleOrder,
 | 
			
		||||
            ruleIds = Object.keys(this.rulesById);
 | 
			
		||||
 | 
			
		||||
        while (ruleIds.includes('rule' + ruleCount)) {
 | 
			
		||||
            ruleCount = ++ruleCount;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ruleId = 'rule' + ruleCount;
 | 
			
		||||
        sourceConfig.id = ruleId;
 | 
			
		||||
        sourceConfig.name += ' Copy';
 | 
			
		||||
        ruleOrder.splice(ruleOrder.indexOf(sourceRuleId) + 1, 0, ruleId);
 | 
			
		||||
        this.domainObject.configuration.ruleOrder = ruleOrder;
 | 
			
		||||
        this.domainObject.configuration.ruleConfigById[ruleId] = sourceConfig;
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.initRule(ruleId, sourceConfig.name);
 | 
			
		||||
        this.refreshRules();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialze a new rule from a default configuration, or build a {Rule} object
 | 
			
		||||
     * from it if already exists
 | 
			
		||||
     * @param {string} ruleId An key to be used to identify this ruleId, or the key
 | 
			
		||||
                              of the rule to be instantiated
 | 
			
		||||
     * @param {string} ruleName The initial human-readable name of this rule
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.initRule = function (ruleId, ruleName) {
 | 
			
		||||
        var ruleConfig,
 | 
			
		||||
            styleObj = {};
 | 
			
		||||
 | 
			
		||||
        Object.assign(styleObj, DEFAULT_PROPS);
 | 
			
		||||
        if (!this.domainObject.configuration.ruleConfigById[ruleId]) {
 | 
			
		||||
            this.domainObject.configuration.ruleConfigById[ruleId] = {
 | 
			
		||||
                name: ruleName || 'Rule',
 | 
			
		||||
                label: 'Unnamed Rule',
 | 
			
		||||
                message: '',
 | 
			
		||||
                id: ruleId,
 | 
			
		||||
                icon: ' ',
 | 
			
		||||
                style: styleObj,
 | 
			
		||||
                description: ruleId === 'default' ? 'Default appearance for the widget' : 'A new rule',
 | 
			
		||||
                conditions: [{
 | 
			
		||||
                    object: '',
 | 
			
		||||
                    key: '',
 | 
			
		||||
                    operation: '',
 | 
			
		||||
                    values: []
 | 
			
		||||
                }],
 | 
			
		||||
                jsCondition: '',
 | 
			
		||||
                trigger: 'any',
 | 
			
		||||
                expanded: 'true'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        ruleConfig = this.domainObject.configuration.ruleConfigById[ruleId];
 | 
			
		||||
        this.rulesById[ruleId] = new Rule(ruleConfig, this.domainObject, this.openmct,
 | 
			
		||||
                                          this.conditionManager, this.widgetDnD, this.container);
 | 
			
		||||
        this.rulesById[ruleId].on('remove', this.refreshRules, this);
 | 
			
		||||
        this.rulesById[ruleId].on('duplicate', this.duplicateRule, this);
 | 
			
		||||
        this.rulesById[ruleId].on('change', this.updateWidget, this);
 | 
			
		||||
        this.rulesById[ruleId].on('conditionChange', this.executeRules, this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given two ruleIds, move the source rule after the target rule and update
 | 
			
		||||
     * the view.
 | 
			
		||||
     * @param {Object} event An event object representing this drop with draggingId
 | 
			
		||||
     *                       and dropTarget fields
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.reorder = function (event) {
 | 
			
		||||
        var ruleOrder = this.domainObject.configuration.ruleOrder,
 | 
			
		||||
            sourceIndex = ruleOrder.indexOf(event.draggingId),
 | 
			
		||||
            targetIndex;
 | 
			
		||||
 | 
			
		||||
        if (event.draggingId !== event.dropTarget) {
 | 
			
		||||
            ruleOrder.splice(sourceIndex, 1);
 | 
			
		||||
            targetIndex = ruleOrder.indexOf(event.dropTarget);
 | 
			
		||||
            ruleOrder.splice(targetIndex + 1, 0, event.draggingId);
 | 
			
		||||
            this.domainObject.configuration.ruleOrder = ruleOrder;
 | 
			
		||||
            this.updateDomainObject();
 | 
			
		||||
        }
 | 
			
		||||
        this.refreshRules();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Apply a list of css properties to an element
 | 
			
		||||
     * @param {element} elem The DOM element to which the rules will be applied
 | 
			
		||||
     * @param {object} style an object representing the style
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.applyStyle = function (elem, style) {
 | 
			
		||||
        Object.keys(style).forEach(function (propId) {
 | 
			
		||||
            elem.css(propId, style[propId]);
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mutate this domain object's configuration with the current local configuration
 | 
			
		||||
     */
 | 
			
		||||
    SummaryWidget.prototype.updateDomainObject = function () {
 | 
			
		||||
        if (this.editing) {
 | 
			
		||||
            this.openmct.objects.mutate(this.domainObject, 'configuration', this.domainObject.configuration);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return SummaryWidget;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										177
									
								
								src/plugins/summaryWidget/src/TestDataItem.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/plugins/summaryWidget/src/TestDataItem.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/testDataItemTemplate.html',
 | 
			
		||||
    './input/ObjectSelect',
 | 
			
		||||
    './input/KeySelect',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    itemTemplate,
 | 
			
		||||
    ObjectSelect,
 | 
			
		||||
    KeySelect,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An object representing a single mock telemetry value
 | 
			
		||||
     * @param {object} itemConfig the configuration for this item, consisting of
 | 
			
		||||
     *                            object, key, and value fields
 | 
			
		||||
     * @param {number} index the index of this TestDataItem object in the data
 | 
			
		||||
     *                 model of its parent {TestDataManager} o be injected into callbacks
 | 
			
		||||
     *                 for removes
 | 
			
		||||
     * @param {ConditionManager} conditionManager a conditionManager instance
 | 
			
		||||
     *                           for populating selects with configuration data
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    function TestDataItem(itemConfig, index, conditionManager) {
 | 
			
		||||
        this.config = itemConfig;
 | 
			
		||||
        this.index = index;
 | 
			
		||||
        this.conditionManager = conditionManager;
 | 
			
		||||
 | 
			
		||||
        this.domElement = $(itemTemplate);
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['remove', 'duplicate', 'change'];
 | 
			
		||||
 | 
			
		||||
        this.deleteButton = $('.t-delete', this.domElement);
 | 
			
		||||
        this.duplicateButton = $('.t-duplicate', this.domElement);
 | 
			
		||||
 | 
			
		||||
        this.selects = {};
 | 
			
		||||
        this.valueInputs = [];
 | 
			
		||||
 | 
			
		||||
        this.remove = this.remove.bind(this);
 | 
			
		||||
        this.duplicate = this.duplicate.bind(this);
 | 
			
		||||
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A change event handler for this item's select inputs, which also invokes
 | 
			
		||||
         * change callbacks registered with this item
 | 
			
		||||
         * @param {string} value The new value of this select item
 | 
			
		||||
         * @param {string} property The property of this item to modify
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onSelectChange(value, property) {
 | 
			
		||||
            if (property === 'key') {
 | 
			
		||||
                self.generateValueInput(value);
 | 
			
		||||
            }
 | 
			
		||||
            self.eventEmitter.emit('change', {
 | 
			
		||||
                value: value,
 | 
			
		||||
                property: property,
 | 
			
		||||
                index: self.index
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An input event handler for this item's value field. Invokes any change
 | 
			
		||||
         * callbacks associated with this item
 | 
			
		||||
         * @param {Event} event The input event that initiated this callback
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onValueInput(event) {
 | 
			
		||||
            var elem = event.target,
 | 
			
		||||
                value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber);
 | 
			
		||||
 | 
			
		||||
            self.eventEmitter.emit('change', {
 | 
			
		||||
                value: value,
 | 
			
		||||
                property: 'value',
 | 
			
		||||
                index: self.index
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.deleteButton.on('click', this.remove);
 | 
			
		||||
        this.duplicateButton.on('click', this.duplicate);
 | 
			
		||||
 | 
			
		||||
        this.selects.object = new ObjectSelect(this.config, this.conditionManager);
 | 
			
		||||
        this.selects.key = new KeySelect(
 | 
			
		||||
            this.config,
 | 
			
		||||
            this.selects.object,
 | 
			
		||||
            this.conditionManager,
 | 
			
		||||
            function (value) {
 | 
			
		||||
                onSelectChange(value, 'key');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        this.selects.object.on('change', function (value) {
 | 
			
		||||
            onSelectChange(value, 'object');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Object.values(this.selects).forEach(function (select) {
 | 
			
		||||
            $('.t-configuration', self.domElement).append(select.getDOM());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $(this.domElement).on('input', 'input', onValueInput);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the DOM associated with this element's view
 | 
			
		||||
     * @return {Element}
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.getDOM = function (container) {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this item: supported callbacks are remove, change,
 | 
			
		||||
     * and duplicate
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hide the appropriate inputs when this is the only item
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.hideButtons = function () {
 | 
			
		||||
        this.deleteButton.hide();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove this item from the configuration. Invokes any registered
 | 
			
		||||
     * remove callbacks
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.remove = function () {
 | 
			
		||||
        var self = this;
 | 
			
		||||
        this.eventEmitter.emit('remove', self.index);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes a deep clone of this item's configuration, and invokes any registered
 | 
			
		||||
     * duplicate callbacks with the cloned configuration as an argument
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.duplicate = function () {
 | 
			
		||||
        var sourceItem = JSON.parse(JSON.stringify(this.config)),
 | 
			
		||||
            self = this;
 | 
			
		||||
        this.eventEmitter.emit('duplicate', {
 | 
			
		||||
            sourceItem: sourceItem,
 | 
			
		||||
            index: self.index
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When a telemetry property key is selected, create the appropriate value input
 | 
			
		||||
     * and add it to the view
 | 
			
		||||
     * @param {string} key The key of currently selected telemetry property
 | 
			
		||||
     */
 | 
			
		||||
    TestDataItem.prototype.generateValueInput = function (key) {
 | 
			
		||||
        var evaluator = this.conditionManager.getEvaluator(),
 | 
			
		||||
            inputArea = $('.t-value-inputs', this.domElement),
 | 
			
		||||
            dataType = this.conditionManager.getTelemetryPropertyType(this.config.object, key),
 | 
			
		||||
            inputType = evaluator.getInputTypeById(dataType);
 | 
			
		||||
 | 
			
		||||
        inputArea.html('');
 | 
			
		||||
        if (inputType) {
 | 
			
		||||
            if (!this.config.value) {
 | 
			
		||||
                this.config.value = (inputType === 'number' ? 0 : '');
 | 
			
		||||
            }
 | 
			
		||||
            this.valueInput = $('<input class="sm" type = "' + inputType + '" value = "' + this.config.value + '"> </input>').get(0);
 | 
			
		||||
            inputArea.append(this.valueInput);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TestDataItem;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										190
									
								
								src/plugins/summaryWidget/src/TestDataManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/plugins/summaryWidget/src/TestDataManager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/testDataTemplate.html',
 | 
			
		||||
    './TestDataItem',
 | 
			
		||||
    'zepto',
 | 
			
		||||
    'lodash'
 | 
			
		||||
], function (
 | 
			
		||||
    testDataTemplate,
 | 
			
		||||
    TestDataItem,
 | 
			
		||||
    $,
 | 
			
		||||
    _
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Controls the input and usage of test data in the summary widget.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} domainObject The summary widget domain object
 | 
			
		||||
     * @param {ConditionManager} conditionManager A conditionManager instance
 | 
			
		||||
     * @param {MCT} openmct and MCT instance
 | 
			
		||||
     */
 | 
			
		||||
    function TestDataManager(domainObject, conditionManager, openmct) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.manager = conditionManager;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
        this.evaluator = this.manager.getEvaluator();
 | 
			
		||||
        this.domElement = $(testDataTemplate);
 | 
			
		||||
        this.config = this.domainObject.configuration.testDataConfig;
 | 
			
		||||
        this.testCache = {};
 | 
			
		||||
 | 
			
		||||
        this.itemArea = $('.t-test-data-config', this.domElement);
 | 
			
		||||
        this.addItemButton = $('.add-test-condition', this.domElement);
 | 
			
		||||
        this.testDataInput = $('.t-test-data-checkbox', this.domElement);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Toggles whether the associated {ConditionEvaluator} uses the actual
 | 
			
		||||
         * subscription cache or the test data cache
 | 
			
		||||
         * @param {Event} event The change event that triggered this callback
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function toggleTestData(event) {
 | 
			
		||||
            var elem = event.target;
 | 
			
		||||
            self.evaluator.useTestData(elem.checked);
 | 
			
		||||
            self.updateTestCache();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.addItemButton.on('click', function () {
 | 
			
		||||
            self.initItem();
 | 
			
		||||
        });
 | 
			
		||||
        this.testDataInput.on('change', toggleTestData);
 | 
			
		||||
 | 
			
		||||
        this.evaluator.setTestDataCache(this.testCache);
 | 
			
		||||
        this.evaluator.useTestData(false);
 | 
			
		||||
 | 
			
		||||
        this.refreshItems();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the DOM element representing this test data manager in the view
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.getDOM = function () {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialze a new test data item, either from a source configuration, or with
 | 
			
		||||
     * the default empty configuration
 | 
			
		||||
     * @param {Object} [config] An object with sourceItem and index fields to instantiate
 | 
			
		||||
     *                          this rule from, optional
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.initItem = function (config) {
 | 
			
		||||
        var sourceIndex = config && config.index,
 | 
			
		||||
        defaultItem = {
 | 
			
		||||
            object: '',
 | 
			
		||||
            key: '',
 | 
			
		||||
            value: ''
 | 
			
		||||
        },
 | 
			
		||||
        newItem;
 | 
			
		||||
 | 
			
		||||
        newItem = (config !== undefined ? config.sourceItem : defaultItem);
 | 
			
		||||
        if (sourceIndex !== undefined) {
 | 
			
		||||
            this.config.splice(sourceIndex + 1, 0, newItem);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.config.push(newItem);
 | 
			
		||||
        }
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.refreshItems();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove an item from this TestDataManager at the given index
 | 
			
		||||
     * @param {number} removeIndex The index of the item to remove
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.removeItem = function (removeIndex) {
 | 
			
		||||
        _.remove(this.config, function (item, index) {
 | 
			
		||||
            return index === removeIndex;
 | 
			
		||||
        });
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.refreshItems();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change event handler for the test data items which compose this
 | 
			
		||||
     * test data generateor
 | 
			
		||||
     * @param {Object} event An object representing this event, with value, property,
 | 
			
		||||
     *                       and index fields
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.onItemChange = function (event) {
 | 
			
		||||
        this.config[event.index][event.property] = event.value;
 | 
			
		||||
        this.updateDomainObject();
 | 
			
		||||
        this.updateTestCache();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds the test cache from the current item configuration, and passes
 | 
			
		||||
     * the new test cache to the associated {ConditionEvaluator} instance
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.updateTestCache = function () {
 | 
			
		||||
        this.generateTestCache();
 | 
			
		||||
        this.evaluator.setTestDataCache(this.testCache);
 | 
			
		||||
        this.manager.triggerTelemetryCallback();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Intantiate {TestDataItem} objects from the current configuration, and
 | 
			
		||||
     * update the view accordingly
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.refreshItems = function () {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        self.items = [];
 | 
			
		||||
        $('.t-test-data-item', this.domElement).remove();
 | 
			
		||||
 | 
			
		||||
        this.config.forEach(function (item, index) {
 | 
			
		||||
            var newItem = new TestDataItem(item, index, self.manager);
 | 
			
		||||
            newItem.on('remove', self.removeItem, self);
 | 
			
		||||
            newItem.on('duplicate', self.initItem, self);
 | 
			
		||||
            newItem.on('change', self.onItemChange, self);
 | 
			
		||||
            self.items.push(newItem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        self.items.forEach(function (item) {
 | 
			
		||||
            // $('li:last-of-type', self.itemArea).before(item.getDOM());
 | 
			
		||||
            self.itemArea.prepend(item.getDOM());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (self.items.length === 1) {
 | 
			
		||||
            self.items[0].hideButtons();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.updateTestCache();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Builds a test data cache in the format of a telemetry subscription cache
 | 
			
		||||
     * as expected by a {ConditionEvaluator}
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.generateTestCache = function () {
 | 
			
		||||
        var testCache = this.testCache,
 | 
			
		||||
            manager = this.manager,
 | 
			
		||||
            compositionObjs = manager.getComposition(),
 | 
			
		||||
            metadata;
 | 
			
		||||
 | 
			
		||||
        testCache = {};
 | 
			
		||||
        Object.keys(compositionObjs).forEach(function (id) {
 | 
			
		||||
            testCache[id] = {};
 | 
			
		||||
            metadata = manager.getTelemetryMetadata(id);
 | 
			
		||||
            Object.keys(metadata).forEach(function (key) {
 | 
			
		||||
                testCache[id][key] = '';
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        this.config.forEach(function (item) {
 | 
			
		||||
            if (testCache[item.object]) {
 | 
			
		||||
                testCache[item.object][item.key] = item.value;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.testCache = testCache;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the domain object configuration associated with this test data manager
 | 
			
		||||
     */
 | 
			
		||||
    TestDataManager.prototype.updateDomainObject = function () {
 | 
			
		||||
        this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TestDataManager;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										167
									
								
								src/plugins/summaryWidget/src/WidgetDnD.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/plugins/summaryWidget/src/WidgetDnD.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../res/ruleImageTemplate.html',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    ruleImageTemplate,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages the Sortable List interface for reordering rules by drag and drop
 | 
			
		||||
     * @param {Element} container The DOM element that contains this Summary Widget's view
 | 
			
		||||
     * @param {string[]} ruleOrder An array of rule IDs representing the current rule order
 | 
			
		||||
     * @param {Object} rulesById An object mapping rule IDs to rule configurations
 | 
			
		||||
     */
 | 
			
		||||
    function WidgetDnD(container, ruleOrder, rulesById) {
 | 
			
		||||
        this.container = container;
 | 
			
		||||
        this.ruleOrder = ruleOrder;
 | 
			
		||||
        this.rulesById = rulesById;
 | 
			
		||||
 | 
			
		||||
        this.imageContainer = $(ruleImageTemplate);
 | 
			
		||||
        this.image = $('.t-drag-rule-image', this.imageContainer);
 | 
			
		||||
        this.draggingId = '';
 | 
			
		||||
        this.draggingRulePrevious = '';
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['drop'];
 | 
			
		||||
 | 
			
		||||
        this.drag = this.drag.bind(this);
 | 
			
		||||
        this.drop = this.drop.bind(this);
 | 
			
		||||
 | 
			
		||||
        $(this.container).on('mousemove', this.drag);
 | 
			
		||||
        $(document).on('mouseup', this.drop);
 | 
			
		||||
        $(this.container).before(this.imageContainer);
 | 
			
		||||
        $(this.imageContainer).hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove event listeners registered to elements external to the widget
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.destroy = function () {
 | 
			
		||||
        $(this.container).off('mousemove', this.drag);
 | 
			
		||||
        $(document).off('mouseup', this.drop);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this WidgetDnD: supported callback is drop
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the image for the dragged element to the given DOM element
 | 
			
		||||
     * @param {Element} image The HTML element to set as the drap image
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.setDragImage = function (image) {
 | 
			
		||||
        this.image.html(image);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate where this rule has been dragged relative to the other rules
 | 
			
		||||
     * @param {Event} event The mousemove or mouseup event that triggered this
 | 
			
		||||
                            event handler
 | 
			
		||||
     * @return {string} The ID of the rule whose drag indicator should be displayed
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.getDropLocation = function (event) {
 | 
			
		||||
        var ruleOrder = this.ruleOrder,
 | 
			
		||||
            rulesById = this.rulesById,
 | 
			
		||||
            draggingId = this.draggingId,
 | 
			
		||||
            offset,
 | 
			
		||||
            y,
 | 
			
		||||
            height,
 | 
			
		||||
            dropY = event.pageY,
 | 
			
		||||
            target = '';
 | 
			
		||||
 | 
			
		||||
        ruleOrder.forEach(function (ruleId, index) {
 | 
			
		||||
            offset = rulesById[ruleId].getDOM().offset();
 | 
			
		||||
            y = offset.top;
 | 
			
		||||
            height = offset.height;
 | 
			
		||||
            if (index === 0) {
 | 
			
		||||
                if (dropY < y + 7 * height / 3) {
 | 
			
		||||
                    target = ruleId;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (index === ruleOrder.length - 1 && ruleId !== draggingId) {
 | 
			
		||||
                if (y + height / 3 < dropY) {
 | 
			
		||||
                    target = ruleId;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (y + height / 3 < dropY && dropY < y + 7 * height / 3) {
 | 
			
		||||
                    target = ruleId;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return target;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called by a {Rule} instance that initiates a drag gesture
 | 
			
		||||
     * @param {string} ruleId The identifier of the rule which is being dragged
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.dragStart = function (ruleId) {
 | 
			
		||||
        var ruleOrder = this.ruleOrder;
 | 
			
		||||
        this.draggingId = ruleId;
 | 
			
		||||
        this.draggingRulePrevious = ruleOrder[ruleOrder.indexOf(ruleId) - 1];
 | 
			
		||||
        this.rulesById[this.draggingRulePrevious].showDragIndicator();
 | 
			
		||||
        this.imageContainer.show();
 | 
			
		||||
        this.imageContainer.offset({
 | 
			
		||||
            top: event.pageY - this.image.height() / 2,
 | 
			
		||||
            left: event.pageX - $('.t-grippy', this.image).width()
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An event handler for a mousemove event, once a rule has begun a drag gesture
 | 
			
		||||
     * @param {Event} event The mousemove event that triggered this callback
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.drag = function (event) {
 | 
			
		||||
        var dragTarget;
 | 
			
		||||
        if (this.draggingId && this.draggingId !== '') {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            dragTarget = this.getDropLocation(event);
 | 
			
		||||
            this.imageContainer.offset({
 | 
			
		||||
                top: event.pageY - this.image.height() / 2,
 | 
			
		||||
                left: event.pageX - $('.t-grippy', this.image).width()
 | 
			
		||||
            });
 | 
			
		||||
            if (this.rulesById[dragTarget]) {
 | 
			
		||||
                this.rulesById[dragTarget].showDragIndicator();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.rulesById[this.draggingRulePrevious].showDragIndicator();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the mouseup event that corresponds to the user dropping the rule
 | 
			
		||||
     * in its final location. Invokes any registered drop callbacks with the dragged
 | 
			
		||||
     * rule's ID and the ID of the target rule that the dragged rule should be
 | 
			
		||||
     * inserted after
 | 
			
		||||
     * @param {Event} event The mouseup event that triggered this callback
 | 
			
		||||
     */
 | 
			
		||||
    WidgetDnD.prototype.drop = function (event) {
 | 
			
		||||
        var dropTarget = this.getDropLocation(event),
 | 
			
		||||
            draggingId = this.draggingId;
 | 
			
		||||
 | 
			
		||||
        if (this.draggingId && this.draggingId !== '') {
 | 
			
		||||
            if (!this.rulesById[dropTarget]) {
 | 
			
		||||
                dropTarget = this.draggingId;
 | 
			
		||||
            }
 | 
			
		||||
            this.eventEmitter.emit('drop', {
 | 
			
		||||
                draggingId: draggingId,
 | 
			
		||||
                dropTarget: dropTarget
 | 
			
		||||
            });
 | 
			
		||||
            this.draggingId = '';
 | 
			
		||||
            this.draggingRulePrevious = '';
 | 
			
		||||
            this.imageContainer.hide();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return WidgetDnD;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										64
									
								
								src/plugins/summaryWidget/src/input/ColorPalette.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/plugins/summaryWidget/src/input/ColorPalette.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
define([
 | 
			
		||||
    './Palette',
 | 
			
		||||
    'zepto'
 | 
			
		||||
],
 | 
			
		||||
function (
 | 
			
		||||
    Palette,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    //The colors that will be used to instantiate this palette if none are provided
 | 
			
		||||
    var DEFAULT_COLORS = [
 | 
			
		||||
        '#000000','#434343','#666666','#999999','#b7b7b7','#cccccc','#d9d9d9','#efefef','#f3f3f3','#ffffff',
 | 
			
		||||
        '#980000','#ff0000','#ff9900','#ffff00','#00ff00','#00ffff','#4a86e8','#0000ff','#9900ff','#ff00ff',
 | 
			
		||||
        '#e6b8af','#f4cccc','#fce5cd','#fff2cc','#d9ead3','#d0e0e3','#c9daf8','#cfe2f3','#d9d2e9','#ead1dc',
 | 
			
		||||
        '#dd7e6b','#dd7e6b','#f9cb9c','#ffe599','#b6d7a8','#a2c4c9','#a4c2f4','#9fc5e8','#b4a7d6','#d5a6bd',
 | 
			
		||||
        '#cc4125','#e06666','#f6b26b','#ffd966','#93c47d','#76a5af','#6d9eeb','#6fa8dc','#8e7cc3','#c27ba0',
 | 
			
		||||
        '#a61c00','#cc0000','#e69138','#f1c232','#6aa84f','#45818e','#3c78d8','#3d85c6','#674ea7','#a64d79',
 | 
			
		||||
        '#85200c','#990000','#b45f06','#bf9000','#38761d','#134f5c','#1155cc','#0b5394','#351c75','#741b47',
 | 
			
		||||
        '#5b0f00','#660000','#783f04','#7f6000','#274e13','#0c343d','#1c4587','#073763','#20124d','#4c1130'
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Instantiates a new Open MCT Color Palette input
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {string} cssClass The class name of the icon which should be applied
 | 
			
		||||
     *                          to this palette
 | 
			
		||||
     * @param {Element} container The view that contains this palette
 | 
			
		||||
     * @param {string[]} colors (optional) A list of colors that should be used to instantiate this palette
 | 
			
		||||
     */
 | 
			
		||||
    function ColorPalette(cssClass, container, colors) {
 | 
			
		||||
        this.colors = colors || DEFAULT_COLORS;
 | 
			
		||||
        this.palette = new Palette(cssClass, container, this.colors);
 | 
			
		||||
 | 
			
		||||
        this.palette.setNullOption('rgba(0,0,0,0)');
 | 
			
		||||
 | 
			
		||||
        var domElement = $(this.palette.getDOM()),
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        $('.s-menu-button', domElement).addClass('t-color-palette-menu-button');
 | 
			
		||||
        $('.t-swatch', domElement).addClass('color-swatch');
 | 
			
		||||
        $('.l-palette', domElement).addClass('l-color-palette');
 | 
			
		||||
 | 
			
		||||
        $('.s-palette-item', domElement).each(function () {
 | 
			
		||||
            var elem = this;
 | 
			
		||||
            $(elem).css('background-color', elem.dataset.item);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Update this palette's current selection indicator with the style
 | 
			
		||||
         * of the currently selected item
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function updateSwatch() {
 | 
			
		||||
            var color = self.palette.getCurrent();
 | 
			
		||||
            $('.color-swatch', domElement).css('background-color', color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.palette.on('change', updateSwatch);
 | 
			
		||||
 | 
			
		||||
        return this.palette;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ColorPalette;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										80
									
								
								src/plugins/summaryWidget/src/input/IconPalette.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/plugins/summaryWidget/src/input/IconPalette.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
define([
 | 
			
		||||
      './Palette',
 | 
			
		||||
      'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    Palette,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
    //The icons that will be used to instantiate this palette if none are provided
 | 
			
		||||
    var DEFAULT_ICONS = [
 | 
			
		||||
        'icon-alert-rect',
 | 
			
		||||
        'icon-alert-triangle',
 | 
			
		||||
        'icon-arrow-down',
 | 
			
		||||
        'icon-arrow-left',
 | 
			
		||||
        'icon-arrow-right',
 | 
			
		||||
        'icon-arrow-double-up',
 | 
			
		||||
        'icon-arrow-tall-up',
 | 
			
		||||
        'icon-arrow-tall-down',
 | 
			
		||||
        'icon-arrow-double-down',
 | 
			
		||||
        'icon-arrow-up',
 | 
			
		||||
        'icon-asterisk',
 | 
			
		||||
        'icon-bell',
 | 
			
		||||
        'icon-check',
 | 
			
		||||
        'icon-eye-open',
 | 
			
		||||
        'icon-gear',
 | 
			
		||||
        'icon-hourglass',
 | 
			
		||||
        'icon-info',
 | 
			
		||||
        'icon-link',
 | 
			
		||||
        'icon-lock',
 | 
			
		||||
        'icon-people',
 | 
			
		||||
        'icon-person',
 | 
			
		||||
        'icon-plus',
 | 
			
		||||
        'icon-trash',
 | 
			
		||||
        'icon-x'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Instantiates a new Open MCT Icon Palette input
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {string} cssClass The class name of the icon which should be applied
 | 
			
		||||
     *                          to this palette
 | 
			
		||||
     * @param {Element} container The view that contains this palette
 | 
			
		||||
     * @param {string[]} icons (optional) A list of icons that should be used to instantiate this palette
 | 
			
		||||
     */
 | 
			
		||||
    function IconPalette(cssClass, container, icons) {
 | 
			
		||||
        this.icons = icons || DEFAULT_ICONS;
 | 
			
		||||
        this.palette = new Palette(cssClass, container, this.icons);
 | 
			
		||||
 | 
			
		||||
        this.palette.setNullOption(' ');
 | 
			
		||||
        this.oldIcon = this.palette.current || ' ';
 | 
			
		||||
 | 
			
		||||
        var domElement = $(this.palette.getDOM()),
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        $('.s-menu-button', domElement).addClass('t-icon-palette-menu-button');
 | 
			
		||||
        $('.t-swatch', domElement).addClass('icon-swatch');
 | 
			
		||||
        $('.l-palette', domElement).addClass('l-icon-palette');
 | 
			
		||||
 | 
			
		||||
        $('.s-palette-item', domElement).each(function () {
 | 
			
		||||
            var elem = this;
 | 
			
		||||
            $(elem).addClass(elem.dataset.item);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Update this palette's current selection indicator with the style
 | 
			
		||||
         * of the currently selected item
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function updateSwatch() {
 | 
			
		||||
            $('.icon-swatch', domElement).removeClass(self.oldIcon)
 | 
			
		||||
                .addClass(self.palette.getCurrent());
 | 
			
		||||
            self.oldIcon = self.palette.getCurrent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.palette.on('change', updateSwatch);
 | 
			
		||||
 | 
			
		||||
        return this.palette;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return IconPalette;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										90
									
								
								src/plugins/summaryWidget/src/input/KeySelect.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/plugins/summaryWidget/src/input/KeySelect.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
define(['./Select'], function (Select) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {Select} element whose composition is dynamically updated with
 | 
			
		||||
     * the telemetry fields of a particular domain object
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} config The current state of this select. Must have object
 | 
			
		||||
     *                        and key fields
 | 
			
		||||
     * @param {ObjectSelect} objectSelect The linked ObjectSelect instance to which
 | 
			
		||||
     *                                    this KeySelect should listen to for change
 | 
			
		||||
     *                                    events
 | 
			
		||||
     * @param {ConditionManager} manager A ConditionManager instance from which
 | 
			
		||||
     *                                   to receive telemetry metadata
 | 
			
		||||
     * @param {function} changeCallback A change event callback to register with this
 | 
			
		||||
     *                                  select on initialization
 | 
			
		||||
     */
 | 
			
		||||
    var NULLVALUE = '- Select Field -';
 | 
			
		||||
 | 
			
		||||
    function KeySelect(config, objectSelect, manager, changeCallback) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        this.objectSelect = objectSelect;
 | 
			
		||||
        this.manager = manager;
 | 
			
		||||
 | 
			
		||||
        this.select = new Select();
 | 
			
		||||
        this.select.hide();
 | 
			
		||||
        this.select.addOption('', NULLVALUE);
 | 
			
		||||
        if (changeCallback) {
 | 
			
		||||
            this.select.on('change', changeCallback);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Change event handler for the {ObjectSelect} to which this KeySelect instance
 | 
			
		||||
         * is linked. Loads the new object's metadata and updates its select element's
 | 
			
		||||
         * composition.
 | 
			
		||||
         * @param {Object} key The key identifying the newly selected domain object
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onObjectChange(key) {
 | 
			
		||||
            var selected = self.manager.metadataLoadCompleted() ? self.select.getSelected() : self.config.key;
 | 
			
		||||
            self.telemetryMetadata = self.manager.getTelemetryMetadata(key) || {};
 | 
			
		||||
            self.generateOptions();
 | 
			
		||||
            self.select.setSelected(selected);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for the intial metadata load event from the associated
 | 
			
		||||
         * ConditionManager. Retreives metadata from the manager and populates
 | 
			
		||||
         * the select element.
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onMetadataLoad() {
 | 
			
		||||
            if (self.manager.getTelemetryMetadata(self.config.object)) {
 | 
			
		||||
                self.telemetryMetadata = self.manager.getTelemetryMetadata(self.config.object);
 | 
			
		||||
                self.generateOptions();
 | 
			
		||||
            }
 | 
			
		||||
            self.select.setSelected(self.config.key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (self.manager.metadataLoadCompleted()) {
 | 
			
		||||
            onMetadataLoad();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.objectSelect.on('change', onObjectChange);
 | 
			
		||||
        this.manager.on('metadata', onMetadataLoad);
 | 
			
		||||
 | 
			
		||||
        return this.select;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Populate this select with options based on its current composition
 | 
			
		||||
     */
 | 
			
		||||
    KeySelect.prototype.generateOptions = function () {
 | 
			
		||||
        var items = Object.entries(this.telemetryMetadata).map(function (metaDatum) {
 | 
			
		||||
            return [metaDatum[0], metaDatum[1].name];
 | 
			
		||||
        });
 | 
			
		||||
        items.splice(0, 0, ['',NULLVALUE]);
 | 
			
		||||
        this.select.setOptions(items);
 | 
			
		||||
 | 
			
		||||
        if (this.select.options.length < 2) {
 | 
			
		||||
            this.select.hide();
 | 
			
		||||
        } else if (this.select.options.length > 1) {
 | 
			
		||||
            this.select.show();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return KeySelect;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										87
									
								
								src/plugins/summaryWidget/src/input/ObjectSelect.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/plugins/summaryWidget/src/input/ObjectSelect.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
define(['./Select'], function (Select) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {Select} element whose composition is dynamically updated with
 | 
			
		||||
     * the current composition of the Summary Widget
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} config The current state of this select. Must have an
 | 
			
		||||
     *                        object field
 | 
			
		||||
     * @param {ConditionManager} manager A ConditionManager instance from which
 | 
			
		||||
     *                                   to receive the current composition status
 | 
			
		||||
     * @param {string[][]} baseOptions A set of [value, label] keyword pairs to
 | 
			
		||||
     *                                 display regardless of the composition state
 | 
			
		||||
     */
 | 
			
		||||
    function ObjectSelect(config, manager, baseOptions) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        this.manager = manager;
 | 
			
		||||
 | 
			
		||||
        this.select = new Select();
 | 
			
		||||
        this.baseOptions = [['', '- Select Telemetry -']];
 | 
			
		||||
        if (baseOptions) {
 | 
			
		||||
            this.baseOptions = this.baseOptions.concat(baseOptions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.baseOptions.forEach(function (option) {
 | 
			
		||||
            self.select.addOption(option[0], option[1]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.compositionObjs = this.manager.getComposition();
 | 
			
		||||
        self.generateOptions();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Add a new composition object to this select when a composition added
 | 
			
		||||
         * is detected on the Summary Widget
 | 
			
		||||
         * @param {Object} obj The newly added domain object
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onCompositionAdd(obj) {
 | 
			
		||||
            self.select.addOption(obj.identifier.key, obj.name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Refresh the composition of this select when a domain object is removed
 | 
			
		||||
         * from the Summary Widget's composition
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onCompositionRemove() {
 | 
			
		||||
            var selected = self.select.getSelected();
 | 
			
		||||
            self.generateOptions();
 | 
			
		||||
            self.select.setSelected(selected);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Defer setting the selected state on initial load until load is complete
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onCompositionLoad() {
 | 
			
		||||
            self.select.setSelected(self.config.object);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.manager.on('add', onCompositionAdd);
 | 
			
		||||
        this.manager.on('remove', onCompositionRemove);
 | 
			
		||||
        this.manager.on('load', onCompositionLoad);
 | 
			
		||||
 | 
			
		||||
        if (this.manager.loadCompleted()) {
 | 
			
		||||
            onCompositionLoad();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.select;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Populate this select with options based on its current composition
 | 
			
		||||
     */
 | 
			
		||||
    ObjectSelect.prototype.generateOptions = function () {
 | 
			
		||||
        var items = Object.values(this.compositionObjs).map(function (obj) {
 | 
			
		||||
            return [obj.identifier.key, obj.name];
 | 
			
		||||
        });
 | 
			
		||||
        this.baseOptions.forEach(function (option, index) {
 | 
			
		||||
            items.splice(index, 0, option);
 | 
			
		||||
        });
 | 
			
		||||
        this.select.setOptions(items);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return ObjectSelect;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										114
									
								
								src/plugins/summaryWidget/src/input/OperationSelect.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/plugins/summaryWidget/src/input/OperationSelect.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
define(['./Select'], function (Select) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {Select} element whose composition is dynamically updated with
 | 
			
		||||
     * the operations applying to a particular telemetry property
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {Object} config The current state of this select. Must have object,
 | 
			
		||||
     *                        key, and operation fields
 | 
			
		||||
     * @param {KeySelect} keySelect The linked Key Select instance to which
 | 
			
		||||
     *                              this OperationSelect should listen to for change
 | 
			
		||||
     *                              events
 | 
			
		||||
     * @param {ConditionManager} manager A ConditionManager instance from which
 | 
			
		||||
     *                                   to receive telemetry metadata
 | 
			
		||||
     * @param {function} changeCallback A change event callback to register with this
 | 
			
		||||
     *                                  select on initialization
 | 
			
		||||
     */
 | 
			
		||||
    var NULLVALUE = '- Select Comparison -';
 | 
			
		||||
 | 
			
		||||
    function OperationSelect(config, keySelect, manager, changeCallback) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        this.keySelect = keySelect;
 | 
			
		||||
        this.manager = manager;
 | 
			
		||||
 | 
			
		||||
        this.operationKeys = [];
 | 
			
		||||
        this.evaluator = this.manager.getEvaluator();
 | 
			
		||||
        this.loadComplete = false;
 | 
			
		||||
 | 
			
		||||
        this.select = new Select();
 | 
			
		||||
        this.select.hide();
 | 
			
		||||
        this.select.addOption('', NULLVALUE);
 | 
			
		||||
        if (changeCallback) {
 | 
			
		||||
            this.select.on('change', changeCallback);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Change event handler for the {KeySelect} to which this OperationSelect instance
 | 
			
		||||
         * is linked. Loads the operations applicable to the given telemetry property and updates
 | 
			
		||||
         * its select element's composition
 | 
			
		||||
         * @param {Object} key The key identifying the newly selected property
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onKeyChange(key) {
 | 
			
		||||
            var selected = self.config.operation;
 | 
			
		||||
            if (self.manager.metadataLoadCompleted()) {
 | 
			
		||||
                self.loadOptions(key);
 | 
			
		||||
                self.generateOptions();
 | 
			
		||||
                self.select.setSelected(selected);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for the intial metadata load event from the associated
 | 
			
		||||
         * ConditionManager. Retreives telemetry property types and updates the
 | 
			
		||||
         * select
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onMetadataLoad() {
 | 
			
		||||
            if (self.manager.getTelemetryPropertyType(self.config.object, self.config.key)) {
 | 
			
		||||
                self.loadOptions(self.config.key);
 | 
			
		||||
                self.generateOptions();
 | 
			
		||||
            }
 | 
			
		||||
            self.select.setSelected(self.config.operation);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.keySelect.on('change', onKeyChange);
 | 
			
		||||
        this.manager.on('metadata', onMetadataLoad);
 | 
			
		||||
 | 
			
		||||
        if (this.manager.metadataLoadCompleted()) {
 | 
			
		||||
            onMetadataLoad();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.select;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Populate this select with options based on its current composition
 | 
			
		||||
     */
 | 
			
		||||
    OperationSelect.prototype.generateOptions = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            items = this.operationKeys.map(function (operation) {
 | 
			
		||||
                return [operation, self.evaluator.getOperationText(operation)];
 | 
			
		||||
            });
 | 
			
		||||
        items.splice(0, 0, ['', NULLVALUE]);
 | 
			
		||||
        this.select.setOptions(items);
 | 
			
		||||
 | 
			
		||||
        if (this.select.options.length < 2) {
 | 
			
		||||
            this.select.hide();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.select.show();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the data type associated with a given telemetry property and
 | 
			
		||||
     * the applicable operations from the {ConditionEvaluator}
 | 
			
		||||
     * @param {string} key The telemetry property to load operations for
 | 
			
		||||
     */
 | 
			
		||||
    OperationSelect.prototype.loadOptions = function (key) {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            operations = self.evaluator.getOperationKeys(),
 | 
			
		||||
            type;
 | 
			
		||||
 | 
			
		||||
        type = self.manager.getTelemetryPropertyType(self.config.object, key);
 | 
			
		||||
 | 
			
		||||
        self.operationKeys = operations.filter(function (operation) {
 | 
			
		||||
            return self.evaluator.operationAppliesTo(operation, type);
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return OperationSelect;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										166
									
								
								src/plugins/summaryWidget/src/input/Palette.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/plugins/summaryWidget/src/input/Palette.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../../res/input/paletteTemplate.html',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    paletteTemplate,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Instantiates a new Open MCT Color Palette input
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @param {string} cssClass The class name of the icon which should be applied
 | 
			
		||||
     *                          to this palette
 | 
			
		||||
     * @param {Element} container The view that contains this palette
 | 
			
		||||
     * @param {string[]} items A list of data items that will be associated with each
 | 
			
		||||
     *                         palette item in the view; how this data is represented is
 | 
			
		||||
     *                         up to the descendent class
 | 
			
		||||
     */
 | 
			
		||||
    function Palette(cssClass, container, items) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.cssClass = cssClass;
 | 
			
		||||
        this.items = items;
 | 
			
		||||
        this.container = container;
 | 
			
		||||
 | 
			
		||||
        this.domElement = $(paletteTemplate);
 | 
			
		||||
        this.itemElements = {
 | 
			
		||||
            nullOption: $('.l-option-row .s-palette-item', this.domElement)
 | 
			
		||||
        };
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['change'];
 | 
			
		||||
        this.value = this.items[0];
 | 
			
		||||
        this.nullOption = ' ';
 | 
			
		||||
 | 
			
		||||
        this.hideMenu = this.hideMenu.bind(this);
 | 
			
		||||
 | 
			
		||||
        self.domElement.addClass(this.cssClass);
 | 
			
		||||
        self.setNullOption(this.nullOption);
 | 
			
		||||
 | 
			
		||||
        $('.l-palette-row', self.domElement).after('<div class = "l-palette-row"> </div>');
 | 
			
		||||
        self.items.forEach(function (item) {
 | 
			
		||||
            var itemElement = $('<div class = "l-palette-item s-palette-item"' +
 | 
			
		||||
                                ' data-item = ' + item + '> </div>');
 | 
			
		||||
            $('.l-palette-row:last-of-type', self.domElement).append(itemElement);
 | 
			
		||||
            self.itemElements[item] = itemElement;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $('.menu', self.domElement).hide();
 | 
			
		||||
 | 
			
		||||
        $(document).on('click', this.hideMenu);
 | 
			
		||||
        $('.l-click-area', self.domElement).on('click', function (event) {
 | 
			
		||||
            event.stopPropagation();
 | 
			
		||||
            $('.menu', self.container).hide();
 | 
			
		||||
            $('.menu', self.domElement).show();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for selection of an individual palette item. Sets the
 | 
			
		||||
         * currently selected element to be the one associated with that item's data
 | 
			
		||||
         * @param {Event} event the click event that initiated this callback
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function handleItemClick(event) {
 | 
			
		||||
            var elem = event.currentTarget,
 | 
			
		||||
                item = elem.dataset.item;
 | 
			
		||||
            self.set(item);
 | 
			
		||||
            $('.menu', self.domElement).hide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('.s-palette-item', self.domElement).on('click', handleItemClick);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the DOM element representing this palette in the view
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.getDOM = function () {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clean up any event listeners registered to DOM elements external to the widget
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.destroy = function () {
 | 
			
		||||
        $(document).off('click', this.hideMenu);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Palette.prototype.hideMenu = function () {
 | 
			
		||||
        $('.menu', this.domElement).hide();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this palette: supported callback is change
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error('Unsupported event type: ' + event);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the currently selected value of this palette
 | 
			
		||||
     * @return {string} The selected value
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.getCurrent = function () {
 | 
			
		||||
        return this.value;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the selected value of this palette; if the item doesn't exist in the
 | 
			
		||||
     * palette's data model, the selected value will not change. Invokes any
 | 
			
		||||
     * change callbacks associated with this palette.
 | 
			
		||||
     * @param {string} item The key of the item to set as selected
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.set = function (item) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
        if (this.items.includes(item) || item === this.nullOption) {
 | 
			
		||||
            this.value = item;
 | 
			
		||||
            if (item === this.nullOption) {
 | 
			
		||||
                this.updateSelected('nullOption');
 | 
			
		||||
            } else {
 | 
			
		||||
                this.updateSelected(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.eventEmitter.emit('change', self.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the view assoicated with the currently selected item
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.updateSelected = function (item) {
 | 
			
		||||
        $('.s-palette-item', this.domElement).removeClass('selected');
 | 
			
		||||
        this.itemElements[item].addClass('selected');
 | 
			
		||||
        if (item === 'nullOption') {
 | 
			
		||||
            $('.t-swatch', this.domElement).addClass('no-selection');
 | 
			
		||||
        } else {
 | 
			
		||||
            $('.t-swatch', this.domElement).removeClass('no-selection');
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * set the property to be used for the 'no selection' item. If not set, this
 | 
			
		||||
     * defaults to a single space
 | 
			
		||||
     * @param {string} item The key to use as the 'no selection' item
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.setNullOption = function (item) {
 | 
			
		||||
        this.nullOption = item;
 | 
			
		||||
        this.itemElements.nullOption.data('item', item);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hides the 'no selection' option to be hidden in the view if it doesn't apply
 | 
			
		||||
     */
 | 
			
		||||
    Palette.prototype.toggleNullOption = function () {
 | 
			
		||||
        $('.l-option-row', this.domElement).toggle();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Palette;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										144
									
								
								src/plugins/summaryWidget/src/input/Select.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/plugins/summaryWidget/src/input/Select.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!../../res/input/selectTemplate.html',
 | 
			
		||||
    'EventEmitter',
 | 
			
		||||
    'zepto'
 | 
			
		||||
], function (
 | 
			
		||||
    selectTemplate,
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    $
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wraps an HTML select element, and provides methods for dynamically altering
 | 
			
		||||
     * its composition from the data model
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    function Select() {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        this.domElement = $(selectTemplate);
 | 
			
		||||
        this.options = [];
 | 
			
		||||
        this.eventEmitter = new EventEmitter();
 | 
			
		||||
        this.supportedCallbacks = ['change'];
 | 
			
		||||
 | 
			
		||||
        this.populate();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Event handler for the wrapped select element. Also invokes any change
 | 
			
		||||
         * callbacks registered with this select with the new value
 | 
			
		||||
         * @param {Event} event The change event that triggered this callback
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        function onChange(event) {
 | 
			
		||||
            var elem = event.target,
 | 
			
		||||
                value = self.options[$(elem).prop('selectedIndex')];
 | 
			
		||||
 | 
			
		||||
            self.eventEmitter.emit('change', value[0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('select', this.domElement).on('change', onChange);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the DOM element representing this Select in the view
 | 
			
		||||
     * @return {Element}
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.getDOM = function () {
 | 
			
		||||
        return this.domElement;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback with this select: supported callback is change
 | 
			
		||||
     * @param {string} event The key for the event to listen to
 | 
			
		||||
     * @param {function} callback The function that this rule will envoke on this event
 | 
			
		||||
     * @param {Object} context A reference to a scope to use as the context for
 | 
			
		||||
     *                         context for the callback function
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.on = function (event, callback, context) {
 | 
			
		||||
        if (this.supportedCallbacks.includes(event)) {
 | 
			
		||||
            this.eventEmitter.on(event, callback, context || this);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error('Unsupported event type' + event);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the select element in the view from the current state of the data
 | 
			
		||||
     * model
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.populate = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            selectedIndex = 0;
 | 
			
		||||
 | 
			
		||||
        selectedIndex = $('select', this.domElement).prop('selectedIndex');
 | 
			
		||||
        $('option', this.domElement).remove();
 | 
			
		||||
 | 
			
		||||
        self.options.forEach(function (option, index) {
 | 
			
		||||
            $('select', self.domElement)
 | 
			
		||||
                .append('<option value = "' + option[0] + '"' + ' >' +
 | 
			
		||||
                        option[1] + '</option>');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $('select', this.domElement).prop('selectedIndex', selectedIndex);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a single option to this select
 | 
			
		||||
     * @param {string} value The value for the new option
 | 
			
		||||
     * @param {string} label The human-readable text for the new option
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.addOption = function (value, label) {
 | 
			
		||||
        this.options.push([value, label]);
 | 
			
		||||
        this.populate();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the available options for this select. Replaces any existing options
 | 
			
		||||
     * @param {string[][]} options An array of [value, label] pairs to display
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.setOptions = function (options) {
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        this.populate();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the currently selected element an invokes any registered change
 | 
			
		||||
     * callbacks with the new value. If the value doesn't exist in this select's
 | 
			
		||||
     * model, its state will not change.
 | 
			
		||||
     * @param {string} value The value to set as the selected option
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.setSelected = function (value) {
 | 
			
		||||
        var selectedIndex = 0,
 | 
			
		||||
            selectedOption;
 | 
			
		||||
 | 
			
		||||
        this.options.forEach (function (option, index) {
 | 
			
		||||
            if (option[0] === value) {
 | 
			
		||||
                selectedIndex = index;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        $('select', this.domElement).prop('selectedIndex', selectedIndex);
 | 
			
		||||
 | 
			
		||||
        selectedOption = this.options[selectedIndex];
 | 
			
		||||
        this.eventEmitter.emit('change', selectedOption[0]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the value of the currently selected item
 | 
			
		||||
     * @return {string}
 | 
			
		||||
     */
 | 
			
		||||
    Select.prototype.getSelected = function () {
 | 
			
		||||
        return $('select', this.domElement).prop('value');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Select.prototype.hide = function () {
 | 
			
		||||
        $(this.domElement).addClass('hidden');
 | 
			
		||||
        $('.equal-to').addClass('hidden');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Select.prototype.show = function () {
 | 
			
		||||
        $(this.domElement).removeClass('hidden');
 | 
			
		||||
        $('.equal-to').removeClass('hidden');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Select;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										336
									
								
								src/plugins/summaryWidget/test/ConditionEvaluatorSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/plugins/summaryWidget/test/ConditionEvaluatorSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,336 @@
 | 
			
		||||
define(['../src/ConditionEvaluator'], function (ConditionEvaluator) {
 | 
			
		||||
    describe('A Summary Widget Rule Evaluator', function () {
 | 
			
		||||
        var evaluator,
 | 
			
		||||
            testEvaluator,
 | 
			
		||||
            testOperation,
 | 
			
		||||
            mockCache,
 | 
			
		||||
            mockTestCache,
 | 
			
		||||
            mockComposition,
 | 
			
		||||
            mockConditions,
 | 
			
		||||
            mockConditionsEmpty,
 | 
			
		||||
            mockConditionsUndefined,
 | 
			
		||||
            mockConditionsAnyTrue,
 | 
			
		||||
            mockConditionsAllTrue,
 | 
			
		||||
            mockConditionsAnyFalse,
 | 
			
		||||
            mockConditionsAllFalse,
 | 
			
		||||
            mockOperations;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockCache = {
 | 
			
		||||
                a: {
 | 
			
		||||
                    alpha: 3,
 | 
			
		||||
                    beta: 9,
 | 
			
		||||
                    gamma: 'Testing 1 2 3'
 | 
			
		||||
                },
 | 
			
		||||
                b: {
 | 
			
		||||
                    alpha: 44,
 | 
			
		||||
                    beta: 23,
 | 
			
		||||
                    gamma: 'Hello World'
 | 
			
		||||
                },
 | 
			
		||||
                c: {
 | 
			
		||||
                    foo: 'bar',
 | 
			
		||||
                    iAm: 'The Walrus',
 | 
			
		||||
                    creature: {
 | 
			
		||||
                        type: 'Centaur'
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockTestCache = {
 | 
			
		||||
                a: {
 | 
			
		||||
                    alpha: 1,
 | 
			
		||||
                    beta: 1,
 | 
			
		||||
                    gamma: 'Testing 4 5 6'
 | 
			
		||||
                },
 | 
			
		||||
                b: {
 | 
			
		||||
                    alpha: 2,
 | 
			
		||||
                    beta: 2,
 | 
			
		||||
                    gamma: 'Goodbye world'
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockComposition = {
 | 
			
		||||
                a: {},
 | 
			
		||||
                b: {},
 | 
			
		||||
                c: {}
 | 
			
		||||
            };
 | 
			
		||||
            mockConditions = [{
 | 
			
		||||
                object: 'a',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'greaterThan',
 | 
			
		||||
                values: [2]
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'b',
 | 
			
		||||
                key: 'gamma',
 | 
			
		||||
                operation: 'lessThan',
 | 
			
		||||
                values: [5]
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsEmpty = [{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsUndefined = [{
 | 
			
		||||
                object: 'No Such Object',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'a',
 | 
			
		||||
                key: 'No Such Key',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'a',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'No Such Operation',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'all',
 | 
			
		||||
                key: 'Nonexistent Field',
 | 
			
		||||
                operation: 'Random Operation',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'any',
 | 
			
		||||
                key: 'Nonexistent Field',
 | 
			
		||||
                operation: 'Whatever Operation',
 | 
			
		||||
                values: []
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsAnyTrue = [{
 | 
			
		||||
                object: 'any',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'greaterThan',
 | 
			
		||||
                values: [5]
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsAnyFalse = [{
 | 
			
		||||
                object: 'any',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'greaterThan',
 | 
			
		||||
                values: [1000]
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsAllFalse = [{
 | 
			
		||||
                object: 'all',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'greaterThan',
 | 
			
		||||
                values: [5]
 | 
			
		||||
            }];
 | 
			
		||||
            mockConditionsAllTrue = [{
 | 
			
		||||
                object: 'all',
 | 
			
		||||
                key: 'alpha',
 | 
			
		||||
                operation: 'greaterThan',
 | 
			
		||||
                values: [0]
 | 
			
		||||
            }];
 | 
			
		||||
            mockOperations = {
 | 
			
		||||
                greaterThan: {
 | 
			
		||||
                    operation: function (input) {
 | 
			
		||||
                        return input[0] > input[1];
 | 
			
		||||
                    },
 | 
			
		||||
                    text: 'is greater than',
 | 
			
		||||
                    appliesTo: ['number'],
 | 
			
		||||
                    inputCount: 1,
 | 
			
		||||
                    getDescription: function (values) {
 | 
			
		||||
                        return ' > ' + values [0];
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                lessThan: {
 | 
			
		||||
                    operation: function (input) {
 | 
			
		||||
                        return input[0] < input[1];
 | 
			
		||||
                    },
 | 
			
		||||
                    text: 'is less than',
 | 
			
		||||
                    appliesTo: ['number'],
 | 
			
		||||
                    inputCount: 1
 | 
			
		||||
                },
 | 
			
		||||
                textContains: {
 | 
			
		||||
                    operation: function (input) {
 | 
			
		||||
                        return input[0] && input[1] && input[0].includes(input[1]);
 | 
			
		||||
                    },
 | 
			
		||||
                    text: 'text contains',
 | 
			
		||||
                    appliesTo: ['string'],
 | 
			
		||||
                    inputCount: 1
 | 
			
		||||
                },
 | 
			
		||||
                textIsExactly: {
 | 
			
		||||
                    operation: function (input) {
 | 
			
		||||
                        return input[0] === input[1];
 | 
			
		||||
                    },
 | 
			
		||||
                    text: 'text is exactly',
 | 
			
		||||
                    appliesTo: ['string'],
 | 
			
		||||
                    inputCount: 1
 | 
			
		||||
                },
 | 
			
		||||
                isHalfHorse: {
 | 
			
		||||
                    operation: function (input) {
 | 
			
		||||
                        return input[0].type === 'Centaur';
 | 
			
		||||
                    },
 | 
			
		||||
                    text: 'is Half Horse',
 | 
			
		||||
                    appliesTo: ['mythicalCreature'],
 | 
			
		||||
                    inputCount: 0,
 | 
			
		||||
                    getDescription: function () {
 | 
			
		||||
                        return 'is half horse';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            evaluator = new ConditionEvaluator(mockCache, mockComposition);
 | 
			
		||||
            testEvaluator = new ConditionEvaluator(mockCache, mockComposition);
 | 
			
		||||
            evaluator.operations = mockOperations;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('evaluates a condition when it has no configuration', function () {
 | 
			
		||||
            expect(evaluator.execute(mockConditionsEmpty, 'any')).toEqual(false);
 | 
			
		||||
            expect(evaluator.execute(mockConditionsEmpty, 'all')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('correctly evaluates a set of conditions', function () {
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('correctly evaluates conditions involving "any telemetry"', function () {
 | 
			
		||||
            expect(evaluator.execute(mockConditionsAnyTrue, 'any')).toEqual(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditionsAnyFalse, 'any')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('correctly evaluates conditions involving "all telemetry"', function () {
 | 
			
		||||
            expect(evaluator.execute(mockConditionsAllTrue, 'any')).toEqual(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditionsAllFalse, 'any')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('handles malformed conditions gracefully', function () {
 | 
			
		||||
            //if no conditions are fully defined, should return false for any mode
 | 
			
		||||
            expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(false);
 | 
			
		||||
            expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(false);
 | 
			
		||||
            //these conditions are true: evaluator should ignore undefined conditions,
 | 
			
		||||
            //and evaluate the rule as true
 | 
			
		||||
            mockConditionsUndefined.push({
 | 
			
		||||
                object: 'a',
 | 
			
		||||
                key: 'gamma',
 | 
			
		||||
                operation: 'textContains',
 | 
			
		||||
                values: ['Testing']
 | 
			
		||||
            });
 | 
			
		||||
            expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(true);
 | 
			
		||||
            mockConditionsUndefined.push({
 | 
			
		||||
                object: 'c',
 | 
			
		||||
                key: 'iAm',
 | 
			
		||||
                operation: 'textContains',
 | 
			
		||||
                values: ['Walrus']
 | 
			
		||||
            });
 | 
			
		||||
            expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets the keys for possible operations', function () {
 | 
			
		||||
            expect(evaluator.getOperationKeys()).toEqual(
 | 
			
		||||
              ['greaterThan', 'lessThan', 'textContains', 'textIsExactly', 'isHalfHorse']
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets output text for a given operation', function () {
 | 
			
		||||
            expect(evaluator.getOperationText('isHalfHorse')).toEqual('is Half Horse');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('correctly returns whether an operation applies to a given type', function () {
 | 
			
		||||
            expect(evaluator.operationAppliesTo('isHalfHorse', 'mythicalCreature')).toEqual(true);
 | 
			
		||||
            expect(evaluator.operationAppliesTo('isHalfHorse', 'spaceJunk')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('returns the HTML input type associated with a given data type', function () {
 | 
			
		||||
            expect(evaluator.getInputTypeById('string')).toEqual('text');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets the number of inputs required for a given operation', function () {
 | 
			
		||||
            expect(evaluator.getInputCount('isHalfHorse')).toEqual(0);
 | 
			
		||||
            expect(evaluator.getInputCount('greaterThan')).toEqual(1);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets a human-readable description of a condition', function () {
 | 
			
		||||
            expect(evaluator.getOperationDescription('isHalfHorse')).toEqual('is half horse');
 | 
			
		||||
            expect(evaluator.getOperationDescription('greaterThan', [1])).toEqual(' > 1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows setting a substitute cache for testing purposes, and toggling its use', function () {
 | 
			
		||||
            evaluator.setTestDataCache(mockTestCache);
 | 
			
		||||
            evaluator.useTestData(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'any')).toEqual(false);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
 | 
			
		||||
            mockConditions.push({
 | 
			
		||||
                object: 'a',
 | 
			
		||||
                key: 'gamma',
 | 
			
		||||
                operation: 'textContains',
 | 
			
		||||
                values: ['4 5 6']
 | 
			
		||||
            });
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
 | 
			
		||||
            mockConditions.pop();
 | 
			
		||||
            evaluator.useTestData(false);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
 | 
			
		||||
            expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('supports all required operations', function () {
 | 
			
		||||
            //equal to
 | 
			
		||||
            testOperation = testEvaluator.operations.equalTo.operation;
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(true);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(false);
 | 
			
		||||
            //not equal to
 | 
			
		||||
            testOperation = testEvaluator.operations.notEqualTo.operation;
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(false);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(true);
 | 
			
		||||
            //greater than
 | 
			
		||||
            testOperation = testEvaluator.operations.greaterThan.operation;
 | 
			
		||||
            expect(testOperation([100, 33])).toEqual(true);
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(false);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(false);
 | 
			
		||||
            //less than
 | 
			
		||||
            testOperation = testEvaluator.operations.lessThan.operation;
 | 
			
		||||
            expect(testOperation([100, 33])).toEqual(false);
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(false);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(true);
 | 
			
		||||
            //greater than or equal to
 | 
			
		||||
            testOperation = testEvaluator.operations.greaterThanOrEq.operation;
 | 
			
		||||
            expect(testOperation([100, 33])).toEqual(true);
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(true);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(false);
 | 
			
		||||
            //less than or equal to
 | 
			
		||||
            testOperation = testEvaluator.operations.lessThanOrEq.operation;
 | 
			
		||||
            expect(testOperation([100, 33])).toEqual(false);
 | 
			
		||||
            expect(testOperation([33, 33])).toEqual(true);
 | 
			
		||||
            expect(testOperation([55, 147])).toEqual(true);
 | 
			
		||||
            //between
 | 
			
		||||
            testOperation = testEvaluator.operations.between.operation;
 | 
			
		||||
            expect(testOperation([100, 33, 66])).toEqual(false);
 | 
			
		||||
            expect(testOperation([1, 33, 66])).toEqual(false);
 | 
			
		||||
            expect(testOperation([45, 33, 66])).toEqual(true);
 | 
			
		||||
            //not between
 | 
			
		||||
            testOperation = testEvaluator.operations.notBetween.operation;
 | 
			
		||||
            expect(testOperation([100, 33, 66])).toEqual(true);
 | 
			
		||||
            expect(testOperation([1, 33, 66])).toEqual(true);
 | 
			
		||||
            expect(testOperation([45, 33, 66])).toEqual(false);
 | 
			
		||||
            //text contains
 | 
			
		||||
            testOperation = testEvaluator.operations.textContains.operation;
 | 
			
		||||
            expect(testOperation(['Testing', 'tin'])).toEqual(true);
 | 
			
		||||
            expect(testOperation(['Testing', 'bind'])).toEqual(false);
 | 
			
		||||
            //text does not contain
 | 
			
		||||
            testOperation = testEvaluator.operations.textDoesNotContain.operation;
 | 
			
		||||
            expect(testOperation(['Testing', 'tin'])).toEqual(false);
 | 
			
		||||
            expect(testOperation(['Testing', 'bind'])).toEqual(true);
 | 
			
		||||
            //text starts with
 | 
			
		||||
            testOperation = testEvaluator.operations.textStartsWith.operation;
 | 
			
		||||
            expect(testOperation(['Testing', 'Tes'])).toEqual(true);
 | 
			
		||||
            expect(testOperation(['Testing', 'ting'])).toEqual(false);
 | 
			
		||||
            //text ends with
 | 
			
		||||
            testOperation = testEvaluator.operations.textEndsWith.operation;
 | 
			
		||||
            expect(testOperation(['Testing', 'Tes'])).toEqual(false);
 | 
			
		||||
            expect(testOperation(['Testing', 'ting'])).toEqual(true);
 | 
			
		||||
            //text is exactly
 | 
			
		||||
            testOperation = testEvaluator.operations.textIsExactly.operation;
 | 
			
		||||
            expect(testOperation(['Testing', 'Testing'])).toEqual(true);
 | 
			
		||||
            expect(testOperation(['Testing', 'Test'])).toEqual(false);
 | 
			
		||||
            //undefined
 | 
			
		||||
            testOperation = testEvaluator.operations.isUndefined.operation;
 | 
			
		||||
            expect(testOperation([1])).toEqual(false);
 | 
			
		||||
            expect(testOperation([])).toEqual(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can produce a description for all supported operations', function () {
 | 
			
		||||
            testEvaluator.getOperationKeys().forEach(function (key) {
 | 
			
		||||
                expect(testEvaluator.getOperationDescription(key, [])).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										372
									
								
								src/plugins/summaryWidget/test/ConditionManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								src/plugins/summaryWidget/test/ConditionManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,372 @@
 | 
			
		||||
define(['../src/ConditionManager'], function (ConditionManager) {
 | 
			
		||||
    describe('A Summary Widget Condition Manager', function () {
 | 
			
		||||
        var conditionManager,
 | 
			
		||||
            mockDomainObject,
 | 
			
		||||
            mockCompObject1,
 | 
			
		||||
            mockCompObject2,
 | 
			
		||||
            mockCompObject3,
 | 
			
		||||
            mockMetadata,
 | 
			
		||||
            mockTelemetryCallbacks,
 | 
			
		||||
            mockEventCallbacks,
 | 
			
		||||
            unsubscribeSpies,
 | 
			
		||||
            unregisterSpies,
 | 
			
		||||
            mockMetadataManagers,
 | 
			
		||||
            mockComposition,
 | 
			
		||||
            mockOpenMCT,
 | 
			
		||||
            mockTelemetryAPI,
 | 
			
		||||
            addCallbackSpy,
 | 
			
		||||
            loadCallbackSpy,
 | 
			
		||||
            removeCallbackSpy,
 | 
			
		||||
            telemetryCallbackSpy,
 | 
			
		||||
            metadataCallbackSpy,
 | 
			
		||||
            mockTelemetryValues,
 | 
			
		||||
            mockTelemetryValues2,
 | 
			
		||||
            mockConditionEvaluator;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockDomainObject = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'testKey'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Test Object',
 | 
			
		||||
                composition: [{
 | 
			
		||||
                    mockCompObject1: {
 | 
			
		||||
                        key: 'mockCompObject1'
 | 
			
		||||
                    },
 | 
			
		||||
                    mockCompObject2 : {
 | 
			
		||||
                        key: 'mockCompObject2'
 | 
			
		||||
                    }
 | 
			
		||||
                }],
 | 
			
		||||
                configuration: {}
 | 
			
		||||
            };
 | 
			
		||||
            mockCompObject1 = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'mockCompObject1'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 1'
 | 
			
		||||
            };
 | 
			
		||||
            mockCompObject2 = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'mockCompObject2'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 2'
 | 
			
		||||
            };
 | 
			
		||||
            mockCompObject3 = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'mockCompObject3'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 3'
 | 
			
		||||
            };
 | 
			
		||||
            mockMetadata = {
 | 
			
		||||
                mockCompObject1: {
 | 
			
		||||
                    property1: {
 | 
			
		||||
                        key: 'property1',
 | 
			
		||||
                        name: 'Property 1'
 | 
			
		||||
                    },
 | 
			
		||||
                    property2: {
 | 
			
		||||
                        key: 'property2',
 | 
			
		||||
                        name: 'Property 2'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject2: {
 | 
			
		||||
                    property3: {
 | 
			
		||||
                        key: 'property3',
 | 
			
		||||
                        name: 'Property 3'
 | 
			
		||||
                    },
 | 
			
		||||
                    property4: {
 | 
			
		||||
                        key: 'property4',
 | 
			
		||||
                        name: 'Property 4'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject3: {
 | 
			
		||||
                    property1: {
 | 
			
		||||
                        key: 'property1',
 | 
			
		||||
                        name: 'Property 1'
 | 
			
		||||
                    },
 | 
			
		||||
                    property2: {
 | 
			
		||||
                        key: 'property2',
 | 
			
		||||
                        name: 'Property 2'
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockTelemetryCallbacks = {};
 | 
			
		||||
            mockEventCallbacks = {};
 | 
			
		||||
            unsubscribeSpies = jasmine.createSpyObj('mockUnsubscribeFunction', [
 | 
			
		||||
                'mockCompObject1',
 | 
			
		||||
                'mockCompObject2',
 | 
			
		||||
                'mockCompObject3'
 | 
			
		||||
            ]);
 | 
			
		||||
            unregisterSpies = jasmine.createSpyObj('mockUnregisterFunctions', [
 | 
			
		||||
                'load',
 | 
			
		||||
                'remove',
 | 
			
		||||
                'add'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTelemetryValues = {
 | 
			
		||||
                mockCompObject1: {
 | 
			
		||||
                    property1: 'Its a string',
 | 
			
		||||
                    property2: 42
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject2: {
 | 
			
		||||
                    property3: 'Execute order:',
 | 
			
		||||
                    property4: 66
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject3: {
 | 
			
		||||
                    property1: 'Testing 1 2 3',
 | 
			
		||||
                    property2: 9000
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockTelemetryValues2 = {
 | 
			
		||||
                mockCompObject1: {
 | 
			
		||||
                    property1: 'Its a different string',
 | 
			
		||||
                    property2: 44
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject2: {
 | 
			
		||||
                    property3: 'Execute catch:',
 | 
			
		||||
                    property4: 22
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject3: {
 | 
			
		||||
                    property1: 'Walrus',
 | 
			
		||||
                    property2: 22
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockMetadataManagers = {
 | 
			
		||||
                mockCompObject1: {
 | 
			
		||||
                    values: jasmine.createSpy('metadataManager').andReturn(
 | 
			
		||||
                        Object.values(mockMetadata.mockCompObject1)
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject2: {
 | 
			
		||||
                    values: jasmine.createSpy('metadataManager').andReturn(
 | 
			
		||||
                        Object.values(mockMetadata.mockCompObject2)
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
                mockCompObject3: {
 | 
			
		||||
                    values: jasmine.createSpy('metadataManager').andReturn(
 | 
			
		||||
                        Object.values(mockMetadata.mockCompObject2)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockComposition = jasmine.createSpyObj('composition', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'off',
 | 
			
		||||
                'load',
 | 
			
		||||
                'triggerCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockComposition.on.andCallFake(function (event, callback, context) {
 | 
			
		||||
                mockEventCallbacks[event] = callback.bind(context);
 | 
			
		||||
            });
 | 
			
		||||
            mockComposition.off.andCallFake(function (event) {
 | 
			
		||||
                unregisterSpies[event]();
 | 
			
		||||
            });
 | 
			
		||||
            mockComposition.load.andCallFake(function () {
 | 
			
		||||
                mockEventCallbacks.add(mockCompObject1);
 | 
			
		||||
                mockEventCallbacks.add(mockCompObject2);
 | 
			
		||||
                mockEventCallbacks.load();
 | 
			
		||||
            });
 | 
			
		||||
            mockComposition.triggerCallback.andCallFake(function (event) {
 | 
			
		||||
                if (event === 'add') {
 | 
			
		||||
                    mockEventCallbacks.add(mockCompObject3);
 | 
			
		||||
                } else if (event === 'remove') {
 | 
			
		||||
                    mockEventCallbacks.remove({
 | 
			
		||||
                        key: 'mockCompObject2'
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    mockEventCallbacks[event]();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
 | 
			
		||||
                'request',
 | 
			
		||||
                'canProvideTelemetry',
 | 
			
		||||
                'getMetadata',
 | 
			
		||||
                'subscribe',
 | 
			
		||||
                'triggerTelemetryCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTelemetryAPI.request.andCallFake(function (obj) {
 | 
			
		||||
                return new Promise(function (resolve, reject) {
 | 
			
		||||
                    resolve(mockTelemetryValues[obj.identifer.key]);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            mockTelemetryAPI.canProvideTelemetry.andReturn(true);
 | 
			
		||||
            mockTelemetryAPI.getMetadata.andCallFake(function (obj) {
 | 
			
		||||
                return mockMetadataManagers[obj.identifier.key];
 | 
			
		||||
            });
 | 
			
		||||
            mockTelemetryAPI.subscribe.andCallFake(function (obj, callback) {
 | 
			
		||||
                mockTelemetryCallbacks[obj.identifier.key] = callback;
 | 
			
		||||
                return unsubscribeSpies[obj.identifier.key];
 | 
			
		||||
            });
 | 
			
		||||
            mockTelemetryAPI.triggerTelemetryCallback.andCallFake(function (key) {
 | 
			
		||||
                mockTelemetryCallbacks[key](mockTelemetryValues2[key]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockOpenMCT = {
 | 
			
		||||
                telemetry: mockTelemetryAPI,
 | 
			
		||||
                composition: {}
 | 
			
		||||
            };
 | 
			
		||||
            mockOpenMCT.composition.get = jasmine.createSpy('get').andReturn(mockComposition);
 | 
			
		||||
 | 
			
		||||
            loadCallbackSpy = jasmine.createSpy('loadCallbackSpy');
 | 
			
		||||
            addCallbackSpy = jasmine.createSpy('addCallbackSpy');
 | 
			
		||||
            removeCallbackSpy = jasmine.createSpy('removeCallbackSpy');
 | 
			
		||||
            metadataCallbackSpy = jasmine.createSpy('metadataCallbackSpy');
 | 
			
		||||
            telemetryCallbackSpy = jasmine.createSpy('telemetryCallbackSpy');
 | 
			
		||||
 | 
			
		||||
            conditionManager = new ConditionManager(mockDomainObject, mockOpenMCT);
 | 
			
		||||
            conditionManager.on('load', loadCallbackSpy);
 | 
			
		||||
            conditionManager.on('add', addCallbackSpy);
 | 
			
		||||
            conditionManager.on('remove', removeCallbackSpy);
 | 
			
		||||
            conditionManager.on('metadata', metadataCallbackSpy);
 | 
			
		||||
            conditionManager.on('receiveTelemetry', telemetryCallbackSpy);
 | 
			
		||||
 | 
			
		||||
            mockConditionEvaluator = jasmine.createSpy('mockConditionEvaluator');
 | 
			
		||||
            mockConditionEvaluator.execute = jasmine.createSpy('execute');
 | 
			
		||||
            conditionManager.evaluator = mockConditionEvaluator;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('loads the initial composition and invokes the appropriate handlers', function () {
 | 
			
		||||
            mockComposition.triggerCallback('load');
 | 
			
		||||
            expect(conditionManager.getComposition()).toEqual({
 | 
			
		||||
                mockCompObject1: mockCompObject1,
 | 
			
		||||
                mockCompObject2: mockCompObject2
 | 
			
		||||
            });
 | 
			
		||||
            expect(loadCallbackSpy).toHaveBeenCalled();
 | 
			
		||||
            expect(conditionManager.loadCompleted()).toEqual(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('loads metadata from composition and gets it upon request', function () {
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('mockCompObject1'))
 | 
			
		||||
                .toEqual(mockMetadata.mockCompObject1);
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('mockCompObject2'))
 | 
			
		||||
                .toEqual(mockMetadata.mockCompObject2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('maintains lists of global metadata, and does not duplicate repeated fields', function () {
 | 
			
		||||
            var allKeys = {
 | 
			
		||||
                property1: {
 | 
			
		||||
                    key: 'property1',
 | 
			
		||||
                    name: 'Property 1'
 | 
			
		||||
                },
 | 
			
		||||
                property2: {
 | 
			
		||||
                    key: 'property2',
 | 
			
		||||
                    name: 'Property 2'
 | 
			
		||||
                },
 | 
			
		||||
                property3: {
 | 
			
		||||
                    key: 'property3',
 | 
			
		||||
                    name: 'Property 3'
 | 
			
		||||
                },
 | 
			
		||||
                property4: {
 | 
			
		||||
                    key: 'property4',
 | 
			
		||||
                    name: 'Property 4'
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
 | 
			
		||||
            mockComposition.triggerCallback('add');
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
 | 
			
		||||
            expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('loads and gets telemetry property types', function () {
 | 
			
		||||
            conditionManager.parseAllPropertyTypes().then(function () {
 | 
			
		||||
                expect(conditionManager.getTelemetryPropertyType('mockCompObject1', 'property1'))
 | 
			
		||||
                    .toEqual('string');
 | 
			
		||||
                expect(conditionManager.getTelemetryPropertyType('mockCompObject2', 'property4'))
 | 
			
		||||
                    .toEqual('number');
 | 
			
		||||
                expect(conditionManager.metadataLoadComplete()).toEqual(true);
 | 
			
		||||
                expect(metadataCallbackSpy).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a composition add event and invokes the appropriate handlers', function () {
 | 
			
		||||
            mockComposition.triggerCallback('add');
 | 
			
		||||
            expect(addCallbackSpy).toHaveBeenCalledWith(mockCompObject3);
 | 
			
		||||
            expect(conditionManager.getComposition()).toEqual({
 | 
			
		||||
                mockCompObject1: mockCompObject1,
 | 
			
		||||
                mockCompObject2: mockCompObject2,
 | 
			
		||||
                mockCompObject3: mockCompObject3
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a composition remove event and invokes the appropriate handlers', function () {
 | 
			
		||||
            mockComposition.triggerCallback('remove');
 | 
			
		||||
            expect(removeCallbackSpy).toHaveBeenCalledWith({
 | 
			
		||||
                key: 'mockCompObject2'
 | 
			
		||||
            });
 | 
			
		||||
            expect(unsubscribeSpies.mockCompObject2).toHaveBeenCalled();
 | 
			
		||||
            expect(conditionManager.getComposition()).toEqual({
 | 
			
		||||
                mockCompObject1: mockCompObject1
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('unregisters telemetry subscriptions and composition listeners on destroy', function () {
 | 
			
		||||
            mockComposition.triggerCallback('add');
 | 
			
		||||
            conditionManager.destroy();
 | 
			
		||||
            Object.values(unsubscribeSpies).forEach(function (spy) {
 | 
			
		||||
                expect(spy).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
            Object.values(unregisterSpies).forEach(function (spy) {
 | 
			
		||||
                expect(spy).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates its LAD cache with historial data on load, if available', function () {
 | 
			
		||||
            conditionManager.parseAllPropertyTypes().then(function () {
 | 
			
		||||
                expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual('Its a string');
 | 
			
		||||
                expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(66);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('updates its LAD cache upon recieving telemetry and invokes the appropriate handlers', function () {
 | 
			
		||||
            mockTelemetryAPI.triggerTelemetryCallback('mockCompObject1');
 | 
			
		||||
            expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual('Its a different string');
 | 
			
		||||
            mockTelemetryAPI.triggerTelemetryCallback('mockCompObject2');
 | 
			
		||||
            expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(22);
 | 
			
		||||
            expect(telemetryCallbackSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('evalutes a set of rules and returns the id of the' +
 | 
			
		||||
           'last active rule, or the first if no rules are active', function () {
 | 
			
		||||
            var mockRuleOrder = ['default', 'rule0', 'rule1'],
 | 
			
		||||
                mockRules = {
 | 
			
		||||
                    default: {
 | 
			
		||||
                        getProperty: function () {}
 | 
			
		||||
                    },
 | 
			
		||||
                    rule0: {
 | 
			
		||||
                        getProperty: function () {}
 | 
			
		||||
                    },
 | 
			
		||||
                    rule1: {
 | 
			
		||||
                        getProperty: function () {}
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
            mockConditionEvaluator.execute.andReturn(false);
 | 
			
		||||
            expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('default');
 | 
			
		||||
            mockConditionEvaluator.execute.andReturn(true);
 | 
			
		||||
            expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('rule1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets the human-readable name of a composition object', function () {
 | 
			
		||||
            expect(conditionManager.getObjectName('mockCompObject1')).toEqual('Object 1');
 | 
			
		||||
            expect(conditionManager.getObjectName('all')).toEqual('all Telemetry');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets the human-readable name of a telemetry field', function () {
 | 
			
		||||
            conditionManager.parseAllPropertyTypes().then(function () {
 | 
			
		||||
                expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1'))
 | 
			
		||||
                    .toEqual('Property 1');
 | 
			
		||||
                expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4'))
 | 
			
		||||
                    .toEqual('Property 4');
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets its associated ConditionEvaluator', function () {
 | 
			
		||||
            expect(conditionManager.getEvaluator()).toEqual(mockConditionEvaluator);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows forcing a receive telemetry event', function () {
 | 
			
		||||
            conditionManager.triggerTelemetryCallback();
 | 
			
		||||
            expect(telemetryCallbackSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										157
									
								
								src/plugins/summaryWidget/test/ConditionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/plugins/summaryWidget/test/ConditionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
define(['../src/Condition', 'zepto'], function (Condition, $) {
 | 
			
		||||
    describe('A summary widget condition', function () {
 | 
			
		||||
        var testCondition,
 | 
			
		||||
            mockConfig,
 | 
			
		||||
            mockConditionManager,
 | 
			
		||||
            mockContainer,
 | 
			
		||||
            mockEvaluator,
 | 
			
		||||
            changeSpy,
 | 
			
		||||
            duplicateSpy,
 | 
			
		||||
            removeSpy,
 | 
			
		||||
            generateValuesSpy;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockContainer = $(document.createElement('div'));
 | 
			
		||||
 | 
			
		||||
            mockConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'property1',
 | 
			
		||||
                operation: 'operation1',
 | 
			
		||||
                values: [1, 2, 3]
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockEvaluator = {};
 | 
			
		||||
            mockEvaluator.getInputCount = jasmine.createSpy('inputCount');
 | 
			
		||||
            mockEvaluator.getInputType = jasmine.createSpy('inputType');
 | 
			
		||||
 | 
			
		||||
            mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'getComposition',
 | 
			
		||||
                'loadCompleted',
 | 
			
		||||
                'getEvaluator',
 | 
			
		||||
                'getTelemetryMetadata',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'getObjectName',
 | 
			
		||||
                'getTelemetryPropertyName'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockConditionManager.loadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.getEvaluator.andReturn(mockEvaluator);
 | 
			
		||||
            mockConditionManager.getComposition.andReturn({});
 | 
			
		||||
            mockConditionManager.getTelemetryMetadata.andReturn({});
 | 
			
		||||
            mockConditionManager.getObjectName.andReturn('Object Name');
 | 
			
		||||
            mockConditionManager.getTelemetryPropertyName.andReturn('Property Name');
 | 
			
		||||
 | 
			
		||||
            duplicateSpy = jasmine.createSpy('duplicate');
 | 
			
		||||
            removeSpy = jasmine.createSpy('remove');
 | 
			
		||||
            changeSpy = jasmine.createSpy('change');
 | 
			
		||||
            generateValuesSpy = jasmine.createSpy('generateValueInputs');
 | 
			
		||||
 | 
			
		||||
            testCondition = new Condition(mockConfig, 54, mockConditionManager);
 | 
			
		||||
 | 
			
		||||
            testCondition.on('duplicate', duplicateSpy);
 | 
			
		||||
            testCondition.on('remove', removeSpy);
 | 
			
		||||
            testCondition.on('change', changeSpy);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('exposes a DOM element to represent itself in the view', function () {
 | 
			
		||||
            mockContainer.append(testCondition.getDOM());
 | 
			
		||||
            expect($('.t-condition', mockContainer).get().length).toEqual(1);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its object select', function () {
 | 
			
		||||
            testCondition.selects.object.setSelected('');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: '',
 | 
			
		||||
                property: 'object',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its key select', function () {
 | 
			
		||||
            testCondition.selects.key.setSelected('');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: '',
 | 
			
		||||
                property: 'key',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its operation select', function () {
 | 
			
		||||
            testCondition.generateValueInputs = generateValuesSpy;
 | 
			
		||||
            testCondition.selects.operation.setSelected('');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: '',
 | 
			
		||||
                property: 'operation',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
            expect(generateValuesSpy).toHaveBeenCalledWith('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('generates value inputs of the appropriate type and quantity', function () {
 | 
			
		||||
            mockContainer.append(testCondition.getDOM());
 | 
			
		||||
            mockEvaluator.getInputType.andReturn('number');
 | 
			
		||||
            mockEvaluator.getInputCount.andReturn(3);
 | 
			
		||||
            testCondition.generateValueInputs('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(3);
 | 
			
		||||
            expect($('input', mockContainer).eq(0).prop('valueAsNumber')).toEqual(1);
 | 
			
		||||
            expect($('input', mockContainer).eq(1).prop('valueAsNumber')).toEqual(2);
 | 
			
		||||
            expect($('input', mockContainer).eq(2).prop('valueAsNumber')).toEqual(3);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getInputType.andReturn('text');
 | 
			
		||||
            mockEvaluator.getInputCount.andReturn(2);
 | 
			
		||||
            testCondition.config.values = ['Text I Am', 'Text It Is'];
 | 
			
		||||
            testCondition.generateValueInputs('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(2);
 | 
			
		||||
            expect($('input', mockContainer).eq(0).prop('value')).toEqual('Text I Am');
 | 
			
		||||
            expect($('input', mockContainer).eq(1).prop('value')).toEqual('Text It Is');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('ensures reasonable defaults on values if none are provided', function () {
 | 
			
		||||
            mockContainer.append(testCondition.getDOM());
 | 
			
		||||
            mockEvaluator.getInputType.andReturn('number');
 | 
			
		||||
            mockEvaluator.getInputCount.andReturn(3);
 | 
			
		||||
            testCondition.config.values = [];
 | 
			
		||||
            testCondition.generateValueInputs('');
 | 
			
		||||
            expect($('input', mockContainer).eq(0).prop('valueAsNumber')).toEqual(0);
 | 
			
		||||
            expect($('input', mockContainer).eq(1).prop('valueAsNumber')).toEqual(0);
 | 
			
		||||
            expect($('input', mockContainer).eq(2).prop('valueAsNumber')).toEqual(0);
 | 
			
		||||
            expect(testCondition.config.values).toEqual([0, 0, 0]);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getInputType.andReturn('text');
 | 
			
		||||
            mockEvaluator.getInputCount.andReturn(2);
 | 
			
		||||
            testCondition.config.values = [];
 | 
			
		||||
            testCondition.generateValueInputs('');
 | 
			
		||||
            expect($('input', mockContainer).eq(0).prop('value')).toEqual('');
 | 
			
		||||
            expect($('input', mockContainer).eq(1).prop('value')).toEqual('');
 | 
			
		||||
            expect(testCondition.config.values).toEqual(['', '']);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its value inputs', function () {
 | 
			
		||||
            mockContainer.append(testCondition.getDOM());
 | 
			
		||||
            mockEvaluator.getInputType.andReturn('number');
 | 
			
		||||
            mockEvaluator.getInputCount.andReturn(3);
 | 
			
		||||
            testCondition.generateValueInputs('');
 | 
			
		||||
            $('input', mockContainer).eq(1).prop('value', 9001);
 | 
			
		||||
            $('input', mockContainer).eq(1).trigger('input');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: 9001,
 | 
			
		||||
                property: 'values[1]',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can remove itself from the configuration', function () {
 | 
			
		||||
            testCondition.remove();
 | 
			
		||||
            expect(removeSpy).toHaveBeenCalledWith(54);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can duplicate itself', function () {
 | 
			
		||||
            testCondition.duplicate();
 | 
			
		||||
            expect(duplicateSpy).toHaveBeenCalledWith({
 | 
			
		||||
                sourceCondition: mockConfig,
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										269
									
								
								src/plugins/summaryWidget/test/RuleSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/plugins/summaryWidget/test/RuleSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
			
		||||
define(['../src/Rule', 'zepto'], function (Rule, $) {
 | 
			
		||||
    describe('A Summary Widget Rule', function () {
 | 
			
		||||
        var mockRuleConfig,
 | 
			
		||||
            mockDomainObject,
 | 
			
		||||
            mockOpenMCT,
 | 
			
		||||
            mockConditionManager,
 | 
			
		||||
            mockWidgetDnD,
 | 
			
		||||
            mockEvaluator,
 | 
			
		||||
            mockContainer,
 | 
			
		||||
            testRule,
 | 
			
		||||
            removeSpy,
 | 
			
		||||
            duplicateSpy,
 | 
			
		||||
            changeSpy,
 | 
			
		||||
            conditionChangeSpy;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockRuleConfig = {
 | 
			
		||||
                name: 'Name',
 | 
			
		||||
                id: 'mockRule',
 | 
			
		||||
                icon: 'test-icon-name',
 | 
			
		||||
                style: {
 | 
			
		||||
                    'background-color': '',
 | 
			
		||||
                    'border-color': '',
 | 
			
		||||
                    'color': ''
 | 
			
		||||
                },
 | 
			
		||||
                expanded: true,
 | 
			
		||||
                conditions: [{
 | 
			
		||||
                    object: '',
 | 
			
		||||
                    key: '',
 | 
			
		||||
                    operation: '',
 | 
			
		||||
                    values: []
 | 
			
		||||
                },{
 | 
			
		||||
                    object: 'blah',
 | 
			
		||||
                    key: 'blah',
 | 
			
		||||
                    operation: 'blah',
 | 
			
		||||
                    values: ['blah.', 'blah!', 'blah?']
 | 
			
		||||
                }]
 | 
			
		||||
            };
 | 
			
		||||
            mockDomainObject = {
 | 
			
		||||
                configuration: {
 | 
			
		||||
                    ruleConfigById: {
 | 
			
		||||
                        mockRule: mockRuleConfig,
 | 
			
		||||
                        otherRule: {}
 | 
			
		||||
                    },
 | 
			
		||||
                    ruleOrder: ['default', 'mockRule', 'otherRule']
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockOpenMCT = {};
 | 
			
		||||
            mockOpenMCT.objects = {};
 | 
			
		||||
            mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
 | 
			
		||||
 | 
			
		||||
            mockEvaluator = {};
 | 
			
		||||
            mockEvaluator.getOperationDescription = jasmine.createSpy('evaluator')
 | 
			
		||||
                                                        .andReturn('Operation Description');
 | 
			
		||||
 | 
			
		||||
            mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'getComposition',
 | 
			
		||||
                'loadCompleted',
 | 
			
		||||
                'getEvaluator',
 | 
			
		||||
                'getTelemetryMetadata',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'getObjectName',
 | 
			
		||||
                'getTelemetryPropertyName'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockConditionManager.loadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.getEvaluator.andReturn(mockEvaluator);
 | 
			
		||||
            mockConditionManager.getComposition.andReturn({});
 | 
			
		||||
            mockConditionManager.getTelemetryMetadata.andReturn({});
 | 
			
		||||
            mockConditionManager.getObjectName.andReturn('Object Name');
 | 
			
		||||
            mockConditionManager.getTelemetryPropertyName.andReturn('Property Name');
 | 
			
		||||
 | 
			
		||||
            mockWidgetDnD = jasmine.createSpyObj('dnd', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'setDragImage',
 | 
			
		||||
                'dragStart'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockContainer = $(document.createElement('div'));
 | 
			
		||||
 | 
			
		||||
            removeSpy = jasmine.createSpy('removeCallback');
 | 
			
		||||
            duplicateSpy = jasmine.createSpy('duplicateCallback');
 | 
			
		||||
            changeSpy = jasmine.createSpy('changeCallback');
 | 
			
		||||
            conditionChangeSpy = jasmine.createSpy('conditionChangeCallback');
 | 
			
		||||
 | 
			
		||||
            testRule = new Rule(mockRuleConfig, mockDomainObject, mockOpenMCT, mockConditionManager,
 | 
			
		||||
                                mockWidgetDnD);
 | 
			
		||||
            testRule.on('remove', removeSpy);
 | 
			
		||||
            testRule.on('duplicate', duplicateSpy);
 | 
			
		||||
            testRule.on('change', changeSpy);
 | 
			
		||||
            testRule.on('conditionChange', conditionChangeSpy);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('closes its configuration panel on initial load', function () {
 | 
			
		||||
            expect(testRule.getProperty('expanded')).toEqual(false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets its DOM element', function () {
 | 
			
		||||
            mockContainer.append(testRule.getDOM());
 | 
			
		||||
            expect($('.l-widget-rule', mockContainer).get().length).toBeGreaterThan(0);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets its configuration properties', function () {
 | 
			
		||||
            expect(testRule.getProperty('name')).toEqual('Name');
 | 
			
		||||
            expect(testRule.getProperty('icon')).toEqual('test-icon-name');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can duplicate itself', function () {
 | 
			
		||||
            testRule.duplicate();
 | 
			
		||||
            mockRuleConfig.expanded = true;
 | 
			
		||||
            expect(duplicateSpy).toHaveBeenCalledWith(mockRuleConfig);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can remove itself from the configuration', function () {
 | 
			
		||||
            testRule.remove();
 | 
			
		||||
            expect(removeSpy).toHaveBeenCalled();
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleConfigById.mockRule).not.toBeDefined();
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleOrder).toEqual(['default', 'otherRule']);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('updates its configuration on a condition change and invokes callbacks', function () {
 | 
			
		||||
            testRule.onConditionChange({
 | 
			
		||||
                value: 'newValue',
 | 
			
		||||
                property: 'object',
 | 
			
		||||
                index: 0
 | 
			
		||||
            });
 | 
			
		||||
            expect(testRule.getProperty('conditions')[0].object).toEqual('newValue');
 | 
			
		||||
            expect(conditionChangeSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows initializing a new condition with a default configuration', function () {
 | 
			
		||||
            testRule.initCondition();
 | 
			
		||||
            expect(mockRuleConfig.conditions).toEqual([{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'blah',
 | 
			
		||||
                key: 'blah',
 | 
			
		||||
                operation: 'blah',
 | 
			
		||||
                values: ['blah.', 'blah!', 'blah?']
 | 
			
		||||
            },{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows initializing a new condition from a given configuration', function () {
 | 
			
		||||
            testRule.initCondition({
 | 
			
		||||
                sourceCondition: {
 | 
			
		||||
                    object: 'object1',
 | 
			
		||||
                    key: 'key1',
 | 
			
		||||
                    operation: 'operation1',
 | 
			
		||||
                    values: [1, 2, 3]
 | 
			
		||||
                },
 | 
			
		||||
                index: 0
 | 
			
		||||
            });
 | 
			
		||||
            expect(mockRuleConfig.conditions).toEqual([{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                operation: '',
 | 
			
		||||
                values: []
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'key1',
 | 
			
		||||
                operation: 'operation1',
 | 
			
		||||
                values: [1, 2, 3]
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'blah',
 | 
			
		||||
                key: 'blah',
 | 
			
		||||
                operation: 'blah',
 | 
			
		||||
                values: ['blah.', 'blah!', 'blah?']
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('invokes mutate when updating the domain object', function () {
 | 
			
		||||
            testRule.updateDomainObject();
 | 
			
		||||
            expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('builds condition view from condition configuration', function () {
 | 
			
		||||
            mockContainer.append(testRule.getDOM());
 | 
			
		||||
            expect($('.t-condition', mockContainer).get().length).toEqual(2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to input of style properties, and updates the preview', function () {
 | 
			
		||||
            testRule.colorInputs['background-color'].set('#434343');
 | 
			
		||||
            expect(mockRuleConfig.style['background-color']).toEqual('#434343');
 | 
			
		||||
            testRule.colorInputs['border-color'].set('#666666');
 | 
			
		||||
            expect(mockRuleConfig.style['border-color']).toEqual('#666666');
 | 
			
		||||
            testRule.colorInputs.color.set('#999999');
 | 
			
		||||
            expect(mockRuleConfig.style.color).toEqual('#999999');
 | 
			
		||||
 | 
			
		||||
            expect(testRule.thumbnail.css('background-color')).toEqual('rgb(67, 67, 67)');
 | 
			
		||||
            expect(testRule.thumbnail.css('border-color')).toEqual('rgb(102, 102, 102)');
 | 
			
		||||
            expect(testRule.thumbnail.css('color')).toEqual('rgb(153, 153, 153)');
 | 
			
		||||
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to input for the icon property', function () {
 | 
			
		||||
            testRule.iconInput.set('icon-alert-rect');
 | 
			
		||||
            expect(mockRuleConfig.icon).toEqual('icon-alert-rect');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        test for js condition commented out for v1
 | 
			
		||||
        */
 | 
			
		||||
 | 
			
		||||
        // it('responds to input of text properties', function () {
 | 
			
		||||
        //     var testInputs = ['name', 'label', 'message', 'jsCondition'],
 | 
			
		||||
        //         input;
 | 
			
		||||
 | 
			
		||||
        //     testInputs.forEach(function (key) {
 | 
			
		||||
        //         input = testRule.textInputs[key];
 | 
			
		||||
        //         input.prop('value', 'A new ' + key);
 | 
			
		||||
        //         input.trigger('input');
 | 
			
		||||
        //         expect(mockRuleConfig[key]).toEqual('A new ' + key);
 | 
			
		||||
        //     });
 | 
			
		||||
 | 
			
		||||
        //     expect(changeSpy).toHaveBeenCalled();
 | 
			
		||||
        // });
 | 
			
		||||
 | 
			
		||||
        it('allows input for when the rule triggers', function () {
 | 
			
		||||
            testRule.trigger.prop('value', 'all');
 | 
			
		||||
            testRule.trigger.trigger('change');
 | 
			
		||||
            expect(testRule.config.trigger).toEqual('all');
 | 
			
		||||
            expect(conditionChangeSpy).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('generates a human-readable description from its conditions', function () {
 | 
			
		||||
            testRule.generateDescription();
 | 
			
		||||
            expect(testRule.config.description).toContain(
 | 
			
		||||
                'Object Name\'s Property Name Operation Description'
 | 
			
		||||
            );
 | 
			
		||||
            testRule.config.trigger = 'js';
 | 
			
		||||
            testRule.generateDescription();
 | 
			
		||||
            expect(testRule.config.description).toContain(
 | 
			
		||||
                'when a custom JavaScript condition evaluates to true'
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('initiates a drag event when its grippy is clicked', function () {
 | 
			
		||||
            testRule.grippy.trigger('mousedown');
 | 
			
		||||
            expect(mockWidgetDnD.setDragImage).toHaveBeenCalled();
 | 
			
		||||
            expect(mockWidgetDnD.dragStart).toHaveBeenCalledWith('mockRule');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        test for js condition commented out for v1
 | 
			
		||||
        */
 | 
			
		||||
 | 
			
		||||
        it('can remove a condition from its configuration', function () {
 | 
			
		||||
            testRule.removeCondition(0);
 | 
			
		||||
            expect(testRule.config.conditions).toEqual([{
 | 
			
		||||
                object: 'blah',
 | 
			
		||||
                key: 'blah',
 | 
			
		||||
                operation: 'blah',
 | 
			
		||||
                values: ['blah.', 'blah!', 'blah?']
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										167
									
								
								src/plugins/summaryWidget/test/SummaryWidgetSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/plugins/summaryWidget/test/SummaryWidgetSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) {
 | 
			
		||||
    describe('The Summary Widget', function () {
 | 
			
		||||
        var summaryWidget,
 | 
			
		||||
            mockDomainObject,
 | 
			
		||||
            mockOldDomainObject,
 | 
			
		||||
            mockOpenMCT,
 | 
			
		||||
            mockObjectService,
 | 
			
		||||
            mockStatusCapability,
 | 
			
		||||
            mockComposition,
 | 
			
		||||
            mockContainer,
 | 
			
		||||
            listenCallback,
 | 
			
		||||
            listenCallbackSpy;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockDomainObject = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'testKey'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'testName',
 | 
			
		||||
                composition: [],
 | 
			
		||||
                configuration: {}
 | 
			
		||||
            };
 | 
			
		||||
            mockComposition = jasmine.createSpyObj('composition', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'off',
 | 
			
		||||
                'load'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockStatusCapability = jasmine.createSpyObj('statusCapability', [
 | 
			
		||||
                'get',
 | 
			
		||||
                'listen',
 | 
			
		||||
                'triggerCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            listenCallbackSpy = jasmine.createSpy('listenCallbackSpy', function () {});
 | 
			
		||||
            mockStatusCapability.get.andReturn([]);
 | 
			
		||||
            mockStatusCapability.listen.andCallFake(function (callback) {
 | 
			
		||||
                listenCallback = callback;
 | 
			
		||||
                return listenCallbackSpy;
 | 
			
		||||
            });
 | 
			
		||||
            mockStatusCapability.triggerCallback.andCallFake(function () {
 | 
			
		||||
                listenCallback(['editing']);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockOldDomainObject = {};
 | 
			
		||||
            mockOldDomainObject.getCapability = jasmine.createSpy('capability');
 | 
			
		||||
            mockOldDomainObject.getCapability.andReturn(mockStatusCapability);
 | 
			
		||||
 | 
			
		||||
            mockObjectService = {};
 | 
			
		||||
            mockObjectService.getObjects = jasmine.createSpy('objectService');
 | 
			
		||||
            mockObjectService.getObjects.andReturn(new Promise(function (resolve, reject) {
 | 
			
		||||
                resolve({
 | 
			
		||||
                    testKey: mockOldDomainObject
 | 
			
		||||
                });
 | 
			
		||||
            }));
 | 
			
		||||
            mockOpenMCT = jasmine.createSpyObj('openmct', [
 | 
			
		||||
                '$injector',
 | 
			
		||||
                'composition',
 | 
			
		||||
                'objects'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockOpenMCT.$injector.get = jasmine.createSpy('get');
 | 
			
		||||
            mockOpenMCT.$injector.get.andReturn(mockObjectService);
 | 
			
		||||
            mockOpenMCT.composition = jasmine.createSpyObj('composition', [
 | 
			
		||||
                'get',
 | 
			
		||||
                'on'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockOpenMCT.composition.get.andReturn(mockComposition);
 | 
			
		||||
            mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
 | 
			
		||||
            mockOpenMCT.objects.observe = jasmine.createSpy('observe');
 | 
			
		||||
            mockOpenMCT.objects.observe.andReturn(function () {});
 | 
			
		||||
 | 
			
		||||
            summaryWidget = new SummaryWidget(mockDomainObject, mockOpenMCT);
 | 
			
		||||
            mockContainer = document.createElement('div');
 | 
			
		||||
            summaryWidget.show(mockContainer);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('adds its DOM element to the view', function () {
 | 
			
		||||
            expect(mockContainer.getElementsByClassName('w-summary-widget').length).toBeGreaterThan(0);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('initialzes a default rule', function () {
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleConfigById.default).toBeDefined();
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleOrder).toEqual(['default']);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('builds rules and rule placeholders in view from configuration', function () {
 | 
			
		||||
            expect($('.l-widget-rule', summaryWidget.ruleArea).get().length).toEqual(2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows initializing a new rule with a particular identifier', function () {
 | 
			
		||||
            summaryWidget.initRule('rule0', 'Rule');
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleConfigById.rule0).toBeDefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows adding a new rule with a unique identifier to the configuration and view', function () {
 | 
			
		||||
            summaryWidget.addRule();
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleOrder.length).toEqual(2);
 | 
			
		||||
            mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
 | 
			
		||||
                expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
            summaryWidget.addRule();
 | 
			
		||||
            expect(mockDomainObject.configuration.ruleOrder.length).toEqual(3);
 | 
			
		||||
            mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
 | 
			
		||||
                expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
            expect($('.l-widget-rule', summaryWidget.ruleArea).get().length).toEqual(6);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows duplicating a rule from source configuration', function () {
 | 
			
		||||
            var sourceConfig = JSON.parse(JSON.stringify(mockDomainObject.configuration.ruleConfigById.default));
 | 
			
		||||
            summaryWidget.duplicateRule(sourceConfig);
 | 
			
		||||
            expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('does not duplicate an existing rule in the configuration', function () {
 | 
			
		||||
            summaryWidget.initRule('default', 'Default');
 | 
			
		||||
            expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(1);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('uses mutate when updating the domain object only when in edit mode', function () {
 | 
			
		||||
            summaryWidget.editing = true;
 | 
			
		||||
            summaryWidget.updateDomainObject();
 | 
			
		||||
            expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('shows configuration interfaces when in edit mode, and hides them otherwise', function () {
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                summaryWidget.onEdit([]);
 | 
			
		||||
                expect(summaryWidget.editing).toEqual(false);
 | 
			
		||||
                expect(summaryWidget.ruleArea.css('display')).toEqual('none');
 | 
			
		||||
                expect(summaryWidget.testDataArea.css('display')).toEqual('none');
 | 
			
		||||
                expect(summaryWidget.addRuleButton.css('display')).toEqual('none');
 | 
			
		||||
                summaryWidget.onEdit(['editing']);
 | 
			
		||||
                expect(summaryWidget.editing).toEqual(true);
 | 
			
		||||
                expect(summaryWidget.ruleArea.css('display')).not.toEqual('none');
 | 
			
		||||
                expect(summaryWidget.testDataArea.css('display')).not.toEqual('none');
 | 
			
		||||
                expect(summaryWidget.addRuleButton.css('display')).not.toEqual('none');
 | 
			
		||||
            }, 100);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('unregisters any registered listeners on a destroy', function () {
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                summaryWidget.destroy();
 | 
			
		||||
                expect(listenCallbackSpy).toHaveBeenCalled();
 | 
			
		||||
            }, 100);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows reorders of rules', function () {
 | 
			
		||||
            summaryWidget.initRule('rule0');
 | 
			
		||||
            summaryWidget.initRule('rule1');
 | 
			
		||||
            summaryWidget.domainObject.configuration.ruleOrder = ['default', 'rule0', 'rule1'];
 | 
			
		||||
            summaryWidget.reorder({
 | 
			
		||||
                draggingId: 'rule1',
 | 
			
		||||
                dropTarget: 'default'
 | 
			
		||||
            });
 | 
			
		||||
            expect(summaryWidget.domainObject.configuration.ruleOrder).toEqual(['default', 'rule1', 'rule0']);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('adds hyperlink to the widget button and sets newTab preference', function () {
 | 
			
		||||
            summaryWidget.addHyperlink('https://www.nasa.gov', 'newTab');
 | 
			
		||||
 | 
			
		||||
            var widgetButton = $('#widget', mockContainer);
 | 
			
		||||
 | 
			
		||||
            expect(widgetButton.attr('href')).toEqual('https://www.nasa.gov');
 | 
			
		||||
            expect(widgetButton.attr('target')).toEqual('_blank');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										140
									
								
								src/plugins/summaryWidget/test/TestDataItemSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/plugins/summaryWidget/test/TestDataItemSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) {
 | 
			
		||||
    describe('A summary widget test data item', function () {
 | 
			
		||||
        var testDataItem,
 | 
			
		||||
            mockConfig,
 | 
			
		||||
            mockConditionManager,
 | 
			
		||||
            mockContainer,
 | 
			
		||||
            mockEvaluator,
 | 
			
		||||
            changeSpy,
 | 
			
		||||
            duplicateSpy,
 | 
			
		||||
            removeSpy,
 | 
			
		||||
            generateValueSpy;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockContainer = $(document.createElement('div'));
 | 
			
		||||
 | 
			
		||||
            mockConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'property1',
 | 
			
		||||
                value: 1
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockEvaluator = {};
 | 
			
		||||
            mockEvaluator.getInputTypeById = jasmine.createSpy('inputType');
 | 
			
		||||
 | 
			
		||||
            mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'getComposition',
 | 
			
		||||
                'loadCompleted',
 | 
			
		||||
                'getEvaluator',
 | 
			
		||||
                'getTelemetryMetadata',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'getObjectName',
 | 
			
		||||
                'getTelemetryPropertyName',
 | 
			
		||||
                'getTelemetryPropertyType'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockConditionManager.loadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.getEvaluator.andReturn(mockEvaluator);
 | 
			
		||||
            mockConditionManager.getComposition.andReturn({});
 | 
			
		||||
            mockConditionManager.getTelemetryMetadata.andReturn({});
 | 
			
		||||
            mockConditionManager.getObjectName.andReturn('Object Name');
 | 
			
		||||
            mockConditionManager.getTelemetryPropertyName.andReturn('Property Name');
 | 
			
		||||
            mockConditionManager.getTelemetryPropertyType.andReturn('');
 | 
			
		||||
 | 
			
		||||
            duplicateSpy = jasmine.createSpy('duplicate');
 | 
			
		||||
            removeSpy = jasmine.createSpy('remove');
 | 
			
		||||
            changeSpy = jasmine.createSpy('change');
 | 
			
		||||
            generateValueSpy = jasmine.createSpy('generateValueInput');
 | 
			
		||||
 | 
			
		||||
            testDataItem = new TestDataItem(mockConfig, 54, mockConditionManager);
 | 
			
		||||
 | 
			
		||||
            testDataItem.on('duplicate', duplicateSpy);
 | 
			
		||||
            testDataItem.on('remove', removeSpy);
 | 
			
		||||
            testDataItem.on('change', changeSpy);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('exposes a DOM element to represent itself in the view', function () {
 | 
			
		||||
            mockContainer.append(testDataItem.getDOM());
 | 
			
		||||
            expect($('.t-test-data-item', mockContainer).get().length).toEqual(1);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its object select', function () {
 | 
			
		||||
            testDataItem.selects.object.setSelected('');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: '',
 | 
			
		||||
                property: 'object',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its key select', function () {
 | 
			
		||||
            testDataItem.generateValueInput = generateValueSpy;
 | 
			
		||||
            testDataItem.selects.key.setSelected('');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: '',
 | 
			
		||||
                property: 'key',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
            expect(generateValueSpy).toHaveBeenCalledWith('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('generates a value input of the appropriate type', function () {
 | 
			
		||||
            mockContainer.append(testDataItem.getDOM());
 | 
			
		||||
            mockEvaluator.getInputTypeById.andReturn('number');
 | 
			
		||||
            testDataItem.generateValueInput('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(1);
 | 
			
		||||
            expect($('input', mockContainer).prop('valueAsNumber')).toEqual(1);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getInputTypeById.andReturn('text');
 | 
			
		||||
            testDataItem.config.value = 'Text I Am';
 | 
			
		||||
            testDataItem.generateValueInput('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(1);
 | 
			
		||||
            expect($('input', mockContainer).prop('value')).toEqual('Text I Am');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('ensures reasonable defaults on values if none are provided', function () {
 | 
			
		||||
            mockContainer.append(testDataItem.getDOM());
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getInputTypeById.andReturn('number');
 | 
			
		||||
            testDataItem.config.value = undefined;
 | 
			
		||||
            testDataItem.generateValueInput('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(1);
 | 
			
		||||
            expect($('input', mockContainer).prop('valueAsNumber')).toEqual(0);
 | 
			
		||||
            expect(testDataItem.config.value).toEqual(0);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getInputTypeById.andReturn('text');
 | 
			
		||||
            testDataItem.config.value = undefined;
 | 
			
		||||
            testDataItem.generateValueInput('');
 | 
			
		||||
            expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(1);
 | 
			
		||||
            expect($('input', mockContainer).prop('value')).toEqual('');
 | 
			
		||||
            expect(testDataItem.config.value).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('responds to a change in its value inputs', function () {
 | 
			
		||||
            mockContainer.append(testDataItem.getDOM());
 | 
			
		||||
            mockEvaluator.getInputTypeById.andReturn('number');
 | 
			
		||||
            testDataItem.generateValueInput('');
 | 
			
		||||
            $('input', mockContainer).prop('value', 9001);
 | 
			
		||||
            $('input', mockContainer).trigger('input');
 | 
			
		||||
            expect(changeSpy).toHaveBeenCalledWith({
 | 
			
		||||
                value: 9001,
 | 
			
		||||
                property: 'value',
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can remove itself from the configuration', function () {
 | 
			
		||||
            testDataItem.remove();
 | 
			
		||||
            expect(removeSpy).toHaveBeenCalledWith(54);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can duplicate itself', function () {
 | 
			
		||||
            testDataItem.duplicate();
 | 
			
		||||
            expect(duplicateSpy).toHaveBeenCalledWith({
 | 
			
		||||
                sourceItem: mockConfig,
 | 
			
		||||
                index: 54
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										231
									
								
								src/plugins/summaryWidget/test/TestDataManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								src/plugins/summaryWidget/test/TestDataManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
define(['../src/TestDataManager', 'zepto'], function (TestDataManager, $) {
 | 
			
		||||
    describe('A Summary Widget Rule', function () {
 | 
			
		||||
        var mockDomainObject,
 | 
			
		||||
            mockOpenMCT,
 | 
			
		||||
            mockConditionManager,
 | 
			
		||||
            mockEvaluator,
 | 
			
		||||
            mockContainer,
 | 
			
		||||
            mockTelemetryMetadata,
 | 
			
		||||
            testDataManager,
 | 
			
		||||
            mockCompObject1,
 | 
			
		||||
            mockCompObject2;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockDomainObject = {
 | 
			
		||||
                configuration: {
 | 
			
		||||
                    testDataConfig: [{
 | 
			
		||||
                        object: '',
 | 
			
		||||
                        key: '',
 | 
			
		||||
                        value: ''
 | 
			
		||||
                    },{
 | 
			
		||||
                        object: 'object1',
 | 
			
		||||
                        key: 'property1',
 | 
			
		||||
                        value: 66
 | 
			
		||||
                    },{
 | 
			
		||||
                        object: 'object2',
 | 
			
		||||
                        key: 'property4',
 | 
			
		||||
                        value: 'Text It Is'
 | 
			
		||||
                    }]
 | 
			
		||||
                },
 | 
			
		||||
                composition: [{
 | 
			
		||||
                    object1: {
 | 
			
		||||
                        key: 'object1',
 | 
			
		||||
                        name: 'Object 1'
 | 
			
		||||
                    },
 | 
			
		||||
                    object2: {
 | 
			
		||||
                        key: 'object2',
 | 
			
		||||
                        name: 'Object 2'
 | 
			
		||||
                    }
 | 
			
		||||
                }]
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockTelemetryMetadata = {
 | 
			
		||||
                object1: {
 | 
			
		||||
                    property1: {
 | 
			
		||||
                        key: 'property1'
 | 
			
		||||
                    },
 | 
			
		||||
                    property2: {
 | 
			
		||||
                        key: 'property2'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                object2 : {
 | 
			
		||||
                    property3: {
 | 
			
		||||
                        key: 'property3'
 | 
			
		||||
                    },
 | 
			
		||||
                    property4: {
 | 
			
		||||
                        key: 'property4'
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockCompObject1 = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'object1'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 1'
 | 
			
		||||
            };
 | 
			
		||||
            mockCompObject2 = {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'object2'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 2'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockOpenMCT = {};
 | 
			
		||||
            mockOpenMCT.objects = {};
 | 
			
		||||
            mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
 | 
			
		||||
 | 
			
		||||
            mockEvaluator = {};
 | 
			
		||||
            mockEvaluator.setTestDataCache = jasmine.createSpy('testDataCache');
 | 
			
		||||
            mockEvaluator.useTestData = jasmine.createSpy('useTestData');
 | 
			
		||||
 | 
			
		||||
            mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'getComposition',
 | 
			
		||||
                'loadCompleted',
 | 
			
		||||
                'getEvaluator',
 | 
			
		||||
                'getTelemetryMetadata',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'getObjectName',
 | 
			
		||||
                'getTelemetryPropertyName',
 | 
			
		||||
                'triggerTelemetryCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockConditionManager.loadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            mockConditionManager.getEvaluator.andReturn(mockEvaluator);
 | 
			
		||||
            mockConditionManager.getComposition.andReturn({
 | 
			
		||||
                object1: mockCompObject1,
 | 
			
		||||
                object2: mockCompObject2
 | 
			
		||||
            });
 | 
			
		||||
            mockConditionManager.getTelemetryMetadata.andCallFake(function (id) {
 | 
			
		||||
                return mockTelemetryMetadata[id];
 | 
			
		||||
            });
 | 
			
		||||
            mockConditionManager.getObjectName.andReturn('Object Name');
 | 
			
		||||
            mockConditionManager.getTelemetryPropertyName.andReturn('Property Name');
 | 
			
		||||
 | 
			
		||||
            mockContainer = $(document.createElement('div'));
 | 
			
		||||
 | 
			
		||||
            testDataManager = new TestDataManager(mockDomainObject, mockConditionManager, mockOpenMCT);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('closes its configuration panel on initial load', function () {
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('exposes a DOM element to represent itself in the view', function () {
 | 
			
		||||
            mockContainer.append(testDataManager.getDOM());
 | 
			
		||||
            expect($('.t-widget-test-data-content', mockContainer).get().length).toBeGreaterThan(0);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('generates a test cache in the format expected by a condition evaluator', function () {
 | 
			
		||||
            testDataManager.updateTestCache();
 | 
			
		||||
            expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
 | 
			
		||||
                object1: {
 | 
			
		||||
                    property1: 66,
 | 
			
		||||
                    property2: ''
 | 
			
		||||
                },
 | 
			
		||||
                object2: {
 | 
			
		||||
                    property3: '',
 | 
			
		||||
                    property4: 'Text It Is'
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('updates its configuration on a item change and provides an updated' +
 | 
			
		||||
           'cache to the evaluator', function () {
 | 
			
		||||
            testDataManager.onItemChange({
 | 
			
		||||
                value: 26,
 | 
			
		||||
                property: 'value',
 | 
			
		||||
                index: 1
 | 
			
		||||
            });
 | 
			
		||||
            expect(testDataManager.config[1].value).toEqual(26);
 | 
			
		||||
            expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
 | 
			
		||||
                object1: {
 | 
			
		||||
                    property1: 26,
 | 
			
		||||
                    property2: ''
 | 
			
		||||
                },
 | 
			
		||||
                object2: {
 | 
			
		||||
                    property3: '',
 | 
			
		||||
                    property4: 'Text It Is'
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows initializing a new item with a default configuration', function () {
 | 
			
		||||
            testDataManager.initItem();
 | 
			
		||||
            expect(mockDomainObject.configuration.testDataConfig).toEqual([{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                value: ''
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'property1',
 | 
			
		||||
                value: 66
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object2',
 | 
			
		||||
                key: 'property4',
 | 
			
		||||
                value: 'Text It Is'
 | 
			
		||||
            },{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                value: ''
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows initializing a new item from a given configuration', function () {
 | 
			
		||||
            testDataManager.initItem({
 | 
			
		||||
                sourceItem: {
 | 
			
		||||
                    object: 'object2',
 | 
			
		||||
                    key: 'property3',
 | 
			
		||||
                    value: 1
 | 
			
		||||
                },
 | 
			
		||||
                index: 0
 | 
			
		||||
            });
 | 
			
		||||
            expect(mockDomainObject.configuration.testDataConfig).toEqual([{
 | 
			
		||||
                object: '',
 | 
			
		||||
                key: '',
 | 
			
		||||
                value: ''
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object2',
 | 
			
		||||
                key: 'property3',
 | 
			
		||||
                value: 1
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'property1',
 | 
			
		||||
                value: 66
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object2',
 | 
			
		||||
                key: 'property4',
 | 
			
		||||
                value: 'Text It Is'
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('invokes mutate when updating the domain object', function () {
 | 
			
		||||
            testDataManager.updateDomainObject();
 | 
			
		||||
            expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('builds item view from item configuration', function () {
 | 
			
		||||
            mockContainer.append(testDataManager.getDOM());
 | 
			
		||||
            expect($('.t-test-data-item', mockContainer).get().length).toEqual(3);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can remove a item from its configuration', function () {
 | 
			
		||||
            testDataManager.removeItem(0);
 | 
			
		||||
            expect(mockDomainObject.configuration.testDataConfig).toEqual([{
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'property1',
 | 
			
		||||
                value: 66
 | 
			
		||||
            },{
 | 
			
		||||
                object: 'object2',
 | 
			
		||||
                key: 'property4',
 | 
			
		||||
                value: 'Text It Is'
 | 
			
		||||
            }]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('exposes a UI element to toggle test data on and off', function () {
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										0
									
								
								src/plugins/summaryWidget/test/WidgetDnDSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/plugins/summaryWidget/test/WidgetDnDSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										23
									
								
								src/plugins/summaryWidget/test/input/ColorPaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/plugins/summaryWidget/test/input/ColorPaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
define(['../../src/input/ColorPalette'], function (ColorPalette) {
 | 
			
		||||
    describe('An Open MCT color palette', function () {
 | 
			
		||||
        var colorPalette, changeCallback;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            changeCallback = jasmine.createSpy('changeCallback');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows defining a custom color set', function () {
 | 
			
		||||
            colorPalette = new ColorPalette('someClass', 'someContainer', ['color1', 'color2', 'color3']);
 | 
			
		||||
            expect(colorPalette.getCurrent()).toEqual('color1');
 | 
			
		||||
            colorPalette.on('change', changeCallback);
 | 
			
		||||
            colorPalette.set('color2');
 | 
			
		||||
            expect(colorPalette.getCurrent()).toEqual('color2');
 | 
			
		||||
            expect(changeCallback).toHaveBeenCalledWith('color2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('loads with a default color set if one is not provided', function () {
 | 
			
		||||
            colorPalette = new ColorPalette('someClass', 'someContainer');
 | 
			
		||||
            expect(colorPalette.getCurrent()).toBeDefined();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										23
									
								
								src/plugins/summaryWidget/test/input/IconPaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/plugins/summaryWidget/test/input/IconPaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
define(['../../src/input/IconPalette'], function (IconPalette) {
 | 
			
		||||
    describe('An Open MCT icon palette', function () {
 | 
			
		||||
        var iconPalette, changeCallback;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            changeCallback = jasmine.createSpy('changeCallback');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows defining a custom icon set', function () {
 | 
			
		||||
            iconPalette = new IconPalette('','someContainer', ['icon1', 'icon2', 'icon3']);
 | 
			
		||||
            expect(iconPalette.getCurrent()).toEqual('icon1');
 | 
			
		||||
            iconPalette.on('change', changeCallback);
 | 
			
		||||
            iconPalette.set('icon2');
 | 
			
		||||
            expect(iconPalette.getCurrent()).toEqual('icon2');
 | 
			
		||||
            expect(changeCallback).toHaveBeenCalledWith('icon2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('loads with a default icon set if one is not provided', function () {
 | 
			
		||||
            iconPalette = new IconPalette('someClass', 'someContainer');
 | 
			
		||||
            expect(iconPalette.getCurrent()).toBeDefined();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										122
									
								
								src/plugins/summaryWidget/test/input/KeySelectSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/plugins/summaryWidget/test/input/KeySelectSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
define(['../../src/input/KeySelect'], function (KeySelect) {
 | 
			
		||||
    describe('A select for choosing composition object properties', function () {
 | 
			
		||||
        var mockConfig, mockBadConfig, mockManager, keySelect, mockMetadata, mockObjectSelect;
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'a'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockBadConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'someNonexistentKey'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockMetadata = {
 | 
			
		||||
                object1: {
 | 
			
		||||
                    a: {
 | 
			
		||||
                        name: 'A'
 | 
			
		||||
                    },
 | 
			
		||||
                    b: {
 | 
			
		||||
                        name: 'B'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                object2: {
 | 
			
		||||
                    alpha: {
 | 
			
		||||
                        name: 'Alpha'
 | 
			
		||||
                    },
 | 
			
		||||
                    beta: {
 | 
			
		||||
                        name: 'Beta'
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                object3: {
 | 
			
		||||
                    a: {
 | 
			
		||||
                        name: 'A'
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockManager = jasmine.createSpyObj('mockManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'triggerCallback',
 | 
			
		||||
                'getTelemetryMetadata'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockObjectSelect = jasmine.createSpyObj('mockObjectSelect', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'triggerCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockObjectSelect.on.andCallFake(function (event, callback) {
 | 
			
		||||
                this.callbacks = this.callbacks || {};
 | 
			
		||||
                this.callbacks[event] = callback;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockObjectSelect.triggerCallback.andCallFake(function (event, key) {
 | 
			
		||||
                this.callbacks[event](key);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.on.andCallFake(function (event, callback) {
 | 
			
		||||
                this.callbacks = this.callbacks || {};
 | 
			
		||||
                this.callbacks[event] = callback;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.triggerCallback.andCallFake(function (event) {
 | 
			
		||||
                this.callbacks[event]();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.getTelemetryMetadata.andCallFake(function (key) {
 | 
			
		||||
                return mockMetadata[key];
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('waits until the metadata fully loads to populate itself', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with metadata on a metadata load', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            mockManager.triggerCallback('metadata');
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('a');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with metadata if metadata load is already complete', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('a');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('clears its selection state if the property in its config is not in its object', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            keySelect = new KeySelect(mockBadConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates with the appropriate options when its linked object changes', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            mockObjectSelect.triggerCallback('change', 'object2');
 | 
			
		||||
            keySelect.setSelected('alpha');
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('alpha');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('clears its selected state on change if the field is not present in the new object', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            mockObjectSelect.triggerCallback('change', 'object2');
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('maintains its selected state on change if field is present in new object', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
 | 
			
		||||
            mockObjectSelect.triggerCallback('change', 'object3');
 | 
			
		||||
            expect(keySelect.getSelected()).toEqual('a');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										109
									
								
								src/plugins/summaryWidget/test/input/ObjectSelectSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/plugins/summaryWidget/test/input/ObjectSelectSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
define(['../../src/input/ObjectSelect'], function (ObjectSelect) {
 | 
			
		||||
    describe('A select for choosing composition objects', function () {
 | 
			
		||||
        var mockConfig, mockBadConfig, mockManager, objectSelect, mockComposition;
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockConfig = {
 | 
			
		||||
                object: 'key1'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockBadConfig = {
 | 
			
		||||
                object: 'someNonexistentObject'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockComposition = {
 | 
			
		||||
                key1: {
 | 
			
		||||
                    identifier: {
 | 
			
		||||
                        key: 'key1'
 | 
			
		||||
                    },
 | 
			
		||||
                    name: 'Object 1'
 | 
			
		||||
                },
 | 
			
		||||
                key2: {
 | 
			
		||||
                    identifier: {
 | 
			
		||||
                        key: 'key2'
 | 
			
		||||
                    },
 | 
			
		||||
                    name: 'Object 2'
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            mockManager = jasmine.createSpyObj('mockManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'loadCompleted',
 | 
			
		||||
                'triggerCallback',
 | 
			
		||||
                'getComposition'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockManager.on.andCallFake(function (event, callback) {
 | 
			
		||||
                this.callbacks = this.callbacks || {};
 | 
			
		||||
                this.callbacks[event] = callback;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.triggerCallback.andCallFake(function (event, newObj) {
 | 
			
		||||
                if (event === 'add') {
 | 
			
		||||
                    this.callbacks.add(newObj);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.callbacks[event]();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.getComposition.andCallFake(function () {
 | 
			
		||||
                return mockComposition;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows setting special keyword options', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(true);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager, [
 | 
			
		||||
                ['keyword1', 'A special option'],
 | 
			
		||||
                ['keyword2', 'A special option']
 | 
			
		||||
            ]);
 | 
			
		||||
            objectSelect.setSelected('keyword1');
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('keyword1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('waits until the composition fully loads to populate itself', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(false);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager);
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with composition objects on a composition load', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(false);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager);
 | 
			
		||||
            mockManager.triggerCallback('load');
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('key1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with composition objects if load is already complete', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(true);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager);
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('key1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('clears its selection state if the object in its config is not in the composition', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(true);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockBadConfig, mockManager);
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('adds a new option on a composition add', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(true);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager);
 | 
			
		||||
            mockManager.triggerCallback('add', {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'key3'
 | 
			
		||||
                },
 | 
			
		||||
                name: 'Object 3'
 | 
			
		||||
            });
 | 
			
		||||
            objectSelect.setSelected('key3');
 | 
			
		||||
            expect(objectSelect.getSelected()).toEqual('key3');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('removes an option on a composition remove', function () {
 | 
			
		||||
            mockManager.loadCompleted.andReturn(true);
 | 
			
		||||
            objectSelect = new ObjectSelect(mockConfig, mockManager);
 | 
			
		||||
            delete mockComposition.key1;
 | 
			
		||||
            mockManager.triggerCallback('remove');
 | 
			
		||||
            expect(objectSelect.getSelected()).not.toEqual('key1');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										142
									
								
								src/plugins/summaryWidget/test/input/OperationSelectSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/plugins/summaryWidget/test/input/OperationSelectSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
define(['../../src/input/OperationSelect'], function (OperationSelect) {
 | 
			
		||||
    describe('A select for choosing composition object properties', function () {
 | 
			
		||||
        var mockConfig, mockBadConfig, mockManager, operationSelect, mockOperations,
 | 
			
		||||
            mockPropertyTypes, mockKeySelect, mockEvaluator;
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
 | 
			
		||||
            mockConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'a',
 | 
			
		||||
                operation: 'operation1'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockBadConfig = {
 | 
			
		||||
                object: 'object1',
 | 
			
		||||
                key: 'a',
 | 
			
		||||
                operation: 'someNonexistentOperation'
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockOperations = {
 | 
			
		||||
                operation1: {
 | 
			
		||||
                    text: 'An operation',
 | 
			
		||||
                    appliesTo: ['number']
 | 
			
		||||
                },
 | 
			
		||||
                operation2: {
 | 
			
		||||
                    text: 'Another operation',
 | 
			
		||||
                    appliesTo: ['string']
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockPropertyTypes = {
 | 
			
		||||
                object1: {
 | 
			
		||||
                    a: 'number',
 | 
			
		||||
                    b: 'string',
 | 
			
		||||
                    c: 'number'
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            mockManager = jasmine.createSpyObj('mockManager', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'metadataLoadCompleted',
 | 
			
		||||
                'triggerCallback',
 | 
			
		||||
                'getTelemetryPropertyType',
 | 
			
		||||
                'getEvaluator'
 | 
			
		||||
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockKeySelect = jasmine.createSpyObj('mockKeySelect', [
 | 
			
		||||
                'on',
 | 
			
		||||
                'triggerCallback'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator = jasmine.createSpyObj('mockEvaluator', [
 | 
			
		||||
                'getOperationKeys',
 | 
			
		||||
                'operationAppliesTo',
 | 
			
		||||
                'getOperationText'
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getOperationKeys.andReturn(Object.keys(mockOperations));
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.getOperationText.andCallFake(function (key) {
 | 
			
		||||
                return mockOperations[key].text;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockEvaluator.operationAppliesTo.andCallFake(function (operation, type) {
 | 
			
		||||
                return (mockOperations[operation].appliesTo.includes(type));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockKeySelect.on.andCallFake(function (event, callback) {
 | 
			
		||||
                this.callbacks = this.callbacks || {};
 | 
			
		||||
                this.callbacks[event] = callback;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockKeySelect.triggerCallback.andCallFake(function (event, key) {
 | 
			
		||||
                this.callbacks[event](key);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.on.andCallFake(function (event, callback) {
 | 
			
		||||
                this.callbacks = this.callbacks || {};
 | 
			
		||||
                this.callbacks[event] = callback;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.triggerCallback.andCallFake(function (event) {
 | 
			
		||||
                this.callbacks[event]();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.getTelemetryPropertyType.andCallFake(function (object, key) {
 | 
			
		||||
                return mockPropertyTypes[object][key];
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            mockManager.getEvaluator.andReturn(mockEvaluator);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('waits until the metadata fully loads to populate itself', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with operations on a metadata load', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(false);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            mockManager.triggerCallback('metadata');
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('operation1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates itself with operations if metadata load is already complete', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('operation1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('clears its selection state if the operation in its config does not apply', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            operationSelect = new OperationSelect(mockBadConfig, mockKeySelect, mockManager);
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('populates with the appropriate options when its linked key changes', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            mockKeySelect.triggerCallback('change', 'b');
 | 
			
		||||
            operationSelect.setSelected('operation2');
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('operation2');
 | 
			
		||||
            operationSelect.setSelected('operation1');
 | 
			
		||||
            expect(operationSelect.getSelected()).not.toEqual('operation1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('clears its selection on a change if the operation does not apply', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            mockKeySelect.triggerCallback('change', 'b');
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('maintains its selected state on change if the operation does apply', function () {
 | 
			
		||||
            mockManager.metadataLoadCompleted.andReturn(true);
 | 
			
		||||
            operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
 | 
			
		||||
            mockKeySelect.triggerCallback('change', 'c');
 | 
			
		||||
            expect(operationSelect.getSelected()).toEqual('operation1');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										42
									
								
								src/plugins/summaryWidget/test/input/PaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/plugins/summaryWidget/test/input/PaletteSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
define(['../../src/input/Palette'], function (Palette) {
 | 
			
		||||
    describe('A generic Open MCT palette input', function () {
 | 
			
		||||
        var palette, callbackSpy1, callbackSpy2;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            palette = new Palette('someClass', 'someContainer', ['item1', 'item2', 'item3']);
 | 
			
		||||
            callbackSpy1 = jasmine.createSpy('changeCallback1');
 | 
			
		||||
            callbackSpy2 = jasmine.createSpy('changeCallback2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets the current item', function () {
 | 
			
		||||
            expect(palette.getCurrent()).toEqual('item1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows setting the current item', function () {
 | 
			
		||||
            palette.set('item2');
 | 
			
		||||
            expect(palette.getCurrent()).toEqual('item2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                palette.on('change', callbackSpy1);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                palette.on('someUnsupportedEvent', callbackSpy1);
 | 
			
		||||
            }).toThrow();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('injects its callbacks with the new selected item on change', function () {
 | 
			
		||||
            palette.on('change', callbackSpy1);
 | 
			
		||||
            palette.on('change', callbackSpy2);
 | 
			
		||||
            palette.set('item2');
 | 
			
		||||
            expect(callbackSpy1).toHaveBeenCalledWith('item2');
 | 
			
		||||
            expect(callbackSpy2).toHaveBeenCalledWith('item2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gracefully handles being set to an item not included in its set', function () {
 | 
			
		||||
            palette.set('foobar');
 | 
			
		||||
            expect(palette.getCurrent()).not.toEqual('foobar');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										51
									
								
								src/plugins/summaryWidget/test/input/SelectSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/plugins/summaryWidget/test/input/SelectSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
define(['../../src/input/Select'], function (Select) {
 | 
			
		||||
    describe('A select wrapper', function () {
 | 
			
		||||
        var select, testOptions, callbackSpy1, callbackSpy2;
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            select = new Select();
 | 
			
		||||
            testOptions = [['item1', 'Item 1'], ['item2', 'Item 2'], ['item3', 'Item 3']];
 | 
			
		||||
            select.setOptions(testOptions);
 | 
			
		||||
            callbackSpy1 = jasmine.createSpy('callbackSpy1');
 | 
			
		||||
            callbackSpy2 = jasmine.createSpy('callbackSpy2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gets and sets the current item', function () {
 | 
			
		||||
            select.setSelected('item1');
 | 
			
		||||
            expect(select.getSelected()).toEqual('item1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows adding a single new option', function () {
 | 
			
		||||
            select.addOption('newOption', 'A New Option');
 | 
			
		||||
            select.setSelected('newOption');
 | 
			
		||||
            expect(select.getSelected()).toEqual('newOption');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows populating with a new set of options', function () {
 | 
			
		||||
            select.setOptions([['newItem1', 'Item 1'], ['newItem2', 'Item 2']]);
 | 
			
		||||
            select.setSelected('newItem1');
 | 
			
		||||
            expect(select.getSelected()).toEqual('newItem1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                select.on('change', callbackSpy1);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                select.on('someUnsupportedEvent', callbackSpy1);
 | 
			
		||||
            }).toThrow();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('injects its callbacks with its property and value on a change', function () {
 | 
			
		||||
            select.on('change', callbackSpy1);
 | 
			
		||||
            select.on('change', callbackSpy2);
 | 
			
		||||
            select.setSelected('item2');
 | 
			
		||||
            expect(callbackSpy1).toHaveBeenCalledWith('item2');
 | 
			
		||||
            expect(callbackSpy2).toHaveBeenCalledWith('item2');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('gracefully handles being set to an item not included in its set', function () {
 | 
			
		||||
            select.setSelected('foobar');
 | 
			
		||||
            expect(select.getSelected()).not.toEqual('foobar');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -79,6 +79,17 @@ define([], function () {
 | 
			
		||||
        return this.providers[key];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Used internally to support seamless usage of new views with old
 | 
			
		||||
     * views.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ViewRegistry.prototype.getByVPID = function (vpid) {
 | 
			
		||||
        return this.providers.filter(function (p) {
 | 
			
		||||
            return p.vpid === vpid;
 | 
			
		||||
        })[0];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A View is used to provide displayable content, and to react to
 | 
			
		||||
     * associated life cycle events.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user