mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 05:37:53 +00:00
Merge remote-tracking branch 'github/master' into open117b
This commit is contained in:
commit
5f7c8ccadb
@ -238,6 +238,9 @@ Commit messages should:
|
||||
* Contain a reference to a relevant issue number in the body of the commit.
|
||||
* This is important for traceability; while branch names also provide this,
|
||||
you cannot tell from looking at a commit what branch it was authored on.
|
||||
* This may be omitted if the relevant issue is otherwise obvious from the
|
||||
commit history (that is, if using `git log` from the relevant commit
|
||||
directly leads to a similar issue reference) to minimize clutter.
|
||||
* Describe the change that was made, and any useful rationale therefore.
|
||||
* Comments in code should explain what things do, commit messages describe
|
||||
how they came to be done that way.
|
||||
|
@ -8,6 +8,11 @@
|
||||
"key": "urlService",
|
||||
"implementation": "services/UrlService.js",
|
||||
"depends": [ "$location" ]
|
||||
},
|
||||
{
|
||||
"key": "popupService",
|
||||
"implementation": "services/PopupService.js",
|
||||
"depends": [ "$document", "$window" ]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
@ -128,7 +133,7 @@
|
||||
{
|
||||
"key": "mctPopup",
|
||||
"implementation": "directives/MCTPopup.js",
|
||||
"depends": [ "$window", "$document", "$compile", "$interval" ]
|
||||
"depends": [ "$compile", "popupService" ]
|
||||
},
|
||||
{
|
||||
"key": "mctScrollX",
|
||||
|
@ -27,33 +27,36 @@ define(
|
||||
|
||||
var TEMPLATE = "<div></div>";
|
||||
|
||||
function MCTPopup($window, $document, $compile) {
|
||||
/**
|
||||
* The `mct-popup` directive may be used to display elements
|
||||
* which "pop up" over other parts of the page. Typically, this is
|
||||
* done in conjunction with an `ng-if` to control the visibility
|
||||
* of the popup.
|
||||
*
|
||||
* Example of usage:
|
||||
*
|
||||
* <mct-popup ng-if="someExpr">
|
||||
* <span>These are the contents of the popup!</span>
|
||||
* </mct-popup>
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/general
|
||||
* @param $compile Angular's $compile service
|
||||
* @param {platform/commonUI/general.PopupService} popupService
|
||||
*/
|
||||
function MCTPopup($compile, popupService) {
|
||||
function link(scope, element, attrs, ctrl, transclude) {
|
||||
var body = $document.find('body'),
|
||||
popup = $compile(TEMPLATE)(scope),
|
||||
winDim = [$window.innerWidth, $window.innerHeight],
|
||||
var div = $compile(TEMPLATE)(scope),
|
||||
rect = element.parent()[0].getBoundingClientRect(),
|
||||
position = [ rect.left, rect.top ],
|
||||
isLeft = position[0] <= (winDim[0] / 2),
|
||||
isTop = position[1] <= (winDim[1] / 2);
|
||||
|
||||
popup.css('position', 'absolute');
|
||||
popup.css(
|
||||
isLeft ? 'left' : 'right',
|
||||
(isLeft ? position[0] : (winDim[0] - position[0])) + 'px'
|
||||
);
|
||||
popup.css(
|
||||
isTop ? 'top' : 'bottom',
|
||||
(isTop ? position[1] : (winDim[1] - position[1])) + 'px'
|
||||
);
|
||||
body.append(popup);
|
||||
popup = popupService.display(div, position);
|
||||
|
||||
transclude(function (clone) {
|
||||
popup.append(clone);
|
||||
div.append(clone);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
popup.remove();
|
||||
popup.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
|
89
platform/commonUI/general/src/services/Popup.js
Normal file
89
platform/commonUI/general/src/services/Popup.js
Normal file
@ -0,0 +1,89 @@
|
||||
/*****************************************************************************
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* A popup is an element that has been displayed at a particular
|
||||
* location within the page.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/general
|
||||
* @param element the jqLite-wrapped element
|
||||
* @param {object} styles an object containing key-value pairs
|
||||
* of styles used to position the element.
|
||||
*/
|
||||
function Popup(element, styles) {
|
||||
this.styles = styles;
|
||||
this.element = element;
|
||||
|
||||
element.css(styles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop showing this popup.
|
||||
*/
|
||||
Popup.prototype.dismiss = function () {
|
||||
this.element.remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this popup is positioned such that it appears to the
|
||||
* left of its original location.
|
||||
* @returns {boolean} true if the popup goes left
|
||||
*/
|
||||
Popup.prototype.goesLeft = function () {
|
||||
return !this.styles.left;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this popup is positioned such that it appears to the
|
||||
* right of its original location.
|
||||
* @returns {boolean} true if the popup goes right
|
||||
*/
|
||||
Popup.prototype.goesRight = function () {
|
||||
return !this.styles.right;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this popup is positioned such that it appears above
|
||||
* its original location.
|
||||
* @returns {boolean} true if the popup goes up
|
||||
*/
|
||||
Popup.prototype.goesUp = function () {
|
||||
return !this.styles.top;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this popup is positioned such that it appears below
|
||||
* its original location.
|
||||
* @returns {boolean} true if the popup goes down
|
||||
*/
|
||||
Popup.prototype.goesDown = function () {
|
||||
return !this.styles.bottom;
|
||||
};
|
||||
|
||||
return Popup;
|
||||
}
|
||||
);
|
127
platform/commonUI/general/src/services/PopupService.js
Normal file
127
platform/commonUI/general/src/services/PopupService.js
Normal file
@ -0,0 +1,127 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['./Popup'],
|
||||
function (Popup) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Displays popup elements at specific positions within the document.
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
function PopupService($document, $window) {
|
||||
this.$document = $document;
|
||||
this.$window = $window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options controlling how the popup is displaed.
|
||||
*
|
||||
* @typedef PopupOptions
|
||||
* @memberof platform/commonUI/general
|
||||
* @property {number} [offsetX] the horizontal distance, in pixels,
|
||||
* to offset the element in whichever direction it is
|
||||
* displayed. Defaults to 0.
|
||||
* @property {number} [offsetY] the vertical distance, in pixels,
|
||||
* to offset the element in whichever direction it is
|
||||
* displayed. Defaults to 0.
|
||||
* @property {number} [marginX] the horizontal position, in pixels,
|
||||
* after which to prefer to display the element to the left.
|
||||
* If negative, this is relative to the right edge of the
|
||||
* page. Defaults to half the window's width.
|
||||
* @property {number} [marginY] the vertical position, in pixels,
|
||||
* after which to prefer to display the element upward.
|
||||
* If negative, this is relative to the right edge of the
|
||||
* page. Defaults to half the window's height.
|
||||
* @property {string} [leftClass] class to apply when shifting to the left
|
||||
* @property {string} [rightClass] class to apply when shifting to the right
|
||||
* @property {string} [upClass] class to apply when shifting upward
|
||||
* @property {string} [downClass] class to apply when shifting downward
|
||||
*/
|
||||
|
||||
/**
|
||||
* Display a popup at a particular location. The location chosen will
|
||||
* be the corner of the element; the element will be positioned either
|
||||
* to the left or the right of this point depending on available
|
||||
* horizontal space, and will similarly be shifted upward or downward
|
||||
* depending on available vertical space.
|
||||
*
|
||||
* @param element the jqLite-wrapped DOM element to pop up
|
||||
* @param {number[]} position x,y position of the element, in
|
||||
* pixel coordinates. Negative values are interpreted as
|
||||
* relative to the right or bottom of the window.
|
||||
* @param {PopupOptions} [options] additional options to control
|
||||
* positioning of the popup
|
||||
* @returns {platform/commonUI/general.Popup} the popup
|
||||
*/
|
||||
PopupService.prototype.display = function (element, position, options) {
|
||||
var $document = this.$document,
|
||||
$window = this.$window,
|
||||
body = $document.find('body'),
|
||||
winDim = [ $window.innerWidth, $window.innerHeight ],
|
||||
styles = { position: 'absolute' },
|
||||
margin,
|
||||
offset,
|
||||
bubble;
|
||||
|
||||
function adjustNegatives(value, index) {
|
||||
return value < 0 ? (value + winDim[index]) : value;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
options = options || {};
|
||||
offset = [
|
||||
options.offsetX !== undefined ? options.offsetX : 0,
|
||||
options.offsetY !== undefined ? options.offsetY : 0
|
||||
];
|
||||
margin = [ options.marginX, options.marginY ].map(function (m, i) {
|
||||
return m === undefined ? (winDim[i] / 2) : m;
|
||||
}).map(adjustNegatives);
|
||||
|
||||
position = position.map(adjustNegatives);
|
||||
|
||||
if (position[0] > margin[0]) {
|
||||
styles.right = (winDim[0] - position[0] + offset[0]) + 'px';
|
||||
} else {
|
||||
styles.left = (position[0] + offset[0]) + 'px';
|
||||
}
|
||||
|
||||
if (position[1] > margin[1]) {
|
||||
styles.bottom = (winDim[1] - position[1] + offset[1]) + 'px';
|
||||
} else {
|
||||
styles.top = (position[1] + offset[1]) + 'px';
|
||||
}
|
||||
|
||||
// Add the menu to the body
|
||||
body.append(element);
|
||||
|
||||
// Return a function to dismiss the bubble
|
||||
return new Popup(element, styles);
|
||||
};
|
||||
|
||||
return PopupService;
|
||||
}
|
||||
);
|
||||
|
@ -29,15 +29,16 @@ define(
|
||||
var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
|
||||
|
||||
describe("The mct-popup directive", function () {
|
||||
var testWindow,
|
||||
mockDocument,
|
||||
mockCompile,
|
||||
var mockCompile,
|
||||
mockPopupService,
|
||||
mockPopup,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockBody,
|
||||
mockTransclude,
|
||||
mockParentEl,
|
||||
mockNewElement,
|
||||
testRect,
|
||||
mctPopup;
|
||||
|
||||
@ -50,12 +51,12 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
testWindow =
|
||||
{ innerWidth: 600, innerHeight: 300 };
|
||||
mockDocument =
|
||||
jasmine.createSpyObj("$document", JQLITE_METHODS);
|
||||
mockCompile =
|
||||
jasmine.createSpy("$compile");
|
||||
mockPopupService =
|
||||
jasmine.createSpyObj("popupService", ["display"]);
|
||||
mockPopup =
|
||||
jasmine.createSpyObj("popup", ["dismiss"]);
|
||||
mockScope =
|
||||
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
|
||||
mockElement =
|
||||
@ -66,6 +67,8 @@ define(
|
||||
jasmine.createSpy("transclude");
|
||||
mockParentEl =
|
||||
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
||||
mockNewElement =
|
||||
jasmine.createSpyObj("newElement", JQLITE_METHODS);
|
||||
|
||||
testAttrs = {
|
||||
mctClickElsewhere: "some Angular expression"
|
||||
@ -77,15 +80,17 @@ define(
|
||||
height: 75
|
||||
};
|
||||
|
||||
mockDocument.find.andReturn(mockBody);
|
||||
mockCompile.andReturn(jasmine.createSpy());
|
||||
mockCompile().andCallFake(function () {
|
||||
return jasmine.createSpyObj("newElement", JQLITE_METHODS);
|
||||
mockCompile.andCallFake(function () {
|
||||
var mockFn = jasmine.createSpy();
|
||||
mockFn.andReturn(mockNewElement);
|
||||
return mockFn;
|
||||
});
|
||||
mockElement.parent.andReturn([mockParentEl]);
|
||||
mockParentEl.getBoundingClientRect.andReturn(testRect);
|
||||
mockPopupService.display.andReturn(mockPopup);
|
||||
|
||||
mctPopup = new MCTPopup(mockCompile, mockPopupService);
|
||||
|
||||
mctPopup = new MCTPopup(testWindow, mockDocument, mockCompile);
|
||||
mctPopup.link(
|
||||
mockScope,
|
||||
mockElement,
|
||||
@ -99,6 +104,32 @@ define(
|
||||
expect(mctPopup.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
describe("creates an element which", function () {
|
||||
it("displays as a popup", function () {
|
||||
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||
mockNewElement,
|
||||
[ testRect.left, testRect.top ]
|
||||
);
|
||||
});
|
||||
|
||||
it("displays transcluded content", function () {
|
||||
var mockClone =
|
||||
jasmine.createSpyObj('clone', JQLITE_METHODS);
|
||||
mockTransclude.mostRecentCall.args[0](mockClone);
|
||||
expect(mockNewElement.append)
|
||||
.toHaveBeenCalledWith(mockClone);
|
||||
});
|
||||
|
||||
it("is removed when its containing scope is destroyed", function () {
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === '$destroy') {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
98
platform/commonUI/general/test/services/PopupServiceSpec.js
Normal file
98
platform/commonUI/general/test/services/PopupServiceSpec.js
Normal file
@ -0,0 +1,98 @@
|
||||
/*****************************************************************************
|
||||
* 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/PopupService"],
|
||||
function (PopupService) {
|
||||
'use strict';
|
||||
|
||||
describe("PopupService", function () {
|
||||
var mockDocument,
|
||||
testWindow,
|
||||
mockBody,
|
||||
mockElement,
|
||||
popupService;
|
||||
|
||||
beforeEach(function () {
|
||||
mockDocument = jasmine.createSpyObj('$document', [ 'find' ]);
|
||||
testWindow = { innerWidth: 1000, innerHeight: 800 };
|
||||
mockBody = jasmine.createSpyObj('body', [ 'append' ]);
|
||||
mockElement = jasmine.createSpyObj('element', [
|
||||
'css',
|
||||
'remove'
|
||||
]);
|
||||
|
||||
mockDocument.find.andCallFake(function (query) {
|
||||
return query === 'body' && mockBody;
|
||||
});
|
||||
|
||||
popupService = new PopupService(mockDocument, testWindow);
|
||||
});
|
||||
|
||||
it("adds elements to the body of the document", function () {
|
||||
popupService.display(mockElement, [ 0, 0 ]);
|
||||
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
|
||||
});
|
||||
|
||||
describe("when positioned in appropriate quadrants", function () {
|
||||
it("orients elements relative to the top-left", function () {
|
||||
popupService.display(mockElement, [ 25, 50 ]);
|
||||
expect(mockElement.css).toHaveBeenCalledWith({
|
||||
position: 'absolute',
|
||||
left: '25px',
|
||||
top: '50px'
|
||||
});
|
||||
});
|
||||
|
||||
it("orients elements relative to the top-right", function () {
|
||||
popupService.display(mockElement, [ 800, 50 ]);
|
||||
expect(mockElement.css).toHaveBeenCalledWith({
|
||||
position: 'absolute',
|
||||
right: '200px',
|
||||
top: '50px'
|
||||
});
|
||||
});
|
||||
|
||||
it("orients elements relative to the bottom-right", function () {
|
||||
popupService.display(mockElement, [ 800, 650 ]);
|
||||
expect(mockElement.css).toHaveBeenCalledWith({
|
||||
position: 'absolute',
|
||||
right: '200px',
|
||||
bottom: '150px'
|
||||
});
|
||||
});
|
||||
|
||||
it("orients elements relative to the bottom-left", function () {
|
||||
popupService.display(mockElement, [ 120, 650 ]);
|
||||
expect(mockElement.css).toHaveBeenCalledWith({
|
||||
position: 'absolute',
|
||||
left: '120px',
|
||||
bottom: '150px'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
74
platform/commonUI/general/test/services/PopupSpec.js
Normal file
74
platform/commonUI/general/test/services/PopupSpec.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*****************************************************************************
|
||||
* 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/Popup"],
|
||||
function (Popup) {
|
||||
'use strict';
|
||||
|
||||
describe("Popup", function () {
|
||||
var mockElement,
|
||||
testStyles,
|
||||
popup;
|
||||
|
||||
beforeEach(function () {
|
||||
mockElement =
|
||||
jasmine.createSpyObj('element', [ 'css', 'remove' ]);
|
||||
testStyles = { left: '12px', top: '14px' };
|
||||
popup = new Popup(mockElement, testStyles);
|
||||
});
|
||||
|
||||
it("applies CSS styles when instantiated", function () {
|
||||
expect(mockElement.css)
|
||||
.toHaveBeenCalledWith(testStyles);
|
||||
});
|
||||
|
||||
it("reports the orientation of the popup", function () {
|
||||
var otherStyles = {
|
||||
right: '12px',
|
||||
bottom: '14px'
|
||||
},
|
||||
otherPopup = new Popup(mockElement, otherStyles);
|
||||
|
||||
expect(popup.goesLeft()).toBeFalsy();
|
||||
expect(popup.goesRight()).toBeTruthy();
|
||||
expect(popup.goesUp()).toBeFalsy();
|
||||
expect(popup.goesDown()).toBeTruthy();
|
||||
|
||||
expect(otherPopup.goesLeft()).toBeTruthy();
|
||||
expect(otherPopup.goesRight()).toBeFalsy();
|
||||
expect(otherPopup.goesUp()).toBeTruthy();
|
||||
expect(otherPopup.goesDown()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("removes elements when dismissed", function () {
|
||||
expect(mockElement.remove).not.toHaveBeenCalled();
|
||||
popup.dismiss();
|
||||
expect(mockElement.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
);
|
@ -17,6 +17,8 @@
|
||||
"directives/MCTPopup",
|
||||
"directives/MCTResize",
|
||||
"directives/MCTScroll",
|
||||
"services/Popup",
|
||||
"services/PopupService",
|
||||
"services/UrlService",
|
||||
"StyleSheetLoader"
|
||||
]
|
||||
|
@ -45,13 +45,12 @@
|
||||
"implementation": "services/InfoService.js",
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$document",
|
||||
"$window",
|
||||
"$rootScope",
|
||||
"popupService",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "INFO_HOVER_DELAY",
|
||||
@ -66,4 +65,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,13 +31,19 @@ define({
|
||||
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
|
||||
"bubble-title=\"{{bubbleTitle}}\" " +
|
||||
"bubble-layout=\"{{bubbleLayout}}\" " +
|
||||
"class=\"bubble-container\">" +
|
||||
"<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
|
||||
"class=\"bubble-container\">" +
|
||||
"<mct-include key=\"bubbleTemplate\" " +
|
||||
"ng-model=\"bubbleModel\">" +
|
||||
"</mct-include>" +
|
||||
"</mct-container>",
|
||||
// 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
|
||||
// Options and classes for bubble
|
||||
BUBBLE_OPTIONS: {
|
||||
offsetX: 0,
|
||||
offsetY: -26
|
||||
},
|
||||
BUBBLE_MOBILE_POSITION: [ 0, -25 ],
|
||||
// Max width and margins allowed for bubbles;
|
||||
// defined in /platform/commonUI/general/res/sass/_constants.scss
|
||||
BUBBLE_MARGIN_LR: 10,
|
||||
BUBBLE_MAX_WIDTH: 300
|
||||
});
|
||||
|
@ -27,18 +27,18 @@ define(
|
||||
"use strict";
|
||||
|
||||
var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
|
||||
OFFSET = InfoConstants.BUBBLE_OFFSET;
|
||||
MOBILE_POSITION = InfoConstants.BUBBLE_MOBILE_POSITION,
|
||||
OPTIONS = InfoConstants.BUBBLE_OPTIONS;
|
||||
|
||||
/**
|
||||
* Displays informative content ("info bubbles") for the user.
|
||||
* @memberof platform/commonUI/inspect
|
||||
* @constructor
|
||||
*/
|
||||
function InfoService($compile, $document, $window, $rootScope, agentService) {
|
||||
function InfoService($compile, $rootScope, popupService, agentService) {
|
||||
this.$compile = $compile;
|
||||
this.$document = $document;
|
||||
this.$window = $window;
|
||||
this.$rootScope = $rootScope;
|
||||
this.popupService = popupService;
|
||||
this.agentService = agentService;
|
||||
}
|
||||
|
||||
@ -55,53 +55,47 @@ define(
|
||||
*/
|
||||
InfoService.prototype.display = function (templateKey, title, content, position) {
|
||||
var $compile = this.$compile,
|
||||
$document = this.$document,
|
||||
$window = this.$window,
|
||||
$rootScope = this.$rootScope,
|
||||
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),
|
||||
span = $compile('<span></span>')(scope),
|
||||
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR +
|
||||
InfoConstants.BUBBLE_MAX_WIDTH,
|
||||
options,
|
||||
popup,
|
||||
bubble;
|
||||
|
||||
|
||||
options = Object.create(OPTIONS);
|
||||
options.marginX = -bubbleSpaceLR;
|
||||
|
||||
// On a phone, bubble takes up more screen real estate,
|
||||
// so position it differently (toward the bottom)
|
||||
if (this.agentService.isPhone(navigator.userAgent)) {
|
||||
position = MOBILE_POSITION;
|
||||
options = {};
|
||||
}
|
||||
|
||||
popup = this.popupService.display(span, position, options);
|
||||
|
||||
// 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;
|
||||
// Style the bubble according to how it was positioned
|
||||
scope.bubbleLayout = [
|
||||
popup.goesUp() ? 'arw-btm' : 'arw-top',
|
||||
popup.goesLeft() ? 'arw-right' : 'arw-left'
|
||||
].join(' ');
|
||||
scope.bubbleLayout = 'arw-top arw-left';
|
||||
|
||||
// Create the context menu
|
||||
// Create the info bubble, now that we know how to
|
||||
// point the arrow...
|
||||
bubble = $compile(BUBBLE_TEMPLATE)(scope);
|
||||
span.append(bubble);
|
||||
|
||||
// Position the bubble
|
||||
bubble.css('position', 'absolute');
|
||||
if (this.agentService.isPhone(navigator.userAgent)) {
|
||||
bubble.css('right', '0px');
|
||||
bubble.css('left', '0px');
|
||||
bubble.css('top', 'auto');
|
||||
bubble.css('bottom', '25px');
|
||||
} else {
|
||||
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 a function to dismiss the info bubble
|
||||
return function dismiss() {
|
||||
popup.dismiss();
|
||||
scope.$destroy();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -28,117 +28,85 @@ define(
|
||||
|
||||
describe("The info service", function () {
|
||||
var mockCompile,
|
||||
mockDocument,
|
||||
testWindow,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService,
|
||||
mockCompiledTemplate,
|
||||
testScope,
|
||||
mockBody,
|
||||
mockElement,
|
||||
mockScope,
|
||||
mockElements,
|
||||
mockPopup,
|
||||
service;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCompile = jasmine.createSpy('$compile');
|
||||
mockDocument = jasmine.createSpyObj('$document', ['find']);
|
||||
testWindow = { innerWidth: 1000, innerHeight: 100 };
|
||||
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
|
||||
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
|
||||
mockCompiledTemplate = jasmine.createSpy('template');
|
||||
testScope = {};
|
||||
mockBody = jasmine.createSpyObj('body', ['append']);
|
||||
mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
|
||||
mockPopupService = jasmine.createSpyObj(
|
||||
'popupService',
|
||||
['display']
|
||||
);
|
||||
mockPopup = jasmine.createSpyObj('popup', [
|
||||
'dismiss',
|
||||
'goesLeft',
|
||||
'goesRight',
|
||||
'goesUp',
|
||||
'goesDown'
|
||||
]);
|
||||
|
||||
mockDocument.find.andCallFake(function (tag) {
|
||||
return tag === 'body' ? mockBody : undefined;
|
||||
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||
mockElements = [];
|
||||
|
||||
mockPopupService.display.andReturn(mockPopup);
|
||||
mockCompile.andCallFake(function () {
|
||||
var mockCompiledTemplate = jasmine.createSpy('template'),
|
||||
mockElement = jasmine.createSpyObj('element', [
|
||||
'css',
|
||||
'remove',
|
||||
'append'
|
||||
]);
|
||||
mockCompiledTemplate.andReturn(mockElement);
|
||||
mockElements.push(mockElement);
|
||||
return mockCompiledTemplate;
|
||||
});
|
||||
mockCompile.andReturn(mockCompiledTemplate);
|
||||
mockCompiledTemplate.andReturn(mockElement);
|
||||
mockRootScope.$new.andReturn(testScope);
|
||||
mockRootScope.$new.andReturn(mockScope);
|
||||
|
||||
service = new InfoService(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
testWindow,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService
|
||||
);
|
||||
});
|
||||
|
||||
it("creates elements and appends them to the body to display", function () {
|
||||
service.display('', '', {}, [0, 0]);
|
||||
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
|
||||
it("creates elements and displays them as popups", function () {
|
||||
service.display('', '', {}, [123, 456]);
|
||||
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||
mockElements[0],
|
||||
[ 123, 456 ],
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("provides a function to remove displayed info bubbles", function () {
|
||||
var fn = service.display('', '', {}, [0, 0]);
|
||||
expect(mockElement.remove).not.toHaveBeenCalled();
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
fn();
|
||||
expect(mockElement.remove).toHaveBeenCalled();
|
||||
expect(mockPopup.dismiss).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'
|
||||
);
|
||||
});
|
||||
|
||||
it("when on phone device, positioning is always on bottom", function () {
|
||||
mockAgentService.isPhone.andReturn(true);
|
||||
service = new InfoService(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
testWindow,
|
||||
mockRootScope,
|
||||
mockAgentService
|
||||
);
|
||||
service.display('', '', {}, [0, 0]);
|
||||
});
|
||||
it("when on phone device, positions at bottom", function () {
|
||||
mockAgentService.isPhone.andReturn(true);
|
||||
service = new InfoService(
|
||||
mockCompile,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService
|
||||
);
|
||||
service.display('', '', {}, [123, 456]);
|
||||
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||
mockElements[0],
|
||||
[ 0, -25 ],
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -3,7 +3,12 @@
|
||||
"representers": [
|
||||
{
|
||||
"implementation": "ConductorRepresenter.js",
|
||||
"depends": [ "conductorService", "$compile", "views[]" ]
|
||||
"depends": [
|
||||
"throttle",
|
||||
"conductorService",
|
||||
"$compile",
|
||||
"views[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
|
@ -31,6 +31,7 @@ define(
|
||||
"<mct-include key=\"'time-controller'\" ng-model='conductor'>",
|
||||
"</mct-include>"
|
||||
].join(''),
|
||||
THROTTLE_MS = 200,
|
||||
GLOBAL_SHOWING = false;
|
||||
|
||||
/**
|
||||
@ -40,6 +41,8 @@ define(
|
||||
* @implements {Representer}
|
||||
* @constructor
|
||||
* @memberof platform/features/conductor
|
||||
* @param {Function} throttle a function used to reduce the frequency
|
||||
* of function invocations
|
||||
* @param {platform/features/conductor.ConductorService} conductorService
|
||||
* service which provides the active time conductor
|
||||
* @param $compile Angular's $compile
|
||||
@ -47,7 +50,15 @@ define(
|
||||
* @param {Scope} the scope of the representation
|
||||
* @param element the jqLite-wrapped representation element
|
||||
*/
|
||||
function ConductorRepresenter(conductorService, $compile, views, scope, element) {
|
||||
function ConductorRepresenter(
|
||||
throttle,
|
||||
conductorService,
|
||||
$compile,
|
||||
views,
|
||||
scope,
|
||||
element
|
||||
) {
|
||||
this.throttle = throttle;
|
||||
this.scope = scope;
|
||||
this.conductorService = conductorService;
|
||||
this.element = element;
|
||||
@ -55,51 +66,59 @@ define(
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
// Combine start/end times into a single object
|
||||
function bounds(start, end) {
|
||||
return { start: start, end: end };
|
||||
}
|
||||
|
||||
// Update the time conductor from the scope
|
||||
function wireScope(conductor, conductorScope, repScope) {
|
||||
function updateConductorOuter() {
|
||||
conductor.queryStart(conductorScope.conductor.outer.start);
|
||||
conductor.queryEnd(conductorScope.conductor.outer.end);
|
||||
repScope.$broadcast(
|
||||
'telemetry:query:bounds',
|
||||
bounds(conductor.queryStart(), conductor.queryEnd())
|
||||
);
|
||||
ConductorRepresenter.prototype.wireScope = function () {
|
||||
var conductor = this.conductorService.getConductor(),
|
||||
conductorScope = this.conductorScope(),
|
||||
repScope = this.scope,
|
||||
lastObservedBounds,
|
||||
broadcastBounds;
|
||||
|
||||
// Combine start/end times into a single object
|
||||
function bounds(start, end) {
|
||||
return {
|
||||
start: conductor.displayStart(),
|
||||
end: conductor.displayEnd()
|
||||
};
|
||||
}
|
||||
|
||||
function boundsAreStable(newlyObservedBounds) {
|
||||
return !lastObservedBounds ||
|
||||
(lastObservedBounds.start === newlyObservedBounds.start &&
|
||||
lastObservedBounds.end === newlyObservedBounds.end);
|
||||
}
|
||||
|
||||
function updateConductorInner() {
|
||||
conductor.displayStart(conductorScope.conductor.inner.start);
|
||||
conductor.displayEnd(conductorScope.conductor.inner.end);
|
||||
repScope.$broadcast(
|
||||
'telemetry:display:bounds',
|
||||
bounds(conductor.displayStart(), conductor.displayEnd())
|
||||
);
|
||||
lastObservedBounds = lastObservedBounds || bounds();
|
||||
broadcastBounds();
|
||||
}
|
||||
|
||||
conductorScope.conductor = {
|
||||
outer: bounds(conductor.queryStart(), conductor.queryEnd()),
|
||||
inner: bounds(conductor.displayStart(), conductor.displayEnd())
|
||||
};
|
||||
broadcastBounds = this.throttle(function () {
|
||||
var newlyObservedBounds = bounds();
|
||||
|
||||
if (boundsAreStable(newlyObservedBounds)) {
|
||||
repScope.$broadcast('telemetry:display:bounds', bounds());
|
||||
lastObservedBounds = undefined;
|
||||
} else {
|
||||
lastObservedBounds = newlyObservedBounds;
|
||||
broadcastBounds();
|
||||
}
|
||||
}, THROTTLE_MS);
|
||||
|
||||
conductorScope.conductor = { outer: bounds(), inner: bounds() };
|
||||
|
||||
conductorScope
|
||||
.$watch('conductor.outer.start', updateConductorOuter);
|
||||
conductorScope
|
||||
.$watch('conductor.outer.end', updateConductorOuter);
|
||||
conductorScope
|
||||
.$watch('conductor.inner.start', updateConductorInner);
|
||||
conductorScope
|
||||
.$watch('conductor.inner.end', updateConductorInner);
|
||||
|
||||
repScope.$on('telemetry:view', updateConductorInner);
|
||||
}
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.conductorScope = function (s) {
|
||||
return (this.cScope = arguments.length > 0 ?
|
||||
s : this.cScope);
|
||||
return (this.cScope = arguments.length > 0 ? s : this.cScope);
|
||||
};
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
@ -113,11 +132,7 @@ define(
|
||||
|
||||
// Create a new scope for the conductor
|
||||
this.conductorScope(this.scope.$new());
|
||||
wireScope(
|
||||
this.conductorService.getConductor(),
|
||||
this.conductorScope(),
|
||||
this.scope
|
||||
);
|
||||
this.wireScope();
|
||||
this.conductorElement =
|
||||
this.$compile(TEMPLATE)(this.conductorScope());
|
||||
this.element.after(this.conductorElement[0]);
|
||||
|
@ -22,8 +22,7 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
['./ConductorTelemetrySeries'],
|
||||
function (ConductorTelemetrySeries) {
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
@ -42,32 +41,6 @@ define(
|
||||
this.telemetryService = telemetryService;
|
||||
}
|
||||
|
||||
// Strip out any realtime data series that is outside of the conductor's
|
||||
// bounds.
|
||||
ConductorTelemetryDecorator.prototype.pruneNonDisplayable = function (packaged) {
|
||||
var conductor = this.conductorService.getConductor(),
|
||||
repackaged = {};
|
||||
|
||||
function filterSource(packagedBySource) {
|
||||
var repackagedBySource = {};
|
||||
|
||||
Object.keys(packagedBySource).forEach(function (k) {
|
||||
repackagedBySource[k] = new ConductorTelemetrySeries(
|
||||
packagedBySource[k],
|
||||
conductor
|
||||
);
|
||||
});
|
||||
|
||||
return repackagedBySource;
|
||||
}
|
||||
|
||||
Object.keys(packaged).forEach(function (source) {
|
||||
repackaged[source] = filterSource(packaged[source]);
|
||||
});
|
||||
|
||||
return repackaged;
|
||||
};
|
||||
|
||||
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
|
||||
var conductor = this.conductorService.getConductor(),
|
||||
start = conductor.displayStart(),
|
||||
@ -86,21 +59,14 @@ define(
|
||||
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
|
||||
var self = this;
|
||||
return this.telemetryService
|
||||
.requestTelemetry(this.amendRequests(requests))
|
||||
.then(function (packaged) {
|
||||
return self.pruneNonDisplayable(packaged);
|
||||
});
|
||||
.requestTelemetry(this.amendRequests(requests));
|
||||
};
|
||||
|
||||
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
|
||||
var self = this;
|
||||
|
||||
function internalCallback(packagedSeries) {
|
||||
return callback(self.pruneNonDisplayable(packagedSeries));
|
||||
}
|
||||
|
||||
return this.telemetryService
|
||||
.subscribe(internalCallback, this.amendRequests(requests));
|
||||
.subscribe(callback, this.amendRequests(requests));
|
||||
};
|
||||
|
||||
return ConductorTelemetryDecorator;
|
||||
|
@ -1,71 +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';
|
||||
|
||||
|
||||
/**
|
||||
* Bound a series of telemetry such that it only includes
|
||||
* points from within the time conductor's displayable window.
|
||||
*
|
||||
* @param {TelemetrySeries} series the telemetry series
|
||||
* @param {platform/features/conductor.TimeConductor} the
|
||||
* time conductor instance which bounds this series
|
||||
* @constructor
|
||||
* @implements {TelemetrySeries}
|
||||
*/
|
||||
function ConductorTelemetrySeries(series, conductor) {
|
||||
var max = series.getPointCount() - 1;
|
||||
|
||||
function binSearch(min, max, value) {
|
||||
var mid = Math.floor((min + max) / 2);
|
||||
|
||||
return min > max ? min :
|
||||
series.getDomainValue(mid) < value ?
|
||||
binSearch(mid + 1, max, value) :
|
||||
binSearch(min, mid - 1, value);
|
||||
}
|
||||
|
||||
this.startIndex = binSearch(0, max, conductor.displayStart());
|
||||
this.endIndex = binSearch(0, max, conductor.displayEnd());
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
ConductorTelemetrySeries.prototype.getPointCount = function () {
|
||||
return Math.max(0, this.endIndex - this.startIndex);
|
||||
};
|
||||
|
||||
ConductorTelemetrySeries.prototype.getDomainValue = function (i, d) {
|
||||
return this.series.getDomainValue(i + this.startIndex, d);
|
||||
};
|
||||
|
||||
ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) {
|
||||
return this.series.getRangeValue(i + this.startIndex, r);
|
||||
};
|
||||
|
||||
return ConductorTelemetrySeries;
|
||||
}
|
||||
);
|
@ -41,35 +41,9 @@ define(
|
||||
* @param {number} end the initial end time
|
||||
*/
|
||||
function TimeConductor(start, end) {
|
||||
this.inner = { start: start, end: end };
|
||||
this.outer = { start: start, end: end };
|
||||
this.range = { start: start, end: end };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the start time for queries.
|
||||
* @param {number} [value] the start time to set
|
||||
* @returns {number} the start time
|
||||
*/
|
||||
TimeConductor.prototype.queryStart = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.outer.start = value;
|
||||
}
|
||||
return this.outer.start;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the end time for queries.
|
||||
* @param {number} [value] the end time to set
|
||||
* @returns {number} the end time
|
||||
*/
|
||||
TimeConductor.prototype.queryEnd = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.outer.end = value;
|
||||
}
|
||||
return this.outer.end;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get or set (if called with an argument) the start time for displays.
|
||||
* @param {number} [value] the start time to set
|
||||
@ -77,9 +51,9 @@ define(
|
||||
*/
|
||||
TimeConductor.prototype.displayStart = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.inner.start = value;
|
||||
this.range.start = value;
|
||||
}
|
||||
return this.inner.start;
|
||||
return this.range.start;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -89,9 +63,9 @@ define(
|
||||
*/
|
||||
TimeConductor.prototype.displayEnd = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.inner.end = value;
|
||||
this.range.end = value;
|
||||
}
|
||||
return this.inner.end;
|
||||
return this.range.end;
|
||||
};
|
||||
|
||||
return TimeConductor;
|
||||
|
@ -47,7 +47,8 @@ define(
|
||||
];
|
||||
|
||||
describe("ConductorRepresenter", function () {
|
||||
var mockConductorService,
|
||||
var mockThrottle,
|
||||
mockConductorService,
|
||||
mockCompile,
|
||||
testViews,
|
||||
mockScope,
|
||||
@ -67,6 +68,7 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockThrottle = jasmine.createSpy('throttle');
|
||||
mockConductorService = jasmine.createSpyObj(
|
||||
'conductorService',
|
||||
['getConductor']
|
||||
@ -77,7 +79,7 @@ define(
|
||||
mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS);
|
||||
mockConductor = jasmine.createSpyObj(
|
||||
'conductor',
|
||||
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||
[ 'displayStart', 'displayEnd' ]
|
||||
);
|
||||
mockCompiledTemplate = jasmine.createSpy('template');
|
||||
mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS);
|
||||
@ -88,8 +90,12 @@ define(
|
||||
mockCompile.andReturn(mockCompiledTemplate);
|
||||
mockCompiledTemplate.andReturn(mockNewElement);
|
||||
mockScope.$new.andReturn(mockNewScope);
|
||||
mockThrottle.andCallFake(function (fn) {
|
||||
return fn;
|
||||
});
|
||||
|
||||
representer = new ConductorRepresenter(
|
||||
mockThrottle,
|
||||
mockConductorService,
|
||||
mockCompile,
|
||||
testViews,
|
||||
@ -127,15 +133,13 @@ define(
|
||||
});
|
||||
|
||||
it("exposes conductor state in scope", function () {
|
||||
mockConductor.queryStart.andReturn(42);
|
||||
mockConductor.queryEnd.andReturn(12321);
|
||||
mockConductor.displayStart.andReturn(1977);
|
||||
mockConductor.displayEnd.andReturn(1984);
|
||||
representer.represent(testViews[0], {});
|
||||
|
||||
expect(mockNewScope.conductor).toEqual({
|
||||
inner: { start: 1977, end: 1984 },
|
||||
outer: { start: 42, end: 12321 }
|
||||
outer: { start: 1977, end: 1984 }
|
||||
});
|
||||
});
|
||||
|
||||
@ -154,12 +158,56 @@ define(
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testState.inner.end);
|
||||
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
|
||||
});
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.outer.start', testState.outer.start);
|
||||
expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977);
|
||||
describe("when bounds are changing", function () {
|
||||
var mockThrottledFn = jasmine.createSpy('throttledFn'),
|
||||
testBounds;
|
||||
|
||||
fireWatch(mockNewScope, 'conductor.outer.end', testState.outer.end);
|
||||
expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321);
|
||||
function fireThrottledFn() {
|
||||
mockThrottle.mostRecentCall.args[0]();
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockThrottle.andReturn(mockThrottledFn);
|
||||
representer.represent(testViews[0], {});
|
||||
testBounds = { start: 0, end: 1000 };
|
||||
mockNewScope.conductor.inner = testBounds;
|
||||
mockConductor.displayStart.andCallFake(function () {
|
||||
return testBounds.start;
|
||||
});
|
||||
mockConductor.displayEnd.andCallFake(function () {
|
||||
return testBounds.end;
|
||||
});
|
||||
});
|
||||
|
||||
it("does not broadcast while bounds are changing", function () {
|
||||
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||
testBounds.start = 100;
|
||||
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||
testBounds.end = 500;
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||
fireThrottledFn();
|
||||
testBounds.start = 200;
|
||||
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||
testBounds.end = 400;
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||
fireThrottledFn();
|
||||
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does broadcast when bounds have stabilized", function () {
|
||||
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||
testBounds.start = 100;
|
||||
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||
testBounds.end = 500;
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||
fireThrottledFn();
|
||||
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||
fireThrottledFn();
|
||||
expect(mockScope.$broadcast).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -43,9 +43,9 @@ define(
|
||||
|
||||
it("initializes a time conductor around the current time", function () {
|
||||
var conductor = conductorService.getConductor();
|
||||
expect(conductor.queryStart() <= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.queryEnd() >= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.queryEnd() > conductor.queryStart())
|
||||
expect(conductor.displayStart() <= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.displayEnd() >= TEST_NOW).toBeTruthy();
|
||||
expect(conductor.displayEnd() > conductor.displayStart())
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
|
@ -110,27 +110,6 @@ define(
|
||||
}]);
|
||||
});
|
||||
|
||||
it("prunes historical values to the displayable range", function () {
|
||||
var packagedTelemetry;
|
||||
decorator.requestTelemetry([{ source: "abc", key: "xyz" }]);
|
||||
packagedTelemetry = mockPromise.then.mostRecentCall.args[0]({
|
||||
"abc": { "xyz": mockSeries }
|
||||
});
|
||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("prunes subscribed values to the displayable range", function () {
|
||||
var mockCallback = jasmine.createSpy('callback'),
|
||||
packagedTelemetry;
|
||||
decorator.subscribe(mockCallback, [{ source: "abc", key: "xyz" }]);
|
||||
mockTelemetryService.subscribe.mostRecentCall.args[0]({
|
||||
"abc": { "xyz": mockSeries }
|
||||
});
|
||||
packagedTelemetry = mockCallback.mostRecentCall.args[0];
|
||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ConductorTelemetrySeries"],
|
||||
function (ConductorTelemetrySeries) {
|
||||
"use strict";
|
||||
|
||||
describe("ConductorTelemetrySeries", function () {
|
||||
var mockSeries,
|
||||
mockConductor,
|
||||
testArray,
|
||||
series;
|
||||
|
||||
beforeEach(function () {
|
||||
testArray = [ -10, 0, 42, 1977, 12321 ];
|
||||
|
||||
mockSeries = jasmine.createSpyObj(
|
||||
'series',
|
||||
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
||||
);
|
||||
mockConductor = jasmine.createSpyObj(
|
||||
'conductor',
|
||||
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||
);
|
||||
|
||||
mockSeries.getPointCount.andCallFake(function () {
|
||||
return testArray.length;
|
||||
});
|
||||
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||
return testArray[i];
|
||||
});
|
||||
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||
return testArray[i] * 2;
|
||||
});
|
||||
|
||||
mockConductor.displayStart.andReturn(0);
|
||||
mockConductor.displayEnd.andReturn(2000);
|
||||
|
||||
series = new ConductorTelemetrySeries(
|
||||
mockSeries,
|
||||
mockConductor
|
||||
);
|
||||
});
|
||||
|
||||
it("reduces the apparent size of a series", function () {
|
||||
expect(series.getPointCount()).toEqual(3);
|
||||
});
|
||||
|
||||
it("maps domain value indexes to the displayable range", function () {
|
||||
[0, 1, 2].forEach(function (i) {
|
||||
expect(series.getDomainValue(i))
|
||||
.toEqual(mockSeries.getDomainValue(i + 1));
|
||||
});
|
||||
});
|
||||
|
||||
it("maps range value indexes to the displayable range", function () {
|
||||
[0, 1, 2].forEach(function (i) {
|
||||
expect(series.getRangeValue(i))
|
||||
.toEqual(mockSeries.getRangeValue(i + 1));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -41,19 +41,13 @@ define(
|
||||
});
|
||||
|
||||
it("provides accessors for query/display start/end times", function () {
|
||||
expect(conductor.queryStart()).toEqual(testStart);
|
||||
expect(conductor.queryEnd()).toEqual(testEnd);
|
||||
expect(conductor.displayStart()).toEqual(testStart);
|
||||
expect(conductor.displayEnd()).toEqual(testEnd);
|
||||
});
|
||||
|
||||
it("provides setters for query/display start/end times", function () {
|
||||
expect(conductor.queryStart(1)).toEqual(1);
|
||||
expect(conductor.queryEnd(2)).toEqual(2);
|
||||
expect(conductor.displayStart(3)).toEqual(3);
|
||||
expect(conductor.displayEnd(4)).toEqual(4);
|
||||
expect(conductor.queryStart()).toEqual(1);
|
||||
expect(conductor.queryEnd()).toEqual(2);
|
||||
expect(conductor.displayStart()).toEqual(3);
|
||||
expect(conductor.displayEnd()).toEqual(4);
|
||||
});
|
||||
|
@ -2,6 +2,5 @@
|
||||
"ConductorRepresenter",
|
||||
"ConductorService",
|
||||
"ConductorTelemetryDecorator",
|
||||
"ConductorTelemetrySeries",
|
||||
"TimeConductor"
|
||||
]
|
||||
|
@ -66,7 +66,6 @@ define(
|
||||
cachedObjects = [],
|
||||
updater,
|
||||
lastBounds,
|
||||
throttledRequery,
|
||||
handle;
|
||||
|
||||
// Populate the scope with axis information (specifically, options
|
||||
@ -188,15 +187,10 @@ define(
|
||||
function changeDisplayBounds(event, bounds) {
|
||||
self.pending = true;
|
||||
releaseSubscription();
|
||||
throttledRequery();
|
||||
subscribe($scope.domainObject);
|
||||
setBasePanZoom(bounds);
|
||||
}
|
||||
|
||||
// Reestablish/reissue request for telemetry
|
||||
throttledRequery = throttle(function () {
|
||||
subscribe($scope.domainObject);
|
||||
}, 250);
|
||||
|
||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||
this.updateValues = updateValues;
|
||||
|
||||
|
@ -54,7 +54,13 @@
|
||||
{
|
||||
"key": "menu",
|
||||
"implementation": "actions/ContextMenuAction.js",
|
||||
"depends": [ "$compile", "$document", "$window", "$rootScope", "agentService" ]
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$document",
|
||||
"$rootScope",
|
||||
"popupService",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -43,40 +43,52 @@ define(
|
||||
* @constructor
|
||||
* @param $compile Angular's $compile service
|
||||
* @param $document the current document
|
||||
* @param $window the active window
|
||||
* @param $rootScope Angular's root scope
|
||||
* @param actionContexr the context in which the action
|
||||
* @param {platform/commonUI/general.PopupService} popupService
|
||||
* @param actionContext the context in which the action
|
||||
* should be performed
|
||||
* @implements {Action}
|
||||
*/
|
||||
function ContextMenuAction($compile, $document, $window, $rootScope, agentService, actionContext) {
|
||||
function ContextMenuAction(
|
||||
$compile,
|
||||
$document,
|
||||
$rootScope,
|
||||
popupService,
|
||||
agentService,
|
||||
actionContext
|
||||
) {
|
||||
this.$compile = $compile;
|
||||
this.agentService = agentService;
|
||||
this.actionContext = actionContext;
|
||||
this.popupService = popupService;
|
||||
this.getDocument = function () { return $document; };
|
||||
this.getWindow = function () { return $window; };
|
||||
this.getRootScope = function () { return $rootScope; };
|
||||
}
|
||||
|
||||
ContextMenuAction.prototype.perform = function () {
|
||||
var $compile = this.$compile,
|
||||
$document = this.getDocument(),
|
||||
$window = this.getWindow(),
|
||||
$rootScope = this.getRootScope(),
|
||||
actionContext = this.actionContext,
|
||||
winDim = [$window.innerWidth, $window.innerHeight],
|
||||
eventCoors = [actionContext.event.pageX, actionContext.event.pageY],
|
||||
eventCoords = [
|
||||
actionContext.event.pageX,
|
||||
actionContext.event.pageY
|
||||
],
|
||||
menuDim = GestureConstants.MCT_MENU_DIMENSIONS,
|
||||
body = $document.find('body'),
|
||||
scope = $rootScope.$new(),
|
||||
goLeft = eventCoors[0] + menuDim[0] > winDim[0],
|
||||
goUp = eventCoors[1] + menuDim[1] > winDim[1],
|
||||
initiatingEvent = this.agentService.isMobile() ? 'touchstart' : 'mousedown',
|
||||
menu;
|
||||
initiatingEvent = this.agentService.isMobile() ?
|
||||
'touchstart' : 'mousedown',
|
||||
menu,
|
||||
popup;
|
||||
|
||||
// Remove the context menu
|
||||
function dismiss() {
|
||||
menu.remove();
|
||||
if (popup) {
|
||||
popup.dismiss();
|
||||
popup = undefined;
|
||||
}
|
||||
scope.$destroy();
|
||||
body.off("mousedown", dismiss);
|
||||
dismissExistingMenu = undefined;
|
||||
}
|
||||
@ -91,21 +103,17 @@ define(
|
||||
|
||||
// Set up the scope, including menu positioning
|
||||
scope.domainObject = actionContext.domainObject;
|
||||
scope.menuStyle = {};
|
||||
scope.menuStyle[goLeft ? "right" : "left"] =
|
||||
(goLeft ? (winDim[0] - eventCoors[0]) : eventCoors[0]) + 'px';
|
||||
scope.menuStyle[goUp ? "bottom" : "top"] =
|
||||
(goUp ? (winDim[1] - eventCoors[1]) : eventCoors[1]) + 'px';
|
||||
scope.menuClass = {
|
||||
"go-left": goLeft,
|
||||
"go-up": goUp,
|
||||
"context-menu-holder": true
|
||||
};
|
||||
scope.menuClass = { "context-menu-holder": true };
|
||||
// Create the context menu
|
||||
menu = $compile(MENU_TEMPLATE)(scope);
|
||||
|
||||
// Add the menu to the body
|
||||
body.append(menu);
|
||||
popup = this.popupService.display(menu, eventCoords, {
|
||||
marginX: -menuDim[0],
|
||||
marginY: -menuDim[1]
|
||||
});
|
||||
|
||||
scope.menuClass['go-left'] = popup.goesLeft();
|
||||
scope.menuClass['go-up'] = popup.goesUp();
|
||||
|
||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||
menu.on(initiatingEvent, function (event) {
|
||||
|
@ -41,13 +41,14 @@ define(
|
||||
mockMenu,
|
||||
mockDocument,
|
||||
mockBody,
|
||||
mockWindow,
|
||||
mockPopupService,
|
||||
mockRootScope,
|
||||
mockAgentService,
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockDomainObject,
|
||||
mockEvent,
|
||||
mockPopup,
|
||||
mockActionContext,
|
||||
action;
|
||||
|
||||
@ -57,36 +58,47 @@ define(
|
||||
mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS);
|
||||
mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS);
|
||||
mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS);
|
||||
mockWindow = { innerWidth: MENU_DIMENSIONS[0] * 4, innerHeight: MENU_DIMENSIONS[1] * 4 };
|
||||
mockPopupService =
|
||||
jasmine.createSpyObj("popupService", ["display"]);
|
||||
mockPopup = jasmine.createSpyObj("popup", [
|
||||
"dismiss",
|
||||
"goesLeft",
|
||||
"goesUp"
|
||||
]);
|
||||
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
|
||||
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
||||
mockScope = {};
|
||||
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||
mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]);
|
||||
mockEvent.pageX = 0;
|
||||
mockEvent.pageY = 0;
|
||||
mockEvent.pageX = 123;
|
||||
mockEvent.pageY = 321;
|
||||
|
||||
mockCompile.andReturn(mockCompiledTemplate);
|
||||
mockCompiledTemplate.andReturn(mockMenu);
|
||||
mockDocument.find.andReturn(mockBody);
|
||||
mockRootScope.$new.andReturn(mockScope);
|
||||
mockPopupService.display.andReturn(mockPopup);
|
||||
|
||||
mockActionContext = {key: 'menu', domainObject: mockDomainObject, event: mockEvent};
|
||||
|
||||
action = new ContextMenuAction(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
mockWindow,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService,
|
||||
mockActionContext
|
||||
);
|
||||
});
|
||||
|
||||
it(" adds a menu to the DOM when perform is called", function () {
|
||||
it("displays a popup when performed", function () {
|
||||
action.perform();
|
||||
expect(mockBody.append).toHaveBeenCalledWith(mockMenu);
|
||||
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||
mockMenu,
|
||||
[ mockEvent.pageX, mockEvent.pageY ],
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("prevents the default context menu behavior", function () {
|
||||
@ -94,29 +106,22 @@ define(
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("positions menus where clicked", function () {
|
||||
mockEvent.pageX = 10;
|
||||
mockEvent.pageY = 5;
|
||||
action.perform();
|
||||
expect(mockScope.menuStyle.left).toEqual("10px");
|
||||
expect(mockScope.menuStyle.top).toEqual("5px");
|
||||
expect(mockScope.menuStyle.right).toBeUndefined();
|
||||
expect(mockScope.menuStyle.bottom).toBeUndefined();
|
||||
expect(mockScope.menuClass['go-up']).toBeFalsy();
|
||||
expect(mockScope.menuClass['go-left']).toBeFalsy();
|
||||
it("adds classes to menus based on position", function () {
|
||||
var booleans = [ false, true ];
|
||||
|
||||
booleans.forEach(function (goLeft) {
|
||||
booleans.forEach(function (goUp) {
|
||||
mockPopup.goesLeft.andReturn(goLeft);
|
||||
mockPopup.goesUp.andReturn(goUp);
|
||||
action.perform();
|
||||
expect(!!mockScope.menuClass['go-up'])
|
||||
.toEqual(goUp);
|
||||
expect(!!mockScope.menuClass['go-left'])
|
||||
.toEqual(goLeft);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("repositions menus near the screen edge", function () {
|
||||
mockEvent.pageX = mockWindow.innerWidth - 10;
|
||||
mockEvent.pageY = mockWindow.innerHeight - 5;
|
||||
action.perform();
|
||||
expect(mockScope.menuStyle.right).toEqual("10px");
|
||||
expect(mockScope.menuStyle.bottom).toEqual("5px");
|
||||
expect(mockScope.menuStyle.left).toBeUndefined();
|
||||
expect(mockScope.menuStyle.top).toBeUndefined();
|
||||
expect(mockScope.menuClass['go-up']).toBeTruthy();
|
||||
expect(mockScope.menuClass['go-left']).toBeTruthy();
|
||||
});
|
||||
|
||||
it("removes a menu when body is clicked", function () {
|
||||
// Show the menu
|
||||
@ -133,7 +138,7 @@ define(
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockMenu.remove).toHaveBeenCalled();
|
||||
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||
|
||||
// Listener should have been detached from body
|
||||
expect(mockBody.off).toHaveBeenCalledWith(
|
||||
@ -149,7 +154,7 @@ define(
|
||||
// Verify precondition
|
||||
expect(mockMenu.remove).not.toHaveBeenCalled();
|
||||
|
||||
// Find and fire body's mousedown listener
|
||||
// Find and fire menu's click listener
|
||||
mockMenu.on.calls.forEach(function (call) {
|
||||
if (call.args[0] === 'click') {
|
||||
call.args[1]();
|
||||
@ -157,7 +162,7 @@ define(
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockMenu.remove).toHaveBeenCalled();
|
||||
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps a menu when menu is clicked", function () {
|
||||
@ -171,7 +176,7 @@ define(
|
||||
});
|
||||
|
||||
// Menu should have been removed
|
||||
expect(mockMenu.remove).not.toHaveBeenCalled();
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
|
||||
// Listener should have been detached from body
|
||||
expect(mockBody.off).not.toHaveBeenCalled();
|
||||
@ -182,8 +187,8 @@ define(
|
||||
action = new ContextMenuAction(
|
||||
mockCompile,
|
||||
mockDocument,
|
||||
mockWindow,
|
||||
mockRootScope,
|
||||
mockPopupService,
|
||||
mockAgentService,
|
||||
mockActionContext
|
||||
);
|
||||
@ -194,6 +199,8 @@ define(
|
||||
call.args[1](mockEvent);
|
||||
}
|
||||
});
|
||||
|
||||
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user