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
     });