[Plots] #638 added onchange handling in order to synchronize forms with domain object model.

Fixed failing test

Added tests

jslint errors

Minor refactoring of layout bundle

revert layout/bundle.json
This commit is contained in:
Henry 2016-02-03 16:00:11 -08:00
parent f2903f4030
commit abf5f22155
24 changed files with 867 additions and 190 deletions

View File

@ -295,7 +295,7 @@ define([
"provides": "typeService", "provides": "typeService",
"type": "decorator", "type": "decorator",
"implementation": TypeRegionDecorator "implementation": TypeRegionDecorator
}, }
], ],
"runs": [ "runs": [
{ {

View File

@ -62,7 +62,7 @@ define(
} }
}; };
this.addPart(metadataPart, 0); this.addPart(metadataPart, 0);
} };
return InspectorRegion; return InspectorRegion;
} }

View File

@ -29,16 +29,17 @@ define(
"use strict"; "use strict";
/** /**
* Adds default screen regions to Type definitions. Screen regions * Adds default browse screen regions to Type definitions. Screen
* are sections of the browse and edit view of an object that can be * regions are sections of the browse and edit view of an object
* customized on a per-type basis. Within {@link Region}s are {@link RegionPart}s. * that can be customized on a per-type basis. Within
* Policies can be used to decide which parts are visible or not based on object state. * {@link Region}s are {@link RegionPart}s. Policies can be used to
* decide which parts are visible or not based on object state.
* @memberOf platform/commonUI/regions * @memberOf platform/commonUI/regions
* @see {@link Region}, {@link RegionPart}, {@link EditableRegionPolicy} * @see {@link Region}, {@link RegionPart}, {@link EditableRegionPolicy}
* @constructor * @constructor
*/ */
function TypeRegionDecorator(typeService) { function TypeRegionDecorator(typeService) {
this.typeService = typeService this.typeService = typeService;
} }
/** /**

View File

@ -33,11 +33,11 @@ define(
var inspectorRegion; var inspectorRegion;
beforeEach(function () { beforeEach(function () {
inspectorRegion = new InspectorRegion; inspectorRegion = new InspectorRegion();
}); });
it("creates default region parts", function () { it("creates default region parts", function () {
expect(inspectorRegion.parts().length).toBe(2); expect(inspectorRegion.parts.length).toBe(1);
}); });
}); });

View File

@ -0,0 +1,70 @@
/*****************************************************************************
* 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*/
/**
* MCTIncudeSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/TypeRegionDecorator"],
function (TypeRegionDecorator) {
"use strict";
describe("The type region decorator", function () {
var typeRegionDecorator,
mockTypeService,
mockType,
mockTypeDefinition;
beforeEach(function () {
mockTypeDefinition = {};
mockType = jasmine.createSpyObj('type', [
'getDefinition'
]);
mockType.getDefinition.andReturn(mockTypeDefinition);
mockTypeService = jasmine.createSpyObj('typeService', [
'listTypes',
'getType'
]);
mockTypeService.getType.andReturn(mockType);
mockTypeService.listTypes.andReturn([mockType]);
typeRegionDecorator = new TypeRegionDecorator(mockTypeService);
});
it("decorates individual type definitions with basic inspector" +
" region", function () {
typeRegionDecorator.getType('someType');
expect(mockTypeDefinition.regions).toBeDefined();
});
it("decorates all type definitions with basic inspector" +
" region", function () {
typeRegionDecorator.listTypes();
expect(mockTypeDefinition.regions).toBeDefined();
});
});
}
);

View File

@ -23,6 +23,7 @@
ng-controller="DateTimeFieldController"> ng-controller="DateTimeFieldController">
<input type="text" <input type="text"
ng-model="textValue" ng-model="textValue"
ng-change="structure.onchange(ngModel[field])"
ng-blur="restoreTextValue(); ngBlur()" ng-blur="restoreTextValue(); ngBlur()"
ng-class="{ ng-class="{
error: textInvalid || error: textInvalid ||

View File

@ -44,11 +44,11 @@ define(
if (domainObject.getCapability('status').get('editing')){ if (domainObject.getCapability('status').get('editing')){
//If the domain object is in edit mode, only include a part //If the domain object is in edit mode, only include a part
// if it is marked editable // if it is marked editable
return regionPart.modes.indexOf('edit') != -1; return regionPart.modes.indexOf('edit') !== -1;
} else { } else {
//If the domain object is not in edit mode, return any parts //If the domain object is not in edit mode, return any parts
// that are not explicitly marked editable // that are not explicitly marked editable
return regionPart.modes.indexOf('browse') != -1; return regionPart.modes.indexOf('browse') !== -1;
} }
}; };

View File

@ -74,17 +74,19 @@ define(
/** /**
* Removes a part from this region. * Removes a part from this region.
* @param {RegionPart} part the part to add * @param {RegionPart | number | strnig} part The region part to
* @param {number} [index] the position to insert the part. By default * remove. If a number, will remove the part at that index. If a
* will add to the end * string, will remove the part with the matching name. If an
* object, will attempt to remove that object from the Region
*/ */
Region.prototype.removePart = function (part){ Region.prototype.removePart = function (part){
if (typeof part === 'number') { if (typeof part === 'number') {
this.parts.splice(part, 1); this.parts.splice(part, 1);
} else if (typeof part === 'string'){ } else if (typeof part === 'string'){
this.parts this.parts = this.parts.filter(function(thisPart) {
} return thisPart.name !== part;
else { });
} else {
this.parts.splice(this.parts.indexOf(part), 1); this.parts.splice(this.parts.indexOf(part), 1);
} }
}; };

View File

@ -37,22 +37,19 @@ define(
typeCapability = domainObject.getCapability('type'); typeCapability = domainObject.getCapability('type');
/** /**
* TODO: Refactor this out, probably to a directive. * Filters region parts to only those allowed by region policies
* Or, alternatively, could have a regionCapability that returns
* regions and parts filtered for applicability to current
* object state.
* @param regions * @param regions
* @returns {*} * @returns {{}}
*/ */
function filterParts(regions) { function filterParts(regions) {
//Dupe so we're not modifying the type definition. //Dupe so we're not modifying the type definition.
var filteredRegions = {}; var filteredRegions = {};
for (var regionName in regions) { Object.keys(regions).forEach(function(regionName) {
filteredRegions[regionName] = Object.create(regions[regionName]); filteredRegions[regionName] = Object.create(regions[regionName]);
filteredRegions[regionName].parts = regions[regionName].parts.filter(function(part){ filteredRegions[regionName].parts = (regions[regionName].parts || []).filter(function(part){
return policyService.allow('region', part, domainObject); return policyService.allow('region', part, domainObject);
}); });
} });
return filteredRegions; return filteredRegions;
} }

View File

@ -0,0 +1,75 @@
/*****************************************************************************
* 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/EditableRegionPolicy'],
function (EditableRegionPolicy) {
"use strict";
describe("The editable region policy ", function () {
var editableRegionPolicy,
mockDomainObject,
mockStatusCapability,
mockBrowseRegionPart = {
modes: 'browse'
},
mockEditRegionPart = {
modes: 'edit'
},
mockAllModesRegionPart = {};
beforeEach(function(){
editableRegionPolicy = new EditableRegionPolicy();
mockStatusCapability = jasmine.createSpyObj("statusCapability", [
"get"
]);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability"
]);
mockDomainObject.getCapability.andReturn(mockStatusCapability);
});
it("includes only browse region parts for object not in edit mode", function() {
mockStatusCapability.get.andReturn(false);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(true);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(false);
});
it("includes only edit region parts for object in edit mode", function() {
mockStatusCapability.get.andReturn(true);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(false);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(true);
});
it("includes region parts with no mode specification", function() {
mockStatusCapability.get.andReturn(false);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
mockStatusCapability.get.andReturn(true);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
});
});
}
);

View File

@ -0,0 +1,82 @@
/*****************************************************************************
* 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/RegionController'],
function (RegionController) {
"use strict";
describe("The region controller ", function () {
var mockScope,
mockDomainObject,
mockTypeCapability,
mockTypeDefinition,
mockPolicyService,
controller;
beforeEach(function(){
mockTypeDefinition = {
regions:
{
'regionOne': {
'parts': [
{'name': 'Part One'},
{'name': 'Part Two'}
]
}
}
};
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
'getDefinition'
]);
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockDomainObject.getCapability.andReturn(mockTypeCapability);
mockPolicyService = jasmine.createSpyObj('policyService', [
'allow'
]);
mockScope = {
domainObject: mockDomainObject
};
});
it("filters out regions disallowed by region policy", function() {
mockPolicyService.allow.andReturn(false);
controller = new RegionController(mockScope, mockPolicyService);
expect(mockScope.regions.regionOne.parts.length).toBe(0);
});
it("does not filter out regions allowed by region policy", function() {
mockPolicyService.allow.andReturn(true);
controller = new RegionController(mockScope, mockPolicyService);
expect(mockScope.regions.regionOne.parts.length).toBe(2);
});
});
}
);

View File

@ -0,0 +1,107 @@
/*****************************************************************************
* 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/Region'],
function (Region) {
"use strict";
describe("The region class ", function () {
var region,
part2 = {'name': 'part2'};
beforeEach(function(){
region = new Region();
region.parts = [
{name: 'part1'},
{name: 'part3'},
{name: 'part4'}
];
});
it("adding a region part at a specified index adds it in that" +
" position", function() {
region.addPart(part2, 1);
expect(region.parts.length).toBe(4);
expect(region.parts[1]).toBe(part2);
});
it("adding a region part without an index adds it at the end", function() {
var partN = {'name': 'partN'};
region.addPart(partN);
expect(region.parts.length).toBe(4);
expect(region.parts[region.parts.length-1]).toBe(partN);
});
describe("removing a region part", function(){
var partName = "part2";
beforeEach(function(){
region.parts = [
{name: 'part1'},
part2,
{name: 'part3'},
{name: 'part4'}
];
});
it("with a string matches on region part" +
" name", function() {
expect(region.parts.length).toBe(4);
expect(region.parts.indexOf(part2)).toBe(1);
region.removePart(partName);
expect(region.parts.length).toBe(3);
expect(region.parts.indexOf(part2)).toBe(-1);
});
it("with a number removes by index", function() {
expect(region.parts.length).toBe(4);
expect(region.parts.indexOf(part2)).toBe(1);
region.removePart(1);
expect(region.parts.length).toBe(3);
expect(region.parts.indexOf(part2)).toBe(-1);
});
it("with object matches that object", function() {
expect(region.parts.length).toBe(4);
expect(region.parts.indexOf(part2)).toBe(1);
region.removePart(part2);
expect(region.parts.length).toBe(3);
expect(region.parts.indexOf(part2)).toBe(-1);
});
});
});
}
);

View File

@ -1,3 +0,0 @@
[
"InspectorRegion"
]

View File

@ -25,45 +25,15 @@ define([
"./src/LayoutController", "./src/LayoutController",
"./src/FixedController", "./src/FixedController",
"./src/LayoutCompositionPolicy", "./src/LayoutCompositionPolicy",
"../../commonUI/browse/src/InspectorRegion",
"./src/PlotOptionsController",
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
LayoutController, LayoutController,
FixedController, FixedController,
LayoutCompositionPolicy, LayoutCompositionPolicy,
InspectorRegion,
PlotOptionsController,
legacyRegistry legacyRegistry
) { ) {
"use strict"; "use strict";
/**
* Customize and extend the default 'Inspector' region for the panel
* type, to add display options for plots. This should be moved to a
* dedicated type.
* @type {InspectorRegion}
*/
var plotInspector = new InspectorRegion(),
plotOptionsBrowsePart = {
name: "plot-options",
title: "Plot Options",
modes: ['browse'],
content: {
key: "plot-options-browse"
}
},
plotOptionsEditPart = {
name: "plot-options",
title: "Plot Options",
modes: ['edit'],
content: {
key: "plot-options-browse"
}
};
plotInspector.addPart(plotOptionsBrowsePart);
plotInspector.addPart(plotOptionsEditPart);
legacyRegistry.register("platform/features/layout", { legacyRegistry.register("platform/features/layout", {
"name": "Layout components.", "name": "Layout components.",
"description": "Plug in adding Layout capabilities.", "description": "Plug in adding Layout capabilities.",
@ -222,10 +192,6 @@ define([
{ {
"key": "frame", "key": "frame",
"templateUrl": "templates/frame.html" "templateUrl": "templates/frame.html"
},
{
"key": "plot-options-browse",
"templateUrl": "templates/plot-options-browse.html"
} }
], ],
"controllers": [ "controllers": [
@ -247,13 +213,6 @@ define([
"telemetryFormatter", "telemetryFormatter",
"throttle" "throttle"
] ]
},
{
"key": "PlotOptionsController",
"implementation": PlotOptionsController,
"depends": [
"$scope"
]
} }
], ],
"templates": [ "templates": [
@ -353,12 +312,7 @@ define([
"property": "layoutGrid", "property": "layoutGrid",
"conversion": "number[]" "conversion": "number[]"
} }
], ]
"regions": {
"inspector": plotInspector
}
} }
] ]
} }

