blob: 0644c322bcd5b92181a649c100c3ac51cd559b5d [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 = {},
Simon Hunt61d04042014-11-11 17:27:16 -080053 fpanels = {},
Simon Hunt195cb382014-11-03 17:50:51 -080054 current = {
55 view: null,
Thomas Vachuska65368e32014-11-08 16:10:20 -080056 ctx: '',
Simon Hunt56d51852014-11-09 13:03:35 -080057 flags: {},
Thomas Vachuska65368e32014-11-08 16:10:20 -080058 theme: settings.theme
Simon Hunt195cb382014-11-03 17:50:51 -080059 },
60 built = false,
Simon Hunt61d04042014-11-11 17:27:16 -080061 buildErrors = [],
Simon Hunt934c3ce2014-11-05 11:45:07 -080062 keyHandler = {
Thomas Vachuska65368e32014-11-08 16:10:20 -080063 globalKeys: {},
64 maskedKeys: {},
65 viewKeys: {},
66 viewFn: null
67 },
68 alerts = {
69 open: false,
70 count: 0
Simon Hunt934c3ce2014-11-05 11:45:07 -080071 };
Simon Hunt195cb382014-11-03 17:50:51 -080072
73 // DOM elements etc.
Simon Hunt61d04042014-11-11 17:27:16 -080074 // TODO: verify existence of following elements...
75 var $view = d3.select('#view'),
76 $floatPanels = d3.select('#floatPanels'),
77 $alerts = d3.select('#alerts'),
78 // note, following elements added programmatically...
Simon Huntdb9eb072014-11-04 19:12:46 -080079 $mastRadio;
Simon Hunt195cb382014-11-03 17:50:51 -080080
81
Simon Hunt0df1b1d2014-11-04 22:58:29 -080082 function whatKey(code) {
83 switch (code) {
84 case 13: return 'enter';
85 case 16: return 'shift';
86 case 17: return 'ctrl';
87 case 18: return 'alt';
88 case 27: return 'esc';
89 case 32: return 'space';
90 case 37: return 'leftArrow';
91 case 38: return 'upArrow';
92 case 39: return 'rightArrow';
93 case 40: return 'downArrow';
94 case 91: return 'cmdLeft';
95 case 93: return 'cmdRight';
96 default:
97 if ((code >= 48 && code <= 57) ||
98 (code >= 65 && code <= 90)) {
99 return String.fromCharCode(code);
100 } else if (code >= 112 && code <= 123) {
101 return 'F' + (code - 111);
102 }
103 return '.';
104 }
105 }
106
107
Simon Hunt195cb382014-11-03 17:50:51 -0800108 // ..........................................................
109 // Internal functions
110
111 // throw an error
112 function throwError(msg) {
113 // separate function, as we might add tracing here too, later
114 throw new Error(msg);
115 }
116
117 function doError(msg) {
118 errorCount++;
Simon Hunt25248912014-11-04 11:25:48 -0800119 console.error(msg);
Simon Hunt56d51852014-11-09 13:03:35 -0800120 doAlert(msg);
Simon Hunt25248912014-11-04 11:25:48 -0800121 }
122
123 function trace(msg) {
124 if (settings.trace) {
125 console.log(msg);
126 }
127 }
128
129 function traceFn(fn, params) {
130 if (settings.trace) {
131 console.log('*FN* ' + fn + '(...): ' + params);
132 }
Simon Hunt195cb382014-11-03 17:50:51 -0800133 }
134
135 // hash navigation
136 function hash() {
137 var hash = window.location.hash,
138 redo = false,
139 view,
140 t;
141
Simon Hunt25248912014-11-04 11:25:48 -0800142 traceFn('hash', hash);
143
Simon Hunt195cb382014-11-03 17:50:51 -0800144 if (!hash) {
Simon Hunt142d0032014-11-04 20:13:09 -0800145 hash = settings.startVid;
Simon Hunt195cb382014-11-03 17:50:51 -0800146 redo = true;
147 }
148
149 t = parseHash(hash);
150 if (!t || !t.vid) {
Simon Hunt56d51852014-11-09 13:03:35 -0800151 doError('Unable to parse target hash: "' + hash + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800152 }
153
154 view = views[t.vid];
155 if (!view) {
156 doError('No view defined with id: ' + t.vid);
157 }
158
159 if (redo) {
160 window.location.hash = makeHash(t);
161 // the above will result in a hashchange event, invoking
162 // this function again
163 } else {
164 // hash was not modified... navigate to where we need to be
165 navigate(hash, view, t);
166 }
Simon Hunt195cb382014-11-03 17:50:51 -0800167 }
168
169 function parseHash(s) {
170 // extract navigation coordinates from the supplied string
Simon Hunt56d51852014-11-09 13:03:35 -0800171 // "vid,ctx?flag1,flag2" --> { vid:vid, ctx:ctx, flags:{...} }
Simon Hunt25248912014-11-04 11:25:48 -0800172 traceFn('parseHash', s);
Simon Hunt195cb382014-11-03 17:50:51 -0800173
Simon Hunt56d51852014-11-09 13:03:35 -0800174 // look for use of flags, first
175 var vidctx,
176 vid,
177 ctx,
178 flags,
179 flagMap,
180 m;
181
182 // RE that includes flags ('?flag1,flag2')
183 m = /^[#]{0,1}(.+)\?(.+)$/.exec(s);
Simon Hunt195cb382014-11-03 17:50:51 -0800184 if (m) {
Simon Hunt56d51852014-11-09 13:03:35 -0800185 vidctx = m[1];
186 flags = m[2];
187 flagMap = {};
188 } else {
189 // no flags
190 m = /^[#]{0,1}((.+)(,.+)*)$/.exec(s);
191 if (m) {
192 vidctx = m[1];
193 } else {
194 // bad hash
195 return null;
196 }
Simon Hunt195cb382014-11-03 17:50:51 -0800197 }
198
Simon Hunt56d51852014-11-09 13:03:35 -0800199 vidctx = vidctx.split(',');
200 vid = vidctx[0];
201 ctx = vidctx[1];
202 if (flags) {
203 flags.split(',').forEach(function (f) {
204 flagMap[f.trim()] = true;
205 });
206 }
207
208 return {
209 vid: vid.trim(),
210 ctx: ctx ? ctx.trim() : '',
211 flags: flagMap
212 };
213
Simon Hunt195cb382014-11-03 17:50:51 -0800214 }
215
Simon Hunt56d51852014-11-09 13:03:35 -0800216 function makeHash(t, ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800217 traceFn('makeHash');
Simon Hunt56d51852014-11-09 13:03:35 -0800218 // make a hash string from the given navigation coordinates,
219 // and optional flags map.
Simon Hunt195cb382014-11-03 17:50:51 -0800220 // if t is not an object, then it is a vid
221 var h = t,
Simon Hunt56d51852014-11-09 13:03:35 -0800222 c = ctx || '',
223 f = $.isPlainObject(flags) ? flags : null;
Simon Hunt195cb382014-11-03 17:50:51 -0800224
225 if ($.isPlainObject(t)) {
226 h = t.vid;
227 c = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800228 f = t.flags || null;
Simon Hunt195cb382014-11-03 17:50:51 -0800229 }
230
231 if (c) {
232 h += ',' + c;
233 }
Simon Hunt56d51852014-11-09 13:03:35 -0800234 if (f) {
235 h += '?' + d3.map(f).keys().join(',');
236 }
Simon Hunt25248912014-11-04 11:25:48 -0800237 trace('hash = "' + h + '"');
Simon Hunt195cb382014-11-03 17:50:51 -0800238 return h;
239 }
240
241 function navigate(hash, view, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800242 traceFn('navigate', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800243 // closePanes() // flyouts etc.
Simon Hunt25248912014-11-04 11:25:48 -0800244 // updateNav() // accordion / selected nav item etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800245 createView(view);
246 setView(view, hash, t);
247 }
248
Simon Hunt61d04042014-11-11 17:27:16 -0800249 function buildError(msg) {
250 buildErrors.push(msg);
251 }
252
Simon Hunt195cb382014-11-03 17:50:51 -0800253 function reportBuildErrors() {
Simon Hunt25248912014-11-04 11:25:48 -0800254 traceFn('reportBuildErrors');
Simon Hunt61d04042014-11-11 17:27:16 -0800255 var nerr = buildErrors.length,
256 errmsg;
257 if (!nerr) {
258 console.log('(no build errors)');
259 } else {
260 errmsg = 'Build errors: ' + nerr + ' found...\n\n' +
261 buildErrors.join('\n');
262 doAlert(errmsg);
263 console.error(errmsg);
264 }
Simon Hunt195cb382014-11-03 17:50:51 -0800265 }
266
Simon Hunt25248912014-11-04 11:25:48 -0800267 // returns the reference if it is a function, null otherwise
268 function isF(f) {
269 return $.isFunction(f) ? f : null;
270 }
271
Simon Hunt195cb382014-11-03 17:50:51 -0800272 // ..........................................................
273 // View life-cycle functions
274
Simon Hunt25248912014-11-04 11:25:48 -0800275 function setViewDimensions(sel) {
276 var w = window.innerWidth,
277 h = window.innerHeight - mastHeight;
278 sel.each(function () {
279 $(this)
280 .css('width', w + 'px')
281 .css('height', h + 'px')
282 });
283 }
284
Simon Hunt195cb382014-11-03 17:50:51 -0800285 function createView(view) {
286 var $d;
Simon Hunt25248912014-11-04 11:25:48 -0800287
Simon Hunt195cb382014-11-03 17:50:51 -0800288 // lazy initialization of the view
289 if (view && !view.$div) {
Simon Hunt25248912014-11-04 11:25:48 -0800290 trace('creating view for ' + view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800291 $d = $view.append('div')
292 .attr({
Simon Hunt25248912014-11-04 11:25:48 -0800293 id: view.vid,
294 class: 'onosView'
Simon Hunt195cb382014-11-03 17:50:51 -0800295 });
Simon Hunt25248912014-11-04 11:25:48 -0800296 setViewDimensions($d);
297 view.$div = $d; // cache a reference to the D3 selection
Simon Hunt195cb382014-11-03 17:50:51 -0800298 }
299 }
300
301 function setView(view, hash, t) {
Simon Hunt25248912014-11-04 11:25:48 -0800302 traceFn('setView', view.vid);
Simon Hunt195cb382014-11-03 17:50:51 -0800303 // set the specified view as current, while invoking the
304 // appropriate life-cycle callbacks
305
Simon Hunt56d51852014-11-09 13:03:35 -0800306 // first, we'll start by closing the alerts pane, if open
307 closeAlerts();
308
Simon Hunt195cb382014-11-03 17:50:51 -0800309 // if there is a current view, and it is not the same as
310 // the incoming view, then unload it...
Simon Hunt25248912014-11-04 11:25:48 -0800311 if (current.view && (current.view.vid !== view.vid)) {
Simon Hunt195cb382014-11-03 17:50:51 -0800312 current.view.unload();
Simon Huntdb9eb072014-11-04 19:12:46 -0800313
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800314 // detach radio buttons, key handlers, etc.
315 $('#mastRadio').children().detach();
Thomas Vachuska65368e32014-11-08 16:10:20 -0800316 keyHandler.viewKeys = {};
317 keyHandler.viewFn = null;
Simon Hunt195cb382014-11-03 17:50:51 -0800318 }
319
320 // cache new view and context
321 current.view = view;
322 current.ctx = t.ctx || '';
Simon Hunt56d51852014-11-09 13:03:35 -0800323 current.flags = t.flags || {};
Simon Hunt195cb382014-11-03 17:50:51 -0800324
Simon Hunt195cb382014-11-03 17:50:51 -0800325 // preload is called only once, after the view is in the DOM
326 if (!view.preloaded) {
Simon Hunt56d51852014-11-09 13:03:35 -0800327 view.preload(current.ctx, current.flags);
Simon Hunt25248912014-11-04 11:25:48 -0800328 view.preloaded = true;
Simon Hunt195cb382014-11-03 17:50:51 -0800329 }
330
331 // clear the view of stale data
332 view.reset();
333
334 // load the view
Simon Hunt56d51852014-11-09 13:03:35 -0800335 view.load(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800336 }
337
Simon Huntdb9eb072014-11-04 19:12:46 -0800338 // generate 'unique' id by prefixing view id
Simon Hunt934c3ce2014-11-05 11:45:07 -0800339 function makeUid(view, id) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800340 return view.vid + '-' + id;
341 }
342
343 // restore id by removing view id prefix
Simon Hunt934c3ce2014-11-05 11:45:07 -0800344 function unmakeUid(view, uid) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800345 var re = new RegExp('^' + view.vid + '-');
346 return uid.replace(re, '');
347 }
348
Simon Hunt934c3ce2014-11-05 11:45:07 -0800349 function setRadioButtons(vid, btnSet) {
Simon Huntdb9eb072014-11-04 19:12:46 -0800350 var view = views[vid],
351 btnG;
352
353 // lazily create the buttons...
354 if (!(btnG = view.radioButtons)) {
355 btnG = d3.select(document.createElement('div'));
Simon Hunt934c3ce2014-11-05 11:45:07 -0800356 btnG.buttonDef = {};
Simon Huntdb9eb072014-11-04 19:12:46 -0800357
358 btnSet.forEach(function (btn, i) {
359 var bid = btn.id || 'b' + i,
360 txt = btn.text || 'Button #' + i,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800361 uid = makeUid(view, bid),
362 button = btnG.append('span')
Simon Huntdb9eb072014-11-04 19:12:46 -0800363 .attr({
Simon Hunt934c3ce2014-11-05 11:45:07 -0800364 id: uid,
Simon Huntdb9eb072014-11-04 19:12:46 -0800365 class: 'radio'
366 })
367 .text(txt);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800368
369 btnG.buttonDef[uid] = btn;
370
Simon Huntdb9eb072014-11-04 19:12:46 -0800371 if (i === 0) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800372 button.classed('active', true);
Simon Huntdb9eb072014-11-04 19:12:46 -0800373 }
374 });
375
376 btnG.selectAll('span')
377 .on('click', function (d) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800378 var button = d3.select(this),
379 uid = button.attr('id'),
380 btn = btnG.buttonDef[uid],
381 act = button.classed('active');
Simon Huntdb9eb072014-11-04 19:12:46 -0800382
383 if (!act) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800384 btnG.selectAll('span').classed('active', false);
385 button.classed('active', true);
386 if (isF(btn.cb)) {
387 btn.cb(view.token(), btn);
388 }
Simon Huntdb9eb072014-11-04 19:12:46 -0800389 }
390 });
391
392 view.radioButtons = btnG;
393 }
394
395 // attach the buttons to the masthead
396 $mastRadio.node().appendChild(btnG.node());
397 }
398
Thomas Vachuska65368e32014-11-08 16:10:20 -0800399 function setupGlobalKeys() {
400 keyHandler.globalKeys = {
401 esc: escapeKey,
402 T: toggleTheme
403 };
404 // Masked keys are global key handlers that always return true.
405 // That is, the view will never see the event for that key.
406 keyHandler.maskedKeys = {
407 T: true
408 };
409 }
410
411 function escapeKey(view, key, code, ev) {
412 if (alerts.open) {
413 closeAlerts();
414 return true;
415 }
416 return false;
417 }
418
419 function toggleTheme(view, key, code, ev) {
420 var body = d3.select('body');
421 current.theme = (current.theme === 'light') ? 'dark' : 'light';
422 body.classed('light dark', false);
423 body.classed(current.theme, true);
424 return true;
425 }
426
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800427 function setKeyBindings(keyArg) {
Thomas Vachuska65368e32014-11-08 16:10:20 -0800428 var viewKeys,
429 masked = [];
430
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800431 if ($.isFunction(keyArg)) {
432 // set general key handler callback
Thomas Vachuska65368e32014-11-08 16:10:20 -0800433 keyHandler.viewFn = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800434 } else {
435 // set specific key filter map
Thomas Vachuska65368e32014-11-08 16:10:20 -0800436 viewKeys = d3.map(keyArg).keys();
437 viewKeys.forEach(function (key) {
438 if (keyHandler.maskedKeys[key]) {
439 masked.push(' Key "' + key + '" is reserved');
440 }
441 });
442
443 if (masked.length) {
444 doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
445 }
446 keyHandler.viewKeys = keyArg;
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800447 }
448 }
449
Thomas Vachuska65368e32014-11-08 16:10:20 -0800450 function keyIn() {
451 var event = d3.event,
452 keyCode = event.keyCode,
453 key = whatKey(keyCode),
454 gcb = isF(keyHandler.globalKeys[key]),
455 vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
456
457 // global callback?
458 if (gcb && gcb(current.view.token(), key, keyCode, event)) {
459 // if the event was 'handled', we are done
460 return;
461 }
462 // otherwise, let the view callback have a shot
463 if (vcb) {
464 vcb(current.view.token(), key, keyCode, event);
465 }
466 }
Simon Hunt1a9eff92014-11-07 11:06:34 -0800467
468 function createAlerts() {
Simon Hunt61d04042014-11-11 17:27:16 -0800469 $alerts.style('display', 'block');
470 $alerts.append('span')
Simon Hunt1a9eff92014-11-07 11:06:34 -0800471 .attr('class', 'close')
472 .text('X')
473 .on('click', closeAlerts);
Simon Hunt61d04042014-11-11 17:27:16 -0800474 $alerts.append('pre');
475 $alerts.append('p').attr('class', 'footnote')
Thomas Vachuska65368e32014-11-08 16:10:20 -0800476 .text('Press ESCAPE to close');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800477 alerts.open = true;
478 alerts.count = 0;
479 }
480
481 function closeAlerts() {
Simon Hunt61d04042014-11-11 17:27:16 -0800482 $alerts.style('display', 'none')
Thomas Vachuska65368e32014-11-08 16:10:20 -0800483 .html('');
Simon Hunt1a9eff92014-11-07 11:06:34 -0800484 alerts.open = false;
485 }
486
487 function addAlert(msg) {
488 var lines,
489 oldContent;
490
491 if (alerts.count) {
Simon Hunt61d04042014-11-11 17:27:16 -0800492 oldContent = $alerts.select('pre').html();
Simon Hunt1a9eff92014-11-07 11:06:34 -0800493 }
494
495 lines = msg.split('\n');
496 lines[0] += ' '; // spacing so we don't crowd 'X'
497 lines = lines.join('\n');
498
499 if (oldContent) {
500 lines += '\n----\n' + oldContent;
501 }
502
Simon Hunt61d04042014-11-11 17:27:16 -0800503 $alerts.select('pre').html(lines);
Simon Hunt1a9eff92014-11-07 11:06:34 -0800504 alerts.count++;
505 }
506
507 function doAlert(msg) {
508 if (!alerts.open) {
509 createAlerts();
510 }
511 addAlert(msg);
512 }
513
Simon Hunt25248912014-11-04 11:25:48 -0800514 function resize(e) {
515 d3.selectAll('.onosView').call(setViewDimensions);
516 // allow current view to react to resize event...
Simon Hunt195cb382014-11-03 17:50:51 -0800517 if (current.view) {
Simon Hunt56d51852014-11-09 13:03:35 -0800518 current.view.resize(current.ctx, current.flags);
Simon Hunt195cb382014-11-03 17:50:51 -0800519 }
520 }
521
522 // ..........................................................
523 // View class
524 // Captures state information about a view.
525
526 // Constructor
527 // vid : view id
528 // nid : id of associated nav-item (optional)
Simon Hunt25248912014-11-04 11:25:48 -0800529 // cb : callbacks (preload, reset, load, unload, resize, error)
Simon Hunt195cb382014-11-03 17:50:51 -0800530 function View(vid) {
531 var av = 'addView(): ',
532 args = Array.prototype.slice.call(arguments),
533 nid,
Simon Hunt25248912014-11-04 11:25:48 -0800534 cb;
Simon Hunt195cb382014-11-03 17:50:51 -0800535
536 args.shift(); // first arg is always vid
537 if (typeof args[0] === 'string') { // nid specified
538 nid = args.shift();
539 }
540 cb = args.shift();
Simon Hunt195cb382014-11-03 17:50:51 -0800541
542 this.vid = vid;
543
544 if (validateViewArgs(vid)) {
545 this.nid = nid; // explicit navitem id (can be null)
546 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
Simon Huntdb9eb072014-11-04 19:12:46 -0800547 this.$div = null; // view not yet added to DOM
548 this.radioButtons = null; // no radio buttons yet
549 this.ok = true; // valid view
Simon Hunt195cb382014-11-03 17:50:51 -0800550 }
Simon Hunt195cb382014-11-03 17:50:51 -0800551 }
552
553 function validateViewArgs(vid) {
Simon Hunt25248912014-11-04 11:25:48 -0800554 var av = "ui.addView(...): ",
555 ok = false;
Simon Hunt195cb382014-11-03 17:50:51 -0800556 if (typeof vid !== 'string' || !vid) {
557 doError(av + 'vid required');
558 } else if (views[vid]) {
559 doError(av + 'View ID "' + vid + '" already exists');
560 } else {
561 ok = true;
562 }
563 return ok;
564 }
565
566 var viewInstanceMethods = {
Simon Hunt25248912014-11-04 11:25:48 -0800567 token: function () {
Simon Hunt195cb382014-11-03 17:50:51 -0800568 return {
Simon Hunt25248912014-11-04 11:25:48 -0800569 // attributes
Simon Hunt195cb382014-11-03 17:50:51 -0800570 vid: this.vid,
571 nid: this.nid,
Simon Hunt25248912014-11-04 11:25:48 -0800572 $div: this.$div,
573
574 // functions
575 width: this.width,
Simon Huntdb9eb072014-11-04 19:12:46 -0800576 height: this.height,
Simon Hunt142d0032014-11-04 20:13:09 -0800577 uid: this.uid,
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800578 setRadio: this.setRadio,
Simon Huntc7ee0662014-11-05 16:44:37 -0800579 setKeys: this.setKeys,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800580 dataLoadError: this.dataLoadError,
581 alert: this.alert
Simon Hunt195cb382014-11-03 17:50:51 -0800582 }
Simon Hunt25248912014-11-04 11:25:48 -0800583 },
584
Simon Huntf67722a2014-11-10 09:32:06 -0800585 // == Life-cycle functions
586 // TODO: factor common code out of life-cycle
Simon Hunt56d51852014-11-09 13:03:35 -0800587 preload: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800588 var c = ctx || '',
589 fn = isF(this.cb.preload);
590 traceFn('View.preload', this.vid + ', ' + c);
591 if (fn) {
592 trace('PRELOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800593 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800594 }
595 },
596
Simon Huntf67722a2014-11-10 09:32:06 -0800597 reset: function (ctx, flags) {
598 var c = ctx || '',
599 fn = isF(this.cb.reset);
Simon Hunt25248912014-11-04 11:25:48 -0800600 traceFn('View.reset', this.vid);
601 if (fn) {
602 trace('RESET cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800603 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800604 } else if (this.cb.reset === true) {
605 // boolean true signifies "clear view"
606 trace(' [true] cleaing view...');
607 viewApi.empty();
608 }
609 },
610
Simon Hunt56d51852014-11-09 13:03:35 -0800611 load: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800612 var c = ctx || '',
613 fn = isF(this.cb.load);
614 traceFn('View.load', this.vid + ', ' + c);
615 this.$div.classed('currentView', true);
Simon Hunt25248912014-11-04 11:25:48 -0800616 if (fn) {
617 trace('LOAD cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800618 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800619 }
620 },
621
Simon Huntf67722a2014-11-10 09:32:06 -0800622 unload: function (ctx, flags) {
623 var c = ctx | '',
624 fn = isF(this.cb.unload);
Simon Hunt25248912014-11-04 11:25:48 -0800625 traceFn('View.unload', this.vid);
626 this.$div.classed('currentView', false);
Simon Hunt25248912014-11-04 11:25:48 -0800627 if (fn) {
628 trace('UNLOAD cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800629 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800630 }
631 },
632
Simon Hunt56d51852014-11-09 13:03:35 -0800633 resize: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800634 var c = ctx || '',
635 fn = isF(this.cb.resize),
636 w = this.width(),
637 h = this.height();
638 traceFn('View.resize', this.vid + '/' + c +
639 ' [' + w + 'x' + h + ']');
640 if (fn) {
641 trace('RESIZE cb for ' + this.vid);
Simon Hunt56d51852014-11-09 13:03:35 -0800642 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800643 }
644 },
645
Simon Huntf67722a2014-11-10 09:32:06 -0800646 error: function (ctx, flags) {
Simon Hunt25248912014-11-04 11:25:48 -0800647 var c = ctx || '',
648 fn = isF(this.cb.error);
649 traceFn('View.error', this.vid + ', ' + c);
650 if (fn) {
651 trace('ERROR cb for ' + this.vid);
Simon Huntf67722a2014-11-10 09:32:06 -0800652 fn(this.token(), c, flags);
Simon Hunt25248912014-11-04 11:25:48 -0800653 }
654 },
655
Simon Huntf67722a2014-11-10 09:32:06 -0800656 // == Token API functions
Simon Hunt25248912014-11-04 11:25:48 -0800657 width: function () {
658 return $(this.$div.node()).width();
659 },
660
661 height: function () {
662 return $(this.$div.node()).height();
Simon Huntdb9eb072014-11-04 19:12:46 -0800663 },
Simon Hunt25248912014-11-04 11:25:48 -0800664
Simon Hunt934c3ce2014-11-05 11:45:07 -0800665 setRadio: function (btnSet) {
666 setRadioButtons(this.vid, btnSet);
Simon Hunt142d0032014-11-04 20:13:09 -0800667 },
668
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800669 setKeys: function (keyArg) {
670 setKeyBindings(keyArg);
671 },
672
Simon Hunt142d0032014-11-04 20:13:09 -0800673 uid: function (id) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800674 return makeUid(this, id);
Simon Huntc7ee0662014-11-05 16:44:37 -0800675 },
676
Simon Huntbb282f52014-11-10 11:08:19 -0800677 // TODO : add exportApi and importApi methods
Simon Hunt1a9eff92014-11-07 11:06:34 -0800678 // TODO : implement custom dialogs
679
680 // Consider enhancing alert mechanism to handle multiples
681 // as individually closable.
682 alert: function (msg) {
683 doAlert(msg);
684 },
Simon Huntc7ee0662014-11-05 16:44:37 -0800685
686 dataLoadError: function (err, url) {
687 var msg = 'Data Load Error\n\n' +
688 err.status + ' -- ' + err.statusText + '\n\n' +
689 'relative-url: "' + url + '"\n\n' +
690 'complete-url: "' + err.responseURL + '"';
Simon Hunt1a9eff92014-11-07 11:06:34 -0800691 this.alert(msg);
Simon Huntdb9eb072014-11-04 19:12:46 -0800692 }
Simon Hunt25248912014-11-04 11:25:48 -0800693
694 // TODO: consider schedule, clearTimer, etc.
Simon Hunt195cb382014-11-03 17:50:51 -0800695 };
696
697 // attach instance methods to the view prototype
698 $.extend(View.prototype, viewInstanceMethods);
699
700 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800701 // UI API
Simon Hunt195cb382014-11-03 17:50:51 -0800702
Simon Hunt25248912014-11-04 11:25:48 -0800703 uiApi = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800704 addLib: function (libName, api) {
705 // TODO: validation of args
706 libApi[libName] = api;
707 },
708
Simon Hunt61d04042014-11-11 17:27:16 -0800709 // TODO: implement floating panel as a class
710 // TODO: parameterize position (currently hard-coded to TopRight)
711 /*
712 * Creates div in floating panels block, with the given id.
713 * Returns panel token used to interact with the panel
714 */
715 addFloatingPanel: function (id, position) {
716 var pos = position || 'TR',
717 el,
718 fp;
719
720 if (fpanels[id]) {
721 buildError('Float panel with id "' + id + '" already exists.');
722 return null;
723 }
724
725 el = $floatPanels.append('div')
726 .attr('id', id)
727 .attr('class', 'fpanel');
728
729 fp = {
730 id: id,
731 el: el,
732 pos: pos,
733 show: function () {
734 console.log('show pane: ' + id);
735 el.transition().duration(750)
736 .style('right', '20px')
737 .style('opacity', 1);
738 },
739 hide: function () {
740 console.log('hide pane: ' + id);
741 el.transition().duration(750)
742 .style('right', '-320px')
743 .style('opacity', 0);
744 },
745 empty: function () {
746 return el.html('');
747 },
748 append: function (what) {
749 return el.append(what);
750 }
751 };
752 fpanels[id] = fp;
753 return fp;
754 },
755
Simon Hunt1a9eff92014-11-07 11:06:34 -0800756 // TODO: it remains to be seen whether we keep this style of docs
Simon Hunt25248912014-11-04 11:25:48 -0800757 /** @api ui addView( vid, nid, cb )
758 * Adds a view to the UI.
759 * <p>
760 * Views are loaded/unloaded into the view content pane at
761 * appropriate times, by the navigation framework. This method
762 * adds a view to the UI and returns a token object representing
763 * the view. A view's token is always passed as the first
764 * argument to each of the view's life-cycle callback functions.
765 * <p>
766 * Note that if the view is directly referenced by a nav-item,
767 * or in a group of views with one of those views referenced by
768 * a nav-item, then the <i>nid</i> argument can be omitted as
769 * the framework can infer it.
770 * <p>
771 * <i>cb</i> is a plain object containing callback functions:
772 * "preload", "reset", "load", "unload", "resize", "error".
773 * <pre>
774 * function myLoad(view, ctx) { ... }
775 * ...
776 * // short form...
777 * onos.ui.addView('viewId', {
778 * load: myLoad
779 * });
780 * </pre>
781 *
782 * @param vid (string) [*] view ID (a unique DOM element id)
783 * @param nid (string) nav-item ID (a unique DOM element id)
784 * @param cb (object) [*] callbacks object
785 * @return the view token
786 */
787 addView: function (vid, nid, cb) {
788 traceFn('addView', vid);
789 var view = new View(vid, nid, cb),
Simon Hunt195cb382014-11-03 17:50:51 -0800790 token;
791 if (view.ok) {
792 views[vid] = view;
793 token = view.token();
794 } else {
795 token = { vid: view.vid, bad: true };
796 }
797 return token;
798 }
799 };
800
Simon Hunt25248912014-11-04 11:25:48 -0800801 // ..........................................................
802 // View API
803
Simon Huntbb282f52014-11-10 11:08:19 -0800804 // TODO: deprecated
Simon Hunt25248912014-11-04 11:25:48 -0800805 viewApi = {
806 /** @api view empty( )
807 * Empties the current view.
808 * <p>
809 * More specifically, removes all DOM elements from the
810 * current view's display div.
811 */
812 empty: function () {
813 if (!current.view) {
814 return;
815 }
816 current.view.$div.html('');
817 }
818 };
819
820 // ..........................................................
821 // Nav API
822 navApi = {
823
824 };
825
826 // ..........................................................
Simon Hunt1a9eff92014-11-07 11:06:34 -0800827 // Library API
828 libApi = {
829
830 };
831
832 // ..........................................................
Simon Hunt25248912014-11-04 11:25:48 -0800833 // Exported API
834
Simon Hunt195cb382014-11-03 17:50:51 -0800835 // function to be called from index.html to build the ONOS UI
836 function buildOnosUi() {
837 tsB = new Date().getTime();
838 tsI = tsB - tsI; // initialization duration
839
840 console.log('ONOS UI initialized in ' + tsI + 'ms');
841
842 if (built) {
843 throwError("ONOS UI already built!");
844 }
845 built = true;
846
Simon Huntdb9eb072014-11-04 19:12:46 -0800847 $mastRadio = d3.select('#mastRadio');
Simon Hunt195cb382014-11-03 17:50:51 -0800848
849 $(window).on('hashchange', hash);
Simon Hunt25248912014-11-04 11:25:48 -0800850 $(window).on('resize', resize);
Simon Hunt195cb382014-11-03 17:50:51 -0800851
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800852 d3.select('body').on('keydown', keyIn);
Thomas Vachuska65368e32014-11-08 16:10:20 -0800853 setupGlobalKeys();
Simon Hunt0df1b1d2014-11-04 22:58:29 -0800854
Simon Hunt195cb382014-11-03 17:50:51 -0800855 // Invoke hashchange callback to navigate to content
856 // indicated by the window location hash.
857 hash();
858
859 // If there were any build errors, report them
860 reportBuildErrors();
861 }
862
Simon Hunt195cb382014-11-03 17:50:51 -0800863 // export the api and build-UI function
864 return {
Simon Hunt25248912014-11-04 11:25:48 -0800865 ui: uiApi,
Simon Hunt1a9eff92014-11-07 11:06:34 -0800866 lib: libApi,
867 //view: viewApi,
Simon Hunt25248912014-11-04 11:25:48 -0800868 nav: navApi,
Simon Huntbb282f52014-11-10 11:08:19 -0800869 buildUi: buildOnosUi,
870 exported: exported
Simon Hunt195cb382014-11-03 17:50:51 -0800871 };
872 };
873
Simon Huntdb9eb072014-11-04 19:12:46 -0800874}(jQuery));