Merge branch 'master' into nem468a.

Many tweaks to fix some issues with tree, labels and Inspector.

Conflicts:
	platform/commonUI/edit/res/templates/elements.html
	platform/commonUI/general/res/sass/search/_search.scss
	platform/commonUI/general/res/sass/tree/_tree.scss
	platform/commonUI/general/res/templates/object-inspector.html
	platform/commonUI/themes/espresso/res/css/theme-espresso.css
	platform/commonUI/themes/snow/res/css/theme-snow.css
	platform/search/res/templates/search.html
This commit is contained in:
Charles Hacskaylo
2016-01-11 13:29:26 -08:00
72 changed files with 4215 additions and 727 deletions

View File

@@ -116,10 +116,6 @@
"implementation": "controllers/GetterSetterController.js",
"depends": [ "$scope" ]
},
{
"key": "SplitPaneController",
"implementation": "controllers/SplitPaneController.js"
},
{
"key": "SelectorController",
"implementation": "controllers/SelectorController.js",

View File

@@ -35,24 +35,23 @@
}
}
.l-autoflow-header {
bottom: auto;
height: $headerH;
line-height: $headerH;
min-width: $colW;
span {
vertical-align: middle;
}
min-width: $colW;
.t-last-update {
overflow: hidden;
}
.s-btn.change-column-width {
@include trans-prop-nice-fade(500ms);
opacity: 0;
}
.l-filter {
margin-left: $interiorMargin;
display: block;
margin-right: $interiorMargin;
input.t-filter-input {
width: 100px;
width: 150px;
}
}
}
@@ -127,4 +126,12 @@
}
}
}
}
.frame {
&.child-frame.panel {
.autoflow .l-autoflow-header .l-filter {
display: none;
}
}
}

View File

@@ -71,7 +71,7 @@ $itemPadLR: 5px;
$treeVCW: 10px;
$treeTypeIconH: 1.4em; // was 16px
$treeTypeIconHPx: 16px;
$treeTypeIconW: 20px;
$treeTypeIconW: 18px;
$treeContextTriggerW: 20px;
// Tabular
$tabularHeaderH: 22px; //18px

View File