View File

@ -19,8 +19,31 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<style>
.l-inspect .l-inspector-part .no-margin .form {
margin-left: 0;
}
.reduced-min-width .form .form-row > .label {
min-width: 80px;
}
</style>
<div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part"> <div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em> <em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<mct-form
ng-model="configuration.plot.xAxis"
structure="xAxisForm"
name="xAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
</mct-form>
<mct-form
ng-model="configuration.plot.yAxis"
structure="yAxisForm"
name="yAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
</mct-form>
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll"> <ul class="first flex-elem grows vscroll">
<ul class="tree"> <ul class="tree">
<li ng-repeat="child in children"> <li ng-repeat="child in children">
@ -41,8 +64,8 @@
</span> </span>
<mct-form <mct-form
ng-class="{hidden: !toggle.isActive()}" ng-class="{hidden: !toggle.isActive()}"
ng-model="plotOptionsModel" ng-model="configuration.plot.series[child.getId()]"
structure="plotOptionsStructure" structure="plotSeriesForm"
name="plotOptionsState" name="plotOptionsState"
class="flex-elem l-flex-row l-controls-first no-validate"> class="flex-elem l-flex-row l-controls-first no-validate">
</mct-form> </mct-form>

View File

@ -27,8 +27,8 @@
* @namespace platform/features/layout * @namespace platform/features/layout
*/ */
define( define(
[], ['./PlotOptionsForm'],
function () { function (PlotOptionsForm) {
"use strict"; "use strict";
/** /**
@ -50,136 +50,98 @@ define(
* @constructor * @constructor
* @param {Scope} $scope the controller's Angular scope * @param {Scope} $scope the controller's Angular scope
*/ */
function PlotOptionsController($scope) { function PlotOptionsController($scope, topic) {
var self = this, var self = this,
domainObject = $scope.domainObject, domainObject = $scope.domainObject,
xAxisForm = { composition,
'name':'x-axis', mutationListener,
'sections': [{ formListener,
'name': 'x-axis', configuration = domainObject.getModel().configuration || {};
'rows': [
{
'name': 'Domain',
'control': 'select',
'key': 'key',
//TODO fetch options from platform or object type configuration
'options': [
{'name':'scet', 'value': 'scet'},
{'name':'sclk', 'value': 'sclk'},
{'name':'lst', 'value': 'lst'}
]
}
]
}]},
yAxisForm = {
'name':'y-axis',
'sections': [{
// Will need to be repeated for each y-axis, with a
// distinct name each. Ideally the name of the axis itself.
'name': 'y-axis',
'rows': [
{
'name': 'Autoscale',
'control': 'checkbox',
'key': 'autoscale',
},
{
'name': 'Min',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]'
},
{
'name': 'Max',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]'
},
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name':'eu', 'value': 'eu'},
{'name':'dn', 'value': 'dn'},
{'name':'status', 'value': 'status'}
]
}
]
}]
},
plotSeriesForm = {
// For correctness of the rendered markup, repeated forms
// will probably need to have unique names.
'name': 'plotSeries',
'sections': [{
'name': 'Plot Series',
'rows': [
{
'name': 'Markers',
'control': 'checkbox',
'key': 'markers'
},
{
'name': 'No Line',
'control': 'radio',
'key': 'noLine'
},
{
'name': 'Step Line',
'control': 'radio',
'key': 'stepLine'
},
{
'name': 'Linear Line',
'control': 'radio',
'key': 'linearLine'
}
]
}]
},
plotOptionsModel = {};
/*domainObject.getModel().configuration.plot.xAxis= { this.plotOptionsForm = new PlotOptionsForm(topic);
'key': 'scet'
};
domainObject.getModel().configuration.plot.yAxis = [{ /*
'autoscale': true, * Determine whether the changes to the model that triggered a
'min': 0, * mutation event were purely compositional.
'max': 15, */
'key': 'eu' function hasCompositionChanged(oldComposition, newComposition){
}]; // Framed slightly strangely, but the boolean logic is
// easier to follow for the unchanged case.
var isUnchanged = oldComposition === newComposition ||
(
oldComposition.length === newComposition.length &&
oldComposition.every( function (currentValue, index) {
return newComposition[index] && currentValue === newComposition[index];
})
);
return !isUnchanged;
}
domainObject.getModel().configuration.plot.series = [ /*
{ Default the plot options model
'id': '', */
'lineStyle': '', function defaultConfiguration() {
'color': '#aaddaa', configuration.plot = configuration.plot || {};
'interpolation': 'none' configuration.plot.xAxis = configuration.plot.xAxis || {};
}, configuration.plot.yAxis = configuration.plot.yAxis || {}; // y-axes will be associative array keyed on axis key
//etc configuration.plot.series = configuration.plot.series || {}; // series will be associative array keyed on sub-object id
];*/ $scope.configuration = configuration;
}
$scope.plotOptionsStructure = plotSeriesForm;
$scope.plotOptionsModel = plotOptionsModel;
/*
When a child is added to, or removed from a plot, update the
plot options model
*/
function updateChildren() { function updateChildren() {
domainObject.useCapability('composition').then(function(children){ domainObject.useCapability('composition').then(function(children){
$scope.children = children; $scope.children = children;
composition = domainObject.getModel().composition;
children.forEach(function(child){
configuration.plot.series[child.getId()] = configuration.plot.series[child.getId()] || {};
});
}); });
} }
/*
On changes to the form, update the configuration on the domain
object
*/
function updateConfiguration() {
domainObject.useCapability('mutation', function(model){
model.configuration = model.configuration || {};
model.configuration.plot = configuration.plot;
});
}
/*
Set form structures on scope
*/
$scope.plotSeriesForm = this.plotOptionsForm.plotSeriesForm;
$scope.xAxisForm = this.plotOptionsForm.xAxisForm;
$scope.yAxisForm = this.plotOptionsForm.yAxisForm;
/* /*
Listen for changes to the domain object and update the object's Listen for changes to the domain object and update the object's
children. children.
*/ */
domainObject.getCapability('mutation').listen(function(model) { mutationListener = domainObject.getCapability('mutation').listen(function(model) {
updateChildren(); if (hasCompositionChanged(composition, model.composition)) {
updateChildren();
}
}); });
formListener = this.plotOptionsForm.listen(updateConfiguration);
defaultConfiguration();
updateChildren(); updateChildren();
$scope.$on("$destroy", function() {
//Clean up any listeners on destruction of controller
mutationListener();
formListener();
});
} }
return PlotOptionsController; return PlotOptionsController;

