mirror of
https://github.com/nasa/openmct.git
synced 2024-12-30 09:58:52 +00:00
Merge pull request #222 from nasa/open195
[Representation] Remove unknown templates from DOM
This commit is contained in:
commit
ec56fe7bfd
@ -55,11 +55,6 @@ define(
|
|||||||
self.trackPosition(event);
|
self.trackPosition(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Also make sure we dismiss bubble if representation is destroyed
|
|
||||||
// before the mouse actually leaves it
|
|
||||||
this.scopeOff =
|
|
||||||
element.scope().$on('$destroy', this.hideBubbleCallback);
|
|
||||||
|
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
this.infoService = infoService;
|
this.infoService = infoService;
|
||||||
@ -143,7 +138,6 @@ define(
|
|||||||
this.hideBubble();
|
this.hideBubble();
|
||||||
// ...and detach listeners
|
// ...and detach listeners
|
||||||
this.element.off('mouseenter', this.showBubbleCallback);
|
this.element.off('mouseenter', this.showBubbleCallback);
|
||||||
this.scopeOff();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return InfoGesture;
|
return InfoGesture;
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
{
|
{
|
||||||
"key": "mctInclude",
|
"key": "mctInclude",
|
||||||
"implementation": "MCTInclude.js",
|
"implementation": "MCTInclude.js",
|
||||||
"depends": [ "templates[]", "$sce" ]
|
"depends": [ "templates[]", "templateLinker" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "mctRepresentation",
|
"key": "mctRepresentation",
|
||||||
"implementation": "MCTRepresentation.js",
|
"implementation": "MCTRepresentation.js",
|
||||||
"depends": [ "representations[]", "views[]", "representers[]", "$q", "$sce", "$log" ]
|
"depends": [ "representations[]", "views[]", "representers[]", "$q", "templateLinker", "$log" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"gestures": [
|
"gestures": [
|
||||||
@ -48,6 +48,12 @@
|
|||||||
"key": "dndService",
|
"key": "dndService",
|
||||||
"implementation": "services/DndService.js",
|
"implementation": "services/DndService.js",
|
||||||
"depends": [ "$log" ]
|
"depends": [ "$log" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "templateLinker",
|
||||||
|
"implementation": "TemplateLinker.js",
|
||||||
|
"depends": [ "$templateRequest", "$sce", "$compile", "$log" ],
|
||||||
|
"comment": "For internal use by mct-include and mct-representation."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
"actions": [
|
||||||
|
@ -54,36 +54,38 @@ define(
|
|||||||
* @param {TemplateDefinition[]} templates an array of
|
* @param {TemplateDefinition[]} templates an array of
|
||||||
* template extensions
|
* template extensions
|
||||||
*/
|
*/
|
||||||
function MCTInclude(templates, $sce) {
|
function MCTInclude(templates, templateLinker) {
|
||||||
var templateMap = {};
|
var templateMap = {};
|
||||||
|
|
||||||
|
function link(scope, element) {
|
||||||
|
var changeTemplate = templateLinker.link(
|
||||||
|
scope,
|
||||||
|
element,
|
||||||
|
scope.key && templateMap[scope.key]
|
||||||
|
);
|
||||||
|
|
||||||
|
scope.$watch('key', function (key) {
|
||||||
|
changeTemplate(key && templateMap[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Prepopulate templateMap for easy look up by key
|
// Prepopulate templateMap for easy look up by key
|
||||||
templates.forEach(function (template) {
|
templates.forEach(function (template) {
|
||||||
var key = template.key,
|
var key = template.key;
|
||||||
path = $sce.trustAsResourceUrl([
|
|
||||||
template.bundle.path,
|
|
||||||
template.bundle.resources,
|
|
||||||
template.templateUrl
|
|
||||||
].join("/"));
|
|
||||||
// First found should win (priority ordering)
|
// First found should win (priority ordering)
|
||||||
templateMap[key] = templateMap[key] || path;
|
templateMap[key] =
|
||||||
|
templateMap[key] || templateLinker.getPath(template);
|
||||||
});
|
});
|
||||||
|
|
||||||
function controller($scope) {
|
|
||||||
// Pass the template URL to ng-include via scope.
|
|
||||||
$scope.inclusion = templateMap[$scope.key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Only show at the element level
|
// Only show at the element level
|
||||||
restrict: "E",
|
restrict: "E",
|
||||||
|
|
||||||
// Use the included controller to populate scope
|
// Use the included controller to populate scope
|
||||||
controller: controller,
|
link: link,
|
||||||
|
|
||||||
// Use ng-include as a template; "inclusion" will be the real
|
// May hide the element, so let other directives act first
|
||||||
// template path
|
priority: -1000,
|
||||||
template: '<ng-include src="inclusion"></ng-include>',
|
|
||||||
|
|
||||||
// Two-way bind key, ngModel, and parameters
|
// Two-way bind key, ngModel, and parameters
|
||||||
scope: { key: "=", ngModel: "=", parameters: "=" }
|
scope: { key: "=", ngModel: "=", parameters: "=" }
|
||||||
|
@ -55,7 +55,7 @@ define(
|
|||||||
* representation extensions
|
* representation extensions
|
||||||
* @param {ViewDefinition[]} views an array of view extensions
|
* @param {ViewDefinition[]} views an array of view extensions
|
||||||
*/
|
*/
|
||||||
function MCTRepresentation(representations, views, representers, $q, $sce, $log) {
|
function MCTRepresentation(representations, views, representers, $q, templateLinker, $log) {
|
||||||
var representationMap = {},
|
var representationMap = {},
|
||||||
gestureMap = {};
|
gestureMap = {};
|
||||||
|
|
||||||
@ -72,11 +72,7 @@ define(
|
|||||||
|
|
||||||
// Get a path to a representation
|
// Get a path to a representation
|
||||||
function getPath(representation) {
|
function getPath(representation) {
|
||||||
return $sce.trustAsResourceUrl([
|
return templateLinker.getPath(representation);
|
||||||
representation.bundle.path,
|
|
||||||
representation.bundle.resources,
|
|
||||||
representation.templateUrl
|
|
||||||
].join("/"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up a matching representation for this domain object
|
// Look up a matching representation for this domain object
|
||||||
@ -94,12 +90,13 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function link($scope, element, attrs) {
|
function link($scope, element, attrs, ctrl, transclude) {
|
||||||
var activeRepresenters = representers.map(function (Representer) {
|
var activeRepresenters = representers.map(function (Representer) {
|
||||||
return new Representer($scope, element, attrs);
|
return new Representer($scope, element, attrs);
|
||||||
}),
|
}),
|
||||||
toClear = [], // Properties to clear out of scope on change
|
toClear = [], // Properties to clear out of scope on change
|
||||||
counter = 0;
|
counter = 0,
|
||||||
|
changeTemplate = templateLinker.link($scope, element);
|
||||||
|
|
||||||
// Populate scope with any capabilities indicated by the
|
// Populate scope with any capabilities indicated by the
|
||||||
// representation's extension definition
|
// representation's extension definition
|
||||||
@ -150,15 +147,17 @@ define(
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
var domainObject = $scope.domainObject,
|
var domainObject = $scope.domainObject,
|
||||||
representation = lookup($scope.key, domainObject),
|
representation = lookup($scope.key, domainObject),
|
||||||
uses = ((representation || {}).uses || []);
|
path = representation && getPath(representation),
|
||||||
|
uses = ((representation || {}).uses || []),
|
||||||
|
canRepresent = !!(path && domainObject);
|
||||||
|
|
||||||
// Create an empty object named "representation", for this
|
// Create an empty object named "representation", for this
|
||||||
// representation to store local variables into.
|
// representation to store local variables into.
|
||||||
$scope.representation = {};
|
$scope.representation = {};
|
||||||
|
|
||||||
// Look up the actual template path, pass it to ng-include
|
// Change templates (passing in undefined to clear
|
||||||
// via the "inclusion" field
|
// if we don't have enough info to show a template.)
|
||||||
$scope.inclusion = representation && getPath(representation);
|
changeTemplate(canRepresent ? path : undefined);
|
||||||
|
|
||||||
// Any existing representers are no longer valid; release them.
|
// Any existing representers are no longer valid; release them.
|
||||||
destroyRepresenters();
|
destroyRepresenters();
|
||||||
@ -176,7 +175,7 @@ define(
|
|||||||
|
|
||||||
// Populate scope with fields associated with the current
|
// Populate scope with fields associated with the current
|
||||||
// domain object (if one has been passed in)
|
// domain object (if one has been passed in)
|
||||||
if (domainObject) {
|
if (canRepresent) {
|
||||||
// Track how many representations we've made in this scope,
|
// Track how many representations we've made in this scope,
|
||||||
// to ensure that the correct representations are matched to
|
// to ensure that the correct representations are matched to
|
||||||
// the correct object/key pairs.
|
// the correct object/key pairs.
|
||||||
@ -233,9 +232,8 @@ define(
|
|||||||
// Handle Angular's linking step
|
// Handle Angular's linking step
|
||||||
link: link,
|
link: link,
|
||||||
|
|
||||||
// Use ng-include as a template; "inclusion" will be the real
|
// May hide the element, so let other directives act first
|
||||||
// template path
|
priority: -1000,
|
||||||
template: '<ng-include src="inclusion"></ng-include>',
|
|
||||||
|
|
||||||
// Two-way bind key and parameters, get the represented domain
|
// Two-way bind key and parameters, get the represented domain
|
||||||
// object as "mct-object"
|
// object as "mct-object"
|
||||||
|
153
platform/representation/src/TemplateLinker.js
Normal file
153
platform/representation/src/TemplateLinker.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `templateLinker` service is intended for internal use by
|
||||||
|
* the `mct-include` and `mct-representation` directives. It is
|
||||||
|
* used to support common behavior of directives; specifically,
|
||||||
|
* loading templates and inserting them into a specified element,
|
||||||
|
* and/or removing that element from the DOM when there is no
|
||||||
|
* template to populate it with.
|
||||||
|
*
|
||||||
|
* @param {Function} $templateRequest Angular's `$templateRequest`
|
||||||
|
* service
|
||||||
|
* @param $sce Angular's `$sce` service
|
||||||
|
* @param {Function} $compile Angular's `$compile` service
|
||||||
|
* @param $log Angular's `$log` service
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function TemplateLinker($templateRequest, $sce, $compile, $log) {
|
||||||
|
this.$templateRequest = $templateRequest;
|
||||||
|
this.$sce = $sce;
|
||||||
|
this.$compile = $compile;
|
||||||
|
this.$log = $log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a template from the given URL. This request will be handled
|
||||||
|
* via `$templateRequest` to ensure caching et cetera.
|
||||||
|
* @param {string} the URL for the template
|
||||||
|
* @returns {Promise.<string>} a promise for the HTML content of
|
||||||
|
* the template
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TemplateLinker.prototype.load = function (templateUrl) {
|
||||||
|
return this.$templateRequest(
|
||||||
|
this.$sce.trustAsResourceUrl(templateUrl),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a path to a template from an extension definition fo
|
||||||
|
* a template, representation, or view.
|
||||||
|
* @param {TemplateDefinition} extensionDefinition the definition
|
||||||
|
* of the template/representation/view to resolve
|
||||||
|
*/
|
||||||
|
TemplateLinker.prototype.getPath = function (extensionDefinition) {
|
||||||
|
return [
|
||||||
|
extensionDefinition.bundle.path,
|
||||||
|
extensionDefinition.bundle.resources,
|
||||||
|
extensionDefinition.templateUrl
|
||||||
|
].join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate the given element with templates, within the given scope;
|
||||||
|
* intended to support the `link` function of the supported directives.
|
||||||
|
*
|
||||||
|
* @param {Scope} scope the Angular scope to use when rendering
|
||||||
|
* templates
|
||||||
|
* @param element the jqLite-wrapped element into which templates
|
||||||
|
* should be inserted
|
||||||
|
* @returns {Function} a function which can be called with a template
|
||||||
|
* URL to switch templates, or `undefined` to remove.
|
||||||
|
*/
|
||||||
|
TemplateLinker.prototype.link = function (scope, element, templateUrl) {
|
||||||
|
var activeElement = element,
|
||||||
|
activeTemplateUrl,
|
||||||
|
comment = this.$compile('<!-- hidden mct element -->')(scope),
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
function removeElement() {
|
||||||
|
if (activeElement !== comment) {
|
||||||
|
activeElement.replaceWith(comment);
|
||||||
|
activeElement = comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addElement() {
|
||||||
|
if (activeElement !== element) {
|
||||||
|
activeElement.replaceWith(element);
|
||||||
|
activeElement = element;
|
||||||
|
activeElement.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateElement(template) {
|
||||||
|
element.empty();
|
||||||
|
element.append(self.$compile(template)(scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
function badTemplate(templateUrl) {
|
||||||
|
self.$log.warn("Couldn't load template at " + templateUrl);
|
||||||
|
removeElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeTemplate(templateUrl) {
|
||||||
|
if (templateUrl !== activeTemplateUrl) {
|
||||||
|
if (templateUrl) {
|
||||||
|
addElement();
|
||||||
|
self.load(templateUrl).then(function (template) {
|
||||||
|
// Avoid race conditions
|
||||||
|
if (templateUrl === activeTemplateUrl) {
|
||||||
|
populateElement(template);
|
||||||
|
}
|
||||||
|
}, function () {
|
||||||
|
badTemplate(templateUrl);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
removeElement();
|
||||||
|
}
|
||||||
|
activeTemplateUrl = templateUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateUrl) {
|
||||||
|
changeTemplate(templateUrl);
|
||||||
|
} else {
|
||||||
|
removeElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
return TemplateLinker;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -31,9 +31,21 @@ define(
|
|||||||
|
|
||||||
describe("The mct-include directive", function () {
|
describe("The mct-include directive", function () {
|
||||||
var testTemplates,
|
var testTemplates,
|
||||||
mockSce,
|
testUrls,
|
||||||
|
mockLinker,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockChangeTemplate,
|
||||||
mctInclude;
|
mctInclude;
|
||||||
|
|
||||||
|
function fireWatch(expr, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testTemplates = [
|
testTemplates = [
|
||||||
{
|
{
|
||||||
@ -47,40 +59,44 @@ define(
|
|||||||
templateUrl: "z/template.html"
|
templateUrl: "z/template.html"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
mockSce = jasmine.createSpyObj(
|
testUrls = {};
|
||||||
'$sce',
|
testTemplates.forEach(function (t, i) {
|
||||||
['trustAsResourceUrl']
|
testUrls[t.key] = "some URL " + String(i);
|
||||||
);
|
|
||||||
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
|
||||||
return url;
|
|
||||||
});
|
});
|
||||||
mctInclude = new MCTInclude(testTemplates, mockSce);
|
mockLinker = jasmine.createSpyObj(
|
||||||
});
|
'templateLinker',
|
||||||
|
['link', 'getPath']
|
||||||
it("has a built-in template, with ng-include src=inclusion", function () {
|
);
|
||||||
// Not rigorous, but should detect many cases when template is broken.
|
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
|
||||||
expect(mctInclude.template.indexOf("ng-include")).not.toEqual(-1);
|
mockElement = jasmine.createSpyObj('element', ['empty']);
|
||||||
expect(mctInclude.template.indexOf("inclusion")).not.toEqual(-1);
|
mockChangeTemplate = jasmine.createSpy('changeTemplate');
|
||||||
|
mockLinker.link.andReturn(mockChangeTemplate);
|
||||||
|
mockLinker.getPath.andCallFake(function (template) {
|
||||||
|
return testUrls[template.key];
|
||||||
|
});
|
||||||
|
mctInclude = new MCTInclude(testTemplates, mockLinker);
|
||||||
|
mctInclude.link(mockScope, mockElement, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is restricted to elements", function () {
|
it("is restricted to elements", function () {
|
||||||
expect(mctInclude.restrict).toEqual("E");
|
expect(mctInclude.restrict).toEqual("E");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reads a template location from a scope's key variable", function () {
|
it("exposes templates via the templateLinker", function () {
|
||||||
var scope = { key: "abc" };
|
expect(mockLinker.link)
|
||||||
mctInclude.controller(scope);
|
.toHaveBeenCalledWith(mockScope, mockElement, undefined);
|
||||||
expect(scope.inclusion).toEqual("a/b/c/template.html");
|
|
||||||
|
|
||||||
scope = { key: "xyz" };
|
|
||||||
mctInclude.controller(scope);
|
|
||||||
expect(scope.inclusion).toEqual("x/y/z/template.html");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("trusts template URLs", function () {
|
it("reads a template location from a scope's key variable", function () {
|
||||||
mctInclude.controller({ key: "xyz" });
|
mockScope.key = 'abc';
|
||||||
expect(mockSce.trustAsResourceUrl)
|
fireWatch('key', mockScope.key);
|
||||||
.toHaveBeenCalledWith("x/y/z/template.html");
|
expect(mockChangeTemplate)
|
||||||
|
.toHaveBeenCalledWith(testUrls.abc);
|
||||||
|
|
||||||
|
mockScope.key = 'xyz';
|
||||||
|
fireWatch('key', mockScope.key);
|
||||||
|
expect(mockChangeTemplate)
|
||||||
|
.toHaveBeenCalledWith(testUrls.xyz);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -36,10 +36,12 @@ define(
|
|||||||
describe("The mct-representation directive", function () {
|
describe("The mct-representation directive", function () {
|
||||||
var testRepresentations,
|
var testRepresentations,
|
||||||
testViews,
|
testViews,
|
||||||
|
testUrls,
|
||||||
mockRepresenters,
|
mockRepresenters,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockSce,
|
mockLinker,
|
||||||
mockLog,
|
mockLog,
|
||||||
|
mockChangeTemplate,
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
@ -54,7 +56,17 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fireWatch(expr, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
testUrls = {};
|
||||||
|
|
||||||
testRepresentations = [
|
testRepresentations = [
|
||||||
{
|
{
|
||||||
key: "abc",
|
key: "abc",
|
||||||
@ -85,6 +97,11 @@ define(
|
|||||||
|
|
||||||
testModel = { someKey: "some value" };
|
testModel = { someKey: "some value" };
|
||||||
|
|
||||||
|
testUrls = {};
|
||||||
|
testViews.concat(testRepresentations).forEach(function (t, i) {
|
||||||
|
testUrls[t.key] = "some URL " + String(i);
|
||||||
|
});
|
||||||
|
|
||||||
mockRepresenters = ["A", "B"].map(function (name) {
|
mockRepresenters = ["A", "B"].map(function (name) {
|
||||||
var constructor = jasmine.createSpy("Representer" + name),
|
var constructor = jasmine.createSpy("Representer" + name),
|
||||||
representer = jasmine.createSpyObj(
|
representer = jasmine.createSpyObj(
|
||||||
@ -96,45 +113,44 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
mockQ = { when: mockPromise };
|
mockQ = { when: mockPromise };
|
||||||
mockSce = jasmine.createSpyObj(
|
mockLinker = jasmine.createSpyObj(
|
||||||
'$sce',
|
'templateLinker',
|
||||||
['trustAsResourceUrl']
|
['link', 'getPath']
|
||||||
);
|
);
|
||||||
|
mockChangeTemplate = jasmine.createSpy('changeTemplate');
|
||||||
mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS);
|
mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS);
|
||||||
|
|
||||||
|
|
||||||
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
|
||||||
return url;
|
|
||||||
});
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
|
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
|
||||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||||
|
|
||||||
mockDomainObject.getModel.andReturn(testModel);
|
mockDomainObject.getModel.andReturn(testModel);
|
||||||
|
mockLinker.link.andReturn(mockChangeTemplate);
|
||||||
|
mockLinker.getPath.andCallFake(function (ext) {
|
||||||
|
return testUrls[ext.key];
|
||||||
|
});
|
||||||
|
|
||||||
mctRepresentation = new MCTRepresentation(
|
mctRepresentation = new MCTRepresentation(
|
||||||
testRepresentations,
|
testRepresentations,
|
||||||
testViews,
|
testViews,
|
||||||
mockRepresenters,
|
mockRepresenters,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockSce,
|
mockLinker,
|
||||||
mockLog
|
mockLog
|
||||||
);
|
);
|
||||||
});
|
mctRepresentation.link(mockScope, mockElement);
|
||||||
|
|
||||||
|
|
||||||
it("has a built-in template, with ng-include src=inclusion", function () {
|
|
||||||
// Not rigorous, but should detect many cases when template is broken.
|
|
||||||
expect(mctRepresentation.template.indexOf("ng-include")).not.toEqual(-1);
|
|
||||||
expect(mctRepresentation.template.indexOf("inclusion")).not.toEqual(-1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is restricted to elements", function () {
|
it("is restricted to elements", function () {
|
||||||
expect(mctRepresentation.restrict).toEqual("E");
|
expect(mctRepresentation.restrict).toEqual("E");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exposes templates via the templateLinker", function () {
|
||||||
|
expect(mockLinker.link)
|
||||||
|
.toHaveBeenCalledWith(mockScope, mockElement);
|
||||||
|
});
|
||||||
|
|
||||||
it("watches scope when linked", function () {
|
it("watches scope when linked", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
"key",
|
"key",
|
||||||
jasmine.any(Function)
|
jasmine.any(Function)
|
||||||
@ -150,42 +166,46 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("recognizes keys for representations", function () {
|
it("recognizes keys for representations", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "abc";
|
mockScope.key = "abc";
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
// Trigger the watch
|
// Trigger the watch
|
||||||
mockScope.$watch.calls[0].args[1]();
|
fireWatch('key', mockScope.key);
|
||||||
|
fireWatch('domainObject', mockDomainObject);
|
||||||
|
|
||||||
expect(mockScope.inclusion).toEqual("a/b/c/template.html");
|
expect(mockChangeTemplate)
|
||||||
|
.toHaveBeenCalledWith(testUrls.abc);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("recognizes keys for views", function () {
|
it("recognizes keys for views", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "xyz";
|
mockScope.key = "xyz";
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
// Trigger the watch
|
// Trigger the watches
|
||||||
mockScope.$watch.calls[0].args[1]();
|
fireWatch('key', mockScope.key);
|
||||||
|
fireWatch('domainObject', mockDomainObject);
|
||||||
|
|
||||||
expect(mockScope.inclusion).toEqual("x/y/z/template.html");
|
expect(mockChangeTemplate)
|
||||||
|
.toHaveBeenCalledWith(testUrls.xyz);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("trusts template URLs", function () {
|
it("does not load templates until there is an object", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "xyz";
|
mockScope.key = "xyz";
|
||||||
|
|
||||||
// Trigger the watch
|
// Trigger the watch
|
||||||
mockScope.$watch.calls[0].args[1]();
|
fireWatch('key', mockScope.key);
|
||||||
|
|
||||||
expect(mockSce.trustAsResourceUrl)
|
expect(mockChangeTemplate)
|
||||||
.toHaveBeenCalledWith("x/y/z/template.html");
|
.not.toHaveBeenCalledWith(jasmine.any(String));
|
||||||
|
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
fireWatch('domainObject', mockDomainObject);
|
||||||
|
|
||||||
|
expect(mockChangeTemplate)
|
||||||
|
.toHaveBeenCalledWith(jasmine.any(String));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads declared capabilities", function () {
|
it("loads declared capabilities", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "def";
|
mockScope.key = "def";
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
@ -199,8 +219,6 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs when no representation is available for a key", function () {
|
it("logs when no representation is available for a key", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "someUnknownThing";
|
mockScope.key = "someUnknownThing";
|
||||||
|
|
||||||
// Verify precondition
|
// Verify precondition
|
||||||
@ -214,8 +232,6 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("clears out obsolete peroperties from scope", function () {
|
it("clears out obsolete peroperties from scope", function () {
|
||||||
mctRepresentation.link(mockScope, mockElement);
|
|
||||||
|
|
||||||
mockScope.key = "def";
|
mockScope.key = "def";
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
mockDomainObject.useCapability.andReturn("some value");
|
mockDomainObject.useCapability.andReturn("some value");
|
||||||
|
211
platform/representation/test/TemplateLinkerSpec.js
Normal file
211
platform/representation/test/TemplateLinkerSpec.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/TemplateLinker"],
|
||||||
|
function (TemplateLinker) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var JQLITE_METHODS = [ 'replaceWith', 'empty', 'append' ];
|
||||||
|
|
||||||
|
describe("TemplateLinker", function () {
|
||||||
|
var mockTemplateRequest,
|
||||||
|
mockSce,
|
||||||
|
mockCompile,
|
||||||
|
mockLog,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockTemplates,
|
||||||
|
mockElements,
|
||||||
|
mockPromise,
|
||||||
|
linker;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockTemplateRequest = jasmine.createSpy('$templateRequest');
|
||||||
|
mockSce = jasmine.createSpyObj('$sce', ['trustAsResourceUrl']);
|
||||||
|
mockCompile = jasmine.createSpy('$compile');
|
||||||
|
mockLog = jasmine.createSpyObj('$log', ['error', 'warn']);
|
||||||
|
mockScope = jasmine.createSpyObj('$scope', ['$on']);
|
||||||
|
mockElement = jasmine.createSpyObj('element', JQLITE_METHODS);
|
||||||
|
mockPromise = jasmine.createSpyObj('promise', ['then']);
|
||||||
|
mockTemplates = {};
|
||||||
|
mockElements = {};
|
||||||
|
|
||||||
|
mockTemplateRequest.andReturn(mockPromise);
|
||||||
|
mockCompile.andCallFake(function (html) {
|
||||||
|
mockTemplates[html] = jasmine.createSpy('template');
|
||||||
|
mockElements[html] =
|
||||||
|
jasmine.createSpyObj('templateEl', JQLITE_METHODS);
|
||||||
|
mockTemplates[html].andReturn(mockElements[html]);
|
||||||
|
return mockTemplates[html];
|
||||||
|
});
|
||||||
|
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
||||||
|
return { trusted: url };
|
||||||
|
});
|
||||||
|
|
||||||
|
linker = new TemplateLinker(
|
||||||
|
mockTemplateRequest,
|
||||||
|
mockSce,
|
||||||
|
mockCompile,
|
||||||
|
mockLog
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves extension paths", function () {
|
||||||
|
expect(linker.getPath({
|
||||||
|
bundle: {
|
||||||
|
path: 'a',
|
||||||
|
resources: 'b'
|
||||||
|
},
|
||||||
|
templateUrl: 'c/d.html'
|
||||||
|
})).toEqual('a/b/c/d.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when linking elements", function () {
|
||||||
|
var changeTemplate,
|
||||||
|
commentElement;
|
||||||
|
|
||||||
|
function findCommentElement() {
|
||||||
|
mockCompile.calls.forEach(function (call) {
|
||||||
|
var html = call.args[0];
|
||||||
|
if (html.indexOf("<!--") > -1) {
|
||||||
|
commentElement = mockElements[html];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
changeTemplate = linker.link(mockScope, mockElement);
|
||||||
|
findCommentElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("compiles a comment to use to replace element", function () {
|
||||||
|
expect(commentElement).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially replaces elements with comments", function () {
|
||||||
|
expect(mockElement.replaceWith)
|
||||||
|
.toHaveBeenCalledWith(commentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a function to change templates", function () {
|
||||||
|
expect(changeTemplate).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and then changing templates", function () {
|
||||||
|
var testUrl,
|
||||||
|
testTemplate;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testUrl = "some/url/template.html";
|
||||||
|
testTemplate = "<div>Some template!</div>";
|
||||||
|
changeTemplate(testUrl);
|
||||||
|
mockPromise.then.mostRecentCall
|
||||||
|
.args[0](testTemplate);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads templates using $templateRequest", function () {
|
||||||
|
expect(mockTemplateRequest).toHaveBeenCalledWith({
|
||||||
|
trusted: testUrl
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("compiles loaded templates with linked scope", function () {
|
||||||
|
expect(mockCompile).toHaveBeenCalledWith(testTemplate);
|
||||||
|
expect(mockTemplates[testTemplate])
|
||||||
|
.toHaveBeenCalledWith(mockScope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces comments with specified element", function () {
|
||||||
|
expect(commentElement.replaceWith)
|
||||||
|
.toHaveBeenCalledWith(mockElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends rendered content to the specified element", function () {
|
||||||
|
expect(mockElement.append)
|
||||||
|
.toHaveBeenCalledWith(mockElements[testTemplate]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears templates when called with undefined", function () {
|
||||||
|
expect(mockElement.replaceWith.callCount)
|
||||||
|
.toEqual(1);
|
||||||
|
changeTemplate(undefined);
|
||||||
|
expect(mockElement.replaceWith.callCount)
|
||||||
|
.toEqual(2);
|
||||||
|
expect(mockElement.replaceWith.mostRecentCall.args[0])
|
||||||
|
.toEqual(commentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs no warnings for nominal changes", function () {
|
||||||
|
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("which cannot be found", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
changeTemplate("some/bad/url");
|
||||||
|
// Reject the template promise
|
||||||
|
mockPromise.then.mostRecentCall.args[1]();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the element from the DOM", function () {
|
||||||
|
expect(mockElement.replaceWith.callCount)
|
||||||
|
.toEqual(2);
|
||||||
|
expect(
|
||||||
|
mockElement.replaceWith.mostRecentCall.args[0]
|
||||||
|
).toEqual(commentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs a warning", function () {
|
||||||
|
expect(mockLog.warn)
|
||||||
|
.toHaveBeenCalledWith(jasmine.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when an initial template URL is provided", function () {
|
||||||
|
var testUrl;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testUrl = "some/test/url.html";
|
||||||
|
linker.link(mockScope, mockElement, testUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not remove the element initially", function () {
|
||||||
|
expect(mockElement.replaceWith)
|
||||||
|
.not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads the specified template", function () {
|
||||||
|
expect(mockTemplateRequest).toHaveBeenCalledWith({
|
||||||
|
trusted: testUrl
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -7,5 +7,6 @@
|
|||||||
"gestures/GestureRepresenter",
|
"gestures/GestureRepresenter",
|
||||||
"services/DndService",
|
"services/DndService",
|
||||||
"MCTInclude",
|
"MCTInclude",
|
||||||
"MCTRepresentation"
|
"MCTRepresentation",
|
||||||
]
|
"TemplateLinker"
|
||||||
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user