1 // 2 // Copyright 2012, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * Hooks for running BajaScript inside of a browser. 7 * 8 * @author Logan Byam 9 * @version 1.0.0.0 10 */ 11 12 /*jslint browser: true, white: true, plusplus: true, nomen: true */ 13 /*global baja, ActiveXObject*/ 14 (function browser() { 15 "use strict"; 16 17 //////////////////////////////////////////////////////////////// 18 // DOM Ready 19 //////////////////////////////////////////////////////////////// 20 21 var domReadyTimer, 22 domReadyCallbacks, 23 domIsReady; 24 25 if (!baja.comm) { 26 throw new Error("comm.js required"); 27 } 28 29 /** 30 * Waits for the DOM to be initialized and then executes a callback. 31 * 32 * @name baja.browser.comm.domReady 33 * @function 34 * @private 35 * @inner 36 * 37 * @param {Function} func a function to be executed once the DOM is ready. 38 */ 39 function domReady(func) { 40 // isDOMReady function 41 var isDOMReady = function () { 42 if (domIsReady) { 43 return false; 44 } 45 46 if (document && 47 document.getElementsByTagName && 48 document.getElementById && 49 document.body) { 50 clearInterval(domReadyTimer); 51 domReadyTimer = null; 52 53 var i; 54 for (i = 0; i < domReadyCallbacks.length; ++i) { 55 domReadyCallbacks[i](); 56 } 57 58 domReadyCallbacks = null; 59 domIsReady = true; 60 } 61 }; 62 63 // If already ready then invoke and return 64 if (domIsReady) { 65 func(); 66 return; 67 } 68 69 if (typeof domReadyTimer === "number") { 70 // Register this callback if we need too 71 domReadyCallbacks.push(func); 72 } 73 else { 74 // Register for dom ready events 75 if (document && document.addEventListener) { 76 document.addEventListener("DOMContentLoaded", isDOMReady, false); 77 } 78 else { 79 window.onload = isDOMReady; 80 } 81 82 domReadyCallbacks = [func]; 83 84 // Use a timer as a fallback just in case! 85 domReadyTimer = setInterval(isDOMReady, 13); 86 } 87 } 88 89 /** 90 * This namespace is for documentation purposes only and will not actually 91 * be available to Bajascript apps. It details enhancements/decorations 92 * applied to functions in <code>baja.comm</code> when Bajascript is deployed 93 * to a web browser environment. 94 * 95 * @namespace 96 * @name baja.browser.comm 97 */ 98 (function comm() { 99 // Ensure the started callbacks only happen once the DOM is fully loaded. 100 var doStart = baja.comm.start; 101 102 /** 103 * In a browser, <code>baja.comm.start</code> will wait for the DOM to be 104 * ready (as well as for communications to the station be established) 105 * before executing its callback. 106 * 107 * @name baja.browser.comm.start 108 * @function 109 * @private 110 * @inner 111 */ 112 baja.comm.start = function start(obj) { 113 var started = obj.started || baja.ok; 114 obj.started = function () { 115 domReady(started); 116 }; 117 doStart(obj); 118 }; 119 120 /** 121 * In a browser, <code>baja.comm.reconnect</code> will simply reload the 122 * browser page. 123 * 124 * @name baja.browser.comm.reconnect 125 * @function 126 * @private 127 * @inner 128 */ 129 baja.comm.reconnect = function reconnect() { 130 // Delay the reconnection slightly - just incase there's a problem and this triggers another refresh! 131 baja.clock.schedule(function () { 132 // If we can get to the login page then attempt a reconnection... 133 // Reconnect by refreshing the page... 134 if (location && location.reload) { 135 location.reload(/*forceget*/false); 136 } 137 }, 2500); 138 }; 139 140 //////////////////////////////////////////////////////////////// 141 // HTTP Comms 142 //////////////////////////////////////////////////////////////// 143 144 /** 145 * An Http Error. 146 * <p> 147 * A HTTP Error happens as a result of problem with HTTP communcation. 148 * 149 * @class 150 * @private 151 * @param {XmlHttpRequest} x the object used for comms. 152 */ 153 baja.comm.HttpError = function (x) { 154 var t = x.responseText || "Session disconnected", 155 status = x.status; 156 157 this.name = "HttpError"; 158 this.message = t + " err: " + x.status; 159 this.status = status; 160 161 // Indicate to delay any reconnection if a 404 or no status is returned. 162 this.delayReconnect = (!status || status === 404); 163 164 }.$extend(baja.comm.ServerError); 165 166 /** 167 * Make an HTTP connection using XMLHttpRequest or its MS equivalents. 168 * 169 * @name baja.browser.comm.sendHttp 170 * @function 171 * @private 172 * @inner 173 * 174 * @param {String} method the method to use (i.e. 'post' or 'get'). 175 * @param {String} uri the URI used in the connection. 176 * @param {Boolean} async boolean flag indicating if this is an asynchronous or synchronous call. 177 * @param {Object} callback this object must have 'ok' and 'fail' functions. 178 * @param {String} [body] the body of the HTTP POST. 179 * @param {String} [contentType] the Content Type for an HTTP POST. 180 */ 181 function sendHttp(method, uri, async, callback, body, contentType) { 182 // TODO: Do we need to handle timeouts? Or just rely on the browser failing? 183 var x = null, 184 handler, 185 ie = false; // Are we Internet Explorer? 186 187 // HTTP Callback Handler 188 handler = function () { 189 var st; // Status 190 191 if (x.readyState === 4) { 192 try { 193 st = parseInt(x.status, 10); 194 195 if (st !== 200 && !baja.isStopping()) { 196 callback.fail(new baja.comm.HttpError(x)); 197 } 198 else { 199 // HTTP 200 ok 200 callback.ok(x.responseText); 201 } 202 } 203 catch (error) { 204 callback.fail(error); 205 } 206 finally { 207 // Request is now dealt with so clean up here 208 x.onreadystatechange = baja.ok; 209 x = null; 210 } 211 } 212 }; 213 214 // Make AJAX network call 215 try { 216 217 // Create XMLHttpRequest 218 try { 219 x = new XMLHttpRequest(); 220 } 221 catch (e) { 222 try { 223 x = new ActiveXObject("Msxml2.XMLHTTP"); 224 ie = true; 225 } 226 catch (e2) { 227 try { 228 x = new ActiveXObject("Microsoft.XMLHTTP"); 229 ie = true; 230 } 231 catch (e3) { 232 // No XMLHttpAvailable? 233 throw new Error("Failed to create XMLHttpRequest: " + e3); 234 } 235 } 236 } 237 238 x.open(method, uri, async); 239 240 if (contentType) { 241 x.setRequestHeader("Content-Type", contentType); 242 } 243 244 // Set HTTP Headers 245 if (ie === undefined) { 246 x.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); 247 } 248 249 if (async) { 250 // Set callback handler 251 x.onreadystatechange = handler; 252 } 253 254 // Send body 255 x.send(body); 256 } 257 catch (error) { 258 callback.fail(error); 259 return; 260 } 261 262 if (!async && x !== null) { 263 // If synchronous then just invoke the handler 264 // Please note: this is outside of the above exception handler because we 265 // don't want to catch any exceptions generated by the handler 266 handler.call(x); 267 } 268 } 269 270 /** 271 * In a browser, <code>baja.comm.BoxFrame#send</code> will delegate to 272 * <code>baja.browser.comm.sendHttp</code>. 273 * 274 * @private 275 * @inner 276 * @ignore 277 */ 278 baja.comm.BoxFrame.prototype.send = function (async, callback) { 279 // Make the HTTP POST to the BOX Servlet 280 sendHttp("post", 281 "/box/", 282 async, 283 callback, 284 this.toString(), 285 "application/json"); 286 }; 287 }()); 288 289 /** 290 * This namespace is for documentation purposes only and will not actually 291 * be available to Bajascript apps. It details enhancements/decorations 292 * applied to functions in <code>baja</code> when Bajascript is deployed 293 * to a web browser environment. 294 * 295 * @namespace 296 * @name baja.browser 297 */ 298 (function sysUtil() { 299 var _outln = baja.outln, 300 _clearOut = baja.clearOut, 301 _error = baja.error; 302 303 /** 304 * In a browser, <code>baja.outln</code> will (in addition to calling 305 * <code>bajaJsPrint()</code>) look for a <code><pre></code> element 306 * with ID <code>bajaScriptOut</code> and append the message to that 307 * element as well. 308 * 309 * @name baja.browser.outln 310 * @function 311 * @private 312 * @inner 313 */ 314 baja.outln = function (msg) { 315 // If BajaScript has stopped then don't output anything else... 316 if (baja.isStopping()) { 317 return this; 318 } 319 320 var bajaScriptOut, 321 escapedMsg; 322 323 msg += "\n"; 324 325 if (document && document.getElementById) { 326 bajaScriptOut = document.getElementById("bajaScriptOut"); 327 if (bajaScriptOut !== null) { 328 escapedMsg = msg.toString(); 329 escapedMsg = msg.replace(/</g, "(").replace(/>/g, ")"); 330 331 // Should work around IE for ignoring newlines in 'pre' and 'textarea' elements 332 escapedMsg = escapedMsg.replace(/\n/g, "\r\n"); 333 bajaScriptOut.appendChild(document.createTextNode(escapedMsg)); 334 } 335 } 336 337 // If available, attempt to write out to Chrome's JavaScript window 338 if (window.console && window.console.log) { 339 window.console.log(msg); 340 } 341 342 return _outln.call(this, msg); 343 }; 344 345 /** 346 * In a browser, <code>baja.clearOut</code> will look for a 347 * <code><pre></code> element with ID <code>bajaScriptOut</code> and 348 * wipe all text from it. 349 * 350 * @name baja.browser.clearOut 351 * @function 352 * @private 353 * @inner 354 */ 355 baja.clearOut = function () { 356 if (document && document.getElementById) { 357 var elem = document.getElementById("bajaScriptOut"); 358 if (elem !== null) { 359 elem.innerHTML = ""; 360 } 361 } 362 363 return _clearOut.apply(this, arguments); 364 }; 365 366 /** 367 * In a browser, <code>baja.error</code> will (in addition to calling 368 * <code>baja.outln</code>) look for <code>window.console.error</code> and, 369 * if it exists, pass the error message to it. 370 * 371 * @name baja.browser.error 372 * @function 373 * @private 374 * @inner 375 */ 376 baja.error = function (msg) { 377 if (baja.isStopping()) { 378 return this; 379 } 380 381 // If available, attempt to write out to Chrome's JavaScript Window 382 if (window.console && window.console.error) { 383 window.console.error(msg); 384 } 385 386 return _error.apply(this, arguments); 387 }; 388 }()); 389 390 391 /** 392 * This namespace is for documentation purposes only and will not actually 393 * be available to Bajascript Apps. It details enhancements/decorations 394 * applied to functions in <code>baja.registry</code> when Bajascript is 395 * deployed to a web browser environment. 396 * 397 * @namespace 398 * @name baja.browser.registry 399 */ 400 (function sysRegistry() { 401 var webStorageKeyName = "bajaScriptReg"; 402 403 /** 404 * In a browser, this overrides <code>baja.registry.clearStorage</code> 405 * and will clear the web storage content (if the browser supports web storage). 406 * 407 * @name baja.browser.registry.clearStorage 408 * @function 409 * @private 410 * @inner 411 */ 412 baja.registry.clearStorage = function () { 413 if (window.localStorage) { 414 // Always access local storage in try/catch... 415 try { 416 window.localStorage.removeItem(webStorageKeyName); 417 } 418 catch (ignore) {} 419 } 420 }; 421 422 /** 423 * In a browser, this overrides <code>baja.registry.saveToStorage</code> 424 * and will write its contents to web storage (if the browser supports web storage). 425 * 426 * @name baja.browser.registry.save 427 * @function 428 * @private 429 * @inner 430 * 431 * @param {Object} regStorage the BajaScript registry information to store 432 */ 433 baja.registry.saveToStorage = function (regStorage) { 434 // If available, save any cached registry information to web storage 435 if (regStorage && window.localStorage) { 436 // Always access local storage in try/catch... 437 try { 438 window.localStorage.setItem(webStorageKeyName, JSON.stringify(regStorage)); 439 } 440 catch (ignore) { 441 baja.registry.clearStorage(); 442 } 443 } 444 }; 445 446 /** 447 * In a browser, this overrides <code>baja.registry.loadFromStorage</code> 448 * and will attempt to read any registry information previously stored in web storage 449 * (if the browser supports web storage). 450 * 451 * @name baja.browser.registry.loadFromStorage 452 * @function 453 * @private 454 * @inner 455 */ 456 baja.registry.loadFromStorage = function () { 457 var regStorage = null; 458 if (window.localStorage) { 459 // Always access local storage in try/catch... 460 try { 461 regStorage = window.localStorage.getItem(webStorageKeyName); 462 463 // If we have data then parse it 464 if (regStorage) { 465 regStorage = JSON.parse(regStorage); 466 } 467 } 468 catch (ignore) { 469 baja.registry.clearStorage(); 470 } 471 } 472 return regStorage; 473 }; 474 }()); 475 }());