View File

@ -0,0 +1,166 @@
/*****************************************************************************
* 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 class for encapsulating structure and behaviour of the plot
* options form
* @memberOf platform/features/layout
* @param topic
* @constructor
*/
function PlotOptionsForm(topic) {
var self = this;
this.onchangeTopic = topic();
function onchange(value){
self.onchangeTopic.notify(value);
}
/*
Defined below are the form structures for the plot options.
*/
this.xAxisForm = {
'name':'x-axis',
'sections': [{
'name': 'x-axis',
'rows': [
{
'name': 'Domain',
'control': 'select',
'key': 'key',
'onchange': onchange,
'options': [
{'name':'scet', 'value': 'scet'},
{'name':'sclk', 'value': 'sclk'},
{'name':'lst', 'value': 'lst'}
]
}
]
}]};
this.yAxisForm = {
'name':'y-axis',
'sections': [{
// Will need to be repeated for each y-axis, with a
// distinct name for each. Ideally the name of the axis
// itself.
'name': 'y-axis',
'rows': [
{
'name': 'Autoscale',
'control': 'checkbox',
'key': 'autoscale',
'onchange': onchange
},
{
'name': 'Min',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]',
'onchange': onchange
},
{
'name': 'Max',
'control': 'textfield',
'key': 'max',
'pattern': '[0-9]',
'onchange': onchange
},
{
'name': 'Range',
'control': 'select',
'key': 'key',
'onchange': onchange,
'options': [
{'name':'eu', 'value': 'eu'},
{'name':'dn', 'value': 'dn'},
{'name':'status', 'value': 'status'}
]
}
]
}]
};
this.plotSeriesForm = {
'name':'Series Options',
'sections': [
{
rows: [
{
'name': 'Color',
'control': 'color',
'key': 'color',
'onchange': onchange
}]
},
{
'rows':[
{
'name': 'Markers',
'control': 'checkbox',
'key': 'markers',
'onchange': onchange
}
]
},
{
'rows':[
{
'name': 'No Line',
'control': 'radio',
'key': 'lineType',
'value': 'noLine',
'onchange': onchange
},
{
'name': 'Step Line',
'control': 'radio',
'key': 'lineType',
'value': 'stepLine',
'onchange': onchange
},
{
'name': 'Linear Line',
'control': 'radio',
'key': 'lineType',
'value': 'linearLine',
'onchange': onchange
}
]
}
]
};
}
PlotOptionsForm.prototype.listen = function (callback){
return this.onchangeTopic.listen(callback);
};
return PlotOptionsForm;
}
);

