Restructuring GUI code - implementing view life-cycles.
Using sample views for now.
Still WIP.
diff --git a/web/gui/src/main/webapp/index2.html b/web/gui/src/main/webapp/index2.html
index 4c4a9c8..8c7cec5 100644
--- a/web/gui/src/main/webapp/index2.html
+++ b/web/gui/src/main/webapp/index2.html
@@ -68,7 +68,10 @@
<!-- Initialize the UI...-->
<script type="text/javascript">
- var ONOS = $.onos({note: "config, if needed"});
+ var ONOS = $.onos({
+ comment: "configuration options",
+ trace: false
+ });
</script>
<!-- Framework module files included here -->
@@ -76,7 +79,8 @@
<!-- Contributed (application) views injected here -->
<!-- TODO: replace with template marker and inject refs server-side -->
- <script src="temp2.js"></script>
+ <script src="sample2.js"></script>
+ <script src="sampleAlt2.js"></script>
<!-- finally, build the UI-->
<script type="text/javascript">
diff --git a/web/gui/src/main/webapp/onos2.css b/web/gui/src/main/webapp/onos2.css
index 63acd00..f693ecc 100644
--- a/web/gui/src/main/webapp/onos2.css
+++ b/web/gui/src/main/webapp/onos2.css
@@ -24,6 +24,19 @@
height: 100%;
}
+div.onosView {
+ display: none;
+}
+
+div.onosView.currentView {
+ display: block;
+}
+
+/*
+ * ==============================================================
+ * END OF NEW ONOS.JS file
+ * ==============================================================
+ */
/*
* === DEBUGGING ======
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index bd001a5..c35b56d 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -24,12 +24,22 @@
'use strict';
var tsI = new Date().getTime(), // initialize time stamp
tsB, // build time stamp
- defaultHash = 'temp1';
+ mastHeight = 36, // see mast2.css
+ defaultHash = 'sample';
// attach our main function to the jQuery object
$.onos = function (options) {
- var publicApi; // public api
+ var uiApi,
+ viewApi,
+ navApi;
+
+ var defaultOptions = {
+ trace: false
+ };
+
+ // compute runtime settings
+ var settings = $.extend({}, defaultOptions, options);
// internal state
var views = {},
@@ -55,7 +65,19 @@
function doError(msg) {
errorCount++;
- console.warn(msg);
+ console.error(msg);
+ }
+
+ function trace(msg) {
+ if (settings.trace) {
+ console.log(msg);
+ }
+ }
+
+ function traceFn(fn, params) {
+ if (settings.trace) {
+ console.log('*FN* ' + fn + '(...): ' + params);
+ }
}
// hash navigation
@@ -65,6 +87,8 @@
view,
t;
+ traceFn('hash', hash);
+
if (!hash) {
hash = defaultHash;
redo = true;
@@ -88,12 +112,12 @@
// hash was not modified... navigate to where we need to be
navigate(hash, view, t);
}
-
}
function parseHash(s) {
// extract navigation coordinates from the supplied string
// "vid,ctx" --> { vid:vid, ctx:ctx }
+ traceFn('parseHash', s);
var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
if (m) {
@@ -105,6 +129,7 @@
}
function makeHash(t, ctx) {
+ traceFn('makeHash');
// make a hash string from the given navigation coordinates.
// if t is not an object, then it is a vid
var h = t,
@@ -118,43 +143,66 @@
if (c) {
h += ',' + c;
}
+ trace('hash = "' + h + '"');
return h;
}
function navigate(hash, view, t) {
+ traceFn('navigate', view.vid);
// closePanes() // flyouts etc.
- // updateNav() // accordion / selected nav item
+ // updateNav() // accordion / selected nav item etc.
createView(view);
setView(view, hash, t);
}
function reportBuildErrors() {
+ traceFn('reportBuildErrors');
// TODO: validate registered views / nav-item linkage etc.
console.log('(no build errors)');
}
+ // returns the reference if it is a function, null otherwise
+ function isF(f) {
+ return $.isFunction(f) ? f : null;
+ }
+
// ..........................................................
// View life-cycle functions
+ function setViewDimensions(sel) {
+ var w = window.innerWidth,
+ h = window.innerHeight - mastHeight;
+ sel.each(function () {
+ $(this)
+ .css('width', w + 'px')
+ .css('height', h + 'px')
+ });
+ }
+
function createView(view) {
var $d;
+
// lazy initialization of the view
if (view && !view.$div) {
+ trace('creating view for ' + view.vid);
$d = $view.append('div')
.attr({
- id: view.vid
+ id: view.vid,
+ class: 'onosView'
});
- view.$div = $d; // cache a reference to the selected div
+ setViewDimensions($d);
+ view.$div = $d; // cache a reference to the D3 selection
}
}
function setView(view, hash, t) {
+ traceFn('setView', view.vid);
// set the specified view as current, while invoking the
// appropriate life-cycle callbacks
// if there is a current view, and it is not the same as
// the incoming view, then unload it...
- if (current.view && !(current.view.vid !== view.vid)) {
+ if (current.view && (current.view.vid !== view.vid)) {
current.view.unload();
}
@@ -162,23 +210,24 @@
current.view = view;
current.ctx = t.ctx || '';
- // TODO: clear radio button set (store on view?)
-
// preload is called only once, after the view is in the DOM
if (!view.preloaded) {
- view.preload(t.ctx);
+ view.preload(current.ctx);
+ view.preloaded = true;
}
// clear the view of stale data
view.reset();
// load the view
- view.load(t.ctx);
+ view.load(current.ctx);
}
- function resizeView() {
+ function resize(e) {
+ d3.selectAll('.onosView').call(setViewDimensions);
+ // allow current view to react to resize event...
if (current.view) {
- current.view.resize();
+ current.view.resize(current.ctx);
}
}
@@ -189,28 +238,24 @@
// Constructor
// vid : view id
// nid : id of associated nav-item (optional)
- // cb : callbacks (preload, reset, load, resize, unload, error)
- // data: custom data object (optional)
+ // cb : callbacks (preload, reset, load, unload, resize, error)
function View(vid) {
var av = 'addView(): ',
args = Array.prototype.slice.call(arguments),
nid,
- cb,
- data;
+ cb;
args.shift(); // first arg is always vid
if (typeof args[0] === 'string') { // nid specified
nid = args.shift();
}
cb = args.shift();
- data = args.shift();
this.vid = vid;
if (validateViewArgs(vid)) {
this.nid = nid; // explicit navitem id (can be null)
this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
- this.data = data; // custom data (can be null)
this.$div = null; // view not yet added to DOM
this.ok = true; // valid view
}
@@ -218,7 +263,8 @@
}
function validateViewArgs(vid) {
- var ok = false;
+ var av = "ui.addView(...): ",
+ ok = false;
if (typeof vid !== 'string' || !vid) {
doError(av + 'vid required');
} else if (views[vid]) {
@@ -234,29 +280,140 @@
return '[View: id="' + this.vid + '"]';
},
- token: function() {
+ token: function () {
return {
+ // attributes
vid: this.vid,
nid: this.nid,
- data: this.data
+ $div: this.$div,
+
+ // functions
+ width: this.width,
+ height: this.height
}
+ },
+
+ preload: function (ctx) {
+ var c = ctx || '',
+ fn = isF(this.cb.preload);
+ traceFn('View.preload', this.vid + ', ' + c);
+ if (fn) {
+ trace('PRELOAD cb for ' + this.vid);
+ fn(this.token(), c);
+ }
+ },
+
+ reset: function () {
+ var fn = isF(this.cb.reset);
+ traceFn('View.reset', this.vid);
+ if (fn) {
+ trace('RESET cb for ' + this.vid);
+ fn(this.token());
+ } else if (this.cb.reset === true) {
+ // boolean true signifies "clear view"
+ trace(' [true] cleaing view...');
+ viewApi.empty();
+ }
+ },
+
+ load: function (ctx) {
+ var c = ctx || '',
+ fn = isF(this.cb.load);
+ traceFn('View.load', this.vid + ', ' + c);
+ this.$div.classed('currentView', true);
+ // TODO: add radio button set, if needed
+ if (fn) {
+ trace('LOAD cb for ' + this.vid);
+ fn(this.token(), c);
+ }
+ },
+
+ unload: function () {
+ var fn = isF(this.cb.unload);
+ traceFn('View.unload', this.vid);
+ this.$div.classed('currentView', false);
+ // TODO: remove radio button set, if needed
+ if (fn) {
+ trace('UNLOAD cb for ' + this.vid);
+ fn(this.token());
+ }
+ },
+
+ resize: function (ctx) {
+ var c = ctx || '',
+ fn = isF(this.cb.resize),
+ w = this.width(),
+ h = this.height();
+ traceFn('View.resize', this.vid + '/' + c +
+ ' [' + w + 'x' + h + ']');
+ if (fn) {
+ trace('RESIZE cb for ' + this.vid);
+ fn(this.token(), c);
+ }
+ },
+
+ error: function (ctx) {
+ var c = ctx || '',
+ fn = isF(this.cb.error);
+ traceFn('View.error', this.vid + ', ' + c);
+ if (fn) {
+ trace('ERROR cb for ' + this.vid);
+ fn(this.token(), c);
+ }
+ },
+
+ width: function () {
+ return $(this.$div.node()).width();
+ },
+
+ height: function () {
+ return $(this.$div.node()).height();
}
- // TODO: create, preload, reset, load, error, resize, unload
+
+
+ // TODO: consider schedule, clearTimer, etc.
};
// attach instance methods to the view prototype
$.extend(View.prototype, viewInstanceMethods);
// ..........................................................
- // Exported API
+ // UI API
- publicApi = {
- printTime: function () {
- console.log("the time is " + new Date());
- },
-
- addView: function (vid, nid, cb, data) {
- var view = new View(vid, nid, cb, data),
+ uiApi = {
+ /** @api ui addView( vid, nid, cb )
+ * Adds a view to the UI.
+ * <p>
+ * Views are loaded/unloaded into the view content pane at
+ * appropriate times, by the navigation framework. This method
+ * adds a view to the UI and returns a token object representing
+ * the view. A view's token is always passed as the first
+ * argument to each of the view's life-cycle callback functions.
+ * <p>
+ * Note that if the view is directly referenced by a nav-item,
+ * or in a group of views with one of those views referenced by
+ * a nav-item, then the <i>nid</i> argument can be omitted as
+ * the framework can infer it.
+ * <p>
+ * <i>cb</i> is a plain object containing callback functions:
+ * "preload", "reset", "load", "unload", "resize", "error".
+ * <pre>
+ * function myLoad(view, ctx) { ... }
+ * ...
+ * // short form...
+ * onos.ui.addView('viewId', {
+ * load: myLoad
+ * });
+ * </pre>
+ *
+ * @param vid (string) [*] view ID (a unique DOM element id)
+ * @param nid (string) nav-item ID (a unique DOM element id)
+ * @param cb (object) [*] callbacks object
+ * @return the view token
+ */
+ addView: function (vid, nid, cb) {
+ traceFn('addView', vid);
+ var view = new View(vid, nid, cb),
token;
if (view.ok) {
views[vid] = view;
@@ -268,6 +425,33 @@
}
};
+ // ..........................................................
+ // View API
+
+ viewApi = {
+ /** @api view empty( )
+ * Empties the current view.
+ * <p>
+ * More specifically, removes all DOM elements from the
+ * current view's display div.
+ */
+ empty: function () {
+ if (!current.view) {
+ return;
+ }
+ current.view.$div.html('');
+ }
+ };
+
+ // ..........................................................
+ // Nav API
+ navApi = {
+
+ };
+
+ // ..........................................................
+ // Exported API
+
// function to be called from index.html to build the ONOS UI
function buildOnosUi() {
tsB = new Date().getTime();
@@ -283,6 +467,7 @@
$view = d3.select('#view');
$(window).on('hashchange', hash);
+ $(window).on('resize', resize);
// Invoke hashchange callback to navigate to content
// indicated by the window location hash.
@@ -295,7 +480,9 @@
// export the api and build-UI function
return {
- api: publicApi,
+ ui: uiApi,
+ view: viewApi,
+ nav: navApi,
buildUi: buildOnosUi
};
};
diff --git a/web/gui/src/main/webapp/sample2.js b/web/gui/src/main/webapp/sample2.js
new file mode 100644
index 0000000..7222452
--- /dev/null
+++ b/web/gui/src/main/webapp/sample2.js
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/*
+ Alternate Sample module file to illustrate framework integration.
+
+ @author Simon Hunt
+ */
+
+(function (onos) {
+ 'use strict';
+
+ var svg;
+
+
+ function sizeSvg(view) {
+ svg.attr({
+ width: view.width(),
+ height: view.height()
+ });
+ }
+
+ // NOTE: view is a view-token data structure:
+ // {
+ // vid: 'view-id',
+ // nid: 'nav-id',
+ // $div: ... // d3 selection of dom view div.
+ // }
+
+ // gets invoked only the first time the view is loaded
+ function preload(view, ctx) {
+ svg = view.$div.append('svg');
+ sizeSvg(view);
+ }
+
+ function reset(view) {
+ // clear our svg of all objects
+ svg.html('');
+ }
+
+ function load(view, ctx) {
+ var fill = 'red',
+ stroke = 'black',
+ ctxText = ctx ? 'Context is "' + ctx + '"' : 'No Context';
+
+ svg.append('circle')
+ .attr({
+ cx: view.width() / 2,
+ cy: view.height() / 2,
+ r: 30
+ })
+ .style({
+ fill: fill,
+ stroke: stroke,
+ 'stroke-width': 3.5
+ });
+
+ svg.append('text')
+ .text(ctxText)
+ .attr({
+ x: 20,
+ y: '1.5em'
+ })
+ .style({
+ fill: 'darkgreen',
+ 'font-size': '20pt'
+ });
+ }
+
+ function resize(view, ctx) {
+ sizeSvg(view);
+ svg.selectAll('circle')
+ .attr({
+ cx: view.width() / 2,
+ cy: view.height() / 2
+ });
+ }
+
+ // == register views here, with links to lifecycle callbacks
+
+ onos.ui.addView('sample', {
+ preload: preload,
+ reset: reset,
+ load: load,
+ resize: resize
+ });
+
+
+}(ONOS));
diff --git a/web/gui/src/main/webapp/sampleAlt2.js b/web/gui/src/main/webapp/sampleAlt2.js
new file mode 100644
index 0000000..f0e662e
--- /dev/null
+++ b/web/gui/src/main/webapp/sampleAlt2.js
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/*
+ Sample module file to illustrate framework integration.
+
+ @author Simon Hunt
+ */
+
+(function (onos) {
+ 'use strict';
+
+ var svg;
+
+
+ function sizeSvg(view) {
+ svg.attr({
+ width: view.width(),
+ height: view.height()
+ });
+ }
+
+ // NOTE: view is a view-token data structure:
+ // {
+ // vid: 'view-id',
+ // nid: 'nav-id',
+ // $div: ... // d3 selection of dom view div.
+ // }
+
+ // gets invoked only the first time the view is loaded
+ function preload(view, ctx) {
+ svg = view.$div.append('svg');
+ sizeSvg(view);
+ }
+
+ function reset(view) {
+ // clear our svg of all objects
+ svg.html('');
+ }
+
+ function load(view, ctx) {
+ var fill = 'blue',
+ stroke = 'grey';
+
+ svg.append('circle')
+ .attr({
+ cx: view.width() / 2,
+ cy: view.height() / 2,
+ r: 30
+ })
+ .style({
+ fill: fill,
+ stroke: stroke,
+ 'stroke-width': 3.5
+ });
+ }
+
+ function resize(view, ctx) {
+ sizeSvg(view);
+ svg.selectAll('circle')
+ .attr({
+ cx: view.width() / 2,
+ cy: view.height() / 2
+ });
+ }
+
+ // == register views here, with links to lifecycle callbacks
+
+ onos.ui.addView('sampleAlt', {
+ preload: preload,
+ reset: reset,
+ load: load,
+ resize: resize
+ });
+
+
+}(ONOS));
diff --git a/web/gui/src/main/webapp/temp2.js b/web/gui/src/main/webapp/temp2.js
deleted file mode 100644
index cc174c0..0000000
--- a/web/gui/src/main/webapp/temp2.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-/*
- Temporary module file to test the framework integration.
-
- @author Simon Hunt
- */
-
-(function (onos) {
- 'use strict';
-
- var api = onos.api;
-
- var vid,
- svg;
-
- // == define your functions here.....
-
-
- // NOTE: view is a data structure:
- // {
- // id: 'view-id',
- // el: ... // d3 selection of dom view div.
- // }
-
- function load(view) {
- vid = view.id;
- svg = view.el.append('svg')
- .attr({
- width: 400,
- height: 300
- });
-
- var fill = (vid === 'temp1') ? 'red' : 'blue',
- stroke = (vid === 'temp2') ? 'yellow' : 'black';
-
- svg.append('circle')
- .attr({
- cx: 200,
- cy: 150,
- r: 30
- })
- .style({
- fill: fill,
- stroke: stroke,
- 'stroke-width': 3.5
- });
- }
-
- // == register views here, with links to lifecycle callbacks
-
- api.addView('temp1', {
- load: load
- });
-
- api.addView('temp2', {
- load: load
- });
-
-
-}(ONOS));
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index b249c09..8d1f9c1 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -23,9 +23,6 @@
(function (onos) {
'use strict';
- // reference to the framework api
- var api = onos.api;
-
// configuration data
var config = {
useLiveData: true,
@@ -1213,7 +1210,7 @@
// ======================================================================
// register with the UI framework
- api.addView('network', {
+ onos.ui.addView('topo', {
load: loadNetworkView
});