@@ -31,10 +31,6 @@ a.disabled {
border-bottom: 1px solid rgba(#fff, 0.3);
}
.outline {
@include boxOutline();
}
.test-stripes {
@include bgDiagonalStripes();
}

View File

@@ -73,31 +73,34 @@
}
.l-icon-alert {
display: none !important; // Remove this when alerts are enabled
display: none !important;
&:before {
color: $colorAlert;
content: "!";
}
}
// NEW!!
.t-item-icon {
// Used in grid-item.html, tree-item, inspector location, more?
@extend .ui-symbol;
@extend .icon;
display: inline-block;
line-height: normal; // This is Ok for the symbolsfont
position: relative;
.t-item-icon-glyph {
position: absolute;
}
&.l-icon-link {
&:before {
color: $colorIconLink;
content: "\f4";
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 10%;
@include transform-origin(bottom, left);
@include transform(scale(0.3));
z-index: 2;
.t-item-icon-glyph {
&:before {
color: $colorIconLink;
content: "\f4";
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 10%;
@include transform-origin(bottom, left);
@include transform(scale(0.3));
z-index: 2;
}
}
}
}

View File

@@ -98,10 +98,19 @@
.inspector-location {
.location-item {
$h: 1.2em;
@include box-sizing(border-box);
cursor: pointer;
display: inline-block;
line-height: $h;
position: relative;
padding: 2px 4px;
.t-object-label {
.t-item-icon {
height: $h;
width: 0.7rem;
}
}
&:hover {
background: $colorItemTreeHoverBg;
color: $colorItemTreeHoverFg;
@@ -116,6 +125,7 @@
display: inline-block;
font-family: symbolsfont;
font-size: 8px;
font-style: normal !important;
line-height: inherit;
margin-left: $interiorMarginSm;
width: 4px;

View File

@@ -60,6 +60,7 @@
@import "overlay/overlay";
@import "mobile/overlay/overlay";
@import "tree/tree";
@import "object-label";
@import "mobile/tree";
@import "user-environ/frame";
@import "user-environ/top-bar";

View File

@@ -306,7 +306,7 @@
@include desktop {
@if $bgHov != none {
&:not(.disabled):hover {
background: $bgHov;
@include background-image($bgHov);
>.icon {
color: lighten($ic, $ltGamma);
}

View File

@@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
// mct-representation surrounding an object-label key="'label'"
.rep-object-label {
@include flex-direction(row);
@include flex(1 1 auto);
height: inherit;
line-height: inherit;
min-width: 0;
}
.t-object-label {
.t-item-icon {
margin-right: $interiorMargin;
}
}
mct-representation {
&.s-status-pending {
.t-object-label {
.t-item-icon {
&:before {
$spinBW: 4px;
$spinD: 0;
@include spinner($spinBW);
content: "";
display: block;
position: absolute;
left: 50%;
top: 50%;
padding: 30%;
width: $spinD;
height: $spinD;
}
.t-item-icon-glyph {
display: none;
}
}
.t-title-label {
font-style: italic;
opacity: 0.6;
}
}
}
}
.selected mct-representation.s-status-pending .t-object-label .t-item-icon:before {
border-color: rgba($colorItemTreeSelectedFg, 0.25);
border-top-color: rgba($colorItemTreeSelectedFg, 1.0);
}

View File

@@ -37,6 +37,8 @@
}
.status.block {
$transDelay: 1.5s;
$transSpeed: .25s;
color: $colorStatusDefault;
cursor: default;
display: inline-block;
@@ -44,13 +46,47 @@
.status-indicator,
.label,
.count {
//@include test(#00ff00);
display: inline-block;
vertical-align: top;
}
&.no-icon {
.status-indicator {
display: none;
}
}
&.float-right {
float: right;
}
&.subtle {
opacity: 0.5;
}
.status-indicator {
margin-right: $interiorMarginSm;
}
&:not(.no-collapse) {
.label {
// Max-width silliness is necessary for width transition
@include trans-prop-nice(max-width, $transSpeed, $transDelay);
overflow: hidden;
max-width: 0px;
}
&:hover {
.label {
@include trans-prop-nice(max-width, $transSpeed, 0s);
max-width: 450px;
width: auto;
}
.count {
@include trans-prop-nice(max-width, $transSpeed, 0s);
opacity: 0;
}
}
}
&.ok .status-indicator,
&.info .status-indicator {
color: $colorStatusInfo;
@@ -63,26 +99,11 @@
&.error .status-indicator {
color: $colorStatusError;
}
.label {
// Max-width silliness is necessary for width transition
@include trans-prop-nice(max-width, .25s);
overflow: hidden;
max-width: 0px;
}
.count {
@include trans-prop-nice(opacity, .25s);
@include trans-prop-nice(opacity, $transSpeed, $transDelay);
font-weight: bold;
opacity: 1;
}
&:hover {
.label {
max-width: 450px;
width: auto;
}
.count {
opacity: 0;
}
}
}
/* Styles for messages and message banners */

View File

@@ -24,21 +24,27 @@
100% { transform: rotate(359deg); }
}
@mixin wait-spinner2($b: 5px, $c: $colorAlt1) {
@mixin spinner($b: 5px) {
@include keyframes(rotateCentered) {
0% { transform: translateX(-50%) translateY(-50%) rotate(0deg); }
100% { transform: translateX(-50%) translateY(-50%) rotate(359deg); }
}
0% { @include transform(translateX(-50%) translateY(-50%) rotate(0deg)); }
100% { @include transform(translateX(-50%) translateY(-50%) rotate(359deg)); }
}
@include animation-name(rotateCentered);
@include animation-duration(0.5s);
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
@include transform-origin(center);
border-style: solid;
border-width: $b;
@include border-radius(100%);
}
@mixin wait-spinner2($b: 5px, $c: $colorAlt1) {
@include spinner($b);
@include box-sizing(border-box);
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: 5px;
@include border-radius(100%);
@include box-sizing(border-box);
display: block;
position: absolute;
height: 0; width: 0;

View File

@@ -31,7 +31,7 @@ $tabletItemH: floor($ueBrowseGridItemLg/3);
/************************** MOBILE TREE MENU DIMENSIONS */
$mobileTreeItemH: 35px;
$mobileTreeItemIndent: 20px;
$mobileTreeItemIndent: 15px;
$mobileTreeRightArrowW: 30px;
/************************** DEVICE WIDTHS */

View File

@@ -30,25 +30,30 @@
}
.tree-item,
.search-result-item {
height: $mobileTreeItemH;
line-height: $mobileTreeItemH;
margin-bottom: 0px;
height: $mobileTreeItemH !important;
line-height: $mobileTreeItemH !important;
margin-bottom: 0px !important;
.view-control {
//@include test(red);
position: absolute;
font-size: 1.1em;
height: $mobileTreeItemH;
line-height: inherit;
right: 0px;
width: $mobileTreeRightArrowW;
text-align: center;
font-size: 1.2em;
margin-right: 0;
order: 2;
width: $mobileTreeItemH;
&.has-children {
&:before {
content: "\7d";
left: 50%;
@include transform(translateX(-50%) rotate(90deg));
}
&.expanded:before {
@include transform(translateX(-50%) rotate(270deg));
}
}
}
.label,
.t-object-label {
left: 0;
right: $mobileTreeRightArrowW + $interiorMargin; // Allows tree item name to stop prior to the arrow
line-height: inherit;
.t-item-icon.l-icon-link .t-item-icon-glyph:before {
bottom: 20%; // Shift up due to height of mobile menu items
}
}
}
}

View File

@@ -1,5 +1,5 @@
@include phone {
.search {
.search-holder {
.search-bar {
// Hide menu-icon and adjust spacing when in phone mode
.menu-icon {

View File

@@ -20,33 +20,92 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.holder-search {
// Moved a lot of stuff in here to _filter.scss
// to generalize approach to search input controls.
.clear-icon,
.menu-icon {
cursor: pointer;
font-family: symbolsfont;
@include trans-prop-nice((opacity, color), 150ms);
}
.clear-icon {
// 'x' in circle icon
&:before {
content: '\e607';
}
}
.holder-search {
$iconWidth: 20px;
$textInputHeight: 19px;
$iconEdgeM: 4px;
$iconD: $treeSearchInputBarH - ($iconEdgeM*2);
.search-bar {
$textInputHeight: 19px; // This is equal to the default value, 19px
$iconEdgeM: 4px;
$iconD: $treeSearchInputBarH - ($iconEdgeM*2);
font-size: 0.8em;
max-width: 250px;
position: relative;
input[type="search"] {
.search-input {
height: $treeSearchInputBarH;
line-height: $treeSearchInputBarH;
position: relative;
width: 100%;
padding-left: $iconD + $interiorMargin !important;
padding-right: ($iconD * 2) + ($interiorMargin * 2) !important;
}
.clear-icon {
right: $iconD + $interiorMargin;
&:before,
.clear-icon,
.menu-icon {
@include box-sizing(border-box);
color: $colorInputIcon;
height: $iconD;
width: $iconD;
line-height: $iconD;
position: absolute;
text-align: center;
top: $iconEdgeM;
}
.search-input {
position: relative;
width: 100%;
padding-left: $iconD + $interiorMargin !important;
padding-right: ($iconD * 2) + ($interiorMargin * 2) !important;
// Make work for mct-control textfield
input {
width: inherit; // was 100%
}
}
&:before {
// Magnify glass icon
content:'\4d';
font-family: symbolsfont;
left: $interiorMarginSm;
@include trans-prop-nice(color, 250ms);
pointer-events: none;
z-index: 1;
}
// Make icon lighten when hovering over search bar
&:hover:before {
color: pullForward($colorInputIcon, 10%);
}
.clear-icon {
right: $iconD + $interiorMargin;
// Icon is visible only when there is text input
visibility: hidden;
opacity: 0;
&.show {
visibility: visible;
opacity: 1;
}
&:hover {
color: pullForward($colorInputIcon, 10%);
}
}
.menu-icon {
// 'v' invoke menu icon
&:before { content: '\76'; }
@@ -69,7 +128,7 @@
}
.active-filter-display {
$s: 0.65em;
$s: 0.7em;
$p: $interiorMargin;
@include box-sizing(border-box);
line-height: 130%;
@@ -88,7 +147,6 @@
.search-results {
@include trans-prop-nice((opacity, visibility), 250ms);
margin-top: $interiorMarginLg; // Always include margin here to fend off the search input
padding-right: $interiorMargin;
.hint {
margin-bottom: $interiorMarginLg;

View File

@@ -35,23 +35,35 @@ ul.tree {
.tree-item,
.search-result-item {
$runningItemW: 0;
@extend .l-flex-row;
@include box-sizing(border-box);
@include border-radius($basicCr);
@include single-transition(background-color, 0.25s);
display: block;
font-size: 0.8rem;
height: $menuLineH;
line-height: $menuLineH;
margin-bottom: $interiorMarginSm;
padding: 0 $interiorMarginSm;
position: relative;
.view-control {
color: $colorItemTreeVC;
display: inline-block;
margin-left: $interiorMargin;
font-size: 0.75em;
font-size: 0.75em;
margin-right: $interiorMargin;
height: 100%;
line-height: inherit;
width: $treeVCW;
$runningItemW: $interiorMargin + $treeVCW;
&.has-children {
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: "\3e";
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
@include desktop {
&:hover {
color: $colorItemTreeVCHover !important;
@@ -59,64 +71,17 @@ ul.tree {
}
}
.label,
.t-object-label {
display: block;
@include absPosDefault();
line-height: $menuLineH;
.t-item-icon {
@include txtShdwSubtle($shdwItemTreeIcon);
font-size: $treeTypeIconH;
color: $colorItemTreeIcon;
position: absolute;
left: $interiorMargin;
top: 50%;
width: $treeTypeIconH;
@include transform(translateY(-50%));
width: $treeTypeIconW;
}
.type-icon {
//@include absPosDefault(0, false);
$d: $treeTypeIconH;
@include txtShdwSubtle($shdwItemTreeIcon);
font-size: $treeTypeIconH;
color: $colorItemTreeIcon;
left: $interiorMargin;
position: absolute;
@include verticalCenterBlock($menuLineHPx, $treeTypeIconHPx);
line-height: 100%;
right: auto; width: $treeTypeIconH;
.icon {
&.l-icon-link,
&.l-icon-alert {
position: absolute;
z-index: 2;
}
&.l-icon-alert {
$d: 8px;
@include ancillaryIcon($d, $colorAlert);
top: 1px;
right: -2px;
}
&.l-icon-link {
$d: 8px;
@include ancillaryIcon($d, $colorIconLink);
left: -3px;
bottom: 0px;
}
}
}
.title-label,
.t-title-label {
@include absPosDefault();
display: block;
left: $runningItemW + ($interiorMargin * 3);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@include ellipsize();
}
}
&.selected {
@@ -126,12 +91,11 @@ ul.tree {
color: $colorItemTreeSelectedVC;
}
.t-object-label .t-item-icon {
color: $colorItemTreeSelectedFg; //$colorItemTreeIconHover;
color: $colorItemTreeSelectedFg;
}
}
&:not(.selected) {
// NOTE: [Mobile] Removed Hover on Mobile
@include desktop {
&:hover {
background: $colorItemTreeHoverBg;
@@ -160,6 +124,31 @@ ul.tree {
}
}
mct-representation {
&.s-status-pending {
.t-object-label {
.t-item-icon {
&:before {
$spinBW: 4px;
@include spinner($spinBW);
border-color: rgba($colorItemTreeIcon, 0.25);
border-top-color: rgba($colorItemTreeIcon, 1.0);
}
.t-item-icon-glyph {
display: none;
}
}
.t-title-label {
font-style: italic;
opacity: 0.6;
}
}
}
}
.selected mct-representation.s-status-pending .t-object-label .t-item-icon:before {
border-color: rgba($colorItemTreeSelectedFg, 0.25);
border-top-color: rgba($colorItemTreeSelectedFg, 1.0);
}
.tree .tree-item,
.search-results .s-status-editing .search-result-item {
.t-object-label {

View File

@@ -302,6 +302,7 @@
.splitter-treeview,
.holder-treeview-elements {
opacity: 0;
pointer-events: none;
}
}
@@ -333,6 +334,7 @@
.l-inspect,
.splitter-inspect {
opacity: 0;
pointer-events: none;
}
}
}

View File

@@ -1,30 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span ng-controller="SplitPaneController as splitter">
<div class="splitter" ng-style="splitter.style()"
mct-drag="splitter.move(delta.x)">
</div>
<div class='split-pane-component items pane' style="right:0;"
ng-style="splitter.style()"
ng-transclude>
</div>
</span>

View File

@@ -20,7 +20,6 @@
at runtime from the About dialog for additional information.
-->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<!--<div ng-init="reps = [1,2,3]"></div>-->
<div class='status block'
title="{{ngModel.getDescription()}}"
ng-click='ngModel.configure()'

View File

@@ -19,7 +19,9 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span class="t-object-label">
<span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }">{{type.getGlyph()}}</span>
<span class='t-title-label'>{{model.name}}</span>
</span>
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem" ng-class="{ 'l-icon-link':location.isLink() }">
<div class="t-item-icon-glyph">{{type.getGlyph()}}</div>
</div>
<div class='t-title-label flex-elem grows'>{{model.name}}</div>
</div>

View File

@@ -20,61 +20,44 @@
at runtime from the About dialog for additional information.
-->
<span class="l-inspect" ng-controller="ObjectInspectorController as controller">
<div ng-controller="PaneController as modelPaneEdit">
<mct-split-pane class='abs contents split-layout' anchor='bottom'>
<div class="split-pane-component pane top">
<div class="abs holder holder-inspector l-flex-col">
<div class="pane-header flex-elem">Inspection</div>
<ul class="flex-elem grows vscroll">
<li>
<em>Properties</em>
<div class="inspector-properties"
ng-repeat="data in metadata"
ng-class="{ first:$index === 0 }">
<div class="label">{{ data.name }}</div>
<div class="value">{{ data.value }}</div>
</div>
</li>
<li ng-if="contextutalParents.length > 0">
<em title="The location of this linked object.">Location</em>
<span class="inspector-location"
ng-repeat="parent in contextutalParents"
ng-class="{ last:($index + 1) === contextualParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>
</li>
<li ng-if="primaryParents.length > 0">
<em title="The location of the original object that this was linked from.">Original Location</em>
<span class="inspector-location"
ng-repeat="parent in primaryParents"
ng-class="{ last:($index + 1) === primaryParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>
</li>
</ul>
</div><!--/ holder-inspector -->
</div><!--/ split-pane-component -->
<mct-splitter class="splitter-inspect-panel mobile-hide"></mct-splitter>
<div class="split-pane-component pane bottom">
<div class="abs holder holder-elements l-flex-col">
<em class="flex-elem">Elements</em>
<mct-representation
key="'edit-elements'"
mct-object="domainObject"
class="flex-elem holder grows vscroll current-elements">
</mct-representation>
</div>
<div class="abs holder holder-inspector-elements l-flex-col">
<div class="pane-header flex-elem">Inspection</div>
<ul class="flex-elem grows vscroll">
<li>
<em>Properties</em>
<div class="inspector-properties"
ng-repeat="data in metadata"
ng-class="{ first:$index === 0 }">
<div class="label">{{ data.name }}</div>
<div class="value">{{ data.value }}</div>
</div>
</mct-split-pane>
</div><!--/ PaneController -->
</li>
<li ng-if="contextutalParents.length > 0">
<em title="The location of this linked object.">Location</em>
<span class="inspector-location"
ng-repeat="parent in contextutalParents"
ng-class="{ last:($index + 1) === contextualParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item rep-object-label">
</mct-representation>
</span>
</li>
<li ng-if="primaryParents.length > 0">
<em title="The location of the original object that this was linked from.">Original Location</em>
<span class="inspector-location"
ng-repeat="parent in primaryParents"
ng-class="{ last:($index + 1) === primaryParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item rep-object-label">
</mct-representation>
</span>
</li>
</ul>
</div>
</span>

View File

@@ -26,42 +26,19 @@
ng-class="{selected: treeNode.isSelected()}"
>
<span
mct-device="desktop"
class='ui-symbol view-control'
class='ui-symbol view-control flex-elem'
ng-class="{ 'has-children': model.composition !== undefined, expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()"
ng-if="model.composition !== undefined"
>
{{toggle.isActive() ? "v" : ">"}}
</span>
<mct-representation
mct-device="desktop"
class="mobile-hide"
class="rep-object-label"
key="'label'"
mct-object="domainObject"
parameters="{suppressMenuOnEdit: true}"
ng-click="treeNode.select()"
>
</mct-representation>
<mct-representation
mct-device="mobile"
class="desktop-hide"
key="'label'"
mct-object="domainObject"
ng-click="(model.composition === undefined) && treeNode.select();
toggle.toggle();
treeNode.trackExpansion();"
>
</mct-representation>
<span
mct-device="mobile"
class='ui-symbol view-control'
ng-model="ngModel"
ng-click="treeNode.select()"
>
}
</span>
</span>
<span
class="tree-item-subtree"

View File

@@ -1,89 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
var DEFAULT_MAXIMUM = 1000,
DEFAULT_MINIMUM = 120;
/**
* Controller for the splitter in Browse mode. Current implementation
* uses many hard-coded constants; this could be generalized.
* @memberof platform/commonUI/general
* @constructor
*/
function SplitPaneController() {
this.current = 200;
this.start = 200;
this.assigned = false;
}
/**
* Get the current position of the splitter, in pixels
* from the left edge.
* @returns {number} position of the splitter, in pixels
*/
SplitPaneController.prototype.state = function (defaultState) {
// Set the state to the desired default, if we don't have a
// "real" current state yet.
if (arguments.length > 0 && !this.assigned) {
this.current = defaultState;
this.assigned = true;
}
return this.current;
};
/**
* Begin moving the splitter; this will note the splitter's
* current position, which is necessary for correct
* interpretation of deltas provided by mct-drag.
*/
SplitPaneController.prototype.startMove = function () {
this.start = this.current;
};
/**
* Move the splitter a number of pixels to the right
* (negative numbers move the splitter to the left.)
* This movement is relative to the position of the
* splitter when startMove was last invoked.
* @param {number} delta number of pixels to move
*/
SplitPaneController.prototype.move = function (delta, minimum, maximum) {
// Ensure defaults for minimum/maximum
maximum = isNaN(maximum) ? DEFAULT_MAXIMUM : maximum;
minimum = isNaN(minimum) ? DEFAULT_MINIMUM : minimum;
// Update current splitter state
this.current = Math.min(
maximum,
Math.max(minimum, this.start + delta)
);
};
return SplitPaneController;
}
);

View File

@@ -22,8 +22,8 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/controllers/DateTimePickerController"],
function (DateTimePickerController) {
["../../src/controllers/DateTimePickerController", "moment"],
function (DateTimePickerController, moment) {
"use strict";
describe("The DateTimePickerController", function () {
@@ -39,6 +39,14 @@ define(
});
}
function fireWatchCollection(expr, value) {
mockScope.$watchCollection.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
@@ -57,6 +65,131 @@ define(
);
});
it("updates value in model when values in scope change", function () {
mockScope.date = {
year: 1998,
month: 0,
day: 6
};
mockScope.time = {
hours: 12,
minutes: 34,
seconds: 56
};
fireWatchCollection("date", mockScope.date);
expect(mockScope.ngModel[mockScope.field])
.toEqual(moment.utc("1998-01-06 12:34:56").valueOf());
});
describe("once initialized with model state", function () {
var testTime = moment.utc("1998-01-06 12:34:56").valueOf();
beforeEach(function () {
fireWatch("ngModel[field]", testTime);
});
it("exposes date/time values in scope", function () {
expect(mockScope.date.year).toEqual(1998);
expect(mockScope.date.month).toEqual(0); // Months are zero-indexed
expect(mockScope.date.day).toEqual(6);
expect(mockScope.time.hours).toEqual(12);
expect(mockScope.time.minutes).toEqual(34);
expect(mockScope.time.seconds).toEqual(56);
});
it("provides names for time properties", function () {
Object.keys(mockScope.time).forEach(function (key) {
expect(mockScope.nameFor(key))
.toEqual(jasmine.any(String));
});
});
it("provides options for time properties", function () {
Object.keys(mockScope.time).forEach(function (key) {
expect(mockScope.optionsFor(key))
.toEqual(jasmine.any(Array));
});
});
it("exposes times to populate calendar as a table", function () {
// Verify that data structure is as expected by template
expect(mockScope.table).toEqual(jasmine.any(Array));
expect(mockScope.table[0]).toEqual(jasmine.any(Array));
expect(mockScope.table[0][0]).toEqual({
year: jasmine.any(Number),
month: jasmine.any(Number),
day: jasmine.any(Number),
dayOfYear: jasmine.any(Number)
});
});
it("contains the current date in its initial table", function () {
var matchingCell;
// Should be able to find the selected date
mockScope.table.forEach(function (row) {
row.forEach(function (cell) {
if (cell.dayOfYear === 6) {
matchingCell = cell;
}
});
});
expect(matchingCell).toEqual({
year: 1998,
month: 0,
day: 6,
dayOfYear: 6
});
});
it("allows the displayed month to be advanced", function () {
// Around the edges of the displayed calendar we
// may be in previous or subsequent month, so
// test around the middle.
var i, originalMonth = mockScope.table[2][0].month;
function mod12(month) {
return ((month % 12) + 12) % 12;
}
for (i = 1; i <= 12; i += 1) {
mockScope.changeMonth(1);
expect(mockScope.table[2][0].month)
.toEqual(mod12(originalMonth + i));
}
for (i = 11; i >= -12; i -= 1) {
mockScope.changeMonth(-1);
expect(mockScope.table[2][0].month)
.toEqual(mod12(originalMonth + i));
}
});
it("allows checking if a cell is in the current month", function () {
expect(mockScope.isInCurrentMonth(mockScope.table[2][0]))
.toBe(true);
});
it("allows cells to be selected", function () {
mockScope.select(mockScope.table[2][0]);
expect(mockScope.isSelected(mockScope.table[2][0]))
.toBe(true);
mockScope.select(mockScope.table[2][1]);
expect(mockScope.isSelected(mockScope.table[2][0]))
.toBe(false);
expect(mockScope.isSelected(mockScope.table[2][1]))
.toBe(true);
});
it("allows cells to be compared", function () {
var table = mockScope.table;
expect(mockScope.dateEquals(table[2][0], table[2][1]))
.toBe(false);
expect(mockScope.dateEquals(table[2][1], table[2][1]))
.toBe(true);
});
});
});
}

View File

@@ -1,74 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/controllers/SplitPaneController"],
function (SplitPaneController) {
"use strict";
describe("The split pane controller", function () {
var controller;
beforeEach(function () {
controller = new SplitPaneController();
});
it("has an initial position", function () {
expect(controller.state() > 0).toBeTruthy();
});
it("can be moved", function () {
var initialState = controller.state();
controller.startMove();
controller.move(50);
expect(controller.state()).toEqual(initialState + 50);
});
it("clamps its position", function () {
var initialState = controller.state();
controller.startMove();
// Move some really extreme number
controller.move(-100000);
// Shouldn't have moved below 0...
expect(controller.state() > 0).toBeTruthy();
// ...but should have moved left somewhere
expect(controller.state() < initialState).toBeTruthy();
// Then do the same to the right
controller.move(100000);
// Shouldn't have moved below 0...
expect(controller.state() < 100000).toBeTruthy();
// ...but should have moved left somewhere
expect(controller.state() > initialState).toBeTruthy();
});
it("accepts a default state", function () {
// Should use default state the first time...
expect(controller.state(12321)).toEqual(12321);
// ...but not after it's been initialized
expect(controller.state(42)).toEqual(12321);
});
});
}
);

View File

@@ -34,14 +34,14 @@ define(
mockElement,
testAttrs,
mockBody,
mockParentEl,
mockPlainEl,
testRect,
mctClickElsewhere;
function testEvent(x, y) {
return {
pageX: x,
pageY: y,
clientX: x,
clientY: y,
preventDefault: jasmine.createSpy("preventDefault")
};
}
@@ -55,8 +55,8 @@ define(
jasmine.createSpyObj("element", JQLITE_METHODS);
mockBody =
jasmine.createSpyObj("body", JQLITE_METHODS);
mockParentEl =
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
mockPlainEl =
jasmine.createSpyObj("htmlElement", ["getBoundingClientRect"]);
testAttrs = {
mctClickElsewhere: "some Angular expression"
@@ -67,6 +67,8 @@ define(
width: 60,
height: 75
};
mockElement[0] = mockPlainEl;
mockPlainEl.getBoundingClientRect.andReturn(testRect);
mockDocument.find.andReturn(mockBody);
@@ -78,6 +80,49 @@ define(
expect(mctClickElsewhere.restrict).toEqual("A");
});
it("detaches listeners when destroyed", function () {
expect(mockBody.off).not.toHaveBeenCalled();
mockScope.$on.calls.forEach(function (call) {
if (call.args[0] === '$destroy') {
call.args[1]();
}
});
expect(mockBody.off).toHaveBeenCalled();
expect(mockBody.off.mostRecentCall.args)
.toEqual(mockBody.on.mostRecentCall.args);
});
it("listens for mousedown on the document's body", function () {
expect(mockBody.on)
.toHaveBeenCalledWith('mousedown', jasmine.any(Function));
});
describe("when a click occurs outside the element's bounds", function () {
beforeEach(function () {
mockBody.on.mostRecentCall.args[1](testEvent(
testRect.left + testRect.width + 10,
testRect.top + testRect.height + 10
));
});
it("triggers an evaluation of its related Angular expression", function () {
expect(mockScope.$eval)
.toHaveBeenCalledWith(testAttrs.mctClickElsewhere);
});
});
describe("when a click occurs within the element's bounds", function () {
beforeEach(function () {
mockBody.on.mostRecentCall.args[1](testEvent(
testRect.left + testRect.width / 2,
testRect.top + testRect.height / 2
));
});
it("triggers no evaluation", function () {
expect(mockScope.$eval).not.toHaveBeenCalled();
});
});
});
}

View File

@@ -30,13 +30,16 @@ define(
'on',
'addClass',
'children',
'eq'
'eq',
'toggleClass',
'css'
];
describe("The mct-split-pane directive", function () {
var mockParse,
mockLog,
mockInterval,
mockParsed,
mctSplitPane;
beforeEach(function () {
@@ -45,6 +48,11 @@ define(
jasmine.createSpyObj('$log', ['warn', 'info', 'debug']);
mockInterval = jasmine.createSpy('$interval');
mockInterval.cancel = jasmine.createSpy('mockCancel');
mockParsed = jasmine.createSpy('parsed');
mockParsed.assign = jasmine.createSpy('assign');
mockParse.andReturn(mockParsed);
mctSplitPane = new MCTSplitPane(
mockParse,
mockLog,
@@ -61,8 +69,19 @@ define(
mockElement,
testAttrs,
mockChildren,
mockFirstPane,
mockSplitter,
mockSecondPane,
controller;
function fireOn(eventType) {
mockScope.$on.calls.forEach(function (call) {
if (call.args[0] === eventType) {
call.args[1]();
}
});
}
beforeEach(function () {
mockScope =
jasmine.createSpyObj('$scope', ['$apply', '$watch', '$on']);
@@ -71,10 +90,33 @@ define(
testAttrs = {};
mockChildren =
jasmine.createSpyObj('children', JQLITE_METHODS);
mockFirstPane =
jasmine.createSpyObj('firstPane', JQLITE_METHODS);
mockSplitter =
jasmine.createSpyObj('splitter', JQLITE_METHODS);
mockSecondPane =
jasmine.createSpyObj('secondPane', JQLITE_METHODS);
mockElement.children.andReturn(mockChildren);
mockChildren.eq.andReturn(mockChildren);
mockChildren[0] = {};
mockElement[0] = {
offsetWidth: 12321,
offsetHeight: 45654
};
mockChildren.eq.andCallFake(function (i) {
return [mockFirstPane, mockSplitter, mockSecondPane][i];
});
mockFirstPane[0] = { offsetWidth: 123, offsetHeight: 456 };
mockSplitter[0] = {
nodeName: 'mct-splitter',
offsetWidth: 10,
offsetHeight: 456
};
mockSecondPane[0] = { offsetWidth: 10, offsetHeight: 456 };
mockChildren[0] = mockFirstPane[0];
mockChildren[1] = mockSplitter[0];
mockChildren[3] = mockSecondPane[0];
mockChildren.length = 3;
controller = mctSplitPane.controller[3](
mockScope,
@@ -87,6 +129,77 @@ define(
expect(mockInterval.mostRecentCall.args[3]).toBe(false);
});
it("exposes its splitter's initial position", function () {
expect(controller.position()).toEqual(
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
);
});
it("exposes the current anchoring mode", function () {
expect(controller.anchor()).toEqual({
edge : 'left',
opposite : 'right',
dimension : 'width',
orientation : 'vertical'
});
});
it("allows classes to be toggled on contained elements", function () {
controller.toggleClass('resizing');
expect(mockChildren.toggleClass)
.toHaveBeenCalledWith('resizing');
});
it("allows positions to be set", function () {
var testValue = mockChildren[0].offsetWidth + 50;
controller.position(testValue);
expect(mockFirstPane.css).toHaveBeenCalledWith(
'width',
(testValue - mockSplitter[0].offsetWidth) + 'px'
);
});
it("issues no warnings under nominal usage", function () {
expect(mockLog.warn).not.toHaveBeenCalled();
});
it("warns if no mct-splitter is present", function () {
mockSplitter[0].nodeName = "not-mct-splitter";
controller = mctSplitPane.controller[3](
mockScope,
mockElement,
testAttrs
);
expect(mockLog.warn).toHaveBeenCalled();
});
it("warns if an unknown anchor key is given", function () {
testAttrs.anchor = "middle";
controller = mctSplitPane.controller[3](
mockScope,
mockElement,
testAttrs
);
expect(mockLog.warn).toHaveBeenCalled();
});
it("updates positions on a timer", function () {
mockFirstPane[0].offsetWidth += 100;
// Should not reflect the change yet
expect(controller.position()).not.toEqual(
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
);
mockInterval.mostRecentCall.args[0]();
expect(controller.position()).toEqual(
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
);
});
it("cancels the active interval when scope is destroyed", function () {
expect(mockInterval.cancel).not.toHaveBeenCalled();
fireOn('$destroy');
expect(mockInterval.cancel).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,113 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/directives/MCTSplitter"],
function (MCTSplitter) {
'use strict';
describe("The mct-splitter directive", function () {
var mctSplitter;
beforeEach(function () {
mctSplitter = new MCTSplitter();
});
it("is applicable to elements", function () {
expect(mctSplitter.restrict).toEqual("E");
});
it("depends on the mct-split-pane controller", function () {
expect(mctSplitter.require).toEqual("^mctSplitPane");
});
describe("when linked", function () {
var mockScope,
mockElement,
testAttrs,
mockSplitPane;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
'$scope',
[ '$on', '$watch' ]
);
mockElement = jasmine.createSpyObj(
'element',
[ 'addClass' ]
);
testAttrs = {};
mockSplitPane = jasmine.createSpyObj(
'mctSplitPane',
[ 'position', 'toggleClass', 'anchor' ]
);
mctSplitter.link(
mockScope,
mockElement,
testAttrs,
mockSplitPane
);
});
it("adds a splitter class", function () {
expect(mockElement.addClass)
.toHaveBeenCalledWith('splitter');
});
describe("and then manipulated", function () {
var testPosition;
beforeEach(function () {
testPosition = 12321;
mockSplitPane.position.andReturn(testPosition);
mockSplitPane.anchor.andReturn({
orientation: 'vertical',
reversed: false
});
mockScope.splitter.startMove();
});
it("adds a 'resizing' class", function () {
expect(mockSplitPane.toggleClass)
.toHaveBeenCalledWith('resizing');
});
it("repositions during drag", function () {
mockScope.splitter.move([ 10, 0 ]);
expect(mockSplitPane.position)
.toHaveBeenCalledWith(testPosition + 10);
});
it("removes the 'resizing' class when finished", function () {
mockSplitPane.toggleClass.reset();
mockScope.splitter.endMove();
expect(mockSplitPane.toggleClass)
.toHaveBeenCalledWith('resizing');
});
});
});
});
}
);

View File

@@ -8,7 +8,6 @@
"controllers/GetterSetterController",
"controllers/ObjectInspectorController",
"controllers/SelectorController",
"controllers/SplitPaneController",
"controllers/TimeRangeController",
"controllers/ToggleController",
"controllers/TreeNodeController",
@@ -20,6 +19,7 @@
"directives/MCTResize",
"directives/MCTScroll",
"directives/MCTSplitPane",
"directives/MCTSplitter",
"services/Popup",
"services/PopupService",
"services/UrlService",

View File

@@ -13,6 +13,12 @@
"implementation": "AgentService.js",
"depends": [ "$window" ]
}
],
"runs": [
{
"implementation": "DeviceClassifier.js",
"depends": [ "agentService", "$document" ]
}
]
}
}

View File

@@ -46,6 +46,7 @@ define(
this.userAgent = userAgent;
this.mobileName = matches[0];
this.$window = $window;
this.touchEnabled = ($window.ontouchstart !== undefined);
}
/**
@@ -92,6 +93,14 @@ define(
return !this.isPortrait();
};
/**
* Check if the user's device supports a touch interface.
* @returns {boolean} true if touch is supported
*/
AgentService.prototype.isTouch = function () {
return this.touchEnabled;
};
/**
* Check if the user agent matches a certain named device,
* as indicated by checking for a case-insensitive substring

View File

@@ -0,0 +1,59 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise*/
define(
['./DeviceMatchers'],
function (DeviceMatchers) {
'use strict';
/**
* Runs at application startup and adds a subset of the following
* CSS classes to the body of the document, depending on device
* attributes:
*
* * `mobile`: Phones or tablets.
* * `phone`: Phones specifically.
* * `tablet`: Tablets specifically.
* * `desktop`: Non-mobile devices.
* * `portrait`: Devices in a portrait-style orientation.
* * `landscape`: Devices in a landscape-style orientation.
* * `touch`: Device supports touch events.
*
* @param {platform/commonUI/mobile.AgentService} agentService
* the service used to examine the user agent
* @param $document Angular's jqLite-wrapped document element
* @constructor
*/
function MobileClassifier(agentService, $document) {
var body = $document.find('body');
Object.keys(DeviceMatchers).forEach(function (key) {
if (DeviceMatchers[key](agentService)) {
body.addClass(key);
}
});
}
return MobileClassifier;
}
);

View File

@@ -0,0 +1,60 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(function () {
"use strict";
/**
* An object containing key-value pairs, where keys are symbolic of
* device attributes, and values are functions that take the
* `agentService` as inputs and return boolean values indicating
* whether or not the current device has these attributes.
*
* For internal use by the mobile support bundle.
*
* @memberof platform/commonUI/mobile
* @private
*/
return {
mobile: function (agentService) {
return agentService.isMobile();
},
phone: function (agentService) {
return agentService.isPhone();
},
tablet: function (agentService) {
return agentService.isTablet();
},
desktop: function (agentService) {
return !agentService.isMobile();
},
portrait: function (agentService) {
return agentService.isPortrait();
},
landscape: function (agentService) {
return agentService.isLandscape();
},
touch: function (agentService) {
return agentService.isTouch();
}
};
});

View File

@@ -22,31 +22,10 @@
/*global define,Promise*/
define(
function () {
['./DeviceMatchers'],
function (DeviceMatchers) {
'use strict';
var DEVICE_MATCHERS = {
mobile: function (agentService) {
return agentService.isMobile();
},
phone: function (agentService) {
return agentService.isPhone();
},
tablet: function (agentService) {
return agentService.isTablet();
},
desktop: function (agentService) {
return !agentService.isMobile();
},
portrait: function (agentService) {
return agentService.isPortrait();
},
landscape: function (agentService) {
return agentService.isLandscape();
}
};
/**
* The `mct-device` directive, when applied as an attribute,
* only includes the element when the device being used matches
@@ -68,6 +47,7 @@ define(
* * `desktop`: Non-mobile devices.
* * `portrait`: Devices in a portrait-style orientation.
* * `landscape`: Devices in a landscape-style orientation.
* * `touch`: Device supports touch events.
*
* @param {AgentService} agentService used to detect device type
* based on information about the user agent
@@ -77,7 +57,7 @@ define(
function deviceMatches(tokens) {
tokens = tokens || "";
return tokens.split(" ").every(function (token) {
var fn = DEVICE_MATCHERS[token];
var fn = DeviceMatchers[token];
return fn && fn(agentService);
});
}

View File

@@ -82,6 +82,15 @@ define(
expect(agentService.isLandscape()).toBeFalsy();
});
it("detects touch support", function () {
testWindow.ontouchstart = null;
expect(new AgentService(testWindow).isTouch())
.toBe(true);
delete testWindow.ontouchstart;
expect(new AgentService(testWindow).isTouch())
.toBe(false);
});
it("allows for checking browser type", function () {
testWindow.navigator.userAgent = "Chromezilla Safarifox";
agentService = new AgentService(testWindow);

View File

@@ -0,0 +1,112 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../src/DeviceClassifier", "../src/DeviceMatchers"],
function (DeviceClassifier, DeviceMatchers) {
"use strict";
var AGENT_SERVICE_METHODS = [
'isMobile',
'isPhone',
'isTablet',
'isPortrait',
'isLandscape',
'isTouch'
],
TEST_PERMUTATIONS = [
[ 'isMobile', 'isPhone', 'isTouch', 'isPortrait' ],
[ 'isMobile', 'isPhone', 'isTouch', 'isLandscape' ],
[ 'isMobile', 'isTablet', 'isTouch', 'isPortrait' ],
[ 'isMobile', 'isTablet', 'isTouch', 'isLandscape' ],
[ 'isTouch' ],
[]
];
describe("DeviceClassifier", function () {
var mockAgentService,
mockDocument,
mockBody;
beforeEach(function () {
mockAgentService = jasmine.createSpyObj(
'agentService',
AGENT_SERVICE_METHODS
);
mockDocument = jasmine.createSpyObj(
'$document',
[ 'find' ]
);
mockBody = jasmine.createSpyObj(
'body',
[ 'addClass' ]
);
mockDocument.find.andCallFake(function (sel) {
return sel === 'body' && mockBody;
});
AGENT_SERVICE_METHODS.forEach(function (m) {
mockAgentService[m].andReturn(false);
});
});
TEST_PERMUTATIONS.forEach(function (trueMethods) {
var summary = trueMethods.length === 0 ?
"device has no detected characteristics" :
"device " + (trueMethods.join(", "));
describe("when " + summary, function () {
var classifier;
beforeEach(function () {
trueMethods.forEach(function (m) {
mockAgentService[m].andReturn(true);
});
classifier = new DeviceClassifier(
mockAgentService,
mockDocument
);
});
it("adds classes for matching, detected characteristics", function () {
Object.keys(DeviceMatchers).filter(function (m) {
return DeviceMatchers[m](mockAgentService);
}).forEach(function (key) {
expect(mockBody.addClass)
.toHaveBeenCalledWith(key);
});
});
it("does not add classes for non-matching characteristics", function () {
Object.keys(DeviceMatchers).filter(function (m) {
return !DeviceMatchers[m](mockAgentService);
}).forEach(function (key) {
expect(mockBody.addClass)
.not.toHaveBeenCalledWith(key);
});
});
});
});
});
}
);

View File

@@ -0,0 +1,81 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../src/DeviceMatchers"],
function (DeviceMatchers) {
'use strict';
describe("DeviceMatchers", function () {
var mockAgentService;
beforeEach(function () {
mockAgentService = jasmine.createSpyObj(
'agentService',
[
'isMobile',
'isPhone',
'isTablet',
'isPortrait',
'isLandscape',
'isTouch'
]
);
});
it("detects when a device is a desktop device", function () {
mockAgentService.isMobile.andReturn(false);
expect(DeviceMatchers.desktop(mockAgentService))
.toBe(true);
mockAgentService.isMobile.andReturn(true);
expect(DeviceMatchers.desktop(mockAgentService))
.toBe(false);
});
function method(deviceType) {
return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
}
[
"mobile",
"phone",
"tablet",
"landscape",
"portrait",
"landscape",
"touch"
].forEach(function (deviceType) {
it("detects when a device is a " + deviceType + " device", function () {
mockAgentService[method(deviceType)].andReturn(true);
expect(DeviceMatchers[deviceType](mockAgentService))
.toBe(true);
mockAgentService[method(deviceType)].andReturn(false);
expect(DeviceMatchers[deviceType](mockAgentService))
.toBe(false);
});
});
});
}
);

View File

@@ -1,4 +1,6 @@
[
"AgentService",
"DeviceClassifier",
"DeviceMatchers",
"MCTDevice"
]