Simon Hunt

GUI -- Implemented Panel Service.

Change-Id: I5e60c6ffa5676bc11f7312681af7bca85b4f8036
/*
* Copyright 2014,2015 Open Networking Laboratory
*
* 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.
*/
/*
ONOS GUI -- Panel Service -- CSS file
*/
.floatpanel {
position: absolute;
z-index: 100;
display: block;
top: 64px;
width: 200px;
right: -220px;
opacity: 0;
background-color: rgba(255,255,255,0.8);
padding: 10px;
color: black;
font-size: 10pt;
-moz-border-radius: 6px;
border-radius: 6px;
box-shadow: 0px 2px 12px #777;
}
/* TODO: light/dark themes */
.light .floatpanel {
}
.dark .floatpanel {
}
......@@ -20,46 +20,161 @@
(function () {
'use strict';
var $log;
var $log, fs;
var defaultSettings = {
position: 'TR',
side: 'right',
width: 200
edge: 'right',
width: 200,
height: 80,
margin: 20,
xtnTime: 750
};
angular.module('onosLayer')
.factory('PanelService', ['$log', function (_$log_) {
$log = _$log_;
var panels,
panelLayer;
function createPanel(opts) {
var settings = angular.extend({}, defaultSettings, opts);
function init() {
panelLayer = d3.select('#floatpanels');
panelLayer.html('');
panels = {};
}
function renderPanel() {
// helpers for panel
function noop() {}
function margin(p) {
return p.settings.margin;
}
function noPx(p, what) {
return Number(p.el.style(what).replace(/px$/, ''));
}
function widthVal(p) {
return noPx(p, 'width');
}
function heightVal(p) {
return noPx(p, 'height');
}
function pxShow(p) {
return margin(p) + 'px';
}
function pxHide(p) {
return (-margin(p) - widthVal(p)) + 'px';
}
function showPanel() {
function makePanel(id, settings) {
var p = {
id: id,
settings: settings,
on: false,
el: null
},
api = {
show: showPanel,
hide: hidePanel,
empty: emptyPanel,
append: appendPanel,
width: panelWidth,
height: panelHeight,
isVisible: panelIsVisible
};
p.el = panelLayer.append('div')
.attr('id', id)
.attr('class', 'floatpanel')
.style('opacity', 0);
// has to be called after el is set
p.el.style(p.settings.edge, pxHide(p));
panelWidth(p.settings.width);
panelHeight(p.settings.height);
panels[id] = p;
function showPanel(cb) {
var endCb = fs.isF(cb) || noop;
p.on = true;
p.el.transition().duration(p.settings.xtnTime)
.each('end', endCb)
.style(p.settings.edge, pxShow(p))
.style('opacity', 1);
}
function hidePanel() {
function hidePanel(cb) {
var endCb = fs.isF(cb) || noop;
p.on = false;
p.el.transition().duration(p.settings.xtnTime)
.each('end', endCb)
.style(p.settings.edge, pxHide(p))
.style('opacity', 0);
}
function emptyPanel() {
return p.el.html('');
}
var api = {
render: renderPanel,
show: showPanel,
hide: hidePanel
};
function appendPanel(what) {
return p.el.append(what);
}
function panelWidth(w) {
if (w === undefined) {
return widthVal(p);
}
p.el.style('width', w + 'px');
}
function panelHeight(h) {
if (h === undefined) {
return heightVal(p);
}
p.el.style('height', h + 'px');
}
function panelIsVisible() {
return p.on;
}
$log.debug('creating panel with settings: ', settings);
return api;
}
function removePanel(id) {
panelLayer.select('#' + id).remove();
delete panels[id];
}
angular.module('onosLayer')
.factory('PanelService', ['$log', 'FnService', function (_$log_, _fs_) {
$log = _$log_;
fs = _fs_;
function createPanel(id, opts) {
var settings = angular.extend({}, defaultSettings, opts);
if (!id) {
$log.warn('createPanel: no ID given');
return null;
}
if (panels[id]) {
$log.warn('Panel with ID "' + id + '" already exists');
return null;
}
$log.debug('creating panel:', id, settings);
return makePanel(id, settings);
}
function destroyPanel(id) {
if (panels[id]) {
$log.debug('destroying panel:', id);
removePanel(id);
} else {
$log.debug('no panel to destroy:', id);
}
}
return {
createPanel: createPanel
init: init,
createPanel: createPanel,
destroyPanel: destroyPanel
};
}]);
......
......@@ -66,6 +66,7 @@
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="fw/mast/mast.css">
<link rel="stylesheet" href="fw/svg/icon.css">
<link rel="stylesheet" href="fw/layer/panel.css">
<link rel="stylesheet" href="fw/nav/nav.css">
<!-- This is where contributed javascript will get injected -->
......
......@@ -24,12 +24,13 @@
// define core module dependencies here...
var coreDependencies = [
'ngRoute',
'onosWidget',
'onosMast',
'onosNav',
'onosUtil',
'onosSvg',
'onosRemote',
'onosMast',
'onosNav'
'onosLayer',
'onosWidget'
];
// view IDs.. note the first view listed is loaded at startup
......@@ -63,9 +64,9 @@
.controller('OnosCtrl', [
'$log', '$route', '$routeParams', '$location',
'KeyService', 'ThemeService', 'GlyphService',
'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
function ($log, $route, $routeParams, $location, ks, ts, gs) {
function ($log, $route, $routeParams, $location, ks, ts, gs, ps) {
var self = this;
self.$route = $route;
......@@ -77,6 +78,7 @@
ts.init();
ks.installOn(d3.select('body'));
gs.init();
ps.init();
$log.log('OnosCtrl has been created');
......
......@@ -28,7 +28,7 @@
];
// references to injected services etc.
var $log, ks, zs, gs, ms, wss;
var $log, ks, zs, gs, ms, wss, ps;
// DOM elements
var ovtopo, svg, defs, zoomLayer, map;
......@@ -170,11 +170,12 @@
angular.module('ovTopo', moduleDependencies)
.controller('OvTopoCtrl', [
'$scope', '$log', '$location',
'$scope', '$log', '$location', '$timeout',
'KeyService', 'ZoomService', 'GlyphService', 'MapService',
'WebSocketService',
'WebSocketService', 'PanelService',
function ($scope, _$log_, $loc, _ks_, _zs_, _gs_, _ms_, _wss_) {
function ($scope, _$log_, $loc, $timeout,
_ks_, _zs_, _gs_, _ms_, _wss_, _ps_) {
var self = this;
$log = _$log_;
ks = _ks_;
......@@ -182,16 +183,18 @@
gs = _gs_;
ms = _ms_;
wss = _wss_;
ps = _ps_;
self.notifyResize = function () {
svgResized(svg.style('width'), svg.style('height'));
};
// Cleanup on destroyed scope..
$scope.$on('$destroy', function () {
$log.log('OvTopoCtrl is saying Buh-Bye!');
// TODO: cleanup when the scope is destroyed...
// for example, closing the web socket.
wsock && wsock.close();
wsock = null;
ps.destroyPanel('topo-p-summary');
});
// svg layer and initialization of components
......@@ -204,6 +207,12 @@
setUpMap();
setUpWebSocket($loc.search().wsport);
// TODO: remove this temporary code....
var p = ps.createPanel('topo-p-summary');
p.append('h1').text('Hello World');
p.show();
$timeout(function () { p.hide(); }, 2000);
$log.log('OvTopoCtrl has been created');
}]);
}());
......
......@@ -18,16 +18,27 @@
ONOS GUI -- Layer -- Panel Service - Unit Tests
*/
describe('factory: fw/layer/panel.js', function () {
var $log, fs, ps;
var $log, $timeout, fs, ps, d3Elem;
beforeEach(module('onosLayer'));
beforeEach(inject(function (_$log_, FnService, PanelService) {
beforeEach(inject(function (_$log_, _$timeout_, FnService, PanelService) {
$log = _$log_;
$timeout = _$timeout_;
fs = FnService;
ps = PanelService;
d3Elem = d3.select('body').append('div').attr('id', 'floatpanels');
ps.init();
}));
afterEach(function () {
d3.select('#floatpanels').remove();
ps.init();
});
function floatPanelSelection() {
return d3Elem.selectAll('.floatpanel');
}
it('should define PanelService', function () {
expect(ps).toBeDefined();
......@@ -35,8 +46,134 @@ describe('factory: fw/layer/panel.js', function () {
it('should define api functions', function () {
expect(fs.areFunctions(ps, [
'createPanel'
'init', 'createPanel', 'destroyPanel'
])).toBeTruthy();
});
it('should have no panels to start', function () {
expect(floatPanelSelection().size()).toBe(0);
});
it('should log a warning if no ID is given', function () {
spyOn($log, 'warn');
var p = ps.createPanel();
expect(p).toBeNull();
expect($log.warn).toHaveBeenCalledWith('createPanel: no ID given');
expect(floatPanelSelection().size()).toBe(0);
});
it('should create a default panel', function () {
spyOn($log, 'warn');
spyOn($log, 'debug');
var p = ps.createPanel('foo');
expect(p).not.toBeNull();
expect($log.warn).not.toHaveBeenCalled();
expect(floatPanelSelection().size()).toBe(1);
expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
edge: 'right',
width: 200,
height: 80,
margin: 20,
xtnTime: 750
});
// check basic properties
expect(p.width()).toEqual(200);
expect(p.isVisible()).toBeFalsy();
var el = floatPanelSelection();
expect(el.style('width')).toEqual('200px');
});
it('should complain when a duplicate ID is used', function () {
spyOn($log, 'warn');
var p = ps.createPanel('foo');
expect(p).not.toBeNull();
expect($log.warn).not.toHaveBeenCalled();
expect(floatPanelSelection().size()).toBe(1);
var dup = ps.createPanel('foo');
expect(dup).toBeNull();
expect($log.warn).toHaveBeenCalledWith('Panel with ID "foo" already exists');
expect(floatPanelSelection().size()).toBe(1);
});
it('should note when there is no panel to destroy', function () {
spyOn($log, 'debug');
ps.destroyPanel('bar');
expect($log.debug).toHaveBeenCalledWith('no panel to destroy:', 'bar')
});
it('should destroy the panel', function () {
spyOn($log, 'debug');
var p = ps.createPanel('foo');
expect(floatPanelSelection().size()).toBe(1);
ps.destroyPanel('foo');
expect($log.debug).toHaveBeenCalledWith('destroying panel:', 'foo')
expect(floatPanelSelection().size()).toBe(0);
});
it('should allow alternate settings to be given', function () {
spyOn($log, 'debug');
var p = ps.createPanel('foo', { width: 250, edge: 'left' });
expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
edge: 'left',
width: 250,
height: 80,
margin: 20,
xtnTime: 750
});
});
it('should show and hide the panel', function () {
var p = ps.createPanel('foo', {xtnTime:0});
expect(p.isVisible()).toBeFalsy();
p.show();
expect(p.isVisible()).toBeTruthy();
p.hide();
expect(p.isVisible()).toBeFalsy();
});
it('should append content to the panel', function () {
var p = ps.createPanel('foo');
var span = p.append('span').attr('id', 'thisIsMySpan');
expect(floatPanelSelection().selectAll('span').attr('id'))
.toEqual('thisIsMySpan');
});
it('should remove content on empty', function () {
var p = ps.createPanel('voop');
p.append('span');
p.append('span');
p.append('span');
expect(floatPanelSelection().selectAll('span').size()).toEqual(3);
p.empty();
expect(floatPanelSelection().selectAll('span').size()).toEqual(0);
expect(floatPanelSelection().html()).toEqual('');
});
it('should allow programmatic setting of width', function () {
var p = ps.createPanel('whatcha', {width:234});
expect(floatPanelSelection().style('width')).toEqual('234px');
expect(p.width()).toEqual(234);
p.width(345);
expect(floatPanelSelection().style('width')).toEqual('345px');
expect(p.width()).toEqual(345);
});
it('should allow programmatic setting of height', function () {
var p = ps.createPanel('ciao', {height:50});
expect(floatPanelSelection().style('height')).toEqual('50px');
expect(p.height()).toEqual(50);
p.height(100);
expect(floatPanelSelection().style('height')).toEqual('100px');
expect(p.height()).toEqual(100);
});
});
......