blob: f38b35fb4dbc0db5e5d43e2c08ccb034814cea20 [file] [log] [blame]
Simon Hunt195cb382014-11-03 17:50:51 -08001/*
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 ONOS GUI -- Base Framework
19
20 @author Simon Hunt
21 */
22
23(function ($) {
24 'use strict';
25 var tsI = new Date().getTime(), // initialize time stamp
26 tsB, // build time stamp
Simon Hunt25248912014-11-04 11:25:48 -080027 mastHeight = 36, // see mast2.css
Simon Hunt142d0032014-11-04 20:13:09 -080028 defaultVid = 'sample';
Simon Hunt195cb382014-11-03 17:50:51 -080029
30
31 // attach our main function to the jQuery object
32 $.onos = function (options) {
Simon Hunt25248912014-11-04 11:25:48 -080033 var uiApi,
34 viewApi,
Simon Hunt1a9eff92014-11-07 11:06:34 -080035 navApi,
Simon Huntbb282f52014-11-10 11:08:19 -080036 libApi,
37 exported = {};
Simon Hunt25248912014-11-04 11:25:48 -080038
39 var defaultOptions = {
Simon Hunt142d0032014-11-04 20:13:09 -080040 trace: false,
Thomas Vachuska65368e32014-11-08 16:10:20 -080041 theme: 'light',
Simon Hunt142d0032014-11-04 20:13:09 -080042 startVid: defaultVid
Simon Hunt25248912014-11-04 11:25:48 -080043 };
44
45 // compute runtime settings
46 var settings = $.extend({}, defaultOptions, options);
Simon Hunt195cb382014-11-03 17:50:51 -080047
Thomas Vachuska65368e32014-11-08 16:10:20 -080048 // set the selected theme
49 d3.select('body').classed(settings.theme, true);
50
Simon Hunt195cb382014-11-03 17:50:51 -080051 // internal state
52 var views = {},
53 current = {
54 view: null,
Thomas Vachuska65368e32014-11-08 16:10:20 -080055 ctx: '',
Simon Hunt56d51852014-11-09 13:03:35 -080056 flags: {},
Thomas Vachuska65368e32014-11-08 16:10:20 -080057 theme: settings.theme
Simon Hunt195cb382014-11-03 17:50:51 -080058 },
59 built = false,
Simon Hunt0df1b1d2014-11-04 22:58:29 -080060 errorCount = 0,
Simon Hunt934c3ce2014-11-05 11:45:07 -080061 keyHandler = {
Thomas Vachuska65368e32014-11-08 16:10:20 -080062 globalKeys: {},
63 maskedKeys: {},
64 viewKeys: {},
65 viewFn: null
66 },
67 alerts = {
68 open: false,
69 count: 0
Simon Hunt934c3ce2014-11-05 11:45:07 -080070 };
Simon Hunt195cb382014-11-03 17:50:51 -080071
72 // DOM elements etc.
Simon Huntdb9eb072014-11-04 19:12:46 -080073 var $view,
74 $mastRadio;
Simon Hunt195cb382014-11-03 17:50:51 -080075
76
Simon Hunt0df1b1d2014-11-04 22:58:29 -080077 function whatKey(code) {
78 switch (code) {
79 case 13: return 'enter';
80 case 16: return 'shift';
81 case 17: return 'ctrl';
82 case 18: return 'alt';
83 case 27: return 'esc';
84 case 32: return 'space';
85 case 37: return 'leftArrow';
86 case 38: return 'upArrow';
87 case 39: return 'rightArrow';
88 case 40: return 'downArrow';
89 case 91: return 'cmdLeft';
90 case 93: return 'cmdRight';
91 default:
92 if ((code >= 48 && code <= 57) ||
93 (code >= 65 && code <= 90)) {
94 return String.fromCharCode(code);
95 } else if (code >= 112 && code <= 123) {
96 return 'F' + (code - 111);
97 }
98 return '.';
99 }
100 }
101
102
Simon Hunt195cb382014-11-03 17:50:51 -0800103 // ..........................................................
104 // Internal functions
105
106 // throw an error
107 function throwError(msg) {
108 // separate function, as we might add tracing here too, later
109 throw new Error(msg);
110 }
111
112 function doError(msg) {
113 errorCount++;
Simon Hunt25248912014-11-04 11:25:48 -0800114 console.error(msg);
Simon Hunt56d51852014-11-09 13:03:35 -0800115 doAlert(msg);
Simon Hunt25248912014-11-04 11:25:48 -0800116 }
117
118 function trace(msg) {
119 if (settings.trace) {
120 console.log(msg);
121 }
122 }
123
124 function traceFn(fn, params) {
125 if (settings.trace) {
126 console.log('*FN* ' + fn + '(...): ' + params);
127 }
Simon Hunt195cb382014-11-03 17:50:51 -0800128 }
129
130 // hash navigation
131 function hash() {
132 var hash = window.location.hash,
133 redo = false,
134 view,
135 t;
136
Simon Hunt25248912014-11-04 11:25:48 -0800137 traceFn('hash', hash);
138
Simon Hunt195cb382014-11-03 17:50:51 -0800139 if (!hash) {
Simon Hunt142d0032014-11-04 20:13:09 -0800140 hash = settings.startVid;
Simon Hunt195cb382014-11-03 17:50:51 -0800141 redo = true;
142 }
143
144 t = parseHash(hash);
145 if (!t || !t.vid) {
Simon Hunt56d51852014-11-09 13:03:35 -0800146 doError('Unable to parse target hash: "' + hash + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800147 }
148
149 view = views[t.vid];
150 if (!view) {
151 doError('No view defined with id: ' + t.vid);
152 }
153
154 if (redo) {
155 window.location.hash = makeHash(t);
156 // the above will result in a hashchange event, invoking
157 // this function again
158 } else {
159 // hash was not modified... navigate to where we need to be
160 navigate(hash, view, t);
161 }
Simon Hunt195cb382014-11-03 17:50:51 -0800162 }
163
164 function parseHash(s) {
165 // extract navigation coordinates from the supplied string
Simon Hunt56d51852014-11-09 13:03:35 -0800166 // "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
Simon Hunt25248912014-11-04 11:25:48 -0800167 traceFn('parseHash', s);
Simon Hunt195cb382014-11-03 17:50:51 -0800168
Simon Hunt56d51852014-11-09 13:03:35 -0800169 // look for use of flags, first
170 var vidctx,
171 vid,
172 ctx,
173 flags,
174 flagMap,
175 m;
176
177 // RE that includes flags ('?flag1,flag2')
178 m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
Simon Hunt195cb382014-11-03 17:50:51 -0800179 if (m) {
Simon Hunt56d51852014-11-09 13:03:35 -0800180 vidctx = m[1];
181 flags = m[2];
182 flagMap = {};
183 } else {
184 // no flags
185 m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
186 if (m) {
187 vidctx = m[1];
188 } else {
189 // bad hash
190 return null;
191 }
Simon Hunt195cb382014-11-03 17:50:51 -0800192 }
193
Simon Hunt56d51852014-11-09 13:03:35 -0800194 vidctx = vidctx.split(',');
195 vid = vidctx[0];
196 ctx = vidctx[1];
197 if (flags) {
198 flags.split(',').forEach(function (f) {
199 flagMap[f.trim()] = true;
200 });
201 }
202
203 return {
204 vid: vid.trim(),
205 ctx: ctx ? ctx.trim() : '',
206 flags: flagMap
207 };
208
Simon Hunt195cb382014-11-03 17:50:51 -0800209 }
210
Simon Hunt56d51852014-11-09 13:03:35 -0800211 function makeHash(t, ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800212 traceFn('makeHash');
Simon Hunt56d51852014-11-09 13:03:35 -0800213 // make a hash string from the given navigation coordinates,
214 // and optional flags map.
Simon Hunt195cb382014-11-03 17:50:51 -0800215 // if t is not an object, then it is a vid
216 var h = t,
Simon Hunt56d51852014-11-09 13:03:35 -0800217 c = ctx || '',
218 f = $.isPlainObject(flags) ? flags : null;
Simon Hunt195cb382014-11-03 17:50:51 -0800219
220 if ($.isPlainObject(t)) {
221 h = t.vid;
222 c = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800223 f = t.flags || null;
Simon Hunt195cb382014-11-03 17:50:51 -0800224 }
225
226 if (c) {
227 h += ',' + c;
228 }
Simon Hunt56d51852014-11-09 13:03:35 -0800229 if (f) {
230 h += '?' + d3.map(f).keys().join(',');
231 }
Simon Hunt25248912014-11-04 11:25:48 -0800232 trace('hash = "' + h + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800233 return h;
234 }
235
236 function navigate(hash, view, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800237 traceFn('navigate', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800238 // closePanes() // flyouts etc.
Simon Hunt25248912014-11-04 11:25:48 -0800239 // updateNav() // accordion / selected nav item etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800240 createView(view);
241 setView(view, hash, t);
242 }
243
244 function reportBuildErrors() {
Simon Hunt25248912014-11-04 11:25:48 -0800245 traceFn('reportBuildErrors');
Simon Hunt195cb382014-11-03 17:50:51 -0800246 // TODO: validate registered views / nav-item linkage etc.
247 console.log('(no build errors)');
248 }
249
Simon Hunt25248912014-11-04 11:25:48 -0800250 // returns the reference if it is a function, null otherwise
251 function isF(f) {
252 return $.isFunction(f) ? f : null;
253 }
254
Simon Hunt195cb382014-11-03 17:50:51 -0800255 // ..........................................................
256 // View life-cycle functions
257
Simon Hunt25248912014-11-04 11:25:48 -0800258 function setViewDimensions(sel) {
259 var w = window.innerWidth,
260 h = window.innerHeight - mastHeight;
261 sel.each(function () {
262 $(this)
263 .css('width', w + 'px')
264 .css('height', h + 'px')
265 });
266 }
267
Simon Hunt195cb382014-11-03 17:50:51 -0800268 function createView(view) {
269 var $d;
Simon Hunt25248912014-11-04 11:25:48 -0800270
Simon Hunt195cb382014-11-03 17:50:51 -0800271 // lazy initialization of the view
272 if (view && !view.$div) {
Simon Hunt25248912014-11-04 11:25:48 -0800273 trace('creating view for ' + view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800274 $d = $view.append('div')
275 .attr({
Simon Hunt25248912014-11-04 11:25:48 -0800276 id: view.vid,
277 class: 'onosView'
Simon Hunt195cb382014-11-03 17:50:51 -0800278 });
Simon Hunt25248912014-11-04 11:25:48 -0800279 setViewDimensions($d);
280 view.$div = $d; // cache a reference to the D3 selection
Simon Hunt195cb382014-11-03 17:50:51 -0800281 }
282 }
283
284 function setView(view, hash, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800285 traceFn('setView', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800286 // set the specified view as current, while invoking the
287 // appropriate life-cycle callbacks
288
Simon Hunt56d51852014-11-09 13:03:35 -0800289 // first, we'll start by closing the alerts pane, if open
290 closeAlerts();
291
Simon Hunt195cb382014-11-03 17:50:51 -0800292 // if there is a current view, and it is not the same as
293 // the incoming view, then unload it...
Simon Hunt25248912014-11-04 11:25:48 -0800294 if (current.view && (current.view.vid !== view.vid)) {
Simon Hunt195cb382014-11-03 17:50:51 -0800295 current.view.unload();
Simon Huntdb9eb072014-11-04 19:12:46 -0800296
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800297 // detach radio buttons, key handlers, etc.
298 $('#mastRadio').children().detach();
Thomas Vachuska65368e32014-11-08 16:10:20 -0800299 keyHandler.viewKeys = {};
300 keyHandler.viewFn = null;
Simon Hunt195cb382014-11-03 17:50:51 -0800301 }
302
303 // cache new view and context
304 current.view = view;
305 current.ctx = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800306 current.flags = t.flags || {};
Simon Hunt195cb382014-11-03 17:50:51 -0800307
Simon Hunt195cb382014-11-03 17:50:51 -0800308 // preload is called only once, after the view is in the DOM
309 if (!view.preloaded) {
Simon Hunt56d51852014-11-09 13:03:35 -0800310 view.preload(current.ctx, current.flags);
Simon Hunt25248912014-11-04 11:25:48 -0800311 view.preloaded = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800312 }
313
314 // clear the view of stale data
315 view.reset();
316
317 // load the view
Simon Hunt56d51852014-11-09 13:03:35 -0800318 view.load(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800319 }
320
Simon Huntdb9eb072014-11-04 19:12:46 -0800321 // generate 'unique' id by prefixing view id
Simon Hunt934c3ce2014-11-05 11:45:07 -0800322 function makeUid(view, id) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800323 return view.vid + '-' + id;
324 }
325
326 // restore id by removing view id prefix
Simon Hunt934c3ce2014-11-05 11:45:07 -0800327 function unmakeUid(view, uid) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800328 var re = new RegExp('^' + view.vid + '-');
329 return uid.replace(re, '');
330 }
331
Simon Hunt934c3ce2014-11-05 11:45:07 -0800332 function setRadioButtons(vid, btnSet) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800333 var view = views[vid],
334 btnG;
335
336 // lazily create the buttons...
337 if (!(btnG = view.radioButtons)) {
338 btnG = d3.select(document.createElement('div'));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800339 btnG.buttonDef = {};
Simon Huntdb9eb072014-11-04 19:12:46 -0800340
341 btnSet.forEach(function (btn, i) {
342 var bid = btn.id || 'b' + i,
343 txt = btn.text || 'Button #' + i,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800344 uid = makeUid(view, bid),
345 button = btnG.append('span')
Simon Huntdb9eb072014-11-04 19:12:46 -0800346 .attr({
Simon Hunt934c3ce2014-11-05 11:45:07 -0800347 id: uid,
Simon Huntdb9eb072014-11-04 19:12:46 -0800348 class: 'radio'
349 })
350 .text(txt);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800351
352 btnG.buttonDef[uid] = btn;
353
Simon Huntdb9eb072014-11-04 19:12:46 -0800354 if (i === 0) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800355 button.classed('active', true);
Simon Huntdb9eb072014-11-04 19:12:46 -0800356 }
357 });
358
359 btnG.selectAll('span')
360 .on('click', function (d) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800361 var button = d3.select(this),
362 uid = button.attr('id'),
363 btn = btnG.buttonDef[uid],
364 act = button.classed('active');
Simon Huntdb9eb072014-11-04 19:12:46 -0800365
366 if (!act) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800367 btnG.selectAll('span').classed('active', false);
368 button.classed('active', true);
369 if (isF(btn.cb)) {
370 btn.cb(view.token(), btn);
371 }
Simon Huntdb9eb072014-11-04 19:12:46 -0800372 }
373 });
374
375 view.radioButtons = btnG;
376 }
377
378 // attach the buttons to the masthead
379 $mastRadio.node().appendChild(btnG.node());
380 }
381
Thomas Vachuska65368e32014-11-08 16:10:20 -0800382 function setupGlobalKeys() {
383 keyHandler.globalKeys = {
384 esc: escapeKey,
385 T: toggleTheme
386 };
387 // Masked keys are global key handlers that always return true.
388 // That is, the view will never see the event for that key.
389 keyHandler.maskedKeys = {
390 T: true
391 };
392 }
393
394 function escapeKey(view, key, code, ev) {
395 if (alerts.open) {
396 closeAlerts();
397 return true;
398 }
399 return false;
400 }
401
402 function toggleTheme(view, key, code, ev) {
403 var body = d3.select('body');
404 current.theme = (current.theme === 'light') ? 'dark' : 'light';
405 body.classed('light dark', false);
406 body.classed(current.theme, true);
407 return true;
408 }
409
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800410 function setKeyBindings(keyArg) {
Thomas Vachuska65368e32014-11-08 16:10:20 -0800411 var viewKeys,
412 masked = [];
413
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800414 if ($.isFunction(keyArg)) {
415 // set general key handler callback
Thomas Vachuska65368e32014-11-08 16:10:20 -0800416 keyHandler.viewFn = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800417 } else {
418 // set specific key filter map
Thomas Vachuska65368e32014-11-08 16:10:20 -0800419 viewKeys = d3.map(keyArg).keys();
420 viewKeys.forEach(function (key) {
421 if (keyHandler.maskedKeys[key]) {
422 masked.push(' Key "' + key + '" is reserved');
423 }
424 });
425
426 if (masked.length) {
427 doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
428 }
429 keyHandler.viewKeys = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800430 }
431 }
432
Thomas Vachuska65368e32014-11-08 16:10:20 -0800433 function keyIn() {
434 var event = d3.event,
435 keyCode = event.keyCode,
436 key = whatKey(keyCode),
437 gcb = isF(keyHandler.globalKeys[key]),
438 vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
439
440 // global callback?
441 if (gcb && gcb(current.view.token(), key, keyCode, event)) {
442 // if the event was 'handled', we are done
443 return;
444 }
445 // otherwise, let the view callback have a shot
446 if (vcb) {
447 vcb(current.view.token(), key, keyCode, event);
448 }
449 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800450
451 function createAlerts() {
452 var al = d3.select('#alerts')
453 .style('display', 'block');
454 al.append('span')
455 .attr('class', 'close')
456 .text('X')
457 .on('click', closeAlerts);
458 al.append('pre');
Thomas Vachuska65368e32014-11-08 16:10:20 -0800459 al.append('p').attr('class', 'footnote')
460 .text('Press ESCAPE to close');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800461 alerts.open = true;
462 alerts.count = 0;
463 }
464
465 function closeAlerts() {
466 d3.select('#alerts')
Thomas Vachuska65368e32014-11-08 16:10:20 -0800467 .style('display', 'none')
468 .html('');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800469 alerts.open = false;
470 }
471
472 function addAlert(msg) {
473 var lines,
474 oldContent;
475
476 if (alerts.count) {
477 oldContent = d3.select('#alerts pre').html();
478 }
479
480 lines = msg.split('\n');
481 lines[0] += ' '; // spacing so we don't crowd 'X'
482 lines = lines.join('\n');
483
484 if (oldContent) {
485 lines += '\n----\n' + oldContent;
486 }
487
488 d3.select('#alerts pre').html(lines);
489 alerts.count++;
490 }
491
492 function doAlert(msg) {
493 if (!alerts.open) {
494 createAlerts();
495 }
496 addAlert(msg);
497 }
498
Simon Hunt25248912014-11-04 11:25:48 -0800499 function resize(e) {
500 d3.selectAll('.onosView').call(setViewDimensions);
501 // allow current view to react to resize event...
Simon Hunt195cb382014-11-03 17:50:51 -0800502 if (current.view) {
Simon Hunt56d51852014-11-09 13:03:35 -0800503 current.view.resize(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800504 }
505 }
506
507 // ..........................................................
508 // View class
509 // Captures state information about a view.
510
511 // Constructor
512 // vid : view id
513 // nid : id of associated nav-item (optional)
Simon Hunt25248912014-11-04 11:25:48 -0800514 // cb : callbacks (preload, reset, load, unload, resize, error)
Simon Hunt195cb382014-11-03 17:50:51 -0800515 function View(vid) {
516 var av = 'addView(): ',
517 args = Array.prototype.slice.call(arguments),
518 nid,
Simon Hunt25248912014-11-04 11:25:48 -0800519 cb;
Simon Hunt195cb382014-11-03 17:50:51 -0800520
521 args.shift(); // first arg is always vid
522 if (typeof args[0] === 'string') { // nid specified
523 nid = args.shift();
524 }
525 cb = args.shift();
Simon Hunt195cb382014-11-03 17:50:51 -0800526
527 this.vid = vid;
528
529 if (validateViewArgs(vid)) {
530 this.nid = nid; // explicit navitem id (can be null)
531 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
Simon Huntdb9eb072014-11-04 19:12:46 -0800532 this.$div = null; // view not yet added to DOM
533 this.radioButtons = null; // no radio buttons yet
534 this.ok = true; // valid view
Simon Hunt195cb382014-11-03 17:50:51 -0800535 }
Simon Hunt195cb382014-11-03 17:50:51 -0800536 }
537
538 function validateViewArgs(vid) {
Simon Hunt25248912014-11-04 11:25:48 -0800539 var av = "ui.addView(...): ",
540 ok = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800541 if (typeof vid !== 'string' || !vid) {
542 doError(av + 'vid required');
543 } else if (views[vid]) {
544 doError(av + 'View ID "' + vid + '" already exists');
545 } else {
546 ok = true;
547 }
548 return ok;
549 }
550
551 var viewInstanceMethods = {
Simon Hunt25248912014-11-04 11:25:48 -0800552 token: function () {
Simon Hunt195cb382014-11-03 17:50:51 -0800553 return {
Simon Hunt25248912014-11-04 11:25:48 -0800554 // attributes
Simon Hunt195cb382014-11-03 17:50:51 -0800555 vid: this.vid,
556 nid: this.nid,
Simon Hunt25248912014-11-04 11:25:48 -0800557 $div: this.$div,
558
559 // functions
560 width: this.width,
Simon Huntdb9eb072014-11-04 19:12:46 -0800561 height: this.height,
Simon Hunt142d0032014-11-04 20:13:09 -0800562 uid: this.uid,
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800563 setRadio: this.setRadio,
Simon Huntc7ee0662014-11-05 16:44:37 -0800564 setKeys: this.setKeys,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800565 dataLoadError: this.dataLoadError,
566 alert: this.alert
Simon Hunt195cb382014-11-03 17:50:51 -0800567 }
Simon Hunt25248912014-11-04 11:25:48 -0800568 },
569
Simon Huntf67722a2014-11-10 09:32:06 -0800570 // == Life-cycle functions
571 // TODO: factor common code out of life-cycle
Simon Hunt56d51852014-11-09 13:03:35 -0800572 preload: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800573 var c = ctx || '',
574 fn = isF(this.cb.preload);
575 traceFn('View.preload', this.vid + ', ' + c);
576 if (fn) {
577 trace('PRELOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800578 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800579 }
580 },
581
Simon Huntf67722a2014-11-10 09:32:06 -0800582 reset: function (ctx, flags) {
583 var c = ctx || '',
584 fn = isF(this.cb.reset);
Simon Hunt25248912014-11-04 11:25:48 -0800585 traceFn('View.reset', this.vid);
586 if (fn) {
587 trace('RESET cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800588 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800589 } else if (this.cb.reset === true) {
590 // boolean true signifies "clear view"
591 trace(' [true] cleaing view...');
592 viewApi.empty();
593 }
594 },
595
Simon Hunt56d51852014-11-09 13:03:35 -0800596 load: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800597 var c = ctx || '',
598 fn = isF(this.cb.load);
599 traceFn('View.load', this.vid + ', ' + c);
600 this.$div.classed('currentView', true);
Simon Hunt25248912014-11-04 11:25:48 -0800601 if (fn) {
602 trace('LOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800603 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800604 }
605 },
606
Simon Huntf67722a2014-11-10 09:32:06 -0800607 unload: function (ctx, flags) {
608 var c = ctx | '',
609 fn = isF(this.cb.unload);
Simon Hunt25248912014-11-04 11:25:48 -0800610 traceFn('View.unload', this.vid);
611 this.$div.classed('currentView', false);
Simon Hunt25248912014-11-04 11:25:48 -0800612 if (fn) {
613 trace('UNLOAD cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800614 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800615 }
616 },
617
Simon Hunt56d51852014-11-09 13:03:35 -0800618 resize: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800619 var c = ctx || '',
620 fn = isF(this.cb.resize),
621 w = this.width(),
622 h = this.height();
623 traceFn('View.resize', this.vid + '/' + c +
624 ' [' + w + 'x' + h + ']');
625 if (fn) {
626 trace('RESIZE cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800627 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800628 }
629 },
630
Simon Huntf67722a2014-11-10 09:32:06 -0800631 error: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800632 var c = ctx || '',
633 fn = isF(this.cb.error);
634 traceFn('View.error', this.vid + ', ' + c);
635 if (fn) {
636 trace('ERROR cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800637 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800638 }
639 },
640
Simon Huntf67722a2014-11-10 09:32:06 -0800641 // == Token API functions
Simon Hunt25248912014-11-04 11:25:48 -0800642 width: function () {
643 return $(this.$div.node()).width();
644 },
645
646 height: function () {
647 return $(this.$div.node()).height();
Simon Huntdb9eb072014-11-04 19:12:46 -0800648 },
Simon Hunt25248912014-11-04 11:25:48 -0800649
Simon Hunt934c3ce2014-11-05 11:45:07 -0800650 setRadio: function (btnSet) {
651 setRadioButtons(this.vid, btnSet);
Simon Hunt142d0032014-11-04 20:13:09 -0800652 },
653
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800654 setKeys: function (keyArg) {
655 setKeyBindings(keyArg);
656 },
657
Simon Hunt142d0032014-11-04 20:13:09 -0800658 uid: function (id) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800659 return makeUid(this, id);
Simon Huntc7ee0662014-11-05 16:44:37 -0800660 },
661
Simon Huntbb282f52014-11-10 11:08:19 -0800662 // TODO : add exportApi and importApi methods
Simon Hunt1a9eff92014-11-07 11:06:34 -0800663 // TODO : implement custom dialogs
664
665 // Consider enhancing alert mechanism to handle multiples
666 // as individually closable.
667 alert: function (msg) {
668 doAlert(msg);
669 },
Simon Huntc7ee0662014-11-05 16:44:37 -0800670
671 dataLoadError: function (err, url) {
672 var msg = 'Data Load Error\n\n' +
673 err.status + ' -- ' + err.statusText + '\n\n' +
674 'relative-url: "' + url + '"\n\n' +
675 'complete-url: "' + err.responseURL + '"';
Simon Hunt1a9eff92014-11-07 11:06:34 -0800676 this.alert(msg);
Simon Huntdb9eb072014-11-04 19:12:46 -0800677 }
Simon Hunt25248912014-11-04 11:25:48 -0800678
679 // TODO: consider schedule, clearTimer, etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800680 };
681
682 // attach instance methods to the view prototype
683 $.extend(View.prototype, viewInstanceMethods);
684
685 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800686 // UI API
Simon Hunt195cb382014-11-03 17:50:51 -0800687
Simon Hunt25248912014-11-04 11:25:48 -0800688 uiApi = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800689 addLib: function (libName, api) {
690 // TODO: validation of args
691 libApi[libName] = api;
692 },
693
694 // TODO: it remains to be seen whether we keep this style of docs
Simon Hunt25248912014-11-04 11:25:48 -0800695 /** @api ui addView( vid, nid, cb )
696 * Adds a view to the UI.
697 * <p>
698 * Views are loaded/unloaded into the view content pane at
699 * appropriate times, by the navigation framework. This method
700 * adds a view to the UI and returns a token object representing
701 * the view. A view's token is always passed as the first
702 * argument to each of the view's life-cycle callback functions.
703 * <p>
704 * Note that if the view is directly referenced by a nav-item,
705 * or in a group of views with one of those views referenced by
706 * a nav-item, then the <i>nid</i> argument can be omitted as
707 * the framework can infer it.
708 * <p>
709 * <i>cb</i> is a plain object containing callback functions:
710 * "preload", "reset", "load", "unload", "resize", "error".
711 * <pre>
712 * function myLoad(view, ctx) { ... }
713 * ...
714 * // short form...
715 * onos.ui.addView('viewId', {
716 * load: myLoad
717 * });
718 * </pre>
719 *
720 * @param vid (string) [*] view ID (a unique DOM element id)
721 * @param nid (string) nav-item ID (a unique DOM element id)
722 * @param cb (object) [*] callbacks object
723 * @return the view token
724 */
725 addView: function (vid, nid, cb) {
726 traceFn('addView', vid);
727 var view = new View(vid, nid, cb),
Simon Hunt195cb382014-11-03 17:50:51 -0800728 token;
729 if (view.ok) {
730 views[vid] = view;
731 token = view.token();
732 } else {
733 token = { vid: view.vid, bad: true };
734 }
735 return token;
736 }
737 };
738
Simon Hunt25248912014-11-04 11:25:48 -0800739 // ..........................................................
740 // View API
741
Simon Huntbb282f52014-11-10 11:08:19 -0800742 // TODO: deprecated
Simon Hunt25248912014-11-04 11:25:48 -0800743 viewApi = {
744 /** @api view empty( )
745 * Empties the current view.
746 * <p>
747 * More specifically, removes all DOM elements from the
748 * current view's display div.
749 */
750 empty: function () {
751 if (!current.view) {
752 return;
753 }
754 current.view.$div.html('');
755 }
756 };
757
758 // ..........................................................
759 // Nav API
760 navApi = {
761
762 };
763
764 // ..........................................................
Simon Hunt1a9eff92014-11-07 11:06:34 -0800765 // Library API
766 libApi = {
767
768 };
769
770 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800771 // Exported API
772
Simon Hunt195cb382014-11-03 17:50:51 -0800773 // function to be called from index.html to build the ONOS UI
774 function buildOnosUi() {
775 tsB = new Date().getTime();
776 tsI = tsB - tsI; // initialization duration
777
778 console.log('ONOS UI initialized in ' + tsI + 'ms');
779
780 if (built) {
781 throwError("ONOS UI already built!");
782 }
783 built = true;
784
785 $view = d3.select('#view');
Simon Huntdb9eb072014-11-04 19:12:46 -0800786 $mastRadio = d3.select('#mastRadio');
Simon Hunt195cb382014-11-03 17:50:51 -0800787
788 $(window).on('hashchange', hash);
Simon Hunt25248912014-11-04 11:25:48 -0800789 $(window).on('resize', resize);
Simon Hunt195cb382014-11-03 17:50:51 -0800790
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800791 d3.select('body').on('keydown', keyIn);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800792 setupGlobalKeys();
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800793
Simon Hunt195cb382014-11-03 17:50:51 -0800794 // Invoke hashchange callback to navigate to content
795 // indicated by the window location hash.
796 hash();
797
798 // If there were any build errors, report them
799 reportBuildErrors();
800 }
801
Simon Hunt195cb382014-11-03 17:50:51 -0800802 // export the api and build-UI function
803 return {
Simon Hunt25248912014-11-04 11:25:48 -0800804 ui: uiApi,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800805 lib: libApi,
806 //view: viewApi,
Simon Hunt25248912014-11-04 11:25:48 -0800807 nav: navApi,
Simon Huntbb282f52014-11-10 11:08:19 -0800808 buildUi: buildOnosUi,
809 exported: exported
Simon Hunt195cb382014-11-03 17:50:51 -0800810 };
811 };
812
Simon Huntdb9eb072014-11-04 19:12:46 -0800813}(jQuery));