blob: bd001a591c947b9df3693b92df188a55df4c809c [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
27 defaultHash = 'temp1';
28
29
30 // attach our main function to the jQuery object
31 $.onos = function (options) {
32 var publicApi; // public api
33
34 // internal state
35 var views = {},
36 current = {
37 view: null,
38 ctx: ''
39 },
40 built = false,
41 errorCount = 0;
42
43 // DOM elements etc.
44 var $view;
45
46
47 // ..........................................................
48 // Internal functions
49
50 // throw an error
51 function throwError(msg) {
52 // separate function, as we might add tracing here too, later
53 throw new Error(msg);
54 }
55
56 function doError(msg) {
57 errorCount++;
58 console.warn(msg);
59 }
60
61 // hash navigation
62 function hash() {
63 var hash = window.location.hash,
64 redo = false,
65 view,
66 t;
67
68 if (!hash) {
69 hash = defaultHash;
70 redo = true;
71 }
72
73 t = parseHash(hash);
74 if (!t || !t.vid) {
75 doError('Unable to parse target hash: ' + hash);
76 }
77
78 view = views[t.vid];
79 if (!view) {
80 doError('No view defined with id: ' + t.vid);
81 }
82
83 if (redo) {
84 window.location.hash = makeHash(t);
85 // the above will result in a hashchange event, invoking
86 // this function again
87 } else {
88 // hash was not modified... navigate to where we need to be
89 navigate(hash, view, t);
90 }
91
92 }
93
94 function parseHash(s) {
95 // extract navigation coordinates from the supplied string
96 // "vid,ctx" --> { vid:vid, ctx:ctx }
97
98 var m = /^[#]{0,1}(\S+),(\S*)$/.exec(s);
99 if (m) {
100 return { vid: m[1], ctx: m[2] };
101 }
102
103 m = /^[#]{0,1}(\S+)$/.exec(s);
104 return m ? { vid: m[1] } : null;
105 }
106
107 function makeHash(t, ctx) {
108 // make a hash string from the given navigation coordinates.
109 // if t is not an object, then it is a vid
110 var h = t,
111 c = ctx || '';
112
113 if ($.isPlainObject(t)) {
114 h = t.vid;
115 c = t.ctx || '';
116 }
117
118 if (c) {
119 h += ',' + c;
120 }
121 return h;
122 }
123
124 function navigate(hash, view, t) {
125 // closePanes() // flyouts etc.
126 // updateNav() // accordion / selected nav item
127 createView(view);
128 setView(view, hash, t);
129 }
130
131 function reportBuildErrors() {
132 // TODO: validate registered views / nav-item linkage etc.
133 console.log('(no build errors)');
134 }
135
136 // ..........................................................
137 // View life-cycle functions
138
139 function createView(view) {
140 var $d;
141 // lazy initialization of the view
142 if (view && !view.$div) {
143 $d = $view.append('div')
144 .attr({
145 id: view.vid
146 });
147 view.$div = $d; // cache a reference to the selected div
148 }
149 }
150
151 function setView(view, hash, t) {
152 // set the specified view as current, while invoking the
153 // appropriate life-cycle callbacks
154
155 // if there is a current view, and it is not the same as
156 // the incoming view, then unload it...
157 if (current.view && !(current.view.vid !== view.vid)) {
158 current.view.unload();
159 }
160
161 // cache new view and context
162 current.view = view;
163 current.ctx = t.ctx || '';
164
165 // TODO: clear radio button set (store on view?)
166
167 // preload is called only once, after the view is in the DOM
168 if (!view.preloaded) {
169 view.preload(t.ctx);
170 }
171
172 // clear the view of stale data
173 view.reset();
174
175 // load the view
176 view.load(t.ctx);
177 }
178
179 function resizeView() {
180 if (current.view) {
181 current.view.resize();
182 }
183 }
184
185 // ..........................................................
186 // View class
187 // Captures state information about a view.
188
189 // Constructor
190 // vid : view id
191 // nid : id of associated nav-item (optional)
192 // cb : callbacks (preload, reset, load, resize, unload, error)
193 // data: custom data object (optional)
194 function View(vid) {
195 var av = 'addView(): ',
196 args = Array.prototype.slice.call(arguments),
197 nid,
198 cb,
199 data;
200
201 args.shift(); // first arg is always vid
202 if (typeof args[0] === 'string') { // nid specified
203 nid = args.shift();
204 }
205 cb = args.shift();
206 data = args.shift();
207
208 this.vid = vid;
209
210 if (validateViewArgs(vid)) {
211 this.nid = nid; // explicit navitem id (can be null)
212 this.cb = $.isPlainObject(cb) ? cb : {}; // callbacks
213 this.data = data; // custom data (can be null)
214 this.$div = null; // view not yet added to DOM
215 this.ok = true; // valid view
216 }
217
218 }
219
220 function validateViewArgs(vid) {
221 var ok = false;
222 if (typeof vid !== 'string' || !vid) {
223 doError(av + 'vid required');
224 } else if (views[vid]) {
225 doError(av + 'View ID "' + vid + '" already exists');
226 } else {
227 ok = true;
228 }
229 return ok;
230 }
231
232 var viewInstanceMethods = {
233 toString: function () {
234 return '[View: id="' + this.vid + '"]';
235 },
236
237 token: function() {
238 return {
239 vid: this.vid,
240 nid: this.nid,
241 data: this.data
242 }
243 }
244 // TODO: create, preload, reset, load, error, resize, unload
245 };
246
247 // attach instance methods to the view prototype
248 $.extend(View.prototype, viewInstanceMethods);
249
250 // ..........................................................
251 // Exported API
252
253 publicApi = {
254 printTime: function () {
255 console.log("the time is " + new Date());
256 },
257
258 addView: function (vid, nid, cb, data) {
259 var view = new View(vid, nid, cb, data),
260 token;
261 if (view.ok) {
262 views[vid] = view;
263 token = view.token();
264 } else {
265 token = { vid: view.vid, bad: true };
266 }
267 return token;
268 }
269 };
270
271 // function to be called from index.html to build the ONOS UI
272 function buildOnosUi() {
273 tsB = new Date().getTime();
274 tsI = tsB - tsI; // initialization duration
275
276 console.log('ONOS UI initialized in ' + tsI + 'ms');
277
278 if (built) {
279 throwError("ONOS UI already built!");
280 }
281 built = true;
282
283 $view = d3.select('#view');
284
285 $(window).on('hashchange', hash);
286
287 // Invoke hashchange callback to navigate to content
288 // indicated by the window location hash.
289 hash();
290
291 // If there were any build errors, report them
292 reportBuildErrors();
293 }
294
295
296 // export the api and build-UI function
297 return {
298 api: publicApi,
299 buildUi: buildOnosUi
300 };
301 };
302
303}(jQuery));