1 /** 2 * @license Copyright 2010, Tridium, Inc. All Rights Reserved. 3 */ 4 5 /** 6 * Core System Architecture for BajaScript including Type and Registry System. 7 * 8 * @author Gareth Johnson 9 * @version 1.0.0.0 10 */ 11 12 //JsLint options (see http://www.jslint.com) 13 /*jslint rhino: true, onevar: false, plusplus: true, white: true, undef: false, nomen: false, 14 eqeqeq: true, bitwise: true, regexp: false, newcap: true, immed: true, strict: false, 15 indent: 2, vars: true, continue: true */ 16 17 /*global setTimeout, clearTimeout, setInterval, clearInterval, bajaJsPrint*/ 18 19 /** 20 * @class BaseBajaObj 21 * <p> 22 * The base class for all BajaScript Objects. 23 */ 24 var BaseBajaObj = function () { 25 "use strict"; 26 }; 27 28 /** 29 * @namespace the core BajaScript namespace. 30 */ 31 var baja = new BaseBajaObj(); 32 33 //////////////////////////////////////////////////////////////// 34 // Very common/generic methods 35 //////////////////////////////////////////////////////////////// 36 37 (function common(baja, BaseBajaObj) { 38 // Use ECMAScript 5 Strict Mode 39 "use strict"; 40 41 /** 42 * Indicates whether some other object is equal to this one. 43 * 44 * @param {Object} obj the reference object with which to compare. 45 * 46 * @returns {Boolean} true if this object is the same as the obj argument; false otherwise. 47 */ 48 BaseBajaObj.prototype.equals = function (obj) { 49 return this === obj; 50 }; 51 52 /** 53 * Return the inner value of object. 54 * <p> 55 * By default the object's instance is returned. 56 * 57 * @returns the inner value of the object or just the object's instance. 58 */ 59 BaseBajaObj.prototype.valueOf = function () { 60 return this; 61 }; 62 63 //////////////////////////////////////////////////////////////// 64 // Extra Function Methods 65 //////////////////////////////////////////////////////////////// 66 67 /** 68 * Extends an Object by setting up the prototype chain. 69 * <p> 70 * This method can have a Function or Object passed in as an argument. 71 * If a Function is passed in, 'new' will be called on it to create an Object. 72 * The new Object will then be set on the target Function's prototype. 73 * 74 * @param {Function|Object} Parent 75 * @returns {Function} the instance Function. 76 */ 77 Function.prototype.$extend = function (Parent) { 78 // So the 'super' class can be tracked, a '$super' property is 79 // created and attached to the Function instance 80 if (typeof Parent === 'function') { 81 this.prototype = new Parent(); 82 this.$super = Parent; 83 } 84 else { 85 this.prototype = Parent; 86 this.$super = Parent.constructor; 87 } 88 this.prototype.constructor = this; 89 return this; 90 }; 91 92 //////////////////////////////////////////////////////////////// 93 // Extra Array Methods 94 //////////////////////////////////////////////////////////////// 95 96 if (typeof Array.prototype.contains !== "function") { 97 if (typeof Array.prototype.indexOf === "function") { 98 /** 99 * @ignore - get JsDoc to ignore this symbols so it's not picked up accidently. 100 */ 101 Array.prototype.contains = function (o) { 102 return this.indexOf(o) > -1; 103 }; 104 } 105 else { 106 Array.prototype.contains = function (o) { 107 var i; 108 for (i = 0; i < this.length; i++) { 109 if (this[i] === o) { 110 return true; 111 } 112 } 113 return false; 114 }; 115 } 116 } 117 118 }(baja, BaseBajaObj)); 119 120 //////////////////////////////////////////////////////////////// 121 // BajaScript 122 //////////////////////////////////////////////////////////////// 123 124 (function bajaNamespace(baja, BaseBajaObj) { 125 // Use ECMAScript 5 Strict Mode 126 "use strict"; 127 128 //////////////////////////////////////////////////////////////// 129 // Baja 130 //////////////////////////////////////////////////////////////// 131 132 /** 133 * BajaScript's version number (maj.min.build.patch). 134 */ 135 baja.version = "1.0.0.0"; 136 137 var bsLexicons = {}, // BajaScript Lexicons 138 bsRegStorage = null, // BajaScript Registry Storage 139 bsClockTimeouts = {}, // BajaScript ticket timeouts 140 bsClockIntervals = {}; // BajaScript ticket intervals 141 142 //////////////////////////////////////////////////////////////// 143 // Debug 144 //////////////////////////////////////////////////////////////// 145 146 (function debug() { 147 148 /** 149 * Print a message to BajaScript's debug output followed by a newline. 150 * By default just looks for a <code>bajaJsPrint</code> global function and 151 * passes the message to that. 152 * 153 * @param {String} msg the message to output. 154 * 155 * @returns baja 156 */ 157 baja.outln = function (msg) { 158 // If BajaScript has stopped then don't output anything else... 159 if (baja.isStopping()) { 160 return this; 161 } 162 163 if (typeof bajaJsPrint === "function") { 164 bajaJsPrint(msg); 165 } 166 return this; 167 }; 168 169 /** 170 * Attempt to clear BajaScript's debug output. 171 * 172 * @returns baja 173 */ 174 baja.clearOut = function () { 175 return this; 176 }; 177 178 /** 179 * Print out the error to the specified line printer function. This gets called 180 * by the default error handler. 181 * 182 * @name baja.printError 183 * @private 184 * @inner 185 * 186 * @see baja.error 187 * 188 * @param {Error} e the error to be printed. 189 * @param {Function} print the print function to be called each time a line of 190 * the error is to be printed out. 191 */ 192 function printError(e, print) { 193 var errorMsg = e.name + ": "; 194 195 // Some JavaScript engines give errors a nice full stack trace... 196 if (e.stack) { 197 198 if (e.message && 199 typeof e.stack === "string" && 200 e.stack.indexOf(errorMsg + e.message) !== 0) { 201 print(errorMsg + e.message); 202 } 203 204 print(e.stack); 205 return; 206 } 207 208 if (e.message) { 209 print(errorMsg + e.message); 210 } 211 else { 212 print(errorMsg + e); 213 } 214 215 // Add a try/catch just in case because ECMAScript 5 disallows access to the callee and caller 216 try { 217 // Cater for IE and Safari and try to print out a pseudo stack trace... 218 var func, 219 fn, 220 args = arguments, 221 stackSize = 0, 222 maxStack = baja.stackTraceLimit || 20, 223 maxFuncLength = 200; 224 225 if (args.callee && args.callee.caller) { 226 func = args.callee.caller; 227 while (typeof func === "function" && stackSize < maxStack) { 228 fn = func.name; 229 if (!fn) { 230 // Attempt to format the function into some sort of stack trace... 231 fn = func.toString().replace(/[\r\n]/g, " "); 232 233 // Don't allow the string to get too big... 234 if (fn.length > maxFuncLength) { 235 fn = fn.substring(0, maxFuncLength) + "..."; 236 } 237 } 238 239 print(" at " + fn); 240 func = func.caller; 241 stackSize++; 242 } 243 if (stackSize >= maxStack) { 244 print("Stack trace limit exceeded (" + maxStack + ")"); 245 } 246 } 247 } 248 catch (ignore) { 249 } 250 } 251 252 /** 253 * Print an error message in BajaScript's debug output. In IE/Safari the 254 * length of the stack trace will be limited to 20 records - you can 255 * change this by setting <code>baja.stackTraceLimit</code>. 256 * <p> 257 * By default, this method calls {@link baja.printError} using 258 * {@link baja.outln} as the printer function. 259 * 260 * @param e the error to output. 261 * 262 * @returns baja 263 */ 264 baja.error = function (e) { 265 if (baja.isStopping()) { 266 return this; 267 } 268 269 // Make sure we have an Error object 270 e = e instanceof Error ? e : new Error(e); 271 272 // Print the error out using baja.outln. 273 printError(e, baja.outln); 274 275 // If this isn't a Server error then attempt to also log it in the Server (if specified) 276 if (baja.isStarted() && 277 baja.isLogClientErrorsInServer() && 278 e && !(e instanceof baja.comm.ServerError)) { 279 var error = ""; 280 printError(e, function (line) { 281 error += line + "\n"; 282 }); 283 284 // Make the network call to log the error 285 baja.comm.error(error); 286 } 287 288 return this; 289 }; 290 291 baja.stackTraceLimit = 20; 292 }()); 293 294 //////////////////////////////////////////////////////////////// 295 // Baja Util 296 //////////////////////////////////////////////////////////////// 297 298 /** 299 * The global default fail callback function. 300 * <p> 301 * Throughout BajaScript there are places where network calls may be made. In one of these cases, 302 * a developer has the option of specifying a 'fail' callback. If the fail callback isn't specifed 303 * by the developer, it will default back to this function. 304 * 305 * @see baja.ok 306 */ 307 baja.fail = function (err) { 308 // Output error to the BajaScript Console... 309 err = err || new Error(); 310 baja.error(err instanceof Error ? err : new Error(err)); 311 }; 312 313 /** 314 * The global default ok callback function. 315 * <p> 316 * Throughout BajaScript there are places where network calls may be made. In one of these cases, 317 * a developer has the option of specifying an 'ok' callback. If the ok callback isn't specifed 318 * by the developer, it will default back to this function. 319 * 320 * @see baja.fail 321 */ 322 baja.ok = function () { 323 }; 324 325 /** 326 * A function that does nothing (noop = no-operation). 327 * 328 * @name baja.noop 329 * @function 330 */ 331 baja.noop = baja.ok; 332 333 /** 334 * @class Lexicon is a map of name/value pairs for a specific locale. 335 * 336 * @see baja.lex 337 * 338 * @name Lexicon 339 * @extends BaseBajaObj 340 * @inner 341 * @public 342 */ 343 var Lexicon = function (moduleName, data) { 344 baja.strictAllArgs([moduleName, data], [String, Object]); 345 this.$moduleName = moduleName; 346 this.$data = data; 347 }.$extend(BaseBajaObj); 348 349 /** 350 * Return a value from the Lexicon for a given key. 351 * <p> 352 * The argument for this method can be either a String key or an Object Literal. 353 * 354 * @see baja.lex 355 * 356 * @param {Object|String} obj the Object Literal that contains the method's arguments or a String key. 357 * @param {String} obj.key the key to look up. 358 * @param {String} obj.def the default value to return if the key can't be found. 359 * By default this is null. 360 * @param {Array|String} obj.args arguments used for String formatting. 361 * 362 * @returns {String} the value for the Lexicon or return def if can't be found. 363 */ 364 Lexicon.prototype.get = function (obj) { 365 obj = baja.objectify(obj, "key"); 366 obj.def = baja.def(obj.def, null); 367 368 var val = this.$data.hasOwnProperty(obj.key) ? this.$data[obj.key] : obj.def; 369 370 // TODO: Niagara uses Java's MessageFormat that does more than this. For now we'll stick 371 // with this use case as this should cover everything. 372 373 // If we have some message formatting arguments them use them 374 if (obj.args && typeof val === "string") { 375 var args = obj.args; 376 if (args.constructor !== Array) { 377 args = [args]; 378 } 379 380 // Replace {number} with value from args 381 var regex = /\{[0-9]+\}/g; 382 val = val.replace(regex, function (entry) { 383 var i = parseInt(entry.substring(1, entry.length - 1), 10); 384 return args[i] !== undefined ? args[i] : entry; 385 }); 386 } 387 388 return val; 389 }; 390 391 /** 392 * Return the Lexicon's module name. 393 * 394 * @returns {String} 395 */ 396 Lexicon.prototype.getModuleName = function () { 397 return this.$moduleName; 398 }; 399 400 (function util() { 401 402 /** 403 * Strict Argument Check. 404 * <p> 405 * Checks a given argument to ensure it's not undefined. If a Constructor is specified 406 * then it matches the argument's Constructor against it. 407 * <pre> 408 * // Example... 409 * baja.strictArg(arg1, String); 410 * </pre> 411 * <p> 412 * The second argument can also be a String TypeSpec or a Type Object. This will ensure the 413 * argument has the correct TypeSpec. 414 * <pre> 415 * // Example... 416 * baja.strictArg(arg1, "control:NumericWritable"); 417 * 418 * ...or... 419 * 420 * baja.strictArg(arg1, baja.lt("control:NumericWritable")); 421 * </pre> 422 * 423 * @private 424 * 425 * @param arg the argument being tested. 426 * @param {Function|String|Type} [ctor] optional Constructor function used to test the argument against. Can also be 427 * a String TypeSpec or a Type Object. 428 * @param {String} [errMsg] optional message to specify if argument check fails. 429 * @returns arg 430 */ 431 baja.strictArg = function (arg, ctor, errMsg) { 432 if (arg === undefined) { 433 if (errMsg) { 434 throw new Error(errMsg); 435 } else { 436 var err = "Invalid argument (undefined)"; 437 if (ctor) { 438 err += ". Expected " + ctor.name; 439 } 440 throw new Error(err); 441 } 442 } 443 // Null arguments are acceptable (just like Java) 444 else if (arg === null) { 445 return arg; 446 } 447 448 // Bail any type checking if we don't have a valid Constructor 449 if (ctor === undefined || ctor === null) { 450 return arg; 451 } 452 453 var typeOfCtor = typeof ctor; 454 455 // Do the type check 456 if (typeOfCtor === "function" && (arg.constructor === ctor || arg instanceof ctor)) { 457 return arg; 458 } 459 460 // Handle if the ctor is a TypeSpec (String TypeSpec or Type Object)... 461 var type; 462 if (typeOfCtor === "string" || typeOfCtor === "object") { 463 type = baja.lt(ctor); 464 if (!baja.hasType(arg)) { 465 throw new Error("Invalid argument. Expected Type: " + type); 466 } 467 if (!arg.getType().is(type)) { 468 throw new Error("Invalid argument. Type " + arg.getType() + " is not expected Type: " + type); 469 } 470 return arg; 471 } 472 473 // Ensure we have a valid names for the Constructors... 474 var nm = ctor.name; 475 if (!nm && ctor.$name) { 476 nm = ctor.$name; 477 } 478 479 var cnm = arg.constructor.name; 480 if (!cnm && arg.constructor.$name) { 481 cnm = arg.constructor.$name; 482 } 483 484 throw new Error("Invalid argument type. Expected " + nm + ", received " + cnm + " instead"); 485 }; 486 487 /** 488 * Strict Arguments Check. 489 * <p> 490 * Checks all of the given arguments to ensure they're not undefined. An array of Constructors 491 * is passed in to ensure they match the argument's Constructors. 492 * <pre> 493 * // Example... 494 * baja.strictAllArgs([arg1, arg2], [String, Number]); 495 * </pre> 496 * Please note, the Constructors array can also hold a Type used for comparison 497 * (in the form of either a String or Type). 498 * 499 * @private 500 * 501 * @see baja.strictArg 502 * 503 * @param {Array} args an array of arguments being tested. 504 * @param {Array} ctors an array of Constructors being used to test against the arguments. 505 */ 506 baja.strictAllArgs = function (args, ctors) { 507 if (ctors.length !== args.length) { 508 throw new Error("Invalid number of arguments. Expected " + ctors.length + 509 ", received " + args.length + " instead."); 510 } 511 512 var i; 513 for (i = 0; i < args.length; i++) { 514 baja.strictArg(args[i], ctors[i]); 515 } 516 }; 517 518 /** 519 * Define a default value for possibly undefined variables. 520 * 521 * @private 522 * 523 * @param val the value to be tested. 524 * @param defVal the default value to be returned if the value is undefined. 525 * @returns the default value if the value is undefined. 526 */ 527 baja.def = function (val, defVal) { 528 return val === undefined ? defVal : val; 529 }; 530 531 // Create local for improved minification 532 var strictArg = baja.strictArg, 533 strictAllArgs = baja.strictAllArgs, 534 bajaDef = baja.def; 535 536 /** 537 * A general utility method that tries to prevents a function from running 538 * more often than the specified interval. Due to the vagaries of Javascript 539 * time management you may see the occasional drop of a few milliseconds but 540 * on the whole execution of the given function should not be permitted more 541 * than once in the specified interval. 542 * 543 * <p>If the function is called before the interval is up, it will still be 544 * executed once the remainder of the interval elapses - it will not be 545 * cancelled. 546 * 547 * <p>However, if the function is called <i>multiple times</i> before the 548 * interval has elapsed, only the <i>last</i>function call will execute - 549 * calls prior to that one will be cancelled. 550 * 551 * <p>The function passed in will be run <i>asynchronously</i> via 552 * <code>setTimeout()</code>, so if you wish to process the results of the 553 * function call, you must do so via callback. 554 * 555 * @param {Function} func a function to be executed - must take no parameters 556 * and may optionally return a value. 557 * @param {Object} obj an object literal with additional parameters - 558 * also, passing a Number in as the second parameter will use that as 559 * <code>obj.interval</code> and the other params will be ignored/false. 560 * @param {Number} [obj.interval] the interval in milliseconds - the 561 * function will be prevented from executing more often than this (if 562 * omitted, 100ms). 563 * @param {Function} [obj.ok] an optional callback function that will 564 * handle the return value from the throttled function. 565 * @param {Function} [obj.fail] an optional fail callback function that 566 * will be called if the function throws any exceptions. 567 * @param {Boolean} [obj.drop] if drop is set to true, then additional 568 * function invocations that occur before the interval is up will be simply 569 * ignored rather than queued to execute at the end of the interval period. 570 * @returns {Function} a throttled function that can be executed the same 571 * way as the original function. 572 */ 573 baja.throttle = function (func, obj) { 574 obj = baja.objectify(obj, 'interval'); 575 576 var interval = obj.interval || 100, 577 ok = obj.ok || baja.ok, 578 fail = obj.fail || baja.fail, 579 drop = obj.drop; 580 581 var mustWaitUntil = 0, 582 waitingForTicket = baja.clock.expiredTicket; 583 584 function getTimeToWait() { 585 var now = baja.clock.ticks(), 586 timeToWait = mustWaitUntil - now; 587 if (timeToWait <= 0) { 588 mustWaitUntil = now + interval; 589 } 590 return timeToWait; 591 } 592 593 return function () { 594 var that = this, 595 args = Array.prototype.slice.call(arguments), 596 funcToRun, 597 timeToWait = getTimeToWait(); 598 599 if ((timeToWait > 0) && drop) { 600 return; 601 } 602 603 funcToRun = function () { 604 try { 605 ok(func.apply(that, args)); 606 } 607 catch (err) { 608 fail(err); 609 } 610 finally { 611 // once the function is done executing, always reset the next time it 612 // will be allowed to execute again 613 getTimeToWait(); 614 } 615 }; 616 617 // don't allow function executions to pile up - only have one waiting at a time 618 waitingForTicket.cancel(); 619 620 if (timeToWait <= 0) { 621 baja.runAsync(funcToRun); 622 } 623 else if (!drop) { 624 waitingForTicket = baja.clock.schedule(funcToRun, timeToWait); 625 } 626 }; 627 }; 628 629 /** 630 * Test an Object to see if it's a Baja Object and has the 'getType' function. 631 * <p> 632 * Please note: this test excludes objects that may extend baja.Slot. 633 * 634 * @param {Object} obj the Object to be tested. 635 * @param {String} [type] the type or (String type specification - module:typeName) to test object against. 636 * Please note, Type.is(...) is used and not equals. 637 * 638 * @returns {Boolean} true if the given Object is a proper BajaScript Object 639 */ 640 baja.hasType = function (obj, type) { 641 if (obj === null || obj === undefined) { 642 return false; 643 } 644 if (obj && typeof obj === "object" && obj instanceof baja.Slot) { 645 return false; 646 } 647 648 if (typeof obj.getType === "function") { 649 if (type) { 650 return obj.getType().is(type); 651 } 652 return true; 653 } 654 else { 655 return false; 656 } 657 }; 658 659 /** 660 * This is a conveniance method used for working with functions that take 661 * an Object Literal as an argument. 662 * <p> 663 * This method always ensures an Object is returned so its properties can be 664 * further validated. 665 * <p> 666 * In some cases, the function may take an object literal or a single argument. 667 * If the function can take a single argument that isn't an Object literal, then 668 * the 'propName' can be specified. If this is specified, an Object is created and 669 * the value is assigned to the Object with the specified property name. 670 * <pre> 671 * // For example, this function can take an Object literal or a Number 672 * function foo(obj) { 673 * obj = baja.objectify(obj, "num"); 674 * 675 * // 'obj' will always point to an Object. We can now test and work with 'num'... 676 * baja.strictArg(obj.num, Number); 677 * } 678 * 679 * // Both method invocations are valid... 680 * foo(23.4); 681 * foo({num: 23.4}); 682 * </pre> 683 * 684 * @private 685 * 686 * @param obj the Object literal or a value to be added onto an Object if propName is specified. 687 * @param {String} [propName] if the object isn't an Object, an Object is created and the value is assigned 688 * to the object with this property name. 689 * 690 * @returns {Object} an Object 691 */ 692 baja.objectify = function (obj, propName) { 693 if (!(obj === undefined || obj === null)) { 694 if (obj.constructor === Object) { 695 return obj; 696 } 697 else if (typeof propName === "string") { 698 var o = {}; 699 o[propName] = obj; 700 return o; 701 } 702 } 703 return {}; 704 }; 705 706 /** 707 * Returns a Lexicon Object for a given module. The locale will be whatever the current 708 * user is set too. 709 * <p> 710 * Please note, if BajaScript has Web Storage enabled, the Lexicon will be permanently cached. 711 * <p> 712 * If the Lexicon for the given module isn't loaded then a network call will be made. 713 * <pre> 714 * // Get a value from a Lexicon. A synchronous network call will be made if the Lexicon 715 * // isn't available locally. 716 * baja.lex("bajaui").get("dialog.ok"); 717 * 718 * // Get a value from a Lexicon. An asynchronous network call will be made if the Lexicon 719 * // isn't available locally. 720 * baja.lex({ 721 * module: "bajaui", 722 * ok: function (lex) { 723 * lex.get("dialog.ok"); 724 * } 725 * }); 726 * </pre> 727 * 728 * @see Lexicon 729 * 730 * @returns {Object} 731 */ 732 baja.lex = function (obj) { 733 obj = baja.objectify(obj, "module"); 734 735 var module = obj.module, 736 cb = new baja.comm.Callback(obj.ok, obj.fail, obj.batch), 737 lx; 738 739 // If already loaded then return the lexicon 740 if (bsLexicons.hasOwnProperty(module)) { 741 lx = bsLexicons[module]; 742 cb.ok(lx); 743 return lx; 744 } 745 746 // If the original ok handler wasn't defined then make a synchronous network call 747 var async = typeof obj.ok === "function"; 748 749 // Make a network call to get the lexicon 750 try { 751 752 // Add intermediate callback 753 cb.addOk(function (ok, fail, lexData) { 754 // If available, store this in web storage 755 if (bsRegStorage) { 756 bsRegStorage.lexicons[module] = lexData; 757 } 758 759 // Create the Lexicon and add it to our cached list 760 lx = new Lexicon(module, lexData); 761 bsLexicons[module] = lx; 762 ok(lx); 763 }); 764 765 baja.comm.lex(module, cb, async); 766 } 767 catch (err) { 768 cb.fail(err); 769 } 770 771 return lx; 772 }; 773 774 /** 775 * Run the specified Function asynchronously. 776 * 777 * @param {Function} fn the Function to run asynchronously. 778 */ 779 baja.runAsync = function (fn) { 780 baja.clock.schedule(fn, 0); 781 }; 782 783 (function iterate() { 784 function iterateArray(arr, start, end, func) { 785 var i, result; 786 787 for (i = start; i < end; i++) { 788 result = func(arr[i], i); 789 if (result !== undefined) { 790 return result; 791 } 792 } 793 } 794 795 function iterateCustomNext(obj, func, nextFunc) { 796 while (obj) { 797 var result = func(obj); 798 if (result !== undefined) { 799 return result; 800 } else { 801 obj = nextFunc(obj); 802 } 803 } 804 } 805 806 function iterateJsProperties(obj, func) { 807 var name, result; 808 for (name in obj) { 809 if (obj.hasOwnProperty(name)) { 810 result = func(obj[name], name); 811 if (result !== undefined) { 812 return result; 813 } 814 } 815 } 816 } 817 818 function iterateByIndex(start, end, func) { 819 var i, result; 820 821 for (i = start; i < end; i++) { 822 result = func(i); 823 if (result !== undefined) { 824 return result; 825 } 826 } 827 } 828 829 /** 830 * A iteration general utility method that performs the given function on every JavaScript 831 * property in the given cursor or Javascript array. This function can be called 832 * with a variety of parameter configurations. 833 * <pre> 834 * baja.iterate(array, function (arrayElement, arrayIndex)) 835 * 836 * baja.iterate(array, startIndex, function (arrayElement, arrayIndex)) 837 * 838 * baja.iterate(array, startIndex, endIndex, function (arrayElement, arrayIndex)) 839 * 840 * baja.iterate(numberOfTimesToIterate, function (index)) 841 * 842 * baja.iterate(iterationStartIndex, iterationEndIndex, function (index)) 843 * 844 * baja.iterate(object, function (objectJsProperty, objectJsPropertyName)) 845 * 846 * baja.iterate(object, function doIterate(object), function getNext(object)) 847 * </pre> 848 * 849 * <p><code>iterate()</code> is compatible with arrays, but <i>not</i> with 850 * <code>arguments</code> objects - pass in 851 * <code>Array.prototype.slice.call(arguments)</code> instead. 852 * 853 * <p>In the last case with the <code>doIterate</code> and 854 * <code>getNext</code> functions, <code>doIterate</code> performs the 855 * iterative action on the object, and <code>getNext</code> returns the 856 * next object to iterate (this will be passed directly back into 857 * <code>doIterate</code>). This is handy for walking up prototype chains, 858 * supertype chains, component hierarchies, etc. 859 * 860 * <p>In all cases, if the function being executed ever returns a value 861 * other than <code>undefined</code>, iteration will be stopped at that 862 * point and <code>iterate()</code> will return that value. 863 * 864 * <p>For invocations of <code>iterate()</code> that include start or end 865 * indexes, note that start indexes are inclusive and end indexes are 866 * exclusive (e.g. <code>iterate(2, 5, function (i) { baja.outln(i); })</code> 867 * would print <code>2,3,4</code>). 868 * 869 * @returns any non-undefined value that's returned from any function 870 * or Cursor. 871 */ 872 baja.iterate = function () { 873 var args = arguments, 874 arg0 = args[0], 875 arg1 = args[1], 876 arg2 = args[2], 877 arg3 = args[3], 878 typeString = Object.prototype.toString.call(arg0); 879 880 if (arg0 === undefined || arg0 === null) { 881 throw new Error("undefined passed to baja.iterate()"); 882 } 883 884 if (typeString === '[object Array]') { 885 switch (arguments.length) { 886 case 2: //iterate(array, function (arrayElement, arrayIndex)) 887 return iterateArray(arg0, 0, arg0.length, arg1); 888 889 case 3: //iterate(array, startIndex, function (arrayElement, arrayIndex)) 890 return iterateArray(arg0, arg1, arg0.length, arg2); 891 892 case 4: //iterate(array, startIndex, endIndex, function (arrayElement, arrayIndex)) 893 return iterateArray(arg0, arg1, arg2, arg3); 894 } 895 } 896 else if (typeString === '[object Object]') { 897 if (arg0 instanceof baja.Cursor) { 898 //bajaScriptCursor.each(function ()) 899 return arg0.each(arg1); 900 } 901 else if (typeof arg2 === 'function') { 902 //iterate(object, function doIterate(object), function getNext(object)) 903 return iterateCustomNext(arg0, arg1, arg2); 904 } 905 else { 906 //iterate(object, function (objectJsProperty, objectJsPropertyName)) 907 return iterateJsProperties(arg0, arg1); 908 } 909 } 910 else if (typeString === '[object Number]') { 911 if (typeof arg1 === 'number') { 912 //iterate(iterationStartIndex, iterationEndIndex, function (index)) 913 return iterateByIndex(arg0, arg1, arg2); 914 915 } 916 else { 917 //iterate(numberOfTimesToIterate, function (index)) 918 return iterateByIndex(0, arg0, arg1); 919 } 920 } else if (typeString === '[object Arguments]') { 921 throw new Error("Arguments object not iterable (pass through " + 922 "Array.prototype.slice first)"); 923 } 924 925 throw new Error(arg0 + " is not iterable"); 926 }; 927 }()); 928 929 //////////////////////////////////////////////////////////////// 930 // Ordered Map and Cursor - used as basis for ComplexSlotMap 931 //////////////////////////////////////////////////////////////// 932 933 var notImplementedStr = "Not implemented"; 934 935 /** 936 * @class A generic cursor used for iteration. 937 */ 938 baja.Cursor = function () { 939 }; 940 941 /** 942 * Return the current item. 943 * 944 * @returns the cursor value (null if none available). 945 */ 946 baja.Cursor.prototype.get = function () { 947 throw new Error(notImplementedStr); 948 }; 949 950 /** 951 * Iterate through the Cursor and call 'each' on every item. 952 * 953 * @param {Function} func function called on every iteration with the 'value' being used as an argument. 954 */ 955 baja.Cursor.prototype.each = function (func) { 956 throw new Error(notImplementedStr); 957 }; 958 959 /** 960 * @class An asynchonrous generic cursor used for iteration. 961 * <p> 962 * An Async Cursor may fetch its results asynchronously (i.e. across a network). 963 * 964 * @extends baja.Cursor 965 */ 966 baja.AsyncCursor = function () { 967 }.$extend(baja.Cursor); 968 969 /** 970 * @class A generic Synchronous cursor used for iteration. 971 * 972 * @extends baja.Cursor 973 */ 974 baja.SyncCursor = function () { 975 }.$extend(baja.Cursor); 976 977 /** 978 * Advance cursor and return true if successful. 979 * 980 * @returns {Boolean} 981 */ 982 baja.SyncCursor.prototype.next = function () { 983 throw new Error(notImplementedStr); 984 }; 985 986 /** 987 * @class A filtered cursor used for iteration. 988 * <p> 989 * This Cursor is a generic Cursor used for iteration in a {@link baja.OrderedMap}. 990 * 991 * @see baja.OrderedMap 992 * 993 * @name baja.FilterCursor 994 * @extends baja.SyncCursor 995 */ 996 baja.FilterCursor = function (context, orderedMap) { 997 baja.FilterCursor.$super.apply(this, arguments); 998 this.$context = context; 999 this.$orderedMap = orderedMap; 1000 this.$keys = orderedMap && orderedMap.getKeys(); 1001 this.$filter = null; 1002 this.$index = -1; 1003 }.$extend(baja.SyncCursor); 1004 1005 function filterNext(cursor) { 1006 if (cursor.$index < cursor.$keys.length) { 1007 ++cursor.$index; 1008 return cursor.$index !== cursor.$keys.length; 1009 } 1010 else { 1011 return false; 1012 } 1013 } 1014 1015 /** 1016 * Advance cursor and return true if successful. 1017 * 1018 * @function 1019 * 1020 * @returns {Boolean} 1021 */ 1022 baja.FilterCursor.prototype.next = function () { 1023 var that = this; 1024 1025 if (!that.$filter) { 1026 return filterNext(this); 1027 } 1028 else { 1029 // If a Constructor has been passed in then keep iterating 1030 // until we find a matching element 1031 do { 1032 if (!filterNext(this)) { 1033 return false; 1034 } 1035 } 1036 while (!that.$filter.call(that.$context, that.get())); 1037 return true; 1038 } 1039 }; 1040 1041 /** 1042 * Return the current item. If this is a SlotCursor, this will 1043 * return a Slot. 1044 * 1045 * @returns the cursor value (null if none available). 1046 */ 1047 baja.FilterCursor.prototype.get = function () { 1048 var x = this.$index, 1049 keys = this.$keys; 1050 1051 if (x === -1 || x >= keys.length) { 1052 return null; 1053 } 1054 else { 1055 return this.$orderedMap.get(keys[x]); 1056 } 1057 }; 1058 1059 /** 1060 * Return the current key. 1061 * <p> 1062 * This is a private method and shouldn't be used by non-Tridium developers. 1063 * 1064 * @private 1065 * 1066 * @returns {String} the cursor key (null if none available). 1067 */ 1068 baja.FilterCursor.prototype.getKey = function () { 1069 if (this.$index === -1) { 1070 return null; 1071 } 1072 else { 1073 return this.$keys[this.$index]; 1074 } 1075 }; 1076 1077 /** 1078 * Return the current index 1079 * <p> 1080 * This is a private method and shouldn't be used by non-Tridium developers. 1081 * 1082 * @private 1083 * 1084 * @returns {Number} the cursor index (null if none available). 1085 */ 1086 baja.FilterCursor.prototype.getIndex = function () { 1087 return this.$index; 1088 }; 1089 1090 /** 1091 * Iterate through the Cursor and call 'each' on every item. 1092 * <p> 1093 * When the function is called, 'this' refers to the 'context' that 1094 * was passed in when the Cursor was created. 1095 * 1096 * @param {Function} func function called on every iteration with the 'value' being used as an argument. 1097 * If this is a Slot Cursor the 'value' will be a Slot. 1098 */ 1099 baja.FilterCursor.prototype.each = function (func) { 1100 strictArg(func, Function); 1101 1102 var result; 1103 while (this.next()) { 1104 result = func.call(this.$context, this.get(), this.getIndex()); 1105 1106 if (result) { 1107 return result; 1108 } 1109 } 1110 }; 1111 1112 /** 1113 * Return true if the Cursor is completely empty (regardless of iterative state). 1114 * 1115 * @returns {Boolean} 1116 */ 1117 baja.FilterCursor.prototype.isEmpty = function () { 1118 // Note the old cursor index 1119 var oldX = this.$index; 1120 1121 // Set the cursor back to the start 1122 this.$index = -1; 1123 1124 // See if we have any valid entries from the start 1125 var res = this.next(); 1126 1127 // Restore the old cursor index 1128 this.$index = oldX; 1129 1130 // Return the result of our search 1131 return !res; 1132 }; 1133 1134 /** 1135 * Return an array of the cursor results (regardless of iterative state). 1136 * If this is a Slot Cursor, this will be an array of Slots. 1137 * 1138 * @returns {Array} 1139 */ 1140 baja.FilterCursor.prototype.toArray = function () { 1141 // Note the old cursor index 1142 var oldX = this.$index, 1143 a = []; 1144 1145 this.$index = -1; 1146 1147 // Iterate through and fill up the array 1148 while (this.next()) { 1149 a.push(this.get()); 1150 } 1151 1152 // Restore the old cursor index 1153 this.$index = oldX; 1154 1155 return a; 1156 }; 1157 1158 /** 1159 * Return an Object Map of keys with their corresponding values. 1160 * If this is a Slot Cursor, this will be a Map of Slot names with their 1161 * corresponding Slots (regardless of iterative state). 1162 * 1163 * @returns {Object} 1164 */ 1165 baja.FilterCursor.prototype.toMap = function () { 1166 var slots = this.toArray(), 1167 map = {}, 1168 s, 1169 i; 1170 1171 for (i = 0; i < slots.length; ++i) { 1172 s = slots[i]; 1173 map[s.getName()] = s; 1174 } 1175 1176 return map; 1177 }; 1178 1179 /** 1180 * Return the size of the cursor (regardless of iterative state). 1181 * 1182 * @returns {Number} 1183 */ 1184 baja.FilterCursor.prototype.getSize = function () { 1185 // Note the old cursor index 1186 var oldX = this.$index, 1187 count = 0; 1188 1189 this.$index = -1; 1190 1191 // Iterate through and fill up the array 1192 while (this.next()) { 1193 ++count; 1194 } 1195 1196 // Restore the old cursor index 1197 this.$index = oldX; 1198 1199 return count; 1200 }; 1201 1202 /** 1203 * Add a filter function to the Cursor. 1204 * 1205 * @param {Function} filter used to filter the results of the Cursor. 1206 * When invoked, the first argument will be the item to filter (i.e. a Slot). 1207 * This function must return a true value for the item to be kept. 1208 * @returns {baja.FilterCursor} itself. 1209 */ 1210 baja.FilterCursor.prototype.filter = function (flt) { 1211 if (!this.$filter) { 1212 this.$filter = flt; 1213 } 1214 else { 1215 var oldFilter = this.$filter; 1216 1217 // Merge the filter functions together 1218 this.$filter = function (val) { 1219 return oldFilter.call(this, val) && flt.call(this, val); 1220 }; 1221 } 1222 return this; 1223 }; 1224 1225 /** 1226 * Return the first item in the cursor (regardless of iterative state). 1227 * <p> 1228 * If this is being used as a Slot Cursor, the Slot will be returned. 1229 * 1230 * @returns first item found in the Cursor (or null if nothing found). 1231 */ 1232 baja.FilterCursor.prototype.first = function () { 1233 // Note the old cursor index 1234 var oldX = this.$index, 1235 val = null; 1236 1237 this.$index = -1; 1238 1239 // Iterate through and fill up the array 1240 if (this.next()) { 1241 val = this.get(); 1242 } 1243 1244 // Restore the old cursor index 1245 this.$index = oldX; 1246 1247 return val; 1248 }; 1249 1250 /** 1251 * Return the last item in the cursor (regardless of iterative state). 1252 * <p> 1253 * If this is being used as a Slot Cursor, the Slot will be returned. 1254 * 1255 * @returns last item found in the Cursor (or null if nothing found). 1256 */ 1257 baja.FilterCursor.prototype.last = function () { 1258 // Note the old cursor index 1259 var oldX = this.$index, 1260 val = null; 1261 1262 this.$index = this.$keys.length - 2; 1263 1264 // Iterate through and fill up the array 1265 if (this.next()) { 1266 val = this.get(); 1267 } 1268 1269 // Restore the old cursor index 1270 this.$index = oldX; 1271 1272 return val; 1273 }; 1274 1275 /** 1276 * @class Maintains an ordered list of key/value pairs. 1277 * <p> 1278 * This object forms the basis of a Complex's Slot Map. 1279 * 1280 * @name baja.OrderedMap 1281 * @extends BaseBajaObj 1282 * @private 1283 */ 1284 baja.OrderedMap = function () { 1285 baja.OrderedMap.$super.apply(this, arguments); 1286 this.$map = {}; // Normal unordered Map 1287 this.$array = []; // Array for maintaining order of keys 1288 }.$extend(BaseBajaObj); 1289 1290 /** 1291 * Assign the value to the Map with the given key. 1292 * 1293 * @private 1294 * 1295 * @param {String} key the key used for the entry in the Map 1296 * @param val the value used to store in the Map 1297 */ 1298 baja.OrderedMap.prototype.put = function (key, val) { 1299 if (!this.$map.hasOwnProperty(key)) { 1300 this.$map[key] = val; 1301 this.$array.push(key); 1302 } 1303 else { 1304 this.$map[key] = val; 1305 } 1306 }; 1307 1308 /** 1309 * Remove a value from the map and return it. 1310 * 1311 * @private 1312 * 1313 * @param {String} key the key used to remove the value. 1314 * @returns the value removed (return null if nothing is found to be removed). 1315 */ 1316 baja.OrderedMap.prototype.remove = function (key) { 1317 strictArg(key, String); 1318 var v, i; 1319 if (this.$map.hasOwnProperty(key)) { 1320 v = this.$map[key]; 1321 1322 // Remove the element from the Map 1323 delete this.$map[key]; 1324 1325 // Find and remove the key from the array 1326 for (i = 0; i < this.$array.length; ++i) { 1327 if (this.$array[i] === key) { 1328 this.$array.splice(i, 1); 1329 break; 1330 } 1331 } 1332 1333 return v; 1334 } 1335 else { 1336 return null; 1337 } 1338 }; 1339 1340 /** 1341 * Query the Map to see if it contains the key. 1342 * 1343 * @private 1344 * 1345 * @param {String} key 1346 * @returns {Boolean} a boolean value indicating if the Map contains the key. 1347 */ 1348 baja.OrderedMap.prototype.contains = function (key) { 1349 strictArg(key, String); 1350 return this.$map.hasOwnProperty(key); 1351 }; 1352 1353 /** 1354 * Return the value for the key. 1355 * 1356 * @private 1357 * 1358 * @param {String} key 1359 * @returns the value for the key (return null if key is not found in Map). 1360 */ 1361 baja.OrderedMap.prototype.get = function (key) { 1362 if (this.$map.hasOwnProperty(key)) { 1363 return this.$map[key]; 1364 } 1365 else { 1366 return null; 1367 } 1368 }; 1369 1370 /** 1371 * Rename an entry. 1372 * 1373 * @private 1374 * 1375 * @param {String} oldName the name of the existing entry to be renamed. 1376 * @param {String} newName the new name of the entry. 1377 * @returns {Boolean} true if the entry was successfully renamed. 1378 */ 1379 baja.OrderedMap.prototype.rename = function (oldName, newName) { 1380 strictArg(oldName, String); 1381 strictArg(newName, String); 1382 1383 if (!this.contains(oldName)) { 1384 return false; 1385 } 1386 if (this.contains(newName)) { 1387 return false; 1388 } 1389 1390 // Get existing entry 1391 var entry = this.$map[oldName]; 1392 delete this.$map[oldName]; 1393 1394 // Create new entry 1395 this.$map[newName] = entry; 1396 1397 // Update array 1398 var i; 1399 for (i = 0; i < this.$array.length; ++i) { 1400 if (this.$array[i] === oldName) { 1401 this.$array[i] = newName; 1402 break; 1403 } 1404 } 1405 1406 return true; 1407 }; 1408 1409 /** 1410 * Return the key's index. 1411 * 1412 * @private 1413 * 1414 * @param {String} key 1415 * @returns {Number} the index for the key (return -1 if key is not found in Map). 1416 */ 1417 baja.OrderedMap.prototype.getIndex = function (key) { 1418 strictArg(key, String); 1419 if (this.$map.hasOwnProperty(key)) { 1420 var i; 1421 for (i = 0; i < this.$array.length; ++i) { 1422 if (this.$array[i] === key) { 1423 return i; 1424 } 1425 } 1426 } 1427 return -1; 1428 }; 1429 1430 /** 1431 * Return the value for the index. 1432 * 1433 * @private 1434 * 1435 * @param {Number} index 1436 * @returns the value for the index (return null if index is not found in Map). 1437 */ 1438 baja.OrderedMap.prototype.getFromIndex = function (index) { 1439 strictArg(index, Number); 1440 var key = this.$array[index]; 1441 if (typeof key === "string") { 1442 return this.$map[key]; 1443 } 1444 return null; 1445 }; 1446 1447 /** 1448 * Return an ordered array of keys for iteration. 1449 * <p> 1450 * Please note, a copy of the internal array will be returned. 1451 * 1452 * @private 1453 * 1454 * @returns {Array} an array of keys that can be used for iteration. 1455 */ 1456 baja.OrderedMap.prototype.getKeys = function () { 1457 // Return a copy of the array 1458 return this.$array.slice(0); 1459 }; 1460 1461 /** 1462 * Return the size of the Map. 1463 * 1464 * @private 1465 * 1466 * @returns {Number} the size of the Map. 1467 */ 1468 baja.OrderedMap.prototype.getSize = function () { 1469 return this.$array.length; 1470 }; 1471 1472 /** 1473 * Sort the Map. 1474 * 1475 * @private 1476 * 1477 * @see Array#sort 1478 * 1479 * @param {Function} sortFunc Function used to sort the map. This definition of the Function 1480 * should be the same as the one passed into a JavaScript Array's 1481 * sort method. 1482 */ 1483 baja.OrderedMap.prototype.sort = function (sortFunc) { 1484 strictArg(sortFunc, Function); 1485 this.$array.sort(sortFunc); 1486 }; 1487 1488 /** 1489 * Return a Cursor used for iteration. 1490 * 1491 * @private 1492 * 1493 * @param context used as 'this' in iteration operations 1494 * @param {Function} the Constructor of the Cursor to use for the Map. 1495 * 1496 * @returns {Cursor} Cursor used for iteration 1497 */ 1498 baja.OrderedMap.prototype.getCursor = function (context, Cursor) { 1499 Cursor = Cursor || baja.FilterCursor; 1500 return new Cursor(context, this); 1501 }; 1502 }()); 1503 1504 // Create local for improved minification 1505 var strictArg = baja.strictArg, 1506 strictAllArgs = baja.strictAllArgs, 1507 bajaDef = baja.def, 1508 objectify = baja.objectify; 1509 1510 //////////////////////////////////////////////////////////////// 1511 // Clock 1512 //////////////////////////////////////////////////////////////// 1513 1514 (function bajaClockNamespace() { 1515 1516 /** 1517 * @namespace Baja Clock methods used for scheduling. 1518 */ 1519 baja.clock = new BaseBajaObj(); 1520 1521 /** 1522 * @class Clock Ticket used when scheduling function calls in BajaScript. 1523 * <p> 1524 * This Constructor shouldn't be invoked directly. 1525 * 1526 * @see baja.clock.schedule 1527 * 1528 * @name Ticket 1529 * @extends BaseBajaObj 1530 * @inner 1531 * @public 1532 */ 1533 var Ticket = function () { 1534 this.$id = -1; 1535 }.$extend(BaseBajaObj); 1536 1537 /** 1538 * Cancel the currently scheduled Ticket. 1539 */ 1540 Ticket.prototype.cancel = function () { 1541 clearTimeout(this.$id); 1542 delete bsClockTimeouts[this.$id.toString()]; 1543 this.$id = -1; 1544 }; 1545 1546 /** 1547 * Test for ticket expiration. 1548 * 1549 * @returns {Boolean} if the scheduled Ticket is currently expired. 1550 */ 1551 Ticket.prototype.isExpired = function () { 1552 return this.$id === -1; 1553 }; 1554 1555 /** 1556 * @class Clock Ticket used when scheduling periodic events in Niagara. 1557 * <p> 1558 * This Constructor shouldn't be invoked directly. 1559 * 1560 * @name IntervalTicket 1561 * @extends Ticket 1562 * @inner 1563 * @public 1564 */ 1565 var IntervalTicket = function () { 1566 IntervalTicket.$super.apply(this, arguments); 1567 }.$extend(Ticket); 1568 1569 /** 1570 * Cancel the currently scheduled Ticket. 1571 */ 1572 IntervalTicket.prototype.cancel = function () { 1573 clearInterval(this.$id); 1574 delete bsClockIntervals[this.$id.toString()]; 1575 this.$id = -1; 1576 }; 1577 1578 /** 1579 * Returns the number of Clock ticks on the system. 1580 * <p> 1581 * This method is typically used for profiling. 1582 * 1583 * @returns {Number} the number of Clock ticks on the system. 1584 */ 1585 baja.clock.ticks = function () { 1586 return new Date().valueOf(); 1587 }; 1588 1589 /** 1590 * An expired Ticket. 1591 */ 1592 baja.clock.expiredTicket = new Ticket(); 1593 1594 /** 1595 * Schedule a one off timer. 1596 * <p> 1597 * When the callback is invoked, 'this' will refer to the Ticket instance. 1598 * <p> 1599 * If any variables need to be passed into this function then it's best to do this via 1600 * JavaScript closures. 1601 * 1602 * @param {Function} func the function to be invoked once the specified time has elapsed. 1603 * @param {Number} time the number of milliseconds before the event is run. 1604 * @returns {Ticket} a new Ticket for the scheduled event. 1605 */ 1606 baja.clock.schedule = function (func, time) { 1607 strictAllArgs([func, time], [Function, Number]); 1608 1609 // If BajaScript has fully stopped then don't schedule anything else... 1610 if (baja.isStopped()) { 1611 return this.expiredTicket; 1612 } 1613 1614 // Create ticket before timer so we get closure 1615 var t = new Ticket(); 1616 t.$id = setTimeout(function () { 1617 delete bsClockTimeouts[t.$id.toString()]; 1618 t.$id = -1; 1619 func.call(t); 1620 }, time); 1621 1622 // Register the ticket so we can keep track of it 1623 bsClockTimeouts[t.$id.toString()] = t; 1624 1625 return t; 1626 }; 1627 1628 /** 1629 * Schedule a periodic timer. 1630 * <p> 1631 * When the callback is invoked, 'this' will refer to the Ticket instance. 1632 * <p> 1633 * If any variables need to be passed into this function then it's best to do this via 1634 * JavaScript closures. 1635 * 1636 * @param {Function} func the function to be invoked each time the specified time has elapsed. 1637 * @param {Number} time the number of milliseconds before the event is run. 1638 * 1639 * @returns {IntervalTicket} a new Ticket for the scheduled event. 1640 */ 1641 baja.clock.schedulePeriodically = function (func, time) { 1642 strictAllArgs([func, time], [Function, Number]); 1643 1644 // If BajaScript has fully stopped then don't schedule anything else... 1645 if (baja.isStopped()) { 1646 return this.expiredTicket; 1647 } 1648 1649 // Create ticket before timer so we get closure 1650 var t = new IntervalTicket(); 1651 t.$id = setInterval(function () { 1652 func.call(t); 1653 }, time); 1654 1655 // Keep track of the ticket internally 1656 bsClockIntervals[t.$id.toString()] = t; 1657 1658 return t; 1659 }; 1660 }()); 1661 1662 //////////////////////////////////////////////////////////////// 1663 // BajsScript Start and Stop 1664 //////////////////////////////////////////////////////////////// 1665 1666 (function startAndStop() { 1667 var bsStarted = false, // BajaScript started flag 1668 bsStopped = false, // BajaScript stopped flag 1669 bsStopping = false, // BajaScript stopping flag 1670 bsStartedCallbacks = null, // Started callbacks 1671 bsPreStopCallbacks = null, // Stopped callbacks 1672 bsUserName = "", // BajaScript user name for current session 1673 bsLang = "", // BajaScript language for user 1674 bsUserHome = "station:|slot:/", // BajaScript user home 1675 bsTimeFormat = "", // BajaScript user time format pattern 1676 bsLogClientErrorsInServer = false, // BajaScript log client errors in the Server 1677 bsPreLoadRegStorage = null; // The pre-loaded BajaScript Registry storage 1678 1679 /** 1680 * Return true if BajaScript has started. 1681 * 1682 * @returns {Boolean} started 1683 */ 1684 baja.isStarted = function () { 1685 return bsStarted; 1686 }; 1687 1688 /** 1689 * Return true if BajaScript has stopped. 1690 * 1691 * @returns {Boolean} stopped 1692 */ 1693 baja.isStopped = function () { 1694 return bsStopped; 1695 }; 1696 1697 /** 1698 * Return true if BajaScript has stopped or is in the process of stopping. 1699 * 1700 * @returns {Boolean} stopping 1701 */ 1702 baja.isStopping = function () { 1703 return bsStopping; 1704 }; 1705 1706 /** 1707 * Add a function to be invoked once BajaScript has started. 1708 * <p> 1709 * If BajaScript has already started, the function will be invoked immediately. 1710 * 1711 * @param {Function} func invoked once BajaScript has started. 1712 */ 1713 baja.started = function (func) { 1714 strictArg(func, Function); 1715 1716 // If we're already started then return immediately 1717 if (bsStarted) { 1718 try { 1719 func.call(baja); 1720 } 1721 catch (err) { 1722 baja.error(err); 1723 } 1724 return; 1725 } 1726 1727 // If not started yet then add the function to a callback list 1728 if (!bsStartedCallbacks) { 1729 bsStartedCallbacks = []; 1730 } 1731 1732 bsStartedCallbacks.push(func); 1733 }; 1734 1735 /** 1736 * Add a function to be invoked just before BajaScript is stopped. 1737 * <p> 1738 * If BajaScript has already stopped, the function will be invoked immediately. 1739 * 1740 * @param {Function} func 1741 */ 1742 baja.preStop = function (func) { 1743 strictArg(func, Function); 1744 1745 // If we're already started then return immediately 1746 if (bsStopped) { 1747 try { 1748 func.call(baja); 1749 } 1750 catch (err) { 1751 baja.error(err); 1752 } 1753 return; 1754 } 1755 1756 // If not started yet then add the function to a callback list 1757 if (!bsPreStopCallbacks) { 1758 bsPreStopCallbacks = []; 1759 } 1760 bsPreStopCallbacks.push(func); 1761 }; 1762 1763 /** 1764 * Start BajaScript. 1765 * <p> 1766 * This must be called to start BajaScript. This will make a network call to create a Session 1767 * that starts BajaScript. It's recommended to call this as soon as possible. 1768 * <p> 1769 * This method takes a started function or an Object Literal for the method's arguments... 1770 * <pre> 1771 * baja.start(function () { 1772 * // Called once BajaScript has started. 1773 * }); 1774 * 1775 * //...or this can be invoked via an Object Literal... 1776 * 1777 * baja.start({ 1778 * started: function () { 1779 * // Called when BajaScript has started. We're ready to rock and roll at this point! 1780 * }, 1781 * commFail: function () { 1782 * // Called when the BajaScript communcations engine completely fails 1783 * }, 1784 * typeSpecs: ["control:BooleanWritable", "control:NumericWritable"] // Types and Contracts we want imported 1785 * // upfront before our Web App loads 1786 * }); 1787 * </pre> 1788 * 1789 * @see baja.started 1790 * @see baja.save 1791 * @see baja.preStop 1792 * @see baja.stop 1793 * 1794 * @param {Object|Function} [obj] the Object Literal for the method's arguments or the function invoke after the comms have started. 1795 * @param {Function} [obj.started] function to invoke after the comms have started and (if running in a browser) the DOM is ready. 1796 * @param {Function} [obj.commFail] function to invoke if the comms fail. 1797 * @param {Array} [obj.typeSpecs] an array of type specs (moduleName:typeName) to import from the Server. 1798 * Please note, this may not be needed if the Type and Contract information 1799 * has been cached by web storage. 1800 * @param {Boolean} [obj.navFile] if true, this will load the nav file for the user on start up. By default, this is false. 1801 */ 1802 baja.start = function (obj) { 1803 1804 // Initialize Comms Engine 1805 obj = objectify(obj, "started"); 1806 1807 var started = obj.started; 1808 obj.started = function () { 1809 // Signal that we have started 1810 bsStarted = true; 1811 1812 // Call started callbacks 1813 if (bsStartedCallbacks) { 1814 var i; 1815 for (i = 0; i < bsStartedCallbacks.length; ++i) { 1816 try { 1817 bsStartedCallbacks[i].call(baja); 1818 } 1819 catch (err) { 1820 baja.error(err); 1821 } 1822 } 1823 bsStartedCallbacks = null; 1824 } 1825 1826 // Invoke original started function 1827 if (typeof started === "function") { 1828 try { 1829 started(); 1830 } 1831 catch (err2) { 1832 baja.error(err2); 1833 } 1834 } 1835 }; 1836 1837 baja.comm.start(obj); 1838 1839 // Registry the common type library 1840 baja.registry.register(JSON.parse(baja.$ctypes)); 1841 delete baja.$ctypes; 1842 1843 // Load Web Storage while the BajaScript comms engine starts. 1844 bsPreLoadRegStorage = baja.registry.loadFromStorage(); 1845 1846 // If a pre-start function was passed in then invoke that here. 1847 if (typeof obj.preStart === "function") { 1848 obj.preStart(); 1849 } 1850 }; 1851 1852 /** 1853 * Save BajaScript's Registry Storage. This is automatically called when 1854 * BajaScript stops. 1855 */ 1856 baja.save = function () { 1857 // If available, save registry information to storage 1858 if (bsRegStorage) { 1859 baja.registry.saveToStorage(bsRegStorage); 1860 } 1861 }; 1862 1863 /** 1864 * Stop BajaScript. 1865 * <p> 1866 * This method should be called when the page running BajaScript is unloaded. This will 1867 * make a network call to stop BajaScript's session. 1868 * <p> 1869 * This method takes a stopped function or an Object Literal for the method's arguments... 1870 * <pre> 1871 * baja.stop(function () { 1872 * // Called once stop has completed 1873 * }); 1874 * 1875 * //...or this can be invoked via an Object Literal... 1876 * 1877 * baja.stop({ 1878 * stopped: function () { 1879 * // Called once stop has completed 1880 * } 1881 * }); 1882 * </pre> 1883 * 1884 * @see baja.start 1885 * @see baja.started 1886 * @see baja.preStop 1887 * 1888 * @param {Object|Function} [obj] the Object Literal for the method's arguments or a function 1889 * to be called once BajaScript has stopped. 1890 * @param {Function} [obj.stopped] called once BajaScript has stopped. 1891 * @param {Function} [obj.preStop] called just before BajaScript has stopped. 1892 */ 1893 baja.stop = function (obj) { 1894 try { 1895 // Don't allow stop to happen twice 1896 if (bsStopped) { 1897 return; 1898 } 1899 1900 // Flag up that we're in the middle of stopping BajaScript... 1901 bsStopping = true; 1902 1903 baja.save(); 1904 1905 obj = objectify(obj, "stopped"); 1906 1907 if (typeof obj.preStop === "function") { 1908 baja.preStop(obj.preStop); 1909 } 1910 1911 // Call preStop callbacks 1912 if (bsPreStopCallbacks) { 1913 var i; 1914 for (i = 0; i < bsPreStopCallbacks.length; ++i) { 1915 try { 1916 bsPreStopCallbacks[i].call(baja); 1917 } 1918 catch (err) { 1919 baja.error(err); 1920 } 1921 } 1922 bsPreStopCallbacks = null; 1923 } 1924 1925 // Stop all registered timers 1926 var id; 1927 for (id in bsClockTimeouts) { 1928 if (bsClockTimeouts.hasOwnProperty(id)) { 1929 bsClockTimeouts[id].cancel(); 1930 } 1931 } 1932 1933 for (id in bsClockIntervals) { 1934 if (bsClockIntervals.hasOwnProperty(id)) { 1935 bsClockIntervals[id].cancel(); 1936 } 1937 } 1938 1939 // These should be empty but we'll recreate them anyway 1940 bsClockTimeouts = {}; 1941 bsClockIntervals = {}; 1942 1943 // Stop Comms Engine 1944 baja.comm.stop(obj); 1945 } 1946 finally { 1947 // Signal that BajaScript has fully stopped 1948 bsStopped = true; 1949 } 1950 }; 1951 1952 /** 1953 * Returns the user name the user is currently logged in with. 1954 * 1955 * @returns {String} the user name. 1956 */ 1957 baja.getUserName = function () { 1958 return bsUserName; 1959 }; 1960 1961 /** 1962 * Returns language code for the user. 1963 * 1964 * @returns {String} the language character code. 1965 */ 1966 baja.getLanguage = function () { 1967 return bsLang; 1968 }; 1969 1970 /** 1971 * Return the user home. 1972 * 1973 * @returns {baja.Ord} 1974 */ 1975 baja.getUserHome = function () { 1976 return baja.Ord.make(bsUserHome); 1977 }; 1978 1979 /** 1980 * Return the user's default time format pattern. 1981 * 1982 * @returns {String} 1983 */ 1984 baja.getTimeFormatPattern = function () { 1985 return bsTimeFormat; 1986 }; 1987 1988 /** 1989 * Return true if any client Errors are also being logged in the Server. 1990 * <p> 1991 * Please note, since BOX and HTTP Errors are technically from the Server, 1992 * these do not constitute as client Errors. 1993 * 1994 * @returns {Boolean} 1995 */ 1996 baja.isLogClientErrorsInServer = function () { 1997 return bsLogClientErrorsInServer; 1998 }; 1999 2000 /** 2001 * Initialize BajaScript from System Properties. 2002 * 2003 * @private 2004 * 2005 * @param {Object} props System Properties loaded from Server. 2006 */ 2007 baja.initFromSysProps = function (props) { 2008 // Record system properties... 2009 bsUserName = props.userName; 2010 bsLang = props.lang; 2011 bsUserHome = props.userHome; 2012 bsTimeFormat = props.timeFormat; 2013 bsLogClientErrorsInServer = props.logClientErrors || false; 2014 2015 // Bail if web storage isn't enabled 2016 if (!props.enableWebStorage) { 2017 // Free up the reference to original loaded data 2018 bsPreLoadRegStorage = null; 2019 2020 // If Storage isn't enabled then try clearing it anyway 2021 baja.registry.clearStorage(); 2022 return; 2023 } 2024 2025 if (bsPreLoadRegStorage) { 2026 // If any of this information has changed then we need to wipe the registry storage and start again... 2027 if (bsPreLoadRegStorage.lang !== props.lang || 2028 bsPreLoadRegStorage.version !== baja.version || 2029 bsPreLoadRegStorage.regLastBuildTime !== props.regLastBuildTime) 2030 { 2031 bsPreLoadRegStorage = null; 2032 baja.registry.clearStorage(); 2033 } 2034 } 2035 2036 if (!bsPreLoadRegStorage) { 2037 bsPreLoadRegStorage = { 2038 "lang": props.lang, 2039 "version": baja.version, 2040 "regLastBuildTime": props.regLastBuildTime, 2041 "types": {}, 2042 "lexicons": {} 2043 }; 2044 } 2045 2046 // Now we know we want to use this as the registry storage, assign it accordingly. 2047 bsRegStorage = bsPreLoadRegStorage; 2048 2049 // Free up the reference to original loaded data 2050 bsPreLoadRegStorage = null; 2051 2052 // Submit all Type information into the registry 2053 baja.registry.register(bsRegStorage.types); 2054 2055 // Load the cached Lexicons 2056 var moduleName; 2057 for (moduleName in bsRegStorage.lexicons) { 2058 if (bsRegStorage.lexicons.hasOwnProperty(moduleName)) { 2059 bsLexicons[moduleName] = new Lexicon(moduleName, bsRegStorage.lexicons[moduleName]); 2060 } 2061 } 2062 }; 2063 }()); // startAndStop 2064 2065 //////////////////////////////////////////////////////////////// 2066 // Types and Registry 2067 //////////////////////////////////////////////////////////////// 2068 2069 (function registry() { 2070 function loadTypes(registry, typeSpecs, encodeContracts, cb, async) { 2071 strictAllArgs([typeSpecs, encodeContracts, cb, async], 2072 [Array, Boolean, baja.comm.Callback, Boolean]); 2073 2074 var rts = [], // Types to request 2075 t, // Type 2076 i; 2077 2078 // Only request Types that aren't already present in the registry 2079 2080 for (i = 0; i < typeSpecs.length; ++i) { 2081 if (registry.$types.hasOwnProperty(typeSpecs[i])) { 2082 t = registry.$types[typeSpecs[i]]; 2083 if (encodeContracts && (t.isComplex() || t.isFrozenEnum()) && !t.hasContract()) { 2084 rts.push(typeSpecs[i]); 2085 } 2086 } 2087 else { 2088 rts.push(typeSpecs[i]); 2089 } 2090 } 2091 2092 var ts = null; 2093 2094 // Create inner callback 2095 cb.addOk(function (ok, fail, resp) { 2096 ts = []; 2097 var i; 2098 2099 if (resp !== undefined) { 2100 // Add information to the registry and make sure registry storage is updated. 2101 registry.register(resp, /*updateRegStorageTypes*/true); 2102 } 2103 2104 // Get all registry information we requested 2105 for (i = 0; i < typeSpecs.length; ++i) { 2106 ts.push(registry.$types[typeSpecs[i]]); 2107 } 2108 2109 ok(ts); 2110 }); 2111 2112 // If there are Types to request then make the network call 2113 if (rts.length > 0) { 2114 baja.comm.loadTypes(rts, encodeContracts, cb, async); 2115 } 2116 else { 2117 // If we already have all of the Type information then return it 2118 cb.ok(); 2119 } 2120 2121 return ts; 2122 } 2123 2124 /** 2125 * Return an instance from the Type. 2126 * <p> 2127 * When creating an instance of a Type, this method should always be used by preference... 2128 * <pre> 2129 * // Create an instance of a NumericWritable Control Component... 2130 * var v = baja.$("control:NumericWritable"); 2131 * </pre> 2132 * <p> 2133 * At first this 'dollar' function looks a bit strange. However, much like other popular JavaScript libraries, this 2134 * function has been reserved for the most commonly used part of BajaScript; creating instances of BajaScript Objects. 2135 * 2136 * @see Type#getInstance 2137 * 2138 * @param {String} typeSpec the Type Specification. 2139 * @returns an instance from the Type 2140 */ 2141 baja.$ = function (typeSpec) { 2142 // Get a fully loaded Type 2143 var type = baja.lt(typeSpec, /*encodeContracts*/true); 2144 2145 // If only the TypeSpec was specified then just create an instance of that Type 2146 if (arguments.length === 1) { 2147 return type.getInstance(); 2148 } 2149 else { 2150 // If more arguments were specified then pass them onto to 'getInstance' 2151 var args = Array.prototype.slice.call(arguments); 2152 args.shift(); // remove the first 'typeSpec argument 2153 return type.getInstance.apply(type, args); 2154 } 2155 }; 2156 2157 /** 2158 * Load the Type and Contract information for the given TypeSpecs. 2159 * <p> 2160 * A TypeSpec is a String in the format of 'moduleName:typeName' in Niagara. 2161 * <p> 2162 * This method may perform a network call if one of the TypeSpecs can't be found locally in the 2163 * Registry or if a Contract needs to be retrieved. 2164 * <p> 2165 * If a network call is expected, it's recommended to pass in ok and fail callback functions that will load the Type 2166 * information asynchronously. 2167 * <p> 2168 * If a number of network calls is expected to be made then a batch {@link baja.comms.Batch} object 2169 * can be passed into this method along with a callback. The network call will then be 'batched' accordingly. 2170 * <p> 2171 * The method can be invoked with an array of TypeSpecs or an Object Literal... 2172 * <pre> 2173 * baja.importTypes(["control:NumericWritable", "control:BooleanWritable"]); 2174 * 2175 * // ...or via an Object Literal to specify further options... 2176 * 2177 * baja.importTypes({ 2178 * typeSpecs: [["control:NumericWritable", "control:BooleanWritable"]], 2179 * ok: function (newlyAddedTypes) { 2180 * // Called on success (if specified, network call will be asynchronous otherwise synchronous) 2181 * }, 2182 * fail: function (err) { 2183 * // Called on failure (optional) 2184 * }, 2185 * batch: batch // If specified, network calls are batched into this object 2186 * }); 2187 * </pre> 2188 * <p> 2189 * If the types for a given Web Application are known upfront then please specify them in {@link baja#start} instead. 2190 * 2191 * @see baja.start 2192 * 2193 * @param {Object} obj the Object Literal for the method's arguments. 2194 * @param {Function} [obj.ok] the ok callback. By defining this, the network call automatically becomes asynchronous. 2195 * An array of the newly added Types will be passed into this handler. 2196 * @param {Function} [obj.fail] the fail callback. 2197 * @param {baja.comm.Batch} [obj.batch] the batch Buffer. If defined, any network calls will be batched into this object. 2198 * @returns {Array} an array of Types if this method is invoked synchronously otherwise null. 2199 */ 2200 baja.importTypes = function (obj) { 2201 obj = objectify(obj, "typeSpecs"); 2202 var cb = new baja.comm.Callback(obj.ok, obj.fail, obj.batch); 2203 try { 2204 return loadTypes(baja.registry, 2205 obj.typeSpecs, 2206 /*encodeContracts*/true, 2207 cb, 2208 /*async*/obj.ok !== undefined); 2209 } 2210 catch (err) { 2211 cb.fail(err); 2212 } 2213 return null; 2214 }; 2215 2216 /** 2217 * Loads and returns a Type. 2218 * <p> 2219 * This queries the BajaScript registry for a Type. If it doesn't exist 2220 * then as a last resort, a synchronous network call is made in an attempt 2221 * to retrieve the Type information. 2222 * <p> 2223 * Please note, no Contract information is retreived if a network call is made, only 2224 * the Type information. 2225 * <p> 2226 * If a network call is expected to be made then please try asynchronously importing the Type 2227 * information first via {@link baja#importTypes}. 2228 * 2229 * @see baja.importTypes 2230 * 2231 * @param {String} typeSpec the type spec of the type we're interested in (moduleName:typeName). 2232 * @param {Boolean} [encodeContracts] encode the Contract when the Type is loaded (false by default). 2233 * @returns {Type} the Type for the given type spec. 2234 * @throws error if the Type can't be found. 2235 */ 2236 baja.lt = function (typeSpec, encodeContracts) { 2237 if (baja.registry.$types.hasOwnProperty(typeSpec)) { 2238 return baja.registry.$types[typeSpec]; 2239 } 2240 else { 2241 encodeContracts = bajaDef(encodeContracts, false); 2242 var cb = new baja.comm.Callback(); 2243 try { 2244 var ts = loadTypes(baja.registry, 2245 [typeSpec], 2246 encodeContracts, 2247 cb, 2248 /*async*/false); 2249 2250 if (ts !== null) { 2251 return ts[0]; 2252 } 2253 } 2254 catch (err) { 2255 cb.fail(err); 2256 } 2257 return null; 2258 } 2259 }; 2260 2261 (function bajaRegistryNamespace() { 2262 // BajaScript Registry 2263 var bsRegistryOrdTypes = {}, 2264 bsRegistrySimples = {}, 2265 bsRegistryCtors = {}; 2266 2267 /** 2268 * @class A BajaScript Type. 2269 * <p> 2270 * This Constructor shouldn't be invoked directly. 2271 * <p> 2272 * Type is a inner class. To access a Type please use {@link baja.lt}. 2273 * 2274 * @name Type 2275 * @extends BaseBajaObj 2276 * @inner 2277 * @public 2278 */ 2279 var Type = function (typeSpec, 2280 superType, 2281 isAbstract, 2282 isInterface, 2283 interfaces, 2284 contract, 2285 trans, 2286 iconStr, 2287 isValue, 2288 isSimple, 2289 isSingleton, 2290 isNumber, 2291 isComplex, 2292 isComponent, 2293 isLink, 2294 isAction, 2295 isTopic, 2296 isFrozenEnum, 2297 isOrdScheme) { 2298 // TODO: Never store transient Types in WebStorage 2299 this.$typeSpec = typeSpec; 2300 this.$superType = superType; 2301 this.$isAbstract = isAbstract; 2302 this.$isInterface = isInterface; 2303 this.$interfaces = interfaces; 2304 this.$contract = contract; 2305 this.$isTransient = trans; 2306 this.$iconStr = iconStr; 2307 this.$isValue = isValue; 2308 this.$isSimple = isSimple; 2309 this.$isSingleton = isSingleton; 2310 this.$isNumber = isNumber; 2311 this.$isComplex = isComplex; 2312 this.$isComponent = isComponent; 2313 this.$isLink = isLink; 2314 this.$isAction = isAction; 2315 this.$isTopic = isTopic; 2316 this.$isFrozenEnum = isFrozenEnum; 2317 this.$isOrdScheme = isOrdScheme; 2318 }.$extend(BaseBajaObj); 2319 2320 /** 2321 * Test for equality. 2322 * 2323 * @param obj value to test for equality. 2324 * @returns {Boolean} 2325 */ 2326 Type.prototype.equals = function (obj) { 2327 if (obj === undefined || obj === null) { 2328 return false; 2329 } 2330 if (obj.constructor !== Type) { 2331 return false; 2332 } 2333 return obj.$typeSpec === this.$typeSpec; 2334 }; 2335 2336 /** 2337 * Return the Module Name for the Type. 2338 * 2339 * @returns {String} module name. 2340 */ 2341 Type.prototype.getModuleName = function () { 2342 return this.$typeSpec.split(":")[0]; 2343 }; 2344 2345 /** 2346 * Return the Type Name for the Type. 2347 * 2348 * @returns {String} type name. 2349 */ 2350 Type.prototype.getTypeName = function () { 2351 return this.$typeSpec.split(":")[1]; 2352 }; 2353 2354 /** 2355 * Return the full Type Specification for the Type (moduleName:typeName). 2356 * 2357 * @returns {String} type spec. 2358 */ 2359 Type.prototype.getTypeSpec = function () { 2360 return this.$typeSpec; 2361 }; 2362 2363 /** 2364 * Return an instance of the Type. 2365 * <p> 2366 * A Type may have an Function Constructor associated with it. If a Constructor 2367 * is found with this Type, this it's used to return an instance. 2368 * <p> 2369 * If a Constructor can't be found on this Type, then the Super Types are inspected 2370 * and the first Constructor found is used instead. This provides an elegant 'dynamic typing' 2371 * mechanism whereby a Constructor is not needed for every single Type. 2372 * <p> 2373 * If the Type is a concrete Simple or Singleton, then the 'DEFAULT' Property on the 2374 * Constructor is returned. 2375 * 2376 * @throws if an instance of the Type can't be created (i.e. if the Type is an Interface or Abstract or no 2377 * constructor can be found). 2378 * @returns instance of Type. 2379 */ 2380 Type.prototype.getInstance = function (arg) { 2381 var typeSpec = this.getTypeSpec(), 2382 def; 2383 2384 if (this.$isInterface) { 2385 throw new Error("Cannot call 'getInstance' on an Interface Type: " + typeSpec); 2386 } 2387 if (this.$isAbstract) { 2388 throw new Error("Cannot call 'getInstance' on an Abstract Type: " + typeSpec); 2389 } 2390 2391 // If this is a Simple then check to see if this Type as a default instance on its Constructor, if so then return it 2392 // If there isn't a Constructor then default to baja.DefaultSimple 2393 if ((this.isSimple() || this.isSingleton()) && bsRegistryCtors.hasOwnProperty(typeSpec)) { 2394 if (arguments.length === 0) { 2395 return bsRegistryCtors[typeSpec].DEFAULT; 2396 } 2397 else { 2398 // If there were any arguments then attempt to use then in the Simple's make method 2399 def = bsRegistryCtors[typeSpec].DEFAULT; 2400 return def.make.apply(def, arguments); 2401 } 2402 } 2403 2404 // If we have a cached version of a Simple then return it. This is used since there may be 2405 // DefaultSimples and FrozenEnums out there that don't have their own Constructor but are still immutable. 2406 // Hence we still have an internal caching mechanism for them as a back up. 2407 if (bsRegistrySimples.hasOwnProperty(typeSpec)) { 2408 if (arguments.length === 0) { 2409 return bsRegistrySimples[typeSpec]; 2410 } 2411 else { 2412 // If there are arguments specified then use them in the Simple's make method 2413 def = bsRegistrySimples[typeSpec]; 2414 return def.make.apply(def, arguments); 2415 } 2416 } 2417 2418 var t = this, // Type used in iteration 2419 Ct; 2420 2421 // Go up Super types until we find a valid constructor 2422 while (t !== null) { 2423 // Create the new Type if there's a Constructor associated with it 2424 if (bsRegistryCtors.hasOwnProperty(t.getTypeSpec())) { 2425 Ct = bsRegistryCtors[t.getTypeSpec()]; 2426 break; 2427 } 2428 else { 2429 t = t.$superType; 2430 } 2431 } 2432 2433 // Throw error if we can't decode this properly. Usually this means we need to rebuild the common type library! 2434 if (t === null) { 2435 throw new Error("Could not find JS Constructor for Type: " + typeSpec); 2436 } 2437 2438 if (typeof Ct !== "function") { 2439 throw new Error("Fatal error, could not create instance of Type: " + typeSpec); 2440 } 2441 2442 // Otherwise just default to creating a new Type 2443 var o = new Ct(arg); 2444 2445 // Update the Type on the new instance if necessary 2446 if (typeSpec !== o.getType().getTypeSpec()) { 2447 var that = this; 2448 o.getType = function () { 2449 return that; 2450 }; 2451 } 2452 2453 // Apply the Contract to the new instance if there's one available. 2454 // This will load the default frozen Slots for the given Type 2455 if (this.isComplex() || this.isFrozenEnum()) { 2456 if (this.isComplex()) { 2457 // Decode Complex BSON for all of the Slots 2458 baja.bson.decodeComplexContract(this, o); 2459 } 2460 else if (this.isFrozenEnum()) { 2461 // Ensure the Contract is loaded (although for better performance it's best to do this before 2462 // hand using something like importTypes(...) 2463 if (!this.hasContract()) { 2464 this.loadContract(); 2465 } 2466 2467 // Get default ordinal for Enum and assign it to the FrozenEnum instance 2468 var ordinals = this.getOrdinals(); 2469 if (ordinals.length > 0) { 2470 o.$ordinal = ordinals[0]; 2471 } 2472 } 2473 2474 // Also pass any start up arguments into Contract Committed 2475 o.contractCommitted(arg); 2476 } 2477 2478 if (this.isSimple()) { 2479 // If this is a simple then cache the instance 2480 bsRegistrySimples[typeSpec] = o; 2481 2482 // If there are arguments specified then use them with the Simple's make method 2483 if (arguments.length > 0) { 2484 o = o.make.apply(o, arguments); 2485 } 2486 } 2487 2488 return o; 2489 }; 2490 2491 /** 2492 * Test one Type is another. 2493 * 2494 * @param {String|Type} type this can be an instance of a Type object or a String type specification (module:typeName). 2495 * @returns {Boolean} true if this Type polymorphically matches the other. 2496 */ 2497 Type.prototype.is = function (type) { 2498 if (type.constructor === String) { 2499 type = baja.lt(type); 2500 } 2501 2502 // Test Type against this 2503 if (this.$typeSpec === type.$typeSpec) { 2504 return true; 2505 } 2506 2507 // Can only perform this test if the type passed in is an interface 2508 var i; 2509 if (type.$isInterface) { 2510 // Test Type against any interfaces 2511 for (i = 0; i < this.$interfaces.length; ++i) { 2512 if (this.$interfaces[i].is(type)) { 2513 return true; 2514 } 2515 } 2516 } 2517 else if (type.$isSimple && !this.isSimple) { 2518 return false; 2519 } 2520 else if (type.$isComponent && !this.$isComponent) { 2521 return false; 2522 } 2523 else if (type.$isComplex && !this.$isComplex) { 2524 return false; 2525 } 2526 2527 // Test Type against any super Types 2528 if (this.$superType) { 2529 return this.$superType.is(type); 2530 } 2531 2532 return false; 2533 }; 2534 2535 /** 2536 * Return true if the Type is a Value. 2537 * 2538 * @returns {Boolean} 2539 */ 2540 Type.prototype.isValue = function () { 2541 return this.$isValue; 2542 }; 2543 2544 /** 2545 * Return true if the Type is a Simple. 2546 * 2547 * @returns {Boolean} 2548 */ 2549 Type.prototype.isSimple = function () { 2550 return this.$isSimple; 2551 }; 2552 2553 /** 2554 * Return true if the Type is a Singleton. 2555 * 2556 * @returns {Boolean} 2557 */ 2558 Type.prototype.isSingleton = function () { 2559 return this.$isSingleton; 2560 }; 2561 2562 /** 2563 * Return true if the Type is a Number. 2564 * 2565 * @returns {Boolean} 2566 */ 2567 Type.prototype.isNumber = function () { 2568 return this.$isNumber; 2569 }; 2570 2571 /** 2572 * Return true if the Type is a Complex. 2573 * 2574 * @returns {Boolean} 2575 */ 2576 Type.prototype.isComplex = function () { 2577 return this.$isComplex; 2578 }; 2579 2580 /** 2581 * Return true if the Type is a Component. 2582 * 2583 * @returns {Boolean} 2584 */ 2585 Type.prototype.isComponent = function () { 2586 return this.$isComponent; 2587 }; 2588 2589 /** 2590 * Return true if the Type is a Struct. 2591 * 2592 * @returns {Boolean} 2593 */ 2594 Type.prototype.isStruct = function () { 2595 return this.isComplex() && !this.isComponent(); 2596 }; 2597 2598 /** 2599 * Return true if the Type is a Link. 2600 * 2601 * @returns {Boolean} 2602 */ 2603 Type.prototype.isLink = function () { 2604 return this.$isLink; 2605 }; 2606 2607 /** 2608 * Return true if the Type is a baja:Action. 2609 * 2610 * @returns {Boolean} 2611 */ 2612 Type.prototype.isAction = function () { 2613 return this.$isAction; 2614 }; 2615 2616 /** 2617 * Return true if the Type is a baja:Topic. 2618 * 2619 * @returns {Boolean} 2620 */ 2621 Type.prototype.isTopic = function () { 2622 return this.$isTopic; 2623 }; 2624 2625 /** 2626 * Return true if the Type is a baja:FrozenEnum. 2627 * 2628 * @returns {Boolean} 2629 */ 2630 Type.prototype.isFrozenEnum = function () { 2631 return this.$isFrozenEnum; 2632 }; 2633 2634 /** 2635 * Return true if the Type is a baja:OrdScheme. 2636 * 2637 * @returns {Boolean} 2638 */ 2639 Type.prototype.isOrdScheme = function () { 2640 return this.$isOrdScheme; 2641 }; 2642 2643 /** 2644 * Return the Super Type. 2645 * 2646 * @returns {Type} Super Type or null if not available 2647 */ 2648 Type.prototype.getSuperType = function () { 2649 return this.$superType; 2650 }; 2651 2652 /** 2653 * Return an array of interfaces Types implemented by this Type. 2654 * 2655 * @returns {Array} an array of interface types (all Type) 2656 */ 2657 Type.prototype.getInterfaces = function () { 2658 return this.$interfaces.slice(0); // Return copy of array 2659 }; 2660 2661 /** 2662 * Return true if Type is Abstract. 2663 * 2664 * @returns {Boolean} 2665 */ 2666 Type.prototype.isAbstract = function () { 2667 return this.$isAbstract; 2668 }; 2669 2670 /** 2671 * Return true if Type is an Interface. 2672 * 2673 * @returns {Boolean} 2674 */ 2675 Type.prototype.isInterface = function () { 2676 return this.$isInterface; 2677 }; 2678 2679 /** 2680 * Return true if Type is transient. 2681 * 2682 * @returns {Boolean} 2683 */ 2684 Type.prototype.isTransient = function () { 2685 return this.$isTransient; 2686 }; 2687 2688 /** 2689 * Return type spec as toString (moduleName:typeName). 2690 * 2691 * @returns {String} type spec 2692 */ 2693 Type.prototype.toString = function () { 2694 return this.getTypeSpec(); 2695 }; 2696 2697 /** 2698 * Return the Contract for the Type. 2699 * 2700 * @private 2701 * 2702 * @returns Contract 2703 */ 2704 Type.prototype.getContract = function () { 2705 return this.$contract; 2706 }; 2707 2708 /** 2709 * Return true if the Type has a Contract. 2710 * 2711 * @private 2712 * 2713 * @returns {Boolean} true if the Type has a Contract. 2714 */ 2715 Type.prototype.hasContract = function () { 2716 return this.$contract !== null; 2717 }; 2718 2719 /** 2720 * Ensures the Contract for the given Type is loaded. 2721 * <p> 2722 * If the Contract doesn't exist and this Type is a Complex or a FrozenEnum, a network call will be made to 2723 * get the Contract. 2724 * 2725 * @private 2726 * 2727 * @param {Object} [obj] Object Literal containing the method's arguments. 2728 * @param {Function} [obj.ok] ok function callback. If defined, the network call is automatically made asynchronously 2729 * otherwise the network call is synchronous. 2730 * @param {Function} [obj.fail] fail function callback. If the Contract fails to load, this function will be called. 2731 * @param {baja.comm.Batch} [obj.batch] the batch buffer. If defined, the network call will be batched into this object. 2732 */ 2733 Type.prototype.loadContract = function (obj) { 2734 obj = objectify(obj); 2735 var cb = new baja.comm.Callback(obj.ok, obj.fail, obj.batch); 2736 2737 try { 2738 // See if the Contract is already present. If not then make a network call 2739 if (!(this.isComplex() || this.isFrozenEnum()) || 2740 this.isInterface() || 2741 this.hasContract()) { 2742 cb.ok(); 2743 return; 2744 } 2745 2746 // Create the Callback to update this Types Contract 2747 cb.addOk(function (ok, fail, contracts) { 2748 var c; // Contract 2749 var ts = []; // TypeSpecs to request 2750 2751 // Update the Registry with all of these Contract definitions 2752 for (c in contracts) { 2753 if (contracts.hasOwnProperty(c)) { 2754 if (!baja.registry.$types.hasOwnProperty(c)) { 2755 ts.push(c); 2756 } 2757 } 2758 } 2759 2760 // If we need to load any types then do so via a network call 2761 if (ts.length > 0) { 2762 // TODO: At some point, allow this to be asynchronous 2763 loadTypes(baja.registry, 2764 ts, 2765 /*encodeContracts*/false, 2766 new baja.comm.Callback(), 2767 /*async*/false); 2768 } 2769 2770 // Update the Contracts 2771 for (c in contracts) { 2772 if (contracts.hasOwnProperty(c)) { 2773 if (baja.registry.$types.hasOwnProperty(c)) { 2774 baja.registry.$types[c].$contract = contracts[c]; 2775 2776 // If available, save contract to web storage... 2777 if (bsRegStorage && bsRegStorage.types.hasOwnProperty(c)) { 2778 bsRegStorage.types[c].c = contracts[c]; 2779 } 2780 } 2781 } 2782 } 2783 2784 ok(); 2785 }); 2786 2787 baja.comm.loadContract(this.getTypeSpec(), cb, /*async*/obj.ok !== undefined); 2788 } 2789 catch (err) { 2790 cb.fail(err); 2791 } 2792 }; 2793 2794 /** 2795 * Return the Types's Icon. 2796 * 2797 * @returns {String} 2798 */ 2799 Type.prototype.getIcon = function () { 2800 if (this.$icon) { 2801 return this.$icon; 2802 } 2803 2804 this.$icon = this.$iconStr ? baja.Icon.DEFAULT.decodeFromString(this.$iconStr) : baja.Icon.getStdObjectIcon(); 2805 return this.$icon; 2806 }; 2807 2808 //////////////////////////////////////////////////////////////// 2809 // Type FrozenEnum Methods 2810 //////////////////////////////////////////////////////////////// 2811 2812 function enumTypeCheck(type) { 2813 if (!type.isFrozenEnum()) { 2814 throw new Error("Type must be a FrozenEnum"); 2815 } 2816 if (!type.hasContract()) { 2817 type.loadContract(); 2818 if (!type.hasContract()) { 2819 throw new Error("Unable to load the FrozenEnum Contract"); 2820 } 2821 } 2822 } 2823 2824 /** 2825 * Returns the ordinals for a Type that maps to a FrozenEnum. 2826 * 2827 * @private 2828 * 2829 * @throws error if Type is not a FrozenEnum or if Contract can't be loaded. 2830 * @returns {Array} array of ordinals for frozen enum. 2831 */ 2832 Type.prototype.getOrdinals = function () { 2833 enumTypeCheck(this); 2834 var ordinals = [], i; 2835 for (i = 0; i < this.$contract.length; ++i) { 2836 ordinals.push(this.$contract[i].o); 2837 } 2838 return ordinals; 2839 }; 2840 2841 /** 2842 * Returns whether the specified ordinal exists for this FrozenEnum Type. 2843 * 2844 * @private 2845 * 2846 * @param {Number} ordinal 2847 * @throws error if Type is not a FrozenEnum or if Contract can't be loaded. 2848 * @returns {Boolean} true if the ordinal number exists in this FrozenEnum Type. 2849 */ 2850 Type.prototype.isOrdinal = function (ordinal) { 2851 enumTypeCheck(this); 2852 var contract = this.$contract, i; 2853 2854 // Lazily generate the byOrdinal map 2855 if (this.$byOrdinal === undefined) { 2856 this.$byOrdinal = {}; 2857 for (i = 0; i < contract.length; ++i) { 2858 this.$byOrdinal[contract[i].o] = { 2859 o: contract[i].o, 2860 t: contract[i].t, 2861 dt: contract[i].dt 2862 }; 2863 } 2864 } 2865 2866 return this.$byOrdinal.hasOwnProperty(ordinal); 2867 }; 2868 2869 /** 2870 * Returns the tag for the ordinal (this Type has to map to a FrozenEnum). 2871 * 2872 * @private 2873 * 2874 * @param {Number} ordinal 2875 * @throws error if Type is not a FrozenEnum, if Contract can't be loaded or if the ordinal doesn't exist. 2876 * @returns {String} the tag for the ordinal. 2877 */ 2878 Type.prototype.getTag = function (ordinal) { 2879 enumTypeCheck(this); 2880 if (this.isOrdinal(ordinal)) { 2881 return this.$byOrdinal[ordinal].t; 2882 } 2883 else { 2884 throw new Error("No tag for ordinal: " + ordinal + " in: " + this.getTypeSpec()); 2885 } 2886 }; 2887 2888 /** 2889 * Returns the display tag for the ordinal (this Type has to map to a FrozenEnum). 2890 * 2891 * @private 2892 * 2893 * @param {Number} ordinal 2894 * @throws error if Type is not a FrozenEnum, if Contract can't be loaded or if the ordinal doesn't exist. 2895 * @returns {String} the display tag for the ordinal. 2896 */ 2897 Type.prototype.getDisplayTag = function (ordinal) { 2898 enumTypeCheck(this); 2899 if (this.isOrdinal(ordinal)) { 2900 return this.$byOrdinal[ordinal].dt; 2901 } 2902 else { 2903 throw new Error("No display tag for ordinal: " + ordinal + " in: " + this.getTypeSpec()); 2904 } 2905 }; 2906 2907 /** 2908 * Returns whether the specified tag exists for this FrozenEnum Type. 2909 * 2910 * @private 2911 * 2912 * @param {String} tag 2913 * @throws error if Type is not a FrozenEnum or if Contract can't be loaded. 2914 * @returns {Boolean} true if the tag exists. 2915 */ 2916 Type.prototype.isTag = function (tag) { 2917 enumTypeCheck(this); 2918 var contract = this.$contract, i; 2919 2920 // Lazily generate the byTag map 2921 if (this.$byTag === undefined) { 2922 this.$byTag = {}; 2923 for (i = 0; i < contract.length; ++i) { 2924 this.$byTag[contract[i].t] = { 2925 o: contract[i].o, 2926 t: contract[i].t, 2927 dt: contract[i].dt 2928 }; 2929 } 2930 } 2931 2932 return this.$byTag.hasOwnProperty(tag); 2933 }; 2934 2935 /** 2936 * Returns the ordinal for the tag (providing the Type maps to a FrozenEnum). 2937 * 2938 * @private 2939 * 2940 * @param {String} tag 2941 * @throws error if Type is not a FrozenEnum, if Contract can't be loaded or the tag doesn't exist. 2942 * @returns {Number} ordinal for the tag. 2943 */ 2944 Type.prototype.tagToOrdinal = function (tag) { 2945 enumTypeCheck(this); 2946 if (this.isTag(tag)) { 2947 return this.$byTag[tag].o; 2948 } 2949 else { 2950 throw new Error("No ordinal tag for tag: " + tag + " in: " + this.getTypeSpec()); 2951 } 2952 }; 2953 2954 /** 2955 * Returns the EnumRange for the Type (providing the Type maps to a FrozenEnum). 2956 * 2957 * @private 2958 * 2959 * @see baja.EnumRange 2960 * 2961 * @throws error if Type is not a FrozenEnum, if Contract can't be loaded or the tag doesn't exist. 2962 * @returns {baja.EnumRange} the enum range. 2963 */ 2964 Type.prototype.getRange = function () { 2965 enumTypeCheck(this); 2966 if (this.$range === undefined) { 2967 this.$range = baja.EnumRange.make(this); 2968 } 2969 return this.$range; 2970 }; 2971 2972 /** 2973 * Returns the FrozenEnum for the Type (providing the Type maps to a FrozenEnum). 2974 * 2975 * @private 2976 * 2977 * @see baja.EnumRange 2978 * 2979 * @param {String|Number} arg the tag or ordinal for the frozen enum to return. 2980 * @throws error if Type is not a FrozenEnum, if Contract can't be loaded or the tag or ordinal doesn't exist. 2981 * @returns {FrozenEnum} the frozen enumeration for the tag or ordinal. 2982 */ 2983 Type.prototype.getFrozenEnum = function (arg) { 2984 enumTypeCheck(this); 2985 2986 var ordinal = 0, fe; 2987 2988 // Set the ordinal depending on the argument... 2989 if (typeof arg === "string") { 2990 // Look up the ordinal if the tag was passed in 2991 ordinal = this.tagToOrdinal(arg); 2992 } 2993 else if (typeof arg === "number" && this.isOrdinal(arg)) { 2994 // Validate the ordinal that was passed in 2995 ordinal = arg; 2996 } 2997 else { 2998 throw new Error("Invalid argument for FrozenEnum creation"); 2999 } 3000 3001 // Lazily create cache of frozen enum instances 3002 if (this.$enums === undefined) { 3003 this.$enums = {}; 3004 } 3005 3006 // Look up enum with ordinal in simples Cache 3007 if (this.$enums.hasOwnProperty(ordinal)) { 3008 fe = this.$enums[ordinal]; 3009 } 3010 else { 3011 3012 // Create instance and set up type access 3013 fe = this.getInstance(); 3014 3015 // If the ordinal differs then make a clone of the object 3016 if (fe.getOrdinal() !== ordinal) { 3017 var newFe = new fe.constructor(); 3018 newFe.getType = fe.getType; 3019 fe = newFe; 3020 fe.$ordinal = ordinal; 3021 } 3022 3023 // Cache the frozen enum in the Type 3024 this.$enums[ordinal] = fe; 3025 } 3026 3027 return fe; 3028 }; 3029 3030 /** 3031 * Return true if this Type has a constructor directly assocated with it. 3032 * 3033 * @private 3034 * 3035 * @returns {Boolean} 3036 */ 3037 Type.prototype.hasConstructor = function () { 3038 return bsRegistryCtors.hasOwnProperty(this.$typeSpec); 3039 }; 3040 3041 /** 3042 * Type Registration. 3043 * <p> 3044 * Registers the Constructor with the given TypeSpec. This method is used to extend BajaScript and associate 3045 * JavaScript Objects with Niagara Types. 3046 * <p> 3047 * This method also performs checks on the given Constructor to ensure it's 3048 * suitable for the given Type. 3049 * 3050 * @private 3051 * 3052 * @param {String} typeSpec the TypeSpec we want to register with. 3053 * @returns {Object} the Object (this). 3054 */ 3055 Function.prototype.registerType = function (typeSpec) { 3056 strictArg(typeSpec, String); 3057 if (typeof this !== "function") { 3058 throw new Error("Can only loadType on a Function Constructor: " + typeSpec); 3059 } 3060 3061 // Register the TypeSpec with this Constructor function 3062 bsRegistryCtors[typeSpec] = this; 3063 3064 // Please note, the Constructor is now lazily validated when the Type information 3065 // is registered with BajaScript. 3066 3067 // Set up default type access 3068 var type; 3069 this.prototype.getType = function () { 3070 // Lazily load Type information 3071 if (!type) { 3072 type = baja.lt(typeSpec); 3073 } 3074 return type; 3075 }; 3076 3077 return this; 3078 }; 3079 3080 /** 3081 * @namespace Baja Type Registry 3082 * <p> 3083 * At the core of BajaScript is a lazily loading Type and Contract system. A Contract 3084 * represents a frozen slot defition for a Complex or a set of FrozenEnum ordinals, tags and display tags 3085 * <p> 3086 * The most commonly used Types are held generated into a Common Type Library that gets 3087 * forms part of BajaScript JavaScript library to avoid unnecessary network calls. 3088 */ 3089 baja.registry = new BaseBajaObj(); 3090 baja.registry.$types = { 3091 // Add very core Baja Types as these will NEVER be included into any JSON 3092 "baja:Object": new Type("baja:Object", // TypeSpec 3093 null, // Super Type 3094 true, // Is Abstract 3095 false, // Is Interface 3096 [], // Interfaces 3097 null, // Contract 3098 false, // Transient 3099 null, // Icon 3100 false, // isValue 3101 false, // isSimple 3102 false, // isSingleton 3103 false, // isNumber 3104 false, // isComplex 3105 false, // isComponent 3106 false, // isLink 3107 false, // isAction 3108 false, // isTopic 3109 false, // isFrozenEnum 3110 false), // isOrdScheme 3111 3112 3113 "baja:Interface": new Type("baja:Interface", // TypeSpec 3114 null, // Super Type 3115 true, // Is Abstract 3116 true, // Is Interface 3117 [], // Interfaces 3118 null, // Contract 3119 false, // Transient 3120 null, // Icon 3121 false, // isValue 3122 false, // isSimple 3123 false, // isSingleton 3124 false, // isNumber 3125 false, // isComplex 3126 false, // isComponent 3127 false, // isLink 3128 false, // isAction 3129 false, // isTopic 3130 false, // isFrozenEnum 3131 false) // isOrdScheme 3132 }; 3133 3134 /** 3135 * Inspect the object structure and create Type information to add to the Registry. 3136 * <p> 3137 * This should only be invoked by Tridium developers and is normally used in a network callback. 3138 * 3139 * @private 3140 * 3141 * @param {Object} obj the Object structure holding the Type information. 3142 * @param {Boolean} [updateRegStorageTypes] if true, this will update the Registry 3143 * storage type database. (Contracts held in the 3144 * registry storage database will be updated regardless 3145 * of whether this flag is truthy or not). 3146 */ 3147 baja.registry.register = function (obj, updateRegStorageTypes) { 3148 if (obj === undefined) { 3149 return; 3150 } 3151 3152 var intType = baja.lt("baja:Interface"), 3153 objType = baja.lt("baja:Object"), 3154 typeSpec, // Add Types to Registry database without super or interface information... 3155 data, 3156 newTypeObj = {}; 3157 3158 for (typeSpec in obj) { 3159 if (obj.hasOwnProperty(typeSpec)) { 3160 data = obj[typeSpec]; 3161 3162 // If the type doesn't exist in the registry then create it... 3163 if (!this.$types.hasOwnProperty(typeSpec)) { 3164 this.$types[typeSpec] = new Type(typeSpec, // TypeSpec 3165 null, // Super Type 3166 data.a || false, // Is Abstract 3167 data.i || false, // Is Interface 3168 [], // Interfaces 3169 bajaDef(data.c, null), // Contract 3170 data.t || false, // Transient 3171 bajaDef(data.ic, null), // Icon String 3172 data.isv || false, // isValue 3173 data.iss || false, // isSimple 3174 data.isg || false, // isSingleton 3175 data.isn || false, // isNumber 3176 data.isx || false, // isComplex 3177 data.isc || false, // isComponent 3178 data.isl || false, // isLink 3179 data.isa || false, // isAction 3180 data.ist || false, // isTopic 3181 data.ise || false, // isFrozenEnum, 3182 data.os || false); // isOrdScheme 3183 3184 // Create object with new information we're processing... 3185 newTypeObj[typeSpec] = data; 3186 3187 // Update local storage if available... 3188 if (updateRegStorageTypes && bsRegStorage) { 3189 // Update web storage (providing Type isn't transient)... 3190 if (!data.t) { 3191 bsRegStorage.types[typeSpec] = data; 3192 } 3193 } 3194 } 3195 else { 3196 // If the type does exist in the registry then ensure it's contract is loaded (if there is one) 3197 if (data.c) { 3198 this.$types[typeSpec].$contract = data.c; 3199 3200 // If available, update contract in web storage... 3201 if (bsRegStorage && bsRegStorage.types.hasOwnProperty(typeSpec)) { 3202 bsRegStorage.types[typeSpec].c = data.c; 3203 } 3204 } 3205 } 3206 } 3207 } 3208 3209 // Now add all super and interface information... 3210 var i, 3211 type; 3212 3213 for (typeSpec in newTypeObj) { 3214 if (newTypeObj.hasOwnProperty(typeSpec)) { 3215 data = newTypeObj[typeSpec]; 3216 type = this.$types[typeSpec]; 3217 3218 // Skip baja:Interface and baja:Object... 3219 if (type === intType || type === objType) { 3220 continue; 3221 } 3222 3223 // Set up interfaces... 3224 if (data.it && data.it.length > 0) { 3225 for (i = 0; i < data.it.length; ++i) { 3226 type.$interfaces.push(baja.lt(data.it[i])); 3227 } 3228 } 3229 else if (data.i) { 3230 // If this is an interface then this must at least extend baja:Interface 3231 type.$interfaces.push(intType); 3232 } 3233 3234 // Set up Super Type for non-interfaces... 3235 if (!data.i) { 3236 if (data.p) { 3237 type.$superType = baja.lt(data.p); 3238 } 3239 else { 3240 type.$superType = objType; 3241 } 3242 } 3243 } 3244 } 3245 3246 // Iterate through newly added Type information 3247 var ctor; 3248 for (typeSpec in newTypeObj) { 3249 if (newTypeObj.hasOwnProperty(typeSpec)) { 3250 data = newTypeObj[typeSpec]; 3251 type = baja.lt(typeSpec); 3252 3253 // Lazily do checks on Ctor information associated with Type 3254 if (!type.isAbstract()) { 3255 if (bsRegistryCtors.hasOwnProperty(typeSpec)) { 3256 ctor = bsRegistryCtors[typeSpec]; 3257 3258 // Check concrete Simple Types conform 3259 if (type.isSimple()) { 3260 if (ctor.DEFAULT === undefined) { 3261 throw new Error("Concrete Simple implementations must define a DEFAULT instance argument on the Constructor: " + typeSpec); 3262 } 3263 if (typeof ctor.DEFAULT.make !== "function") { 3264 throw new Error("Concrete Simple implementations must define a make method: " + typeSpec); 3265 } 3266 if (typeof ctor.DEFAULT.decodeFromString !== "function") { 3267 throw new Error("Concrete Simple implementations must define a decodeFromString method: " + typeSpec); 3268 } 3269 if (typeof ctor.DEFAULT.encodeToString !== "function") { 3270 throw new Error("Concrete Simple implementations must define a encodeToString method: " + typeSpec); 3271 } 3272 } 3273 3274 // Check concrete Singletons Types conform 3275 if (type.isSingleton()) { 3276 if (ctor.DEFAULT === undefined) { 3277 throw new Error("Concrete Singletons must define a DEFAULT instance argument on the Constructor: " + typeSpec); 3278 } 3279 } 3280 } 3281 3282 // Register ORD schemes... 3283 if (data.os && type.isOrdScheme()) { 3284 bsRegistryOrdTypes[data.os] = type; 3285 } 3286 } 3287 } 3288 } 3289 }; 3290 3291 /** 3292 * Return the Type for the given ORD scheme name. 3293 * 3294 * @private 3295 * 3296 * @param {String} schemeId the ORD scheme name. 3297 * @returns the Type for the ORD Scheme (null is returned if a Type can't be found). 3298 */ 3299 baja.registry.getOrdScheme = function (schemeId) { 3300 return bajaDef(bsRegistryOrdTypes[schemeId], null); 3301 }; 3302 3303 /** 3304 * Does the Type exist in the BajaScript registry? 3305 * <p> 3306 * This method will not result in any network calls. 3307 * 3308 * @param {String} typeSpec the type specification to query the registry for. 3309 * @returns {Boolean} true if the Type exists in the BajaScript registry. 3310 */ 3311 baja.registry.hasType = function (typeSpec) { 3312 return this.$types.hasOwnProperty(typeSpec); 3313 }; 3314 3315 /** 3316 * Same as {@link baja#lt}. 3317 * 3318 * @name baja.registry#loadType 3319 * @function 3320 * 3321 * @see baja.lt 3322 */ 3323 baja.registry.loadType = baja.lt; 3324 3325 /** 3326 * Same as {@link baja#importTypes}. 3327 * 3328 * @name baja.registry.loadTypesWithContract 3329 * @function 3330 * 3331 * @see baja.importTypes 3332 */ 3333 baja.registry.loadTypesWithContract = baja.importTypes; 3334 3335 /** 3336 * Get an array of concrete Types from the given typeInfo. This method 3337 * will make a network call. The type information returned in the 3338 * ok handler may not necessarily have Contract information loaded. 3339 * To then ensure the Contract information for Complex and FrozenEnums 3340 * is loaded, please use {@link baja#importTypes}. 3341 * <p> 3342 * This method takes an Object Literal as an argument. 3343 * <pre> 3344 * baja.getConcreteTypes({ 3345 * type: "control:ControlPoint", 3346 * ok: function (concreteTypes) { 3347 * // Called on success 3348 * }, 3349 * fail: function (err) { 3350 * // Called on failure (optional) 3351 * }, 3352 * batch: batch // If specified, network calls are batched into this object 3353 * }); 3354 * </pre> 3355 * 3356 * @see baja.importTypes 3357 * @see Type 3358 * 3359 * @param {Object} obj the Object literal for the method's arguments. 3360 * @param {String|Type} obj.type the type or (String type specification - module:typeName) to query the registry for. 3361 * @param {Function} obj.ok the ok callback. When invoked, an array of Types will be passed 3362 * in as an argument. 3363 * @param {Function} [obj.fail] the fail callback. 3364 * @param {baja.comm.Batch} [obj.batch] the batch Buffer. If defined, the network call will be batched into this object. 3365 */ 3366 baja.registry.getConcreteTypes = function (obj) { 3367 obj = objectify(obj); 3368 3369 var typeSpec; 3370 if (obj.type) { 3371 typeSpec = obj.type.toString(); 3372 } 3373 3374 // Ensure we have these arguments 3375 baja.strictAllArgs([typeSpec, obj.ok], [String, Function]); 3376 3377 var cb = new baja.comm.Callback(obj.ok, obj.fail, obj.batch); 3378 3379 cb.addOk(function (ok, fail, resp) { 3380 var types = resp.t, 3381 specs = resp.s; 3382 3383 // Register all of the Type information we get back 3384 baja.registry.register(types, /*updateRegStorageTypes*/true); 3385 3386 // Get all of the Type inforamtion from the BajaScript registry 3387 var concreteTypes = []; 3388 baja.iterate(specs, function (typeSpec) { 3389 concreteTypes.push(baja.lt(typeSpec)); 3390 }); 3391 3392 // Make the callback with the Type information. 3393 ok(concreteTypes); 3394 }); 3395 3396 // Make the network call 3397 baja.comm.getConcreteTypes(typeSpec, cb); 3398 }; 3399 3400 /** 3401 * Clear the registry from permanent storage. By default, this does nothing and 3402 * must be overridden in a utility library like browser.js to clear the 3403 * localStorage (or to a flat file in a server environment, etc). 3404 * 3405 * @name baja.registry.clearStorage 3406 * @function 3407 * @private 3408 */ 3409 baja.registry.clearStorage = function () { 3410 // Do nothing - override with browser-dependent localStorage hook, etc. 3411 }; 3412 3413 /** 3414 * Saves the registry to permanent storage. By default, this does nothing and 3415 * must be overridden in a utility library like browser.js to save to 3416 * localStorage (or to a flat file in a server environment, etc). 3417 * 3418 * @name baja.registry.saveToStorage 3419 * @function 3420 * @private 3421 * 3422 * @param {Object} regStorage the BajaScript registry information to store 3423 */ 3424 baja.registry.saveToStorage = function (regStorage) { 3425 // Do nothing - override with browser-dependent localStorage hook, etc. 3426 }; 3427 3428 /** 3429 * Load Registry Storage information and return it. 3430 * By default, this does nothing and must be overriden in a utility library 3431 * like browser.js to load from localStorage (or from a flat file in a server 3432 * environment, etc). 3433 * 3434 * @name baja.registry.loadFromStorage 3435 * @function 3436 * @private 3437 */ 3438 baja.registry.loadFromStorage = function () { 3439 // Return null - override with browser-dependent localStorage hook, etc., 3440 return null; 3441 }; 3442 3443 }()); 3444 }()); 3445 3446 }(baja, BaseBajaObj));