[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

@@ -25,45 +25,15 @@ define([
"./src/LayoutController",
"./src/FixedController",
"./src/LayoutCompositionPolicy",
"../../commonUI/browse/src/InspectorRegion",
"./src/PlotOptionsController",
'legacyRegistry'
], function (
LayoutController,
FixedController,
LayoutCompositionPolicy,
InspectorRegion,
PlotOptionsController,
legacyRegistry
) {
"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", {
"name": "Layout components.",
"description": "Plug in adding Layout capabilities.",
@@ -222,10 +192,6 @@ define([
{
"key": "frame",
"templateUrl": "templates/frame.html"
},
{
"key": "plot-options-browse",
"templateUrl": "templates/plot-options-browse.html"
}
],
"controllers": [
@@ -247,13 +213,6 @@ define([
"telemetryFormatter",
"throttle"
]
},
{
"key": "PlotOptionsController",
"implementation": PlotOptionsController,
"depends": [
"$scope"
]
}
],
"templates": [
@@ -353,12 +312,7 @@ define([
"property": "layoutGrid",
"conversion": "number[]"
}
],
"regions": {
"inspector": plotInspector
}
]
}
]
}

View File

@@ -19,8 +19,31 @@
this source code distribution or the Licensing information page available
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">
<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="tree">
<li ng-repeat="child in children">
@@ -41,8 +64,8 @@
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="plotOptionsModel"
structure="plotOptionsStructure"
ng-model="configuration.plot.series[child.getId()]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row l-controls-first no-validate">
</mct-form>

View File

@@ -27,8 +27,8 @@
* @namespace platform/features/layout
*/
define(
[],
function () {
['./PlotOptionsForm'],
function (PlotOptionsForm) {
"use strict";
/**
@@ -50,136 +50,98 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function PlotOptionsController($scope) {
function PlotOptionsController($scope, topic) {
var self = this,
domainObject = $scope.domainObject,
xAxisForm = {
'name':'x-axis',
'sections': [{
'name': 'x-axis',
'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 = {};
composition,
mutationListener,
formListener,
configuration = domainObject.getModel().configuration || {};
/*domainObject.getModel().configuration.plot.xAxis= {
'key': 'scet'
};
this.plotOptionsForm = new PlotOptionsForm(topic);
domainObject.getModel().configuration.plot.yAxis = [{
'autoscale': true,
'min': 0,
'max': 15,
'key': 'eu'
}];
/*
* Determine whether the changes to the model that triggered a
* mutation event were purely compositional.
*/
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 = [
{
'id': '',
'lineStyle': '',
'color': '#aaddaa',
'interpolation': 'none'
},
//etc
];*/
$scope.plotOptionsStructure = plotSeriesForm;
$scope.plotOptionsModel = plotOptionsModel;
/*
Default the plot options model
*/
function defaultConfiguration() {
configuration.plot = configuration.plot || {};
configuration.plot.xAxis = configuration.plot.xAxis || {};
configuration.plot.yAxis = configuration.plot.yAxis || {}; // y-axes will be associative array keyed on axis key
configuration.plot.series = configuration.plot.series || {}; // series will be associative array keyed on sub-object id
$scope.configuration = configuration;
}
/*
When a child is added to, or removed from a plot, update the
plot options model
*/
function updateChildren() {
domainObject.useCapability('composition').then(function(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
children.
*/
domainObject.getCapability('mutation').listen(function(model) {
updateChildren();
mutationListener = domainObject.getCapability('mutation').listen(function(model) {
if (hasCompositionChanged(composition, model.composition)) {
updateChildren();
}
});
formListener = this.plotOptionsForm.listen(updateConfiguration);
defaultConfiguration();
updateChildren();
$scope.$on("$destroy", function() {
//Clean up any listeners on destruction of controller
mutationListener();
formListener();
});
}
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);
});
});
}
);