View File

@ -0,0 +1,161 @@
/*****************************************************************************
* 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,jasmine,xit*/
define(
['../src/PlotOptionsController'],
function (PlotOptionsController) {
"use strict";
describe("The Plot Options controller", function () {
var plotOptionsController,
mockTopicFunction,
mockTopicObject,
mockDomainObject,
mockMutationCapability,
mockUseCapabilities,
mockCompositionCapability,
mockComposition,
mockUnlisten,
mockFormUnlisten,
mockChildOne,
mockChildTwo,
model,
mockScope;
beforeEach(function () {
model = {
composition: ['childOne']
};
mockChildOne = jasmine.createSpyObj('domainObject', [
'getId'
]);
mockChildOne.getId.andReturn('childOne');
mockChildTwo = jasmine.createSpyObj('childTwo', [
'getId'
]);
mockChildOne.getId.andReturn('childTwo');
mockCompositionCapability = jasmine.createSpyObj('compositionCapability', [
'then'
]);
mockComposition = [
mockChildOne
];
mockCompositionCapability.then.andCallFake(function (callback){
callback(mockComposition);
});
mockUseCapabilities = jasmine.createSpyObj('useCapabilities', [
'composition',
'mutation'
]);
mockUseCapabilities.composition.andReturn(mockCompositionCapability);
mockMutationCapability = jasmine.createSpyObj('mutationCapability', [
'listen'
]);
mockUnlisten = jasmine.createSpy('unlisten');
mockMutationCapability.listen.andReturn(mockUnlisten);
mockTopicObject = jasmine.createSpyObj('Topic', [
'listen',
'notify'
]);
mockFormUnlisten = jasmine.createSpy('formUnlisten');
mockTopicObject.listen.andReturn(mockFormUnlisten);
mockTopicFunction = function() {
return mockTopicObject;
};
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getModel',
'useCapability',
'getCapability'
]);
mockDomainObject.useCapability.andCallFake(function(capability){
return mockUseCapabilities[capability]();
});
mockDomainObject.getCapability.andReturn(mockMutationCapability);
mockDomainObject.getModel.andReturn(model);
mockScope = jasmine.createSpyObj('scope', [
'$on'
]);
mockScope.domainObject = mockDomainObject;
plotOptionsController = new PlotOptionsController(mockScope, mockTopicFunction);
});
it("sets form definitions on scope", function () {
expect(mockScope.xAxisForm).toBeDefined();
expect(mockScope.yAxisForm).toBeDefined();
expect(mockScope.plotSeriesForm).toBeDefined();
});
it("sets object children on scope", function () {
expect(mockScope.children).toBe(mockComposition);
});
it("on changes in object composition, updates the form", function () {
expect(mockMutationCapability.listen).toHaveBeenCalled();
expect(mockScope.children).toBe(mockComposition);
expect(mockScope.children.length).toBe(1);
mockComposition.push(mockChildTwo);
model.composition.push('childTwo');
mockMutationCapability.listen.mostRecentCall.args[0](model);
expect(mockScope.children).toBe(mockComposition);
expect(mockScope.children.length).toBe(2);
});
it("on changes in form values, updates the object model", function () {
var scopeConfiguration = mockScope.configuration,
model = mockDomainObject.getModel();
scopeConfiguration.plot.xAxis.key = 'lst';
scopeConfiguration.plot.yAxis.autoScale = true;
scopeConfiguration.plot.yAxis.key = 'eu';
expect(mockTopicObject.listen).toHaveBeenCalled();
mockTopicObject.listen.mostRecentCall.args[0]();
expect(mockDomainObject.useCapability).toHaveBeenCalledWith('mutation', jasmine.any(Function));
mockDomainObject.useCapability.mostRecentCall.args[1](model);
expect(model.configuration.plot.xAxis.key).toBe('lst');
expect(model.configuration.plot.yAxis.autoScale).toBe(true);
expect(model.configuration.plot.yAxis.key).toBe('eu');
});
it("cleans up listeners on destruction of the controller", function () {
mockScope.$on.mostRecentCall.args[1]();
expect(mockUnlisten).toHaveBeenCalled();
expect(mockFormUnlisten).toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,73 @@
/*****************************************************************************
* 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,jasmine,xit*/
define(
['../src/PlotOptionsForm'],
function (PlotOptionsForm) {
"use strict";
describe("The Plot Options form", function () {
var plotOptionsForm,
mockTopicFunction,
mockTopicObject,
listener;
beforeEach(function () {
mockTopicObject = jasmine.createSpyObj('Topic', [
'listen',
'notify'
]);
mockTopicFunction = function() {
return mockTopicObject;
};
plotOptionsForm = new PlotOptionsForm(mockTopicFunction);
});
it("defines form specs for x-axis, y-axis, and series data", function () {
expect(plotOptionsForm.xAxisForm).toBeDefined();
expect(plotOptionsForm.xAxisForm.sections).toBeDefined();
expect(plotOptionsForm.xAxisForm.sections[0].rows).toBeDefined();
expect(plotOptionsForm.xAxisForm.sections[0].rows.length).toBeGreaterThan(0);
expect(plotOptionsForm.yAxisForm).toBeDefined();
expect(plotOptionsForm.plotSeriesForm).toBeDefined();
});
it("uses a topic to register listeners and inform them when a" +
" form value changes", function () {
var changedValue = 'changedValue';
expect(plotOptionsForm.xAxisForm.sections[0].rows[0].onchange).toBeDefined();
plotOptionsForm.listen(listener);
expect(mockTopicObject.listen).toHaveBeenCalledWith(listener);
plotOptionsForm.xAxisForm.sections[0].rows[0].onchange(changedValue);
expect(mockTopicObject.notify).toHaveBeenCalledWith(changedValue);
});
});
}
);

View File

@ -23,6 +23,7 @@
<input type="checkbox" <input type="checkbox"
name="mctControl" name="mctControl"
ng-model="ngModel[field]" ng-model="ngModel[field]"
ng-change="structure.onchange(ngModel[field])"
ng-disabled="ngDisabled"> ng-disabled="ngDisabled">
<em></em> <em></em>
</label> </label>

View File

@ -23,6 +23,8 @@
<input type="radio" <input type="radio"
name="mctControl" name="mctControl"
ng-model="ngModel[field]" ng-model="ngModel[field]"
ng-disabled="ngDisabled"> ng-disabled="ngDisabled"
ng-change="structure.onchange(ngModel[field])"
ng-value="structure.value">
<em></em> <em></em>
</label> </label>

View File

@ -24,6 +24,7 @@
ng-model="ngModel[field]" ng-model="ngModel[field]"
ng-options="opt.value as opt.name for opt in options" ng-options="opt.value as opt.name for opt in options"
ng-required="ngRequired" ng-required="ngRequired"
ng-change="structure.onchange(ngModel[field])"
name="mctControl"> name="mctControl">
<option value="" ng-show="!ngModel[field]">- Select One -</option> <option value="" ng-show="!ngModel[field]">- Select One -</option>
</select> </select>

View File

@ -25,6 +25,7 @@
ng-required="ngRequired" ng-required="ngRequired"
ng-model="ngModel[field]" ng-model="ngModel[field]"
ng-pattern="ngPattern" ng-pattern="ngPattern"
ng-change="structure.onchange(ngModel[field])"
size="{{structure.size}}" size="{{structure.size}}"
name="mctControl"> name="mctControl">
</span> </span>

View File

@ -41,6 +41,7 @@
<mct-control key="row.control" <mct-control key="row.control"
ng-model="ngModel" ng-model="ngModel"
ng-required="row.required" ng-required="row.required"
ng-change="row.onchange"
ng-pattern="getRegExp(row.pattern)" ng-pattern="getRegExp(row.pattern)"
options="row.options" options="row.options"
structure="row" structure="row"