Simon Hunt

Restructuring GUI code - implementing view life-cycles.

Using sample views for now.
Still WIP.
...@@ -68,7 +68,10 @@ ...@@ -68,7 +68,10 @@
68 68
69 <!-- Initialize the UI...--> 69 <!-- Initialize the UI...-->
70 <script type="text/javascript"> 70 <script type="text/javascript">
71 - var ONOS = $.onos({note: "config, if needed"}); 71 + var ONOS = $.onos({
72 + comment: "configuration options",
73 + trace: false
74 + });
72 </script> 75 </script>
73 76
74 <!-- Framework module files included here --> 77 <!-- Framework module files included here -->
...@@ -76,7 +79,8 @@ ...@@ -76,7 +79,8 @@
76 79
77 <!-- Contributed (application) views injected here --> 80 <!-- Contributed (application) views injected here -->
78 <!-- TODO: replace with template marker and inject refs server-side --> 81 <!-- TODO: replace with template marker and inject refs server-side -->
79 - <script src="temp2.js"></script> 82 + <script src="sample2.js"></script>
83 + <script src="sampleAlt2.js"></script>
80 84
81 <!-- finally, build the UI--> 85 <!-- finally, build the UI-->
82 <script type="text/javascript"> 86 <script type="text/javascript">
......
...@@ -24,6 +24,19 @@ html, body { ...@@ -24,6 +24,19 @@ html, body {
24 height: 100%; 24 height: 100%;
25 } 25 }
26 26
27 +div.onosView {
28 + display: none;
29 +}
30 +
31 +div.onosView.currentView {
32 + display: block;
33 +}
34 +
35 +/*
36 + * ==============================================================
37 + * END OF NEW ONOS.JS file
38 + * ==============================================================
39 + */
27 40
28 /* 41 /*
29 * === DEBUGGING ====== 42 * === DEBUGGING ======
......
...@@ -24,12 +24,22 @@ ...@@ -24,12 +24,22 @@
24 'use strict'; 24 'use strict';
25 var tsI = new Date().getTime(), // initialize time stamp 25 var tsI = new Date().getTime(), // initialize time stamp
26 tsB, // build time stamp 26 tsB, // build time stamp
27 - defaultHash = 'temp1'; 27 + mastHeight = 36, // see mast2.css
28 + defaultHash = 'sample';
28 29
29 30
30 // attach our main function to the jQuery object 31 // attach our main function to the jQuery object
31 $.onos = function (options) { 32 $.onos = function (options) {
32 - var publicApi; // public api 33 + var uiApi,
34 + viewApi,
35 + navApi;
36 +
37 + var defaultOptions = {
38 + trace: false
39 + };
40 +
41 + // compute runtime settings
42 + var settings = $.extend({}, defaultOptions, options);
33 43
34 // internal state 44 // internal state
35 var views = {}, 45 var views = {},
...@@ -55,7 +65,19 @@ ...@@ -55,7 +65,19 @@
55 65
56 function doError(msg) { 66 function doError(msg) {
57 errorCount++; 67 errorCount++;
58 - console.warn(msg); 68 + console.error(msg);
69 + }
70 +
71 + function trace(msg) {
72 + if (settings.trace) {
73 + console.log(msg);
74 + }
75 + }
76 +
77 + function traceFn(fn, params) {
78 + if (settings.trace) {
79 + console.log('*FN* ' + fn + '(...): ' + params);
80 + }
59 } 81 }
60 82
61 // hash navigation 83 // hash navigation
...@@ -65,6 +87,8 @@ ...@@ -65,6 +87,8 @@
65 view, 87 view,
66 t; 88 t;
67 89
90 + traceFn('hash', hash);
91 +
68 if (!hash) { 92 if (!hash) {
69 hash = defaultHash; 93 hash = defaultHash;
70 redo = true; 94 redo = true;
...@@ -88,12 +112,12 @@ ...@@ -88,12 +112,12 @@
88 // hash was not modified... navigate to where we need to be 112 // hash was not modified... navigate to where we need to be
89 navigate(hash, view, t); 113 navigate(hash, view, t);
90 } 114 }
91 -
92 } 115 }
93 116
94 function parseHash(s) { 117 function parseHash(s) {
95 // extract navigation coordinates from the supplied string 118 // extract navigation coordinates from the supplied string
96 // "vid,ctx" --> { vid:vid, ctx:ctx } 119 // "vid,ctx" --> { vid:vid, ctx:ctx }
120 + traceFn('parseHash', s);
97 121
98 var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s); 122 var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
99 if (m) { 123 if (m) {
...@@ -105,6 +129,7 @@ ...@@ -105,6 +129,7 @@
105 } 129 }
106 130
107 function makeHash(t, ctx) { 131 function makeHash(t, ctx) {
132 + traceFn('makeHash');
108 // make a hash string from the given navigation coordinates. 133 // make a hash string from the given navigation coordinates.
109 // if t is not an object, then it is a vid 134 // if t is not an object, then it is a vid
110 var h = t, 135 var h = t,
...@@ -118,43 +143,66 @@ ...@@ -118,43 +143,66 @@
118 if (c) { 143 if (c) {
119 h += ',' + c; 144 h += ',' + c;
120 } 145 }
146 + trace('hash = "' + h + '"');
121 return h; 147 return h;
122 } 148 }
123 149
124 function navigate(hash, view, t) { 150 function navigate(hash, view, t) {
151 + traceFn('navigate', view.vid);
125 // closePanes() // flyouts etc. 152 // closePanes() // flyouts etc.
126 - // updateNav() // accordion / selected nav item 153 + // updateNav() // accordion / selected nav item etc.
127 createView(view); 154 createView(view);
128 setView(view, hash, t); 155 setView(view, hash, t);
129 } 156 }
130 157
131 function reportBuildErrors() { 158 function reportBuildErrors() {
159 + traceFn('reportBuildErrors');
132 // TODO: validate registered views / nav-item linkage etc. 160 // TODO: validate registered views / nav-item linkage etc.
133 console.log('(no build errors)'); 161 console.log('(no build errors)');
134 } 162 }
135 163
164 + // returns the reference if it is a function, null otherwise
165 + function isF(f) {
166 + return $.isFunction(f) ? f : null;
167 + }
168 +
136 // .......................................................... 169 // ..........................................................
137 // View life-cycle functions 170 // View life-cycle functions
138 171
172 + function setViewDimensions(sel) {
173 + var w = window.innerWidth,
174 + h = window.innerHeight - mastHeight;
175 + sel.each(function () {
176 + $(this)
177 + .css('width', w + 'px')
178 + .css('height', h + 'px')
179 + });
180 + }
181 +
139 function createView(view) { 182 function createView(view) {
140 var $d; 183 var $d;
184 +
141 // lazy initialization of the view 185 // lazy initialization of the view
142 if (view && !view.$div) { 186 if (view && !view.$div) {
187 + trace('creating view for ' + view.vid);
143 $d = $view.append('div') 188 $d = $view.append('div')
144 .attr({ 189 .attr({
145 - id: view.vid 190 + id: view.vid,
191 + class: 'onosView'
146 }); 192 });
147 - view.$div = $d; // cache a reference to the selected div 193 + setViewDimensions($d);
194 + view.$div = $d; // cache a reference to the D3 selection
148 } 195 }
149 } 196 }
150 197
151 function setView(view, hash, t) { 198 function setView(view, hash, t) {
199 + traceFn('setView', view.vid);
152 // set the specified view as current, while invoking the 200 // set the specified view as current, while invoking the
153 // appropriate life-cycle callbacks 201 // appropriate life-cycle callbacks
154 202
155 // if there is a current view, and it is not the same as 203 // if there is a current view, and it is not the same as
156 // the incoming view, then unload it... 204 // the incoming view, then unload it...
157 - if (current.view && !(current.view.vid !== view.vid)) { 205 + if (current.view && (current.view.vid !== view.vid)) {
158 current.view.unload(); 206 current.view.unload();
159 } 207 }
160 208
...@@ -162,23 +210,24 @@ ...@@ -162,23 +210,24 @@
162 current.view = view; 210 current.view = view;
163 current.ctx = t.ctx || ''; 211 current.ctx = t.ctx || '';
164 212
165 - // TODO: clear radio button set (store on view?)
166 -
167 // preload is called only once, after the view is in the DOM 213 // preload is called only once, after the view is in the DOM
168 if (!view.preloaded) { 214 if (!view.preloaded) {
169 - view.preload(t.ctx); 215 + view.preload(current.ctx);
216 + view.preloaded = true;
170 } 217 }
171 218
172 // clear the view of stale data 219 // clear the view of stale data
173 view.reset(); 220 view.reset();
174 221
175 // load the view 222 // load the view
176 - view.load(t.ctx); 223 + view.load(current.ctx);
177 } 224 }
178 225
179 - function resizeView() { 226 + function resize(e) {
227 + d3.selectAll('.onosView').call(setViewDimensions);
228 + // allow current view to react to resize event...
180 if (current.view) { 229 if (current.view) {
181 - current.view.resize(); 230 + current.view.resize(current.ctx);
182 } 231 }
183 } 232 }
184 233
...@@ -189,28 +238,24 @@ ...@@ -189,28 +238,24 @@
189 // Constructor 238 // Constructor
190 // vid : view id 239 // vid : view id
191 // nid : id of associated nav-item (optional) 240 // nid : id of associated nav-item (optional)
192 - // cb : callbacks (preload, reset, load, resize, unload, error) 241 + // cb : callbacks (preload, reset, load, unload, resize, error)
193 - // data: custom data object (optional)
194 function View(vid) { 242 function View(vid) {
195 var av = 'addView(): ', 243 var av = 'addView(): ',
196 args = Array.prototype.slice.call(arguments), 244 args = Array.prototype.slice.call(arguments),
197 nid, 245 nid,
198 - cb, 246 + cb;
199 - data;
200 247
201 args.shift(); // first arg is always vid 248 args.shift(); // first arg is always vid
202 if (typeof args[0] === 'string') { // nid specified 249 if (typeof args[0] === 'string') { // nid specified
203 nid = args.shift(); 250 nid = args.shift();
204 } 251 }
205 cb = args.shift(); 252 cb = args.shift();
206 - data = args.shift();
207 253
208 this.vid = vid; 254 this.vid = vid;
209 255
210 if (validateViewArgs(vid)) { 256 if (validateViewArgs(vid)) {
211 this.nid = nid; // explicit navitem id (can be null) 257 this.nid = nid; // explicit navitem id (can be null)
212 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks 258 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
213 - this.data = data; // custom data (can be null)
214 this.$div = null; // view not yet added to DOM 259 this.$div = null; // view not yet added to DOM
215 this.ok = true; // valid view 260 this.ok = true; // valid view
216 } 261 }
...@@ -218,7 +263,8 @@ ...@@ -218,7 +263,8 @@
218 } 263 }
219 264
220 function validateViewArgs(vid) { 265 function validateViewArgs(vid) {
221 - var ok = false; 266 + var av = "ui.addView(...): ",
267 + ok = false;
222 if (typeof vid !== 'string' || !vid) { 268 if (typeof vid !== 'string' || !vid) {
223 doError(av + 'vid required'); 269 doError(av + 'vid required');
224 } else if (views[vid]) { 270 } else if (views[vid]) {
...@@ -234,29 +280,140 @@ ...@@ -234,29 +280,140 @@
234 return '[View: id="' + this.vid + '"]'; 280 return '[View: id="' + this.vid + '"]';
235 }, 281 },
236 282
237 - token: function() { 283 + token: function () {
238 return { 284 return {
285 + // attributes
239 vid: this.vid, 286 vid: this.vid,
240 nid: this.nid, 287 nid: this.nid,
241 - data: this.data 288 + $div: this.$div,
289 +
290 + // functions
291 + width: this.width,
292 + height: this.height
293 + }
294 + },
295 +
296 + preload: function (ctx) {
297 + var c = ctx || '',
298 + fn = isF(this.cb.preload);
299 + traceFn('View.preload', this.vid + ', ' + c);
300 + if (fn) {
301 + trace('PRELOAD cb for ' + this.vid);
302 + fn(this.token(), c);
303 + }
304 + },
305 +
306 + reset: function () {
307 + var fn = isF(this.cb.reset);
308 + traceFn('View.reset', this.vid);
309 + if (fn) {
310 + trace('RESET cb for ' + this.vid);
311 + fn(this.token());
312 + } else if (this.cb.reset === true) {
313 + // boolean true signifies "clear view"
314 + trace(' [true] cleaing view...');
315 + viewApi.empty();
316 + }
317 + },
318 +
319 + load: function (ctx) {
320 + var c = ctx || '',
321 + fn = isF(this.cb.load);
322 + traceFn('View.load', this.vid + ', ' + c);
323 + this.$div.classed('currentView', true);
324 + // TODO: add radio button set, if needed
325 + if (fn) {
326 + trace('LOAD cb for ' + this.vid);
327 + fn(this.token(), c);
328 + }
329 + },
330 +
331 + unload: function () {
332 + var fn = isF(this.cb.unload);
333 + traceFn('View.unload', this.vid);
334 + this.$div.classed('currentView', false);
335 + // TODO: remove radio button set, if needed
336 + if (fn) {
337 + trace('UNLOAD cb for ' + this.vid);
338 + fn(this.token());
242 } 339 }
340 + },
341 +
342 + resize: function (ctx) {
343 + var c = ctx || '',
344 + fn = isF(this.cb.resize),
345 + w = this.width(),
346 + h = this.height();
347 + traceFn('View.resize', this.vid + '/' + c +
348 + ' [' + w + 'x' + h + ']');
349 + if (fn) {
350 + trace('RESIZE cb for ' + this.vid);
351 + fn(this.token(), c);
352 + }
353 + },
354 +
355 + error: function (ctx) {
356 + var c = ctx || '',
357 + fn = isF(this.cb.error);
358 + traceFn('View.error', this.vid + ', ' + c);
359 + if (fn) {
360 + trace('ERROR cb for ' + this.vid);
361 + fn(this.token(), c);
362 + }
363 + },
364 +
365 + width: function () {
366 + return $(this.$div.node()).width();
367 + },
368 +
369 + height: function () {
370 + return $(this.$div.node()).height();
243 } 371 }
244 - // TODO: create, preload, reset, load, error, resize, unload 372 +
373 +
374 + // TODO: consider schedule, clearTimer, etc.
245 }; 375 };
246 376
247 // attach instance methods to the view prototype 377 // attach instance methods to the view prototype
248 $.extend(View.prototype, viewInstanceMethods); 378 $.extend(View.prototype, viewInstanceMethods);
249 379
250 // .......................................................... 380 // ..........................................................
251 - // Exported API 381 + // UI API
252 - 382 +
253 - publicApi = { 383 + uiApi = {
254 - printTime: function () { 384 + /** @api ui addView( vid, nid, cb )
255 - console.log("the time is " + new Date()); 385 + * Adds a view to the UI.
256 - }, 386 + * <p>
257 - 387 + * Views are loaded/unloaded into the view content pane at
258 - addView: function (vid, nid, cb, data) { 388 + * appropriate times, by the navigation framework. This method
259 - var view = new View(vid, nid, cb, data), 389 + * adds a view to the UI and returns a token object representing
390 + * the view. A view's token is always passed as the first
391 + * argument to each of the view's life-cycle callback functions.
392 + * <p>
393 + * Note that if the view is directly referenced by a nav-item,
394 + * or in a group of views with one of those views referenced by
395 + * a nav-item, then the <i>nid</i> argument can be omitted as
396 + * the framework can infer it.
397 + * <p>
398 + * <i>cb</i> is a plain object containing callback functions:
399 + * "preload", "reset", "load", "unload", "resize", "error".
400 + * <pre>
401 + * function myLoad(view, ctx) { ... }
402 + * ...
403 + * // short form...
404 + * onos.ui.addView('viewId', {
405 + * load: myLoad
406 + * });
407 + * </pre>
408 + *
409 + * @param vid (string) [*] view ID (a unique DOM element id)
410 + * @param nid (string) nav-item ID (a unique DOM element id)
411 + * @param cb (object) [*] callbacks object
412 + * @return the view token
413 + */
414 + addView: function (vid, nid, cb) {
415 + traceFn('addView', vid);
416 + var view = new View(vid, nid, cb),
260 token; 417 token;
261 if (view.ok) { 418 if (view.ok) {
262 views[vid] = view; 419 views[vid] = view;
...@@ -268,6 +425,33 @@ ...@@ -268,6 +425,33 @@
268 } 425 }
269 }; 426 };
270 427
428 + // ..........................................................
429 + // View API
430 +
431 + viewApi = {
432 + /** @api view empty( )
433 + * Empties the current view.
434 + * <p>
435 + * More specifically, removes all DOM elements from the
436 + * current view's display div.
437 + */
438 + empty: function () {
439 + if (!current.view) {
440 + return;
441 + }
442 + current.view.$div.html('');
443 + }
444 + };
445 +
446 + // ..........................................................
447 + // Nav API
448 + navApi = {
449 +
450 + };
451 +
452 + // ..........................................................
453 + // Exported API
454 +
271 // function to be called from index.html to build the ONOS UI 455 // function to be called from index.html to build the ONOS UI
272 function buildOnosUi() { 456 function buildOnosUi() {
273 tsB = new Date().getTime(); 457 tsB = new Date().getTime();
...@@ -283,6 +467,7 @@ ...@@ -283,6 +467,7 @@
283 $view = d3.select('#view'); 467 $view = d3.select('#view');
284 468
285 $(window).on('hashchange', hash); 469 $(window).on('hashchange', hash);
470 + $(window).on('resize', resize);
286 471
287 // Invoke hashchange callback to navigate to content 472 // Invoke hashchange callback to navigate to content
288 // indicated by the window location hash. 473 // indicated by the window location hash.
...@@ -295,7 +480,9 @@ ...@@ -295,7 +480,9 @@
295 480
296 // export the api and build-UI function 481 // export the api and build-UI function
297 return { 482 return {
298 - api: publicApi, 483 + ui: uiApi,
484 + view: viewApi,
485 + nav: navApi,
299 buildUi: buildOnosUi 486 buildUi: buildOnosUi
300 }; 487 };
301 }; 488 };
......
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + Alternate Sample module file to illustrate framework integration.
19 +
20 + @author Simon Hunt
21 + */
22 +
23 +(function (onos) {
24 + 'use strict';
25 +
26 + var svg;
27 +
28 +
29 + function sizeSvg(view) {
30 + svg.attr({
31 + width: view.width(),
32 + height: view.height()
33 + });
34 + }
35 +
36 + // NOTE: view is a view-token data structure:
37 + // {
38 + // vid: 'view-id',
39 + // nid: 'nav-id',
40 + // $div: ... // d3 selection of dom view div.
41 + // }
42 +
43 + // gets invoked only the first time the view is loaded
44 + function preload(view, ctx) {
45 + svg = view.$div.append('svg');
46 + sizeSvg(view);
47 + }
48 +
49 + function reset(view) {
50 + // clear our svg of all objects
51 + svg.html('');
52 + }
53 +
54 + function load(view, ctx) {
55 + var fill = 'red',
56 + stroke = 'black',
57 + ctxText = ctx ? 'Context is "' + ctx + '"' : 'No Context';
58 +
59 + svg.append('circle')
60 + .attr({
61 + cx: view.width() / 2,
62 + cy: view.height() / 2,
63 + r: 30
64 + })
65 + .style({
66 + fill: fill,
67 + stroke: stroke,
68 + 'stroke-width': 3.5
69 + });
70 +
71 + svg.append('text')
72 + .text(ctxText)
73 + .attr({
74 + x: 20,
75 + y: '1.5em'
76 + })
77 + .style({
78 + fill: 'darkgreen',
79 + 'font-size': '20pt'
80 + });
81 + }
82 +
83 + function resize(view, ctx) {
84 + sizeSvg(view);
85 + svg.selectAll('circle')
86 + .attr({
87 + cx: view.width() / 2,
88 + cy: view.height() / 2
89 + });
90 + }
91 +
92 + // == register views here, with links to lifecycle callbacks
93 +
94 + onos.ui.addView('sample', {
95 + preload: preload,
96 + reset: reset,
97 + load: load,
98 + resize: resize
99 + });
100 +
101 +
102 +}(ONOS));
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
15 */ 15 */
16 16
17 /* 17 /*
18 - Temporary module file to test the framework integration. 18 + Sample module file to illustrate framework integration.
19 19
20 @author Simon Hunt 20 @author Simon Hunt
21 */ 21 */
...@@ -23,35 +23,42 @@ ...@@ -23,35 +23,42 @@
23 (function (onos) { 23 (function (onos) {
24 'use strict'; 24 'use strict';
25 25
26 - var api = onos.api; 26 + var svg;
27 27
28 - var vid,
29 - svg;
30 -
31 - // == define your functions here.....
32 28
29 + function sizeSvg(view) {
30 + svg.attr({
31 + width: view.width(),
32 + height: view.height()
33 + });
34 + }
33 35
34 - // NOTE: view is a data structure: 36 + // NOTE: view is a view-token data structure:
35 // { 37 // {
36 - // id: 'view-id', 38 + // vid: 'view-id',
37 - // el: ... // d3 selection of dom view div. 39 + // nid: 'nav-id',
40 + // $div: ... // d3 selection of dom view div.
38 // } 41 // }
39 42
40 - function load(view) { 43 + // gets invoked only the first time the view is loaded
41 - vid = view.id; 44 + function preload(view, ctx) {
42 - svg = view.el.append('svg') 45 + svg = view.$div.append('svg');
43 - .attr({ 46 + sizeSvg(view);
44 - width: 400, 47 + }
45 - height: 300 48 +
46 - }); 49 + function reset(view) {
50 + // clear our svg of all objects
51 + svg.html('');
52 + }
47 53
48 - var fill = (vid === 'temp1') ? 'red' : 'blue', 54 + function load(view, ctx) {
49 - stroke = (vid === 'temp2') ? 'yellow' : 'black'; 55 + var fill = 'blue',
56 + stroke = 'grey';
50 57
51 svg.append('circle') 58 svg.append('circle')
52 .attr({ 59 .attr({
53 - cx: 200, 60 + cx: view.width() / 2,
54 - cy: 150, 61 + cy: view.height() / 2,
55 r: 30 62 r: 30
56 }) 63 })
57 .style({ 64 .style({
...@@ -61,14 +68,22 @@ ...@@ -61,14 +68,22 @@
61 }); 68 });
62 } 69 }
63 70
64 - // == register views here, with links to lifecycle callbacks 71 + function resize(view, ctx) {
65 - 72 + sizeSvg(view);
66 - api.addView('temp1', { 73 + svg.selectAll('circle')
67 - load: load 74 + .attr({
75 + cx: view.width() / 2,
76 + cy: view.height() / 2
68 }); 77 });
78 + }
79 +
80 + // == register views here, with links to lifecycle callbacks
69 81
70 - api.addView('temp2', { 82 + onos.ui.addView('sampleAlt', {
71 - load: load 83 + preload: preload,
84 + reset: reset,
85 + load: load,
86 + resize: resize
72 }); 87 });
73 88
74 89
......
...@@ -23,9 +23,6 @@ ...@@ -23,9 +23,6 @@
23 (function (onos) { 23 (function (onos) {
24 'use strict'; 24 'use strict';
25 25
26 - // reference to the framework api
27 - var api = onos.api;
28 -
29 // configuration data 26 // configuration data
30 var config = { 27 var config = {
31 useLiveData: true, 28 useLiveData: true,
...@@ -1213,7 +1210,7 @@ ...@@ -1213,7 +1210,7 @@
1213 // ====================================================================== 1210 // ======================================================================
1214 // register with the UI framework 1211 // register with the UI framework
1215 1212
1216 - api.addView('network', { 1213 + onos.ui.addView('topo', {
1217 load: loadNetworkView 1214 load: loadNetworkView
1218 }); 1215 });
1219 1216
......