1 // 2 // Copyright 2010, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * Core Component 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, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false, indent: 2, vars: true, continue: true */ 14 15 // Globals for JsLint to ignore 16 /*global baja, syncVal, BaseBajaObj, syncProps*/ 17 18 (function comp(baja, BaseBajaObj) { 19 20 // Use ECMAScript 5 Strict Mode 21 "use strict"; 22 23 var emptyArray = [], // An empty Array that should be treated as being immutable 24 strictArg = baja.strictArg, // Create local for improved minification 25 strictAllArgs = baja.strictAllArgs, 26 bajaDef = baja.def, 27 objectify = baja.objectify, 28 Callback = baja.comm.Callback; 29 30 //////////////////////////////////////////////////////////////// 31 // Flags 32 //////////////////////////////////////////////////////////////// 33 34 /** 35 * @class Slot Flags. 36 * <p> 37 * Flags are boolean values which are stored as 38 * a bitmask on each slot in a Complex. Some 39 * flags apply to all slot types, while some only 40 * have meaning for certain slot types. 41 * <p> 42 * Flags should always be a Number. This Constructor should only be 43 * used to create new objects by Tridium developers. 44 * 45 * @name baja.Flags 46 * @extends BaseBajaObj 47 */ 48 baja.Flags = function (mask, symbol, fname) { 49 this.$mask = mask; 50 this.$symbol = symbol; 51 this.$name = fname; 52 53 // Cache this Flag via the symbol name in an Object for quick decoding 54 baja.Flags.bySymbol[symbol] = this; 55 }.$extend(BaseBajaObj); 56 57 baja.Flags.bySymbol = {}; // Used to quickly decode Flag Strings 58 59 /** 60 * Return the mask for the Flags. 61 * 62 * @returns {Number} 63 */ 64 baja.Flags.prototype.getMask = function () { 65 return this.$mask; 66 }; 67 68 /** 69 * Return the symbol for the Flags. 70 * 71 * @returns {String} 72 */ 73 baja.Flags.prototype.getSymbol = function () { 74 return this.$symbol; 75 }; 76 77 /** 78 * Return the String representation of the Flags. 79 * 80 * @returns {String} 81 */ 82 baja.Flags.prototype.toString = function () { 83 return this.$name; 84 }; 85 86 /** 87 * Equality test. 88 * 89 * @param obj 90 * @returns {Boolean} 91 */ 92 baja.Flags.prototype.equals = function (obj) { 93 return obj instanceof baja.Flags && obj.getMask() === this.getMask(); 94 }; 95 96 /** 97 * readonly flag (Number). 98 */ 99 baja.Flags.READONLY = 0x00000001; // 'r' 100 101 /** 102 * transient flag (Number). 103 */ 104 baja.Flags.TRANSIENT = 0x00000002; // 't' 105 106 /** 107 * hidden flag (Number). 108 */ 109 baja.Flags.HIDDEN = 0x00000004; // 'h' 110 111 /** 112 * summary flag (Number). 113 */ 114 baja.Flags.SUMMARY = 0x00000008; // 's' 115 116 /** 117 * async flag (Number). 118 */ 119 baja.Flags.ASYNC = 0x00000010; // 'a' 120 121 /** 122 * noRun flag (Number). 123 */ 124 baja.Flags.NO_RUN = 0x00000020; // 'n' 125 126 /** 127 * defaultOnClone flag (Number). 128 */ 129 baja.Flags.DEFAULT_ON_CLONE = 0x00000040; // 'd' 130 131 /** 132 * confirmRequired flag (Number). 133 */ 134 baja.Flags.CONFIRM_REQUIRED = 0x00000080; // 'c' 135 136 /** 137 * operator flag (Number). 138 */ 139 baja.Flags.OPERATOR = 0x00000100; // 'o' 140 141 /** 142 * executeOnChange flag (Number). 143 */ 144 baja.Flags.EXECUTE_ON_CHANGE = 0x00000200; // 'x' 145 146 /** 147 * fanIn flag (Number). 148 */ 149 baja.Flags.FAN_IN = 0x00000400; // 'f' 150 151 /** 152 * noAudit flag (Number). 153 */ 154 baja.Flags.NO_AUDIT = 0x00000800; // 'A' 155 156 /** 157 * composite flag (Number). 158 */ 159 baja.Flags.COMPOSITE = 0x00001000; // 'p' 160 161 /** 162 * removeOnClone flag (Number). 163 */ 164 baja.Flags.REMOVE_ON_CLONE = 0x00002000; // 'R' 165 166 /** 167 * metaData flag (Number). 168 */ 169 baja.Flags.METADATA = 0x00004000; // 'm' 170 171 /** 172 * linkTarget flag (Number). 173 */ 174 baja.Flags.LINK_TARGET = 0x00008000; // 'L' 175 176 /** 177 * nonCritical flag (Number). 178 */ 179 baja.Flags.NON_CRITICAL = 0x00010000; // 'N' 180 181 /** 182 * userDefined1 flag (Number). 183 */ 184 baja.Flags.USER_DEFINED_1 = 0x10000000; // '1' 185 186 /** 187 * userDefined2 flag (Number). 188 */ 189 baja.Flags.USER_DEFINED_2 = 0x20000000; // '2' 190 191 /** 192 * userDefined3 flag (Number). 193 */ 194 baja.Flags.USER_DEFINED_3 = 0x40000000; // '3' 195 196 /** 197 * userDefined4 flag (Number). 198 */ 199 baja.Flags.USER_DEFINED_4 = 0x80000000; // '4' 200 201 baja.Flags.flags = [new baja.Flags(baja.Flags.READONLY, "r", "readonly"), 202 new baja.Flags(baja.Flags.TRANSIENT, "t", "transient"), 203 new baja.Flags(baja.Flags.HIDDEN, "h", "hidden"), 204 new baja.Flags(baja.Flags.SUMMARY, "s", "summary"), 205 new baja.Flags(baja.Flags.ASYNC, "a", "async"), 206 new baja.Flags(baja.Flags.NO_RUN, "n", "noRun"), 207 new baja.Flags(baja.Flags.DEFAULT_ON_CLONE, "d", "defaultOnClone"), 208 new baja.Flags(baja.Flags.CONFIRM_REQUIRED, "c", "confirmRequired"), 209 new baja.Flags(baja.Flags.OPERATOR, "o", "operator"), 210 new baja.Flags(baja.Flags.EXECUTE_ON_CHANGE, "x", "executeOnChange"), 211 new baja.Flags(baja.Flags.FAN_IN, "f", "fanIn"), 212 new baja.Flags(baja.Flags.NO_AUDIT, "A", "noAudit"), 213 new baja.Flags(baja.Flags.COMPOSITE, "p", "composite"), 214 new baja.Flags(baja.Flags.REMOVE_ON_CLONE, "R", "removeOnClone"), 215 new baja.Flags(baja.Flags.METADATA, "m", "metadata"), 216 new baja.Flags(baja.Flags.NON_CRITICAL, "N", "nonCritical"), 217 new baja.Flags(baja.Flags.LINK_TARGET, "L", "linkTarget"), 218 new baja.Flags(baja.Flags.USER_DEFINED_1, "1", "userDefined1"), 219 new baja.Flags(baja.Flags.USER_DEFINED_2, "2", "userDefined2"), 220 new baja.Flags(baja.Flags.USER_DEFINED_3, "3", "userDefined3"), 221 new baja.Flags(baja.Flags.USER_DEFINED_4, "4", "userDefined4") 222 ]; 223 224 /** 225 * Encode Slot Flags to a String. 226 * 227 * @param {Number} flags the flags to be encoded. 228 * 229 * @returns {String} 230 */ 231 baja.Flags.encodeToString = function (flags) { 232 if (flags === 0) { 233 return ""; 234 } 235 236 strictArg(flags, Number); 237 var s = "", i; 238 for (i = 0; i < baja.Flags.flags.length; ++i) { 239 if ((baja.Flags.flags[i].getMask() & flags) !== 0) { 240 s += baja.Flags.flags[i].getSymbol(); 241 } 242 } 243 return s; 244 }; 245 246 /** 247 * Decode Slot Flags from a String. 248 * 249 * @param {String} flagsStr the Flags encoded as a String. 250 * @returns {Number} 251 */ 252 baja.Flags.decodeFromString = function (flagsStr) { 253 if (flagsStr === "0") { 254 return 0; 255 } 256 257 strictArg(flagsStr, String); 258 259 var decodedFlags = 0, 260 symbols = flagsStr.split(''), 261 flags, 262 i; 263 264 for (i = 0; i < symbols.length; ++i) { 265 // Find the flags via a Symbol look up 266 flags = baja.Flags.bySymbol[symbols[i]]; 267 268 if (flags) { 269 // Add the mask for the flag to the result 270 decodedFlags |= flags.getMask(); 271 } 272 } 273 return decodedFlags; 274 }; 275 276 //////////////////////////////////////////////////////////////// 277 // Slots 278 //////////////////////////////////////////////////////////////// 279 280 /** 281 * @class Slot 282 * <p> 283 * A Niagara Complex is made up of Slots. A Slot can be a Property, Action or a Topic. 284 * This is the base class for all Slots in BajaScript. 285 * <p> 286 * A new object should never be directly created with this Constructor. 287 * 288 * @name baja.Slot 289 * @extends BaseBajaObj 290 * 291 * @param {String} slotName the name of the Slot. 292 * @param {String} displayName the display name of the Slot. 293 */ 294 baja.Slot = function (slotName, displayName) { 295 this.$slotName = slotName || ""; 296 this.$displayName = displayName || ""; 297 }.$extend(BaseBajaObj); 298 299 /** 300 * Return the name of the Slot. 301 * 302 * @returns {String} 303 */ 304 baja.Slot.prototype.getName = function () { 305 return this.$slotName; 306 }; 307 308 /** 309 * Return a String representation of the Slot. 310 * 311 * @returns {String} 312 */ 313 baja.Slot.prototype.toString = function () { 314 return this.getName(); 315 }; 316 317 /** 318 * Return the display name of the Slot. 319 * <p> 320 * Please note, this method is intended for INTERNAL use by Tridium only. An 321 * external developer should never call this method. 322 * 323 * @private 324 * @returns {String} 325 */ 326 baja.Slot.prototype.$getDisplayName = function () { 327 return this.$displayName; 328 }; 329 330 /** 331 * Set the display name of the Slot. 332 * <p> 333 * Please note, this method is intended for INTERNAL use by Tridium only. An 334 * external developer should never call this method. 335 * 336 * @private 337 * @param {String} displayName 338 */ 339 baja.Slot.prototype.$setDisplayName = function (displayName) { 340 this.$displayName = displayName; 341 }; 342 343 /** 344 * Is the Slot frozen? 345 * 346 * @returns {Boolean} 347 */ 348 baja.Slot.prototype.isFrozen = function () { 349 return false; 350 }; 351 352 /** 353 * Is the Slot a Property? 354 * 355 * @returns {Boolean} 356 */ 357 baja.Slot.prototype.isProperty = function () { 358 return false; 359 }; 360 361 /** 362 * Is the Slot a Topic? 363 * 364 * @returns {Boolean} 365 */ 366 baja.Slot.prototype.isTopic = function () { 367 return false; 368 }; 369 370 /** 371 * Is the Slot an Action? 372 * 373 * @returns {Boolean} 374 */ 375 baja.Slot.prototype.isAction = function () { 376 return false; 377 }; 378 379 /** 380 * @class Property Slot. 381 * <p> 382 * Property defines a Slot which is a storage location 383 * for a variable in a Complex. 384 * <p> 385 * A new object should never be directly created with this Constructor. All Slots are 386 * created internally by BajaScript. 387 * 388 * @name baja.Property 389 * @extends baja.Slot 390 */ 391 baja.Property = function (slotName, displayName) { 392 baja.Property.$super.apply(this, arguments); 393 }.$extend(baja.Slot); 394 395 /** 396 * Is this Slot a Property? 397 * 398 * @returns {Boolean} 399 */ 400 baja.Property.prototype.isProperty = function () { 401 return true; 402 }; 403 404 /** 405 * @class Dynamic Property Slot. 406 * <p> 407 * Property defines a Slot which is a storage location 408 * for a variable in a Complex. 409 * <p> 410 * A new object should never be directly created with this Constructor. All Slots are 411 * created internally by BajaScript. 412 * 413 * @name baja.DynamicProperty 414 * @extends baja.Property 415 * @inner 416 */ 417 var DynamicProperty = function (slotName, displayName, display, flags, facets, value) { 418 DynamicProperty.$super.apply(this, [slotName, displayName]); 419 this.$display = display || ""; 420 this.$flags = flags || 0; 421 this.$facets = facets || baja.Facets.DEFAULT; 422 this.$val = bajaDef(value, null); 423 }.$extend(baja.Property); 424 425 /** 426 * Return the Property value. 427 * <p> 428 * Please note, this method is intended for INTERNAL use by Tridium only. An 429 * external developer should never call this method. Access a Property's value 430 * through the associated baja.Complex instead. 431 * 432 * @see baja.Complex#get 433 * 434 * @name baja.DynamicProperty#$getValue 435 * @function 436 * @private 437 * 438 * @returns value 439 */ 440 DynamicProperty.prototype.$getValue = function () { 441 return this.$val; 442 }; 443 444 /** 445 * Set the Property value. 446 * <p> 447 * Please note, this method is intended for INTERNAL use by Tridium only. An 448 * external developer should never call this method. 449 * 450 * @name baja.DynamicProperty#$setValue 451 * @function 452 * @private 453 * 454 * @param val value to be set. 455 */ 456 DynamicProperty.prototype.$setValue = function (val) { 457 this.$val = val; 458 }; 459 460 /** 461 * Return the Flags for the Property. 462 * 463 * @name baja.DynamicProperty#getFlags 464 * @function 465 * @see baja.Flags 466 * 467 * @returns {Number} 468 */ 469 DynamicProperty.prototype.getFlags = function () { 470 return this.$flags; 471 }; 472 473 /** 474 * Set the Flags for the Property. 475 * <p> 476 * Please note, this method is intended for INTERNAL use by Tridium only. An 477 * external developer should never call this method. 478 * 479 * @name baja.DynamicProperty#$setFlags 480 * @function 481 * @private 482 * @see baja.Flags 483 * 484 * @param {Number} flags 485 */ 486 DynamicProperty.prototype.$setFlags = function (flags) { 487 this.$flags = flags; 488 }; 489 490 /** 491 * Return the Facets for the Property. 492 * 493 * @name baja.DynamicProperty#getFacets 494 * @function 495 * @see baja.Facets 496 * 497 * @returns the Slot Facets 498 */ 499 DynamicProperty.prototype.getFacets = function () { 500 return this.$facets; 501 }; 502 503 /** 504 * Set the Facets for the Property. 505 * <p> 506 * Please note, this method is intended for INTERNAL use by Tridium only. An 507 * external developer should never call this method. 508 * 509 * @name baja.DynamicProperty#$setFacets 510 * @function 511 * @private 512 * @see baja.Facets 513 * 514 * @param {baja.Facets} facets 515 */ 516 DynamicProperty.prototype.$setFacets = function (facets) { 517 this.$facets = facets; 518 }; 519 520 /** 521 * Return the default flags for the Property. 522 * 523 * @name baja.DynamicProperty#getDefaultFlags 524 * @function 525 * 526 * @returns {Number} 527 */ 528 DynamicProperty.prototype.getDefaultFlags = function () { 529 return this.getFlags(); 530 }; 531 532 /** 533 * Return the default value for the Property. 534 * 535 * @name baja.DynamicProperty#getDefaultValue 536 * @function 537 * 538 * @returns the default value for the Property. 539 */ 540 DynamicProperty.prototype.getDefaultValue = function () { 541 return this.$val; 542 }; 543 544 /** 545 * Return the Type for this Property. 546 * 547 * @name baja.DynamicProperty#getType 548 * @function 549 * 550 * @returns the Type for the Property. 551 */ 552 DynamicProperty.prototype.getType = function () { 553 return this.$val.getType(); 554 }; 555 556 /** 557 * Return the display String for this Property. 558 * <p> 559 * Please note, this method is intended for INTERNAL use by Tridium only. An 560 * external developer should never call this method. 561 * 562 * @name baja.DynamicProperty#$getDisplay 563 * @function 564 * @private 565 * 566 * @returns {String} 567 */ 568 DynamicProperty.prototype.$getDisplay = function () { 569 return this.$display; 570 }; 571 572 /** 573 * Set the display for this Property. 574 * <p> 575 * Please note, this method is intended for INTERNAL use by Tridium only. An 576 * external developer should never call this method. 577 * 578 * @name baja.DynamicProperty#$setDisplay 579 * @function 580 * @private 581 * 582 * @param {String} display the display String. 583 */ 584 DynamicProperty.prototype.$setDisplay = function (display) { 585 this.$display = display; 586 }; 587 588 /** 589 * @class Action Slot. 590 * <p> 591 * Action is a Slot that defines a behavior which can 592 * be invoked on a Component. 593 * <p> 594 * A new object should never be directly created with this Constructor. All Slots are 595 * created internally by BajaScript. 596 * 597 * @name baja.Action 598 * @extends baja.Slot 599 */ 600 baja.Action = function (slotName, displayName) { 601 baja.Action.$super.call(this, slotName, displayName); 602 }.$extend(baja.Slot); 603 604 /** 605 * Is the Slot an Action? 606 * 607 * @returns {Boolean} 608 */ 609 baja.Action.prototype.isAction = function () { 610 return true; 611 }; 612 613 /** 614 * @class Topic Slot. 615 * <p> 616 * Topic defines a Slot which indicates an event that 617 * is fired on a Component. 618 * <p> 619 * A new object should never be directly created with this Constructor. All Slots are 620 * created internally by BajaScript. 621 * 622 * @name baja.Topic 623 * @extends baja.Slot 624 */ 625 baja.Topic = function (slotName, displayName) { 626 baja.Topic.$super.call(this, slotName, displayName); 627 }.$extend(baja.Slot); 628 629 /** 630 * Is the Slot a Topic? 631 * 632 * @returns {Boolean} 633 */ 634 baja.Topic.prototype.isTopic = function () { 635 return true; 636 }; 637 638 /** 639 * @class PropertyAction Slot. 640 * <p> 641 * A Property that is also an Action. Typically this is used for dynamic Actions. 642 * <p> 643 * A new object should never be directly created with this Constructor. All Slots are 644 * created internally by BajaScript. 645 * 646 * @name baja.PropertyAction 647 * @extends baja.DynamicProperty 648 */ 649 var PropertyAction = function (slotName, displayName, display, flags, facets, value) { 650 PropertyAction.$super.apply(this, arguments); 651 }.$extend(DynamicProperty); 652 653 /** 654 * Is the Slot an Action? 655 * 656 * @name baja.PropertyAction#isAction 657 * @function 658 * 659 * @returns {Boolean} 660 */ 661 PropertyAction.prototype.isAction = function () { 662 return true; 663 }; 664 665 /** 666 * Return the Action's parameter Type. 667 * 668 * @name baja.PropertyAction#getParamType 669 * @function 670 * 671 * @returns {Type} the parameter type (or null if the Action doesn't have a parameter). 672 */ 673 PropertyAction.prototype.getParamType = function () { 674 return this.$val.getParamType(); 675 }; 676 677 /** 678 * Return the Action's parameter default value. 679 * 680 * @name baja.PropertyAction#getParamDefault 681 * @function 682 * 683 * @returns the parameter default value (or null if the Action doesn't have a parameter). 684 */ 685 PropertyAction.prototype.getParamDefault = function () { 686 return this.$val.getParamDefault(); 687 }; 688 689 /** 690 * Return the Action's return Type. 691 * 692 * @name baja.PropertyAction#getReturnType 693 * @function 694 * 695 * @returns {Type} the return type (or null if the Action doesn't have a return Type). 696 */ 697 PropertyAction.prototype.getReturnType = function () { 698 return this.$val.getReturnType(); 699 }; 700 701 /** 702 * @class PropertyTopic Slot. 703 * <p> 704 * A Property that is also a Topic. Typically this is used for dynamic Topics. 705 * <p> 706 * A new object should never be directly created with this Constructor. All Slots are 707 * created internally by BajaScript. 708 * 709 * @name baja.PropertyTopic 710 * @extends baja.DynamicProperty 711 */ 712 var PropertyTopic = function (slotName, displayName, display, flags, facets, value) { 713 PropertyTopic.$super.apply(this, arguments); 714 }.$extend(DynamicProperty); 715 716 /** 717 * Is the Property a Topic? 718 * 719 * @name baja.PropertyTopic#isTopic 720 * @function 721 * 722 * @returns {Boolean} 723 */ 724 PropertyTopic.prototype.isTopic = function () { 725 return true; 726 }; 727 728 /** 729 * Return the Topic's event Type. 730 * 731 * @name baja.PropertyTopic#getEventType 732 * @function 733 * 734 * @returns {Type} the event Type (or null if the Topic has not event Type). 735 */ 736 PropertyTopic.prototype.getEventType = function () { 737 return this.$val.getEventType(); 738 }; 739 740 //////////////////////////////////////////////////////////////// 741 // Slot Cursor 742 //////////////////////////////////////////////////////////////// 743 744 /** 745 * @class A Cursor used for Slot iteration. 746 * 747 * @name baja.SlotCursor 748 * @extends baja.FilterCursor 749 */ 750 var SlotCursor = function () { 751 SlotCursor.$super.apply(this, arguments); 752 }.$extend(baja.FilterCursor); 753 754 /** 755 * If the Slot is a Property, return its value (otherwise return null). 756 * 757 * @name baja.SlotCursor#getValue 758 * @function 759 * 760 * @returns a Property value. 761 */ 762 SlotCursor.prototype.getValue = function () { 763 var slot = this.get(); 764 return slot !== null && slot.isProperty() ? this.$context.get(slot) : null; 765 }; 766 767 /** 768 * If the Slot is a Property, return its display String (otherwise return null). 769 * 770 * @name baja.SlotCursor#getDisplay 771 * @function 772 * 773 * @returns {String} display String. 774 */ 775 SlotCursor.prototype.getDisplay = function () { 776 var slot = this.get(); 777 return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null; 778 }; 779 780 /** 781 * Return the first Property value in the cursor (regardless of iterative state). 782 * 783 * @name baja.SlotCursor#firstValue 784 * @function 785 * 786 * @returns first Property value found in the Cursor (or null if nothing found). 787 */ 788 SlotCursor.prototype.firstValue = function () { 789 var slot = this.first(); 790 return slot !== null && slot.isProperty() ? this.$context.get(slot) : null; 791 }; 792 793 /** 794 * Return the first Property display String in the cursor (regardless of iterative state). 795 * 796 * @name baja.SlotCursor#firstDisplay 797 * @function 798 * 799 * @returns first Property display String found in the Cursor (or null if nothing found). 800 */ 801 SlotCursor.prototype.firstDisplay = function () { 802 var slot = this.first(); 803 return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null; 804 }; 805 806 /** 807 * Return the last Property value in the cursor (regardless of iterative state). 808 * 809 * @name baja.SlotCursor#lastValue 810 * @function 811 * 812 * @returns first Property value found in the Cursor (or null if nothing found). 813 */ 814 SlotCursor.prototype.lastValue = function () { 815 var slot = this.last(); 816 return slot !== null && slot.isProperty() ? this.$context.get(slot) : null; 817 }; 818 819 /** 820 * Return the last Property display String in the cursor (regardless of iterative state). 821 * 822 * @name baja.SlotCursor#lastDisplay 823 * @function 824 * 825 * @returns first Property display String found in the Cursor (or null if nothing found). 826 */ 827 SlotCursor.prototype.lastDisplay = function () { 828 var slot = this.last(); 829 return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null; 830 }; 831 832 /** 833 * Iterate through the Cursor and call 'each' on every Property Slot and get its value. 834 * <p> 835 * When the function is called, 'this' refers to the associated Complex and the argument 836 * is the value of the Property. 837 * 838 * @name baja.SlotCursor#eachValue 839 * @function 840 * 841 * @param {Function} func function called on every iteration with the argument being a Property's value. 842 */ 843 SlotCursor.prototype.eachValue = function (func) { 844 return this.each(function (slot, i) { 845 if (slot.isProperty()) { 846 return func.call(this, this.get(slot), i); 847 } 848 849 // Return false so nothing stops iterating 850 return false; 851 }); 852 }; 853 854 /** 855 * Iterate through the Cursor and call 'each' on every Property Slot and get its display String. 856 * <p> 857 * When the function is called, 'this' refers to the associated Complex and the argument 858 * is the display String. 859 * 860 * @name baja.SlotCursor#eachDisplay 861 * @function 862 * 863 * @param {Function} func function called on every iteration with the argument being a Property's display String 864 */ 865 SlotCursor.prototype.eachDisplay = function (func) { 866 return this.each(function (slot, i) { 867 if (slot.isProperty()) { 868 return func.call(this, this.getDisplay(slot), i); 869 } 870 871 // Return false so nothing stops iterating 872 return false; 873 }); 874 }; 875 876 /** 877 * Return an array of Property values (regardless of iterative state). 878 * 879 * @name baja.SlotCursor#toValueArray 880 * @function 881 * 882 * @returns {Number} 883 */ 884 SlotCursor.prototype.toValueArray = function () { 885 var slots = this.toArray(), 886 values = [], 887 i; 888 889 for (i = 0; i < slots.length; ++i) { 890 if (slots[i].isProperty()) { 891 values.push(this.$context.get(slots[i])); 892 } 893 } 894 895 return values; 896 }; 897 898 /** 899 * Return an array of Property display Strings (regardless of iterative state). 900 * 901 * @name baja.SlotCursor#toDisplayArray 902 * @function 903 * 904 * @returns {Number} 905 */ 906 SlotCursor.prototype.toDisplayArray = function () { 907 var slots = this.toArray(), 908 displays = [], 909 i; 910 911 for (i = 0; i < slots.length; ++i) { 912 if (slots[i].isProperty()) { 913 displays.push(this.$context.getDisplay(slots[i])); 914 } 915 } 916 917 return displays; 918 }; 919 920 /** 921 * Return an Object Map of Property names with their corresponding values (regardless of iterative state). 922 * 923 * @name baja.SlotCursor#toValueMap 924 * @function 925 * 926 * @returns {Object} 927 */ 928 SlotCursor.prototype.toValueMap = function () { 929 var slots = this.toArray(), 930 map = {}, 931 s, 932 i; 933 934 for (i = 0; i < slots.length; ++i) { 935 s = slots[i]; 936 if (s.isProperty()) { 937 map[s.getName()] = this.$context.get(s); 938 } 939 } 940 941 return map; 942 }; 943 944 /** 945 * Return an Object Map of Property names with their corresponding display Strings (regardless of iterative state). 946 * 947 * @name baja.SlotCursor#toDisplayMap 948 * @function 949 * 950 * @returns {Object} 951 */ 952 SlotCursor.prototype.toDisplayMap = function () { 953 var slots = this.toArray(), 954 map = {}, 955 s, 956 i; 957 958 for (i = 0; i < slots.length; ++i) { 959 s = slots[i]; 960 if (s.isProperty()) { 961 map[s.getName()] = this.$context.getDisplay(s); 962 } 963 } 964 965 return map; 966 }; 967 968 function slotCursorFrozen(slot) { 969 return slot.isFrozen(); 970 } 971 972 /** 973 * Adds a filter to the Cursor for frozen Slots. 974 * 975 * @name baja.SlotCursor#frozen 976 * @function 977 * 978 * @returns {baja.SlotCursor} itself. 979 */ 980 SlotCursor.prototype.frozen = function () { 981 this.filter(slotCursorFrozen); 982 return this; 983 }; 984 985 function slotCursorDynamic(slot) { 986 return !slot.isFrozen(); 987 } 988 989 /** 990 * Adds a filter to the Cursor for dynamic Slots. 991 * 992 * @name baja.SlotCursor#dynamic 993 * @function 994 * 995 * @returns {baja.SlotCursor} itself. 996 */ 997 SlotCursor.prototype.dynamic = function () { 998 this.filter(slotCursorDynamic); 999 return this; 1000 }; 1001 1002 function slotCursorProperties(slot) { 1003 return slot.isProperty(); 1004 } 1005 1006 /** 1007 * Adds a filter to the Cursor for Properties. 1008 * 1009 * @name baja.SlotCursor#properties 1010 * @function 1011 * 1012 * @returns {baja.SlotCursor} itself. 1013 */ 1014 SlotCursor.prototype.properties = function () { 1015 this.filter(slotCursorProperties); 1016 return this; 1017 }; 1018 1019 function slotCursorActions(slot) { 1020 return slot.isAction(); 1021 } 1022 1023 /** 1024 * Adds a filter to the Cursor for Actions. 1025 * 1026 * @name baja.SlotCursor#actions 1027 * @function 1028 * 1029 * @returns {baja.SlotCursor} itself. 1030 */ 1031 SlotCursor.prototype.actions = function () { 1032 this.filter(slotCursorActions); 1033 return this; 1034 }; 1035 1036 function slotCursorTopics(slot) { 1037 return slot.isTopic(); 1038 } 1039 1040 /** 1041 * Adds a filter to the Cursor for Topics. 1042 * 1043 * @name baja.SlotCursor#topics 1044 * @function 1045 * 1046 * @returns {baja.SlotCursor} itself. 1047 */ 1048 SlotCursor.prototype.topics = function () { 1049 this.filter(slotCursorTopics); 1050 return this; 1051 }; 1052 1053 /** 1054 * Adds a filter for Property values that match the TypeSpec via {@link Type#is}. 1055 * <p> 1056 * This method can take a variable number of TypeSpecs. If a variable number of TypeSpecs 1057 * are specified then a slot will be filtered through if any of the TypeSpecs match (logical OR). 1058 * 1059 * @name baja.SlotCursor#is 1060 * @function 1061 * @see Type#is 1062 * 1063 * @param {Type|String} typeSpec the TypeSpec to test against. 1064 * 1065 * @returns {baja.SlotCursor} itself. 1066 */ 1067 SlotCursor.prototype.is = function (typeSpec) { 1068 typeSpec = Array.prototype.slice.call(arguments); 1069 1070 this.filter(function (slot) { 1071 if (slot.isProperty()) { 1072 var t = slot.getType(), 1073 i; 1074 1075 for (i = 0; i < typeSpec.length; ++i) { 1076 if (t.is(typeSpec[i])) { 1077 return true; 1078 } 1079 } 1080 } 1081 return false; 1082 }); 1083 1084 return this; 1085 }; 1086 1087 function slotCursorIsValue(slot) { 1088 return slot.isProperty() && slot.getType().isValue(); 1089 } 1090 1091 /** 1092 * Adds a filter for Property values that are of Type baja:Value {@link Type#isValue}. 1093 * 1094 * @name baja.SlotCursor#isValue 1095 * @function 1096 * @see Type#isValue 1097 * 1098 * @returns {baja.SlotCursor} itself. 1099 */ 1100 SlotCursor.prototype.isValue = function () { 1101 this.filter(slotCursorIsValue); 1102 return this; 1103 }; 1104 1105 function slotCursorIsSimple(slot) { 1106 return slot.isProperty() && slot.getType().isSimple(); 1107 } 1108 1109 /** 1110 * Adds a filter for Property values that are of Type baja:Simple {@link Type#isSimple}. 1111 * 1112 * @name baja.SlotCursor#isSimple 1113 * @function 1114 * @see Type#isSimple 1115 * 1116 * @returns {baja.SlotCursor} itself. 1117 */ 1118 SlotCursor.prototype.isSimple = function () { 1119 this.filter(slotCursorIsSimple); 1120 return this; 1121 }; 1122 1123 function slotCursorIsNumber(slot) { 1124 return slot.isProperty() && slot.getType().isNumber(); 1125 } 1126 1127 /** 1128 * Adds a filter for Property values that are of Type baja:Number {@link Type#isNumber}. 1129 * 1130 * @name baja.SlotCursor#isNumber 1131 * @function 1132 * @see Type#isNumber 1133 * 1134 * @returns {baja.SlotCursor} itself. 1135 */ 1136 SlotCursor.prototype.isNumber = function () { 1137 this.filter(slotCursorIsNumber); 1138 return this; 1139 }; 1140 1141 function slotCursorIsComplex(slot) { 1142 return slot.isProperty() && slot.getType().isComplex(); 1143 } 1144 1145 /** 1146 * Adds a filter for Property values that are of Type baja:Complex {@link Type#isComplex}. 1147 * 1148 * @name baja.SlotCursor#isComplex 1149 * @function 1150 * @see Type#isComplex 1151 * 1152 * @returns {baja.SlotCursor} itself. 1153 */ 1154 SlotCursor.prototype.isComplex = function () { 1155 this.filter(slotCursorIsComplex); 1156 return this; 1157 }; 1158 1159 function slotCursorIsComponent(slot) { 1160 return slot.isProperty() && slot.getType().isComponent(); 1161 } 1162 1163 /** 1164 * Adds a filter for Property values that are of Type baja:Component {@link Type#isComponent}. 1165 * 1166 * @name baja.SlotCursor#isComponent 1167 * @function 1168 * @see Type#isComponent 1169 * 1170 * @returns {baja.SlotCursor} itself. 1171 */ 1172 SlotCursor.prototype.isComponent = function () { 1173 this.filter(slotCursorIsComponent); 1174 return this; 1175 }; 1176 1177 function slotCursorIsStruct(slot) { 1178 return slot.isProperty() && slot.getType().isStruct(); 1179 } 1180 1181 /** 1182 * Adds a filter for Property values that are of Type baja:Struct {@link Type#isStruct}. 1183 * 1184 * @name baja.SlotCursor#isStruct 1185 * @function 1186 * @see Type#isStruct 1187 * 1188 * @returns {baja.SlotCursor} itself. 1189 */ 1190 SlotCursor.prototype.isStruct = function () { 1191 this.filter(slotCursorIsStruct); 1192 return this; 1193 }; 1194 1195 /** 1196 * Adds a filter for Properties whose Type matches via equals. 1197 * <p> 1198 * This method can take a variable number of TypeSpecs. If a variable number of TypeSpecs 1199 * are specified then a slot will be filtered through if any of the TypeSpecs match (logical OR). 1200 * 1201 * @name baja.SlotCursor#equalType 1202 * @function 1203 * 1204 * @param {Type|String|Array} typeSpec the TypeSpec to test against. 1205 * 1206 * @returns {baja.SlotCursor} itself. 1207 */ 1208 SlotCursor.prototype.equalType = function (typeSpec) { 1209 typeSpec = Array.prototype.slice.call(arguments); 1210 1211 // Ensure we have the Types we're interested in 1212 var i; 1213 for (i = 0; i < typeSpec.length; ++i) { 1214 typeSpec[i] = typeof typeSpec[i] === "string" ? baja.lt(typeSpec[i]) : typeSpec[i]; 1215 } 1216 1217 this.filter(function (slot) { 1218 if (slot.isProperty()) { 1219 var t = slot.getType(), 1220 i; 1221 1222 for (i = 0; i < typeSpec.length; ++i) { 1223 if (t.equals(typeSpec[i])) { 1224 return true; 1225 } 1226 } 1227 } 1228 return false; 1229 }); 1230 1231 return this; 1232 }; 1233 1234 /** 1235 * Adds a filter for Property values that match via equals. 1236 * <p> 1237 * This method can take a variable number of values. If a variable number of values 1238 * are specified then a slot will be filtered through if any of the values match (logical OR). 1239 * 1240 * @name baja.SlotCursor#equalValue 1241 * @function 1242 * 1243 * @param value the value to be used for equals. 1244 * 1245 * @returns {baja.SlotCursor} itself. 1246 */ 1247 SlotCursor.prototype.equalValue = function (value) { 1248 value = Array.prototype.slice.call(arguments); 1249 1250 this.filter(function (slot) { 1251 if (slot.isProperty()) { 1252 var v = this.get(slot), 1253 i; 1254 1255 for (i = 0; i < value.length; ++i) { 1256 if (v.equals(value[i])) { 1257 return true; 1258 } 1259 } 1260 } 1261 return false; 1262 }); 1263 1264 return this; 1265 }; 1266 1267 /** 1268 * Adds a filter for Property values that match via equivalent. 1269 * <p> 1270 * This method can take a variable number of values. If a variable number of values 1271 * are specified then a slot will be filtered through if any of the values match (logical OR). 1272 * 1273 * @name baja.SlotCursor#equivalent 1274 * @function 1275 * 1276 * @param value the value to be used for equivalent. 1277 * 1278 * @returns {baja.SlotCursor} itself. 1279 */ 1280 SlotCursor.prototype.equivalent = function (value) { 1281 value = Array.prototype.slice.call(arguments); 1282 1283 this.filter(function (slot) { 1284 if (slot.isProperty()) { 1285 var v = this.get(slot), 1286 i; 1287 1288 for (i = 0; i < value.length; ++i) { 1289 if (v.equivalent(value[i])) { 1290 return true; 1291 } 1292 } 1293 } 1294 return false; 1295 }); 1296 1297 return this; 1298 }; 1299 1300 /** 1301 * Adds a filter for Slots that match the given Slot name. 1302 * 1303 * @name baja.SlotCursor#slotName 1304 * @function 1305 * 1306 * @param {String|RegEx} slotName a String or Regular Expression for matching Slots via name. 1307 * 1308 * @returns {baja.SlotCursor} itself. 1309 */ 1310 SlotCursor.prototype.slotName = function (sName) { 1311 if (typeof sName === "string") { 1312 // String Comparison 1313 this.filter(function (slot) { 1314 return slot.getName().equals(sName.toString()); 1315 }); 1316 } 1317 else { 1318 // RegEx test 1319 this.filter(function (slot) { 1320 return sName.test(slot.getName()); 1321 }); 1322 } 1323 return this; 1324 }; 1325 1326 /** 1327 * Adds a filter for Slots that match the requested Slot Flags. 1328 * 1329 * @name baja.SlotCursor#flags 1330 * @function 1331 * 1332 * @see baja.Flags 1333 * 1334 * @param {Number} flgs the Slot flags to be tested for. 1335 * 1336 * @returns {baja.SlotCursor} itself. 1337 */ 1338 SlotCursor.prototype.flags = function (flgs) { 1339 this.filter(function (slot) { 1340 return (this.getFlags(slot) & flgs); 1341 }); 1342 return this; 1343 }; 1344 1345 //////////////////////////////////////////////////////////////// 1346 // Baja Components 1347 //////////////////////////////////////////////////////////////// 1348 1349 // Internal Component Event flags 1350 var CHANGED = 0, 1351 ADDED = 1, 1352 REMOVED = 2, 1353 RENAMED = 3, 1354 REORDERED = 4, 1355 PARENTED = 5, 1356 UNPARENTED = 6, 1357 ACTION_INVOKED = 7, 1358 TOPIC_FIRED = 8, 1359 FLAGS_CHANGED = 9, 1360 FACETS_CHANGED = 10, 1361 RECATEGORIZED = 11, 1362 KNOB_ADDED = 12, 1363 KNOB_REMOVED = 13, 1364 SUBSCRIBED = 14, 1365 UNSUBSCRIBED = 15; 1366 1367 function applyObjToComplex(clx, obj) { 1368 var slotName, 1369 value, 1370 isComponent = clx.getType().isComponent(); 1371 1372 for (slotName in obj) { 1373 if (obj.hasOwnProperty(slotName)) { 1374 value = obj[slotName]; 1375 if (clx.has(slotName)) { 1376 1377 // If value is a Object Literal then recursively apply it 1378 if (value && value.constructor === Object) { 1379 var v = clx.get(slotName); 1380 applyObjToComplex(v, 1381 value); 1382 } 1383 else { 1384 clx.set({ 1385 slot: slotName, 1386 value: value 1387 }); 1388 } 1389 } 1390 else if (isComponent) { 1391 1392 // If value is an Object Literal then recursively apply it 1393 if (value && value.constructor === Object) { 1394 var oldValue = value; 1395 value = new baja.Component(); 1396 applyObjToComplex(value, 1397 oldValue); 1398 } 1399 1400 clx.add({ 1401 slot: slotName, 1402 value: value 1403 }); 1404 } 1405 } 1406 } 1407 } 1408 1409 /** 1410 * @class Complex 1411 * <p> 1412 * Complex is the Value which is defined by one or more 1413 * property slots. Complex is never used directly, rather 1414 * it is the base class for Struct and Component. 1415 * <p> 1416 * Since Complex relates to a abstract Type, this Constructor should 1417 * never be directly used to create a new object. 1418 * 1419 * @see baja.Struct 1420 * @see baja.Component 1421 * 1422 * @name baja.Complex 1423 * @extends baja.Value 1424 */ 1425 baja.Complex = function () { 1426 baja.Complex.$super.apply(this, arguments); 1427 this.$map = new baja.OrderedMap(); 1428 this.$parent = null; 1429 this.$propInParent = null; 1430 }.$extend(baja.Value); 1431 1432 /** 1433 * Called once the frozen Slots have been loaded onto the Complex. 1434 * 1435 * @private 1436 */ 1437 baja.Complex.prototype.contractCommitted = function (arg) { 1438 // If this is a complex and there was an argument then attempt 1439 // to set (of add if a Component) Properties... 1440 if (arg && arg.constructor === Object) { 1441 applyObjToComplex(this, arg); 1442 } 1443 }; 1444 1445 /** 1446 * Return the name of the Component. 1447 * <p> 1448 * The name is taken from the parent Component's Property for this Component instance. 1449 * 1450 * @returns {String} name (null if not mounted). 1451 */ 1452 baja.Complex.prototype.getName = function () { 1453 return this.$propInParent === null ? null : this.$propInParent.getName(); 1454 }; 1455 1456 /** 1457 * Return a display name. 1458 * <p> 1459 * If a Slot is defined as an argument, the display name for the slot will be returned. 1460 * If not Slot is defined, the display name of the Complex will be returned. 1461 * 1462 * @param {baja.Slot|String} [slot] the Slot or Slot name. 1463 * 1464 * @returns {String} the display name (or null if none available). 1465 */ 1466 baja.Complex.prototype.getDisplayName = function (slot) { 1467 // If no Slot defined then get the display name of the Complex 1468 if (slot === undefined) { 1469 return this.getPropertyInParent() === null ? null : this.getParent().getDisplayName(this.getPropertyInParent()); 1470 } 1471 1472 slot = this.getSlot(slot); 1473 1474 // Bail if this slot doesn't exist 1475 if (slot === null) { 1476 return null; 1477 } 1478 1479 // This should be ok but just double check to ensure we have a display string 1480 var s = slot.$getDisplayName(); 1481 if (typeof s !== "string") { 1482 s = ""; 1483 } 1484 1485 // If there is no display name then default to unescaping the slot name 1486 if (s === "") { 1487 s = baja.SlotPath.unescape(slot.getName()); 1488 } 1489 1490 return s; 1491 }; 1492 1493 /** 1494 * Return a display string. 1495 * <p> 1496 * If a Slot argument is defined, the display name for the Slot will be returned. 1497 * If a Slot argument is not defined, the display name for the Complex will be returned. 1498 * <p> 1499 * Note that when an instance of a Complex is created, auto-generated accessors are 1500 * created to make accessing a frozen Slot's display string convenient... 1501 * <pre> 1502 * // myPoint has a Property named out... 1503 * baja.outln("The display string of the out Property: " + myPoint.getOutDisplay()); 1504 * </pre> 1505 * The auto-generated accessor is in the format of <code>'get(first letter is captialized)SlotNameDisplay()'</code> 1506 * <p> 1507 * If the name of an automatically generated method is already used in the Complex, a number will be added to the function name. 1508 * 1509 * @param {baja.Slot|String} [slot] the Slot or Slot name. 1510 * 1511 * @returns {String} display (or null if none available). 1512 */ 1513 baja.Complex.prototype.getDisplay = function (slot) { 1514 if (slot === undefined) { 1515 return this.getPropertyInParent() === null ? null : this.getPropertyInParent().$getDisplay(); 1516 } 1517 slot = this.getSlot(slot); 1518 return slot === null ? null : slot.$getDisplay(); 1519 }; 1520 1521 /** 1522 * Return the String representation. 1523 * 1524 * @returns {String} 1525 */ 1526 baja.Complex.prototype.toString = function () { 1527 var str = this.getDisplay(); 1528 return typeof str === "string" ? str : this.getType().toString(); 1529 }; 1530 1531 /** 1532 * Return the parent. 1533 * 1534 * @returns parent 1535 */ 1536 baja.Complex.prototype.getParent = function () { 1537 return this.$parent; 1538 }; 1539 1540 /** 1541 * Return the Property in the parent. 1542 * 1543 * @returns {baja.Property} the Property in the parent (null if not mounted). 1544 */ 1545 baja.Complex.prototype.getPropertyInParent = function () { 1546 return this.$propInParent; 1547 }; 1548 1549 /** 1550 * Return the Slot. 1551 * <p> 1552 * This is useful method to ensure you have the Slot instance instead of the Slot name String. 1553 * If a Slot is passed in, it will simply be checked and returned. 1554 * 1555 * @param {baja.Slot|String} slot the Slot or Slot name. 1556 * @returns {baja.Slot} the Slot for the Component (or null if the Slot doesn't exist). 1557 */ 1558 baja.Complex.prototype.getSlot = function (slot) { 1559 if (typeof slot === "string") { 1560 return this.$map.get(slot); 1561 } 1562 else { 1563 strictArg(slot, baja.Slot); 1564 return this.$map.get(slot.getName()); 1565 } 1566 }; 1567 1568 /** 1569 * Return a Cursor for accessing a Complex's Slots. 1570 * <p> 1571 * Please see {@link baja.SlotCursor} for useful builder methods. For example... 1572 * <pre> 1573 * // A Cursor for Dynamic Properties 1574 * var frozenPropCursor = myComp.getSlots().dynamic().properties(); 1575 * 1576 * // A Cursor for Frozen Actions 1577 * var frozenPropCursor = myComp.getSlots().frozen().actions(); 1578 * 1579 * // An Array of Control Points 1580 * var valArray = myComp.getSlots().properties().is("control:ControlPoint").toValueArray(); 1581 * 1582 * // An Array of Action Slots 1583 * var actionArray = myComp.getSlots().actions().toArray(); 1584 * 1585 * // An Object Map of slot name/value pairs 1586 * var map = myComp.getSlots().properties().toMap(); 1587 * 1588 * // The very first dynamic Property 1589 * var firstProp = myComp.getSlots().dynamic().properties().first(); 1590 * 1591 * // The very last dynamic Property 1592 * var lastProp = myComp.getSlots().dynamic().properties().last(); 1593 * 1594 * // The very first dynamic Property value 1595 * var firstVal = myComp.getSlots().dynamic().properties().firstValue(); 1596 * 1597 * // The very first dynamic Property value 1598 * var lastVal = myComp.getSlots().dynamic().properties().lastValue(); 1599 * 1600 * // All the Slots that start with the name 'foo' 1601 * var slotNameCursor = myComp.getSlots().slotName(/^foo/); 1602 * 1603 * // Use a custom Cursor to find all of the Slots that have a particular facets key/value 1604 * var custom = myComp.getSlots(function (slot) { 1605 * return slot.isProperty() && (this.getFacets(slot).get("myKey", "def") === "foo"); 1606 * }); 1607 * 1608 * // Same as above 1609 * var custom2 = myComp.getSlots().filter(function (slot) { 1610 * return slot.isProperty() && (this.getFacets(slot).get("myKey", "def") === "foo"); 1611 * }); 1612 * 1613 * // All Slots marked summary on the Component 1614 * var summarySlotCursor = myComp.getSlots().flags(baja.Flags.SUMMARY); 1615 * 1616 * // Call function for each Property that's a ControlPoint 1617 * myComp.getSlots().is("control:ControlPoint").each(function (slot) { 1618 * baja.outln("The Nav ORD for the ControlPoint: " + this.get(slot).getNavOrd(); 1619 * }); 1620 * </pre> 1621 * 1622 * @param {Function} [filter] function to filter out the Slots we're not interested in. 1623 * The filter function will be passed each Slot to see if it should be 1624 * be included. The function must return false to filter out a value and true 1625 * to keep it. 1626 * 1627 * @returns {SlotCursor} a Cursor for iterating through the Complex's Slots. 1628 */ 1629 baja.Complex.prototype.getSlots = function (filter) { 1630 var cursor = this.$map.getCursor(this, SlotCursor); 1631 if (filter) { 1632 cursor.filter(filter); 1633 } 1634 return cursor; 1635 }; 1636 1637 /** 1638 * Return Flags for a slot or for the Complex's parent Property. 1639 * <p> 1640 * If no arguments are provided and the Complex has a parent, the 1641 * flags for the parent's Property will be returned. 1642 * 1643 * @see baja.Flags 1644 * 1645 * @param {baja.Slot|String} [slot] Slot or Slot name. 1646 * @returns {Number} the flags for the Slot or the parent's Property flags. 1647 */ 1648 baja.Complex.prototype.getFlags = function (slot) { 1649 // If no arguments are specified then attempt to get parent properly slot Flags 1650 if (arguments.length === 0) { 1651 if (this.$parent !== null && this.$propInParent !== null) { 1652 return this.$parent.getFlags(this.$propInParent); 1653 } 1654 else { 1655 throw new Error("Complex has no parent"); 1656 } 1657 } 1658 1659 slot = this.getSlot(slot); 1660 if (slot === null) { 1661 throw new Error("Slot doesn't exist: " + slot); 1662 } 1663 return slot.getFlags(); 1664 }; 1665 1666 /** 1667 * Return a Property's value. 1668 * <p> 1669 * Note that when an instance of a Complex is created, auto-generated accessors are 1670 * created to make accessing a frozen Property's value convenient... 1671 * <pre> 1672 * // myPoint has a Property named out... 1673 * var val = myPoint.getOut(); 1674 * </pre> 1675 * The auto-generated accessor is in the format of <code>'get(first letter is captialized)SlotName()'</code>. 1676 * <p> 1677 * If the name of an automatically generated method is already used in the Complex, a number will be added to the function name. 1678 * 1679 * @param {baja.Property|String} prop the Property or Property name. 1680 * @returns the value for the Property (null if the Property doesn't exist). 1681 */ 1682 baja.Complex.prototype.get = function (prop) { 1683 prop = this.getSlot(prop); 1684 if (prop === null) { 1685 return null; 1686 } 1687 return prop.$getValue(); 1688 }; 1689 1690 /** 1691 * Return true if the Slot exists. 1692 * 1693 * @param {baja.Property|String} prop the Property or Property name 1694 * @returns {Boolean} 1695 */ 1696 baja.Complex.prototype.has = function (prop) { 1697 return this.getSlot(prop) !== null; 1698 }; 1699 1700 /** 1701 * Return the result of 'valueOf' on the specified Property's value. 1702 * If valueOf is not available then the Property's value is returned. 1703 * 1704 * @see baja.Complex#get 1705 * 1706 * @param {baja.Property|String} prop the Property or Property name. 1707 * @returns the valueOf for the Property's value or the Property's value 1708 * (null if the Property doesn't exist). 1709 */ 1710 baja.Complex.prototype.getValueOf = function (prop) { 1711 var v = this.get(prop); 1712 if (v !== null && typeof v.valueOf === "function") { 1713 return v.valueOf(); 1714 } 1715 else { 1716 return v; 1717 } 1718 }; 1719 1720 function syncStruct(fromVal, toVal) { 1721 fromVal.getSlots().properties().each(function (fromProp) { 1722 var toProp = toVal.getSlot(fromProp.getName()); 1723 1724 // Sync value display and slot display name 1725 fromProp.$setDisplay(toProp.$getDisplay()); 1726 fromProp.$setDisplayName(toProp.$getDisplayName()); 1727 1728 if (fromProp.getType().isStruct()) { 1729 // If another struct then sync appropriately 1730 syncStruct(fromProp.$getValue(), toProp.$getValue()); 1731 } 1732 else { 1733 // If a simple then directly set the value 1734 fromProp.$setValue(toProp.$getValue()); 1735 } 1736 }); 1737 } 1738 1739 /** 1740 * Set a Property's value. 1741 * <p> 1742 * If the Complex is mounted, this will <strong>asynchronously</strong> set the Properties 1743 * value on the Server. 1744 * <p> 1745 * An Object Literal is used to specify the method's arguments... 1746 * <pre> 1747 * myObj.set({ 1748 * slot: "outsideAirTemp", 1749 * value: 23.5, 1750 * ok: function () { 1751 * // Called once value has been set on the Server (optional) 1752 * }, 1753 * fail: function (err) { 1754 * // Called if the value fails to set on the Server (optional) 1755 * }, 1756 * batch // if defined, any network calls will be batched into this object (optional) 1757 * }); 1758 * </pre> 1759 * <p> 1760 * Note that when an instance of a Complex is created, auto-generated setters are 1761 * created to make setting a frozen Property's value convenient... 1762 * <pre> 1763 * // myPoint has a Property named outsideAirTemp... 1764 * myObj.setOutsideAirTemp(23.5); 1765 * 1766 * // ... or via an Object Literal if more arguments are needed... 1767 * 1768 * myObj.setOutsideAirTemp({ 1769 * value: 23.5, 1770 * ok: function () { 1771 * // Called once value has been set on the Server (optional) 1772 * } 1773 * }); 1774 * </pre> 1775 * The auto-generated setter is in the format of <code>'set(first letter is captialized)SlotName(...)'</code>. 1776 * <p> 1777 * If the name of an automatically generated method is already used in the Complex, a number will be added to the function name. 1778 * <p> 1779 * For callbacks, the 'this' keyword is set to the parent Component instance (if the Component is available). 1780 * 1781 * @param {Object} obj the Object Literal for the method's arguments. 1782 * @param {baja.Property|String} obj.slot the Property or Property name the value will be set on. 1783 * @param obj.value the value being set (Type must extend baja:Value). 1784 * @param {Function} [obj.ok] the ok function callback. Called once network call has succeeded on the Server. 1785 * @param {Function} [obj.fail] the fail function callback. Called if this method has an error. 1786 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 1787 * @param [obj.cx] the Context (used internally by BajaScript). 1788 */ 1789 baja.Complex.prototype.set = function (obj) { 1790 obj = objectify(obj); 1791 1792 var cb = new Callback(obj.ok, obj.fail, obj.batch), 1793 prop = obj.slot, 1794 val = obj.value, 1795 cx = bajaDef(obj.cx, null), 1796 serverDecode = cx && cx.serverDecode, 1797 commit = cx && cx.commit, 1798 syncStructVals = cx && cx.syncStructVals, 1799 comp = this; // Ensure 'this' is the Component in the ok and fail callback... 1800 1801 // Find the top level Component 1802 while (comp !== null && !comp.getType().isComponent()) { 1803 comp = comp.getParent(); 1804 } 1805 1806 cb.addOk(function (ok, fail, resp) { 1807 if (comp !== null) { 1808 ok.call(comp, resp); 1809 } 1810 else { 1811 ok(resp); 1812 } 1813 }); 1814 1815 cb.addFail(function (ok, fail, err) { 1816 if (comp !== null) { 1817 fail.call(comp, err); 1818 } 1819 else { 1820 fail(err); 1821 } 1822 }); 1823 1824 try { 1825 prop = this.getSlot(prop); 1826 1827 // If decoding from the Server then short circuit some of this 1828 if (!serverDecode) { 1829 // Validate arguments 1830 strictArg(prop, baja.Property); 1831 strictArg(val); 1832 1833 if (prop === null) { 1834 throw new Error("Could not find Property: " + obj.slot); 1835 } 1836 if (!baja.hasType(val)) { 1837 throw new Error("Can only set BValue Types as Component Properties"); 1838 } 1839 if (val.getType().isAbstract()) { 1840 throw new Error("Cannot set value in Complex to Abstract Type: " + val.getType()); 1841 } 1842 if (val.getType().isNumber() && prop.getType().isNumber() && !val.getType().equals(prop.getType())) { 1843 // Recreate the number with the correct boxed type if the type spec differs 1844 val = prop.getType().getInstance().constructor.make(val.valueOf()); 1845 } 1846 if (!val.getType().isValue()) { 1847 throw new Error("Cannot set non Value Types as Properties in a Complex"); 1848 } 1849 if (val === this) { 1850 throw new Error("Illegal argument: this === value"); 1851 } 1852 } 1853 1854 if (cx) { 1855 if (typeof cx.displayName === "string") { 1856 prop.$setDisplayName(cx.displayName); 1857 } 1858 if (typeof cx.display === "string") { 1859 prop.$setDisplay(cx.display); 1860 } 1861 } 1862 1863 if (val.equals(prop.$getValue())) { 1864 // TODO: May need to check for mounted on Components here 1865 cb.ok(); 1866 return; 1867 } 1868 1869 // Return if this set is trapped. If the set is trapped then the set operation will 1870 // be proxied off to a remote Space elsewhere... 1871 if (!commit && this.$fw("modifyTrap", [prop], val, cb, cx)) { 1872 return; 1873 } 1874 1875 // Unparent 1876 var isClx = val.getType().isComplex(); 1877 if (isClx) { 1878 if (val.getParent()) { 1879 throw new Error("Complex already parented: " + val.getType()); 1880 } 1881 1882 val.$parent = null; 1883 val.$propInParent = null; 1884 } 1885 1886 // If this is the same Struct from a Server decode then attempt to sync it 1887 // rather than completely replace it... 1888 if (syncStructVals && val.getType().isStruct() && val.getType().equals(prop.getType())) { 1889 syncStruct(/*from*/prop.$getValue(), /*to*/val); 1890 } 1891 else { 1892 // Set new Property value 1893 prop.$setValue(val); 1894 1895 // Parent 1896 if (isClx) { 1897 val.$parent = this; 1898 val.$propInParent = prop; 1899 1900 // If we have a Component then attempt to mount it 1901 if (val.getType().isComponent() && this.isMounted()) { 1902 this.$space.$fw("mount", val); 1903 } 1904 } 1905 } 1906 1907 // Invoke modified event (this will bubble up to a Component for the changed callback etc). 1908 this.$fw("modified", prop, cx); 1909 1910 // TODO: Modified a link. Need to set up Knobs? 1911 cb.ok(); 1912 } 1913 catch (err) { 1914 cb.fail(err); 1915 } 1916 }; 1917 1918 /** 1919 * Load all of the Slots on the Complex. 1920 * 1921 * @param {Object} [obj] the Object Literal for the method's arguments. 1922 * @param {Function} [obj.ok] the ok function callback. Called once network call has succeeded on the Server. 1923 * @param {Function} [obj.fail] the fail function callback. Called if this method has an error. 1924 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 1925 * 1926 * @see baja.Component#loadSlots 1927 */ 1928 baja.Complex.prototype.loadSlots = function (obj) { 1929 if (obj && obj.ok && typeof obj.ok === "function") { 1930 obj.ok.call(this); 1931 } 1932 }; 1933 1934 /** 1935 * Return the Facets for a Slot. 1936 * <p> 1937 * If no arguments are provided and the Complex has a parent, the 1938 * facets for the parent's Property will be returned. 1939 * 1940 * @param {baja.Slot|String} [slot] the Slot or Slot name. 1941 * @returns {baja.Facets} the Facets for the Slot (or null if Slot not found) or 1942 * the parent's Property facets. 1943 */ 1944 baja.Complex.prototype.getFacets = function (slot) { 1945 // If no arguments are specified then attempt to get parent properly slot Flags 1946 if (arguments.length === 0) { 1947 if (this.$parent !== null && this.$propInParent !== null) { 1948 return this.$parent.getFacets(this.$propInParent); 1949 } 1950 else { 1951 return null; 1952 } 1953 } 1954 1955 slot = this.getSlot(slot); 1956 return slot === null ? null : slot.getFacets(); 1957 }; 1958 1959 /** 1960 * Compare if all of this object's properties are equal to the specified object. 1961 * 1962 * @param obj 1963 * @returns {Boolean} true if this object is equivalent this the specified object. 1964 */ 1965 baja.Complex.prototype.equivalent = function (obj) { 1966 if (!baja.hasType(obj)) { 1967 return false; 1968 } 1969 1970 if (!obj.getType().equals(this.getType())) { 1971 return false; 1972 } 1973 1974 if (this.$map.getSize() !== obj.$map.getSize()) { 1975 return false; 1976 } 1977 1978 // Only compare against Properties 1979 var props = obj.getSlots().properties(), 1980 p, 1981 val; 1982 1983 while (props.next()) { 1984 p = props.get(); 1985 1986 // Always check flags 1987 if (!this.getFlags(p.getName()).equals(obj.getFlags(p))) { 1988 return false; 1989 } 1990 1991 val = obj.get(p); 1992 1993 if (val === null) { 1994 return false; 1995 } 1996 1997 // Compare Property values 1998 if (!val.equivalent(this.get(p.getName()))) { 1999 return false; 2000 } 2001 2002 // Ensure they're the same order in the SlotMap 2003 if (this.$map.getIndex(p.getName()) !== props.getIndex()) { 2004 return false; 2005 } 2006 } 2007 2008 return true; 2009 }; 2010 2011 var copyingContext = { type: "copying" }; 2012 2013 /** 2014 * Create a clone of this Complex. 2015 * <p> 2016 * If the exact argument is true and this Complex is a Component then 2017 * the defaultOnClone and removeOnClone flags will be ignored. 2018 * 2019 * @param {Boolean} [exact] flag to indicate whether to create an exact copy (false by default). 2020 * @returns cloned Complex. 2021 */ 2022 baja.Complex.prototype.newCopy = function (exact) { 2023 var newInstance = this.getType().getInstance(), 2024 props = this.getSlots().properties(), 2025 p, 2026 val; 2027 2028 while (props.next()) { 2029 p = props.get(); 2030 2031 // If default on clone and it's not an exact copy then skip this Property 2032 if (!exact && (baja.Flags.DEFAULT_ON_CLONE & p.getFlags()) === baja.Flags.DEFAULT_ON_CLONE) { 2033 continue; 2034 } 2035 2036 // Make a copy of the Property value 2037 val = this.get(p).newCopy(exact); 2038 2039 if (p.isFrozen()) { 2040 newInstance.set({ 2041 "slot": p.getName(), 2042 "value": val, 2043 "cx": copyingContext 2044 }); 2045 } 2046 else { 2047 // If remove on clone and it's not an exact copy then skip copying this Property 2048 if (!exact && (baja.Flags.REMOVE_ON_CLONE & p.getFlags()) === baja.Flags.REMOVE_ON_CLONE) { 2049 continue; 2050 } 2051 2052 // TODO: Skip BLinks, dynamic slots added in constructor and slots with removeOnClone set 2053 p = newInstance.add({ 2054 "slot": p.getName(), 2055 "value": val, 2056 "flags": p.getFlags(), 2057 "facets": p.getFacets(), 2058 "cx": copyingContext 2059 }); 2060 } 2061 2062 // Copy of flags 2063 newInstance.getSlot(p.getName()).$setFlags(p.getFlags()); 2064 } 2065 2066 return newInstance; 2067 }; 2068 2069 2070 /** 2071 * Internal framework method. 2072 * <p> 2073 * This method should only be used by Tridium developers. It follows 2074 * the same design pattern as Niagara's Component 'fw' method. 2075 * 2076 * @private 2077 */ 2078 baja.Complex.prototype.$fw = function (x, a, b, c, d) { 2079 if (x === "modified") { 2080 if (this.$parent !== null) { 2081 this.$parent.$fw(x, this.$propInParent, b, c, d); 2082 } 2083 } 2084 else if (x === "modifyTrap") { 2085 if (this.$parent !== null) { 2086 a.push(this.$propInParent); 2087 return this.$parent.$fw(x, a, b, c, d); 2088 } 2089 else { 2090 return false; 2091 } 2092 } 2093 }; 2094 2095 /** 2096 * @class Represents a baja:Struct in BajaScript. 2097 * <p> 2098 * Struct is the base class for a component which has 2099 * one or more properties. Structs must only declare 2100 * properties which are typed as boolean, int, float, 2101 * String, Simple, or other Structs. This means 2102 * that a Struct may never have a Component property. 2103 * Structs only support Property slots, never Actions or Topics. 2104 * <p> 2105 * A Struct can only contain frozen Slots. A frozen Slot is defined 2106 * at compile time (usually hard coded in Java). 2107 * 2108 * @see baja.Component 2109 * 2110 * @name baja.Struct 2111 * @extends baja.Complex 2112 */ 2113 baja.Struct = function () { 2114 baja.Struct.$super.apply(this, arguments); 2115 }.$extend(baja.Complex).registerType("baja:Struct"); 2116 2117 /** 2118 * @class Represents a baja:Action in BajaScript. 2119 * <p> 2120 * Please note: this represents the Property's value and NOT the Property itself. 2121 * 2122 * @name baja.ActionProperty 2123 * @extends baja.Struct 2124 */ 2125 baja.ActionProperty = function () { 2126 baja.ActionProperty.$super.apply(this, arguments); 2127 this.$paramType = null; 2128 this.$paramDef = null; 2129 this.$returnType = null; 2130 }.$extend(baja.Struct).registerType("baja:Action"); 2131 2132 /** 2133 * Return the Action's parameter Type. 2134 * 2135 * @returns {Type} parameter type (or null if the Action has no parameter). 2136 */ 2137 baja.ActionProperty.prototype.getParamType = function () { 2138 return this.$paramType; 2139 }; 2140 2141 /** 2142 * Return the Action's parameter default value. 2143 * 2144 * @returns parameter default value (or null if the Action has no parameter). 2145 */ 2146 baja.ActionProperty.prototype.getParamDefault = function () { 2147 return this.$paramDef; 2148 }; 2149 2150 /** 2151 * Return the Action's return Type. 2152 * 2153 * @returns return type (or null if the Action has nothing to return). 2154 */ 2155 baja.ActionProperty.prototype.getReturnType = function () { 2156 return this.$returnType; 2157 }; 2158 2159 /** 2160 * Called when the Action Property is invoked. 2161 * 2162 * @private 2163 * 2164 * @param target the Component target the Action is being invoked upon. 2165 * @param arg the argument for the Action. 2166 * @param cx the Context for the Action invocation (could be null). 2167 * @returns the Action's return value (null if nothing to return). 2168 */ 2169 baja.ActionProperty.prototype.invoke = function (target, arg, cx) { 2170 return null; 2171 }; 2172 2173 /** 2174 * @class Represents a baja:Topic in BajaScript. 2175 * <p> 2176 * Please note: this represents the Property's value and not the Property itself. 2177 * 2178 * @name baja.TopicProperty 2179 * @extends baja.Struct 2180 */ 2181 baja.TopicProperty = function () { 2182 baja.TopicProperty.$super.apply(this, arguments); 2183 this.$eventType = null; 2184 }.$extend(baja.Struct).registerType("baja:Topic"); 2185 2186 /** 2187 * Return the Topic's event Type. 2188 * 2189 * @returns {Type} event Type (or null if the Topic has no event Type). 2190 */ 2191 baja.TopicProperty.prototype.getEventType = function () { 2192 return this.$eventType; 2193 }; 2194 2195 /** 2196 * Called when the Topic Property is fired. 2197 * 2198 * @private 2199 * 2200 * @param target the Component target the Topic is being fired upon. 2201 * @param event the event for the Topic. 2202 * @param cx the Context for the Topic being fired (could be null). 2203 */ 2204 baja.TopicProperty.prototype.fire = function (target, event, cx) { 2205 }; 2206 2207 /** 2208 * @class Represents a baja:Component in BajaScript. 2209 * <p> 2210 * Component is the required base class for all 2211 * Baja component classes. 2212 * <p> 2213 * Just like Niagara, 'baja:Component' contains a lot of the core functionality of the framework. 2214 * Unlike 'baja:Struct', a Component can contain both frozen and dynamic Slots. Frozen Slots are 2215 * defined at compile time (typically hard coded in Java) and Dynamic Slots can be added at 2216 * runtime (i.e. when the Station is running). There are three different types of Slots that 2217 * a Component can contain (Property, Action and Topic). 2218 * 2219 * @see baja.Struct 2220 * @see baja.Property 2221 * @see baja.Action 2222 * @see baja.Topic 2223 * 2224 * @name baja.Component 2225 * @extends baja.Complex 2226 */ 2227 baja.Component = function () { 2228 baja.Component.$super.apply(this, arguments); 2229 this.$space = null; 2230 this.$handle = null; 2231 this.$bPropsLoaded = false; 2232 this.$subs = []; 2233 this.$lease = false; 2234 this.$leaseTicket = baja.clock.expiredTicket; 2235 this.$knobs = null; 2236 this.$permissionsStr = null; 2237 this.$permissions = null; 2238 }.$extend(baja.Complex).registerType("baja:Component"); 2239 2240 // This is a generic component event handling function 2241 // that can route events to Component or Subscriber event handlers 2242 function handleComponentEvent(component, handlers, id, slot, obj, str, cx) { 2243 var error = baja.error; 2244 2245 if (id === CHANGED) { 2246 handlers.fireHandlers("changed", error, component, slot, cx); 2247 } 2248 else if (id === ADDED) { 2249 handlers.fireHandlers("added", error, component, slot, cx); 2250 } 2251 else if (id === REMOVED) { 2252 handlers.fireHandlers("removed", error, component, slot, obj, cx); 2253 } 2254 else if (id === RENAMED) { 2255 handlers.fireHandlers("renamed", error, component, slot, str, cx); 2256 } 2257 else if (id === REORDERED) { 2258 handlers.fireHandlers("reordered", error, component, cx); 2259 } 2260 else if (id === TOPIC_FIRED) { 2261 handlers.fireHandlers("topicFired", error, component, slot, obj, cx); 2262 } 2263 else if (id === FLAGS_CHANGED) { 2264 handlers.fireHandlers("flagsChanged", error, component, slot, cx); 2265 } 2266 else if (id === FACETS_CHANGED) { 2267 handlers.fireHandlers("facetsChanged", error, component, slot, cx); 2268 } 2269 else if (id === SUBSCRIBED) { 2270 handlers.fireHandlers("subscribed", error, component, cx); 2271 } 2272 else if (id === UNSUBSCRIBED) { 2273 handlers.fireHandlers("unsubscribed", error, component, cx); 2274 } 2275 else if (id === KNOB_ADDED) { 2276 handlers.fireHandlers("addKnob", error, component, slot, obj, cx); 2277 } 2278 else if (id === KNOB_REMOVED) { 2279 handlers.fireHandlers("removeKnob", error, component, slot, obj, cx); 2280 } 2281 } 2282 2283 // Handle Component child events 2284 function handleComponentChildEvent(component, handlers, id, str, cx) { 2285 var error = baja.error; 2286 if (id === RENAMED) { 2287 handlers.fireHandlers("componentRenamed", error, component, str, cx); 2288 } 2289 else if (id === FLAGS_CHANGED) { 2290 handlers.fireHandlers("componentFlagsChanged", error, component, cx); 2291 } 2292 else if (id === FACETS_CHANGED) { 2293 handlers.fireHandlers("componentFacetsChanged", error, component, cx); 2294 } 2295 } 2296 2297 // Handler Reorder Component Child Events 2298 function handleReorderComponentChildEvent(component, handlers, cx) { 2299 handlers.fireHandlers("componentReordered", baja.error, component, cx); 2300 } 2301 2302 function fwCompEvent(comp, id, slot, obj, str, cx) { 2303 2304 if (comp.isSubscribed() || id === UNSUBSCRIBED) { 2305 // First support framework callback 2306 // TODO: Commented out for now for improved performance. Are these really needed? 2307 /* 2308 try { 2309 if (id === CHANGED) { 2310 comp.$fw("changed", slot, cx); 2311 } 2312 else if (id === ADDED) { 2313 comp.$fw("added", slot, cx); 2314 } 2315 else if (id === REMOVED) { 2316 comp.$fw("removed", slot, obj, cx); 2317 } 2318 else if (id === RENAMED) { 2319 comp.$fw("renamed", slot, str, cx); 2320 } 2321 else if (id === REORDERED) { 2322 comp.$fw("reordered", cx); 2323 } 2324 else if (id === TOPIC_FIRED) { 2325 comp.$fw("fired", slot, obj, cx); 2326 } 2327 else if (id === SUBSCRIBED) { 2328 comp.$fw("subscribed", cx); 2329 } 2330 else if (id === UNSUBSCRIBED) { 2331 comp.$fw("unsubscribed", cx); 2332 } 2333 else if (id === KNOB_ADDED) { 2334 comp.$fw("knobAdded", obj, cx); 2335 } 2336 else if (id === KNOB_REMOVED) { 2337 comp.$fw("knobRemoved", obj, cx); 2338 } 2339 } 2340 catch (e) { 2341 error(e); 2342 } 2343 */ 2344 2345 // Route to event handlers on the Component 2346 if (comp.hasHandlers()) { 2347 handleComponentEvent(comp, comp, id, slot, obj, str, cx); 2348 } 2349 2350 // Route to Subscribers if there are any registered 2351 if (comp.$subs.length > 0) { 2352 // Route to all registered Subscribers 2353 var i; 2354 for (i = 0; i < comp.$subs.length; ++i) { 2355 // Route to event handlers on the Subscriber 2356 if (comp.$subs[i].hasHandlers()) { 2357 handleComponentEvent(comp, comp.$subs[i], id, slot, obj, str, cx); 2358 } 2359 } 2360 } 2361 } 2362 2363 var targetVal = null; 2364 if (id === RENAMED) { 2365 if (slot && slot.isProperty()) { 2366 targetVal = comp.get(slot); 2367 } 2368 } 2369 else if (id === FLAGS_CHANGED || 2370 id === FACETS_CHANGED) { 2371 if (slot && slot.isProperty()) { 2372 targetVal = comp.get(slot); 2373 } 2374 } 2375 2376 // Route to child Component 2377 if (targetVal !== null && targetVal.getType().isComponent() && targetVal.isSubscribed()) { 2378 // Route to event handlers on the Component 2379 handleComponentChildEvent(targetVal, targetVal, id, str, cx); 2380 2381 // Route to Subscribers if there are any registered 2382 if (targetVal.$subs.length > 0) { 2383 // Route to all registered Subscribers 2384 var x; 2385 for (x = 0; x < targetVal.$subs.length; ++x) { 2386 // Route to event handlers on the Subscriber 2387 handleComponentChildEvent(targetVal, targetVal.$subs[x], id, str, cx); 2388 } 2389 } 2390 } 2391 2392 // Special case for child reordered Component events. We need to route to all child Components on the target Component 2393 if (id === REORDERED) { 2394 comp.getSlots(function (slot) { 2395 return slot.isProperty() && slot.getType().isComponent() && this.get(slot).isSubscribed(); 2396 }).each(function (slot) { 2397 // Route reordered event 2398 var component = this.get(slot); 2399 handleReorderComponentChildEvent(component, component, cx); 2400 if (component.$subs.length > 0) { 2401 var i; 2402 for (i = 0; i < component.$subs.length; ++i) { 2403 // Route to event handlers on the Subscriber 2404 handleReorderComponentChildEvent(component, component.$subs[i], cx); 2405 } 2406 } 2407 }); 2408 } 2409 2410 // NavEvents 2411 if (baja.nav.hasHandlers() && 2412 comp.isMounted() && 2413 ((id === ADDED && slot.getType().isComponent()) || 2414 (id === REMOVED && obj.getType().isComponent()) || 2415 (id === RENAMED && slot.getType().isComponent()) || 2416 id === REORDERED)) { 2417 handleComponentEvent(comp, baja.nav, id, slot, obj, str, cx); 2418 } 2419 } 2420 2421 function setContextInOkCallback(comp, cb) { 2422 cb.addOk(function (ok, fail, resp) { 2423 ok.call(comp, resp); 2424 }); 2425 } 2426 2427 function setContextInFailCallback(comp, cb) { 2428 cb.addFail(function (ok, fail, err) { 2429 fail.call(comp, err); 2430 }); 2431 } 2432 2433 /** 2434 * Set a Slot's flags. 2435 * <p> 2436 * If the Complex is mounted, this will <strong>asynchronously</strong> set the Slot Flags on the Server. 2437 * <p> 2438 * An Object Literal is used to specify the method's arguments... 2439 * <pre> 2440 * myObj.setFlags({ 2441 * slot: "outsideAirTemp", 2442 * flags: baja.Flags.SUMMARY, 2443 * ok: function () { 2444 * // Called once the Flags have been set (optional) 2445 * }, 2446 * fail: function (err) { 2447 * // Called if the flags fail to set (optional) 2448 * }, 2449 * batch // if defined, any network calls will be batched into this object (optional) 2450 * }); 2451 * </pre> 2452 * For callbacks, the 'this' keyword is set to the Component instance. 2453 * 2454 * @param {Object} obj the Object Literal for the method's arguments. 2455 * @param {baja.Slot|String} obj.slot the Slot of Slot name. 2456 * @param {Number} obj.flags the new flags for the Slot. 2457 * @param {Function} [obj.ok] the ok function callback. Called once the method has succeeded. 2458 * @param {Function} [obj.fail] the fail function callback. Called if this method has an error. 2459 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 2460 * @param [obj.cx] the Context (used internally by BajaScript). 2461 */ 2462 baja.Component.prototype.setFlags = function (obj) { 2463 obj = objectify(obj); 2464 2465 var slot = obj.slot, 2466 flags = obj.flags, 2467 cx = obj.cx, 2468 serverDecode = cx && cx.serverDecode, 2469 commit = cx && cx.commit, 2470 cb = new Callback(obj.ok, obj.fail, obj.batch); 2471 2472 // Ensure 'this' is Component in callbacks... 2473 setContextInOkCallback(this, cb); 2474 setContextInFailCallback(this, cb); 2475 2476 try { 2477 slot = this.getSlot(slot); 2478 2479 // Short circuit some of this if this is called from a Server decode 2480 if (!serverDecode) { 2481 // Validate arguments 2482 strictArg(slot, baja.Slot); 2483 strictArg(flags, Number); 2484 2485 if (slot === null) { 2486 throw new Error("Could not find Slot: " + obj.slot); 2487 } 2488 2489 // Subclass check 2490 if (typeof this.checkSetFlags === "function") { 2491 this.checkSetFlags(slot, flags, cx); 2492 } 2493 } 2494 2495 // Check if this is a proxy. If so then trap it... 2496 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 2497 this.$space.getCallbacks().setFlags(this, slot, flags, cb); 2498 return; 2499 } 2500 2501 // Set the flags for the Slot 2502 slot.$setFlags(flags); 2503 2504 if (cx) { 2505 if (typeof cx.displayName === "string") { 2506 slot.$setDisplayName(cx.displayName); 2507 } 2508 if (typeof cx.display === "string") { 2509 slot.$setDisplay(cx.display); 2510 } 2511 } 2512 2513 // Fire Component Event 2514 fwCompEvent(this, FLAGS_CHANGED, slot, null, null, cx); 2515 2516 cb.ok(); 2517 } 2518 catch (err) { 2519 cb.fail(err); 2520 } 2521 }; 2522 2523 /** 2524 * Set a dynamic Slot's facets. 2525 * <p> 2526 * If the Complex is mounted, this will <strong>asynchronously</strong> change the facets on the Server. 2527 * <p> 2528 * An Object Literal is used to specify the method's arguments... 2529 * <pre> 2530 * myObj.setFacets({ 2531 * slot: "outsideAirTemp", 2532 * facets: baja.Facets.make(["foo"], ["boo"]), 2533 * ok: function () { 2534 * // Called once the Facets have been set (optional) 2535 * }, 2536 * fail: function (err) { 2537 * // Called if the facets fail to change (optional) 2538 * }, 2539 * batch // if defined, any network calls will be batched into this object (optional) 2540 * }); 2541 * </pre> 2542 * For callbacks, the 'this' keyword is set to the Component instance. 2543 * 2544 * @param {Object} obj the Object Literal for the method's arguments. 2545 * @param {baja.Slot|String} obj.slot the Slot of Slot name. 2546 * @param {baja.Facets} obj.facets the new facets for the dynamic Slot. 2547 * @param {Function} [obj.ok] the ok function callback. Called once the method has succeeded. 2548 * @param {Function} [obj.fail] the fail function callback. Called if this method has an error. 2549 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 2550 * @param [obj.cx] the Context (used internally by BajaScript). 2551 */ 2552 baja.Component.prototype.setFacets = function (obj) { 2553 obj = objectify(obj); 2554 var cb = new Callback(obj.ok, obj.fail, obj.batch); 2555 2556 // Ensure 'this' is Component in callbacks... 2557 setContextInOkCallback(this, cb); 2558 setContextInFailCallback(this, cb); 2559 2560 var slot = obj.slot, 2561 facets = obj.facets, 2562 cx = obj.cx, 2563 serverDecode = cx && cx.serverDecode, 2564 commit = cx && cx.commit; 2565 2566 try { 2567 slot = this.getSlot(slot); 2568 2569 if (facets === null) { 2570 facets = baja.Facets.DEFAULT; 2571 } 2572 2573 // Short circuit some of this if this is the result of a Server Decode 2574 if (!serverDecode) { 2575 // Validate arguments 2576 strictArg(slot, baja.Slot); 2577 strictArg(facets, baja.Facets); 2578 2579 if (slot === null) { 2580 throw new Error("Could not find Slot: " + obj.slot); 2581 } 2582 2583 if (slot.isFrozen()) { 2584 throw new Error("Cannot set facets of frozen Slot: " + slot.getName()); 2585 } 2586 2587 // Subclass check 2588 if (typeof this.checkSetFacets === "function") { 2589 this.checkSetFacets(slot, facets, cx); 2590 } 2591 } 2592 2593 // Check if this is a proxy. If so then trap it... 2594 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 2595 this.$space.getCallbacks().setFacets(this, slot, facets, cb); 2596 return; 2597 } 2598 2599 // Set the flags for the Slot 2600 slot.$setFacets(facets); 2601 2602 if (cx) { 2603 if (typeof cx.displayName === "string") { 2604 slot.$setDisplayName(cx.displayName); 2605 } 2606 if (typeof cx.display === "string") { 2607 slot.$setDisplay(cx.display); 2608 } 2609 } 2610 2611 // Fire Component Event 2612 fwCompEvent(this, FACETS_CHANGED, slot, facets, null, cx); 2613 2614 cb.ok(); 2615 } 2616 catch (err) { 2617 cb.fail(err); 2618 } 2619 }; 2620 2621 /** 2622 * Add a dynamic Property to a Component. 2623 * <p> 2624 * If the value extends baja:Action, the new slot is also an Action. 2625 * If the value extends baja:Topic, the new slot is also a Topic. 2626 * <p> 2627 * If the Complex is mounted, this will <strong>asynchronously</strong> add 2628 * the Property to the Component on the Server. 2629 * <p> 2630 * An Object Literal is used to specify the method's arguments... 2631 * <pre> 2632 * myObj.add({ 2633 * slot: "foo", 2634 * value: "slot value", 2635 * facets: baja.Facets.make(["doo"], ["boo"]), // Optional 2636 * flags: baja.Flags.SUMMARY, // Optional 2637 * ok: function (prop) { 2638 * // Called once the Property has been added (optional) 2639 * }, 2640 * fail: function (err) { 2641 * // Called if the Property fails to add (optional) 2642 * }, 2643 * batch // if defined, any network calls will be batched into this object (optional) 2644 * }); 2645 * </pre> 2646 * For callbacks, the 'this' keyword is set to the Component instance. 2647 * 2648 * @see baja.Facets 2649 * @see baja.Flags 2650 * @see baja.Component#getUniqueName 2651 * 2652 * @param {Object} obj the Object Literal for the method's arguments. 2653 * @param {String} obj.slot the Slot name the unique name to use as the String 2654 * key for the slot. If null is passed, then a 2655 * unique name will automatically be generated. 2656 * If the name ends with the '?' character a unique 2657 * name will automatically be generated by appending 2658 * numbers to the specified name. The name must meet 2659 * the "name" production in the SlotPath BNF grammar. 2660 * Informally this means that the name must start with 2661 * an ascii letter and contain only ascii letters, ascii 2662 * digits, or '_'. Escape sequences can be specified 2663 * using the '$' char. Use baja.SlotPath.escape() to escape 2664 * illegal characters. 2665 * @param {Object} obj.value the value to be added (Type must extend baja:Value). 2666 * @param {Number} [obj.flags] optional Slot flags. 2667 * @param {baja.Facets} [obj.facets] optional Slot Facets. 2668 * @param {Function} [obj.ok] the ok callback. This function is called once the Property 2669 * has been added to the Server. The function is passed the new 2670 * Property that has just been added. 2671 * @param {Function} [obj.fail] the fail callback. This function is called if the Property 2672 * fails to add. Any error information is passed into this function. 2673 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object 2674 * @param [obj.cx] the Context (used internally by BajaScript). 2675 * @returns if not mounted, the newly added Property. Otherwise null is returned. 2676 */ 2677 baja.Component.prototype.add = function (obj) { 2678 obj = objectify(obj, "value"); 2679 2680 var cb = new Callback(obj.ok, obj.fail, obj.batch); 2681 2682 // Ensure 'this' is Component in callbacks... 2683 setContextInOkCallback(this, cb); 2684 setContextInFailCallback(this, cb); 2685 2686 var slotName = obj.slot, 2687 val = obj.value, 2688 flags = obj.flags, 2689 facets = obj.facets, 2690 cx = obj.cx, 2691 serverDecode, 2692 commit; 2693 2694 try { 2695 slotName = bajaDef(slotName, null); 2696 flags = bajaDef(flags, 0); 2697 facets = bajaDef(facets, baja.Facets.DEFAULT); 2698 cx = bajaDef(cx, null); 2699 serverDecode = cx && cx.serverDecode; 2700 commit = cx && cx.commit; 2701 2702 // Short-circuit some of this if this is the result of a Server Decode 2703 if (!serverDecode) { 2704 // Validate arguments 2705 strictArg(slotName, String); 2706 strictArg(val); 2707 strictArg(flags, Number); 2708 strictArg(facets, baja.Facets); 2709 strictArg(cx); 2710 2711 if (!baja.hasType(val)) { 2712 throw new Error("Can only add BValue Types as Component Properties"); 2713 } 2714 if (val.getType().isAbstract()) { 2715 throw new Error("Cannot add Abstract Type to Component: " + val.getType()); 2716 } 2717 if (!val.getType().isValue()) { 2718 throw new Error("Cannot add non Value Types as Properties to a Component"); 2719 } 2720 if (val === this) { 2721 throw new Error("Illegal argument value === this"); 2722 } 2723 // Custom check add 2724 if (typeof this.checkAdd === "function") { 2725 this.checkAdd(slotName, val, flags, facets, cx); 2726 } 2727 if (val.getType().isComponent()) { 2728 if (typeof val.isParentLegal === "function") { 2729 if (!this.getType().is("baja:UnrestrictedFolder")) { 2730 if (!val.isParentLegal(this)) { 2731 throw new Error("Illegal parent: " + this.getType() + " for child " + val.getType()); 2732 } 2733 } 2734 } 2735 if (typeof this.isChildLegal === "function") { 2736 if (!this.isChildLegal(val)) { 2737 throw new Error("Illegal child: " + val.getType() + " for parent " + this.getType()); 2738 } 2739 } 2740 } 2741 } 2742 2743 // Check if this is a proxy. If so then trap it... 2744 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 2745 this.$space.getCallbacks().add(this, 2746 slotName, 2747 val, 2748 flags, 2749 facets, 2750 cb); 2751 return null; 2752 } 2753 2754 if (!serverDecode) { 2755 if (slotName === null) { 2756 slotName = this.getUniqueName(val.getType().getTypeName()); // TODO: Need extra argument checking before this is reached 2757 } 2758 else if (slotName.substring(slotName.length - 1, slotName.length) === "?") { 2759 slotName = this.getUniqueName(slotName.substring(0, slotName.length - 1)); 2760 } 2761 2762 baja.SlotPath.verifyValidName(slotName); 2763 } 2764 2765 // Check for duplicate Slot 2766 if (this.$map.get(slotName) !== null) { 2767 throw new Error("Duplicate Slot: " + slotName); 2768 } 2769 2770 var displayName = slotName, 2771 display = ""; 2772 2773 if (cx) { 2774 if (typeof cx.displayName === "string") { 2775 displayName = cx.displayName; 2776 } 2777 if (typeof cx.display === "string") { 2778 display = cx.display; 2779 } 2780 } 2781 2782 var p; 2783 if (val.getType().isAction()) { 2784 p = new PropertyAction(slotName, displayName, display, flags, facets, val); 2785 } 2786 else if (val.getType().isTopic()) { 2787 p = new PropertyTopic(slotName, displayName, display, flags, facets, val); 2788 } 2789 else { 2790 p = new DynamicProperty(slotName, displayName, display, flags, facets, val); 2791 } 2792 2793 // Add the Slot to the map 2794 this.$map.put(slotName, p); 2795 2796 // Set up any parenting if needed 2797 if (val.getType().isComplex()) { 2798 if (val.getParent() !== null) { 2799 throw new Error("Complex already parented: " + val.getType()); 2800 } 2801 val.$parent = this; 2802 val.$propInParent = p; 2803 2804 // If we have a Component then attempt to mount it 2805 if (val.getType().isComponent() && this.isMounted()) { 2806 this.$space.$fw("mount", val); 2807 } 2808 } 2809 2810 // Fire Component Event 2811 fwCompEvent(this, ADDED, p, null, null, cx); 2812 2813 cb.ok(p); 2814 return p; 2815 } 2816 catch (err) { 2817 cb.fail(err); 2818 } 2819 return null; 2820 }; 2821 2822 /** 2823 * Return a unique name for a potential new Slot in this Component. 2824 * <p> 2825 * Please note, this method inspects the current Slots this Component has loaded 2826 * to find a unique name. Therefore, if this Component is a Proxy, it must be 2827 * fully loaded and subscribed. Also please refrain from using this method in a 2828 * batch operation since it's likely the other operations in the batch will influence 2829 * a Slot name's uniqueness. 2830 * 2831 * @param {String} slotName the initial Slot name used to ensure uniqueness. This must be 2832 * a valid Slot name. 2833 * @returns {String} a unique name. 2834 */ 2835 baja.Component.prototype.getUniqueName = function (slotName) { 2836 baja.SlotPath.verifyValidName(slotName); 2837 var n = slotName, 2838 i = 1; 2839 while (this.getSlot(n) !== null) { 2840 n = slotName + i; 2841 i++; 2842 } 2843 return n; 2844 }; 2845 2846 function removeUnmountEvent(component, handlers, cx) { 2847 handlers.fireHandlers("unmount", baja.error, component, cx); 2848 } 2849 2850 function removePropagateUnmountEvent(component, cx) { 2851 // If the Component is subscribed then trigger the unmount events 2852 if (component.isSubscribed()) { 2853 removeUnmountEvent(component, component, cx); 2854 2855 if (component.$subs.length > 0) { 2856 // Route to all registered Subscribers 2857 var i; 2858 for (i = 0; i < component.$subs.length; ++i) { 2859 // Route to event handlers on the Subscriber 2860 removeUnmountEvent(component, component.$subs[i], cx); 2861 } 2862 } 2863 } 2864 2865 // Search all child Components and trigger the unmount event 2866 component.getSlots(function (slot) { 2867 return slot.isProperty() && slot.getType().isComponent(); 2868 }).each(function (slot) { 2869 removePropagateUnmountEvent(this.get(slot), cx); 2870 }); 2871 } 2872 2873 /** 2874 * Remove the dynamic Slot by the specified name. 2875 * <p> 2876 * If the Complex is mounted, this will <strong>asynchronously</strong> remove 2877 * the Property from the Component on the Server. 2878 * <p> 2879 * The Slot, Slot name, a Complex or an Object Literal can be used for the method's arguments... 2880 * <pre> 2881 * myObj.remove("foo"); 2882 * 2883 * //...or via the Slot itself... 2884 * 2885 * myObj.remove(theFooSlot); 2886 * 2887 * //...or remove the Complex instance from the parent... 2888 * 2889 * myObj.remove(aComplexInstance); 2890 * 2891 * //... of if more arguments are needed then via Object Literal notation... 2892 * 2893 * myObj.remove({ 2894 * slot: "foo", 2895 * ok: function () { 2896 * // Called once the Property has been removed (optional) 2897 * }, 2898 * fail: function (err) { 2899 * // Called if the Property fails to remove (optional) 2900 * }, 2901 * batch // if defined, any network calls will be batched into this object (optional) 2902 * }); 2903 * </pre> 2904 * For callbacks, the 'this' keyword is set to the Component instance. 2905 * 2906 * @param {baja.Slot|String|Object} obj the Slot, Slot name, Complex instance or an Object Literal. 2907 * @param {String} obj.slot the Slot, Slot name or Complex instance to remove. 2908 * @param {Function} [obj.ok] the ok callback. This function is called once the Property 2909 * has been remove from the Server. 2910 * @param {Function} [obj.fail] the fail callback. This function is called if the Property 2911 * fails to remove. Any error information is passed into this function. 2912 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 2913 * @param [obj.cx] the Context (used internally by BajaScript). 2914 */ 2915 baja.Component.prototype.remove = function (obj) { 2916 obj = objectify(obj, "slot"); 2917 2918 var slot = obj.slot, 2919 cx = obj.cx, 2920 serverDecode = cx && cx.serverDecode, 2921 commit = cx && cx.commit, 2922 cb = new Callback(obj.ok, obj.fail, obj.batch); 2923 2924 // Ensure 'this' is Component in callbacks... 2925 setContextInOkCallback(this, cb); 2926 setContextInFailCallback(this, cb); 2927 2928 try { 2929 strictArg(slot); 2930 2931 var val = null; 2932 if (baja.hasType(slot) && slot.getType().isComplex()) { 2933 val = slot; 2934 slot = slot.getPropertyInParent(); 2935 } 2936 else { 2937 slot = this.getSlot(slot); 2938 } 2939 2940 // Short circuit some of this on a Server decode 2941 if (!serverDecode) { 2942 if (slot === null) { 2943 throw new Error("Invalid slot for Component remove"); 2944 } 2945 if (!slot.isProperty() || slot.isFrozen()) { 2946 throw new Error("Cannot remove Slot that isn't a dynamic Property: " + slot.getName()); 2947 } 2948 2949 // Subclass check 2950 if (typeof this.checkRemove === "function") { 2951 this.checkRemove(slot, cx); 2952 } 2953 } 2954 2955 // Check if this is a proxy. If so then trap it... 2956 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 2957 this.$space.getCallbacks().remove(this, slot, cb); 2958 return; 2959 } 2960 2961 // TODO: Remove links? 2962 2963 if (val === null) { 2964 val = this.get(slot); 2965 } 2966 2967 // Unparent 2968 if (val.getType().isComplex()) { 2969 2970 // Unmount from Component Space 2971 if (val.getType().isComponent()) { 2972 // Trigger unmount event to this component and all child Components just before it's properly removed 2973 removePropagateUnmountEvent(val, cx); 2974 2975 // If we have a Component then attempt to unmount it 2976 // (includes unregistering from subscription) 2977 if (this.isMounted()) { 2978 this.$space.$fw("unmount", val); 2979 } 2980 } 2981 2982 val.$parent = null; 2983 val.$propInParent = null; 2984 } 2985 2986 // Remove the Component from the Slot Map 2987 this.$map.remove(slot.getName()); 2988 2989 // Fire Component Event 2990 fwCompEvent(this, REMOVED, slot, val, null, cx); 2991 2992 cb.ok(); 2993 } 2994 catch (err) { 2995 cb.fail(err); 2996 } 2997 }; 2998 2999 /** 3000 * Rename the specified dynamic Slot. 3001 * <p> 3002 * If the Complex is mounted, this will <strong>asynchronously</strong> rename 3003 * the Slot in the Component on the Server. 3004 * <p> 3005 * An Object Literal is used for the method's arguments... 3006 * <pre> 3007 * myObj.rename({ 3008 * slot: "foo", 3009 * newName: "boo", 3010 * ok: function () { 3011 * // Called once the Slot has been renamed (optional) 3012 * }, 3013 * fail: function (err) { 3014 * // Called if the Slot fails to rename (optional) 3015 * }, 3016 * batch // if defined, any network calls will be batched into this object (optional) 3017 * }); 3018 * </pre> 3019 * For callbacks, the 'this' keyword is set to the Component instance. 3020 * 3021 * @param {Object} obj the Object Literal used for the method's arguments. 3022 * @param {baja.Slot|String} obj.slot the dynamic Slot or dynamic Slot name that will be renamed 3023 * @param {String} obj.newName the new name of the Slot. 3024 * The name must meet the "name" production in the 3025 * SlotPath BNF grammar. Informally this means that 3026 * the name must start with an ascii letter, and 3027 * contain only ascii letters, ascii digits, or '_'. 3028 * Escape sequences can be specified using the '$' char. 3029 * Use baja.SlotPath.escape() to escape illegal characters. 3030 * @param {Function} [obj.ok] the ok callback. This function is called once the Slot 3031 * has been renamed. 3032 * @param {Function} [obj.fail] the fail callback. This function is called if the Slot 3033 * fails to rename. Any error information is passed into this function. 3034 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3035 * @param [obj.cx] the Context (used internally by BajaScript). 3036 */ 3037 baja.Component.prototype.rename = function (obj) { 3038 obj = objectify(obj); 3039 3040 var slot = obj.slot, 3041 newName = obj.newName, 3042 cx = obj.cx, 3043 serverDecode = cx && cx.serverDecode, 3044 commit = cx && cx.commit, 3045 cb = new Callback(obj.ok, obj.fail, obj.batch); 3046 3047 // Ensure 'this' is Component in callbacks... 3048 setContextInOkCallback(this, cb); 3049 setContextInFailCallback(this, cb); 3050 3051 try { 3052 var s = this.getSlot(slot); 3053 3054 if (s === null) { 3055 throw new Error("Cannot rename. Slot doesn't exist: " + slot + " -> " + newName); 3056 } 3057 3058 // Short circuit some of these checks on a Server decode 3059 if (!serverDecode) { 3060 strictArg(s, baja.Slot); 3061 strictArg(newName, String); 3062 3063 baja.SlotPath.verifyValidName(newName); 3064 3065 if (s.isFrozen()) { 3066 throw new Error("Cannot rename frozen Slot: " + slot + " -> " + newName); 3067 } 3068 if (this.getSlot(newName) !== null) { 3069 throw new Error("Cannot rename. Slot name already used: " + slot + " -> " + newName); 3070 } 3071 3072 // Subclass check 3073 if (typeof this.checkRename === "function") { 3074 this.checkRename(s, newName, cx); 3075 } 3076 } 3077 3078 // Record the old name 3079 var oldName = s.getName(); 3080 3081 // Check if this is a proxy. If so then trap it... 3082 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 3083 this.$space.getCallbacks().rename(this, oldName, newName, cb); 3084 return; 3085 } 3086 3087 // Rename the Component from the Slot Map 3088 if (!this.$map.rename(oldName, newName)) { 3089 throw new Error("Cannot rename: " + oldName + " -> " + newName); 3090 } 3091 3092 s.$slotName = newName; 3093 3094 if (cx) { 3095 if (typeof cx.displayName === "string") { 3096 s.$setDisplayName(cx.displayName); 3097 } 3098 if (typeof cx.display === "string") { 3099 s.$setDisplay(cx.display); 3100 } 3101 } 3102 3103 // Fire Component Event 3104 fwCompEvent(this, RENAMED, s, null, oldName, cx); 3105 3106 cb.ok(); 3107 } 3108 catch (err) { 3109 cb.fail(err); 3110 } 3111 }; 3112 3113 /** 3114 * Reorder the Component's dynamic Properties. 3115 * <p> 3116 * If the Complex is mounted, this will <strong>asynchronously</strong> reorder 3117 * the dynamic Properties in the Component on the Server. 3118 * <p> 3119 * An Property array or an Object Literal can used for the method's arguments... 3120 * <pre> 3121 * // Order via an array of Properties... 3122 * myObj.reorder([booProp, fooProp, dooProp]); 3123 * 3124 * // ...or order via an array of Property names... 3125 * myObj.reorder(["boo", "foo", "doo"]); 3126 * 3127 * // ...or for more arguments, use an Object Literal... 3128 * myObj.reorder({ 3129 * dynamicProperties: [booProp, fooProp, dooProp], // Can also be a Property name array! 3130 * ok: function () { 3131 * // Called once the Properties have been reordered (optional) 3132 * }, 3133 * fail: function (err) { 3134 * // Called if the Slot fails to reorder (optional) 3135 * }, 3136 * batch // if defined, any network calls will be batched into this object (optional) 3137 * }); 3138 * </pre> 3139 * For callbacks, the 'this' keyword is set to the Component instance. 3140 * 3141 * @param {Array|Object} obj the array of Properties, Property names or an Object Literal used for the method's arguments. 3142 * @param {Array} obj.dynamicProperties an array of Properties or Property names for the slot order. 3143 * @param {Function} [obj.ok] the ok callback. This function is called once the dynamic Properties have 3144 * been reordered. 3145 * @param {Function} [obj.fail] the fail callback. This function is called if the dynamic Properties fail to reorder. 3146 * Any error information is passed into this function. 3147 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3148 * @param [obj.cx] the Context (used internally by BajaScript). 3149 */ 3150 baja.Component.prototype.reorder = function (obj) { 3151 obj = objectify(obj, "dynamicProperties"); 3152 3153 var dynamicProperties = obj.dynamicProperties, 3154 cx = obj.cx, 3155 serverDecode = cx && cx.serverDecode, 3156 commit = cx && cx.commit, 3157 cb = new Callback(obj.ok, obj.fail, obj.batch), 3158 i; 3159 3160 // Ensure 'this' is Component in callbacks... 3161 setContextInOkCallback(this, cb); 3162 setContextInFailCallback(this, cb); 3163 3164 // If this is a commit and the component is mounted then only process if the component is subscribed and fully loaded. 3165 // Otherwise it probably won't work 3166 if (commit && this.isMounted() && !(this.isSubscribed() && this.$bPropsLoaded)) { 3167 cb.ok(); 3168 return; 3169 } 3170 3171 try { 3172 // Verify the array contents 3173 if (!serverDecode) { 3174 strictArg(dynamicProperties, Array); 3175 } 3176 3177 var preGetSlot; 3178 for (i = 0; i < dynamicProperties.length; ++i) { 3179 preGetSlot = dynamicProperties[i]; 3180 dynamicProperties[i] = this.getSlot(dynamicProperties[i]); 3181 3182 if (!dynamicProperties[i]) { 3183 throw new Error("Could not find dynamic Property to reorder: " + preGetSlot + " in " + this.toPathString()); 3184 } 3185 if (dynamicProperties[i].isFrozen()) { 3186 throw new Error("Cannot reorder frozen Properties"); 3187 } 3188 } 3189 3190 var currentDynProps = this.getSlots().dynamic().properties(), 3191 dynPropCount = 0; 3192 while (currentDynProps.next()) { 3193 ++dynPropCount; 3194 } 3195 if (dynPropCount === 0) { 3196 throw new Error("Cannot reorder. No dynamic Props!"); 3197 } 3198 if (dynPropCount !== dynamicProperties.length) { 3199 throw new Error("Cannot reorder. Actual count: " + dynPropCount + " != " + dynamicProperties.length); 3200 } 3201 3202 // Subclass check 3203 if (!serverDecode && typeof this.checkReorder === "function") { 3204 this.checkReorder(dynamicProperties, cx); 3205 } 3206 3207 // Check if this is a proxy. If so then trap it... 3208 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 3209 this.$space.getCallbacks().reorder(this, dynamicProperties, cb); 3210 return; 3211 } 3212 3213 // Get a copy of the keys used in the SlotMap 3214 var keys = this.$map.getKeys(); 3215 3216 var getKeyIndex = function (k) { 3217 var i; 3218 for (i = 0; i < keys.length; ++i) { 3219 if (k === keys[i]) { 3220 return i; 3221 } 3222 } 3223 throw new Error("Could not find Property in reorder: " + k); 3224 }; 3225 3226 var getNewPropsIndex = function (k) { 3227 var i; 3228 for (i = 0; i < dynamicProperties.length; ++i) { 3229 if (k === dynamicProperties[i].getName()) { 3230 return i; 3231 } 3232 } 3233 throw new Error("Could not find dynamic Property in reorder: " + k); 3234 }; 3235 3236 // Sort the map accordingly 3237 var that = this; 3238 this.$map.sort(function (a, b) { 3239 var as = that.$map.get(a); 3240 var bs = that.$map.get(b); 3241 3242 // If both Properties are frozen then just compare against the current indexes 3243 if (as.isFrozen() && bs.isFrozen()) { 3244 return getKeyIndex(a) < getKeyIndex(b) ? -1 : 1; 3245 } 3246 else if (as.isFrozen()) { 3247 return -1; 3248 } 3249 else if (bs.isFrozen()) { 3250 return 1; 3251 } 3252 else { 3253 // If both Properties are dynamic then we can order as per the new array 3254 return getNewPropsIndex(a) < getNewPropsIndex(b) ? -1 : 1; 3255 } 3256 }); 3257 3258 // Fire Component Event 3259 fwCompEvent(this, REORDERED, null, null, null, cx); 3260 3261 cb.ok(); 3262 } 3263 catch (err) { 3264 cb.fail(err); 3265 } 3266 }; 3267 3268 var invalidActionArgErrMsg = "Invalid Action Argument: "; 3269 3270 /** 3271 * Invoke an Action. 3272 * <p> 3273 * If the Complex is mounted, this will <strong>asynchronously</strong> invoke 3274 * the Action on the Component in the Server. 3275 * <p> 3276 * A Slot, Slot name or an Object Literal can used for the method's arguments... 3277 * <pre> 3278 * // Invoke the Action via its Action Slot... 3279 * myObj.invoke(fooAction); 3280 * 3281 * // ...or via the Action's Slot name... 3282 * myObj.invoke("foo"); 3283 * 3284 * // ...or for more arguments, use an Object Literal... 3285 * myObj.invoke({ 3286 * slot: actionSlot, // Can also be an Action Slot name 3287 * value: "the Action's argument", 3288 * ok: function (returnValue) { 3289 * // Called once the Action has been invoked (optional) 3290 * }, 3291 * fail: function (err) { 3292 * // Called if the Action fails to invoke (optional) 3293 * }, 3294 * batch // if defined, any network calls will be batched into this object (optional) 3295 * }); 3296 * </pre> 3297 * For callbacks, the 'this' keyword is set to the Component instance. 3298 * <p> 3299 * Please note that auto-generated convenience methods are created and added to a Component for invoking 3300 * frozen Actions... 3301 * <pre> 3302 * // Invoke an Action called 'override'. Pass in an argument 3303 * myPoint.override(overrideVal); 3304 * 3305 * // ...or via an Object Literal for more arguments... 3306 * myPoint.override({ 3307 * value: overrideVal, 3308 * ok: function (returnValue) { 3309 * // Called once the Action has been invoked (optional) 3310 * }, 3311 * fail: function (err) { 3312 * // Called if the Action fails to invoke (optional) 3313 * }, 3314 * batch // if defined, any network calls will be batched into this object (optional) 3315 * }); 3316 * </pre> 3317 * If the name of the auto-generated Action method is already used, BajaScript will attach a number to the end 3318 * of the method name so it becomes unique. For example, the 'set' Action on a NumericWritable would be called 'set1' 3319 * because Component already has a 'set' method. 3320 * 3321 * @param {baja.Action|String|Object} obj the Action, Action name or Object Literal for the method's arguments. 3322 * @param {baja.Action|String} obj.slot the Action or Action name. 3323 * @param [obj.value] the Action's argument. 3324 * @param {Function} [obj.ok] the ok callback. This function is called once Action has been invoked. 3325 * If the Action has a returned argument, this will be passed to this function. 3326 * @param {Function} [obj.fail] the fail callback. This function is called if the Action fails to invoke. 3327 * Any error information is passed into this function. 3328 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3329 * @param [obj.cx] the Context (used internally by BajaScript). 3330 */ 3331 baja.Component.prototype.invoke = function (obj) { 3332 obj = objectify(obj, "slot"); 3333 3334 var action = obj.slot, 3335 arg = bajaDef(obj.value, null), 3336 cx = obj.cx, 3337 retVal = null, 3338 cb = new Callback(obj.ok, obj.fail, obj.batch), 3339 flags = 0, 3340 paramType; 3341 3342 // Ensure 'this' is Component in callbacks... 3343 setContextInOkCallback(this, cb); 3344 setContextInFailCallback(this, cb); 3345 3346 try { 3347 action = this.getSlot(action); 3348 3349 if (action === null) { 3350 throw new Error("Action does not exist: " + obj.slot); 3351 } 3352 3353 // Get Slot flags so we can test for 'async' 3354 flags = this.getFlags(action); 3355 paramType = action.getParamType(); 3356 3357 // Check we have a valid argument for this Action 3358 if (paramType) { 3359 if (baja.hasType(arg)) { 3360 if (arg.getType().isNumber() && paramType.isNumber() && !arg.getType().equals(paramType)) { 3361 // Recreate the number with the correct boxed type if the type spec differs 3362 arg = paramType.getInstance().constructor.make(arg.valueOf()); 3363 } 3364 else if (!arg.getType().is(paramType)) { 3365 throw new Error(invalidActionArgErrMsg + arg); 3366 } 3367 } 3368 else { 3369 throw new Error(invalidActionArgErrMsg + arg); 3370 } 3371 } 3372 } 3373 catch (err) { 3374 // Notify fail 3375 cb.fail(err); 3376 3377 // We should ALWAYS bail after calling a callback fail! 3378 return; 3379 } 3380 3381 var that = this; 3382 function inv() { 3383 try { 3384 if (that.isMounted() && that.$space.hasCallbacks()) { 3385 // If mounted then make a network call for the Action invocation 3386 that.$space.getCallbacks().invokeAction(that, action, arg, cb); 3387 return; 3388 } 3389 3390 if (!action.isProperty()) { 3391 // Invoke do method of Action 3392 var s = "do" + action.getName().capitalizeFirstLetter(); 3393 if (typeof that[s] === "function") { 3394 // Invoke but ensure null is returned if the function returns undefined 3395 retVal = bajaDef(that[s](arg, cx), null); 3396 } 3397 else { 3398 throw new Error("Could not find do method for Action: " + action.getName()); 3399 } 3400 } 3401 else { 3402 // If the Action is also a Property then forward its invocation on to the value 3403 retVal = bajaDef(that.get(action).invoke(that, arg, cx), null); 3404 } 3405 3406 cb.ok(retVal); 3407 } 3408 catch (err) { 3409 cb.fail(err); 3410 } 3411 } 3412 3413 if ((flags & baja.Flags.ASYNC) !== baja.Flags.ASYNC) { 3414 inv(); 3415 } 3416 else { 3417 baja.runAsync(inv); 3418 } 3419 3420 return retVal; 3421 }; 3422 3423 /** 3424 * Fire a Topic. 3425 * <p> 3426 * If the Complex is mounted, this will <strong>asynchronously</strong> fire 3427 * the Topic on the Component in the Server. 3428 * <p> 3429 * A Slot, Slot name or an Object Literal can used for the method's arguments... 3430 * <pre> 3431 * // Fire the Topic via its Topic Slot... 3432 * myObj.fire(fooTopic); 3433 * 3434 * // ...or via the Topic's Slot name... 3435 * myObj.fire("foo"); 3436 * 3437 * // ...or for more arguments, use an Object Literal... 3438 * myObj.fire({ 3439 * slot: topicSlot, // Can also be a Topic Slot name 3440 * value: "the Topic event argument", 3441 * ok: function () { 3442 * // Called once the Topic has been fired (optional) 3443 * }, 3444 * fail: function (err) { 3445 * // Called if the Topic fails to fire (optional) 3446 * }, 3447 * batch // if defined, any network calls will be batched into this object (optional) 3448 * }); 3449 * </pre> 3450 * <p> 3451 * Please note that auto-generated convenience methods are created and added to a Component for firing 3452 * frozen Topics... 3453 * <pre> 3454 * // Fire a Topic called 'foo' 3455 * myObj.fireFoo(); 3456 * 3457 * // Fire a Topic called foo with an event argument... 3458 * myObj.fireFoo("the Topic event argument"); 3459 * 3460 * // ...or via an Object Literal for more arguments... 3461 * myObj.fireFoo({ 3462 * value: "the Topic event argument", 3463 * ok: function () { 3464 * // Called once the Topic has been fired (optional) 3465 * }, 3466 * fail: function (err) { 3467 * // Called if the Topic fails to fire (optional) 3468 * }, 3469 * batch // if defined, any network calls will be batched into this object (optional) 3470 * }); 3471 * </pre> 3472 * If the name of the auto-generated Topic method is already used, BajaScript will attach a number to the end 3473 * of the method name so it becomes unique. 3474 * <p> 3475 * For callbacks, the 'this' keyword is set to the Component instance. 3476 * 3477 * @param {baja.Action|String|Object} obj the Topic, Topic name or Object Literal for the method's arguments. 3478 * @param {baja.Action|String} obj.slot the Topic or Topic name. 3479 * @param [obj.value] the Topic's event. 3480 * @param {Function} [obj.ok] the ok callback. This function is called once Topic has been fired. 3481 * @param {Function} [obj.fail] the fail callback. This function is called if the Topic fails to fire. 3482 * Any error information is passed into this function. 3483 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3484 * @param [obj.cx] the Context (used internally by BajaScript). 3485 */ 3486 baja.Component.prototype.fire = function (obj) { 3487 obj = objectify(obj, "slot"); 3488 3489 var topic = obj.slot, 3490 event = obj.value, 3491 cx = obj.cx, 3492 serverDecode = cx && cx.serverDecode, 3493 commit = cx && cx.commit, 3494 cb = new Callback(obj.ok, obj.fail, obj.batch); 3495 3496 // Ensure 'this' is Component in callbacks... 3497 setContextInOkCallback(this, cb); 3498 setContextInFailCallback(this, cb); 3499 3500 try { 3501 // Ensure we have a Topic Slot 3502 topic = this.getSlot(topic); 3503 if (!topic.isTopic()) { 3504 throw new Error("Slot is not a Topic: " + topic.getName()); 3505 } 3506 3507 // Ensure event is not undefined 3508 event = bajaDef(event, null); 3509 3510 // Short circuit some of this on a Server decode 3511 if (!serverDecode) { 3512 // Validate the event 3513 if (event !== null) { 3514 if (!baja.hasType(event)) { 3515 throw new Error("Topic event is not a BValue"); 3516 } 3517 if (event.getType().isAbstract()) { 3518 throw new Error("Topic event is has abstract Type: " + event.getType()); 3519 } 3520 if (!event.getType().isValue()) { 3521 throw new Error("Topic event is not a BValue: " + event.getType()); 3522 } 3523 } 3524 } 3525 3526 // Check if this is a proxy. If so then trap it... 3527 if (!commit && this.isMounted() && this.$space.hasCallbacks()) { 3528 this.$space.getCallbacks().fire(this, topic, event, cb); 3529 return; 3530 } 3531 3532 // If the Topic is a Property then fire on the Property 3533 if (topic.isProperty()) { 3534 this.get(topic).fire(this, event, cx); 3535 } 3536 3537 // Fire component event for Topic 3538 fwCompEvent(this, TOPIC_FIRED, topic, event, null, cx); 3539 } 3540 catch (err) { 3541 cb.fail(err); 3542 } 3543 }; 3544 3545 /** 3546 * Internal Framework Method. 3547 * 3548 * @private 3549 * 3550 * @see baja.Complex#$fw 3551 */ 3552 baja.Component.prototype.$fw = function (x, a, b, c, d) { 3553 3554 if (x === "modified") { 3555 // Fire a Component modified event for Property changed 3556 fwCompEvent(this, CHANGED, a, null, null, b); 3557 return; 3558 } 3559 else if (x === "fwSubscribed") { 3560 fwCompEvent(this, SUBSCRIBED, null, null, null, b); 3561 return; 3562 } 3563 else if (x === "fwUnsubscribed") { 3564 fwCompEvent(this, UNSUBSCRIBED, null, null, null, b); 3565 return; 3566 } 3567 else if (x === "modifyTrap") { 3568 // Check if this is a proxy. If so then trap any modifications 3569 if (this.isMounted() && this.$space.hasCallbacks() && !(d && d.commit)) { 3570 this.$space.getCallbacks().set(this, 3571 a, // propertyPath 3572 b, // value 3573 c); // callback 3574 return true; 3575 } 3576 else { 3577 return false; 3578 } 3579 } 3580 else if (x === "installKnob") { 3581 // Add a knob to the Component 3582 this.$knobs = this.$knobs || {}; 3583 this.$knobCount = this.$knobCount || 0; 3584 3585 this.$knobs[a.getId()] = a; 3586 ++this.$knobCount; 3587 3588 fwCompEvent(this, KNOB_ADDED, this.getSlot(a.getSourceSlotName()), /*a=Knob*/a, null, /*b=Context*/b); 3589 return; 3590 } 3591 else if (x === "uninstallKnob") { 3592 // Remove the knob from the Component 3593 if (this.$knobs && this.$knobs.hasOwnProperty(a)) { 3594 var k = this.$knobs[a]; 3595 delete this.$knobs[a]; 3596 --this.$knobCount; 3597 3598 fwCompEvent(this, KNOB_REMOVED, /*b=Slot name*/this.getSlot(b), k, null, /*c=Context*/c); 3599 } 3600 return; 3601 } 3602 else if (x === "setPermissions") { 3603 // Set the permissions on the Component 3604 this.$permissionsStr = a; 3605 3606 // Nullify any decoded permissions 3607 this.$permissions = null; 3608 } 3609 3610 return baja.Component.$super.prototype.$fw.apply(this, arguments); 3611 }; 3612 3613 /** 3614 * Return true if the Component is mounted inside a Space. 3615 * 3616 * @returns {Boolean} 3617 */ 3618 baja.Component.prototype.isMounted = function () { 3619 return this.$space !== null; 3620 }; 3621 3622 /** 3623 * Return the Component Space. 3624 * 3625 * @returns the Component Space for this Component (if mounted) otherwise return null. 3626 */ 3627 baja.Component.prototype.getComponentSpace = function () { 3628 return this.$space; 3629 }; 3630 3631 /** 3632 * Return the Component's handle. 3633 * 3634 * @returns {String} handle for this Component (if mounted) otherwise return null. 3635 */ 3636 baja.Component.prototype.getHandle = function () { 3637 return this.$handle; 3638 }; 3639 3640 /** 3641 * Return the ORD in session for this Component. 3642 * 3643 * @returns {baja.Ord} ORD in Session for this Component (or null if not mounted). 3644 */ 3645 baja.Component.prototype.getOrdInSession = function () { 3646 return this.getHandle() === null ? null : baja.Ord.make("station:|h:" + this.getHandle()); 3647 }; 3648 3649 var unlease = function () { 3650 if (!this.$lease) { 3651 return; 3652 } 3653 3654 // Are currently subscribed? 3655 var prevSub = this.isSubscribed(); 3656 this.$lease = false; 3657 this.$leaseTicket.cancel(); 3658 3659 // If the Component is unsubscribed but was previously subscribed then make a network call 3660 if (!this.isSubscribed() && prevSub && this.isMounted() && this.$space.hasCallbacks()) { 3661 this.$space.getCallbacks().unsubscribe(["h:" + this.$handle], new Callback()); 3662 } 3663 }; 3664 3665 /** 3666 * Subscribe a number of Components for a period of time. 3667 * <p> 3668 * The default period of time is 10 seconds. 3669 * <p> 3670 * Please note that a {@link baja.Subscriber} can also be used to put a Component into 3671 * and out of subscription. 3672 * <p> 3673 * If the Component is mounted and it can be subscribed, this will result in 3674 * an <strong>asynchronous</strong> network call. 3675 * <p> 3676 * If any of the the Components are already leased, the lease timer will just be renewed. 3677 * <p> 3678 * A time (Number or baja.RelTime) or an Object Literal can be used to specify the method's arguments... 3679 * <pre> 3680 * // Lease an array of Components for the default time period 3681 * myComp.lease([comp1, comp2, comp3]); 3682 * 3683 * // ...or lease for 2 and half minutes... 3684 * myComp.lease({ 3685 * time: baja.RelTime.make({minutes: 2, seconds: 30}), 3686 * comps: [comp1, comp2, comp3] 3687 * }); 3688 * </pre> 3689 * For callbacks, the 'this' keyword is set to whatever the 'comps' argument was originally set to. 3690 * 3691 * @see baja.Subscriber 3692 * @see baja.Component#lease 3693 * 3694 * @param {Object} obj an Object Literal for the method's arguments. 3695 * @param {Array|baja.Component} obj.comps the Components to be subscribed. 3696 * @param {Function} [obj.ok] the ok callback. Called once the Component has been unsubscribed. 3697 * @param {Function} [obj.fail] the fail callback. Called if the Component fails to unsubscribe. 3698 * Any errors will be passed to this function. 3699 * @param {Number|RelTime} [obj.time] the number of milliseconds or RelTime for the lease. 3700 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3701 * The timer will only be started once the batch has fully committed. 3702 */ 3703 baja.Component.lease = function (obj) { 3704 obj = objectify(obj, "comps"); 3705 var cb = new Callback(obj.ok, obj.fail, obj.batch); 3706 3707 function scheduleUnlease(comp, time) { 3708 // Cancel the current lease ticket 3709 comp.$leaseTicket.cancel(); 3710 3711 // Schedule to expire the Subscription in 60 seconds 3712 comp.$leaseTicket = baja.clock.schedule(function () { 3713 unlease.call(comp); 3714 }, time); 3715 } 3716 3717 try { 3718 var comps = obj.comps; 3719 3720 if (!comps) { 3721 throw new Error("Must specify Components for lease"); 3722 } 3723 3724 // Ensure 'comps' is used as the context in the callback... 3725 setContextInOkCallback(comps, cb); 3726 setContextInFailCallback(comps, cb); 3727 3728 // Get the lease time 3729 var time = bajaDef(obj.time, /*10 seconds*/10000); 3730 3731 // If a rel time then get the number of milliseconds from it 3732 if (baja.hasType(time) && time.getType().is("baja:RelTime")) { 3733 time = time.getMillis(); 3734 } 3735 3736 strictArg(time, Number); 3737 if (time < 1) { 3738 throw new Error("Invalid lease time (time must be > 0 ms): " + time); 3739 } 3740 3741 if (!(comps instanceof Array)) { 3742 comps = [comps]; 3743 } 3744 3745 if (comps.length === 0) { 3746 cb.ok(); 3747 return; 3748 } 3749 3750 var space = null, i; 3751 for (i = 0; i < comps.length; ++i) { 3752 // Check all Components are valid 3753 strictArg(comps[i], baja.Component); 3754 if (!comps[i].isMounted()) { 3755 throw new Error("Cannot subscribe unmounted Component!"); 3756 } 3757 if (!space) { 3758 space = comps[i].getComponentSpace(); 3759 } 3760 if (space !== comps[i].getComponentSpace()) { 3761 throw new Error("All Components must belong to the same Component Space!"); 3762 } 3763 } 3764 3765 cb.addOk(function (ok, fail) { 3766 var x; 3767 for (x = 0; x < comps.length; ++x) { 3768 scheduleUnlease(comps[x], time); 3769 } 3770 3771 ok(); 3772 }); 3773 3774 // Build handles we want to subscribe 3775 var handles = [], 3776 compsToSub = [], 3777 j; 3778 3779 for (j = 0; j < comps.length; ++j) { 3780 // If already subscribed then don't bother making a network call for these 3781 if (!comps[j].isSubscribed()) { 3782 handles.push("h:" + comps[j].getHandle()); 3783 compsToSub.push(comps[j]); 3784 } 3785 comps[j].$lease = true; 3786 } 3787 3788 // If there's currently a lease active then renew it by calling ok on the callback 3789 if (handles.length === 0 || !space.hasCallbacks()) { 3790 cb.ok(); 3791 return; 3792 } 3793 3794 // Signal that each Component has been subscribed 3795 cb.addOk(function (ok, fail, resp) { 3796 var i; 3797 for (i = 0; i < compsToSub.length; ++i) { 3798 try { 3799 compsToSub[i].$fw("fwSubscribed"); 3800 } 3801 catch (err) { 3802 baja.error(err); 3803 } 3804 } 3805 3806 ok(); 3807 }); 3808 3809 // Make network call for subscription 3810 space.getCallbacks().subscribe(handles, cb, obj.importAsync); 3811 } 3812 catch (err) { 3813 cb.fail(err); 3814 } 3815 }; 3816 3817 /** 3818 * Subscribe a Component for a period of time. 3819 * <p> 3820 * The default lease time is 10 seconds. 3821 * <p> 3822 * Please note that a {@link baja.Subscriber} can also be used to put a Component into 3823 * and out of subscription. 3824 * <p> 3825 * If the Component is mounted and it can be subscribed, this will result in 3826 * an <strong>asynchronous</strong> network call. 3827 * <p> 3828 * If lease is called while the Component is already leased, the timer will just be renewed. 3829 * <p> 3830 * A time (Number or baja.RelTime) or an Object Literal can be used to specify the method's arguments... 3831 * <pre> 3832 * // Lease for 15 seconds 3833 * myComp.lease(15000); 3834 * 3835 * // ...or lease for 2 and half minutes... 3836 * myComp.lease(baja.RelTime.make({minutes: 2, seconds: 30})); 3837 * 3838 * // ...or lease using an Object Literal for more arguments... 3839 * myComp.lease({ 3840 * time: 1000, // in milliseconds. Can also be a RelTime. 3841 * ok: function () { 3842 * // Called once the Component is subscribed (optional) 3843 * }, 3844 * fail: function (err) { 3845 * // Called if the Component failed to subscribe (optional) 3846 * }, 3847 * batch // if defined, any network calls will be batched into this object (optional) 3848 * }); 3849 * </pre> 3850 * For callbacks, the 'this' keyword is set to the Component instance. 3851 * 3852 * @see baja.Subscriber 3853 * @see baja.Component.lease 3854 * 3855 * @param {Number|baja.RelTime|Object} [obj] the number of milliseconds, RelTime or an Object Literal 3856 * for the method's arguments. 3857 * @param {Function} [obj.ok] the ok callback. Called once the Component has been unsubscribed. 3858 * @param {Function} [obj.fail] the fail callback. Called if the Component fails to unsubscribe. 3859 * Any errors will be passed to this function. 3860 * @param {Number|RelTime} [obj.time] the number of milliseconds or RelTime for the lease. 3861 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3862 * The timer will only be started once the batch has fully committed. 3863 */ 3864 baja.Component.prototype.lease = function (obj) { 3865 obj = objectify(obj, "time"); 3866 obj.comps = this; 3867 baja.Component.lease(obj); 3868 }; 3869 3870 /** 3871 * Is the Component subscribed? 3872 * 3873 * @returns {Boolean} 3874 */ 3875 baja.Component.prototype.isSubscribed = function () { 3876 // Component is subscribed if is leased or a Subscriber is registered on it 3877 return this.$lease || this.$subs.length > 0; 3878 }; 3879 3880 /** 3881 * Load all of the Slots on the Component. 3882 * 3883 * If the Component is mounted and it can be loaded, this will result in 3884 * an <strong>asynchronous</strong> network call. 3885 * <p> 3886 * An optional Object Literal can be used to specify the method's arguments... 3887 * <pre> 3888 * myComp.loadSlots({ 3889 * ok: function () { 3890 * // Called once the Component is loaded (optional) 3891 * }, 3892 * fail: function (err) { 3893 * // Called if the Component failed to load (optional) 3894 * }, 3895 * batch // if defined, any network calls will be batched into this object (optional) 3896 * }); 3897 * </pre> 3898 * For callbacks, the 'this' keyword is set to the Component instance. 3899 * 3900 * @param {Function} [obj.ok] the ok callback. Called once the Component has been loaded. 3901 * @param {Function} [obj.fail] the fail callback. Called if the Component fails to load. 3902 * Any errors will be passed to this function. 3903 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3904 */ 3905 baja.Component.prototype.loadSlots = function (obj) { 3906 obj = objectify(obj); 3907 3908 var cb = obj.cb; 3909 if (!cb) { 3910 cb = new Callback(obj.ok, obj.fail, obj.batch); 3911 } 3912 3913 // Ensure 'this' is Component in callbacks... 3914 setContextInOkCallback(this, cb); 3915 setContextInFailCallback(this, cb); 3916 3917 try { 3918 if (!this.$bPropsLoaded && this.isMounted() && this.$space.hasCallbacks()) { 3919 this.$space.getCallbacks().loadSlots("h:" + this.getHandle(), 0, cb); 3920 } 3921 else { 3922 cb.ok(); 3923 } 3924 } 3925 catch (err) { 3926 cb.fail(err); 3927 } 3928 }; 3929 3930 /** 3931 * Make a Server Side Call. 3932 * <p> 3933 * Sometimes it's useful to invoke a method on the server from BajaScript. 3934 * A Server Side Call is how this is achieved. 3935 * <p> 3936 * This will result in an <strong>asynchronous</strong> network call. 3937 * <p> 3938 * In order to make a Server Side Call, the developer needs to first create a 3939 * Niagara (Server Side) class that implements the box:javax.baja.box.BIServerSideCallHandler interface. 3940 * The implementation should also declare itself as an Agent on the target Component Type (more information in Java interface docs). 3941 * Here's an example of how a method implemented by this handler can be invoked... 3942 * <pre> 3943 * // A resolved and mounted Component... 3944 * myComp.serverSideCall({ 3945 * typeSpec: "foo:MyServerSideCallHandler", // The TypeSpec (moduleName:typeName) of the Server Side Call Handler 3946 * methodName: "bar", // The name of the public method we wish to invoke in the handler 3947 * value: "the argument for the method", // The argument to pass into the method (this can be any Baja Object/Component structure). 3948 * It will be deserialized automatically by Niagara. 3949 * ok: function (returnVal) { 3950 * // Called once the method has been executed on the Server (optional) 3951 * }, 3952 * fail: function (err) { 3953 * // Called if the method throws an exception on the Server or can't execute for some reason (optional) 3954 * }, 3955 * batch // if defined, any network calls will be batched into this object (optional) 3956 * }); 3957 * <\pre> 3958 * For callbacks, the 'this' keyword is set to the Component instance. 3959 * 3960 * @param {Object} obj the Object Literal for the method's arguments. 3961 * @param {String} obj.typeSpec the type specification of the Server Side Call Handler (moduleName:typeName). 3962 * @param {String} obj.methodName the name of the method to invoke in the Server Side Call Handler 3963 * @param obj.value the value for the server side method argument (must be a BajaScript Type) 3964 * @param {Function} [obj.ok] the ok callback. Called once the Server Side Call has been invoked. 3965 * Any return value is passed to this function. 3966 * @param {Function} [obj.fail] the fail callback. Called if the Component fails to load. 3967 * Any errors will be passed to this function. 3968 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 3969 */ 3970 baja.Component.prototype.serverSideCall = function (obj) { 3971 obj = objectify(obj); 3972 3973 var typeSpec = obj.typeSpec, 3974 methodName = obj.methodName, 3975 val = bajaDef(obj.value, null), 3976 cb = new Callback(obj.ok, obj.fail, obj.batch); 3977 3978 // Ensure 'this' is Component in callbacks... 3979 setContextInOkCallback(this, cb); 3980 setContextInFailCallback(this, cb); 3981 3982 try { 3983 // Check arguments 3984 strictArg(typeSpec, String); 3985 strictArg(methodName, String); 3986 strictArg(val); 3987 3988 // Can only make this call on proper mounted Components that have Space Callbacks 3989 if (this.isMounted() && this.$space.hasCallbacks()) { 3990 this.$space.getCallbacks().serverSideCall(this, typeSpec, methodName, val, cb); 3991 } 3992 else { 3993 throw new Error("Unable to make serverSideCall on non-proxy Component Space"); 3994 } 3995 } 3996 catch (err) { 3997 cb.fail(err); 3998 } 3999 }; 4000 4001 /** 4002 * Return the Slot Path of the Component. 4003 * 4004 * @returns {baja.SlotPath} the Slot Path or null if not mounted. 4005 */ 4006 baja.Component.prototype.getSlotPath = function () { 4007 if (!this.isMounted()) { 4008 return null; 4009 } 4010 4011 var slotNames = []; 4012 function getParNames(comp) { 4013 slotNames.push(comp.getName()); 4014 4015 var p = comp.getParent(); 4016 if (p !== null) { 4017 getParNames(p); 4018 } 4019 } 4020 4021 getParNames(this); 4022 var b = slotNames.reverse().join("/"); 4023 if (b.length === 0) { 4024 b = "/"; 4025 } 4026 return new baja.SlotPath(b); 4027 }; 4028 4029 /** 4030 * Return the path string of the Component. 4031 * 4032 * @returns {String} the Path String or null if not mounted. 4033 */ 4034 baja.Component.prototype.toPathString = function () { 4035 if (!this.isMounted()) { 4036 return null; 4037 } 4038 return this.getSlotPath().getBody(); 4039 }; 4040 4041 /** 4042 * Return the Nav ORD for the Component. 4043 * 4044 * @returns {baja.Ord} the Nav ORD or null if it's not mounted. 4045 */ 4046 baja.Component.prototype.getNavOrd = function () { 4047 if (!this.isMounted()) { 4048 return null; 4049 } 4050 var spaceOrd = this.$space.getAbsoluteOrd(); 4051 if (spaceOrd === null) { 4052 return null; 4053 } 4054 return baja.Ord.make(spaceOrd.toString() + "|" + this.getSlotPath().toString()); 4055 }; 4056 4057 /** 4058 * Return the Nav Name for the Component. 4059 * 4060 * @returns {String} 4061 */ 4062 baja.Component.prototype.getNavName = function () { 4063 var name = this.getName(); 4064 4065 if (name !== null) { 4066 return name; 4067 } 4068 4069 var space = this.getComponentSpace(); 4070 if (space && space.getRootComponent() !== null) { 4071 return space.getNavName(); 4072 } 4073 else { 4074 return null; 4075 } 4076 }; 4077 4078 /** 4079 * Return the Nav Display Name for the Component. 4080 * 4081 * @returns {String} 4082 */ 4083 baja.Component.prototype.getNavDisplayName = function () { 4084 return this.getDisplayName(); 4085 }; 4086 4087 /** 4088 * Return the Nav Parent for the Component. 4089 * 4090 * @returns parent Nav Node 4091 */ 4092 baja.Component.prototype.getNavParent = function () { 4093 var parent = this.getParent(); 4094 return parent || this.getComponentSpace(); 4095 }; 4096 4097 /** 4098 * Access the Nav Children for the Component. 4099 * 4100 * @see baja.NavContainer#getNavChildren 4101 */ 4102 baja.Component.prototype.getNavChildren = function (obj) { 4103 obj = objectify(obj, "ok"); 4104 4105 if (this.isMounted() && this.$space.hasCallbacks()) { 4106 // If we're mounted then make a network call to get the NavChildren since this 4107 // is always implemented Server Side 4108 this.$space.getCallbacks().getNavChildren(this.getHandle(), 4109 new Callback(obj.ok, obj.fail, obj.batch)); 4110 } 4111 else { 4112 var kids = []; 4113 this.getSlots().properties().isComponent().each(function (slot) { 4114 if ((this.getFlags(slot) & baja.Flags.HIDDEN) === 0) { 4115 kids.push(this.get(slot)); 4116 } 4117 }); 4118 obj.ok(kids); 4119 } 4120 }; 4121 4122 /** 4123 * Return the Nav Icon for the Component. 4124 * 4125 * @returns {baja.Icon} 4126 */ 4127 baja.Component.prototype.getNavIcon = function () { 4128 return this.getIcon(); 4129 }; 4130 4131 /** 4132 * Return the Nav Description for the Component. 4133 * 4134 * @returns {String} 4135 */ 4136 baja.Component.prototype.getNavDescription = function () { 4137 return this.getType().toString(); 4138 }; 4139 4140 // Mix-in the event handlers for baja.Component 4141 baja.event.mixin(baja.Component.prototype); 4142 4143 // These comments are added for the benefit of JsDoc Toolkit... 4144 4145 /** 4146 * Attach an Event Handler to this Component instance. 4147 * <p> 4148 * When an instance of Component is subscribed to a Component running 4149 * in the Station, BajaScript can be used to listen for Component Events. 4150 * For instance, a common one would be a Property changed event... 4151 * <pre> 4152 * // myPoint is a mounted and subscribed Component... 4153 * myPoint.attach("changed", function (prop, cx) { 4154 * if (prop.getName() === "out") { 4155 * baja.outln("The output of the point is: " + this.getOutDisplay()); 4156 * } 4157 * }); 4158 * </pre> 4159 * Therefore, an event handler consists of a name and a function. When the 4160 * function is called, 'this' will map to the target Component the handler 4161 * is attached too. 4162 * <p> 4163 * For a list of all the event handlers and some of this method's more advanced 4164 * features, please see {@link baja.Subscriber#attach}. 4165 * 4166 * @function 4167 * @name baja.Component#attach 4168 * 4169 * @see baja.Subscriber 4170 * @see baja.Component#detach 4171 * @see baja.Component#getHandlers 4172 * @see baja.Component#hasHandlers 4173 * 4174 * @param {String} event handler name 4175 * @param {Function} func the event handler function 4176 */ 4177 4178 /** 4179 * Detach an Event Handler from the Component. 4180 * <p> 4181 * If no arguments are used with this method then all events are removed. 4182 * <p> 4183 * For some of this method's more advanced features, please see {@link baja.Subscriber#detach}. 4184 * <p> 4185 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 4186 * 4187 * @function 4188 * @name baja.Component#detach 4189 * 4190 * @see baja.Subscriber 4191 * @see baja.Component#attach 4192 * @see baja.Component#getHandlers 4193 * @see baja.Component#hasHandlers 4194 * 4195 * @param {String} [hName] the name of the handler to detach from the Component. 4196 * @param {Function} [func] the function to remove from the Subscriber. It's recommended to supply this just in case 4197 * other scripts have added event handlers. 4198 */ 4199 4200 /** 4201 * Return an array of event handlers. 4202 * <p> 4203 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 4204 * <p> 4205 * To access multiple handlers, insert a space between the handler names. 4206 * 4207 * @function 4208 * @name baja.Component#getHandlers 4209 * 4210 * @see baja.Subscriber 4211 * @see baja.Component#detach 4212 * @see baja.Component#attach 4213 * @see baja.Component#hasHandlers 4214 * 4215 * @param {String} hName the name of the handler 4216 * @returns {Array} 4217 */ 4218 4219 /** 4220 * Return true if there any handlers registered for the given handler name. 4221 * <p> 4222 * If no handler name is specified then test to see if there are any handlers registered at all. 4223 * <p> 4224 * Multiple handlers can be tested for by using a space character between the names. 4225 * <p> 4226 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 4227 * 4228 * @function 4229 * @name baja.Component#hasHandlers 4230 * 4231 * @see baja.Subscriber 4232 * @see baja.Component#detach 4233 * @see baja.Component#attach 4234 * @see baja.Component#getHandlers 4235 * 4236 * @param {String} [hName] the name of the handler. If undefined, then see if there are any 4237 * handlers registered at all. 4238 * @returns {Boolean} 4239 */ 4240 4241 /** 4242 * Returns the default parameter for an Action. 4243 * <p> 4244 * For unmounted Components, by default it calls <code>Action.getParameterDefault()</code>. 4245 * If mounted in a Proxy Component Space, this will result in an asynchronous network call. 4246 * <p> 4247 * Here's an example of how to invoke the method... 4248 * <pre> 4249 * // A resolved and mounted Component... 4250 * myComp.getActionParameterDefault({ 4251 * slot: "myAction", 4252 * ok: function (param) { 4253 * // Called once we're received the parameter. Note that param can be null 4254 * // if there's no parameter for the Action. 4255 * }, 4256 * fail: function (err) { 4257 * // Called if the method throws an exception on the Server or can't execute for some reason (optional) 4258 * }, 4259 * batch // if defined, any network calls will be batched into this object (optional) 4260 * }); 4261 * <\pre> 4262 * For callbacks, the 'this' keyword is set to the Component instance. 4263 * 4264 * @param {Object} obj the Object Literal for the method's arguments. 4265 * @param {baja.Action|String} obj.slot the Action or Action name. 4266 * @param {Function} [obj.ok] the ok callback. Called once the Action Parameter Default has been received. 4267 * Any return value is passed to this function (could be null 4268 * if no Action parameter is defined). 4269 * @param {Function} [obj.fail] the fail callback. 4270 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 4271 * 4272 * @returns {baja.Value} if the Component is not mounted in a Proxy Component Space, this method will return a value. 4273 * If there's no Action Parameter Default then null will be returned. 4274 */ 4275 baja.Component.prototype.getActionParameterDefault = function (obj) { 4276 obj = objectify(obj, "slot"); 4277 4278 var cb = new Callback(obj.ok, obj.fail, obj.batch); 4279 4280 // Ensure 'this' is Component in callbacks... 4281 setContextInOkCallback(this, cb); 4282 setContextInFailCallback(this, cb); 4283 4284 var slot = this.getSlot(obj.slot); 4285 4286 try { 4287 // Check arguments 4288 if (slot === null) { 4289 throw new Error("Unable to find Action: " + obj.slot); 4290 } 4291 if (!slot.isAction()) { 4292 throw new Error("Slot is not an Action: " + obj.slot); 4293 } 4294 4295 if (this.isMounted() && this.$space.hasCallbacks()) { 4296 // If mounted then make a network call for the Action invocation 4297 this.$space.getCallbacks().getActionParameterDefault(this, slot, cb); 4298 } 4299 else { 4300 // If not mounted then just return it from the Slot 4301 var def = slot.getParamDefault(); 4302 cb.ok(def); 4303 return def; 4304 } 4305 } 4306 catch (err) { 4307 cb.fail(err); 4308 } 4309 }; 4310 4311 /** 4312 * Return the knob count for the Component. 4313 * 4314 * @returns {Number} the number of knobs installed on the Component 4315 */ 4316 baja.Component.prototype.getKnobCount = function () { 4317 return this.$knobCount || 0; 4318 }; 4319 4320 /** 4321 * Return the Knobs for a particular Slot or the whole Component. 4322 * <p> 4323 * If no slot is passed in all the knobs for the Component will be returned. 4324 * 4325 * @param {baja.Slot|String} [slot] the Slot or Slot name. 4326 * 4327 * @returns {Array} array of knobs 4328 */ 4329 baja.Component.prototype.getKnobs = function (slot) { 4330 if (!this.$knobCount) { 4331 return emptyArray; 4332 } 4333 4334 if (arguments.length > 0) { 4335 // Find Knobs for a particular Slot 4336 slot = this.getSlot(slot); 4337 4338 if (!slot) { 4339 throw new Error("Invalid Slot: " + slot); 4340 } 4341 } 4342 4343 var p, 4344 knob, 4345 k = []; 4346 4347 // Build up knobs array 4348 for (p in this.$knobs) { 4349 if (this.$knobs.hasOwnProperty(p)) { 4350 knob = this.$knobs[p]; 4351 if (slot) { 4352 if (knob.getSourceSlotName() === slot.getName()) { 4353 k.push(knob); 4354 } 4355 } 4356 else { 4357 k.push(knob); 4358 } 4359 } 4360 } 4361 4362 return k; 4363 }; 4364 4365 /** 4366 * Return the Links for a particular Slot or the whole Component. 4367 * <p> 4368 * If no slot is passed in all the links for the Component will be returned. 4369 * 4370 * @param {baja.Slot|String} [slot] the Slot or Slot name. 4371 * 4372 * @returns {Array} array of links. 4373 */ 4374 baja.Component.prototype.getLinks = function (slot) { 4375 var links = []; 4376 4377 if (arguments.length === 0) { 4378 // If no arguments then return all the knobs for this component 4379 this.getSlots(function (s) { 4380 return s.isProperty() && s.getType().isLink(); 4381 }).each(function (s) { 4382 links.push(this.get(s)); 4383 }); 4384 } 4385 else { 4386 // Find Links for a particular Slot 4387 slot = this.getSlot(slot); 4388 if (slot === null) { 4389 throw new Error("Invalid Slot: " + slot); 4390 } 4391 this.getSlots(function (s) { 4392 return s.isProperty() && s.getType().isLink() && this.get(s).getTargetSlotName() === slot.getName(); 4393 }).each(function (s) { 4394 links.push(this.get(s)); 4395 }); 4396 } 4397 4398 return links; 4399 }; 4400 4401 /** 4402 * Create an instance of a Link to use for a link to the specified source Component. 4403 * <p> 4404 * For unmounted Components, by default this method returns a plain baja:Link instance. 4405 * If mounted in a Proxy Component Space, this will result in an asynchronous network call. 4406 * <\pre> 4407 * For callbacks, the 'this' keyword is set to the Component instance. 4408 * 4409 * @param {Object} obj the Object Literal for the method's arguments. 4410 * @param {baja.Component} obj.source the source Component for the link. 4411 * @param {baja.Slot|String} obj.sourceSlot the source Slot or Slot name. 4412 * @param {baja.Slot|String} obj.targetSlot the target Slot or Slot name. 4413 * @param {Function} [obj.ok] the ok callback. A link will be passed as an argument to this function. 4414 * @param {Function} [obj.fail] the fail callback. 4415 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 4416 * 4417 * @returns {baja.Value} if the Component is not mounted in a Proxy Component Space, this method will return a value. 4418 */ 4419 baja.Component.prototype.makeLink = function (obj) { 4420 obj = objectify(obj); 4421 4422 var cb = new Callback(obj.ok, obj.fail, obj.batch); 4423 4424 // Ensure 'this' is Component in callbacks... 4425 setContextInOkCallback(this, cb); 4426 setContextInFailCallback(this, cb); 4427 4428 try { 4429 strictArg(obj.source, baja.Component); 4430 4431 var source = obj.source, 4432 sourceSlot = source.getSlot(obj.sourceSlot), 4433 targetSlot = this.getSlot(obj.targetSlot); 4434 4435 if (!targetSlot) { 4436 throw new Error("Invalid Source Slot"); 4437 } 4438 4439 if (!sourceSlot) { 4440 throw new Error("Invalid Target Slot"); 4441 } 4442 4443 if (this.isMounted() && this.$space.hasCallbacks()) { 4444 // If mounted then make a network call to get the link 4445 this.$space.getCallbacks().makeLink(source, sourceSlot, this, targetSlot, cb); 4446 } 4447 else { 4448 // If not mounted then just return it from the Slot 4449 var link = baja.$("baja:Link"); 4450 cb.ok(link); 4451 return link; 4452 } 4453 } 4454 catch (err) { 4455 cb.fail(err); 4456 } 4457 }; 4458 4459 /** 4460 * Return the permissions for this Component. 4461 * 4462 * @returns {baja.Permissions} 4463 */ 4464 baja.Component.prototype.getPermissions = function () { 4465 var p = this.$permissions; 4466 if (!p) { 4467 if (typeof this.$permissionsStr === "string") { 4468 p = this.$permissions = baja.Permissions.make(this.$permissionsStr); 4469 } 4470 else { 4471 p = baja.Permissions.all; 4472 } 4473 } 4474 return p; 4475 }; 4476 4477 //////////////////////////////////////////////////////////////// 4478 // Component Space 4479 //////////////////////////////////////////////////////////////// 4480 4481 /** 4482 * @class Represents a baja:ComponentSpace in BajaScript. 4483 * 4484 * @name baja.ComponentSpace 4485 * @extends baja.NavContainer 4486 * 4487 * @param {String} ordInSession 4488 * @param host 4489 */ 4490 baja.ComponentSpace = function (name, ordInSession, host) { 4491 baja.ComponentSpace.$super.call(this, { 4492 navName: name 4493 }); 4494 this.$map = {}; 4495 this.$root = null; 4496 this.$callbacks = null; 4497 this.$host = bajaDef(host, null); 4498 this.$ordInSession = bajaDef(ordInSession, ""); 4499 }.$extend(baja.NavContainer).registerType("baja:ComponentSpace"); 4500 4501 /** 4502 * Called to initialize the Component Space. 4503 * 4504 * @private 4505 */ 4506 baja.ComponentSpace.prototype.init = function () { 4507 }; 4508 4509 /** 4510 * Mount a Component in the Component Space. 4511 * <p> 4512 * This is a private internal method to mount Components into a Component Space. 4513 * 4514 * @private 4515 */ 4516 var mount = function (comp) { 4517 var h = comp.getHandle(); 4518 if (h === null) { 4519 // TODO: What about automatically generating a handle for a non Proxy Component Space? 4520 // TODO: Generate error if not a handle? 4521 return; 4522 } 4523 4524 if (this.$map.hasOwnProperty(h)) { 4525 throw new Error("Fatal error: handle already used in Component Space: " + h); 4526 } 4527 4528 // Set up the Space and Handle reference 4529 comp.$space = this; 4530 this.$map[h] = comp; 4531 4532 // Mount any child Components 4533 var cursor = comp.getSlots().properties(); 4534 while (cursor.next()) { 4535 if (cursor.get().getType().isComponent()) { 4536 mount.call(this, comp.get(cursor.get())); 4537 } 4538 } 4539 }; 4540 4541 /** 4542 * Unmount a Component in the Component Space. 4543 * <p> 4544 * This is a private internal method to unmount Components into a Component Space. 4545 * 4546 * @private 4547 */ 4548 var unmount = function (comp) { 4549 var h = comp.getHandle(); 4550 if (h === null) { 4551 // TODO: Generate error if not a handle? 4552 return; 4553 } 4554 4555 if (!this.$map.hasOwnProperty(h)) { 4556 throw new Error("Fatal error: handle not mapped into Space: " + h); 4557 } 4558 4559 var prevSub = comp.isSubscribed(); 4560 4561 // delete the Space and Handle reference (after this isMounted() will return false) 4562 comp.$space = null; 4563 delete this.$map[h]; 4564 4565 // Unsubscribe Component from all Subscribers 4566 var subs = comp.$subs.slice(), i; 4567 for (i = 0; i < subs.length; ++i) { 4568 subs[i].unsubscribe({ 4569 "comps": comp 4570 }); 4571 } 4572 4573 // Make sure we're not leased 4574 unlease.call(comp); 4575 4576 // If this was subscribed before, make sure this callback is made 4577 if (prevSub && typeof comp.unsubscribed === "function") { 4578 try { 4579 comp.unsubscribed(); 4580 } 4581 catch (err) { 4582 baja.error(err); 4583 } 4584 } 4585 4586 // TODO: What about nullifying the handle reference on the Component? 4587 4588 // Unmount any child Components 4589 var cursor = comp.getSlots().properties(); 4590 while (cursor.next()) { 4591 if (cursor.get().getType().isComponent()) { 4592 unmount.call(this, comp.get(cursor.get())); 4593 } 4594 } 4595 }; 4596 4597 /** 4598 * Private framework handler for a Component Space. 4599 * <p> 4600 * This is a private internal method for framework developers. 4601 * 4602 * @private 4603 */ 4604 baja.ComponentSpace.prototype.$fw = function (x, a, b, c) { 4605 if (x === "mount") { 4606 // Mount this Component 4607 mount.call(this, /*Component*/a); 4608 } 4609 else if (x === "unmount") { 4610 // Unmount this Component 4611 unmount.call(this, /*Component*/a); 4612 } 4613 }; 4614 4615 /** 4616 * Return the root Component of the Component Space. 4617 * 4618 * @returns the root Component for the Space. 4619 */ 4620 baja.ComponentSpace.prototype.getRootComponent = function () { 4621 return this.$root; 4622 }; 4623 4624 /** 4625 * Return the ORD in Session for the Component Space. 4626 * 4627 * @returns {baja.Ord} 4628 */ 4629 baja.ComponentSpace.prototype.getOrdInSession = function () { 4630 return baja.Ord.make(this.$ordInSession); 4631 }; 4632 4633 /** 4634 * Return absolute ORD for the Component Space. 4635 * 4636 * @returns {baja.Ord} 4637 */ 4638 baja.ComponentSpace.prototype.getAbsoluteOrd = function () { 4639 if (this.$host === null) { 4640 return baja.Ord.DEFAULT; 4641 } 4642 return baja.Ord.make(this.$host.getAbsoluteOrd().toString() + "|" + this.$ordInSession); 4643 }; 4644 4645 /** 4646 * Find the Component via its handle (null if not found). 4647 * <p> 4648 * This method does not result in any network calls. 4649 * 4650 * @private 4651 * 4652 * @param {String} handle the Component's handle. 4653 * 4654 * @returns the Component via its handle (null if not found). 4655 */ 4656 baja.ComponentSpace.prototype.findByHandle = function (handle) { 4657 strictArg(handle, String); 4658 return bajaDef(this.$map[handle], null); 4659 }; 4660 4661 /** 4662 * Find the Component via its handle (null if not found). 4663 * <p> 4664 * This method may result in an <strong>asynchronous</strong> network call if 4665 * the Component can't be found locally and the Space is a Proxy. 4666 * <p> 4667 * An Object Literal is used for the method's arguments. 4668 * 4669 * @private 4670 * 4671 * @param {Object} [obj] the Object Literal for the method's arguments. 4672 * @param {Function} [obj.ok] the ok callback. Called if the Component is resolved. 4673 * The Component instance will be passed to this function. 4674 * @param {Function} [obj.fail] the fail callback. Call if there's an error or the Component 4675 * can't be resolved. 4676 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 4677 */ 4678 baja.ComponentSpace.prototype.resolveByHandle = function (obj) { 4679 obj = objectify(obj); 4680 4681 var handle = obj.handle, 4682 cb = new Callback(obj.ok, obj.fail); 4683 4684 try { 4685 var comp = this.findByHandle(handle); 4686 if (comp !== null) { 4687 cb.ok(comp); 4688 } 4689 else { 4690 throw new Error("Could not find Component from Handle"); 4691 } 4692 } 4693 catch (err) { 4694 cb.fail(err); 4695 } 4696 }; 4697 4698 /** 4699 * Return true if this Component Space has Space callbacks. 4700 * <p> 4701 * Space callbacks are normally used to make network calls. 4702 * 4703 * @private 4704 * 4705 * @returns {Boolean} 4706 */ 4707 baja.ComponentSpace.prototype.hasCallbacks = function () { 4708 return this.$callbacks !== null; 4709 }; 4710 4711 /** 4712 * Return the Space Callbacks. 4713 * 4714 * @private 4715 * 4716 * @returns Space Callbacks 4717 */ 4718 baja.ComponentSpace.prototype.getCallbacks = function () { 4719 return this.$callbacks; 4720 }; 4721 4722 /** 4723 * Sync the Component Space. 4724 * <p> 4725 * If the Space is a Proxy, this method will result in an 4726 * <strong>asynchronous</strong> network call to sync the master Space with this one. 4727 * <p> 4728 * An Object Literal is used for the method's arguments. 4729 * 4730 * @param {Object} [obj] the Object Literal for the method's arguments. 4731 * @param {Function} [obj.ok] the ok callback. Called once the Component Space has 4732 * been successfully synchronized with the Server. 4733 * @param {Function} [obj.fail] the fail callback. Called If the Component Space 4734 * can't be synchronized. 4735 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 4736 */ 4737 baja.ComponentSpace.prototype.sync = function (obj) { 4738 obj = objectify(obj, "ok"); 4739 new Callback(obj.ok, obj.fail).ok(); 4740 }; 4741 4742 /** 4743 * Return the Nav ORD of the Root Component. 4744 * 4745 * @private 4746 * 4747 * @returns {baja.Ord} 4748 */ 4749 baja.ComponentSpace.prototype.getNavOrd = function () { 4750 return this.$root === null ? null : this.$root.getNavOrd(); 4751 }; 4752 4753 /** 4754 * Access the Nav Children. 4755 * 4756 * @see baja.NavContainer#getNavChildren 4757 * 4758 * @returns {String} 4759 */ 4760 baja.ComponentSpace.prototype.getNavChildren = function (obj) { 4761 obj = objectify(obj, "ok"); 4762 if (this.$root) { 4763 this.$root.getNavChildren(obj); 4764 } 4765 else { 4766 obj.ok([]); 4767 } 4768 }; 4769 4770 /** 4771 * Return the Nav Icon. 4772 * 4773 * @returns {baja.Icon} 4774 */ 4775 baja.ComponentSpace.prototype.getNavIcon = function () { 4776 return this.$root ? this.$root.getNavIcon() : baja.ComponentSpace.$super.prototype.getNavIcon.call(this); 4777 }; 4778 4779 /** 4780 * @class Represents a baja:Host in BajaScript. 4781 * 4782 * @name baja.Host 4783 * @extends baja.Object 4784 */ 4785 baja.Host = function () { 4786 baja.Host.$super.apply(this, arguments); 4787 }.$extend(baja.NavContainer).registerType("baja:Host"); 4788 4789 /** 4790 * Return a Host's Nav ORD. 4791 * 4792 * @private 4793 * 4794 * @returns {baja.Ord} 4795 */ 4796 baja.Host.prototype.getNavOrd = function () { 4797 return this.getAbsoluteOrd(); 4798 }; 4799 4800 /** 4801 * Return a Host's Absolute ORD. 4802 * 4803 * @private 4804 * 4805 * @returns {baja.Ord} 4806 */ 4807 baja.Host.prototype.getAbsoluteOrd = function () { 4808 return null; 4809 }; 4810 4811 /** 4812 * @class Represents a baja:LocalHost in BajaScript. 4813 * 4814 * @name baja.LocalHost 4815 * @extends baja.Host 4816 */ 4817 baja.LocalHost = function () { 4818 baja.LocalHost.$super.call(this, { 4819 navName: "localhost", 4820 icon: "module://icons/x16/localhost.png" 4821 }); 4822 }.$extend(baja.Host).registerType("baja:LocalHost"); 4823 4824 /** 4825 * Return a Local Host's Absolute ORD. 4826 * 4827 * @private 4828 * 4829 * @returns {baja.Ord} 4830 */ 4831 baja.LocalHost.prototype.getAbsoluteOrd = function () { 4832 return baja.Ord.make("local:"); 4833 }; 4834 4835 //////////////////////////////////////////////////////////////// 4836 // Subscriber 4837 //////////////////////////////////////////////////////////////// 4838 4839 /** 4840 * @class Component Subscriber used to subscribe to multiple Components for events. 4841 * 4842 * @name baja.Subscriber 4843 * @extends BaseBajaObj 4844 */ 4845 baja.Subscriber = function () { 4846 this.$comps = []; 4847 }.$extend(BaseBajaObj); 4848 4849 // Mix-in the event handlers for baja.Subscriber 4850 baja.event.mixin(baja.Subscriber.prototype); 4851 4852 // The following comments have been added for the benefit of JsDoc Toolkit... 4853 4854 /** 4855 * Attach an Event Handler to this Subscriber. 4856 * <p> 4857 * A Subscriber can be used to subscribe to multiple Components 4858 * in the Station and can be used to listen for Component Events. 4859 * For instance, a common one would be a Property changed event... 4860 * <pre> 4861 * // sub is a Subscriber and is subscribed to a Component 4862 * sub.attach("changed", function (prop, cx) { 4863 * if (prop.getName() === "out") { 4864 * baja.outln("The output of the point is: " + this.getOutDisplay()); 4865 * } 4866 * }); 4867 * </pre> 4868 * An event handler consists of a name and a function. When the 4869 * function is called, 'this' will map to the target Component. 4870 * <p> 4871 * Here are some examples of the different event handlers that can be 4872 * attached to a Component... 4873 * <pre> 4874 * // Property Changed 4875 * sub.attach("changed", function (prop, cx) { 4876 * // prop: the Property that has changed 4877 * // cx: the Context (used internally) 4878 * }); 4879 * 4880 * // Property Added 4881 * sub.attach("added", function (prop, cx) { 4882 * // prop: the Property that has been added 4883 * // cx: the Context (used internally) 4884 * }); 4885 * 4886 * // Property Removed 4887 * sub.attach("removed", function (prop, val, cx) { 4888 * // prop: the Property that has been removed 4889 * // val: the old value of the Property 4890 * // cx: the Context (used internally) 4891 * }); 4892 * 4893 * // Property Renamed 4894 * sub.attach("renamed", function (prop, oldName, cx) { 4895 * // prop: the Property that has been renamed 4896 * // oldName: the old slot name 4897 * // cx: the Context (used internally) 4898 * }); 4899 * 4900 * // Dynamic Slots Reordered 4901 * sub.attach("reordered", function (cx) { 4902 * // cx: the Context (used internally) 4903 * }); 4904 * 4905 * // Topic Fired 4906 * sub.attach("topicFired", function (topic, event, cx) { 4907 * // topic: the Topic that has been fired 4908 * // event: the Topic event data (can be null) 4909 * // cx: the Context (used internally) 4910 * }); 4911 * 4912 * // Slot Flags Changed 4913 * sub.attach("flagsChanged", function (slot, cx) { 4914 * // slot: the slot whose flags have changed 4915 * // cx: the Context (used internally) 4916 * }); 4917 * 4918 * // Slot Facets Changed 4919 * sub.attach("facetsChanged", function (slot, cx) { 4920 * // slot: the slot whose facets have changed 4921 * // cx: the Context (used internally) 4922 * }); 4923 * 4924 * // Component subscribed 4925 * sub.attach("subscribed", function (cx) { 4926 * // cx: the Context (used internally) 4927 * }); 4928 * 4929 * // Component unsubscribed 4930 * sub.attach("unsubscribed", function (cx) { 4931 * // cx: the Context (used internally) 4932 * }); 4933 * 4934 * // Component unmounted (called just before Component is removed from parent) 4935 * sub.attach("unmount", function (cx) { 4936 * // cx: the Context (used internally) 4937 * }); 4938 * 4939 * // Component renamed in parent 4940 * sub.attach("componentRenamed", function (oldName, cx) { 4941 * // cx: the Context (used internally) 4942 * }); 4943 * 4944 * // Component's flags changed in parent 4945 * sub.attach("componentFlagsChanged", function (cx) { 4946 * // cx: the Context (used internally) 4947 * }); 4948 * 4949 * // Component's facets changed in parent 4950 * sub.attach("componentFacetsChanged", function (cx) { 4951 * // cx: the Context (used internally) 4952 * }); 4953 * 4954 * // Component reordered in parent 4955 * sub.attach("componentReordered", function (cx) { 4956 * // cx: the Context (used internally) 4957 * }); 4958 * </pre> 4959 * <p> 4960 * An Object Literal can be used to specify multiple handlers. For example... 4961 * <pre> 4962 * var sub = new baja.Subscriber(); 4963 * sub.attach({ 4964 * changed: function (prop, cx) { 4965 * }, 4966 * subscribed: function (cx) { 4967 * } 4968 * }); 4969 * </pre> 4970 * <p> 4971 * Spaces can be used in a name to specify a function for multiple events... 4972 * <pre> 4973 * var sub = new baja.Subscriber(); 4974 * sub.attach("subscribed changed", function () { 4975 * updateGui(this); 4976 * }); 4977 * </pre> 4978 * 4979 * @function 4980 * @name baja.Subscriber#attach 4981 * 4982 * @see baja.Component#attach 4983 * @see baja.Subscriber#detach 4984 * @see baja.Subscriber#getHandlers 4985 * @see baja.Subscriber#hasHandlers 4986 * @see baja.Slot 4987 * 4988 * @param {String} event handler name. 4989 * @param {Function} func the event handler function. 4990 */ 4991 4992 /** 4993 * Detach an Event Handler from the Subscriber. 4994 * <p> 4995 * If no arguments are used with this method then all events are removed. 4996 * <p> 4997 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 4998 * <p> 4999 * A String with spaces can be supplied to remove multiple event handlers. For example... 5000 * <pre> 5001 * sub.detach("subscribed changed"); // Remove all subscribed and changed event handlers 5002 * </pre> 5003 * 5004 * @function 5005 * @name baja.Subscriber#detach 5006 * 5007 * @see baja.Component#attach 5008 * @see baja.Subscriber#attach 5009 * @see baja.Subscriber#getHandlers 5010 * @see baja.Subscriber#hasHandlers 5011 * 5012 * @param {String} [hName] the name of the handler to detach from the Subscriber. 5013 * @param {Function} [func] the function to remove from the Subscriber. It's recommended to supply this just in case 5014 * other scripts have added event handlers. 5015 */ 5016 5017 /** 5018 * Return an array of event handlers. 5019 * <p> 5020 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 5021 * <p> 5022 * To access multiple handlers, insert a space between the handler names. 5023 * 5024 * @function 5025 * @name baja.Subscriber#getHandlers 5026 * 5027 * @see baja.Component#attach 5028 * @see baja.Subscriber#detach 5029 * @see baja.Subscriber#attach 5030 * @see baja.Subscriber#hasHandlers 5031 * 5032 * @param {String} hName the name of the handler 5033 * @returns {Array} 5034 */ 5035 5036 /** 5037 * Return true if there any handlers registered for the given handler name. 5038 * <p> 5039 * If no handler name is specified then test to see if there are any handlers registered at all. 5040 * <p> 5041 * For a list of all the event handlers, please see {@link baja.Subscriber#attach}. 5042 * <p> 5043 * Multiple handlers can be tested for by using a space character between the names. 5044 * 5045 * @function 5046 * @name baja.Subscriber#hasHandlers 5047 * 5048 * @see baja.Component#attach 5049 * @see baja.Subscriber#detach 5050 * @see baja.Subscriber#attach 5051 * @see baja.Subscriber#getHandlers 5052 * 5053 * @param {String} [hName] the name of the handler. If undefined, then see if there are any 5054 * handlers registered at all. 5055 * @returns {Boolean} 5056 */ 5057 5058 /** 5059 * Return an array of the Components currently being 5060 * subscribed to by this Subscriber. 5061 * 5062 * @returns {Array} a copy of the array used to subscribe Components (baja.Component). 5063 */ 5064 baja.Subscriber.prototype.getComponents = function () { 5065 return this.$comps.slice(); 5066 }; 5067 5068 /** 5069 * Subscribe a Component or a number of Components. 5070 * <p> 5071 * This will put the Components into subscription if they are not already subscribed and are mounted. 5072 * The Subscription will last until the page is refreshed or unsubscribe is called. 5073 * <p> 5074 * If the Components are mounted and able to be subscribed, this will result in 5075 * an <strong>asynchronous</strong> network call. 5076 * <p> 5077 * A Component instance, array of Components or an optional Object Literal can be used to 5078 * specify the method's arguments... 5079 * <pre> 5080 * // Subscribe a single Component 5081 * sub.subscribe(aComp); 5082 * 5083 * // ...or subscribe an array of Components... 5084 * sub.subscribe([aComp1, aComp2]); 5085 * 5086 * // ...or use an Object Literal for more arguments... 5087 * sub.subscribe({ 5088 * comps: [aComp1, aComp2], // Can also just be an singular Component instance 5089 * ok: function () { 5090 * // Called once the Components are subscribed (optional) 5091 * }, 5092 * fail: function (err) { 5093 * // Called if the Components fail to subscribe (optional) 5094 * }, 5095 * batch // if defined, any network calls will be batched into this object (optional) 5096 * }); 5097 * </pre> 5098 * For callbacks, the 'this' keyword is set to the comps property. 5099 * 5100 * @param {Object} [obj] the Object Literal used for the method's arguments. 5101 * @param {Function} [obj.ok] the ok callback. Called once the Components have been subscribed. 5102 * @param {Function} [obj.fail] the fail callback. Called if the Components fail to subscribe. 5103 * Any errors will be passed to this function. 5104 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 5105 */ 5106 baja.Subscriber.prototype.subscribe = function (obj) { 5107 obj = objectify(obj, "comps"); 5108 5109 var comps = obj.comps, 5110 cb = new Callback(obj.ok, obj.fail, obj.batch); 5111 5112 // Ensure 'this' is Component in callbacks... 5113 setContextInOkCallback(comps, cb); 5114 setContextInFailCallback(comps, cb); 5115 5116 try { 5117 // Ensure we have an array of valid Components to subscribe too 5118 if (!(comps instanceof Array)) { 5119 comps = [comps]; 5120 } 5121 5122 var c, i; 5123 5124 // Check for errors first 5125 for (i = 0; i < comps.length; ++i) { 5126 c = comps[i]; 5127 5128 // Make sure we have a Component 5129 strictArg(c, baja.Component); 5130 5131 if (!c.isMounted()) { 5132 throw new Error("Cannot subscribe unmounted Component!"); 5133 } 5134 } 5135 5136 var space = null, 5137 ords = [], 5138 compsToSub = [], 5139 prevSub; 5140 5141 // Remove references and see if we need a network call 5142 for (i = 0; i < comps.length; ++i) { 5143 c = comps[i]; 5144 5145 if (!space) { 5146 space = c.getComponentSpace(); 5147 } 5148 5149 prevSub = c.isSubscribed(); 5150 5151 // Add to this Subscribers Component array 5152 if (!this.$comps.contains(c)) { 5153 this.$comps.push(c); 5154 } 5155 5156 // Make sure this Subscriber is listed in the Subscribed Component 5157 if (!c.$subs.contains(this)) { 5158 c.$subs.push(this); 5159 } 5160 5161 // If the Component is now subscribed but was previously unsubscribed 5162 // then make a network call 5163 if (c.isSubscribed() && !prevSub) { 5164 ords.push("h:" + c.getHandle()); 5165 compsToSub.push(c); 5166 } 5167 } 5168 5169 // If there is nothing to subscribe to at this point then just bail 5170 if (ords.length > 0 && space.hasCallbacks() && bajaDef(obj.netCall, true)) { 5171 5172 // Signal that each Component has been subscribed 5173 cb.addOk(function (ok, fail, resp) { 5174 var i; 5175 for (i = 0; i < compsToSub.length; ++i) { 5176 try { 5177 compsToSub[i].$fw("fwSubscribed"); 5178 } 5179 catch (err) { 5180 baja.error(err); 5181 } 5182 } 5183 5184 ok(); 5185 }); 5186 5187 // Make the network call through the Space 5188 space.getCallbacks().subscribe(ords, cb, obj.importAsync); 5189 } 5190 else { 5191 cb.ok(); 5192 } 5193 } 5194 catch (err) { 5195 cb.fail(err); 5196 } 5197 }; 5198 5199 /** 5200 * An internal private method for subscribing Components via their ORDs. Please note, this method 5201 * is strictly intended for Tridium developers only! 5202 * 5203 * @private 5204 * @internal 5205 * 5206 * @param {Object} [obj] the Object Literal used for the method's arguments. 5207 * @param {Array} ords an Array of String ORDs that should resolve to Components for subscription. 5208 * @param {baja.ComponentSpace} space the Component Space used for ORD resolution. 5209 * @param {Function} [obj.ok] the ok callback. Called once the Components have been subscribed. 5210 * @param {Function} [obj.fail] the fail callback. Called if the Components fail to subscribe. 5211 * Any errors will be passed to this function. 5212 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 5213 */ 5214 baja.Subscriber.prototype.$ordSubscribe = function (obj) { 5215 var ords = obj.ords, 5216 space = obj.space, 5217 cb = new Callback(obj.ok, obj.fail, obj.batch), 5218 that = this; 5219 5220 try { 5221 // Ensure these Components are all subscribed 5222 cb.addOk(function(ok, fail, handles) { 5223 // Remove references and see if we need a network call 5224 var i, 5225 c, 5226 prevSub; 5227 5228 for (i = 0; i < handles.length; ++i) { 5229 // Attempt to find the Component locally 5230 c = space.findByHandle(handles[i]); 5231 5232 if (c) { 5233 // Mark the Component as subscribed 5234 prevSub = c.isSubscribed(); 5235 5236 // Add to this Subscribers Component array 5237 if (!that.$comps.contains(c)) { 5238 that.$comps.push(c); 5239 } 5240 5241 // Make sure this Subscriber is listed in the Subscribed Component 5242 if (!c.$subs.contains(that)) { 5243 c.$subs.push(that); 5244 } 5245 5246 // If this is now subscribed then fire the relevant callback 5247 if (c.isSubscribed() && !prevSub) { 5248 try { 5249 c.$fw("fwSubscribed"); 5250 } 5251 catch (err) { 5252 baja.error(err); 5253 } 5254 } 5255 } 5256 else { 5257 baja.error("Could not Batch Resolve Subscribe: " + handles[i]); 5258 } 5259 5260 ok(); 5261 } 5262 }); 5263 5264 // Make the network call through the Space 5265 space.getCallbacks().subscribe(ords, cb, /*importAsync*/false); 5266 } 5267 catch (err) { 5268 cb.fail(err); 5269 } 5270 }; 5271 5272 /** 5273 * Unsubscribe a Component or a number of Components. 5274 * <p> 5275 * This will unsubscribe the mounted Components if they are not already unsubscribed. 5276 * <p> 5277 * If the Components are able to be unsubscribed, this will result in 5278 * an <strong>asynchronous</strong> network call. 5279 * <p> 5280 * A Component instance, array of Components or an optional Object Literal can be used to 5281 * specify the method's arguments... 5282 * <pre> 5283 * // Unsubscribe a single Component 5284 * sub.unsubscribe(aComp); 5285 * 5286 * // ...or unsubscribe an array of Components... 5287 * sub.unsubscribe([aComp1, aComp2]); 5288 * 5289 * // ...or use an Object Literal for more arguments... 5290 * sub.unsubscribe({ 5291 * comps: [aComp1, aComp2], // Can also just be an singular Component instance 5292 * ok: function () { 5293 * // Called once the Components are unsubscribed (optional) 5294 * }, 5295 * fail: function (err) { 5296 * // Called if the Components fail to unsubscribe (optional) 5297 * }, 5298 * batch // if defined, any network calls will be batched into this object (optional) 5299 * }); 5300 * </pre> 5301 * For callbacks, the 'this' keyword is set to the comps property. 5302 * 5303 * @param {Object} [obj] the Object Literal used for the method's arguments. 5304 * @param {Function} [obj.ok] the ok callback. Called once the Components have been unsubscribed. 5305 * @param {Function} [obj.fail] the fail callback. Called if the Components fail to subscribe. 5306 * Any errors will be passed to this function. 5307 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 5308 */ 5309 baja.Subscriber.prototype.unsubscribe = function (obj) { 5310 obj = objectify(obj, "comps"); 5311 5312 var comps = obj.comps, 5313 cb = new Callback(obj.ok, obj.fail, obj.batch); 5314 5315 // Ensure 'this' is Component in callbacks... 5316 setContextInOkCallback(this, cb); 5317 setContextInFailCallback(this, cb); 5318 5319 try { 5320 // Ensure we have an array of valid Components to subscribe too 5321 if (!(comps instanceof Array)) { 5322 comps = [comps]; 5323 } 5324 5325 var c, i, j, k; 5326 5327 // Check for errors first 5328 for (i = 0; i < comps.length; ++i) { 5329 c = comps[i]; 5330 5331 // Make sure we have a Component 5332 strictArg(c, baja.Component); 5333 } 5334 5335 var space = null, 5336 ords = [], 5337 prevSub; 5338 5339 // Add references and see if we need a network call 5340 for (i = 0; i < comps.length; ++i) { 5341 c = comps[i]; 5342 5343 if (!space) { 5344 space = c.getComponentSpace(); 5345 } 5346 5347 prevSub = c.isSubscribed(); 5348 5349 // Attempt to remove Component from this Subscribers Component list 5350 for (j = 0; j < this.$comps.length; ++j) { 5351 if (this.$comps[j] === c) { 5352 this.$comps.splice(j, 1); 5353 break; 5354 } 5355 } 5356 5357 // Remove this Subscriber from the Component 5358 for (k = 0; k < c.$subs.length; ++k) { 5359 if (c.$subs[k] === this) { 5360 c.$subs.splice(k, 1); 5361 break; 5362 } 5363 } 5364 5365 // If the Component is not subscribed but was previously subscribed then make a network call 5366 if (!c.isSubscribed() && prevSub && c.isMounted()) { 5367 ords.push("h:" + c.getHandle()); 5368 } 5369 } 5370 5371 // If there is nothing to unsubscribe at this point then just bail 5372 if (ords.length > 0 && space.hasCallbacks()) { 5373 // Make the network call through the Space 5374 space.getCallbacks().unsubscribe(ords, cb); 5375 } 5376 else { 5377 cb.ok(); 5378 } 5379 } 5380 catch (err) { 5381 cb.fail(err); 5382 } 5383 }; 5384 5385 /** 5386 * Unsubscribe all Components from a Subscriber. 5387 * <p> 5388 * This will unregister all Components from this Subscriber. 5389 * <p> 5390 * If the Components are able to be unsubscribed, this will result in 5391 * an <strong>asynchronous</strong> network call. 5392 * <p> 5393 * An Object Literal is used to specify the method's arguments... 5394 * <pre> 5395 * // ...or use an Object Literal for more arguments... 5396 * sub.unsubscribeAll({ 5397 * ok: function () { 5398 * // Called once the Components are unsubscribed (optional) 5399 * }, 5400 * fail: function (err) { 5401 * // Called if the Components fail to unsubscribe (optional) 5402 * }, 5403 * batch // if defined, any network calls will be batched into this object (optional) 5404 * }); 5405 * </pre> 5406 * For callbacks, the 'this' keyword is set to the internal component array. 5407 * 5408 * @param {Object} [obj] the Object Literal used for the method's arguments. 5409 * @param {Function} [obj.ok] the ok callback. Called once the Components have been unsubscribed. 5410 * @param {Function} [obj.fail] the fail callback. Called if the Components fail to subscribe. 5411 * Any errors will be passed to this function. 5412 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 5413 */ 5414 baja.Subscriber.prototype.unsubscribeAll = function (obj) { 5415 obj = objectify(obj); 5416 obj.comps = this.$comps.slice(); 5417 this.unsubscribe(obj); 5418 }; 5419 5420 /** 5421 * Return true if the Component is subscribed in this Subscriber. 5422 * 5423 * @param {Object} comp the Component to be tested for Subscription. 5424 * @returns {Boolean} 5425 */ 5426 baja.Subscriber.prototype.isSubscribed = function (comp) { 5427 strictArg(comp, baja.Component); 5428 return this.$comps.contains(comp); 5429 }; 5430 5431 //////////////////////////////////////////////////////////////// 5432 // ControlPoint 5433 //////////////////////////////////////////////////////////////// 5434 5435 /** 5436 * @class Represents a control:ControlPoint in BajaScript. 5437 * 5438 * @name baja.ControlPoint 5439 * @private 5440 * @inner 5441 * @extends baja.Component 5442 */ 5443 var ControlPoint = function () { 5444 ControlPoint.$super.apply(this, arguments); 5445 }.$extend(baja.Component).registerType("control:ControlPoint"); 5446 5447 /** 5448 * Return the Facets for a Slot. 5449 * <p> 5450 * If no arguments are provided and the Complex has a parent, the 5451 * facets for the parent's Property will be returned. 5452 * 5453 * @name baja.ControlPoint#getFacets 5454 * @function 5455 * @private 5456 * @inner 5457 * 5458 * @param {baja.Slot|String} [slot] the Slot or Slot name. 5459 * @returns {baja.Facets} the Facets for the Slot (or null if Slot not found) or 5460 * the parent's Property facets. 5461 */ 5462 ControlPoint.prototype.getFacets = function (slot) { 5463 // Attempt to match Station Component's 'getSlotFacets' implementation... 5464 if (slot) { 5465 slot = this.getSlot(slot); 5466 if (slot) { 5467 var nm = slot.getName(); 5468 if (nm.match(/^(out|in[0-9][1-6]?|fallback|override|emergencyOverride|set)/)) { 5469 return this.get("facets"); 5470 } 5471 } 5472 } 5473 5474 // Call base class 'getFacets' 5475 return ControlPoint.$super.prototype.getFacets.apply(this, arguments); 5476 }; 5477 5478 }(baja, BaseBajaObj));