blob: a0276dfd6a48741aef9bf8fe32d67b355ef920e3 [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: '',
Simon Hunt56d51852014-11-09 13:03:35 -080055 flags: {},
Thomas Vachuska65368e32014-11-08 16:10:20 -080056 theme: settings.theme
Simon Hunt195cb382014-11-03 17:50:51 -080057 },
58 built = false,
Simon Hunt0df1b1d2014-11-04 22:58:29 -080059 errorCount = 0,
Simon Hunt934c3ce2014-11-05 11:45:07 -080060 keyHandler = {
Thomas Vachuska65368e32014-11-08 16:10:20 -080061 globalKeys: {},
62 maskedKeys: {},
63 viewKeys: {},
64 viewFn: null
65 },
66 alerts = {
67 open: false,
68 count: 0
Simon Hunt934c3ce2014-11-05 11:45:07 -080069 };
Simon Hunt195cb382014-11-03 17:50:51 -080070
71 // DOM elements etc.
Simon Huntdb9eb072014-11-04 19:12:46 -080072 var $view,
73 $mastRadio;
Simon Hunt195cb382014-11-03 17:50:51 -080074
75
Simon Hunt0df1b1d2014-11-04 22:58:29 -080076 function whatKey(code) {
77 switch (code) {
78 case 13: return 'enter';
79 case 16: return 'shift';
80 case 17: return 'ctrl';
81 case 18: return 'alt';
82 case 27: return 'esc';
83 case 32: return 'space';
84 case 37: return 'leftArrow';
85 case 38: return 'upArrow';
86 case 39: return 'rightArrow';
87 case 40: return 'downArrow';
88 case 91: return 'cmdLeft';
89 case 93: return 'cmdRight';
90 default:
91 if ((code >= 48 && code <= 57) ||
92 (code >= 65 && code <= 90)) {
93 return String.fromCharCode(code);
94 } else if (code >= 112 && code <= 123) {
95 return 'F' + (code - 111);
96 }
97 return '.';
98 }
99 }
100
101
Simon Hunt195cb382014-11-03 17:50:51 -0800102 // ..........................................................
103 // Internal functions
104
105 // throw an error
106 function throwError(msg) {
107 // separate function, as we might add tracing here too, later
108 throw new Error(msg);
109 }
110
111 function doError(msg) {
112 errorCount++;
Simon Hunt25248912014-11-04 11:25:48 -0800113 console.error(msg);
Simon Hunt56d51852014-11-09 13:03:35 -0800114 doAlert(msg);
Simon Hunt25248912014-11-04 11:25:48 -0800115 }
116
117 function trace(msg) {
118 if (settings.trace) {
119 console.log(msg);
120 }
121 }
122
123 function traceFn(fn, params) {
124 if (settings.trace) {
125 console.log('*FN* ' + fn + '(...): ' + params);
126 }
Simon Hunt195cb382014-11-03 17:50:51 -0800127 }
128
129 // hash navigation
130 function hash() {
131 var hash = window.location.hash,
132 redo = false,
133 view,
134 t;
135
Simon Hunt25248912014-11-04 11:25:48 -0800136 traceFn('hash', hash);
137
Simon Hunt195cb382014-11-03 17:50:51 -0800138 if (!hash) {
Simon Hunt142d0032014-11-04 20:13:09 -0800139 hash = settings.startVid;
Simon Hunt195cb382014-11-03 17:50:51 -0800140 redo = true;
141 }
142
143 t = parseHash(hash);
144 if (!t || !t.vid) {
Simon Hunt56d51852014-11-09 13:03:35 -0800145 doError('Unable to parse target hash: "' + hash + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800146 }
147
148 view = views[t.vid];
149 if (!view) {
150 doError('No view defined with id: ' + t.vid);
151 }
152
153 if (redo) {
154 window.location.hash = makeHash(t);
155 // the above will result in a hashchange event, invoking
156 // this function again
157 } else {
158 // hash was not modified... navigate to where we need to be
159 navigate(hash, view, t);
160 }
Simon Hunt195cb382014-11-03 17:50:51 -0800161 }
162
163 function parseHash(s) {
164 // extract navigation coordinates from the supplied string
Simon Hunt56d51852014-11-09 13:03:35 -0800165 // "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
Simon Hunt25248912014-11-04 11:25:48 -0800166 traceFn('parseHash', s);
Simon Hunt195cb382014-11-03 17:50:51 -0800167
Simon Hunt56d51852014-11-09 13:03:35 -0800168 // look for use of flags, first
169 var vidctx,
170 vid,
171 ctx,
172 flags,
173 flagMap,
174 m;
175
176 // RE that includes flags ('?flag1,flag2')
177 m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
Simon Hunt195cb382014-11-03 17:50:51 -0800178 if (m) {
Simon Hunt56d51852014-11-09 13:03:35 -0800179 vidctx = m[1];
180 flags = m[2];
181 flagMap = {};
182 } else {
183 // no flags
184 m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
185 if (m) {
186 vidctx = m[1];
187 } else {
188 // bad hash
189 return null;
190 }
Simon Hunt195cb382014-11-03 17:50:51 -0800191 }
192
Simon Hunt56d51852014-11-09 13:03:35 -0800193 vidctx = vidctx.split(',');
194 vid = vidctx[0];
195 ctx = vidctx[1];
196 if (flags) {
197 flags.split(',').forEach(function (f) {
198 flagMap[f.trim()] = true;
199 });
200 }
201
202 return {
203 vid: vid.trim(),
204 ctx: ctx ? ctx.trim() : '',
205 flags: flagMap
206 };
207
Simon Hunt195cb382014-11-03 17:50:51 -0800208 }
209
Simon Hunt56d51852014-11-09 13:03:35 -0800210 function makeHash(t, ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800211 traceFn('makeHash');
Simon Hunt56d51852014-11-09 13:03:35 -0800212 // make a hash string from the given navigation coordinates,
213 // and optional flags map.
Simon Hunt195cb382014-11-03 17:50:51 -0800214 // if t is not an object, then it is a vid
215 var h = t,
Simon Hunt56d51852014-11-09 13:03:35 -0800216 c = ctx || '',
217 f = $.isPlainObject(flags) ? flags : null;
Simon Hunt195cb382014-11-03 17:50:51 -0800218
219 if ($.isPlainObject(t)) {
220 h = t.vid;
221 c = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800222 f = t.flags || null;
Simon Hunt195cb382014-11-03 17:50:51 -0800223 }
224
225 if (c) {
226 h += ',' + c;
227 }
Simon Hunt56d51852014-11-09 13:03:35 -0800228 if (f) {
229 h += '?' + d3.map(f).keys().join(',');
230 }
Simon Hunt25248912014-11-04 11:25:48 -0800231 trace('hash = "' + h + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800232 return h;
233 }
234
235 function navigate(hash, view, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800236 traceFn('navigate', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800237 // closePanes() // flyouts etc.
Simon Hunt25248912014-11-04 11:25:48 -0800238 // updateNav() // accordion / selected nav item etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800239 createView(view);
240 setView(view, hash, t);
241 }
242
243 function reportBuildErrors() {
Simon Hunt25248912014-11-04 11:25:48 -0800244 traceFn('reportBuildErrors');
Simon Hunt195cb382014-11-03 17:50:51 -0800245 // TODO: validate registered views / nav-item linkage etc.
246 console.log('(no build errors)');
247 }
248
Simon Hunt25248912014-11-04 11:25:48 -0800249 // returns the reference if it is a function, null otherwise
250 function isF(f) {
251 return $.isFunction(f) ? f : null;
252 }
253
Simon Hunt195cb382014-11-03 17:50:51 -0800254 // ..........................................................
255 // View life-cycle functions
256
Simon Hunt25248912014-11-04 11:25:48 -0800257 function setViewDimensions(sel) {
258 var w = window.innerWidth,
259 h = window.innerHeight - mastHeight;
260 sel.each(function () {
261 $(this)
262 .css('width', w + 'px')
263 .css('height', h + 'px')
264 });
265 }
266
Simon Hunt195cb382014-11-03 17:50:51 -0800267 function createView(view) {
268 var $d;
Simon Hunt25248912014-11-04 11:25:48 -0800269
Simon Hunt195cb382014-11-03 17:50:51 -0800270 // lazy initialization of the view
271 if (view && !view.$div) {
Simon Hunt25248912014-11-04 11:25:48 -0800272 trace('creating view for ' + view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800273 $d = $view.append('div')
274 .attr({
Simon Hunt25248912014-11-04 11:25:48 -0800275 id: view.vid,
276 class: 'onosView'
Simon Hunt195cb382014-11-03 17:50:51 -0800277 });
Simon Hunt25248912014-11-04 11:25:48 -0800278 setViewDimensions($d);
279 view.$div = $d; // cache a reference to the D3 selection
Simon Hunt195cb382014-11-03 17:50:51 -0800280 }
281 }
282
283 function setView(view, hash, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800284 traceFn('setView', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800285 // set the specified view as current, while invoking the
286 // appropriate life-cycle callbacks
287
Simon Hunt56d51852014-11-09 13:03:35 -0800288 // first, we'll start by closing the alerts pane, if open
289 closeAlerts();
290
Simon Hunt195cb382014-11-03 17:50:51 -0800291 // if there is a current view, and it is not the same as
292 // the incoming view, then unload it...
Simon Hunt25248912014-11-04 11:25:48 -0800293 if (current.view && (current.view.vid !== view.vid)) {
Simon Hunt195cb382014-11-03 17:50:51 -0800294 current.view.unload();
Simon Huntdb9eb072014-11-04 19:12:46 -0800295
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800296 // detach radio buttons, key handlers, etc.
297 $('#mastRadio').children().detach();
Thomas Vachuska65368e32014-11-08 16:10:20 -0800298 keyHandler.viewKeys = {};
299 keyHandler.viewFn = null;
Simon Hunt195cb382014-11-03 17:50:51 -0800300 }
301
302 // cache new view and context
303 current.view = view;
304 current.ctx = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800305 current.flags = t.flags || {};
Simon Hunt195cb382014-11-03 17:50:51 -0800306
Simon Hunt195cb382014-11-03 17:50:51 -0800307 // preload is called only once, after the view is in the DOM
308 if (!view.preloaded) {
Simon Hunt56d51852014-11-09 13:03:35 -0800309 view.preload(current.ctx, current.flags);
Simon Hunt25248912014-11-04 11:25:48 -0800310 view.preloaded = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800311 }
312
313 // clear the view of stale data
314 view.reset();
315
316 // load the view
Simon Hunt56d51852014-11-09 13:03:35 -0800317 view.load(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800318 }
319
Simon Huntdb9eb072014-11-04 19:12:46 -0800320 // generate 'unique' id by prefixing view id
Simon Hunt934c3ce2014-11-05 11:45:07 -0800321 function makeUid(view, id) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800322 return view.vid + '-' + id;
323 }
324
325 // restore id by removing view id prefix
Simon Hunt934c3ce2014-11-05 11:45:07 -0800326 function unmakeUid(view, uid) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800327 var re = new RegExp('^' + view.vid + '-');
328 return uid.replace(re, '');
329 }
330
Simon Hunt934c3ce2014-11-05 11:45:07 -0800331 function setRadioButtons(vid, btnSet) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800332 var view = views[vid],
333 btnG;
334
335 // lazily create the buttons...
336 if (!(btnG = view.radioButtons)) {
337 btnG = d3.select(document.createElement('div'));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800338 btnG.buttonDef = {};
Simon Huntdb9eb072014-11-04 19:12:46 -0800339
340 btnSet.forEach(function (btn, i) {
341 var bid = btn.id || 'b' + i,
342 txt = btn.text || 'Button #' + i,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800343 uid = makeUid(view, bid),
344 button = btnG.append('span')
Simon Huntdb9eb072014-11-04 19:12:46 -0800345 .attr({
Simon Hunt934c3ce2014-11-05 11:45:07 -0800346 id: uid,
Simon Huntdb9eb072014-11-04 19:12:46 -0800347 class: 'radio'
348 })
349 .text(txt);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800350
351 btnG.buttonDef[uid] = btn;
352
Simon Huntdb9eb072014-11-04 19:12:46 -0800353 if (i === 0) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800354 button.classed('active', true);
Simon Huntdb9eb072014-11-04 19:12:46 -0800355 }
356 });
357
358 btnG.selectAll('span')
359 .on('click', function (d) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800360 var button = d3.select(this),
361 uid = button.attr('id'),
362 btn = btnG.buttonDef[uid],
363 act = button.classed('active');
Simon Huntdb9eb072014-11-04 19:12:46 -0800364
365 if (!act) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800366 btnG.selectAll('span').classed('active', false);
367 button.classed('active', true);
368 if (isF(btn.cb)) {
369 btn.cb(view.token(), btn);
370 }
Simon Huntdb9eb072014-11-04 19:12:46 -0800371 }
372 });
373
374 view.radioButtons = btnG;
375 }
376
377 // attach the buttons to the masthead
378 $mastRadio.node().appendChild(btnG.node());
379 }
380
Thomas Vachuska65368e32014-11-08 16:10:20 -0800381 function setupGlobalKeys() {
382 keyHandler.globalKeys = {
383 esc: escapeKey,
384 T: toggleTheme
385 };
386 // Masked keys are global key handlers that always return true.
387 // That is, the view will never see the event for that key.
388 keyHandler.maskedKeys = {
389 T: true
390 };
391 }
392
393 function escapeKey(view, key, code, ev) {
394 if (alerts.open) {
395 closeAlerts();
396 return true;
397 }
398 return false;
399 }
400
401 function toggleTheme(view, key, code, ev) {
402 var body = d3.select('body');
403 current.theme = (current.theme === 'light') ? 'dark' : 'light';
404 body.classed('light dark', false);
405 body.classed(current.theme, true);
406 return true;
407 }
408
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800409 function setKeyBindings(keyArg) {
Thomas Vachuska65368e32014-11-08 16:10:20 -0800410 var viewKeys,
411 masked = [];
412
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800413 if ($.isFunction(keyArg)) {
414 // set general key handler callback
Thomas Vachuska65368e32014-11-08 16:10:20 -0800415 keyHandler.viewFn = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800416 } else {
417 // set specific key filter map
Thomas Vachuska65368e32014-11-08 16:10:20 -0800418 viewKeys = d3.map(keyArg).keys();
419 viewKeys.forEach(function (key) {
420 if (keyHandler.maskedKeys[key]) {
421 masked.push(' Key "' + key + '" is reserved');
422 }
423 });
424
425 if (masked.length) {
426 doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
427 }
428 keyHandler.viewKeys = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800429 }
430 }
431
Thomas Vachuska65368e32014-11-08 16:10:20 -0800432 function keyIn() {
433 var event = d3.event,
434 keyCode = event.keyCode,
435 key = whatKey(keyCode),
436 gcb = isF(keyHandler.globalKeys[key]),
437 vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
438
439 // global callback?
440 if (gcb && gcb(current.view.token(), key, keyCode, event)) {
441 // if the event was 'handled', we are done
442 return;
443 }
444 // otherwise, let the view callback have a shot
445 if (vcb) {
446 vcb(current.view.token(), key, keyCode, event);
447 }
448 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800449
450 function createAlerts() {
451 var al = d3.select('#alerts')
452 .style('display', 'block');
453 al.append('span')
454 .attr('class', 'close')
455 .text('X')
456 .on('click', closeAlerts);
457 al.append('pre');
Thomas Vachuska65368e32014-11-08 16:10:20 -0800458 al.append('p').attr('class', 'footnote')
459 .text('Press ESCAPE to close');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800460 alerts.open = true;
461 alerts.count = 0;
462 }
463
464 function closeAlerts() {
465 d3.select('#alerts')
Thomas Vachuska65368e32014-11-08 16:10:20 -0800466 .style('display', 'none')
467 .html('');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800468 alerts.open = false;
469 }
470
471 function addAlert(msg) {
472 var lines,
473 oldContent;
474
475 if (alerts.count) {
476 oldContent = d3.select('#alerts pre').html();
477 }
478
479 lines = msg.split('\n');
480 lines[0] += ' '; // spacing so we don't crowd 'X'
481 lines = lines.join('\n');
482
483 if (oldContent) {
484 lines += '\n----\n' + oldContent;
485 }
486
487 d3.select('#alerts pre').html(lines);
488 alerts.count++;
489 }
490
491 function doAlert(msg) {
492 if (!alerts.open) {
493 createAlerts();
494 }
495 addAlert(msg);
496 }
497
Simon Hunt25248912014-11-04 11:25:48 -0800498 function resize(e) {
499 d3.selectAll('.onosView').call(setViewDimensions);
500 // allow current view to react to resize event...
Simon Hunt195cb382014-11-03 17:50:51 -0800501 if (current.view) {
Simon Hunt56d51852014-11-09 13:03:35 -0800502 current.view.resize(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800503 }
504 }
505
506 // ..........................................................
507 // View class
508 // Captures state information about a view.
509
510 // Constructor
511 // vid : view id
512 // nid : id of associated nav-item (optional)
Simon Hunt25248912014-11-04 11:25:48 -0800513 // cb : callbacks (preload, reset, load, unload, resize, error)
Simon Hunt195cb382014-11-03 17:50:51 -0800514 function View(vid) {
515 var av = 'addView(): ',
516 args = Array.prototype.slice.call(arguments),
517 nid,
Simon Hunt25248912014-11-04 11:25:48 -0800518 cb;
Simon Hunt195cb382014-11-03 17:50:51 -0800519
520 args.shift(); // first arg is always vid
521 if (typeof args[0] === 'string') { // nid specified
522 nid = args.shift();
523 }
524 cb = args.shift();
Simon Hunt195cb382014-11-03 17:50:51 -0800525
526 this.vid = vid;
527
528 if (validateViewArgs(vid)) {
529 this.nid = nid; // explicit navitem id (can be null)
530 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
Simon Huntdb9eb072014-11-04 19:12:46 -0800531 this.$div = null; // view not yet added to DOM
532 this.radioButtons = null; // no radio buttons yet
533 this.ok = true; // valid view
Simon Hunt195cb382014-11-03 17:50:51 -0800534 }
Simon Hunt195cb382014-11-03 17:50:51 -0800535 }
536
537 function validateViewArgs(vid) {
Simon Hunt25248912014-11-04 11:25:48 -0800538 var av = "ui.addView(...): ",
539 ok = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800540 if (typeof vid !== 'string' || !vid) {
541 doError(av + 'vid required');
542 } else if (views[vid]) {
543 doError(av + 'View ID "' + vid + '" already exists');
544 } else {
545 ok = true;
546 }
547 return ok;
548 }
549
550 var viewInstanceMethods = {
Simon Hunt25248912014-11-04 11:25:48 -0800551 token: function () {
Simon Hunt195cb382014-11-03 17:50:51 -0800552 return {
Simon Hunt25248912014-11-04 11:25:48 -0800553 // attributes
Simon Hunt195cb382014-11-03 17:50:51 -0800554 vid: this.vid,
555 nid: this.nid,
Simon Hunt25248912014-11-04 11:25:48 -0800556 $div: this.$div,
557
558 // functions
559 width: this.width,
Simon Huntdb9eb072014-11-04 19:12:46 -0800560 height: this.height,
Simon Hunt142d0032014-11-04 20:13:09 -0800561 uid: this.uid,
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800562 setRadio: this.setRadio,
Simon Huntc7ee0662014-11-05 16:44:37 -0800563 setKeys: this.setKeys,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800564 dataLoadError: this.dataLoadError,
565 alert: this.alert
Simon Hunt195cb382014-11-03 17:50:51 -0800566 }
Simon Hunt25248912014-11-04 11:25:48 -0800567 },
568
Simon Hunt56d51852014-11-09 13:03:35 -0800569 preload: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800570 var c = ctx || '',
571 fn = isF(this.cb.preload);
572 traceFn('View.preload', this.vid + ', ' + c);
573 if (fn) {
574 trace('PRELOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800575 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800576 }
577 },
578
579 reset: function () {
580 var fn = isF(this.cb.reset);
581 traceFn('View.reset', this.vid);
582 if (fn) {
583 trace('RESET cb for ' + this.vid);
584 fn(this.token());
585 } else if (this.cb.reset === true) {
586 // boolean true signifies "clear view"
587 trace(' [true] cleaing view...');
588 viewApi.empty();
589 }
590 },
591
Simon Hunt56d51852014-11-09 13:03:35 -0800592 load: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800593 var c = ctx || '',
594 fn = isF(this.cb.load);
595 traceFn('View.load', this.vid + ', ' + c);
596 this.$div.classed('currentView', true);
Simon Hunt25248912014-11-04 11:25:48 -0800597 if (fn) {
598 trace('LOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800599 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800600 }
601 },
602
603 unload: function () {
604 var fn = isF(this.cb.unload);
605 traceFn('View.unload', this.vid);
606 this.$div.classed('currentView', false);
Simon Hunt25248912014-11-04 11:25:48 -0800607 if (fn) {
608 trace('UNLOAD cb for ' + this.vid);
609 fn(this.token());
610 }
611 },
612
Simon Hunt56d51852014-11-09 13:03:35 -0800613 resize: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800614 var c = ctx || '',
615 fn = isF(this.cb.resize),
616 w = this.width(),
617 h = this.height();
618 traceFn('View.resize', this.vid + '/' + c +
619 ' [' + w + 'x' + h + ']');
620 if (fn) {
621 trace('RESIZE cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800622 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800623 }
624 },
625
626 error: function (ctx) {
627 var c = ctx || '',
628 fn = isF(this.cb.error);
629 traceFn('View.error', this.vid + ', ' + c);
630 if (fn) {
631 trace('ERROR cb for ' + this.vid);
632 fn(this.token(), c);
633 }
634 },
635
636 width: function () {
637 return $(this.$div.node()).width();
638 },
639
640 height: function () {
641 return $(this.$div.node()).height();
Simon Huntdb9eb072014-11-04 19:12:46 -0800642 },
Simon Hunt25248912014-11-04 11:25:48 -0800643
Simon Hunt934c3ce2014-11-05 11:45:07 -0800644 setRadio: function (btnSet) {
645 setRadioButtons(this.vid, btnSet);
Simon Hunt142d0032014-11-04 20:13:09 -0800646 },
647
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800648 setKeys: function (keyArg) {
649 setKeyBindings(keyArg);
650 },
651
Simon Hunt142d0032014-11-04 20:13:09 -0800652 uid: function (id) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800653 return makeUid(this, id);
Simon Huntc7ee0662014-11-05 16:44:37 -0800654 },
655
Simon Hunt1a9eff92014-11-07 11:06:34 -0800656 // TODO : implement custom dialogs
657
658 // Consider enhancing alert mechanism to handle multiples
659 // as individually closable.
660 alert: function (msg) {
661 doAlert(msg);
662 },
Simon Huntc7ee0662014-11-05 16:44:37 -0800663
664 dataLoadError: function (err, url) {
665 var msg = 'Data Load Error\n\n' +
666 err.status + ' -- ' + err.statusText + '\n\n' +
667 'relative-url: "' + url + '"\n\n' +
668 'complete-url: "' + err.responseURL + '"';
Simon Hunt1a9eff92014-11-07 11:06:34 -0800669 this.alert(msg);
Simon Huntdb9eb072014-11-04 19:12:46 -0800670 }
Simon Hunt25248912014-11-04 11:25:48 -0800671
672 // TODO: consider schedule, clearTimer, etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800673 };
674
675 // attach instance methods to the view prototype
676 $.extend(View.prototype, viewInstanceMethods);
677
678 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800679 // UI API
Simon Hunt195cb382014-11-03 17:50:51 -0800680
Simon Hunt25248912014-11-04 11:25:48 -0800681 uiApi = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800682 addLib: function (libName, api) {
683 // TODO: validation of args
684 libApi[libName] = api;
685 },
686
687 // TODO: it remains to be seen whether we keep this style of docs
Simon Hunt25248912014-11-04 11:25:48 -0800688 /** @api ui addView( vid, nid, cb )
689 * Adds a view to the UI.
690 * <p>
691 * Views are loaded/unloaded into the view content pane at
692 * appropriate times, by the navigation framework. This method
693 * adds a view to the UI and returns a token object representing
694 * the view. A view's token is always passed as the first
695 * argument to each of the view's life-cycle callback functions.
696 * <p>
697 * Note that if the view is directly referenced by a nav-item,
698 * or in a group of views with one of those views referenced by
699 * a nav-item, then the <i>nid</i> argument can be omitted as
700 * the framework can infer it.
701 * <p>
702 * <i>cb</i> is a plain object containing callback functions:
703 * "preload", "reset", "load", "unload", "resize", "error".
704 * <pre>
705 * function myLoad(view, ctx) { ... }
706 * ...
707 * // short form...
708 * onos.ui.addView('viewId', {
709 * load: myLoad
710 * });
711 * </pre>
712 *
713 * @param vid (string) [*] view ID (a unique DOM element id)
714 * @param nid (string) nav-item ID (a unique DOM element id)
715 * @param cb (object) [*] callbacks object
716 * @return the view token
717 */
718 addView: function (vid, nid, cb) {
719 traceFn('addView', vid);
720 var view = new View(vid, nid, cb),
Simon Hunt195cb382014-11-03 17:50:51 -0800721 token;
722 if (view.ok) {
723 views[vid] = view;
724 token = view.token();
725 } else {
726 token = { vid: view.vid, bad: true };
727 }
728 return token;
729 }
730 };
731
Simon Hunt25248912014-11-04 11:25:48 -0800732 // ..........................................................
733 // View API
734
735 viewApi = {
736 /** @api view empty( )
737 * Empties the current view.
738 * <p>
739 * More specifically, removes all DOM elements from the
740 * current view's display div.
741 */
742 empty: function () {
743 if (!current.view) {
744 return;
745 }
746 current.view.$div.html('');
747 }
748 };
749
750 // ..........................................................
751 // Nav API
752 navApi = {
753
754 };
755
756 // ..........................................................
Simon Hunt1a9eff92014-11-07 11:06:34 -0800757 // Library API
758 libApi = {
759
760 };
761
762 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800763 // Exported API
764
Simon Hunt195cb382014-11-03 17:50:51 -0800765 // function to be called from index.html to build the ONOS UI
766 function buildOnosUi() {
767 tsB = new Date().getTime();
768 tsI = tsB - tsI; // initialization duration
769
770 console.log('ONOS UI initialized in ' + tsI + 'ms');
771
772 if (built) {
773 throwError("ONOS UI already built!");
774 }
775 built = true;
776
777 $view = d3.select('#view');
Simon Huntdb9eb072014-11-04 19:12:46 -0800778 $mastRadio = d3.select('#mastRadio');
Simon Hunt195cb382014-11-03 17:50:51 -0800779
780 $(window).on('hashchange', hash);
Simon Hunt25248912014-11-04 11:25:48 -0800781 $(window).on('resize', resize);
Simon Hunt195cb382014-11-03 17:50:51 -0800782
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800783 d3.select('body').on('keydown', keyIn);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800784 setupGlobalKeys();
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800785
Simon Hunt195cb382014-11-03 17:50:51 -0800786 // Invoke hashchange callback to navigate to content
787 // indicated by the window location hash.
788 hash();
789
790 // If there were any build errors, report them
791 reportBuildErrors();
792 }
793
Simon Hunt195cb382014-11-03 17:50:51 -0800794 // export the api and build-UI function
795 return {
Simon Hunt25248912014-11-04 11:25:48 -0800796 ui: uiApi,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800797 lib: libApi,
798 //view: viewApi,
Simon Hunt25248912014-11-04 11:25:48 -0800799 nav: navApi,
Simon Hunt195cb382014-11-03 17:50:51 -0800800 buildUi: buildOnosUi
801 };
802 };
803
Simon Huntdb9eb072014-11-04 19:12:46 -0800804}(jQuery));