blob: 4aeb23e691eebb0648f7c720bb2bf3df2b07edad [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,
36 libApi;
Simon Hunt25248912014-11-04 11:25:48 -080037
38 var defaultOptions = {
Simon Hunt142d0032014-11-04 20:13:09 -080039 trace: false,
Thomas Vachuska65368e32014-11-08 16:10:20 -080040 theme: 'light',
Simon Hunt142d0032014-11-04 20:13:09 -080041 startVid: defaultVid
Simon Hunt25248912014-11-04 11:25:48 -080042 };
43
44 // compute runtime settings
45 var settings = $.extend({}, defaultOptions, options);
Simon Hunt195cb382014-11-03 17:50:51 -080046
Thomas Vachuska65368e32014-11-08 16:10:20 -080047 // set the selected theme
48 d3.select('body').classed(settings.theme, true);
49
Simon Hunt195cb382014-11-03 17:50:51 -080050 // internal state
51 var views = {},
52 current = {
53 view: null,
Thomas Vachuska65368e32014-11-08 16:10:20 -080054 ctx: '',
55 theme: settings.theme
Simon Hunt195cb382014-11-03 17:50:51 -080056 },
57 built = false,
Simon Hunt0df1b1d2014-11-04 22:58:29 -080058 errorCount = 0,
Simon Hunt934c3ce2014-11-05 11:45:07 -080059 keyHandler = {
Thomas Vachuska65368e32014-11-08 16:10:20 -080060 globalKeys: {},
61 maskedKeys: {},
62 viewKeys: {},
63 viewFn: null
64 },
65 alerts = {
66 open: false,
67 count: 0
Simon Hunt934c3ce2014-11-05 11:45:07 -080068 };
Simon Hunt195cb382014-11-03 17:50:51 -080069
70 // DOM elements etc.
Simon Huntdb9eb072014-11-04 19:12:46 -080071 var $view,
72 $mastRadio;
Simon Hunt195cb382014-11-03 17:50:51 -080073
74
Simon Hunt0df1b1d2014-11-04 22:58:29 -080075 function whatKey(code) {
76 switch (code) {
77 case 13: return 'enter';
78 case 16: return 'shift';
79 case 17: return 'ctrl';
80 case 18: return 'alt';
81 case 27: return 'esc';
82 case 32: return 'space';
83 case 37: return 'leftArrow';
84 case 38: return 'upArrow';
85 case 39: return 'rightArrow';
86 case 40: return 'downArrow';
87 case 91: return 'cmdLeft';
88 case 93: return 'cmdRight';
89 default:
90 if ((code >= 48 && code <= 57) ||
91 (code >= 65 && code <= 90)) {
92 return String.fromCharCode(code);
93 } else if (code >= 112 && code <= 123) {
94 return 'F' + (code - 111);
95 }
96 return '.';
97 }
98 }
99
100
Simon Hunt195cb382014-11-03 17:50:51 -0800101 // ..........................................................
102 // Internal functions
103
104 // throw an error
105 function throwError(msg) {
106 // separate function, as we might add tracing here too, later
107 throw new Error(msg);
108 }
109
110 function doError(msg) {
111 errorCount++;
Simon Hunt25248912014-11-04 11:25:48 -0800112 console.error(msg);
113 }
114
115 function trace(msg) {
116 if (settings.trace) {
117 console.log(msg);
118 }
119 }
120
121 function traceFn(fn, params) {
122 if (settings.trace) {
123 console.log('*FN* ' + fn + '(...): ' + params);
124 }
Simon Hunt195cb382014-11-03 17:50:51 -0800125 }
126
127 // hash navigation
128 function hash() {
129 var hash = window.location.hash,
130 redo = false,
131 view,
132 t;
133
Simon Hunt25248912014-11-04 11:25:48 -0800134 traceFn('hash', hash);
135
Simon Hunt195cb382014-11-03 17:50:51 -0800136 if (!hash) {
Simon Hunt142d0032014-11-04 20:13:09 -0800137 hash = settings.startVid;
Simon Hunt195cb382014-11-03 17:50:51 -0800138 redo = true;
139 }
140
141 t = parseHash(hash);
142 if (!t || !t.vid) {
143 doError('Unable to parse target hash: ' + hash);
144 }
145
146 view = views[t.vid];
147 if (!view) {
148 doError('No view defined with id: ' + t.vid);
149 }
150
151 if (redo) {
152 window.location.hash = makeHash(t);
153 // the above will result in a hashchange event, invoking
154 // this function again
155 } else {
156 // hash was not modified... navigate to where we need to be
157 navigate(hash, view, t);
158 }
Simon Hunt195cb382014-11-03 17:50:51 -0800159 }
160
161 function parseHash(s) {
162 // extract navigation coordinates from the supplied string
163 // "vid,ctx" --> { vid:vid, ctx:ctx }
Simon Hunt25248912014-11-04 11:25:48 -0800164 traceFn('parseHash', s);
Simon Hunt195cb382014-11-03 17:50:51 -0800165
166 var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
167 if (m) {
168 return { vid: m[1], ctx: m[2] };
169 }
170
171 m = /^[#]{0,1}(\S+)$/.exec(s);
172 return m ? { vid: m[1] } : null;
173 }
174
175 function makeHash(t, ctx) {
Simon Hunt25248912014-11-04 11:25:48 -0800176 traceFn('makeHash');
Simon Hunt195cb382014-11-03 17:50:51 -0800177 // make a hash string from the given navigation coordinates.
178 // if t is not an object, then it is a vid
179 var h = t,
180 c = ctx || '';
181
182 if ($.isPlainObject(t)) {
183 h = t.vid;
184 c = t.ctx || '';
185 }
186
187 if (c) {
188 h += ',' + c;
189 }
Simon Hunt25248912014-11-04 11:25:48 -0800190 trace('hash = "' + h + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800191 return h;
192 }
193
194 function navigate(hash, view, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800195 traceFn('navigate', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800196 // closePanes() // flyouts etc.
Simon Hunt25248912014-11-04 11:25:48 -0800197 // updateNav() // accordion / selected nav item etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800198 createView(view);
199 setView(view, hash, t);
200 }
201
202 function reportBuildErrors() {
Simon Hunt25248912014-11-04 11:25:48 -0800203 traceFn('reportBuildErrors');
Simon Hunt195cb382014-11-03 17:50:51 -0800204 // TODO: validate registered views / nav-item linkage etc.
205 console.log('(no build errors)');
206 }
207
Simon Hunt25248912014-11-04 11:25:48 -0800208 // returns the reference if it is a function, null otherwise
209 function isF(f) {
210 return $.isFunction(f) ? f : null;
211 }
212
Simon Hunt195cb382014-11-03 17:50:51 -0800213 // ..........................................................
214 // View life-cycle functions
215
Simon Hunt25248912014-11-04 11:25:48 -0800216 function setViewDimensions(sel) {
217 var w = window.innerWidth,
218 h = window.innerHeight - mastHeight;
219 sel.each(function () {
220 $(this)
221 .css('width', w + 'px')
222 .css('height', h + 'px')
223 });
224 }
225
Simon Hunt195cb382014-11-03 17:50:51 -0800226 function createView(view) {
227 var $d;
Simon Hunt25248912014-11-04 11:25:48 -0800228
Simon Hunt195cb382014-11-03 17:50:51 -0800229 // lazy initialization of the view
230 if (view && !view.$div) {
Simon Hunt25248912014-11-04 11:25:48 -0800231 trace('creating view for ' + view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800232 $d = $view.append('div')
233 .attr({
Simon Hunt25248912014-11-04 11:25:48 -0800234 id: view.vid,
235 class: 'onosView'
Simon Hunt195cb382014-11-03 17:50:51 -0800236 });
Simon Hunt25248912014-11-04 11:25:48 -0800237 setViewDimensions($d);
238 view.$div = $d; // cache a reference to the D3 selection
Simon Hunt195cb382014-11-03 17:50:51 -0800239 }
240 }
241
242 function setView(view, hash, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800243 traceFn('setView', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800244 // set the specified view as current, while invoking the
245 // appropriate life-cycle callbacks
246
247 // if there is a current view, and it is not the same as
248 // the incoming view, then unload it...
Simon Hunt25248912014-11-04 11:25:48 -0800249 if (current.view && (current.view.vid !== view.vid)) {
Simon Hunt195cb382014-11-03 17:50:51 -0800250 current.view.unload();
Simon Huntdb9eb072014-11-04 19:12:46 -0800251
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800252 // detach radio buttons, key handlers, etc.
253 $('#mastRadio').children().detach();
Thomas Vachuska65368e32014-11-08 16:10:20 -0800254 keyHandler.viewKeys = {};
255 keyHandler.viewFn = null;
Simon Hunt195cb382014-11-03 17:50:51 -0800256 }
257
258 // cache new view and context
259 current.view = view;
260 current.ctx = t.ctx || '';
261
Simon Hunt195cb382014-11-03 17:50:51 -0800262 // preload is called only once, after the view is in the DOM
263 if (!view.preloaded) {
Simon Hunt25248912014-11-04 11:25:48 -0800264 view.preload(current.ctx);
265 view.preloaded = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800266 }
267
268 // clear the view of stale data
269 view.reset();
270
271 // load the view
Simon Hunt25248912014-11-04 11:25:48 -0800272 view.load(current.ctx);
Simon Hunt195cb382014-11-03 17:50:51 -0800273 }
274
Simon Huntdb9eb072014-11-04 19:12:46 -0800275 // generate 'unique' id by prefixing view id
Simon Hunt934c3ce2014-11-05 11:45:07 -0800276 function makeUid(view, id) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800277 return view.vid + '-' + id;
278 }
279
280 // restore id by removing view id prefix
Simon Hunt934c3ce2014-11-05 11:45:07 -0800281 function unmakeUid(view, uid) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800282 var re = new RegExp('^' + view.vid + '-');
283 return uid.replace(re, '');
284 }
285
Simon Hunt934c3ce2014-11-05 11:45:07 -0800286 function setRadioButtons(vid, btnSet) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800287 var view = views[vid],
288 btnG;
289
290 // lazily create the buttons...
291 if (!(btnG = view.radioButtons)) {
292 btnG = d3.select(document.createElement('div'));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800293 btnG.buttonDef = {};
Simon Huntdb9eb072014-11-04 19:12:46 -0800294
295 btnSet.forEach(function (btn, i) {
296 var bid = btn.id || 'b' + i,
297 txt = btn.text || 'Button #' + i,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800298 uid = makeUid(view, bid),
299 button = btnG.append('span')
Simon Huntdb9eb072014-11-04 19:12:46 -0800300 .attr({
Simon Hunt934c3ce2014-11-05 11:45:07 -0800301 id: uid,
Simon Huntdb9eb072014-11-04 19:12:46 -0800302 class: 'radio'
303 })
304 .text(txt);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800305
306 btnG.buttonDef[uid] = btn;
307
Simon Huntdb9eb072014-11-04 19:12:46 -0800308 if (i === 0) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800309 button.classed('active', true);
Simon Huntdb9eb072014-11-04 19:12:46 -0800310 }
311 });
312
313 btnG.selectAll('span')
314 .on('click', function (d) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800315 var button = d3.select(this),
316 uid = button.attr('id'),
317 btn = btnG.buttonDef[uid],
318 act = button.classed('active');
Simon Huntdb9eb072014-11-04 19:12:46 -0800319
320 if (!act) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800321 btnG.selectAll('span').classed('active', false);
322 button.classed('active', true);
323 if (isF(btn.cb)) {
324 btn.cb(view.token(), btn);
325 }
Simon Huntdb9eb072014-11-04 19:12:46 -0800326 }
327 });
328
329 view.radioButtons = btnG;
330 }
331
332 // attach the buttons to the masthead
333 $mastRadio.node().appendChild(btnG.node());
334 }
335
Thomas Vachuska65368e32014-11-08 16:10:20 -0800336 function setupGlobalKeys() {
337 keyHandler.globalKeys = {
338 esc: escapeKey,
339 T: toggleTheme
340 };
341 // Masked keys are global key handlers that always return true.
342 // That is, the view will never see the event for that key.
343 keyHandler.maskedKeys = {
344 T: true
345 };
346 }
347
348 function escapeKey(view, key, code, ev) {
349 if (alerts.open) {
350 closeAlerts();
351 return true;
352 }
353 return false;
354 }
355
356 function toggleTheme(view, key, code, ev) {
357 var body = d3.select('body');
358 current.theme = (current.theme === 'light') ? 'dark' : 'light';
359 body.classed('light dark', false);
360 body.classed(current.theme, true);
361 return true;
362 }
363
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800364 function setKeyBindings(keyArg) {
Thomas Vachuska65368e32014-11-08 16:10:20 -0800365 var viewKeys,
366 masked = [];
367
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800368 if ($.isFunction(keyArg)) {
369 // set general key handler callback
Thomas Vachuska65368e32014-11-08 16:10:20 -0800370 keyHandler.viewFn = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800371 } else {
372 // set specific key filter map
Thomas Vachuska65368e32014-11-08 16:10:20 -0800373 viewKeys = d3.map(keyArg).keys();
374 viewKeys.forEach(function (key) {
375 if (keyHandler.maskedKeys[key]) {
376 masked.push(' Key "' + key + '" is reserved');
377 }
378 });
379
380 if (masked.length) {
381 doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
382 }
383 keyHandler.viewKeys = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800384 }
385 }
386
Thomas Vachuska65368e32014-11-08 16:10:20 -0800387 function keyIn() {
388 var event = d3.event,
389 keyCode = event.keyCode,
390 key = whatKey(keyCode),
391 gcb = isF(keyHandler.globalKeys[key]),
392 vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
393
394 // global callback?
395 if (gcb && gcb(current.view.token(), key, keyCode, event)) {
396 // if the event was 'handled', we are done
397 return;
398 }
399 // otherwise, let the view callback have a shot
400 if (vcb) {
401 vcb(current.view.token(), key, keyCode, event);
402 }
403 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800404
405 function createAlerts() {
406 var al = d3.select('#alerts')
407 .style('display', 'block');
408 al.append('span')
409 .attr('class', 'close')
410 .text('X')
411 .on('click', closeAlerts);
412 al.append('pre');
Thomas Vachuska65368e32014-11-08 16:10:20 -0800413 al.append('p').attr('class', 'footnote')
414 .text('Press ESCAPE to close');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800415 alerts.open = true;
416 alerts.count = 0;
417 }
418
419 function closeAlerts() {
420 d3.select('#alerts')
Thomas Vachuska65368e32014-11-08 16:10:20 -0800421 .style('display', 'none')
422 .html('');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800423 alerts.open = false;
424 }
425
426 function addAlert(msg) {
427 var lines,
428 oldContent;
429
430 if (alerts.count) {
431 oldContent = d3.select('#alerts pre').html();
432 }
433
434 lines = msg.split('\n');
435 lines[0] += ' '; // spacing so we don't crowd 'X'
436 lines = lines.join('\n');
437
438 if (oldContent) {
439 lines += '\n----\n' + oldContent;
440 }
441
442 d3.select('#alerts pre').html(lines);
443 alerts.count++;
444 }
445
446 function doAlert(msg) {
447 if (!alerts.open) {
448 createAlerts();
449 }
450 addAlert(msg);
451 }
452
Simon Hunt25248912014-11-04 11:25:48 -0800453 function resize(e) {
454 d3.selectAll('.onosView').call(setViewDimensions);
455 // allow current view to react to resize event...
Simon Hunt195cb382014-11-03 17:50:51 -0800456 if (current.view) {
Simon Hunt25248912014-11-04 11:25:48 -0800457 current.view.resize(current.ctx);
Simon Hunt195cb382014-11-03 17:50:51 -0800458 }
459 }
460
461 // ..........................................................
462 // View class
463 // Captures state information about a view.
464
465 // Constructor
466 // vid : view id
467 // nid : id of associated nav-item (optional)
Simon Hunt25248912014-11-04 11:25:48 -0800468 // cb : callbacks (preload, reset, load, unload, resize, error)
Simon Hunt195cb382014-11-03 17:50:51 -0800469 function View(vid) {
470 var av = 'addView(): ',
471 args = Array.prototype.slice.call(arguments),
472 nid,
Simon Hunt25248912014-11-04 11:25:48 -0800473 cb;
Simon Hunt195cb382014-11-03 17:50:51 -0800474
475 args.shift(); // first arg is always vid
476 if (typeof args[0] === 'string') { // nid specified
477 nid = args.shift();
478 }
479 cb = args.shift();
Simon Hunt195cb382014-11-03 17:50:51 -0800480
481 this.vid = vid;
482
483 if (validateViewArgs(vid)) {
484 this.nid = nid; // explicit navitem id (can be null)
485 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
Simon Huntdb9eb072014-11-04 19:12:46 -0800486 this.$div = null; // view not yet added to DOM
487 this.radioButtons = null; // no radio buttons yet
488 this.ok = true; // valid view
Simon Hunt195cb382014-11-03 17:50:51 -0800489 }
Simon Hunt195cb382014-11-03 17:50:51 -0800490 }
491
492 function validateViewArgs(vid) {
Simon Hunt25248912014-11-04 11:25:48 -0800493 var av = "ui.addView(...): ",
494 ok = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800495 if (typeof vid !== 'string' || !vid) {
496 doError(av + 'vid required');
497 } else if (views[vid]) {
498 doError(av + 'View ID "' + vid + '" already exists');
499 } else {
500 ok = true;
501 }
502 return ok;
503 }
504
505 var viewInstanceMethods = {
Simon Hunt25248912014-11-04 11:25:48 -0800506 token: function () {
Simon Hunt195cb382014-11-03 17:50:51 -0800507 return {
Simon Hunt25248912014-11-04 11:25:48 -0800508 // attributes
Simon Hunt195cb382014-11-03 17:50:51 -0800509 vid: this.vid,
510 nid: this.nid,
Simon Hunt25248912014-11-04 11:25:48 -0800511 $div: this.$div,
512
513 // functions
514 width: this.width,
Simon Huntdb9eb072014-11-04 19:12:46 -0800515 height: this.height,
Simon Hunt142d0032014-11-04 20:13:09 -0800516 uid: this.uid,
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800517 setRadio: this.setRadio,
Simon Huntc7ee0662014-11-05 16:44:37 -0800518 setKeys: this.setKeys,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800519 dataLoadError: this.dataLoadError,
520 alert: this.alert
Simon Hunt195cb382014-11-03 17:50:51 -0800521 }
Simon Hunt25248912014-11-04 11:25:48 -0800522 },
523
524 preload: function (ctx) {
525 var c = ctx || '',
526 fn = isF(this.cb.preload);
527 traceFn('View.preload', this.vid + ', ' + c);
528 if (fn) {
529 trace('PRELOAD cb for ' + this.vid);
530 fn(this.token(), c);
531 }
532 },
533
534 reset: function () {
535 var fn = isF(this.cb.reset);
536 traceFn('View.reset', this.vid);
537 if (fn) {
538 trace('RESET cb for ' + this.vid);
539 fn(this.token());
540 } else if (this.cb.reset === true) {
541 // boolean true signifies "clear view"
542 trace(' [true] cleaing view...');
543 viewApi.empty();
544 }
545 },
546
547 load: function (ctx) {
548 var c = ctx || '',
549 fn = isF(this.cb.load);
550 traceFn('View.load', this.vid + ', ' + c);
551 this.$div.classed('currentView', true);
552 // TODO: add radio button set, if needed
553 if (fn) {
554 trace('LOAD cb for ' + this.vid);
555 fn(this.token(), c);
556 }
557 },
558
559 unload: function () {
560 var fn = isF(this.cb.unload);
561 traceFn('View.unload', this.vid);
562 this.$div.classed('currentView', false);
563 // TODO: remove radio button set, if needed
564 if (fn) {
565 trace('UNLOAD cb for ' + this.vid);
566 fn(this.token());
567 }
568 },
569
570 resize: function (ctx) {
571 var c = ctx || '',
572 fn = isF(this.cb.resize),
573 w = this.width(),
574 h = this.height();
575 traceFn('View.resize', this.vid + '/' + c +
576 ' [' + w + 'x' + h + ']');
577 if (fn) {
578 trace('RESIZE cb for ' + this.vid);
579 fn(this.token(), c);
580 }
581 },
582
583 error: function (ctx) {
584 var c = ctx || '',
585 fn = isF(this.cb.error);
586 traceFn('View.error', this.vid + ', ' + c);
587 if (fn) {
588 trace('ERROR cb for ' + this.vid);
589 fn(this.token(), c);
590 }
591 },
592
593 width: function () {
594 return $(this.$div.node()).width();
595 },
596
597 height: function () {
598 return $(this.$div.node()).height();
Simon Huntdb9eb072014-11-04 19:12:46 -0800599 },
Simon Hunt25248912014-11-04 11:25:48 -0800600
Simon Hunt934c3ce2014-11-05 11:45:07 -0800601 setRadio: function (btnSet) {
602 setRadioButtons(this.vid, btnSet);
Simon Hunt142d0032014-11-04 20:13:09 -0800603 },
604
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800605 setKeys: function (keyArg) {
606 setKeyBindings(keyArg);
607 },
608
Simon Hunt142d0032014-11-04 20:13:09 -0800609 uid: function (id) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800610 return makeUid(this, id);
Simon Huntc7ee0662014-11-05 16:44:37 -0800611 },
612
Simon Hunt1a9eff92014-11-07 11:06:34 -0800613 // TODO : implement custom dialogs
614
615 // Consider enhancing alert mechanism to handle multiples
616 // as individually closable.
617 alert: function (msg) {
618 doAlert(msg);
619 },
Simon Huntc7ee0662014-11-05 16:44:37 -0800620
621 dataLoadError: function (err, url) {
622 var msg = 'Data Load Error\n\n' +
623 err.status + ' -- ' + err.statusText + '\n\n' +
624 'relative-url: "' + url + '"\n\n' +
625 'complete-url: "' + err.responseURL + '"';
Simon Hunt1a9eff92014-11-07 11:06:34 -0800626 this.alert(msg);
Simon Huntdb9eb072014-11-04 19:12:46 -0800627 }
Simon Hunt25248912014-11-04 11:25:48 -0800628
629 // TODO: consider schedule, clearTimer, etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800630 };
631
632 // attach instance methods to the view prototype
633 $.extend(View.prototype, viewInstanceMethods);
634
635 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800636 // UI API
Simon Hunt195cb382014-11-03 17:50:51 -0800637
Simon Hunt25248912014-11-04 11:25:48 -0800638 uiApi = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800639 addLib: function (libName, api) {
640 // TODO: validation of args
641 libApi[libName] = api;
642 },
643
644 // TODO: it remains to be seen whether we keep this style of docs
Simon Hunt25248912014-11-04 11:25:48 -0800645 /** @api ui addView( vid, nid, cb )
646 * Adds a view to the UI.
647 * <p>
648 * Views are loaded/unloaded into the view content pane at
649 * appropriate times, by the navigation framework. This method
650 * adds a view to the UI and returns a token object representing
651 * the view. A view's token is always passed as the first
652 * argument to each of the view's life-cycle callback functions.
653 * <p>
654 * Note that if the view is directly referenced by a nav-item,
655 * or in a group of views with one of those views referenced by
656 * a nav-item, then the <i>nid</i> argument can be omitted as
657 * the framework can infer it.
658 * <p>
659 * <i>cb</i> is a plain object containing callback functions:
660 * "preload", "reset", "load", "unload", "resize", "error".
661 * <pre>
662 * function myLoad(view, ctx) { ... }
663 * ...
664 * // short form...
665 * onos.ui.addView('viewId', {
666 * load: myLoad
667 * });
668 * </pre>
669 *
670 * @param vid (string) [*] view ID (a unique DOM element id)
671 * @param nid (string) nav-item ID (a unique DOM element id)
672 * @param cb (object) [*] callbacks object
673 * @return the view token
674 */
675 addView: function (vid, nid, cb) {
676 traceFn('addView', vid);
677 var view = new View(vid, nid, cb),
Simon Hunt195cb382014-11-03 17:50:51 -0800678 token;
679 if (view.ok) {
680 views[vid] = view;
681 token = view.token();
682 } else {
683 token = { vid: view.vid, bad: true };
684 }
685 return token;
686 }
687 };
688
Simon Hunt25248912014-11-04 11:25:48 -0800689 // ..........................................................
690 // View API
691
692 viewApi = {
693 /** @api view empty( )
694 * Empties the current view.
695 * <p>
696 * More specifically, removes all DOM elements from the
697 * current view's display div.
698 */
699 empty: function () {
700 if (!current.view) {
701 return;
702 }
703 current.view.$div.html('');
704 }
705 };
706
707 // ..........................................................
708 // Nav API
709 navApi = {
710
711 };
712
713 // ..........................................................
Simon Hunt1a9eff92014-11-07 11:06:34 -0800714 // Library API
715 libApi = {
716
717 };
718
719 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800720 // Exported API
721
Simon Hunt195cb382014-11-03 17:50:51 -0800722 // function to be called from index.html to build the ONOS UI
723 function buildOnosUi() {
724 tsB = new Date().getTime();
725 tsI = tsB - tsI; // initialization duration
726
727 console.log('ONOS UI initialized in ' + tsI + 'ms');
728
729 if (built) {
730 throwError("ONOS UI already built!");
731 }
732 built = true;
733
734 $view = d3.select('#view');
Simon Huntdb9eb072014-11-04 19:12:46 -0800735 $mastRadio = d3.select('#mastRadio');
Simon Hunt195cb382014-11-03 17:50:51 -0800736
737 $(window).on('hashchange', hash);
Simon Hunt25248912014-11-04 11:25:48 -0800738 $(window).on('resize', resize);
Simon Hunt195cb382014-11-03 17:50:51 -0800739
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800740 d3.select('body').on('keydown', keyIn);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800741 setupGlobalKeys();
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800742
Simon Hunt195cb382014-11-03 17:50:51 -0800743 // Invoke hashchange callback to navigate to content
744 // indicated by the window location hash.
745 hash();
746
747 // If there were any build errors, report them
748 reportBuildErrors();
749 }
750
Simon Hunt195cb382014-11-03 17:50:51 -0800751 // export the api and build-UI function
752 return {
Simon Hunt25248912014-11-04 11:25:48 -0800753 ui: uiApi,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800754 lib: libApi,
755 //view: viewApi,
Simon Hunt25248912014-11-04 11:25:48 -0800756 nav: navApi,
Simon Hunt195cb382014-11-03 17:50:51 -0800757 buildUi: buildOnosUi
758 };
759 };
760
Simon Huntdb9eb072014-11-04 19:12:46 -0800761}(jQuery));