diff --git a/bundles.json b/bundles.json
index de005c5961..0486bdf24b 100644
--- a/bundles.json
+++ b/bundles.json
@@ -7,6 +7,7 @@
"platform/commonUI/edit",
"platform/commonUI/dialog",
"platform/commonUI/general",
+ "platform/commonUI/inspect",
"platform/containment",
"platform/telemetry",
"platform/features/layout",
@@ -16,7 +17,6 @@
"platform/forms",
"platform/persistence/queue",
"platform/policy",
- "platform/entanglement",
"example/persistence",
"example/generator"
diff --git a/platform/commonUI/browse/bundle.json b/platform/commonUI/browse/bundle.json
index dcf1ba9a38..28d96c5ec2 100644
--- a/platform/commonUI/browse/bundle.json
+++ b/platform/commonUI/browse/bundle.json
@@ -57,7 +57,8 @@
{
"key": "grid-item",
"templateUrl": "templates/items/grid-item.html",
- "uses": [ "type", "action" ]
+ "uses": [ "type", "action" ],
+ "gestures": [ "info", "menu" ]
},
{
"key": "object-header",
diff --git a/platform/commonUI/general/bundle.json b/platform/commonUI/general/bundle.json
index 2923d96c47..ead610c963 100644
--- a/platform/commonUI/general/bundle.json
+++ b/platform/commonUI/general/bundle.json
@@ -181,7 +181,7 @@
"key": "label",
"templateUrl": "templates/label.html",
"uses": [ "type" ],
- "gestures": [ "drag", "menu" ]
+ "gestures": [ "drag", "menu", "info" ]
},
{
"key": "node",
diff --git a/platform/commonUI/general/res/css/theme-espresso.css b/platform/commonUI/general/res/css/theme-espresso.css
index 900558efd9..5a8bcac07a 100644
--- a/platform/commonUI/general/res/css/theme-espresso.css
+++ b/platform/commonUI/general/res/css/theme-espresso.css
@@ -328,6 +328,10 @@ span {
*/ }
/* line 72, ../sass/_global.scss */
+mct-container {
+ display: block; }
+
+/* line 76, ../sass/_global.scss */
.abs, .btn-menu span.l-click-area {
position: absolute;
top: 0;
@@ -337,51 +341,51 @@ span {
height: auto;
width: auto; }
-/* line 82, ../sass/_global.scss */
+/* line 86, ../sass/_global.scss */
.code, .codehilite {
font-family: "Lucida Console", monospace;
font-size: 0.7em;
line-height: 150%;
white-space: pre; }
-/* line 89, ../sass/_global.scss */
+/* line 93, ../sass/_global.scss */
.codehilite {
background-color: rgba(255, 255, 255, 0.1);
padding: 1em; }
-/* line 95, ../sass/_global.scss */
+/* line 99, ../sass/_global.scss */
.align-right {
text-align: right; }
-/* line 99, ../sass/_global.scss */
+/* line 103, ../sass/_global.scss */
.centered {
text-align: center; }
-/* line 103, ../sass/_global.scss */
+/* line 107, ../sass/_global.scss */
.no-margin {
margin: 0; }
-/* line 107, ../sass/_global.scss */
+/* line 111, ../sass/_global.scss */
.colorKey {
color: #0099cc; }
-/* line 111, ../sass/_global.scss */
+/* line 115, ../sass/_global.scss */
.ds {
-moz-box-shadow: rgba(0, 0, 0, 0.7) 0 4px 10px 2px;
-webkit-box-shadow: rgba(0, 0, 0, 0.7) 0 4px 10px 2px;
box-shadow: rgba(0, 0, 0, 0.7) 0 4px 10px 2px; }
-/* line 115, ../sass/_global.scss */
+/* line 119, ../sass/_global.scss */
.hide,
.hidden {
display: none !important; }
-/* line 121, ../sass/_global.scss */
+/* line 125, ../sass/_global.scss */
.paused:not(.s-btn):not(.icon-btn) {
border-color: #c56f01 !important;
color: #c56f01 !important; }
-/* line 127, ../sass/_global.scss */
+/* line 131, ../sass/_global.scss */
.sep {
color: rgba(255, 255, 255, 0.2); }
@@ -2611,7 +2615,7 @@ label.checkbox.custom {
position: absolute;
height: 200px;
width: 170px;
- z-index: 59; }
+ z-index: 70; }
/* line 172, ../sass/controls/_menus.scss */
.context-menu-holder .context-menu-wrapper {
position: absolute;
@@ -4215,137 +4219,101 @@ input[type="text"] {
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* line 24, ../sass/helpers/_bubbles.scss */
-.l-bubble-wrapper {
- position: absolute;
- z-index: 70; }
- /* line 27, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-bubble {
- padding: 5px 10px; }
- /* line 29, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-bubble .l-btn.close {
- padding: 0 2px;
- position: absolute;
- right: 5px;
- top: 5px; }
- /* line 36, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .arw {
- position: absolute; }
- /* line 38, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .arw.arw-up {
- bottom: 100%; }
- /* line 42, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .arw.arw-down {
- top: 100%; }
- /* line 47, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble {
+.l-infobubble-wrapper {
+ -moz-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
+ box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
+ position: relative;
+ z-index: 50; }
+ /* line 29, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble {
display: inline-block;
- max-width: 250px; }
- /* line 51, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble:before {
+ min-width: 100px;
+ max-width: 300px;
+ padding: 5px 10px; }
+ /* line 34, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble:before {
content: "";
position: absolute;
width: 0;
height: 0; }
- /* line 57, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble table {
+ /* line 40, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble table {
width: 100%; }
- /* line 60, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble table tr td {
+ /* line 43, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble table tr td {
padding: 2px 0;
vertical-align: top; }
- /* line 67, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble table tr td.label {
+ /* line 50, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble table tr td.label {
padding-right: 10px;
white-space: nowrap; }
- /* line 71, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble table tr td.value {
+ /* line 54, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble table tr td.value {
white-space: nowrap; }
- /* line 75, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble table tr td.align-wrap {
+ /* line 58, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble table tr td.align-wrap {
white-space: normal; }
- /* line 81, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper .l-infobubble .title {
+ /* line 64, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .l-infobubble .title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 5px; }
- /* line 88, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-left {
+ /* line 71, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-left {
margin-left: 20px; }
- /* line 90, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-left .l-infobubble::before {
+ /* line 73, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-left .l-infobubble::before {
right: 100%;
width: 0;
height: 0;
border-top: 6.66667px solid transparent;
border-bottom: 6.66667px solid transparent;
border-right: 10px solid #ddd; }
- /* line 96, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-right {
+ /* line 79, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-right {
margin-right: 20px; }
- /* line 98, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-right .l-infobubble::before {
+ /* line 81, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-right .l-infobubble::before {
left: 100%;
width: 0;
height: 0;
border-top: 6.66667px solid transparent;
border-bottom: 6.66667px solid transparent;
border-left: 10px solid #ddd; }
- /* line 105, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-top .l-infobubble::before {
+ /* line 88, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-top .l-infobubble::before {
top: 20px; }
- /* line 111, ../sass/helpers/_bubbles.scss */
- .l-bubble-wrapper.arw-btm .l-infobubble::before {
+ /* line 94, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-btm .l-infobubble::before {
bottom: 20px; }
-
-/* line 121, ../sass/helpers/_bubbles.scss */
-.l-thumbsbubble-wrapper {
- position: absolute;
- left: 10px;
- right: 10px;
- height: 183px;
- width: auto; }
- /* line 128, ../sass/helpers/_bubbles.scss */
- .l-thumbsbubble-wrapper .l-thumbsbubble {
- overflow: hidden;
- position: absolute;
- top: 0px;
- right: 0px;
- bottom: 0px;
- left: 0px;
- width: auto;
- height: auto; }
- /* line 130, ../sass/helpers/_bubbles.scss */
- .l-thumbsbubble-wrapper .l-thumbsbubble .l-image-thumbs-wrapper {
- height: auto;
- top: 5px !important;
- right: 25px;
- bottom: 5px !important;
- left: 5px; }
- /* line 135, ../sass/helpers/_bubbles.scss */
- .l-thumbsbubble-wrapper .arw {
+ /* line 99, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-down {
+ margin-bottom: 10px; }
+ /* line 101, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-down .l-infobubble::before {
+ left: 50%;
+ top: 100%;
+ margin-left: -5px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 7.5px solid #ddd; }
+ /* line 110, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper .arw {
z-index: 2; }
- /* line 140, ../sass/helpers/_bubbles.scss */
- .l-thumbsbubble-wrapper.arw-up .arw.arw-down, .l-thumbsbubble-wrapper.arw-down .arw.arw-up {
+ /* line 113, ../sass/helpers/_bubbles.scss */
+ .l-infobubble-wrapper.arw-up .arw.arw-down, .l-infobubble-wrapper.arw-down .arw.arw-up {
display: none; }
-/* line 150, ../sass/helpers/_bubbles.scss */
-.s-bubble {
- -moz-border-radius: 2px;
- -webkit-border-radius: 2px;
- border-radius: 2px;
- -moz-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
- -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
- box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px; }
-
-/* line 156, ../sass/helpers/_bubbles.scss */
+/* line 120, ../sass/helpers/_bubbles.scss */
.l-thumbsbubble-wrapper .arw-up {
width: 0;
height: 0;
border-left: 6.66667px solid transparent;
border-right: 6.66667px solid transparent;
border-bottom: 10px solid #4d4d4d; }
-/* line 159, ../sass/helpers/_bubbles.scss */
+/* line 123, ../sass/helpers/_bubbles.scss */
.l-thumbsbubble-wrapper .arw-down {
width: 0;
height: 0;
@@ -4353,27 +4321,33 @@ input[type="text"] {
border-right: 6.66667px solid transparent;
border-top: 10px solid #4d4d4d; }
-/* line 163, ../sass/helpers/_bubbles.scss */
+/* line 127, ../sass/helpers/_bubbles.scss */
.s-infobubble {
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+ -moz-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
+ box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
background: #ddd;
color: #666;
font-size: 0.8rem; }
- /* line 168, ../sass/helpers/_bubbles.scss */
+ /* line 134, ../sass/helpers/_bubbles.scss */
.s-infobubble .title {
color: #333333;
font-weight: bold; }
- /* line 173, ../sass/helpers/_bubbles.scss */
+ /* line 139, ../sass/helpers/_bubbles.scss */
.s-infobubble tr td {
border-top: 1px solid #c4c4c4;
font-size: 0.9em; }
- /* line 177, ../sass/helpers/_bubbles.scss */
+ /* line 143, ../sass/helpers/_bubbles.scss */
.s-infobubble tr:first-child td {
border-top: none; }
- /* line 181, ../sass/helpers/_bubbles.scss */
+ /* line 147, ../sass/helpers/_bubbles.scss */
.s-infobubble .value {
color: #333333; }
-/* line 186, ../sass/helpers/_bubbles.scss */
+/* line 152, ../sass/helpers/_bubbles.scss */
.s-thumbsbubble {
background: #4d4d4d;
color: #b3b3b3; }
diff --git a/platform/commonUI/general/res/sass/_constants.scss b/platform/commonUI/general/res/sass/_constants.scss
index e5385b19ac..feab47efab 100644
--- a/platform/commonUI/general/res/sass/_constants.scss
+++ b/platform/commonUI/general/res/sass/_constants.scss
@@ -67,6 +67,8 @@ $colorLimitRed: #aa0000;
$colorTelemFresh: #fff;
$colorTelemStale: #888;
$styleTelemState: italic;
+$colorInfoBubbleFg: #666;
+$colorInfoBubbleBg: #ddd;
// Ratios
$ltGamma: 20%;
@@ -150,6 +152,9 @@ $imageThumbPad: 1px;
// Bubbles
$bubbleArwSize: 10px;
$bubblePad: $interiorMargin;
+$bubbleMinW: 100px;
+$bubbleMaxW: 300px;
+
// Timing
-$controlFadeMs: 100ms;
+$controlFadeMs: 100ms;
\ No newline at end of file
diff --git a/platform/commonUI/general/res/sass/_global.scss b/platform/commonUI/general/res/sass/_global.scss
index 0ada165ec9..be0afc896d 100644
--- a/platform/commonUI/general/res/sass/_global.scss
+++ b/platform/commonUI/general/res/sass/_global.scss
@@ -69,6 +69,10 @@ span {
*/
}
+mct-container {
+ display: block;
+}
+
.abs {
position: absolute;
top: 0;
diff --git a/platform/commonUI/general/res/sass/controls/_menus.scss b/platform/commonUI/general/res/sass/controls/_menus.scss
index 01606d0c6d..cc6f65fb7a 100644
--- a/platform/commonUI/general/res/sass/controls/_menus.scss
+++ b/platform/commonUI/general/res/sass/controls/_menus.scss
@@ -168,7 +168,7 @@
position: absolute;
height: 200px;
width: 170px;
- z-index: 59;
+ z-index: 70;
.context-menu-wrapper {
position: absolute;
height: 100%;
diff --git a/platform/commonUI/general/res/sass/helpers/_bubbles.scss b/platform/commonUI/general/res/sass/helpers/_bubbles.scss
index 0dad9e115b..e9648523c4 100644
--- a/platform/commonUI/general/res/sass/helpers/_bubbles.scss
+++ b/platform/commonUI/general/res/sass/helpers/_bubbles.scss
@@ -21,33 +21,16 @@
*****************************************************************************/
//************************************************* LAYOUT
-.l-bubble-wrapper {
- position: absolute;
- z-index: 70;
- .l-bubble {
- padding: $bubblePad $bubblePad*2;
- .l-btn.close {
- padding: 0 2px;
- position: absolute;
- right: $interiorMargin;
- top: $interiorMargin;
- }
- }
- .arw {
- position: absolute;
- &.arw-up {
- bottom: 100%;
-
- }
- &.arw-down {
- top: 100%;
- }
- }
-
+.l-infobubble-wrapper {
+ $arwSize: 5px;
+ @include box-shadow(rgba(black, 0.4) 0 1px 5px);
+ position: relative;
+ z-index: 50;
.l-infobubble {
display: inline-block;
- max-width: 250px;
- //padding: 5px 10px;
+ min-width: $bubbleMinW;
+ max-width: $bubbleMaxW;
+ padding: 5px 10px;
&:before {
content:"";
position: absolute;
@@ -114,27 +97,17 @@
}
&.arw-down {
-
- }
-}
-
-.l-thumbsbubble-wrapper {
- $closeBtnD: 15px;
- position: absolute;
- left: $interiorMarginLg;
- right: $interiorMarginLg;
- height: $imageThumbsWrapperH + $bubblePad*2 + $interiorMargin;
- width: auto;
- .l-thumbsbubble {
- @include absPosDefault();
- .l-image-thumbs-wrapper {
- height: auto;
- top: $bubblePad !important; right: $closeBtnD + ($interiorMargin*2); bottom: $bubblePad !important; left: $bubblePad;
+ margin-bottom: $arwSize*2;
+ .l-infobubble::before {
+ left: 50%;
+ top: 100%;
+ margin-left: -1 * $arwSize;
+ border-left: $arwSize solid transparent;
+ border-right: $arwSize solid transparent;
+ border-top: ($arwSize * 1.5) solid $colorInfoBubbleBg;
}
}
.arw {
- //left: 50%;
- //margin-left: $bubbleArwSize / -2;
z-index: 2;
}
&.arw-up .arw.arw-down,
@@ -143,15 +116,6 @@
//************************************************* LOOK AND FEEL
-.s-bubble-wrapper {
- //@include box-shadow(rgba(black, 0.4) 0 1px 5px);
-}
-
-.s-bubble {
- @include border-radius($basicCr);
- @include box-shadow(rgba(black, 0.4) 0 1px 5px);
-}
-
.l-thumbsbubble-wrapper {
.arw-up {
@include triangle('up', $bubbleArwSize, 1.5, $colorThumbsBubbleBg);
@@ -162,6 +126,8 @@
}
.s-infobubble {
$emFg: darken($colorInfoBubbleFg, 20%);
+ @include border-radius($basicCr);
+ @include box-shadow(rgba(black, 0.4) 0 1px 5px);
background: $colorInfoBubbleBg;
color: $colorInfoBubbleFg;
font-size: 0.8rem;
diff --git a/platform/commonUI/inspect/bundle.json b/platform/commonUI/inspect/bundle.json
index 16f2bdf89c..07506d0983 100644
--- a/platform/commonUI/inspect/bundle.json
+++ b/platform/commonUI/inspect/bundle.json
@@ -1,20 +1,50 @@
{
"extensions": {
- "types": [
+ "templates": [
{
- "key": "infobubble",
- "name": "Info Bubble",
- "glyph": "\u00EA",
- "description": "Static markup for info bubbles",
- "features": [ "creation" ]
+ "key": "info-table",
+ "templateUrl": "info-table.html"
+ },
+ {
+ "key": "info-bubble",
+ "templateUrl": "info-bubble.html"
}
],
- "views": [
+ "containers": [
{
- "templateUrl": "infobubble.html",
- "name": "Info Bubble",
- "type": "infobubble",
- "key": "infobubble"
+ "key": "bubble",
+ "templateUrl": "bubble.html",
+ "attributes": [ "bubbleTitle", "bubbleLayout" ],
+ "alias": "bubble"
+ }
+ ],
+ "gestures": [
+ {
+ "key": "info",
+ "implementation": "gestures/InfoGesture.js",
+ "depends": [
+ "$timeout",
+ "infoService",
+ "INFO_HOVER_DELAY"
+ ]
+ }
+ ],
+ "services": [
+ {
+ "key": "infoService",
+ "implementation": "services/InfoService.js",
+ "depends": [
+ "$compile",
+ "$document",
+ "$window",
+ "$rootScope"
+ ]
+ }
+ ],
+ "constants": [
+ {
+ "key": "INFO_HOVER_DELAY",
+ "value": 500
}
]
}
diff --git a/platform/commonUI/inspect/res/bubble.html b/platform/commonUI/inspect/res/bubble.html
new file mode 100644
index 0000000000..f528d6432f
--- /dev/null
+++ b/platform/commonUI/inspect/res/bubble.html
@@ -0,0 +1,9 @@
+
+
+
+ {{bubble.bubbleTitle}}
+
+
+
+
diff --git a/platform/commonUI/inspect/res/info-bubble.html b/platform/commonUI/inspect/res/info-bubble.html
new file mode 100644
index 0000000000..1deeeade15
--- /dev/null
+++ b/platform/commonUI/inspect/res/info-bubble.html
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/platform/commonUI/inspect/res/info-table.html b/platform/commonUI/inspect/res/info-table.html
new file mode 100644
index 0000000000..0c89a498d9
--- /dev/null
+++ b/platform/commonUI/inspect/res/info-table.html
@@ -0,0 +1,8 @@
+
+
+ | {{property.name}} |
+
+ {{property.value}}
+ |
+
+
diff --git a/platform/commonUI/inspect/src/InfoConstants.js b/platform/commonUI/inspect/src/InfoConstants.js
new file mode 100644
index 0000000000..86570911c6
--- /dev/null
+++ b/platform/commonUI/inspect/src/InfoConstants.js
@@ -0,0 +1,35 @@
+/*****************************************************************************
+ * 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({
+ BUBBLE_TEMPLATE: "" +
+ "" +
+ "" +
+ "",
+ // Pixel offset for bubble, to align arrow position
+ BUBBLE_OFFSET: [ 0, -26 ],
+ // Max width and margins allowed for bubbles; defined in /platform/commonUI/general/res/sass/_constants.scss
+ BUBBLE_MARGIN_LR: 10,
+ BUBBLE_MAX_WIDTH: 300
+});
\ No newline at end of file
diff --git a/platform/commonUI/inspect/src/gestures/InfoGesture.js b/platform/commonUI/inspect/src/gestures/InfoGesture.js
new file mode 100644
index 0000000000..879cc2ac36
--- /dev/null
+++ b/platform/commonUI/inspect/src/gestures/InfoGesture.js
@@ -0,0 +1,121 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define(
+ [],
+ function () {
+ "use strict";
+
+ /**
+ * The `info` gesture displays domain object metadata in a
+ * bubble on hover.
+ *
+ * @constructor
+ * @param $timeout Angular's `$timeout`
+ * @param {InfoService} infoService a service which shows info bubbles
+ * @param {number} DELAY delay, in milliseconds, before bubble appears
+ * @param element jqLite-wrapped DOM element
+ * @param {DomainObject} domainObject the domain object for which to
+ * show information
+ */
+ function InfoGesture($timeout, infoService, DELAY, element, domainObject) {
+ var dismissBubble,
+ pendingBubble,
+ mousePosition,
+ scopeOff;
+
+ function trackPosition(event) {
+ // Record mouse position, so bubble can be shown at latest
+ // mouse position (not just where the mouse entered)
+ mousePosition = [ event.clientX, event.clientY ];
+ }
+
+ function hideBubble() {
+ // If a bubble is showing, dismiss it
+ if (dismissBubble) {
+ dismissBubble();
+ element.off('mouseleave', hideBubble);
+ dismissBubble = undefined;
+ }
+ // If a bubble will be shown on a timeout, cancel that
+ if (pendingBubble) {
+ $timeout.cancel(pendingBubble);
+ element.off('mousemove', trackPosition);
+ element.off('mouseleave', hideBubble);
+ pendingBubble = undefined;
+ }
+ // Also clear mouse position so we don't have a ton of tiny
+ // arrays allocated while user mouses over things
+ mousePosition = undefined;
+ }
+
+ function showBubble(event) {
+ trackPosition(event);
+
+ // Also need to track position during hover
+ element.on('mousemove', trackPosition);
+
+ // Show the bubble, after a suitable delay (if mouse has
+ // left before this time is up, this will be canceled.)
+ pendingBubble = $timeout(function () {
+ dismissBubble = infoService.display(
+ "info-table",
+ domainObject.getModel().name,
+ domainObject.useCapability('metadata'),
+ mousePosition
+ );
+ element.off('mousemove', trackPosition);
+ pendingBubble = undefined;
+ }, DELAY);
+
+ element.on('mouseleave', hideBubble);
+ }
+
+ // Show bubble (on a timeout) on mouse over
+ element.on('mouseenter', showBubble);
+
+ // Also make sure we dismiss bubble if representation is destroyed
+ // before the mouse actually leaves it
+ scopeOff = element.scope().$on('$destroy', hideBubble);
+
+ return {
+ /**
+ * Detach any event handlers associated with this gesture.
+ * @memberof InfoGesture
+ * @method
+ */
+ destroy: function () {
+ // Dismiss any active bubble...
+ hideBubble();
+ // ...and detach listeners
+ element.off('mouseenter', showBubble);
+ scopeOff();
+ }
+ };
+ }
+
+ return InfoGesture;
+
+ }
+
+);
diff --git a/platform/commonUI/inspect/src/services/InfoService.js b/platform/commonUI/inspect/src/services/InfoService.js
new file mode 100644
index 0000000000..b67192ba30
--- /dev/null
+++ b/platform/commonUI/inspect/src/services/InfoService.js
@@ -0,0 +1,95 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define(
+ ['../InfoConstants'],
+ function (InfoConstants) {
+ "use strict";
+
+ var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
+ OFFSET = InfoConstants.BUBBLE_OFFSET;
+
+ /**
+ * Displays informative content ("info bubbles") for the user.
+ * @constructor
+ */
+ function InfoService($compile, $document, $window, $rootScope) {
+
+ function display(templateKey, title, content, position) {
+ var body = $document.find('body'),
+ scope = $rootScope.$new(),
+ winDim = [$window.innerWidth, $window.innerHeight],
+ bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR + InfoConstants.BUBBLE_MAX_WIDTH,
+ goLeft = position[0] > (winDim[0] - bubbleSpaceLR),
+ goUp = position[1] > (winDim[1] / 2),
+ bubble;
+
+ // Pass model & container parameters into the scope
+ scope.bubbleModel = content;
+ scope.bubbleTemplate = templateKey;
+ scope.bubbleLayout = (goUp ? 'arw-btm' : 'arw-top') + ' ' +
+ (goLeft ? 'arw-right' : 'arw-left');
+ scope.bubbleTitle = title;
+
+ // Create the context menu
+ bubble = $compile(BUBBLE_TEMPLATE)(scope);
+
+ // Position the bubble
+ bubble.css('position', 'absolute');
+ if (goLeft) {
+ bubble.css('right', (winDim[0] - position[0] + OFFSET[0]) + 'px');
+ } else {
+ bubble.css('left', position[0] + OFFSET[0] + 'px');
+ }
+ if (goUp) {
+ bubble.css('bottom', (winDim[1] - position[1] + OFFSET[1]) + 'px');
+ } else {
+ bubble.css('top', position[1] + OFFSET[1] + 'px');
+ }
+
+ // Add the menu to the body
+ body.append(bubble);
+
+ // Return a function to dismiss the bubble
+ return function () { bubble.remove(); };
+ }
+
+ return {
+ /**
+ * Display an info bubble at the specified location.
+ * @param {string} templateKey template to place in bubble
+ * @param {string} title title for the bubble
+ * @param {*} content content to pass to the template, via
+ * `ng-model`
+ * @param {number[]} x,y position of the info bubble, in
+ * pixel coordinates.
+ * @returns {Function} a function that may be invoked to
+ * dismiss the info bubble
+ */
+ display: display
+ };
+ }
+
+ return InfoService;
+ }
+);
diff --git a/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js b/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js
new file mode 100644
index 0000000000..44f2b1e54b
--- /dev/null
+++ b/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js
@@ -0,0 +1,157 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
+
+define(
+ ['../../src/gestures/InfoGesture'],
+ function (InfoGesture) {
+ "use strict";
+
+ describe("The info gesture", function () {
+ var mockTimeout,
+ mockInfoService,
+ testDelay = 12321,
+ mockElement,
+ mockDomainObject,
+ mockScope,
+ mockOff,
+ testMetadata,
+ mockPromise,
+ mockHide,
+ gesture;
+
+ function fireEvent(evt, value) {
+ mockElement.on.calls.forEach(function (call) {
+ if (call.args[0] === evt) {
+ call.args[1](value);
+ }
+ });
+ }
+
+ beforeEach(function () {
+ mockTimeout = jasmine.createSpy('$timeout');
+ mockTimeout.cancel = jasmine.createSpy('cancel');
+ mockInfoService = jasmine.createSpyObj(
+ 'infoService',
+ [ 'display' ]
+ );
+ mockElement = jasmine.createSpyObj(
+ 'element',
+ [ 'on', 'off', 'scope', 'css' ]
+ );
+ mockDomainObject = jasmine.createSpyObj(
+ 'domainObject',
+ [ 'getId', 'getCapability', 'useCapability', 'getModel' ]
+ );
+ mockScope = jasmine.createSpyObj('$scope', [ '$on' ]);
+ mockOff = jasmine.createSpy('$off');
+ testMetadata = [ { name: "Test name", value: "Test value" } ];
+ mockPromise = jasmine.createSpyObj('promise', ['then']);
+ mockHide = jasmine.createSpy('hide');
+
+ mockDomainObject.getModel.andReturn({ name: "Test Object" });
+ mockDomainObject.useCapability.andCallFake(function (c) {
+ return (c === 'metadata') ? testMetadata : undefined;
+ });
+ mockElement.scope.andReturn(mockScope);
+ mockScope.$on.andReturn(mockOff);
+ mockTimeout.andReturn(mockPromise);
+ mockInfoService.display.andReturn(mockHide);
+
+ gesture = new InfoGesture(
+ mockTimeout,
+ mockInfoService,
+ testDelay,
+ mockElement,
+ mockDomainObject
+ );
+ });
+
+ it("listens for mouseenter on the representation", function () {
+ expect(mockElement.on)
+ .toHaveBeenCalledWith('mouseenter', jasmine.any(Function));
+ });
+
+ it("displays an info bubble on a delay after mouseenter", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ expect(mockTimeout)
+ .toHaveBeenCalledWith(jasmine.any(Function), testDelay);
+ mockTimeout.mostRecentCall.args[0]();
+ expect(mockInfoService.display).toHaveBeenCalledWith(
+ jasmine.any(String),
+ "Test Object",
+ testMetadata,
+ [ 1977, 42 ]
+ );
+ });
+
+ it("does not display info bubble if mouse leaves too soon", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ fireEvent("mouseleave", { clientX: 1977, clientY: 42 });
+ expect(mockTimeout.cancel).toHaveBeenCalledWith(mockPromise);
+ expect(mockInfoService.display).not.toHaveBeenCalled();
+ });
+
+ it("hides a shown bubble when mouse leaves", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ mockTimeout.mostRecentCall.args[0]();
+ expect(mockHide).not.toHaveBeenCalled(); // verify precondition
+ fireEvent("mouseleave", {});
+ expect(mockHide).toHaveBeenCalled();
+ });
+
+ it("tracks mouse position", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ fireEvent("mousemove", { clientX: 1999, clientY: 11 });
+ fireEvent("mousemove", { clientX: 1984, clientY: 11 });
+ mockTimeout.mostRecentCall.args[0]();
+ // Should have displayed at the latest observed mouse position
+ expect(mockInfoService.display).toHaveBeenCalledWith(
+ jasmine.any(String),
+ "Test Object",
+ testMetadata,
+ [ 1984, 11 ]
+ );
+ });
+
+ it("hides shown bubbles when destroyed", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ mockTimeout.mostRecentCall.args[0]();
+ expect(mockHide).not.toHaveBeenCalled(); // verify precondition
+ gesture.destroy();
+ expect(mockHide).toHaveBeenCalled();
+ });
+
+ it("detaches listeners when destroyed", function () {
+ fireEvent("mouseenter", { clientX: 1977, clientY: 42 });
+ gesture.destroy();
+ mockElement.on.calls.forEach(function (call) {
+ expect(mockElement.off).toHaveBeenCalledWith(
+ call.args[0],
+ call.args[1]
+ );
+ });
+ });
+
+ });
+ }
+);
diff --git a/platform/commonUI/inspect/test/services/InfoServiceSpec.js b/platform/commonUI/inspect/test/services/InfoServiceSpec.js
new file mode 100644
index 0000000000..858427fe84
--- /dev/null
+++ b/platform/commonUI/inspect/test/services/InfoServiceSpec.js
@@ -0,0 +1,131 @@
+/*****************************************************************************
+ * 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/services/InfoService', '../../src/InfoConstants'],
+ function (InfoService, InfoConstants) {
+ "use strict";
+
+ describe("The info service", function () {
+ var mockCompile,
+ mockDocument,
+ testWindow,
+ mockRootScope,
+ mockCompiledTemplate,
+ testScope,
+ mockBody,
+ mockElement,
+ service;
+
+ beforeEach(function () {
+ mockCompile = jasmine.createSpy('$compile');
+ mockDocument = jasmine.createSpyObj('$document', ['find']);
+ testWindow = { innerWidth: 1000, innerHeight: 100 };
+ mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
+ mockCompiledTemplate = jasmine.createSpy('template');
+ testScope = {};
+ mockBody = jasmine.createSpyObj('body', ['append']);
+ mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
+
+ mockDocument.find.andCallFake(function (tag) {
+ return tag === 'body' ? mockBody : undefined;
+ });
+ mockCompile.andReturn(mockCompiledTemplate);
+ mockCompiledTemplate.andReturn(mockElement);
+ mockRootScope.$new.andReturn(testScope);
+
+ service = new InfoService(
+ mockCompile,
+ mockDocument,
+ testWindow,
+ mockRootScope
+ );
+ });
+
+ it("creates elements and appends them to the body to display", function () {
+ service.display('', '', {}, [0, 0]);
+ expect(mockBody.append).toHaveBeenCalledWith(mockElement);
+ });
+
+ it("provides a function to remove displayed info bubbles", function () {
+ var fn = service.display('', '', {}, [0, 0]);
+ expect(mockElement.remove).not.toHaveBeenCalled();
+ fn();
+ expect(mockElement.remove).toHaveBeenCalled();
+ });
+
+ describe("depending on mouse position", function () {
+ // Positioning should vary based on quadrant in window,
+ // which is 1000 x 100 in this test case.
+ it("displays from the top-left in the top-left quadrant", function () {
+ service.display('', '', {}, [250, 25]);
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'left',
+ (250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
+ );
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'top',
+ (25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
+ );
+ });
+
+ it("displays from the top-right in the top-right quadrant", function () {
+ service.display('', '', {}, [700, 25]);
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'right',
+ (300 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
+ );
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'top',
+ (25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
+ );
+ });
+
+ it("displays from the bottom-left in the bottom-left quadrant", function () {
+ service.display('', '', {}, [250, 70]);
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'left',
+ (250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
+ );
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'bottom',
+ (30 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
+ );
+ });
+
+ it("displays from the bottom-right in the bottom-right quadrant", function () {
+ service.display('', '', {}, [800, 60]);
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'right',
+ (200 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
+ );
+ expect(mockElement.css).toHaveBeenCalledWith(
+ 'bottom',
+ (40 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
+ );
+ });
+ });
+
+ });
+ }
+);
diff --git a/platform/commonUI/inspect/test/suite.json b/platform/commonUI/inspect/test/suite.json
new file mode 100644
index 0000000000..82650e2dff
--- /dev/null
+++ b/platform/commonUI/inspect/test/suite.json
@@ -0,0 +1,4 @@
+[
+ "gestures/InfoGesture",
+ "services/InfoService"
+]
diff --git a/platform/core/bundle.json b/platform/core/bundle.json
index 5a2727eb75..97ee2d262f 100644
--- a/platform/core/bundle.json
+++ b/platform/core/bundle.json
@@ -165,6 +165,10 @@
"implementation": "capabilities/PersistenceCapability.js",
"depends": [ "persistenceService", "PERSISTENCE_SPACE" ]
},
+ {
+ "key": "metadata",
+ "implementation": "capabilities/MetadataCapability.js"
+ },
{
"key": "mutation",
"implementation": "capabilities/MutationCapability.js",
diff --git a/platform/core/src/capabilities/MetadataCapability.js b/platform/core/src/capabilities/MetadataCapability.js
new file mode 100644
index 0000000000..677dab0008
--- /dev/null
+++ b/platform/core/src/capabilities/MetadataCapability.js
@@ -0,0 +1,92 @@
+/*global define*/
+
+define(
+ ['moment'],
+ function (moment) {
+ "use strict";
+
+ /**
+ * A piece of information about a domain object.
+ * @typedef {Object} MetadataProperty
+ * @property {string} name the human-readable name of this property
+ * @property {string} value the human-readable value of this property,
+ * for this specific domain object
+ */
+
+ var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
+
+ /**
+ * Implements the `metadata` capability of a domain object, providing
+ * properties of that object for display.
+ *
+ * Usage: `domainObject.useCapability("metadata")`
+ *
+ * ...which will return an array of objects containing `name` and
+ * `value` properties describing that domain object (suitable for
+ * display.)
+ *
+ * @constructor
+ */
+ function MetadataCapability(domainObject) {
+ var model = domainObject.getModel();
+
+ function hasDisplayableValue(metadataProperty) {
+ var t = typeof metadataProperty.value;
+ return (t === 'string' || t === 'number');
+ }
+
+ function formatTimestamp(timestamp) {
+ return typeof timestamp === 'number' ?
+ (moment.utc(timestamp).format(TIME_FORMAT) + " UTC") :
+ undefined;
+ }
+
+ function getProperties() {
+ var type = domainObject.getCapability('type');
+
+ function lookupProperty(typeProperty) {
+ return {
+ name: typeProperty.getDefinition().name,
+ value: typeProperty.getValue(model)
+ };
+ }
+
+ return (type ? type.getProperties() : []).map(lookupProperty);
+ }
+
+ function getCommonMetadata() {
+ var type = domainObject.getCapability('type');
+ // Note that invalid values will be filtered out later
+ return [
+ {
+ name: "Updated",
+ value: formatTimestamp(model.modified)
+ },
+ {
+ name: "Type",
+ value: type && type.getName()
+ },
+ {
+ name: "ID",
+ value: domainObject.getId()
+ }
+ ];
+ }
+
+ function getMetadata() {
+ return getProperties().concat(getCommonMetadata())
+ .filter(hasDisplayableValue);
+ }
+
+ return {
+ /**
+ * Get metadata about this object.
+ * @returns {MetadataProperty[]} metadata about this object
+ */
+ invoke: getMetadata
+ };
+ }
+
+ return MetadataCapability;
+ }
+);
diff --git a/platform/core/test/capabilities/MetadataCapabilitySpec.js b/platform/core/test/capabilities/MetadataCapabilitySpec.js
new file mode 100644
index 0000000000..272746f037
--- /dev/null
+++ b/platform/core/test/capabilities/MetadataCapabilitySpec.js
@@ -0,0 +1,101 @@
+/*****************************************************************************
+ * 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/capabilities/MetadataCapability'],
+ function (MetadataCapability) {
+ "use strict";
+
+ describe("The metadata capability", function () {
+ var mockDomainObject,
+ mockType,
+ mockProperties,
+ testModel,
+ metadata;
+
+ function getCapability(key) {
+ return key === 'type' ? mockType : undefined;
+ }
+
+ function findValue(properties, name) {
+ var i;
+ for (i = 0; i < properties.length; i += 1) {
+ if (properties[i].name === name) {
+ return properties[i].value;
+ }
+ }
+ }
+
+ beforeEach(function () {
+ mockDomainObject = jasmine.createSpyObj(
+ 'domainObject',
+ ['getId', 'getCapability', 'useCapability', 'getModel']
+ );
+ mockType = jasmine.createSpyObj(
+ 'type',
+ ['getProperties', 'getName']
+ );
+ mockProperties = ['a', 'b', 'c'].map(function (k) {
+ var mockProperty = jasmine.createSpyObj(
+ 'property-' + k,
+ ['getValue', 'getDefinition']
+ );
+ mockProperty.getValue.andReturn("Value " + k);
+ mockProperty.getDefinition.andReturn({ name: "Property " + k});
+ return mockProperty;
+ });
+ testModel = { name: "" };
+
+ mockDomainObject.getId.andReturn("Test id");
+ mockDomainObject.getModel.andReturn(testModel);
+ mockDomainObject.getCapability.andCallFake(getCapability);
+ mockDomainObject.useCapability.andCallFake(getCapability);
+ mockType.getProperties.andReturn(mockProperties);
+ mockType.getName.andReturn("Test type");
+
+ metadata = new MetadataCapability(mockDomainObject);
+ });
+
+ it("reads properties from the domain object model", function () {
+ metadata.invoke();
+ mockProperties.forEach(function (mockProperty) {
+ expect(mockProperty.getValue).toHaveBeenCalledWith(testModel);
+ });
+ });
+
+ it("reports type-specific properties", function () {
+ var properties = metadata.invoke();
+ expect(findValue(properties, 'Property a')).toEqual("Value a");
+ expect(findValue(properties, 'Property b')).toEqual("Value b");
+ expect(findValue(properties, 'Property c')).toEqual("Value c");
+ });
+
+ it("reports generic properties", function () {
+ var properties = metadata.invoke();
+ expect(findValue(properties, 'ID')).toEqual("Test id");
+ expect(findValue(properties, 'Type')).toEqual("Test type");
+ });
+
+ });
+ }
+);
diff --git a/platform/core/test/suite.json b/platform/core/test/suite.json
index 36f3e81980..26749fa612 100644
--- a/platform/core/test/suite.json
+++ b/platform/core/test/suite.json
@@ -9,6 +9,7 @@
"capabilities/ContextualDomainObject",
"capabilities/CoreCapabilityProvider",
"capabilities/DelegationCapability",
+ "capabilities/MetadataCapability",
"capabilities/MutationCapability",
"capabilities/PersistenceCapability",
"capabilities/RelationshipCapability",
diff --git a/platform/forms/test/controllers/DateTimeControllerSpec.js b/platform/forms/test/controllers/DateTimeControllerSpec.js
index 11c97ada07..230d6a7a33 100644
--- a/platform/forms/test/controllers/DateTimeControllerSpec.js
+++ b/platform/forms/test/controllers/DateTimeControllerSpec.js
@@ -84,6 +84,22 @@ define(
// Should have cleared out the time stamp
expect(mockScope.ngModel.test).toBeUndefined();
});
+
+ it("initializes form fields with values from ng-model", function () {
+ mockScope.ngModel = { test: 1417215313000 };
+ mockScope.field = "test";
+ mockScope.$watch.calls.forEach(function (call) {
+ if (call.args[0] === 'ngModel[field]') {
+ call.args[1](mockScope.ngModel.test);
+ }
+ });
+ expect(mockScope.datetime).toEqual({
+ date: "2014-332",
+ hour: "22",
+ min: "55",
+ sec: "13"
+ });
+ });
});
}
-);
\ No newline at end of file
+);
diff --git a/platform/telemetry/src/TelemetryFormatter.js b/platform/telemetry/src/TelemetryFormatter.js
index 0bb13fbf6d..853db642d7 100644
--- a/platform/telemetry/src/TelemetryFormatter.js
+++ b/platform/telemetry/src/TelemetryFormatter.js
@@ -22,8 +22,8 @@
/*global define,moment*/
define(
- ['../lib/moment.min.js'],
- function () {
+ ['moment'],
+ function (moment) {
"use strict";
// Date format to use for domain values; in particular,