1 // 2 // Copyright 2010, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * Core Object Resolution Descriptor Architecture for BajaScript. 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: true, newcap: true, immed: true, strict: false, 15 indent: 2, vars: true, continue: true */ 16 17 // Globals for JsLint to ignore 18 /*global baja, BaseBajaObj, encodeURI, decodeURI, decodeURIComponent, encodeURIComponent*/ 19 20 (function ord(baja, BaseBajaObj) { 21 22 // Use ECMAScript 5 Strict Mode 23 "use strict"; 24 25 // Create local for improved minification 26 var strictArg = baja.strictArg, 27 bajaDef = baja.def, 28 objectify = baja.objectify, 29 Callback = baja.comm.Callback; 30 31 //////////////////////////////////////////////////////////////// 32 // Local Host and Component Space 33 //////////////////////////////////////////////////////////////// 34 35 /** 36 * @namespace Local Host 37 */ 38 baja.nav.localhost = baja.nav.$addChildNode(new baja.LocalHost()); 39 40 /** 41 * @namespace local Station Component Space 42 */ 43 baja.nav.localhost.station = baja.nav.localhost.$addChildNode(new baja.BoxComponentSpace("station", "station:", baja.nav.localhost)); 44 45 /** 46 * Shortcut to local Station Component Space 47 * 48 * @see baja.nav.localhost.station 49 */ 50 baja.station = baja.nav.localhost.station; 51 52 //////////////////////////////////////////////////////////////// 53 // ORD 54 //////////////////////////////////////////////////////////////// 55 56 /** 57 * @class ORD Query. 58 * <p> 59 * The base class for all OrdQuery Objects. 60 * 61 * @name OrdQuery 62 * @private 63 * @inner 64 */ 65 var OrdQuery = function (obj) { 66 obj = obj || {}; 67 this.$scheme = obj.scheme; 68 this.$schemeName = obj.schemeName; 69 this.$body = obj.body; 70 this.$isHost = obj.isHost || false; 71 this.$isSession = obj.isSession || false; 72 73 // Override any functions 74 var p; 75 for (p in obj) { 76 if (obj.hasOwnProperty(p) && typeof obj[p] === "function") { 77 this[p] = obj[p]; 78 } 79 } 80 }.$extend(BaseBajaObj); 81 82 /** 83 * Return the ORD Scheme. 84 * 85 * @returns {baja.OrdScheme} 86 */ 87 OrdQuery.prototype.getScheme = function () { 88 return this.$scheme; 89 }; 90 91 /** 92 * Return the ORD Scheme name. 93 * 94 * @returns {String} 95 */ 96 OrdQuery.prototype.getSchemeName = function () { 97 return this.$schemeName; 98 }; 99 100 /** 101 * Return the body for the query. 102 * 103 * @returns {String} 104 */ 105 OrdQuery.prototype.getBody = function () { 106 return this.$body; 107 }; 108 109 /** 110 * Return a String representation of the query. 111 * 112 * @returns {String} 113 */ 114 OrdQuery.prototype.toString = function () { 115 return this.getSchemeName() + ":" + this.getBody(); 116 }; 117 118 /** 119 * Return true if the Query is a Host. 120 * 121 * @returns {Boolean} 122 */ 123 OrdQuery.prototype.isHost = function () { 124 return this.$isHost; 125 }; 126 127 /** 128 * Return true if the Query is a Session. 129 * 130 * @returns {Boolean} 131 */ 132 OrdQuery.prototype.isSession = function () { 133 return this.$isSession; 134 }; 135 136 /** 137 * Normalize the query and return true if modified. 138 * 139 * @private 140 * 141 * @param {OrdQueryList} list 142 * @param {Number} index 143 * 144 * @returns {Boolean} 145 */ 146 OrdQuery.prototype.normalize = function (list, index) { 147 return false; 148 }; 149 150 /** 151 * @class Cursor for an ORD Query List. 152 * 153 * @name OrdQueryListCursor 154 * @extends baja.SyncCursor 155 * @inner 156 * @public 157 */ 158 var OrdQueryListCursor = function (list) { 159 OrdQueryListCursor.$super.apply(this, arguments); 160 this.$list = list; 161 this.$index = -1; 162 }.$extend(baja.SyncCursor); 163 164 /** 165 * Return true if there's another query in the Cursor. 166 * 167 * @returns {Boolean} 168 */ 169 OrdQueryListCursor.prototype.hasNext = function () { 170 return this.$index + 1 < this.$list.$queries.length; 171 }; 172 173 /** 174 * Advance the Cursor to the next query. 175 * 176 * @returns {Boolean} returns true if there's another query in the Cursor. 177 */ 178 OrdQueryListCursor.prototype.next = function () { 179 if (!this.hasNext()) { 180 return false; 181 } 182 else { 183 this.$index++; 184 return true; 185 } 186 }; 187 188 /** 189 * Return the current query from the Cursor. 190 * 191 * @returns the ORD Query. 192 */ 193 OrdQueryListCursor.prototype.get = function () { 194 if (this.$index === -1) { 195 throw new Error("Illegal cursor index"); 196 } 197 return this.$list.$queries[this.$index]; 198 }; 199 200 /** 201 * Iterate through the Cursor and call 'each' on every item. 202 * 203 * @param {Function} func function called on every iteration with the 'value' being used as an argument. 204 */ 205 OrdQueryListCursor.prototype.each = function (func) { 206 var result; 207 while (this.next()) { 208 result = func(this.get(), this.$index); 209 if (result) { 210 return result; 211 } 212 } 213 }; 214 215 /** 216 * Return the current index for the Cursor. 217 * 218 * @returns {Number} 219 */ 220 OrdQueryListCursor.prototype.getIndex = function () { 221 return this.$index; 222 }; 223 224 /** 225 * Return the current ORD String at the current index. 226 * 227 * @returns {String} 228 */ 229 OrdQueryListCursor.prototype.getOrd = function () { 230 return this.$list.toString(this.$index + 1); 231 }; 232 233 /** 234 * Resolve the next ORD Query. 235 * 236 * @param {OrdTarget} target 237 * @param {Object} options 238 */ 239 OrdQueryListCursor.prototype.resolveNext = function (target, options) { 240 try { 241 // Resolve the next part of the ORD scheme 242 if (this.next()) { 243 var query = this.get(); 244 query.getScheme().resolve(target, query, this, options); 245 } 246 // If we've finished iterating through the ORDs then 247 // call the original ok callback 248 else { 249 options.callback.ok(target); 250 } 251 } 252 catch (err) { 253 options.callback.fail(err); 254 } 255 }; 256 257 /** 258 * @class ORD Query List. 259 * <p> 260 * Used to hold a list of OrdQueries. 261 * 262 * @name baja.OrdQueryList 263 * @extends BaseBajaObj 264 * 265 * @see OrdQuery 266 * 267 * @param {Array} [queries] an array of ORD queries. 268 */ 269 baja.OrdQueryList = function (queries) { 270 this.$queries = queries || []; 271 }.$extend(BaseBajaObj); 272 273 /** 274 * Add an ORD Query to the List 275 * 276 * @param query 277 */ 278 baja.OrdQueryList.prototype.add = function (query) { 279 this.$queries.push(query); 280 this.$hasUnknown = undefined; // reset the cached hasUnknown result 281 }; 282 283 /** 284 * Is the list empty? 285 * 286 * @returns {Boolean} 287 */ 288 baja.OrdQueryList.prototype.isEmpty = function () { 289 return this.$queries.length === 0; 290 }; 291 292 /** 293 * Does the list contain an unknown ORD scheme? 294 * 295 * @private 296 * 297 * @returns {Boolean} 298 */ 299 baja.OrdQueryList.prototype.hasUnknown = function () { 300 if (this.$hasUnknown !== undefined) { 301 return this.$hasUnknown; 302 } 303 304 // Search for an unknown ORD scheme and cache the result 305 var i, unknown = false; 306 for (i = 0; i < this.$queries.length; ++i) { 307 if (this.$queries[i].getScheme() instanceof baja.UnknownScheme) { 308 unknown = true; 309 } 310 } 311 this.$hasUnknown = unknown; 312 return unknown; 313 }; 314 315 /** 316 * Returns a Cursor for use with the ORD Query List. 317 * 318 * @returns {OrdQueryListCursor} the Cursor for the ORD Query List. 319 */ 320 baja.OrdQueryList.prototype.getCursor = function () { 321 return new OrdQueryListCursor(this); 322 }; 323 324 function normalizeOrdQueryList(list) { 325 var i; 326 for (i = 0; i < list.$queries.length; ++i) { 327 if (list.$queries[i].normalize(list, i)) { 328 return true; 329 } 330 } 331 return false; 332 } 333 334 /** 335 * Normalize the ORD Query List. 336 * 337 * @private 338 * 339 * @returns {baja.OrdQueryList} returns itself. 340 */ 341 baja.OrdQueryList.prototype.normalize = function () { 342 // Don't try to normalize if there's unknown client ORD Schemes 343 if (!this.hasUnknown()) { 344 for (;;) { 345 if (!normalizeOrdQueryList(this)) { 346 break; 347 } 348 } 349 } 350 return this; 351 }; 352 353 /** 354 * Return String representation of the ORD Query List. 355 * 356 * @returns {String} 357 */ 358 baja.OrdQueryList.prototype.toString = function (length) { 359 length = bajaDef(length, this.$queries.length); 360 var a = [], i; 361 for (i = 0; i < length; ++i) { 362 a.push(this.$queries[i].toString()); 363 } 364 return a.join("|"); 365 }; 366 367 /** 368 * Return the query object at the specified index. 369 * 370 * @param {Number|String} index or scheme name. 371 * @returns query (or null if can't be found). 372 */ 373 baja.OrdQueryList.prototype.get = function (index) { 374 var to = typeof index, 375 queries = this.$queries, 376 i, 377 q; 378 379 if (to === "number") { 380 // Get via index 381 q = queries[index]; 382 } 383 else if (to === "string") { 384 // Search via scheme name 385 for (i = 0; i < queries.length; ++i) { 386 if (queries[i].getSchemeName() === index) { 387 q = queries[i]; 388 break; 389 } 390 } 391 } 392 393 return q || null; 394 }; 395 396 /** 397 * Set an ORD query object at the given index. 398 * 399 * @param {Number} index 400 * @param query 401 */ 402 baja.OrdQueryList.prototype.set = function (index, query) { 403 if (index < 0 || index > this.$queries.length) { 404 throw new Error("Invalid index (" + index + ")"); 405 } 406 this.$queries[index] = query; 407 this.$hasUnknown = undefined; // reset the cached hasUnknown result 408 }; 409 410 /** 411 * Remove the entry at the specified index and return it. 412 * 413 * @param {Number} index 414 * @returns query 415 */ 416 baja.OrdQueryList.prototype.remove = function (index) { 417 var query = null; 418 if (index >= 0 && index < this.$queries.length) { 419 query = this.$queries.splice(index, 1)[0]; 420 this.$hasUnknown = undefined; // reset the cached hasUnknown result 421 } 422 return query; 423 }; 424 425 /** 426 * Return the size of the query list. 427 * 428 * @returns {Number} size of the list. 429 */ 430 baja.OrdQueryList.prototype.size = function () { 431 return this.$queries.length; 432 }; 433 434 /** 435 * @class ORD Target. 436 * <p> 437 * This constructor shouldn't be invoked directly. 438 * 439 * @name OrdTarget 440 * @extends BaseBajaObj 441 * @inner 442 * @public 443 * 444 * @param {OrdTarget} [base] the base ORD Target 445 */ 446 var OrdTarget = function (base) { 447 this.base = base || null; 448 this.object = null; 449 450 if (base && typeof base === "object") { 451 base.next = this; 452 } 453 }.$extend(BaseBajaObj); 454 455 /** 456 * Return the Component for the ORD Target. 457 * 458 * @returns {baja.Component} 459 */ 460 OrdTarget.prototype.getComponent = function () { 461 if (baja.hasType(this.container) && this.container.getType().isComponent()) { 462 return this.container; 463 } 464 else if (baja.hasType(this.object)) { 465 if (this.object.getType().isComponent()) { 466 return this.object; 467 } 468 else if (this.object.getType().isComplex()) { 469 var o = this.object.getParent(); 470 while (o !== null) { 471 if (o.getType().isComponent()) { 472 break; 473 } 474 o = o.getParent(); 475 } 476 if (o !== null) { 477 return o; 478 } 479 } 480 } 481 if (this.base && typeof this.base === "object") { 482 return this.base.getComponent(); 483 } 484 return null; 485 }; 486 487 /** 488 * Return the object associated with this OrdTarget. 489 * <p> 490 * This method will attempt to access any resolved value from its 491 * Property. If the resolved value wasn't resolved from a Property then 492 * the original resolved object is returned. 493 * 494 * @returns {baja.Object} 495 */ 496 OrdTarget.prototype.getObject = function () { 497 if (this.container) { 498 var parent = this.container, 499 slot, 500 i; 501 502 // Walk up the Property Path 503 if (this.propertyPath) { 504 if (this.propertyPath.length > 0) { 505 var val = parent; 506 for (i = 0; i < this.propertyPath.length; ++i) { 507 val = val.get(this.propertyPath[i]); 508 } 509 return val; 510 } 511 } 512 // If no PropertyPath then access from the Slot 513 else if (this.slot) { 514 return parent.get(this.slot); 515 } 516 } 517 // By default, just return the object originally resolved 518 return this.object; 519 }; 520 521 /** 522 * @class Object Resolution Descriptor. 523 * <p> 524 * An ORD is how we can access Objects in the Server from BajaScript. It's 525 * similar to a URI but is much more powerful and extensible. For more 526 * information, please see the Niagara developer documentation on ORDs and how 527 * they're used. 528 * <pre> 529 * // Resolve an ORD 530 * baja.Ord.make("station:|slot:/Folder/NumericWritable").get({ 531 * ok: function () { 532 * baja.outln(this.getOutDisplay()); 533 * }, 534 * lease: true 535 * }); 536 * </pre> 537 * <p> 538 * If more than one ORD needs to be resolved then use a {@link baja.BatchResolve}. 539 * <p> 540 * This Constructor shouldn't be invoked directly. Please use the 'make' methods to create 541 * an instance of an ORD. 542 * 543 * @see baja.Ord.make 544 * @see baja.BatchResolve 545 * 546 * @name baja.Ord 547 * @extends baja.Simple 548 */ 549 baja.Ord = function (ord) { 550 baja.Ord.$super.apply(this, arguments); 551 this.$ord = strictArg(ord, String); 552 }.$extend(baja.Simple); 553 554 /** 555 * Default ORD instance. 556 */ 557 baja.Ord.DEFAULT = new baja.Ord(""); 558 559 /** 560 * Make an ORD. 561 * <p> 562 * The argument can be a String, baja.Ord or an Object. 563 * If an Object is passed in then if there's a base and child Property, this 564 * will be used to construct the ORD (by calling 'toString' on each). Otherwise 'toString' will be called 565 * on the Object for the ORD. 566 * <pre> 567 * // Resolve an ORD 568 * baja.Ord.make("station:|slot:/Folder/NumericWritable").get({ 569 * ok: function () { 570 * baja.outln(this.getOutDisplay()); 571 * }, 572 * lease: true 573 * }); 574 * </pre> 575 * 576 * @param {String|Object} ord 577 * @returns {baja.Ord} 578 */ 579 baja.Ord.make = function (ord) { 580 if (arguments.length === 0) { 581 return baja.Ord.DEFAULT; 582 } 583 584 // Handle child and base 585 if (typeof ord === "object" && ord.base && ord.child) { 586 ord = ord.base.toString() + "|" + ord.child.toString(); 587 } 588 else { 589 ord = ord.toString(); 590 } 591 592 // Handle URL decoding 593 if (ord.match(/^\/ord/)) { 594 // Remove '/ord?' or '/ord/' 595 ord = ord.substring(5, ord.length); 596 597 // Replace this with the pipe character 598 ord = decodeURI(ord); 599 } 600 601 if (ord === "" || ord === "null") { 602 return baja.Ord.DEFAULT; 603 } 604 605 return new baja.Ord(ord); 606 }; 607 608 /** 609 * Make an ORD. 610 * 611 * @see baja.Ord.make 612 * 613 * @param {String} ord 614 * @returns {baja.Ord} 615 */ 616 baja.Ord.prototype.make = function (ord) { 617 return baja.Ord.make(ord); 618 }; 619 620 /** 621 * Decode an ORD from a String. 622 * 623 * @param {String} str the ORD String. 624 * @returns {baja.Ord} the decoded ORD. 625 */ 626 baja.Ord.prototype.decodeFromString = function (str) { 627 return baja.Ord.make(str); 628 }; 629 630 /** 631 * Encode an ORD to a String. 632 * 633 * @returns {String} the ORD encoded to a String. 634 */ 635 baja.Ord.prototype.encodeToString = function () { 636 return this.$ord; 637 }; 638 639 // Register Type 640 baja.Ord.registerType("baja:Ord"); 641 642 /** 643 * Return an String representation of the object. 644 * 645 * @returns {String} a String representation of an ORD. 646 */ 647 baja.Ord.prototype.toString = function () { 648 return this.$ord; 649 }; 650 651 /** 652 * Return the inner value of this Object. 653 * 654 * @returns {String} a String representation of an ORD. 655 */ 656 baja.Ord.prototype.valueOf = function () { 657 return this.toString(); 658 }; 659 660 /** 661 * Parse an ORD to a number of ORD Query objects. 662 * 663 * @returns {baja.OrdQueryList} a list of ORDs to resolve. 664 */ 665 baja.Ord.prototype.parse = function () { 666 // TODO: Validate all characters are valid 667 var list = new baja.OrdQueryList(); 668 if (this.$ord.length === 0) { 669 return list; 670 } 671 672 var os = this.$ord.split("|"), // ORDs 673 i, 674 ind, 675 schemeName, 676 scheme, 677 body; 678 679 for (i = 0; i < os.length; ++i) { 680 ind = os[i].indexOf(":"); 681 if (ind === -1) { 682 throw new Error("Unable to resolve ORD: " + os[i]); 683 } 684 685 schemeName = os[i].substring(0, ind); 686 body = os[i].substring(ind + 1, os[i].length); 687 scheme = baja.OrdScheme.lookup(schemeName); 688 689 // Create the ORD scheme 690 list.add(scheme.parse(schemeName, body)); 691 } 692 return list; 693 }; 694 695 /** 696 * Resolve an ORD. 697 * <p> 698 * Resolving an ORD consists of parsing and processing it to get a result. The result is an ORD Target. 699 * <p> 700 * Any network calls that result from processing an ORD are always asynchronous. 701 * <p> 702 * The resolve method requires an ok function callback or an Object Literal that contains the method's arguments... 703 * <pre> 704 * baja.Ord.make("station:|slot:/").resolve(function ok(target) { 705 * // process the ORD Target 706 * }); 707 * 708 * //...or use an Object Literal to specify multiple arguments... 709 * 710 * baja.Ord.make("station:|slot:/").resolve({ 711 * ok: function (target) { 712 * // process the ORD target 713 * }, 714 * fail: function (err) { 715 * // process the failure 716 * }, 717 * lease: true // ensure any resolved Components are leased before calling 'ok' 718 * }); 719 * </pre> 720 * <p> 721 * Please note that unlike other methods that require network calls, no batch object can be specified! 722 * 723 * @see OrdTarget 724 * @see baja.Ord#get 725 * @see baja.RelTime 726 * 727 * @param {Object} [obj] the Object Literal that contains the method's arguments. 728 * @param {Function} [obj.ok] the ok function called once the ORD has been successfully resolved. The ORD Target is passed to this function when invoked. 729 * @param {Function} [obj.fail] the fail function called if the ORD fails to resolve. An error cause is passed to this function when invoked. 730 * @param [obj.base] the base Object to resolve the ORD against. 731 * @param {Boolean} [obj.lease] if defined and true, any Components are temporarily subscribed. 732 * @param {Number|baja.RelTime} [obj.leaseTime] the amount of time in milliseconds to lease for (lease argument must be true). As well as a Number, this can also 733 * a baja.RelTime. 734 * @param {baja.Subscriber} [obj.subscriber] if defined the Component is subscribed using this Subscriber. 735 * @param {Object} [obj.cursor] if defined, this specifies parameters for iterating through a Cursor (providing the ORD resolves to a Collection or Table). 736 * For more information, please see {@link baja.coll.Collection#cursor}. 737 */ 738 baja.Ord.prototype.resolve = function (obj) { 739 obj = objectify(obj, "ok"); 740 741 var base = bajaDef(obj.base, baja.nav.localhost), 742 cb = obj.cb === undefined ? new Callback(obj.ok, obj.fail) : obj.cb, 743 subscriber = bajaDef(obj.subscriber, null), 744 lease = bajaDef(obj.lease, false), 745 leaseTime = obj.leaseTime, // If undefined, lease will use lease default 746 full = bajaDef(obj.full, false), 747 cursor = obj.cursor; 748 749 // Ensure 'this' in callback is the target's Component. If it's not a Component then 750 // fallback to the resolved Object. 751 cb.addOk(function (ok, fail, target) { 752 var resolvedObj = target.getComponent(); 753 if (!resolvedObj) { 754 resolvedObj = target.object; 755 } 756 if (resolvedObj !== null) { 757 ok.call(resolvedObj, target); 758 } 759 else { 760 ok(target); 761 } 762 }); 763 764 if (subscriber !== null) { 765 // If we need to subscribe using a Subscriber once the Component is resolved... 766 cb.addOk(function (ok, fail, target) { 767 function newOk() { 768 ok(target); 769 } 770 var comp = target.getComponent(); 771 if (comp !== null && comp.isMounted()) { 772 subscriber.subscribe({ 773 "comps": [comp], 774 "ok": newOk, 775 "fail": fail, 776 "importAsync": true 777 }); 778 } 779 else { 780 newOk(); 781 } 782 }); 783 } 784 785 if (lease) { 786 // If we need to lease once the Component is resolved... 787 cb.addOk(function (ok, fail, target) { 788 function newOk() { 789 ok(target); 790 } 791 var comp = target.getComponent(); 792 if (comp !== null && comp.isMounted()) { 793 comp.lease({ 794 "ok": newOk, 795 "fail": fail, 796 "time": leaseTime, 797 "importAsync": true 798 }); 799 } 800 else { 801 newOk(); 802 } 803 }); 804 } 805 806 try { 807 // Check the user isnt trying to batch an ORD as this isn't supported 808 if (obj.batch) { 809 throw new Error("Cannot batch ORD resolution"); 810 } 811 812 var ordQueries = this.parse(); 813 if (ordQueries.isEmpty()) { 814 throw new Error("Cannot resolve null ORD: " + this.toString()); 815 } 816 817 var target = new OrdTarget(); 818 target.object = base; 819 820 var options = { 821 "full": full, 822 "callback": cb, 823 "queries": ordQueries, 824 "ord": this, 825 "cursor": cursor 826 }; 827 828 // Normalize 829 ordQueries.normalize(); 830 831 // If there are ORD Schemes that aren't implemented in BajaScript then we 832 // simply make a network call and resolve the ORD Server side 833 if (ordQueries.hasUnknown()) { 834 var newTarget = new OrdTarget(target); 835 836 cb.addOk(function (ok, fail, resp) { 837 var newOk = function () { 838 // Decode the result 839 var t = newTarget.object = baja.bson.decodeValue(resp.o, baja.$serverDecodeContext); 840 841 // Finished iterating so just make the callback 842 ok(newTarget); 843 844 // If we've got a collection result cached then call 'cursor' on the Collection or Table 845 if (resp.c && 846 baja.hasType(t) && 847 t.getType().is("baja:ICollection") && 848 typeof t.cursor === "function") { 849 cursor.$data = resp.c; 850 t.cursor(cursor); 851 } 852 }; 853 854 // Scan for any unknown types we don't have here and make another 855 // network request if necessary 856 var unknownTypes = baja.bson.scanForUnknownTypes(resp); 857 858 if (unknownTypes.length > 0) { 859 var importBatch = new baja.comm.Batch(); 860 861 baja.importTypes({ 862 "typeSpecs": unknownTypes, 863 "ok": newOk, 864 "fail": fail, 865 "batch": importBatch 866 }); 867 868 // Commit synchronously if this has been resolved from a BatchResolve. 869 // It's best to do this as there's other ORDs also resolving in tandem. 870 if (obj.fromBatchResolve) { 871 importBatch.commitSync(); 872 } 873 else { 874 importBatch.commit(); 875 } 876 } 877 else { 878 newOk(); 879 } 880 }); 881 882 // If Cursor information is defined, ensure we set some defaults 883 if (options.cursor) { 884 options.cursor.limit = options.cursor.limit || 10; 885 options.cursor.offset = options.cursor.offset || 0; 886 } 887 888 // Make the network call to resolve the complete ORD Server side 889 baja.comm.resolve(this, base, cb, options); 890 } 891 else { 892 893 // Resolve the ORD. Each ORD scheme must call 'resolveNext' on the cursor to process the next 894 // part of the ORD. This design has been chosen because some ORD schemes may need to make network calls. 895 // If the network call is asynchronous in nature then they'll be a delay before the ORD can process further 896 ordQueries.getCursor().resolveNext(target, options); 897 } 898 } 899 catch (err) { 900 cb.fail(err); 901 } 902 }; 903 904 /** 905 * Resolve the ORD and get the resolved Object from the ORD Target. 906 * <p> 907 * This method calls {@link baja.Ord#resolve} and calls 'get' on the ORD Target to 908 * pass the object onto the ok function callback. 909 * <p> 910 * An Object Literal is used to to specify the method's arguments. For more information 911 * on how to use this method please see {@link baja.Ord#resolve}. 912 * <pre> 913 * baja.Ord.make("service:baja:UserService|slot:jack").get({ 914 * ok: function (user) { 915 * // Do something with the user... 916 * }, 917 * lease: true 918 * }); 919 * </pre> 920 * 921 * @see OrdTarget#resolve 922 * 923 * @param {Object} [object] 924 * @returns the value resolved from the ORD. 925 */ 926 baja.Ord.prototype.get = function (obj) { 927 obj = objectify(obj, "ok"); 928 929 obj.cb = new Callback(obj.ok, obj.fail); 930 obj.cb.addOk(function (ok, fail, target) { 931 ok.call(this, target.object); 932 }); 933 934 this.resolve(obj); 935 }; 936 937 /** 938 * Return a normalized version of the ORD. 939 * 940 * @returns {baja.Ord} 941 */ 942 baja.Ord.prototype.normalize = function () { 943 return baja.Ord.make(this.parse().normalize()); 944 }; 945 946 /** 947 * Relativize is used to extract the relative portion 948 * of this ord within an session: 949 * <ol> 950 * <li> 951 * First the ord is normalized.</li> 952 * <li> 953 * Starting from the left to right, if any queries are 954 * found which return true for isSession(), then remove 955 * everything from that query to the left.</li> 956 * </ol> 957 * 958 * @returns {baja.Ord} 959 */ 960 baja.Ord.prototype.relativizeToSession = function () { 961 var list = this.parse().normalize(), 962 q, 963 newList = new baja.OrdQueryList(), 964 i; 965 966 for (i = 0; i < list.size(); ++i) { 967 q = list.get(i); 968 969 if (!q.isSession() && !q.isHost()) { 970 newList.add(q); 971 } 972 } 973 return baja.Ord.make(newList); 974 }; 975 976 /** 977 * Return the ORD as URI that can be used in a browser. 978 * 979 * @returns {String} 980 */ 981 baja.Ord.prototype.toUri = function () { 982 var uri = encodeURI(this.relativizeToSession().toString()); 983 984 // Hack for HTTP scheme 985 return uri.match(/^http/i) ? uri : ("/ord?" + uri); 986 }; 987 988 /** 989 * @class ORD Scheme. 990 * <p> 991 * An ORD is made up of a series of ORD Queries separated by the '|' character. 992 * Each ORD Query has an ORD Scheme name (i.e. 'slot') and a body (i.e. '/Drivers/ModbusNetwork'). 993 * 994 * @name baja.OrdScheme 995 * @extends baja.Singleton 996 */ 997 baja.OrdScheme = function () { 998 baja.OrdScheme.$super.apply(this, arguments); 999 }.$extend(baja.Singleton).registerType("baja:OrdScheme"); 1000 1001 /** 1002 * All extended ORD Schemes must implement this method so an ORD can be resolved! 1003 * 1004 * @param {ORDTarget} target the current ORD Target 1005 * @param {Object} query the ORD Query used in resolving the ORD 1006 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD 1007 * @param {Object} options options used for resolving an ORD 1008 */ 1009 baja.OrdScheme.prototype.resolve = function (target, query, cursor, options) { 1010 throw new Error("ORD Scheme must implement resolve: " + this.getType()); 1011 }; 1012 1013 /** 1014 * Return the ORD Scheme for the given scheme name. 1015 * 1016 * @returns {baja.OrdScheme} 1017 */ 1018 baja.OrdScheme.lookup = function (schemeName) { 1019 var type = baja.registry.getOrdScheme(schemeName); 1020 return type && type.hasConstructor() ? type.getInstance() : baja.UnknownScheme.DEFAULT; 1021 }; 1022 1023 /** 1024 * Return an ORD Query for the scheme. 1025 * 1026 * @returns {OrdQuery} 1027 */ 1028 baja.OrdScheme.prototype.parse = function (schemeName, body) { 1029 return new OrdQuery({ 1030 scheme: this, 1031 schemeName: schemeName, 1032 body: body 1033 }); 1034 }; 1035 1036 //////////////////////////////////////////////////////////////// 1037 // Unknown ORD Scheme 1038 //////////////////////////////////////////////////////////////// 1039 1040 /** 1041 * @class Unknown ORD Scheme. 1042 * <p> 1043 * Some ORD Schemes are represented in BajaScript and some are not. When an ORD is resolved, 1044 * the BajaScript Registry is used to see if we locally have an ORD scheme representation. 1045 * If all of the ORD Schemes in an ORD do have a local representation (i.e. they have JS Constructors), 1046 * the ORD is resolved locally. If any unknown ORD schemes are found then the entire ORD is resolved 1047 * on the Server and the corresponding results are serialized and sent back down to the client. 1048 * 1049 * @name baja.UnknownScheme 1050 * @extends baja.OrdScheme 1051 * @private 1052 */ 1053 baja.UnknownScheme = function () { 1054 baja.UnknownScheme.$super.apply(this, arguments); 1055 }.$extend(baja.OrdScheme); 1056 1057 /** 1058 * Default Unknown ORD Scheme instance. 1059 * @private 1060 */ 1061 baja.UnknownScheme.DEFAULT = new baja.UnknownScheme(); 1062 1063 /** 1064 * Called when an ORD is resolved. 1065 * 1066 * @private 1067 * 1068 * @see baja.OrdScheme#resolve 1069 * 1070 * @param {ORDTarget} target the current ORD Target. 1071 * @param {Object} query the ORD Query used in resolving the ORD. 1072 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1073 * @param {Object} options options used for resolving an ORD. 1074 */ 1075 baja.UnknownScheme.prototype.resolve = function (target, query, cursor, options) { 1076 1077 // Fail since this should always be resolved Server side 1078 var newTarget = new OrdTarget(target); 1079 options.callback.fail("Unknown BajaScript ORD Scheme: " + query.getSchemeName()); 1080 }; 1081 1082 //////////////////////////////////////////////////////////////// 1083 // Http ORD Scheme 1084 //////////////////////////////////////////////////////////////// 1085 1086 /** 1087 * @class HTTP ORD Scheme. 1088 * 1089 * @name baja.HttpScheme 1090 * @extends baja.OrdScheme 1091 * @private 1092 */ 1093 baja.HttpScheme = function () { 1094 baja.HttpScheme.$super.apply(this, arguments); 1095 }.$extend(baja.OrdScheme).registerType("net:HttpScheme"); 1096 1097 /** 1098 * Default HTTP ORD Scheme instance. 1099 * @private 1100 */ 1101 baja.HttpScheme.DEFAULT = new baja.HttpScheme(); 1102 1103 /** 1104 * Called when an ORD is resolved. 1105 * 1106 * @private 1107 * 1108 * @see baja.OrdScheme#resolve 1109 * 1110 * @param {ORDTarget} target the current ORD Target. 1111 * @param {Object} query the ORD Query used in resolving the ORD. 1112 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1113 * @param {Object} options options used for resolving an ORD. 1114 */ 1115 baja.HttpScheme.prototype.resolve = function (target, query, cursor, options) { 1116 cursor.resolveNext(new OrdTarget(target), options); 1117 }; 1118 1119 function trimToStart(list, index) { 1120 if (index > 0) { 1121 var size = list.size(), i; 1122 for (i = 0; i < index; ++i) { 1123 list.remove(0); 1124 } 1125 return true; 1126 } 1127 return false; 1128 } 1129 1130 /** 1131 * Return an ORD Query for the scheme. 1132 * 1133 * @returns {OrdQuery} 1134 */ 1135 baja.HttpScheme.prototype.parse = function (schemeName, body) { 1136 return new OrdQuery({ 1137 scheme: this, 1138 schemeName: schemeName, 1139 body: body, 1140 normalize: trimToStart 1141 }); 1142 }; 1143 1144 //////////////////////////////////////////////////////////////// 1145 // Host Schemes 1146 //////////////////////////////////////////////////////////////// 1147 1148 /** 1149 * @class Local Host ORD Scheme. 1150 * <p> 1151 * This scheme resolved to the local host. The local host represents the Station BajaScript is directly connected too. 1152 * 1153 * @name baja.LocalScheme 1154 * @extends baja.OrdScheme 1155 * @private 1156 */ 1157 baja.LocalScheme = function () { 1158 baja.LocalScheme.$super.apply(this, arguments); 1159 }.$extend(baja.OrdScheme); 1160 1161 /** 1162 * Default Local ORD Scheme instance. 1163 * @private 1164 */ 1165 baja.LocalScheme.DEFAULT = new baja.LocalScheme(); 1166 1167 // Register Type 1168 baja.LocalScheme.registerType("baja:LocalScheme"); 1169 1170 /** 1171 * Called when an ORD is resolved. 1172 * 1173 * @private 1174 * 1175 * @see baja.OrdScheme#resolve 1176 * 1177 * @param {ORDTarget} target the current ORD Target. 1178 * @param {Object} query the ORD Query used in resolving the ORD. 1179 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1180 * @param {Object} options options used for resolving an ORD. 1181 */ 1182 baja.LocalScheme.prototype.resolve = function (target, query, cursor, options) { 1183 var newTarget = new OrdTarget(target); 1184 newTarget.object = baja.nav.localhost; 1185 cursor.resolveNext(newTarget, options); 1186 }; 1187 1188 /** 1189 * Return an ORD Query for the scheme. 1190 * 1191 * @returns {OrdQuery} 1192 */ 1193 baja.LocalScheme.prototype.parse = function (schemeName, body) { 1194 return new OrdQuery({ 1195 scheme: this, 1196 schemeName: schemeName, 1197 body: body, 1198 isHost: true, 1199 isSession: true, 1200 normalize: trimToStart 1201 }); 1202 }; 1203 1204 //////////////////////////////////////////////////////////////// 1205 // Session Schemes 1206 //////////////////////////////////////////////////////////////// 1207 1208 /** 1209 * @class Fox ORD Scheme. 1210 * 1211 * @name baja.FoxScheme 1212 * @extends baja.OrdScheme 1213 * @private 1214 */ 1215 baja.FoxScheme = function () { 1216 baja.FoxScheme.$super.apply(this, arguments); 1217 }.$extend(baja.OrdScheme); 1218 1219 /** 1220 * Default Fox ORD Scheme instance. 1221 * @private 1222 */ 1223 baja.FoxScheme.DEFAULT = new baja.FoxScheme(); 1224 1225 // Register Type 1226 baja.FoxScheme.registerType("fox:FoxScheme"); 1227 1228 /** 1229 * Called when an ORD is resolved. 1230 * 1231 * @private 1232 * 1233 * @see baja.OrdScheme#resolve 1234 * 1235 * @param {ORDTarget} target the current ORD Target. 1236 * @param {Object} query the ORD Query used in resolving the ORD. 1237 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1238 * @param {Object} options options used for resolving an ORD. 1239 */ 1240 baja.FoxScheme.prototype.resolve = function (target, query, cursor, options) { 1241 var newTarget = new OrdTarget(target); 1242 newTarget.object = target.object; 1243 cursor.resolveNext(newTarget, options); 1244 }; 1245 1246 /** 1247 * Return an ORD Query for the scheme. 1248 * 1249 * @returns {OrdQuery} 1250 */ 1251 baja.FoxScheme.prototype.parse = function (schemeName, body) { 1252 return new OrdQuery({ 1253 scheme: this, 1254 schemeName: schemeName, 1255 body: body, 1256 isSession: true, 1257 normalize: function (list, index) { 1258 // Shift to host 1259 var i, q, modified = false; 1260 for (i = index - 1; i >= 0; --i) { 1261 q = list.get(i); 1262 if (!q.isHost()) { 1263 list.remove(i); 1264 modified = true; 1265 } 1266 } 1267 return modified; 1268 } 1269 }); 1270 }; 1271 1272 //////////////////////////////////////////////////////////////// 1273 // Space Schemes 1274 //////////////////////////////////////////////////////////////// 1275 1276 /** 1277 * @class Station ORD Scheme. 1278 * <p> 1279 * This scheme resolves to a Station. 1280 * 1281 * @name baja.StationScheme 1282 * @extends baja.OrdScheme 1283 * @private 1284 */ 1285 baja.StationScheme = function () { 1286 baja.StationScheme.$super.apply(this, arguments); 1287 }.$extend(baja.OrdScheme); // TODO: Need to extend from SpaceScheme eventually 1288 1289 /** 1290 * Default Station ORD Scheme instance. 1291 * @private 1292 */ 1293 baja.StationScheme.DEFAULT = new baja.StationScheme(); 1294 1295 // Register Type 1296 baja.StationScheme.registerType("baja:StationScheme"); 1297 1298 /** 1299 * Called when an ORD is resolved. 1300 * 1301 * @private 1302 * 1303 * @see baja.OrdScheme#resolve 1304 * 1305 * @param {ORDTarget} target the current ORD Target. 1306 * @param {Object} query the ORD Query used in resolving the ORD. 1307 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1308 * @param {Object} options options used for resolving an ORD. 1309 */ 1310 baja.StationScheme.prototype.resolve = function (target, query, cursor, options) { 1311 1312 var newTarget = new OrdTarget(target); 1313 newTarget.object = target.object; 1314 1315 if (!newTarget.object.getType().is("baja:ComponentSpace")) { 1316 newTarget.object = baja.nav.localhost.station; 1317 } 1318 1319 cursor.resolveNext(newTarget, options); 1320 }; 1321 1322 /** 1323 * Return an ORD Query for the scheme. 1324 * 1325 * @returns {OrdQuery} 1326 */ 1327 baja.StationScheme.prototype.parse = function (schemeName, body) { 1328 return new OrdQuery({ 1329 scheme: this, 1330 schemeName: schemeName, 1331 body: body, 1332 normalize: function (list, index) { 1333 // Shift to session 1334 var i, q, modified = false; 1335 for (i = index - 1; i >= 0; --i) { 1336 q = list.get(i); 1337 if (!q.isHost() && !q.isSession()) { 1338 list.remove(i); 1339 modified = true; 1340 } 1341 } 1342 return modified; 1343 } 1344 }); 1345 }; 1346 1347 //////////////////////////////////////////////////////////////// 1348 // SlotPath Resolution 1349 //////////////////////////////////////////////////////////////// 1350 1351 /** 1352 * @class Slot ORD Scheme. 1353 * <p> 1354 * This scheme resolves a SlotPath to a Niagara Object. 1355 * 1356 * @see baja.SlotPath 1357 * 1358 * @name baja.SlotScheme 1359 * @extends baja.OrdScheme 1360 */ 1361 baja.SlotScheme = function () { 1362 baja.SlotScheme.$super.apply(this, arguments); 1363 }.$extend(baja.OrdScheme); // TODO: Need to extend from SpaceScheme eventually 1364 1365 /** 1366 * Default Slot ORD Scheme instance. 1367 * @private 1368 */ 1369 baja.SlotScheme.DEFAULT = new baja.SlotScheme(); 1370 1371 // Register Type 1372 baja.SlotScheme.registerType("baja:SlotScheme"); 1373 1374 // Empty fail function. It doesn't matter is the loads fail as the resolve without the 1375 // network calls will result in the fail we want 1376 var emptyFail = function (err) {}; 1377 1378 function slotSchemeResolve(scheme, target, query, cursor, options, netcall) { 1379 var newTarget = new OrdTarget(target), 1380 object = target.object; 1381 1382 newTarget.slotPath = query; 1383 1384 var slotPath = newTarget.slotPath, // SlotPath 1385 space = null, // Space 1386 container = null; // base container 1387 1388 netcall = bajaDef(netcall, true); 1389 1390 // TODO: May need to make more robust if other types of Slot Paths are added 1391 var isVirtual = slotPath.constructor !== baja.SlotPath; 1392 1393 // TODO: Property Container support 1394 1395 if (object.getType().is("baja:VirtualGateway") && isVirtual) { 1396 space = object.getVirtualSpace(); 1397 container = space.getRootComponent(); 1398 } 1399 else if (object.getType().is("baja:ComponentSpace")) { 1400 space = object; 1401 container = object.getRootComponent(); 1402 } 1403 else if (object.getType().isComponent()) { 1404 space = object.getComponentSpace(); 1405 if (slotPath.isAbsolute()) { 1406 container = space.getRootComponent(); 1407 } 1408 else { 1409 container = object; 1410 } 1411 } 1412 else { 1413 throw new Error("Slot Scheme Unsupported ORD base: " + object.getType()); 1414 } 1415 1416 // Hack for Virtual Space 1417 if (isVirtual && slotPath.getBody() === "") { 1418 newTarget.object = space; 1419 cursor.resolveNext(newTarget, options); 1420 return; 1421 } 1422 1423 // Avoid a network call if the Component Space doesn't support callbacks 1424 if (netcall) { 1425 if (space === null) { 1426 netcall = false; 1427 } 1428 else { 1429 netcall = space.hasCallbacks(); 1430 } 1431 } 1432 1433 var value = container, 1434 nameAtDepth, 1435 slot = null, 1436 propertyPath = null, 1437 depthRequestIndex = -1, 1438 backupDepth = slotPath.getBackupDepth(), 1439 k, i, j; 1440 1441 // first walk up using backup 1442 for (k = 0; k < backupDepth; ++k) { 1443 container = container.getType().isComponent() ? container.getParent() : null; 1444 value = container; 1445 1446 if (value === null) { 1447 throw new Error("Cannot walk backup depth: " + object); 1448 } 1449 } 1450 1451 // Walk the SlotPath 1452 for (i = 0; i < slotPath.depth(); ++i) { 1453 nameAtDepth = slotPath.nameAt(i); 1454 1455 if (isVirtual) { 1456 nameAtDepth = baja.SlotPath.escape(nameAtDepth); 1457 } 1458 1459 if (value.getType().isComplex()) { 1460 slot = value.getSlot(nameAtDepth); 1461 } 1462 else { 1463 throw new Error("Unable to resolve ORD: " + slotPath); 1464 } 1465 1466 if (slot === null) { 1467 if (netcall) { 1468 depthRequestIndex = i; 1469 break; 1470 } 1471 1472 throw new Error("Unresolved ORD - unable to resolve SlotPath"); 1473 } 1474 1475 if (!slot.isProperty()) { 1476 if (i !== slotPath.depth() - 1) { // Actions and Topics must be at the final depth 1477 throw new Error("Unresolved ORD - Actions/Topics must be at final depth: " + slotPath); 1478 } 1479 1480 newTarget.container = container; 1481 newTarget.slot = slot; 1482 1483 // Resolve the next part of the ORD and feed this target into it 1484 cursor.resolveNext(newTarget, options); 1485 return; 1486 } 1487 1488 value = value.get(slot); 1489 1490 // If we have a Component without a Handle then it's probably a value from a frozen Property 1491 // that's completely unloaded. In this case, we need to perform a load for this Component 1492 if (value.getType().isComponent() && value.$handle === null && space !== null) { 1493 if (netcall) { 1494 depthRequestIndex = i; 1495 break; 1496 } 1497 1498 throw new Error("Unresolved ORD - unable to load handle for Component"); 1499 } 1500 1501 if (propertyPath === null && value.getType().is("baja:IPropertyContainer")) { 1502 container = value; 1503 } 1504 else { 1505 if (propertyPath === null) { 1506 propertyPath = []; 1507 } 1508 propertyPath.push(slot); 1509 } 1510 } 1511 1512 // Make a network call to resolve the SlotPath 1513 var slotOrd, 1514 batch; 1515 1516 if (netcall && depthRequestIndex > -1) { 1517 batch = new baja.comm.Batch(); 1518 1519 // Load ops on Slots that don't exist 1520 slotOrd = "slot:"; 1521 if (slotPath.isAbsolute()) { 1522 slotOrd += "/"; 1523 } 1524 1525 var slotPathInfo = []; 1526 for (j = 0; j < slotPath.depth(); ++j) { 1527 if (j >= depthRequestIndex) { 1528 1529 slotPathInfo.push({ 1530 o: slotOrd, 1531 sn: isVirtual ? baja.SlotPath.escape(slotPath.nameAt(j)) : slotPath.nameAt(j) 1532 }); 1533 } 1534 1535 if (j > 0) { 1536 slotOrd += "/"; 1537 } 1538 slotOrd += isVirtual ? baja.SlotPath.escape(slotPath.nameAt(j)) : slotPath.nameAt(j); 1539 } 1540 1541 var newOk = function () { 1542 // Now the network calls have all been committed, resolve this 1543 // Slot path without making any network calls 1544 scheme.resolve(target, query, cursor, options, /*network call*/false); 1545 }; 1546 1547 var newFail = function (err) { 1548 options.callback.fail(err); 1549 }; 1550 1551 // Attempt to load the missing Slot Path information 1552 space.getCallbacks().loadSlotPath(slotPathInfo, 1553 container, 1554 new Callback(newOk, newFail, batch)); 1555 1556 batch.commit(); 1557 return; 1558 } 1559 else if (slot === null && slotPath.depth() > 0) { 1560 throw new Error("Unable to resolve ORD: " + slotPath); 1561 } 1562 1563 if (propertyPath === null) { 1564 newTarget.object = container; 1565 } 1566 else { 1567 // If there was a Property Path then use the first Property in the Property Path as the Slot 1568 slot = propertyPath[0]; 1569 newTarget.container = container; 1570 newTarget.object = value; 1571 newTarget.slot = slot; 1572 newTarget.propertyPath = propertyPath; 1573 } 1574 1575 // Resolve the next part of the ORD and feed this target into it 1576 cursor.resolveNext(newTarget, options); 1577 } 1578 1579 function slotSchemeResolveFull(scheme, target, query, cursor, options, netcall) { 1580 1581 var newTarget = new OrdTarget(target), 1582 object = target.object; 1583 1584 newTarget.slotPath = query; 1585 1586 var slotPath = newTarget.slotPath, // SlotPath 1587 space = null, // Space 1588 container = null, // base container 1589 isVirtual = slotPath.constructor !== baja.SlotPath; 1590 1591 netcall = bajaDef(netcall, true); 1592 1593 // TODO: Property Container support 1594 1595 if (object.getType().is("baja:VirtualGateway") && isVirtual) { 1596 space = object.getVirtualSpace(); 1597 container = space.getRootComponent(); 1598 } 1599 else if (object.getType().is("baja:ComponentSpace")) { 1600 space = object; 1601 container = object.getRootComponent(); 1602 } 1603 else if (object.getType().isComponent()) { 1604 space = object.getComponentSpace(); 1605 if (slotPath.isAbsolute()) { 1606 container = space.getRootComponent(); 1607 } 1608 else { 1609 container = object; 1610 } 1611 } 1612 else { 1613 throw new Error("Slot Scheme Unsupported ORD base: " + object.getType()); 1614 } 1615 1616 // Hack for Virtual Space 1617 if (isVirtual && slotPath.getBody() === "") { 1618 newTarget.object = space; 1619 cursor.resolveNext(newTarget, options); 1620 return; 1621 } 1622 1623 // Avoid a network call if the Component Space doesn't support callbacks 1624 if (netcall) { 1625 if (space === null) { 1626 netcall = false; 1627 } 1628 else { 1629 netcall = space.hasCallbacks(); 1630 } 1631 } 1632 1633 var value = container, 1634 nameAtDepth, 1635 slot = null, 1636 propertyPath = null, 1637 batch = new baja.comm.Batch(), 1638 depthRequestIndex = 0, 1639 backupDepth = slotPath.getBackupDepth(), 1640 k, i, j; 1641 1642 // first walk up using backup 1643 for (k = 0; k < backupDepth; ++k) { 1644 container = container.getType().isComponent() ? container.getParent() : null; 1645 value = container; 1646 1647 if (value === null) { 1648 throw new Error("Cannot walk backup depth: " + object); 1649 } 1650 } 1651 1652 // Attempt to load slots on the container if needed 1653 if (container.getType().isComplex()) { 1654 container.loadSlots({ 1655 "ok": baja.ok, 1656 "fail": emptyFail, 1657 "batch": batch 1658 }); 1659 } 1660 1661 if (batch.isEmpty()) { 1662 // Walk the SlotPath 1663 for (i = 0; i < slotPath.depth(); ++i) { 1664 nameAtDepth = slotPath.nameAt(i); 1665 1666 if (isVirtual) { 1667 nameAtDepth = baja.SlotPath.escape(nameAtDepth); 1668 } 1669 1670 if (value.getType().isComplex()) { 1671 value.loadSlots({ 1672 "ok": baja.ok, 1673 "fail": emptyFail, 1674 "batch": batch 1675 }); 1676 1677 slot = value.getSlot(nameAtDepth); 1678 } 1679 else { 1680 throw new Error("Unable to resolve ORD: " + slotPath); 1681 } 1682 1683 if (netcall && !batch.isEmpty()) { 1684 depthRequestIndex = i; 1685 break; 1686 } 1687 1688 if (slot === null) { 1689 throw new Error("Unresolved ORD - unable to resolve SlotPath"); 1690 } 1691 1692 if (!slot.isProperty()) { 1693 if (i !== slotPath.depth() - 1) { // Actions and Topics must be at the final depth 1694 throw new Error("Unresolved ORD - Actions/Topics must be at final depth: " + slotPath); 1695 } 1696 1697 newTarget.container = container; 1698 newTarget.slot = slot; 1699 1700 // Resolve the next part of the ORD and feed this target into it 1701 cursor.resolveNext(newTarget, options); 1702 return; 1703 } 1704 1705 value = value.get(slot); 1706 1707 // If we have a Component without a Handle then it's probably a value from a frozen Property 1708 // that's completely unloaded. In this case, we need to perform a load for this Component 1709 if (value.getType().isComponent() && value.$handle === null && space !== null) { 1710 if (netcall) { 1711 depthRequestIndex = i; 1712 break; 1713 } 1714 1715 throw new Error("Unresolved ORD - unable to load handle for Component"); 1716 } 1717 1718 if (propertyPath === null && value.getType().is("baja:IPropertyContainer")) { 1719 container = value; 1720 } 1721 else { 1722 if (propertyPath === null) { 1723 propertyPath = []; 1724 } 1725 propertyPath.push(slot); 1726 } 1727 } 1728 } 1729 1730 // Make a network call to resolve the SlotPath 1731 var slotOrd; 1732 if (!batch.isEmpty() && netcall) { 1733 1734 // Load ops on Slots that don't exist 1735 slotOrd = "slot:"; 1736 if (slotPath.isAbsolute()) { 1737 slotOrd += "/"; 1738 } 1739 for (j = 0; j < slotPath.depth(); ++j) { 1740 if (j > 0) { 1741 slotOrd += "/"; 1742 } 1743 slotOrd += isVirtual ? baja.SlotPath.escape(slotPath.nameAt(j)) : slotPath.nameAt(j); 1744 1745 if (j >= depthRequestIndex) { 1746 space.getCallbacks().loadSlots(slotOrd, 0, new Callback(baja.ok, emptyFail, batch)); 1747 } 1748 } 1749 1750 var newOk = function () { 1751 // Now the network calls have all been committed, resolve this 1752 // Slot path without making any network calls 1753 scheme.resolve(target, query, cursor, options, false); 1754 }; 1755 1756 var newFail = function (err) { 1757 options.callback.fail(err); 1758 }; 1759 1760 // Finally perform a poll so all of the sync ops can be processed from all the subsequent load ops 1761 baja.comm.poll(new Callback(newOk, newFail, batch)); 1762 1763 batch.commit(); 1764 return; 1765 } 1766 else if (slot === null && slotPath.depth() > 0) { 1767 throw new Error("Unable to resolve ORD: " + slotPath); 1768 } 1769 1770 if (propertyPath === null) { 1771 newTarget.object = container; 1772 } 1773 else { 1774 // If there was a Property Path then use the first Property in the Property Path as the Slot 1775 slot = propertyPath[0]; 1776 newTarget.container = container; 1777 newTarget.object = value; 1778 newTarget.slot = slot; 1779 newTarget.propertyPath = propertyPath; 1780 } 1781 1782 // Resolve the next part of the ORD and feed this target into it 1783 cursor.resolveNext(newTarget, options); 1784 } 1785 1786 /** 1787 * Called when an ORD is resolved. 1788 * 1789 * @private 1790 * 1791 * @see baja.OrdScheme#resolve 1792 * 1793 * @param {ORDTarget} target the current ORD Target. 1794 * @param {Object} query the ORD Query used in resolving the ORD. 1795 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 1796 * @param {Object} options options used for resolving an ORD. 1797 * @param {Boolean} [netcall] optional boolean to specify whether a network call should be attempted (used internally). 1798 */ 1799 baja.SlotScheme.prototype.resolve = function (target, query, cursor, options, netcall) { 1800 if (options.full) { 1801 slotSchemeResolveFull(this, target, query, cursor, options, netcall); 1802 } 1803 else { 1804 slotSchemeResolve(this, target, query, cursor, options, netcall); 1805 } 1806 }; 1807 1808 /** 1809 * Return an ORD Query for the scheme. 1810 * 1811 * @returns {Object} 1812 */ 1813 baja.SlotScheme.prototype.parse = function (schemeName, body) { 1814 return new baja.SlotPath(body); 1815 }; 1816 1817 function parseSlotPathBackup(slotPath) { 1818 var body = slotPath.getBody(), 1819 len = body.length, 1820 c0, c1, c2, i; 1821 1822 for (i = 0; i < len; i += 3) { 1823 c0 = body.charAt(i); 1824 c1 = (i + 1 < len) ? body.charAt(i + 1) : -1; 1825 c2 = (i + 2 < len) ? body.charAt(i + 2) : "/"; 1826 1827 if (c0 !== ".") { 1828 return i; 1829 } 1830 1831 if (c1 !== "." || c2 !== "/") { 1832 // Since we know c0 is a period ('.'), we can check to see 1833 // if that is a valid path name. For SlotPath's, it 1834 // should always return false, so the SyntaxException 1835 // will be thrown. But for subclasses (such as VirtualPath), 1836 // this may be a legal path name, so we don't want to throw 1837 // the Syntax Exception. 1838 if (slotPath.isValidPathName(c0)) { 1839 return i; 1840 } 1841 1842 throw new Error("Expecting ../ backup"); 1843 } 1844 1845 slotPath.$backupDepth++; 1846 } 1847 1848 return len; 1849 } 1850 1851 function parseSlotPathNames(slotPath, start) { 1852 var body = slotPath.getBody(), 1853 len = body.length, 1854 n = 0, // Array index 1855 c, // Character 1856 nm, // Name 1857 i; 1858 1859 if (start >= len) { 1860 return; 1861 } 1862 1863 if (body.charAt(len - 1) === "/") { 1864 throw new Error("Invalid Slot Path - Trailing Slash"); 1865 } 1866 1867 for (i = start; i < len; ++i) { 1868 c = body.charAt(i); 1869 1870 if (c === "/") { 1871 if (i === start) { 1872 throw new Error("Invalid Slot Path - Double Slashes"); 1873 } 1874 nm = body.substring(start, i); 1875 1876 if (!slotPath.isValidPathName(nm)) { 1877 throw new Error("Invalid name in path"); 1878 } 1879 1880 slotPath.$names.push(nm); 1881 start = i + 1; 1882 } 1883 } 1884 1885 nm = body.substring(start, len); 1886 1887 if (!slotPath.isValidPathName(nm)) { 1888 throw new Error("Invalid name in path"); 1889 } 1890 1891 slotPath.$names.push(nm); 1892 } 1893 1894 function parseSlotPath(slotPath) { 1895 slotPath.$names = []; 1896 slotPath.$abs = false; 1897 1898 if (slotPath.getBody().length === 0) { 1899 return; 1900 } 1901 1902 var s = 0, // Start 1903 c = slotPath.getBody().charAt(0); 1904 1905 if (c === "/") { 1906 slotPath.$abs = true; 1907 s = 1; 1908 } 1909 else if (c === ".") { 1910 s = parseSlotPathBackup(slotPath); 1911 } 1912 1913 parseSlotPathNames(slotPath, s); 1914 } 1915 1916 /** 1917 * @class SlotPath. 1918 * <p> 1919 * SlotPath is used for resolving BValues using slot names. 1920 * 1921 * @name baja.SlotPath 1922 * @extends OrdQuery 1923 * 1924 * @param {String} body the body of the ORD scheme 1925 */ 1926 baja.SlotPath = function (body) { 1927 baja.SlotPath.$super.call(this, { 1928 scheme: baja.SlotScheme.DEFAULT, 1929 schemeName: "slot", 1930 body: strictArg(body, String) 1931 }); 1932 this.$abs = false; 1933 this.$names = []; 1934 this.$backupDepth = 0; 1935 parseSlotPath(this); 1936 }.$extend(OrdQuery); 1937 1938 /** 1939 * Make a Slot Path. 1940 * 1941 * @private 1942 * 1943 * @param {Object} body the body. 1944 * @returns {baja.SlotPath} the new Slot Path. 1945 */ 1946 baja.SlotPath.prototype.makeSlotPath = function (body) { 1947 return new baja.SlotPath(body); 1948 }; 1949 1950 /** 1951 * Return the SlotPath depth. 1952 * 1953 * @returns the SlotPath depth. 1954 */ 1955 baja.SlotPath.prototype.depth = function () { 1956 return this.$names.length; 1957 }; 1958 1959 /** 1960 * Return the SlotPath backup depth. 1961 * 1962 * @returns the SlotPath depth. 1963 */ 1964 baja.SlotPath.prototype.getBackupDepth = function () { 1965 return this.$backupDepth; 1966 }; 1967 1968 /** 1969 * Return the name at the given depth. 1970 * 1971 * @param {Number} depth the specified depth for the name. 1972 * 1973 * @returns {String} the name at the specified depth. 1974 */ 1975 baja.SlotPath.prototype.nameAt = function (depth) { 1976 baja.strictArg(depth, Number); 1977 return this.$names[depth]; 1978 }; 1979 1980 /** 1981 * Return true if the SlotPath is absolute. 1982 * 1983 * @returns {Boolean} true if the SlotPath is absolute. 1984 */ 1985 baja.SlotPath.prototype.isAbsolute = function () { 1986 return this.$abs; 1987 }; 1988 1989 /** 1990 * Return whether the specified path name is valid. 1991 * 1992 * @param {String} pathName the path name to validate. 1993 * 1994 * @returns {Boolean} true if the slot name is valid. 1995 */ 1996 baja.SlotPath.prototype.isValidPathName = function (pathName) { 1997 return baja.SlotPath.isValidName(pathName); 1998 }; 1999 2000 /** 2001 * Return whether the slot name is valid 2002 * 2003 * @param {String} nm the name to be validated. 2004 * 2005 * @returns {Boolean} true if the slot name is valid. 2006 */ 2007 baja.SlotPath.isValidName = function (nm) { 2008 return (/^([a-zA-Z$]([a-zA-Z0-9_]|(\$([0-9a-fA-F]{2}))|(\$u([0-9a-fA-F]{4})))*)$/).test(nm); 2009 }; 2010 2011 /** 2012 * Verify whether the slot name is valid. 2013 * 2014 * @param {String} nm the name to be validated. 2015 * 2016 * @throws error if the slot name isn't valid. 2017 */ 2018 baja.SlotPath.verifyValidName = function (nm) { 2019 if (!baja.SlotPath.isValidName(nm)) { 2020 throw new Error("Illegal name for Slot: " + nm); 2021 } 2022 }; 2023 2024 // Converts a character 2025 function convertSlotPathChar(c) { 2026 var code = c.charCodeAt(0), 2027 hex = code.toString(16), 2028 buf = "$"; 2029 2030 if (code < 0x10) { 2031 buf += "0" + hex; 2032 } 2033 else if (code < 0x100) { 2034 buf += hex; 2035 } 2036 else if (code < 0x1000) { 2037 buf += "u0" + hex; 2038 } 2039 else { 2040 buf += "u" + hex; 2041 } 2042 return buf; 2043 } 2044 2045 /** 2046 * Escape the string so it becomes a valid name for a slot. 2047 * 2048 * @see baja.SlotPath.unescape 2049 * 2050 * @param {String} str the string to be escaped. 2051 * 2052 * @returns {String} the escaped String. 2053 */ 2054 baja.SlotPath.escape = function (str) { 2055 if (str.length === 0) { 2056 return str; 2057 } 2058 2059 // Convert first character 2060 var res = str.charAt(0).replace(/[^a-zA-Z]/, function (c) { 2061 return convertSlotPathChar(c); 2062 }); 2063 2064 if (str.length > 1) { 2065 // Convert everything after first character 2066 res += str.substring(1, str.length).replace(/[^a-zA-Z0-9_]/g, function (c) { 2067 return convertSlotPathChar(c); 2068 }); 2069 } 2070 2071 return res; 2072 }; 2073 2074 /** 2075 * Unescape the string so all escaped characters become readable. 2076 * 2077 * @see baja.SlotPath.escape 2078 * 2079 * @param {String} str the string to be unescaped. 2080 * 2081 * @returns {String} the unescaped String. 2082 */ 2083 baja.SlotPath.unescape = function (str) { 2084 if (str.length === 0) { 2085 return str; 2086 } 2087 2088 // Convert from $xx 2089 str = str.replace(/\$[0-9a-fA-F]{2}/g, function (s) { 2090 return String.fromCharCode(parseInt(s.substring(1, s.length), 16)); 2091 }); 2092 2093 // Convert from $uxxxx 2094 str = str.replace(/\$u[0-9a-fA-F]{4}/g, function (s) { 2095 return String.fromCharCode(parseInt(s.substring(2, s.length), 16)); 2096 }); 2097 2098 return str; 2099 }; 2100 2101 /** 2102 * Merge this path with the specified path. 2103 * 2104 * @param {baja.SlotPath} a 2105 * @returns {String} the body of the SlotPath. 2106 */ 2107 baja.SlotPath.prototype.merge = function (a) { 2108 // if absolute then return a 2109 if (a.isAbsolute()) { 2110 return a.getBody(); 2111 } 2112 2113 // otherwise we have no backup or a backup 2114 // contained within my path 2115 var s = ""; 2116 if (this.isAbsolute()) { 2117 s += "/"; 2118 } 2119 2120 // if the backup is past me 2121 if (a.getBackupDepth() > 0 && a.getBackupDepth() > this.depth()) { 2122 // can't handle backup past absolute root 2123 if (this.isAbsolute()) { 2124 throw new Error("Invalid merge " + this + " + " + a); 2125 } 2126 2127 var backups = a.getBackupDepth() - this.depth() + this.getBackupDepth(), 2128 i; 2129 for (i = 0; i < backups; ++i) { 2130 s += "../"; 2131 } 2132 } 2133 2134 // add my path minus backup 2135 var needSlash = false, 2136 x; 2137 for (x = 0; x < this.depth() - a.getBackupDepth(); ++x) 2138 { 2139 if (needSlash) { 2140 s += "/"; 2141 } 2142 else { 2143 needSlash = true; 2144 } 2145 s += this.nameAt(x); 2146 } 2147 2148 // now add relative path 2149 var j; 2150 for (j = 0; j < a.depth(); ++j) { 2151 if (needSlash) { 2152 s += '/'; 2153 } 2154 else { 2155 needSlash = true; 2156 } 2157 s += a.nameAt(j); 2158 } 2159 2160 return s; 2161 }; 2162 2163 /** 2164 * Normalize the ORD Query list. 2165 * 2166 * @private 2167 * 2168 * @param {baja.OrdQueryList} list the ORD Query List. 2169 * @param {Number} index the ORD Query List index. 2170 * @param {Boolean} return true if the list was modified. 2171 */ 2172 baja.SlotPath.prototype.normalize = function (list, index) { 2173 var current = list.get(index), 2174 next = list.get(index + 1), 2175 modified = false; 2176 2177 // Merge two Slot paths together 2178 if (next && next.getSchemeName() === current.getSchemeName()) { 2179 2180 // Merge the slot paths together 2181 var currentSlotPath = this.makeSlotPath(current.getBody()), 2182 newSlotPath = this.makeSlotPath(currentSlotPath.merge(this.makeSlotPath(next.getBody()))); 2183 2184 // Update the OrdQueryList 2185 list.set(index, newSlotPath); 2186 2187 // Remove the next item from the list 2188 list.remove(index + 1); 2189 2190 modified = true; 2191 } 2192 2193 return modified; 2194 }; 2195 2196 //////////////////////////////////////////////////////////////// 2197 // Handle Scheme 2198 //////////////////////////////////////////////////////////////// 2199 2200 /** 2201 * @class Handle ORD Scheme. 2202 * <p> 2203 * This scheme resolves a SlotPath to a handle. Each Component in a ComponentSpace has a 2204 * unique handle. This is a great way to keep track of a Component regardless of whether 2205 * its SlotPath changes. 2206 * 2207 * @name baja.HandleScheme 2208 * @extends baja.OrdScheme 2209 * @private 2210 */ 2211 baja.HandleScheme = function () { 2212 baja.HandleScheme.$super.apply(this, arguments); 2213 }.$extend(baja.OrdScheme); 2214 2215 /** 2216 * Default Handle ORD Scheme instance 2217 * @private 2218 */ 2219 baja.HandleScheme.DEFAULT = new baja.HandleScheme(); 2220 2221 // Register Type 2222 baja.HandleScheme.registerType("baja:HandleScheme"); 2223 2224 /** 2225 * Called when an ORD is resolved. 2226 * 2227 * @private 2228 * 2229 * @see baja.OrdScheme#resolve 2230 * 2231 * @param {ORDTarget} target the current ORD Target. 2232 * @param {Object} query the ORD Query used in resolving the ORD. 2233 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 2234 * @param {Object} options options used for resolving an ORD. 2235 */ 2236 baja.HandleScheme.prototype.resolve = function (target, query, cursor, options) { 2237 2238 var object = target.object, 2239 handle = query.getBody(), 2240 space; 2241 2242 if (!baja.hasType(object)) { 2243 throw new Error("Not based via ComponentSpace. Invalid Object"); 2244 } 2245 else if (object.getType().isComponent()) { 2246 space = object.getComponentSpace(); 2247 } 2248 else if (object.getType().is("baja:ComponentSpace")) { 2249 space = object; 2250 } 2251 2252 // Pick up whether the Space is null 2253 if (!baja.hasType(space)) { 2254 throw new Error("Not based via ComponentSpace"); 2255 } 2256 2257 var ok = function (comp) { 2258 var newTarget = new OrdTarget(target); 2259 newTarget.object = comp; 2260 cursor.resolveNext(newTarget, options); 2261 }; 2262 2263 var fail = function (err) { 2264 options.callback.fail(err); 2265 }; 2266 2267 // Resolve the handle in the Space (may make network calls if necessary) 2268 space.resolveByHandle({ 2269 "handle": handle, 2270 "ok": ok, 2271 "fail": fail 2272 }); 2273 }; 2274 2275 //////////////////////////////////////////////////////////////// 2276 // Service Scheme 2277 //////////////////////////////////////////////////////////////// 2278 2279 /** 2280 * @class Service ORD Scheme. 2281 * <p> 2282 * This scheme is used to resolve a Server running in a Station. 2283 * 2284 * @name baja.ServiceScheme 2285 * @extends baja.OrdScheme 2286 * @private 2287 */ 2288 baja.ServiceScheme = function () { 2289 baja.ServiceScheme.$super.apply(this, arguments); 2290 }.$extend(baja.OrdScheme); 2291 2292 /** 2293 * Default Service Scheme instance. 2294 * @private 2295 */ 2296 baja.ServiceScheme.DEFAULT = new baja.ServiceScheme(); 2297 2298 // Register Type 2299 baja.ServiceScheme.registerType("baja:ServiceScheme"); 2300 2301 /** 2302 * Called when an ORD is resolved. 2303 * 2304 * @private 2305 * 2306 * @see baja.OrdScheme#resolve 2307 * 2308 * @param {ORDTarget} target the current ORD Target. 2309 * @param {Object} query the ORD Query used in resolving the ORD. 2310 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 2311 * @param {Object} options options used for resolving an ORD. 2312 */ 2313 baja.ServiceScheme.prototype.resolve = function (target, query, cursor, options) { 2314 2315 var object = target.object, 2316 typeSpec = query.getBody(), 2317 space; 2318 2319 if (!baja.hasType(object)) { 2320 throw new Error("Not based via ComponentSpace. Invalid Object"); 2321 } 2322 else if (object.getType().isComponent()) { 2323 space = object.getComponentSpace(); 2324 } 2325 else if (object.getType().is("baja:ComponentSpace")) { 2326 space = object; 2327 } 2328 else if (object.getType().is("baja:ISession") && object.station) { 2329 space = object.station; 2330 } 2331 2332 // Pick up whether the Space is null 2333 if (!baja.hasType(space)) { 2334 throw new Error("Not based via ComponentSpace"); 2335 } 2336 2337 var ok = function (comp) { 2338 var newTarget = new OrdTarget(target); 2339 newTarget.object = comp; 2340 cursor.resolveNext(newTarget, options); 2341 }; 2342 2343 var fail = function (err) { 2344 options.callback.fail(err); 2345 }; 2346 2347 if (space.hasCallbacks()) { 2348 space.getCallbacks().getService(typeSpec, new Callback(ok, fail)); 2349 } 2350 else { 2351 throw new Error("Unable to resolve Service: " + typeSpec); 2352 } 2353 }; 2354 2355 //////////////////////////////////////////////////////////////// 2356 // View Scheme 2357 //////////////////////////////////////////////////////////////// 2358 2359 /** 2360 * @class View ORD Scheme. 2361 * <p> 2362 * This scheme is used to process View query related information. 2363 * 2364 * @name baja.ViewScheme 2365 * @extends baja.OrdScheme 2366 * @private 2367 */ 2368 baja.ViewScheme = function () { 2369 baja.ViewScheme.$super.apply(this, arguments); 2370 }.$extend(baja.OrdScheme); 2371 2372 /** 2373 * Default View Scheme instance. 2374 * @private 2375 */ 2376 baja.ViewScheme.DEFAULT = new baja.ViewScheme(); 2377 2378 // Register Type 2379 baja.ViewScheme.registerType("baja:ViewScheme"); 2380 2381 /** 2382 * Called when an ORD is resolved. 2383 * 2384 * @private 2385 * 2386 * @see baja.OrdScheme#resolve 2387 * 2388 * @param {ORDTarget} target the current ORD Target. 2389 * @param {Object} query the ORD Query used in resolving the ORD. 2390 * @param cursor the ORD Query List cursor used for helping to asynchronously resolve the ORD. 2391 * @param {Object} options options used for resolving an ORD. 2392 */ 2393 baja.ViewScheme.prototype.resolve = function (target, query, cursor, options) { 2394 // Note down the view query information onto the ORD target so it can be accessed 2395 target.view = { 2396 id: query.getViewId(), 2397 params: query.getParameters() 2398 }; 2399 2400 cursor.resolveNext(target, options); 2401 }; 2402 2403 /** 2404 * Return an ORD Query for the scheme. 2405 * 2406 * @returns {OrdQuery} 2407 */ 2408 baja.ViewScheme.prototype.parse = function (schemeName, body) { 2409 return new baja.ViewQuery(body); 2410 }; 2411 2412 function parseViewQuery(body) { 2413 // Parse the view query (viewId?params) 2414 var res; 2415 if (body) { 2416 res = /^([^?]*)(?:[\?](.*))?$/.exec(body); 2417 } 2418 2419 if (!res || (res && res.length < 2)) { 2420 throw new Error("Invalid view query: " + body); 2421 } 2422 2423 // Note down the view query information onto the ORD target so it can be accessed 2424 var view = { 2425 id: res[1] || "", 2426 params: {} 2427 }; 2428 2429 // If there are some view parameters then parse them 2430 if (res[2]) { 2431 var regex = /([^=]+)\=([^;]+)\;?/g, 2432 params = regex.exec(res[2]); 2433 2434 while (params) { 2435 view.params[decodeURIComponent(params[1])] = decodeURIComponent(params[2]); 2436 params = regex.exec(res[2]); 2437 } 2438 } 2439 2440 return view; 2441 } 2442 2443 /** 2444 * @class ViewQuery 2445 * <p> 2446 * ViewQuery defines user agent information. 2447 * 2448 * @name baja.ViewQuery 2449 * @extends OrdQuery 2450 * 2451 * @param {String|Object} body the view query body or an Object Literal for 2452 * the view id and parameters. 2453 * @param {String} [body.id] view id. 2454 * @param {Object} [body.params] view parameters (key value pairs in an Object Literal). 2455 */ 2456 baja.ViewQuery = function (body) { 2457 // If an Object is passed in then attempt to get id and params from object 2458 if (body && typeof body === "object") { 2459 var params = body.params || {}; 2460 2461 this.$view = { 2462 id: body.id || "", 2463 params: params 2464 }; 2465 2466 // Build up a new view query body String 2467 var i = 0; 2468 body = this.$view.id; 2469 2470 baja.iterate(params, function (prop, propName) { 2471 if (i === 0) { 2472 body += "?"; 2473 } 2474 else if (i > 0) { 2475 body += ";"; 2476 } 2477 body += encodeURIComponent(propName) + "=" + encodeURIComponent(prop); 2478 ++i; 2479 }); 2480 } 2481 else { 2482 this.$view = parseViewQuery(body); 2483 } 2484 2485 baja.ViewQuery.$super.call(this, { 2486 scheme: baja.ViewScheme.DEFAULT, 2487 schemeName: "view", 2488 body: strictArg(body, String) 2489 }); 2490 }.$extend(OrdQuery); 2491 2492 /** 2493 * Normalize the query and return true if modified. 2494 * 2495 * @private 2496 * 2497 * @param {OrdQueryList} list 2498 * @param {Number} index 2499 * 2500 * @returns {Boolean} 2501 */ 2502 baja.ViewQuery.prototype.normalize = function (list, index) { 2503 var modified = false; 2504 if (list.get(index + 1) && 2505 list.get(index + 1).getSchemeName().equals("view")) { 2506 // If the next scheme is the same as this one then 2507 list.remove(index + 1); 2508 modified = true; 2509 } 2510 else if (index < list.size() - 1) { 2511 // Ensure view query is always the last in the list 2512 list.remove(index); 2513 modified = true; 2514 } 2515 return modified; 2516 }; 2517 2518 /** 2519 * Return the view id (this could be view type spec or the name of a Px view). 2520 * 2521 * @returns {String} 2522 */ 2523 baja.ViewQuery.prototype.getViewId = function () { 2524 return this.$view.id; 2525 }; 2526 2527 /** 2528 * Return the view parameters as an Object Literal. 2529 * <p> 2530 * Please note, this returns a defensive copy of the parameters. 2531 * 2532 * @returns {Object} the parameters. 2533 */ 2534 baja.ViewQuery.prototype.getParameters = function () { 2535 var o = {}; 2536 baja.iterate(this.$view.params, function (prop, propName) { 2537 o[propName] = prop; 2538 }); 2539 return o; 2540 }; 2541 2542 //////////////////////////////////////////////////////////////// 2543 // Batch Resolve 2544 //////////////////////////////////////////////////////////////// 2545 2546 // Enclose BatchResolve in its own anonymous function as it uses quite a lot of inner private functions 2547 (function batchRes() { 2548 2549 /** 2550 * @class BatchResolve is used to resolve a list of ORDs together. 2551 * <p> 2552 * This method should always be used if multiple ORDs need to be resolved at the same time. 2553 * 2554 * @name baja.BatchResolve 2555 * @extends BaseBajaObj 2556 * 2557 * @param {Array} ords an array of ORDs to resolve. The array can be Strings or baja.Ord. 2558 */ 2559 baja.BatchResolve = function (ords) { 2560 baja.BatchResolve.$super.apply(this, arguments); 2561 strictArg(ords, Array); 2562 // Ensure ORDs are normalized 2563 var items = [], i; 2564 for (i = 0; i < ords.length; ++i) { 2565 items.push({ 2566 ord: baja.Ord.make(ords[i].toString()).normalize(), 2567 target: null 2568 }); 2569 } 2570 this.$items = items; 2571 this.$groups = []; 2572 this.$ok = baja.ok; 2573 this.$fail = baja.fail; 2574 this.$resolved = false; 2575 }.$extend(BaseBajaObj); 2576 2577 /** 2578 * Return the number of items in the Batch. 2579 * 2580 * @returns {Number} 2581 */ 2582 baja.BatchResolve.prototype.size = function () { 2583 return this.$items.length; 2584 }; 2585 2586 /** 2587 * Return the ORD at the specified index or null if the index is invalid. 2588 * 2589 * @param {Number} index 2590 * @returns {baja.Ord} 2591 */ 2592 baja.BatchResolve.prototype.getOrd = function (index) { 2593 strictArg(index, Number); 2594 var t = this.$items[index]; 2595 return t ? t.ord : null; 2596 }; 2597 2598 /** 2599 * Return true if the ORD at the specified index has successfully resolved. 2600 * 2601 * @param {Number} index 2602 * @returns {Boolean} 2603 */ 2604 baja.BatchResolve.prototype.isResolved = function (index) { 2605 strictArg(index, Number); 2606 var t = this.$items[index]; 2607 return t && t.target ? true : false; 2608 }; 2609 2610 /** 2611 * Return the error for a particular ORD that failed to resolve or null for no error. 2612 * 2613 * @param {Number} index 2614 * @returns {Error} the error or null if no error. 2615 */ 2616 baja.BatchResolve.prototype.getFail = function (index) { 2617 strictArg(index, Number); 2618 var t = this.$items[index]; 2619 return t && t.fail ? t.fail : null; 2620 }; 2621 2622 /** 2623 * Return the ORD Target at the specified index. 2624 * <p> 2625 * If the ORD failed to resolve, an error will be thrown. 2626 * 2627 * @param {Number} index 2628 * @returns {OrdTarget} the ORD Target 2629 * @throws {Error} thrown if ORD failed to resolve 2630 */ 2631 baja.BatchResolve.prototype.getTarget = function (index) { 2632 strictArg(index, Number); 2633 var t = this.$items[index]; 2634 if (t && t.target) { 2635 return t.target; 2636 } 2637 else { 2638 throw t && t.fail ? t.fail : new Error("Unresolved ORD"); 2639 } 2640 }; 2641 2642 /** 2643 * Return an array of resolved ORD Targets. 2644 * <p> 2645 * If any of the ORDs failed to resolve, an error will be thrown. 2646 * 2647 * @returns {Array} an array of ORD Targets 2648 * @throws {Error} thrown if any ORDs failed to resolve 2649 */ 2650 baja.BatchResolve.prototype.getTargets = function () { 2651 var targets = [], i; 2652 for (i = 0; i < this.$items.length; ++i) { 2653 targets.push(this.getTarget(i)); 2654 } 2655 return targets; 2656 }; 2657 2658 /** 2659 * Return an array of resolved objects. 2660 * <p> 2661 * If any of the ORDs failed to resolve, an error will be thrown. 2662 * 2663 * @returns {Array} an array of objects 2664 * @throws {Error} thrown if any ORDs failed to resolve 2665 */ 2666 baja.BatchResolve.prototype.getTargetObjects = function () { 2667 var objects = [], i; 2668 for (i = 0; i < this.$items.length; ++i) { 2669 objects.push(this.get(i)); 2670 } 2671 return objects; 2672 }; 2673 2674 /** 2675 * Return the resolved object at the specified index. 2676 * <p> 2677 * If the ORD failed to resolve, an error will be thrown. 2678 * 2679 * @param {Number} index 2680 * @returns the resolved object 2681 * @throws {Error} thrown if the ORD failed to resolve 2682 */ 2683 baja.BatchResolve.prototype.get = function (index) { 2684 return this.getTarget(index).object; 2685 }; 2686 2687 /** 2688 * For each resolved target, call the specified function. 2689 * <p> 2690 * If any ORDs failed to resolve, an error will be thrown. 2691 * <p> 2692 * When the function is called, the 'this' will be the resolved Component's target. 2693 * The target's object will be passed as a parameter into the function. 2694 * 2695 * @param {Function} func 2696 * @throws {Error} thrown if any of the ORDs failed to resolve 2697 */ 2698 baja.BatchResolve.prototype.each = function (func) { 2699 strictArg(func, Function); 2700 var target, 2701 result, 2702 i, 2703 obj; 2704 2705 for (i = 0; i < this.$items.length; ++i) { 2706 target = this.getTarget(i); 2707 try { 2708 obj = target.getComponent(); 2709 if (!obj) { 2710 obj = target.object; 2711 } 2712 2713 result = func.call(obj, target.object, i); 2714 if (result) { 2715 return result; 2716 } 2717 } 2718 catch (err) { 2719 baja.error(err); 2720 } 2721 } 2722 }; 2723 2724 function endResolve(batchResolve, obj) { 2725 // Called at the very end once everything has resolved 2726 // If there are any unresolved ORDs then fail the callback 2727 var failedErr, i; 2728 for (i = 0; i < batchResolve.$items.length; ++i) { 2729 if (!batchResolve.$items[i].target) { 2730 failedErr = batchResolve.$items[i].fail || new Error("Unresolved ORD"); 2731 break; 2732 } 2733 } 2734 2735 if (failedErr) { 2736 batchResolve.$fail.call(batchResolve, failedErr); 2737 } 2738 else { 2739 batchResolve.$ok.call(batchResolve); 2740 } 2741 } 2742 2743 function subscribeTargets(batchResolve, index, batch, obj) { 2744 // If there's no lease and no subscriber then automatically commit 2745 if (!obj.subscriber && !obj.lease) { 2746 index = batchResolve.$groups.length; 2747 } 2748 2749 if (index >= batchResolve.$groups.length) { 2750 // If we've resolved everything we can do then end the resolution 2751 batch.addCallback(function () { 2752 endResolve(batchResolve, obj); 2753 }); 2754 2755 batch.commit(); 2756 return; 2757 } 2758 2759 var group = batchResolve.$groups[index], 2760 comps = [], 2761 c, i; 2762 2763 // Try to find some components we can subscribe too 2764 for (i = 0; i < group.items.length; ++i) { 2765 if (group.items[i].target) { 2766 c = group.items[i].target.getComponent(); 2767 2768 // Only try to subscribe to components that are mounted and support subscription 2769 if (c && c.isMounted() && c.getComponentSpace().hasCallbacks()) { 2770 comps.push(c); 2771 } 2772 } 2773 } 2774 2775 // If we can subscribe then batch up a subscription network call 2776 if (comps.length > 0) { 2777 if (obj.subscriber) { 2778 // Subscribe using Subscriber 2779 obj.subscriber.subscribe({ 2780 comps: comps, 2781 batch: batch 2782 }); 2783 } 2784 2785 // If leasing then lease the Components for a pre-defined period of time 2786 if (obj.lease) { 2787 baja.Component.lease({ 2788 comps: comps, 2789 time: obj.leaseTime, 2790 batch: batch 2791 }); 2792 } 2793 } 2794 2795 // Try batch the next group 2796 subscribeTargets(batchResolve, ++index, batch, obj); 2797 } 2798 2799 function resolveOrds(batchResolve, index, obj) { 2800 // If we've resolved everything then try to subscribing to the components (if at all possible) 2801 if (index >= batchResolve.$items.length) { 2802 subscribeTargets(batchResolve, 0, new baja.comm.Batch(), obj); 2803 return; 2804 } 2805 2806 // Resolve each ORD to its target (unless it's an unknown ORD whereby we've 2807 // already tried to resolve it earlier) 2808 var item = batchResolve.$items[index]; 2809 if (!item.unknown) { 2810 item.ord.resolve({ 2811 ok: function (target) { 2812 item.target = target; 2813 resolveOrds(batchResolve, ++index, obj); 2814 }, 2815 fail: function (err) { 2816 item.fail = err; 2817 resolveOrds(batchResolve, ++index, obj); 2818 }, 2819 base: obj.base 2820 }); 2821 } 2822 else { 2823 resolveOrds(batchResolve, ++index, obj); 2824 } 2825 } 2826 2827 function resolveSlotPaths(batchResolve, index, batch, obj) { 2828 2829 if (index >= batchResolve.$groups.length) { 2830 batch.addCallback(function () { 2831 // If we've resolved the SlotPaths from each group then we can finally attempt 2832 // to resolve each ORD. With a bit of luck, this will result in minimal network calls 2833 resolveOrds(batchResolve, 0, obj); 2834 }); 2835 2836 batch.commit(); 2837 return; 2838 } 2839 2840 var group = batchResolve.$groups[index], path, comp, isVirtual, nameAtDepth, arg, fullPath, slotOrd, slot, i, x, j, 2841 slotPathInfo = [], 2842 slotPathInfoMap = {}, 2843 depthRequestIndex = -1, 2844 subscribeOrds = obj.subscriber ? [] : null; 2845 2846 for (i = 0; i < group.items.length; ++i) { 2847 comp = group.space.getRootComponent(); 2848 path = group.items[i].slot; 2849 2850 // Skip if no valid SlotPath is available 2851 if (!path) { 2852 continue; 2853 } 2854 2855 // Record ORD for possible subscription 2856 if (subscribeOrds) { 2857 subscribeOrds.push(path.toString()); 2858 } 2859 2860 isVirtual = path instanceof baja.VirtualPath; 2861 2862 // Find out what exists and doesn't exist 2863 for (x = 0; x < path.depth(); ++x) { 2864 nameAtDepth = path.nameAt(x); 2865 2866 if (isVirtual) { 2867 nameAtDepth = baja.SlotPath.escape(nameAtDepth); 2868 } 2869 2870 slot = comp.getSlot(nameAtDepth); 2871 2872 // If there's no slot present then we need to try and make a network call for it. 2873 if (slot === null) { 2874 depthRequestIndex = x; 2875 break; 2876 } 2877 2878 // If the Slot isn't a Property then bail 2879 if (!slot.isProperty()) { 2880 break; 2881 } 2882 // If the Property isn't a Component then bail since we're only interested 2883 // in really loading up to a Component 2884 if (!slot.getType().isComponent()) { 2885 break; 2886 } 2887 comp = comp.get(slot); 2888 } 2889 2890 // If we've got Slots to request then do so 2891 if (depthRequestIndex > -1) { 2892 2893 // Load ops on Slots that don't exist 2894 slotOrd = "slot:/"; 2895 2896 for (j = 0; j < path.depth(); ++j) { 2897 // If we've gone past the depth we need to request then build up the network 2898 // calls we need to make 2899 if (j >= depthRequestIndex) { 2900 arg = { 2901 o: slotOrd, 2902 sn: isVirtual ? baja.SlotPath.escape(path.nameAt(j)) : path.nameAt(j) 2903 }; 2904 2905 fullPath = arg.o + "/" + arg.sn; 2906 2907 // Only request the Slot Path if it already isn't going to be requested 2908 if (!slotPathInfoMap.hasOwnProperty(fullPath)) { 2909 slotPathInfoMap[fullPath] = arg; 2910 slotPathInfo.push(arg); 2911 } 2912 } 2913 2914 if (j > 0) { 2915 slotOrd += "/"; 2916 } 2917 slotOrd += isVirtual ? baja.SlotPath.escape(path.nameAt(j)) : path.nameAt(j); 2918 } 2919 } 2920 } 2921 2922 // Make network request if there are slot paths to load for this Space 2923 if (slotPathInfo.length > 0) { 2924 group.space.getCallbacks().loadSlotPath(slotPathInfo, 2925 group.space, 2926 new Callback(baja.ok, baja.fail, batch), 2927 /*importAsync*/false); 2928 2929 // Attempt to roll the network subscription call into 2930 // the Slot Path resolution to avoid another network call... 2931 if (subscribeOrds) { 2932 // Subscribe using Subscriber for the given Space 2933 2934 // TODO: subscribing in this way is not ideal. Here we're subscribing Components before they're 2935 // fully loaded into the Proxy Component Space to avoid another network call. This assumes that 2936 // each of these Components will fully load into the Component Space without any problems. This will 2937 // do for now since it's critical to customer's perceptions that BajaScript loads values quickly. 2938 // If any errors do occur (i.e. the values haven't loaded properly), they are flagged up using baja.error. 2939 obj.subscriber.$ordSubscribe({ 2940 ords: subscribeOrds, 2941 space: group.space, 2942 batch: batch 2943 }); 2944 } 2945 } 2946 2947 // Resolve next group 2948 resolveSlotPaths(batchResolve, ++index, batch, obj); 2949 } 2950 2951 function resolveHandlesToSlotPaths(batchResolve, index, batch, obj) { 2952 // If there are any handle ORDs then resolve them to their SlotPaths 2953 if (index >= batchResolve.$items.length) { 2954 batch.addCallback(function () { 2955 // If we resolved all of the handles to SlotPaths then resolve the SlotPaths 2956 // Piggy back the unknown ORD resolve off the Slot Path resolve 2957 resolveSlotPaths(batchResolve, 0, batchResolve.$unknownBatch || new baja.comm.Batch(), obj); 2958 }); 2959 2960 // Commit the batch for getting all of the handles as SlotPaths 2961 batch.commit(); 2962 return; 2963 } 2964 2965 var item = batchResolve.$items[index]; 2966 2967 // If we're got a handle to resolve then attempt make a network call to resolve the 2968 if (item.h && item.space && item.space.hasCallbacks() && item.space.findByHandle(item.h) === null) { 2969 var cb = new Callback(function ok(slotPath) { 2970 item.slot = slotPath; 2971 }, 2972 function fail(err) { 2973 item.fail = err; 2974 }, 2975 batch); 2976 2977 // Batch up this network request 2978 item.space.getCallbacks().handleToPath(item.h, cb); 2979 } 2980 2981 // Resolve next handle 2982 resolveHandlesToSlotPaths(batchResolve, ++index, batch, obj); 2983 } 2984 2985 function groupComponentSpaceItems(batchResolve, obj) { 2986 // Group Items together by Component Space 2987 var added, 2988 item, 2989 group, 2990 i, 2991 x; 2992 2993 for (i = 0; i < batchResolve.$items.length; ++i) { 2994 item = batchResolve.$items[i]; 2995 added = false; 2996 2997 // Skip grouping for Spaces that don't have callbacks 2998 if (!item.space) { 2999 continue; 3000 } 3001 if (!item.space.hasCallbacks()) { 3002 continue; 3003 } 3004 3005 for (x = 0; x < batchResolve.$groups.length; ++x) { 3006 group = batchResolve.$groups[x]; 3007 3008 if (group.space.getNavOrd().toString().equals(item.spaceOrd.toString())) { 3009 group.items.push(item); 3010 added = true; 3011 break; 3012 } 3013 } 3014 3015 // If the item isn't added then create a new group for this item 3016 if (!added) { 3017 batchResolve.$groups.push({ 3018 space: item.space, 3019 items: [item] 3020 }); 3021 } 3022 } 3023 3024 // Resolve all of the Handles to SlotPaths 3025 resolveHandlesToSlotPaths(batchResolve, 0, new baja.comm.Batch(), obj); 3026 } 3027 3028 function resolveComponentSpaces(batchResolve, index, obj) { 3029 3030 // If we've resolved each space then group all of the items together by Component Space 3031 if (index >= batchResolve.$items.length) { 3032 groupComponentSpaceItems(batchResolve, obj); 3033 return; 3034 } 3035 3036 if (!batchResolve.$items[index].spaceOrd) { 3037 // Skip to next item if the Component Space ORD can't be found 3038 resolveComponentSpaces(batchResolve, ++index, obj); 3039 } 3040 else { 3041 // Recusively resolve each ORD to its Space 3042 batchResolve.$items[index].spaceOrd.get({ 3043 ok: function (value) { 3044 var s; 3045 if (value.getType().is("baja:ComponentSpace")) { 3046 s = batchResolve.$items[index].space = value; 3047 batchResolve.$items[index].spaceOrd = s.getNavOrd(); 3048 } 3049 else if (this.getType().is("baja:VirtualGateway")) { 3050 // Note: this may result in some network calls to mount the Virtual Component Space 3051 s = batchResolve.$items[index].space = this.getVirtualSpace(); 3052 batchResolve.$items[index].spaceOrd = s.getNavOrd(); 3053 } 3054 else if (this.getType().is("baja:Component")) { 3055 s = batchResolve.$items[index].space = this.getComponentSpace(); 3056 batchResolve.$items[index].spaceOrd = s.getNavOrd(); 3057 } 3058 3059 resolveComponentSpaces(batchResolve, ++index, obj); 3060 }, 3061 fail: function (err) { 3062 baja.error(err); 3063 3064 resolveComponentSpaces(batchResolve, ++index, obj); 3065 }, 3066 base: obj.base 3067 }); 3068 } 3069 } 3070 3071 function makeAbsSlotPath(query, obj) { 3072 // Create an absolute SlotPath using the base if necessary 3073 var isVirtual = query.getSchemeName() === "virtual", 3074 path = isVirtual ? new baja.VirtualPath(query.getBody()) : new baja.SlotPath(query.getBody()); 3075 3076 // If the path is already absolute then use it 3077 if (path.isAbsolute()) { 3078 return path; 3079 } 3080 3081 // Attempt to merge the ORD with the base to get our Absolute SlotPath 3082 if (obj.base.getType().isComponent() && !isVirtual) { 3083 var basePath = obj.base.getSlotPath(); 3084 if (basePath !== null) { 3085 var newBody = basePath.merge(path); 3086 return isVirtual ? new baja.VirtualPath(newBody) : new baja.SlotPath(newBody); 3087 } 3088 } 3089 3090 return null; 3091 } 3092 3093 function resolveUnknown(item, obj, batch) { 3094 // Make the network call to resolve the complete ORD Server side 3095 var cb = new Callback(function ok(target) { 3096 item.target = target; 3097 }, 3098 function fail(err) { 3099 item.fail = err; 3100 }, 3101 batch); 3102 3103 // Batch up the unknown ORD resolution... 3104 item.ord.resolve({ 3105 cb: cb, 3106 base: obj.base, 3107 fromBatchResolve: true 3108 }); 3109 } 3110 3111 function processOrds(batchResolve, obj) { 3112 // Find all of the Component Space ORDs 3113 var list, 3114 item, 3115 cursor, 3116 foundIndex, 3117 q, 3118 i; 3119 3120 for (i = 0; i < batchResolve.$items.length; ++i) { 3121 item = batchResolve.$items[i]; 3122 list = item.list = item.ord.parse(); 3123 3124 // If processed as unknown then skip since this ORD will be completely resolved Server Side 3125 if (list.hasUnknown()) { 3126 item.unknown = true; 3127 3128 // Create and cache the unknown batch object 3129 if (!batchResolve.$unknownBatch) { 3130 batchResolve.$unknownBatch = new baja.comm.Batch(); 3131 } 3132 3133 resolveUnknown(item, obj, batchResolve.$unknownBatch); 3134 continue; 3135 } 3136 3137 cursor = list.getCursor(); 3138 foundIndex = -1; 3139 3140 // Work out the ORD just before the virtual, slot or handle scheme 3141 while (cursor.next()) { 3142 q = cursor.get(); 3143 3144 if (q.getSchemeName() === "virtual") { 3145 foundIndex = cursor.getIndex(); 3146 item.slot = makeAbsSlotPath(q, obj); 3147 break; 3148 } 3149 else if (q.getSchemeName() === "h" && foundIndex === -1) { 3150 foundIndex = cursor.getIndex(); 3151 item.h = q.getBody(); 3152 } 3153 else if (q.getSchemeName() === "slot" && foundIndex === -1) { 3154 foundIndex = cursor.getIndex(); 3155 item.slot = makeAbsSlotPath(q, obj); 3156 } 3157 } 3158 3159 // Note down the ORD to the Space 3160 if (foundIndex !== -1) { 3161 item.spaceOrd = baja.Ord.make(list.toString(foundIndex)); 3162 3163 // If there's no ORD then just try using the base to resolve the CS. 3164 if (item.spaceOrd === baja.Ord.DEFAULT) { 3165 item.spaceOrd = obj.base.getNavOrd(); 3166 } 3167 } 3168 } 3169 3170 // Resolve any Space ORDs found 3171 resolveComponentSpaces(batchResolve, 0, obj); 3172 } 3173 3174 /** 3175 * Batch resolve an array of ORDs. 3176 * <p> 3177 * A Batch Resolve should be used whenever more than one ORD needs to resolved. 3178 * <p> 3179 * Any network calls that result from processing an ORD are always asynchronous. 3180 * <p> 3181 * This method can only be called once per BatchResolve instance. 3182 * <p> 3183 * An Object Literal is used to supply the method's arguments. For example... 3184 * <pre> 3185 * var r = baja.BatchResolve(["station:|slot:/Ramp", "station:|slot:/SineWave"]); 3186 * var sub = new baja.Subscriber(); // Also batch subscribe all resolved Components 3187 * 3188 * r.resolve({ 3189 * ok: function () { 3190 * // Get resolved objects 3191 * var objs = this.getTargetObjects(); 3192 * }, 3193 * fail: function (err) { 3194 * // Called if any of the ORDs fail to resolve 3195 * }, 3196 * subscriber: sub 3197 * }); 3198 * 3199 * // Or use the each method (will only be called if all ORDs resolve). Each will 3200 * // be called for each target. 3201 * r.resolve({ 3202 * each: function () { 3203 * baja.outln("Resolved: " + this.toPathString()); 3204 * }, 3205 * fail: function (err) { 3206 * // Called if any of the ORDs fail to resolve 3207 * }, 3208 * subscriber: sub 3209 * }); 3210 * </pre> 3211 * 3212 * @see baja.Ord 3213 * 3214 * @param {Object} [obj] the Object Literal that contains the method's arguments. 3215 * @param {Function} [obj.ok] the ok function called once all of the ORDs have been successfully resolved. 3216 * When the function is called, 'this' is set to the BatchResolve object. 3217 * @param {Function} [obj.fail] the fail function called if any of the ORDs fail to resolve. 3218 * The first error found is pass as an argument to this function. 3219 * @param [obj.base] the base Object to resolve the ORDs against. 3220 * @param {baja.Subscriber} [obj.subscriber] if defined, any mounted Components are subscribed using this Subscriber. 3221 * @param {Boolean} [obj.lease] if defined, any resolved and mounted components are leased. 3222 * @param {Number|baja.RelTime} [obj.leaseTime] the lease time used for leasing Components. 3223 */ 3224 baja.BatchResolve.prototype.resolve = function (obj) { 3225 obj = objectify(obj); 3226 3227 var ok = obj.ok || baja.ok; 3228 3229 // If an each function was passed in then call if everything resolves ok. 3230 this.$ok = function () { 3231 var result; 3232 if (typeof obj.each === "function") { 3233 try { 3234 result = this.each(obj.each); 3235 } 3236 catch (err) { 3237 baja.error(); 3238 } 3239 } 3240 ok.call(this, result); 3241 }; 3242 3243 this.$fail = obj.fail || baja.fail; 3244 3245 // Can only resolve once 3246 if (this.$resolved) { 3247 this.$fail.call(this, "Cannot call resolve more than once"); 3248 return; 3249 } 3250 this.$resolved = true; 3251 3252 // Initialize 3253 obj.base = bajaDef(obj.base, baja.nav.localhost); 3254 3255 // Check the user isn't trying to batch an ORD as this isn't supported 3256 if (obj.batch) { 3257 this.$fail.call(this, "Cannot batch ORD resolution"); 3258 return; 3259 } 3260 3261 // Start resolution 3262 if (this.$items.length > 0) { 3263 processOrds(this, obj); 3264 } 3265 else { 3266 this.$ok.call(this); 3267 } 3268 }; 3269 3270 }()); // batchRes 3271 3272 }(baja, BaseBajaObj));