mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
46 Commits
omm-large-
...
open315
Author | SHA1 | Date | |
---|---|---|---|
ea21cd7ccf | |||
d1f74677a2 | |||
8a783d4a87 | |||
342a6b4579 | |||
7fb45888d4 | |||
173be63844 | |||
9df54e875f | |||
c500d0b84e | |||
1c0be97a6d | |||
0d06fe3c07 | |||
0946e82dd1 | |||
c7b094f26e | |||
e898fee504 | |||
c02d1ae902 | |||
3f80951ba9 | |||
deb4314dce | |||
b0812bf8e3 | |||
4fea6b932c | |||
6a7727e1c6 | |||
de136bc19a | |||
404327e583 | |||
e12e0d72da | |||
4eb17342f6 | |||
e453868994 | |||
111ebe83da | |||
a16cb16c31 | |||
b5fb2176e9 | |||
70e11d66e1 | |||
a039b9b5fe | |||
5fdffee9a5 | |||
1781e9be32 | |||
756e445a80 | |||
b450d36472 | |||
b5d2949b8f | |||
c6eb07a810 | |||
63ce7349e3 | |||
add4e22cd3 | |||
40bd04f455 | |||
4fee1ee153 | |||
cfb6d4ccbf | |||
50db3287db | |||
4a3ecf1435 | |||
c24c3d4534 | |||
d6d57a396a | |||
16781b6156 | |||
24e1c1ff8c |
@ -1113,9 +1113,9 @@ contents of this object are managed entirely by the view/representation which
|
||||
receives it.
|
||||
* `representation`: An empty object, useful as a 'scratch pad' for
|
||||
representation state.
|
||||
* `ngModel`: An object passed through the ng-model attribute of the
|
||||
* `mctModel`: An object passed through the `mct-model` attribute of the
|
||||
`mct-representation` , if any.
|
||||
* `parameters`: An object passed through the parameters attribute of the
|
||||
* `parameters`: An object passed through the `parameters` attribute of the
|
||||
`mct-representation`, if any.
|
||||
* Any capabilities requested by the uses property of the representation
|
||||
definition.
|
||||
@ -1507,10 +1507,12 @@ attributes, all of which are specified as Angular expressions:
|
||||
|
||||
* `key`: Machine-readable identifier for the template (of extension category
|
||||
templates ) to be displayed.
|
||||
* `ng-model`: _Optional_; will be passed into the template's scope as `ngModel`.
|
||||
Intended usage is for two-way bound user input.
|
||||
* `mct-model`: _Optional_; will be passed into the template's scope as `mctModel`.
|
||||
Intended usage is for modification by the template.
|
||||
Note that this value will _not_ be two-way bound, so bi-directional
|
||||
communication should be achieved by modifying _properties_ on the object.
|
||||
* `parameters`: _Optional_; will be passed into the template's scope as
|
||||
parameters. Intended usage is for template-specific display parameters.
|
||||
`parameters`. Intended usage is for template-specific display parameters.
|
||||
|
||||
## Representation
|
||||
|
||||
@ -1522,11 +1524,14 @@ attributes, all of which are specified as Angular expressions:
|
||||
|
||||
* `key`: Machine-readable identifier for the representation (of extension
|
||||
category _representations_ or _views_ ) to be displayed.
|
||||
* `mct-object`: The domain object being represented.
|
||||
* `ng-model`: Optional; will be passed into the template's scope as `ngModel`.
|
||||
Intended usage is for two-way bound user input.
|
||||
* `parameters`: Optional; will be passed into the template's scope as
|
||||
parameters . Intended usage is for template-specific display parameters.
|
||||
* `mct-object`: The domain object being represented. Will be available in the
|
||||
representation's scope as `domainObject`.
|
||||
* `mct-model`: Optional; will be passed into the template's scope as `mctModel`.
|
||||
Intended usage is for modification by the template.
|
||||
Note that this value will _not_ be two-way bound, so bi-directional
|
||||
communication should be achieved by modifying _properties_ on the object.
|
||||
* `parameters`: Optional; will be passed into the representation's scope as
|
||||
`parameters`. Intended usage is for representation-specific display parameters.
|
||||
|
||||
## Resize
|
||||
|
||||
|
@ -44,6 +44,7 @@ define([
|
||||
"./src/directives/MCTContainer",
|
||||
"./src/directives/MCTDrag",
|
||||
"./src/directives/MCTClickElsewhere",
|
||||
"./src/directives/MCTRefresh",
|
||||
"./src/directives/MCTResize",
|
||||
"./src/directives/MCTPopup",
|
||||
"./src/directives/MCTScroll",
|
||||
@ -92,6 +93,7 @@ define([
|
||||
MCTContainer,
|
||||
MCTDrag,
|
||||
MCTClickElsewhere,
|
||||
MCTRefresh,
|
||||
MCTResize,
|
||||
MCTPopup,
|
||||
MCTScroll,
|
||||
@ -344,6 +346,10 @@ define([
|
||||
"$document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctRefresh",
|
||||
"implementation": MCTRefresh
|
||||
},
|
||||
{
|
||||
"key": "mctResize",
|
||||
"implementation": MCTResize,
|
||||
|
@ -19,9 +19,10 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="t-object-label l-flex-row flex-elem grows">
|
||||
<div class="t-item-icon flex-elem" ng-class="{ 'l-icon-link':location.isLink() }">
|
||||
<div class="t-item-icon-glyph">{{type.getGlyph()}}</div>
|
||||
<div class="t-object-label l-flex-row flex-elem grows"
|
||||
mct-refresh="domainObject.getCapability('mutation').listen(callback)">
|
||||
<div class="t-item-icon flex-elem" ng-class="::{ 'l-icon-link':location.isLink() }">
|
||||
<div class="t-item-icon-glyph">{{::type.getGlyph()}}</div>
|
||||
</div>
|
||||
<div class='t-title-label flex-elem grows'>{{model.name}}</div>
|
||||
<div class='t-title-label flex-elem grows'>{{::model.name}}</div>
|
||||
</div>
|
||||
|
@ -19,18 +19,19 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<ul class="tree">
|
||||
<li ng-if="!composition">
|
||||
<ul class="tree"
|
||||
mct-refresh="domainObject.getCapability('mutation').listen(callback)">
|
||||
<li ng-hide="::composition">
|
||||
<span class="tree-item">
|
||||
<span class="icon wait-spinner"></span>
|
||||
<span class="title-label">Loading...</span>
|
||||
</span>
|
||||
</li>
|
||||
<li ng-repeat="child in composition">
|
||||
<li ng-repeat="child in ::composition">
|
||||
<mct-representation key="'tree-node'"
|
||||
mct-object="child"
|
||||
parameters="parameters"
|
||||
ng-model="ngModel">
|
||||
mct-object="::child"
|
||||
parameters="::parameters"
|
||||
mct-model="::mctModel">
|
||||
</mct-representation>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -19,15 +19,15 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="ToggleController as toggle">
|
||||
<span ng-controller="TreeNodeController as treeNode">
|
||||
|
||||
<span ng-controller="TreeNodeController as treeNode">
|
||||
<span mct-refresh="treeNode.listen(callback)">
|
||||
<span
|
||||
class="tree-item menus-to-left"
|
||||
ng-class="{selected: treeNode.isSelected()}"
|
||||
>
|
||||
ng-class="::{selected: treeNode.isSelected()}">
|
||||
<span
|
||||
class='ui-symbol view-control flex-elem'
|
||||
ng-class="{ 'has-children': model.composition !== undefined, expanded: toggle.isActive() }"
|
||||
ng-class="::{ 'has-children': model.composition !== undefined, expanded: toggle.isActive() }"
|
||||
ng-click="toggle.toggle(); treeNode.trackExpansion()"
|
||||
>
|
||||
</span>
|
||||
@ -35,21 +35,21 @@
|
||||
class="rep-object-label"
|
||||
key="'label'"
|
||||
mct-object="domainObject"
|
||||
parameters="{suppressMenuOnEdit: true}"
|
||||
parameters="::{suppressMenuOnEdit: true}"
|
||||
ng-click="treeNode.select()"
|
||||
>
|
||||
</mct-representation>
|
||||
</span>
|
||||
<span
|
||||
class="tree-item-subtree"
|
||||
ng-show="toggle.isActive()"
|
||||
ng-if="model.composition !== undefined"
|
||||
ng-if="::treeNode.isExpanded()"
|
||||
>
|
||||
|
||||
<mct-representation key="'subtree'"
|
||||
ng-model="ngModel"
|
||||
parameters="parameters"
|
||||
mct-object="treeNode.hasBeenExpanded() && domainObject">
|
||||
mct-model="::mctModel"
|
||||
parameters="::parameters"
|
||||
mct-object="::(treeNode.hasBeenExpanded() && domainObject)"
|
||||
>
|
||||
</mct-representation>
|
||||
|
||||
</span>
|
||||
|
@ -22,9 +22,9 @@
|
||||
<ul class="tree">
|
||||
<li>
|
||||
<mct-representation key="'tree-node'"
|
||||
mct-object="domainObject"
|
||||
ng-model="ngModel"
|
||||
parameters="parameters">
|
||||
mct-object="::domainObject"
|
||||
mct-model="::mctModel"
|
||||
parameters="::parameters">
|
||||
</mct-representation>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -97,6 +97,7 @@ define(
|
||||
navContext = navObject &&
|
||||
navObject.getCapability('context'),
|
||||
nodePath,
|
||||
wasSelected = self.isSelected(),
|
||||
navPath;
|
||||
|
||||
// Deselect; we will reselect below, iff we are
|
||||
@ -121,15 +122,17 @@ define(
|
||||
// otherwise, expand.
|
||||
if (nodePath.length === navPath.length) {
|
||||
self.isSelectedFlag = true;
|
||||
} else { // node path is shorter: Expand!
|
||||
if ($scope.toggle) {
|
||||
$scope.toggle.setState(true);
|
||||
}
|
||||
self.trackExpansion();
|
||||
} else if (!self.isExpanded()) {
|
||||
// node path is shorter: Expand!
|
||||
self.toggle();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (self.isSelected() !== wasSelected) {
|
||||
self.notifyListener();
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for the selection updates; track the currently
|
||||
@ -139,16 +142,53 @@ define(
|
||||
checkSelection();
|
||||
}
|
||||
|
||||
this.isExpandedFlag = false;
|
||||
this.isSelectedFlag = false;
|
||||
this.hasBeenExpandedFlag = false;
|
||||
this.$timeout = $timeout;
|
||||
this.$scope = $scope;
|
||||
|
||||
checkSelection();
|
||||
|
||||
// Listen for changes which will effect display parameters
|
||||
$scope.$watch("ngModel.selectedObject", setSelection);
|
||||
$scope.$watch("domainObject", checkSelection);
|
||||
}
|
||||
|
||||
TreeNodeController.prototype.notifyListener = function () {
|
||||
if (this.listener) {
|
||||
this.listener();
|
||||
}
|
||||
};
|
||||
|
||||
TreeNodeController.prototype.isExpanded = function () {
|
||||
return this.isExpandedFlag;
|
||||
};
|
||||
|
||||
TreeNodeController.prototype.toggle = function () {
|
||||
this.isExpandedFlag = !this.isExpandedFlag;
|
||||
if (this.isExpanded() && !this.hasBeenExpanded()) {
|
||||
this.trackExpansion();
|
||||
}
|
||||
this.notifyListener();
|
||||
};
|
||||
|
||||
TreeNodeController.prototype.listen = function (callback) {
|
||||
var self = this,
|
||||
domainObject = this.$scope.domainObject,
|
||||
unlistenToMutation;
|
||||
|
||||
this.listener = callback;
|
||||
unlistenToMutation = domainObject.getCapability('mutation')
|
||||
.listen(callback);
|
||||
|
||||
return function () {
|
||||
unlistenToMutation();
|
||||
if (self.listener === callback) {
|
||||
delete self.listener;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the domain object represented by this node in the tree.
|
||||
* This will both update the `selectedObject` property in
|
||||
@ -178,6 +218,7 @@ define(
|
||||
// want this to be spread across multiple digest cycles.
|
||||
self.$timeout(function () {
|
||||
self.hasBeenExpandedFlag = true;
|
||||
self.notifyListener();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
91
platform/commonUI/general/src/directives/MCTRefresh.js
Normal file
91
platform/commonUI/general/src/directives/MCTRefresh.js
Normal file
@ -0,0 +1,91 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
['angular'],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The `mct-refresh` directive may be used to explicitly
|
||||
* trigger the refresh of the contents of the HTML element
|
||||
* which has this attribute. When used in combination with
|
||||
* one-time binding, this allows templates (or sections thereof)
|
||||
* to eschew watches and instead use other strategies for
|
||||
* change detection.
|
||||
*
|
||||
* The `mct-refresh` directive is applied as an attribute
|
||||
* whose value should be an Angular expression which:
|
||||
*
|
||||
* * Will be evaluated with a variable `callback`, which is
|
||||
* a function that, when invoked, will trigger a refresh.
|
||||
* * May return a function which will be invoked by `mct-refresh`
|
||||
* when the directive is no longer applicable; this should
|
||||
* be used to release any resources associated with the
|
||||
* above callback.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```
|
||||
* <span mct-refresh="someObservable.observe(callback)">
|
||||
* <div>{{::someObservable.getValue()}}</div>
|
||||
* </span>
|
||||
* ```
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/general
|
||||
*/
|
||||
function MCTRefresh() {
|
||||
|
||||
function link(scope, elem, attrs, ctrl, transclude) {
|
||||
var unlisten;
|
||||
|
||||
function recreateContents() {
|
||||
transclude(function (clone) {
|
||||
elem.empty();
|
||||
elem.append(clone);
|
||||
});
|
||||
}
|
||||
|
||||
recreateContents();
|
||||
|
||||
unlisten = scope.$eval(
|
||||
attrs.mctRefresh,
|
||||
{ callback: recreateContents }
|
||||
);
|
||||
|
||||
if (angular.isFunction(unlisten)) {
|
||||
scope.$on("$destroy", unlisten);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
transclude: true,
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTRefresh;
|
||||
}
|
||||
);
|
122
platform/commonUI/general/test/directives/MCTRefreshSpec.js
Normal file
122
platform/commonUI/general/test/directives/MCTRefreshSpec.js
Normal file
@ -0,0 +1,122 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/directives/MCTRefresh"],
|
||||
function (MCTRefresh) {
|
||||
"use strict";
|
||||
|
||||
describe("The mct-refresh directive", function () {
|
||||
var mctRefresh;
|
||||
|
||||
beforeEach(function () {
|
||||
mctRefresh = new MCTRefresh();
|
||||
});
|
||||
|
||||
it("is applicable as an attribute only", function () {
|
||||
expect(mctRefresh.restrict).toEqual("A");
|
||||
});
|
||||
|
||||
describe("when linked", function () {
|
||||
var mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockTransclude,
|
||||
mockClone,
|
||||
mockUnlisten;
|
||||
|
||||
function fireEvent(event) {
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === event) {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$eval", "$on", "$apply" ]
|
||||
);
|
||||
mockElement = jasmine.createSpyObj(
|
||||
"elem",
|
||||
[ "empty", "append" ]
|
||||
);
|
||||
testAttrs = { mctRefresh: "some-expr" };
|
||||
mockTransclude = jasmine.createSpy();
|
||||
mockTransclude.andCallFake(function (fn) {
|
||||
fn(mockClone);
|
||||
});
|
||||
mockClone = jasmine.createSpyObj(
|
||||
"elem",
|
||||
[ "empty", "append" ]
|
||||
);
|
||||
mockUnlisten = jasmine.createSpy();
|
||||
|
||||
mockScope.$eval.andReturn(mockUnlisten);
|
||||
|
||||
mctRefresh.link(
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
{},
|
||||
mockTransclude
|
||||
);
|
||||
});
|
||||
|
||||
it("adds its transcluded content", function () {
|
||||
expect(mockElement.append)
|
||||
.toHaveBeenCalledWith(mockClone);
|
||||
});
|
||||
|
||||
it("passes a callback into its associated expression", function () {
|
||||
expect(mockScope.$eval).toHaveBeenCalledWith(
|
||||
testAttrs.mctRefresh,
|
||||
{ callback: jasmine.any(Function) }
|
||||
);
|
||||
});
|
||||
|
||||
describe("and triggered via callback", function () {
|
||||
beforeEach(function () {
|
||||
mockScope.$eval.mostRecentCall.args[1].callback();
|
||||
});
|
||||
|
||||
it("transcludes its content again", function () {
|
||||
expect(mockTransclude.calls.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and then destroyed", function () {
|
||||
beforeEach(function () {
|
||||
fireEvent("$destroy");
|
||||
});
|
||||
|
||||
it("stops listening", function () {
|
||||
expect(mockUnlisten).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -16,6 +16,7 @@
|
||||
"directives/MCTContainer",
|
||||
"directives/MCTDrag",
|
||||
"directives/MCTPopup",
|
||||
"directives/MCTRefresh",
|
||||
"directives/MCTResize",
|
||||
"directives/MCTScroll",
|
||||
"directives/MCTSplitPane",
|
||||
|
@ -25,8 +25,8 @@
|
||||
* Module defining MCTInclude. Created by vwoeltje on 11/7/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
["./OneWayBinder"],
|
||||
function (OneWayBinder) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -35,19 +35,26 @@ define(
|
||||
* key which can be exposed by bundles, instead of requiring
|
||||
* an explicit path.
|
||||
*
|
||||
* This directive uses two-way binding for three attributes:
|
||||
* This directive uses the following attributes:
|
||||
*
|
||||
* * `key`, matched against the key of a defined template extension
|
||||
* in order to determine which actual template to include.
|
||||
* * `ng-model`, populated as `ngModel` in the loaded template's
|
||||
* scope; used for normal ng-model purposes (e.g. if the
|
||||
* included template is meant to two-way bind to a data model.)
|
||||
* * `parameters`, used to communicate display parameters to
|
||||
* the included template (e.g. title.) The difference between
|
||||
* `parameters` and `ngModel` is intent: Both are two-way
|
||||
* bound, but `ngModel` is useful for data models (more like
|
||||
* an output) and `parameters` is meant to be useful for
|
||||
* display parameterization (more like an input.)
|
||||
* * `key`: An Angular expression, matched against the key of a
|
||||
* defined template extension in order to determine which actual
|
||||
* template to include.
|
||||
* * `mct-model`: An Angular expression; its value is watched
|
||||
* and passed into the template's scope as property `mctModel`.
|
||||
* * `parameters`: An Angular expression; its value is watched
|
||||
* and passed into the template's scope as property `parameters`.
|
||||
*
|
||||
* The difference between `parameters` and `mct-model` is intent;
|
||||
* `parameters` should be used for display-time parameters which
|
||||
* are not meant to be changed, whereas `mct-model` should be
|
||||
* used to pass in objects whose properties will (or may) be
|
||||
* modified by the included template.
|
||||
*
|
||||
* (For backwards compatibility, `ng-model` is treated identically
|
||||
* to `mct-model`, and the property `ngModel` will be provided
|
||||
* in scope with the same value as `mctModel`. This usage is
|
||||
* deprecated and should be avoided.)
|
||||
*
|
||||
* @memberof platform/representation
|
||||
* @constructor
|
||||
@ -57,14 +64,24 @@ define(
|
||||
function MCTInclude(templates, templateLinker) {
|
||||
var templateMap = {};
|
||||
|
||||
function link(scope, element) {
|
||||
var changeTemplate = templateLinker.link(
|
||||
scope,
|
||||
element,
|
||||
scope.key && templateMap[scope.key]
|
||||
);
|
||||
function link(scope, element, attrs) {
|
||||
var parent = scope.$parent,
|
||||
key = parent.$eval(attrs.key),
|
||||
changeTemplate = templateLinker.link(
|
||||
scope,
|
||||
element,
|
||||
key && templateMap[key]
|
||||
),
|
||||
binder = new OneWayBinder(scope, attrs);
|
||||
|
||||
scope.$watch('key', function (key) {
|
||||
binder.bind('ngModel');
|
||||
binder.bind('mctModel');
|
||||
binder.bind('parameters');
|
||||
|
||||
binder.alias('ngModel', 'mctModel');
|
||||
binder.alias('mctModel', 'ngModel');
|
||||
|
||||
binder.watch('key', function (key) {
|
||||
changeTemplate(key && templateMap[key]);
|
||||
});
|
||||
}
|
||||
@ -87,8 +104,8 @@ define(
|
||||
// May hide the element, so let other directives act first
|
||||
priority: -1000,
|
||||
|
||||
// Two-way bind key, ngModel, and parameters
|
||||
scope: { key: "=", ngModel: "=", parameters: "=" }
|
||||
// Isolate this scope; do not inherit properties from parent
|
||||
scope: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -27,26 +27,40 @@
|
||||
* @namespace platform/representation
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
["./OneWayBinder"],
|
||||
function (OneWayBinder) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Defines the mct-representation directive. This may be used to
|
||||
* present domain objects as HTML (with event wiring), with the
|
||||
* specific representation being mapped to a defined extension
|
||||
* (as defined in either the `representation` category-of-extension,
|
||||
* (as defined in either the `representations` category-of-extension,
|
||||
* or the `views` category-of-extension.)
|
||||
*
|
||||
* This directive uses two-way binding for three attributes:
|
||||
*
|
||||
* * `key`, matched against the key of a defined template extension
|
||||
* in order to determine which actual template to include.
|
||||
* * `mct-object`, populated as `domainObject` in the loaded
|
||||
* template's scope. This is the domain object being
|
||||
* represented as HTML by this directive.
|
||||
* * `parameters`, used to communicate display parameters to
|
||||
* the included template (e.g. title.)
|
||||
* * `key`: An Angular expression, matched against the key of a
|
||||
* defined representation or view extension in order to determine
|
||||
* which actual template to include.
|
||||
* * `mct-model`: An Angular expression; its value is watched
|
||||
* and passed into the template's scope as property `mctModel`.
|
||||
* * `parameters`: An Angular expression; its value is watched
|
||||
* and passed into the template's scope as property `parameters`.
|
||||
*
|
||||
* The difference between `parameters` and `mct-model` is intent;
|
||||
* `parameters` should be used for display-time parameters which
|
||||
* are not meant to be changed, whereas `mct-model` should be
|
||||
* used to pass in objects whose properties will (or may) be
|
||||
* modified by the included representation.
|
||||
*
|
||||
* (For backwards compatibility, `ng-model` is treated identically
|
||||
* to `mct-model`, and the property `ngModel` will be provided
|
||||
* in scope with the same value as `mctModel`. This usage is
|
||||
* deprecated and should be avoided.)
|
||||
*
|
||||
* @memberof platform/representation
|
||||
* @constructor
|
||||
@ -94,7 +108,8 @@ define(
|
||||
couldEdit = false,
|
||||
lastIdPath = [],
|
||||
lastKey,
|
||||
changeTemplate = templateLinker.link($scope, element);
|
||||
changeTemplate = templateLinker.link($scope, element),
|
||||
binder = new OneWayBinder($scope, attrs);
|
||||
|
||||
// Populate scope with any capabilities indicated by the
|
||||
// representation's extension definition
|
||||
@ -236,13 +251,20 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
binder.bind('parameters');
|
||||
binder.bind('mctModel');
|
||||
binder.bind('ngModel');
|
||||
|
||||
binder.alias('ngModel', 'mctModel');
|
||||
binder.alias('mctModel', 'ngModel');
|
||||
|
||||
// Update the representation when the key changes (e.g. if a
|
||||
// different representation has been selected)
|
||||
$scope.$watch("key", refresh);
|
||||
binder.bind('key', refresh);
|
||||
|
||||
// Also update when the represented domain object changes
|
||||
// (to a different object)
|
||||
$scope.$watch("domainObject", refresh);
|
||||
binder.alias('mctObject', 'domainObject', refresh);
|
||||
|
||||
// Finally, also update when there is a new version of that
|
||||
// same domain object; these changes should be tracked in the
|
||||
@ -270,14 +292,8 @@ define(
|
||||
// May hide the element, so let other directives act first
|
||||
priority: -1000,
|
||||
|
||||
// Two-way bind key and parameters, get the represented domain
|
||||
// object as "mct-object"
|
||||
scope: {
|
||||
key: "=",
|
||||
domainObject: "=mctObject",
|
||||
ngModel: "=",
|
||||
parameters: "="
|
||||
}
|
||||
// Isolate this scope
|
||||
scope: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
113
platform/representation/src/OneWayBinder.js
Normal file
113
platform/representation/src/OneWayBinder.js
Normal file
@ -0,0 +1,113 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Supports the "one-way" binding behavior of `mct-representation`
|
||||
* and `mct-include`; watches expression associated with attributes
|
||||
* in a parent scope, then passes these into the child scope when
|
||||
* they change (but does not assign anything back to the parent
|
||||
* scope if the child changes.)
|
||||
* @constructor
|
||||
* @memberof platform/representation
|
||||
* @param scope the Angular scope to watch
|
||||
* @param attrs the relevant attributes, as passed into the `link`
|
||||
* function of the relevant directive
|
||||
*/
|
||||
function OneWayBinder(scope, attrs) {
|
||||
var self = this;
|
||||
|
||||
this.unwatches = [];
|
||||
this.scope = scope;
|
||||
this.parent = scope.$parent;
|
||||
this.attrs = attrs;
|
||||
|
||||
// Detach any listeners from the parent
|
||||
scope.$on('$destroy', function () {
|
||||
self.unwatches.forEach(function (unwatch) {
|
||||
unwatch();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* One-way bind an attribute. The value of the named attribute will
|
||||
* be watched as an Angular expression in the parent scope; its
|
||||
* value will be exposed in the child scope as a property of
|
||||
* the same name.
|
||||
* @param {string} attr the name of the attribute to watch
|
||||
* @param {Function} [callback] a callback to invoke with new values
|
||||
*/
|
||||
OneWayBinder.prototype.bind = function (attr, callback) {
|
||||
this.alias(attr, attr, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* One-way bind an attribute. As `bind`, but allows the property
|
||||
* name in the child scope used to expose these values to be
|
||||
* specified as something different from the attribute name.
|
||||
* @param {string} attr the name of the attribute to watch
|
||||
* @param {string} property the name of the property to use in scope
|
||||
* @param {Function} [callback] a callback to invoke with new values
|
||||
*/
|
||||
OneWayBinder.prototype.alias = function (attr, property, callback) {
|
||||
var scope = this.scope;
|
||||
|
||||
this.watch(attr, function expose(value) {
|
||||
scope[property] = value;
|
||||
if (callback) {
|
||||
callback(value);
|
||||
}
|
||||
});
|
||||
|
||||
// Expose in scope immediately, similar to scope: { attr: "=" }
|
||||
// in a directive definition object.
|
||||
scope[property] = this.parent.$eval(this.attrs[attr]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Watch for changes in this attribute. The named attribute's value
|
||||
* will be watched as an Angular expression in the parent scope,
|
||||
* and the provided callback will be invoked with the value of that
|
||||
* expression when changes occur.
|
||||
* @param {string} attr the name of the attribute to watch
|
||||
* @param {Function} callback the callback to invoke with new values
|
||||
*/
|
||||
OneWayBinder.prototype.watch = function (attr, callback) {
|
||||
var expr = this.attrs[attr];
|
||||
if (expr) {
|
||||
this.unwatches.push(this.parent.$watch(
|
||||
expr,
|
||||
callback,
|
||||
expr && expr[0] === '{'
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
return OneWayBinder;
|
||||
}
|
||||
);
|
@ -35,11 +35,12 @@ define(
|
||||
mockLinker,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockChangeTemplate,
|
||||
mctInclude;
|
||||
|
||||
function fireWatch(expr, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
mockScope.$parent.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
@ -68,6 +69,8 @@ define(
|
||||
['link', 'getPath']
|
||||
);
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
|
||||
mockScope.$parent =
|
||||
jasmine.createSpyObj('parent', ['$watch', '$eval']);
|
||||
mockElement = jasmine.createSpyObj('element', ['empty']);
|
||||
mockChangeTemplate = jasmine.createSpy('changeTemplate');
|
||||
mockLinker.link.andReturn(mockChangeTemplate);
|
||||
@ -75,7 +78,12 @@ define(
|
||||
return testUrls[template.key];
|
||||
});
|
||||
mctInclude = new MCTInclude(testTemplates, mockLinker);
|
||||
mctInclude.link(mockScope, mockElement, {});
|
||||
testAttrs = {
|
||||
key: "parentKey",
|
||||
mctModel: "someExpr",
|
||||
ngModel: "someOtherExpr"
|
||||
};
|
||||
mctInclude.link(mockScope, mockElement, testAttrs);
|
||||
});
|
||||
|
||||
it("is restricted to elements", function () {
|
||||
@ -88,17 +96,28 @@ define(
|
||||
});
|
||||
|
||||
it("reads a template location from a scope's key variable", function () {
|
||||
mockScope.key = 'abc';
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch(testAttrs.key, 'abc');
|
||||
expect(mockChangeTemplate)
|
||||
.toHaveBeenCalledWith(testTemplates[0]);
|
||||
|
||||
mockScope.key = 'xyz';
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch(testAttrs.key, 'xyz');
|
||||
expect(mockChangeTemplate)
|
||||
.toHaveBeenCalledWith(testTemplates[1]);
|
||||
});
|
||||
|
||||
it("watches for changes on both ng-model and mct-model", function () {
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.ngModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.mctModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -44,6 +44,7 @@ define(
|
||||
mockChangeTemplate,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockDomainObject,
|
||||
testModel,
|
||||
mctRepresentation;
|
||||
@ -57,7 +58,7 @@ define(
|
||||
}
|
||||
|
||||
function fireWatch(expr, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
mockScope.$parent.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
@ -102,6 +103,13 @@ define(
|
||||
testUrls[t.key] = "some URL " + String(i);
|
||||
});
|
||||
|
||||
testAttrs = {
|
||||
"mctObject": "someExpr",
|
||||
"key": "someOtherExpr",
|
||||
"ngModel": "yetAnotherExpr",
|
||||
"mctModel": "theExprsKeepOnComing"
|
||||
};
|
||||
|
||||
mockRepresenters = ["A", "B"].map(function (name) {
|
||||
var constructor = jasmine.createSpy("Representer" + name),
|
||||
representer = jasmine.createSpyObj(
|
||||
@ -121,6 +129,8 @@ define(
|
||||
mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS);
|
||||
|
||||
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
|
||||
mockScope.$parent =
|
||||
jasmine.createSpyObj('parent', ['$watch', '$eval']);
|
||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||
|
||||
@ -138,7 +148,7 @@ define(
|
||||
mockLinker,
|
||||
mockLog
|
||||
);
|
||||
mctRepresentation.link(mockScope, mockElement);
|
||||
mctRepresentation.link(mockScope, mockElement, testAttrs);
|
||||
});
|
||||
|
||||
it("is restricted to elements", function () {
|
||||
@ -150,15 +160,7 @@ define(
|
||||
.toHaveBeenCalledWith(mockScope, mockElement);
|
||||
});
|
||||
|
||||
it("watches scope when linked", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"key",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"domainObject",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
it("watches for model changes when linked", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"domainObject.getModel().modified",
|
||||
jasmine.any(Function)
|
||||
@ -166,24 +168,16 @@ define(
|
||||
});
|
||||
|
||||
it("recognizes keys for representations", function () {
|
||||
mockScope.key = "abc";
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Trigger the watch
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
fireWatch(testAttrs.key, "abc");
|
||||
fireWatch(testAttrs.mctObject, mockDomainObject);
|
||||
|
||||
expect(mockChangeTemplate)
|
||||
.toHaveBeenCalledWith(testRepresentations[0]);
|
||||
});
|
||||
|
||||
it("recognizes keys for views", function () {
|
||||
mockScope.key = "xyz";
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Trigger the watches
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
fireWatch(testAttrs.key, "xyz");
|
||||
fireWatch(testAttrs.mctObject, mockDomainObject);
|
||||
|
||||
expect(mockChangeTemplate)
|
||||
.toHaveBeenCalledWith(testViews[1]);
|
||||
@ -192,25 +186,20 @@ define(
|
||||
it("does not load templates until there is an object", function () {
|
||||
mockScope.key = "xyz";
|
||||
|
||||
// Trigger the watch
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch(testAttrs.key, "xyz");
|
||||
|
||||
expect(mockChangeTemplate)
|
||||
.not.toHaveBeenCalledWith(jasmine.any(Object));
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
fireWatch(testAttrs.mctObject, mockDomainObject);
|
||||
|
||||
expect(mockChangeTemplate)
|
||||
.toHaveBeenCalledWith(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it("loads declared capabilities", function () {
|
||||
mockScope.key = "def";
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Trigger the watch
|
||||
mockScope.$watch.calls[0].args[1]();
|
||||
fireWatch(testAttrs.key, "def");
|
||||
fireWatch(testAttrs.mctObject, mockDomainObject);
|
||||
|
||||
expect(mockDomainObject.useCapability)
|
||||
.toHaveBeenCalledWith("testCapability");
|
||||
@ -219,35 +208,43 @@ define(
|
||||
});
|
||||
|
||||
it("logs when no representation is available for a key", function () {
|
||||
mockScope.key = "someUnknownThing";
|
||||
|
||||
// Verify precondition
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
|
||||
// Trigger the watch
|
||||
mockScope.$watch.calls[0].args[1]();
|
||||
fireWatch(testAttrs.key, "someUnkownThing");
|
||||
|
||||
// Should have gotten a warning - that's an unknown key
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears out obsolete peroperties from scope", function () {
|
||||
mockScope.key = "def";
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockDomainObject.useCapability.andReturn("some value");
|
||||
|
||||
// Trigger the watch
|
||||
mockScope.$watch.calls[0].args[1]();
|
||||
fireWatch(testAttrs.key, "def");
|
||||
fireWatch(testAttrs.mctObject, mockDomainObject);
|
||||
expect(mockScope.testCapability).toBeDefined();
|
||||
|
||||
// Change the view
|
||||
mockScope.key = "xyz";
|
||||
// Change the view; should clear capabilities from scope
|
||||
fireWatch(testAttrs.key, "xyz");
|
||||
|
||||
// Trigger the watch again; should clear capability from scope
|
||||
mockScope.$watch.calls[0].args[1]();
|
||||
expect(mockScope.testCapability).toBeUndefined();
|
||||
});
|
||||
|
||||
it("watches for changes on both ng-model and mct-model", function () {
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.ngModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.mctModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("detects changes among linked instances", function () {
|
||||
var mockContext = jasmine.createSpyObj('context', ['getPath']),
|
||||
mockContext2 = jasmine.createSpyObj('context', ['getPath']),
|
||||
@ -295,6 +292,19 @@ define(
|
||||
expect(mockChangeTemplate.calls.length)
|
||||
.toEqual(callCount + 1);
|
||||
});
|
||||
|
||||
it("watches for changes on both ng-model and mct-model", function () {
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.ngModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
|
||||
testAttrs.mctModel,
|
||||
jasmine.any(Function),
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
177
platform/representation/test/OneWayBinderSpec.js
Normal file
177
platform/representation/test/OneWayBinderSpec.js
Normal file
@ -0,0 +1,177 @@
|
||||
/*****************************************************************************
|
||||
* 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/OneWayBinder"],
|
||||
function (OneWayBinder) {
|
||||
'use strict';
|
||||
|
||||
describe("OneWayBinder", function () {
|
||||
var mockScope,
|
||||
testAttrs,
|
||||
testValues,
|
||||
mockUnwatches,
|
||||
binder;
|
||||
|
||||
function fireEvent(event) {
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === event) {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fireParentWatch(expr) {
|
||||
mockScope.$parent.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](mockScope.$parent.$eval(expr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockUnwatches = [];
|
||||
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$on']);
|
||||
mockScope.$parent = jasmine.createSpyObj(
|
||||
'$parent',
|
||||
[ '$watch', '$eval' ]
|
||||
);
|
||||
testAttrs = { a: 'attrA', b: 'attrB', c: 'attrC' };
|
||||
testValues = { attrA: 42, attrB: ['foo'], attrC: { a: 0 } };
|
||||
|
||||
mockScope.$parent.$eval.andCallFake(function (expr) {
|
||||
return testValues[expr];
|
||||
});
|
||||
|
||||
mockScope.$parent.$watch.andCallFake(function () {
|
||||
var mockUnwatch = jasmine.createSpy();
|
||||
mockUnwatches.push(mockUnwatch);
|
||||
return mockUnwatch;
|
||||
});
|
||||
|
||||
binder = new OneWayBinder(mockScope, testAttrs);
|
||||
});
|
||||
|
||||
describe("bind", function () {
|
||||
var attrNames;
|
||||
|
||||
beforeEach(function () {
|
||||
attrNames = Object.keys(testAttrs);
|
||||
attrNames.forEach(function (attr) {
|
||||
binder.bind(attr);
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes values from the parent in scope", function () {
|
||||
attrNames.forEach(function (attr) {
|
||||
expect(mockScope[attr])
|
||||
.toEqual(testValues[testAttrs[attr]]);
|
||||
});
|
||||
});
|
||||
|
||||
it("updates values from the parent in scope", function () {
|
||||
var oldValues = testValues,
|
||||
newValues = {};
|
||||
Object.keys(oldValues).forEach(function (key) {
|
||||
newValues[key] = oldValues[key] + " a change";
|
||||
});
|
||||
|
||||
testValues = newValues;
|
||||
|
||||
attrNames.forEach(function (attr) {
|
||||
expect(mockScope[attr])
|
||||
.toEqual(oldValues[testAttrs[attr]]);
|
||||
fireParentWatch(testAttrs[attr]);
|
||||
expect(mockScope[attr])
|
||||
.toEqual(newValues[testAttrs[attr]]);
|
||||
});
|
||||
});
|
||||
|
||||
it("attaches one watch per attribute", function () {
|
||||
expect(mockUnwatches.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("alias", function () {
|
||||
var attrNames;
|
||||
|
||||
beforeEach(function () {
|
||||
binder.alias('a', 'someAlias');
|
||||
});
|
||||
|
||||
it("exposes values under a different name", function () {
|
||||
expect(mockScope.someAlias).toEqual(testValues.attrA);
|
||||
});
|
||||
|
||||
it("updates values under a different name", function () {
|
||||
var newValue = "some new value";
|
||||
testValues.attrA = newValue;
|
||||
expect(mockScope.someAlias).not.toEqual(newValue);
|
||||
fireParentWatch(testAttrs.a);
|
||||
expect(mockScope.someAlias).toEqual(newValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("watch", function () {
|
||||
var mockCallback = jasmine.createSpy();
|
||||
beforeEach(function () {
|
||||
binder.watch('b', mockCallback);
|
||||
});
|
||||
|
||||
it("invokes callbacks when values change", function () {
|
||||
var newValue = "some new value";
|
||||
testValues.attrB = newValue;
|
||||
expect(mockCallback).not.toHaveBeenCalled();
|
||||
fireParentWatch(testAttrs.b);
|
||||
expect(mockCallback).toHaveBeenCalledWith(newValue);
|
||||
});
|
||||
|
||||
it("generally watches for reference equality", function () {
|
||||
expect(mockScope.$parent.$watch.mostRecentCall.args[2])
|
||||
.toBeFalsy();
|
||||
});
|
||||
|
||||
it("watches for equivalence when expressions are anonymous objects", function () {
|
||||
testAttrs.d = "{ a: 'foo' }";
|
||||
binder.watch('d', mockCallback);
|
||||
expect(mockScope.$parent.$watch.mostRecentCall.args[2])
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("releases watches from parent when scope is destroyed", function () {
|
||||
binder.bind('a');
|
||||
binder.alias('b', 'xyz');
|
||||
binder.watch('c', jasmine.createSpy());
|
||||
fireEvent('$destroy');
|
||||
mockUnwatches.forEach(function (mockUnwatch) {
|
||||
expect(mockUnwatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
);
|
@ -48,10 +48,11 @@ define(function () {
|
||||
this.$scope = $scope;
|
||||
this.searchService = searchService;
|
||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||
this.availabileResults = 0;
|
||||
this.availableResults = 0;
|
||||
this.$scope.results = [];
|
||||
this.$scope.loading = false;
|
||||
this.pendingQuery = undefined;
|
||||
this.$scope.ngModel = this.$scope.ngModel || {};
|
||||
this.$scope.ngModel.filter = function () {
|
||||
return controller.onFilterChange.apply(controller, arguments);
|
||||
};
|
||||
|
@ -68,6 +68,8 @@ requirejs.config({
|
||||
}
|
||||
},
|
||||
|
||||
waitSeconds: 30,
|
||||
|
||||
// dynamically load all test files
|
||||
deps: allTestFiles,
|
||||
|
||||
|
Reference in New Issue
Block a user