1 // 2 // Copyright 2010, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * Box Component Space 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, 14 bitwise: true, regexp: true, newcap: true, immed: true, strict: false, indent: 2, vars: true, continue: true */ 15 16 // Globals for JsLint to ignore 17 /*global baja, syncVal, BaseBajaObj, syncProps, syncComp*/ 18 19 (function boxcs(baja) { 20 21 // Use ECMAScript 5 Strict Mode 22 "use strict"; 23 24 // 25 // Please note, a context may a commit flag that indicates not to trap 26 // the update to a network call. The serverDecode flag is used as an 27 // optimization by BajaScript's Component safety checks. It's assumed 28 // any BSON decoded from the Server is probably ok and hence we 29 // can short circuit some of the checks. The syncStructVals 30 // flag is used exclusively by Complex 'set' to determine whether 31 // any incoming struct values should be synced instead being 32 // replaced outright. 33 // 34 35 // Create local for improved minification 36 var strictArg = baja.strictArg, 37 bajaDef = baja.def, 38 serverDecodeContext = baja.$serverDecodeContext, 39 bsonDecodeValue = baja.bson.decodeValue, 40 Callback = baja.comm.Callback; 41 42 //////////////////////////////////////////////////////////////// 43 // SyncOps 44 //////////////////////////////////////////////////////////////// 45 46 // A map of of SyncOp constructors used in decoding SyncOps 47 var syncOps = {}; 48 49 /** 50 * @class Base SyncOp. 51 * <p> 52 * All other SyncOps extend from this constructor. 53 * 54 * @name SyncOp 55 * @extends BaseBajaObj 56 * @inner 57 * @private 58 */ 59 var SyncOp = function () { 60 this.$arg = null; 61 }.$extend(BaseBajaObj); 62 63 // The unique identifier for the SyncOp 64 SyncOp.id = ""; 65 66 /** 67 * Return the id for the SyncOp. 68 * <p> 69 * The id is used for encoding and decoding a SyncOp. 70 * 71 * @private 72 * 73 * @returns {String} 74 */ 75 SyncOp.prototype.getId = function () { 76 return this.constructor.id; 77 }; 78 79 /** 80 * Perform a syncTo network call. 81 * 82 * @private 83 * 84 * @param space the Component Space we're syncing too. 85 * @param {baja.comm.Callback} cb the callback handler. 86 */ 87 SyncOp.prototype.syncTo = function (space, cb) { 88 89 // Create BSON SyncOp data structure 90 var sync = { 91 nm: "sync", 92 ver: 1.0, 93 ops: [ this.$arg ] 94 }; 95 96 // TODO: at some point, we'll want to batch up the syncTo requests and have one syncFrom request 97 // for great bandwidth and efficiency. At the same time, we should also try and introduce the idea 98 // of unsolicited messages that we'll subscribe too. All of the syncFrom events would then come from 99 // unsolicited messages that have been registered for with a topic name. This sets us up for eventually 100 // using a WebSocket instead of constant HTTP request/responses. When receiving a BOX frame with unsolicited 101 // messages, the unsolicited messages should be processed first before dealing with the responses 102 103 var failedCalled = false, 104 syncToResp; 105 106 // Only call the ok if fail hasn't been called on the syncTo handler 107 cb.addOk(function (ok, fail, resp) { 108 if (!failedCalled) { 109 // Pass results of the committed syncOps into the result of the original callback 110 ok(syncToResp); 111 } 112 }); 113 114 var syncToCb = new Callback(baja.ok, cb.fail, cb.getBatch()); 115 116 syncToCb.addOk(function (ok, fail, resp) { 117 // Get results of syncTo (which are the results of the committed syncOps) 118 syncToResp = resp; 119 ok(resp); 120 }); 121 122 // Mark that fail has been called 123 syncToCb.addFail(function (ok, fail, err) { 124 failedCalled = true; 125 fail(err); 126 }); 127 128 // syncTo 129 baja.comm.serverHandlerCall(space.getAbsoluteOrd().toString(), 130 "syncTo", 131 sync, 132 syncToCb); 133 134 // syncFrom 135 space.getCallbacks().poll(cb); 136 }; 137 138 /** 139 * @class Add SyncOp. 140 * 141 * @name AddOp 142 * @extends SyncOp 143 * @inner 144 * @private 145 * 146 * @param comp the Component the add is happening upon. 147 * @param {String} slotName the name of the slot being added. 148 * @param val the value for the add operation. 149 * @param {Number} flags the slot facets. 150 * @param {baja.Facets} facets the slot facets. 151 */ 152 var AddOp = function (comp, slotName, val, flags, facets) { 153 AddOp.$super.apply(this, arguments); 154 155 // TODO: What about getting the name of the Property that was created 156 // from the Server??? 157 158 // Encode argument to a data structure 159 var a = { 160 nm: this.getId(), 161 h: comp.getHandle(), 162 b: baja.bson.encodeValue(val) // Encode the value to BSON 163 }; 164 165 if (slotName !== null) { 166 a.n = slotName; 167 } 168 169 if (flags !== 0) { 170 a.f = baja.Flags.encodeToString(flags); 171 } 172 173 if (facets !== null && facets !== baja.Facets.DEFAULT) { 174 a.facets = facets.encodeToString(); 175 } 176 177 this.$arg = a; 178 }.$extend(SyncOp); 179 180 AddOp.id = "a"; 181 syncOps[AddOp.id] = AddOp; 182 183 /** 184 * Decode and commit the SyncOp. 185 * 186 * @private 187 * 188 * @param comp the Component being added too. 189 * @param sp the syncOp data structure to be decoded. 190 */ 191 AddOp.decodeAndCommit = function (comp, sp) { 192 // TODO: Shouldn't need to add the 'get' check on the end of this if statement but it'll 193 // have to do for now. Really the Server should keep track of what Components the client 194 // has loaded instead of just firing everything down it gets! 195 196 if (comp !== null && comp.get(sp.n) === null) { 197 var name = bajaDef(sp.n, null), 198 displayName = sp.dn, 199 display = sp.b.d, 200 flags = baja.Flags.decodeFromString(bajaDef(sp.f, "")), 201 fcts = baja.Facets.DEFAULT.decodeFromString(bajaDef(sp.facets, "")); 202 203 // Perform Component add with Commit Context 204 comp.add({ 205 "slot": name, 206 "value": bsonDecodeValue(sp.b, serverDecodeContext), 207 "flags": flags, 208 "facets": fcts, 209 "cx": { commit: true, displayName: displayName, display: display, serverDecode: true } 210 }); 211 } 212 }; 213 214 /** 215 * @class Set SyncOp 216 * 217 * @name SetOp 218 * @extends SyncOp 219 * @inner 220 * @private 221 * 222 * @param comp the Component the set is happening upon. 223 * @param {Array} propPath an array of Property names for the set. 224 * @param val the value being used in the set. 225 */ 226 var SetOp = function (comp, propPath, val) { 227 SetOp.$super.apply(this, arguments); 228 229 // Encode argument to a data structure 230 var a = { 231 nm: this.getId(), 232 h: comp.getHandle(), 233 n: propPath.reverse().join("/"), 234 b: baja.bson.encodeValue(val) // Encode the value to BSON 235 }; 236 237 this.$arg = a; 238 239 }.$extend(SyncOp); 240 241 SetOp.id = "s"; 242 syncOps[SetOp.id] = SetOp; 243 244 /** 245 * Decode and commit the SyncOp. 246 * 247 * @private 248 * 249 * @param comp the Component the set is happening on. 250 * @param sp the syncOp data structure to be decoded. 251 */ 252 SetOp.decodeAndCommit = function (comp, sp) { 253 if (comp !== null) { 254 // Decode the value and call 'set' 255 var names = sp.n.split("/"), 256 displayName = sp.dn, 257 display = sp.b.d, 258 target = comp, 259 n = null, 260 i; 261 262 for (i = 0; i < names.length; ++i) { 263 if (n !== null) { 264 target = target.get(n); 265 } 266 n = names[i]; 267 } 268 269 // Set the desired target 270 target.set({ 271 "slot": n, 272 "value": bsonDecodeValue(sp.b, serverDecodeContext), 273 "cx": { commit: true, serverDecode: true, syncStructVals: true, displayName: displayName, display: display } 274 }); 275 } 276 }; 277 278 /** 279 * @class Remove SyncOp. 280 * 281 * @name RemoveOp 282 * @extends SyncOp 283 * @inner 284 * @private 285 * 286 * @param comp the Component the remove is happening upon. 287 * @param {baja.Slot} slot the Slot to remove from the Component. 288 */ 289 var RemoveOp = function (comp, slot) { 290 RemoveOp.$super.apply(this, arguments); 291 292 // Encode argument to a data structure 293 var a = { 294 nm: this.getId(), 295 h: comp.getHandle(), 296 n: slot.getName() 297 }; 298 299 this.$arg = a; 300 301 }.$extend(SyncOp); 302 303 RemoveOp.id = "v"; 304 syncOps[RemoveOp.id] = RemoveOp; 305 306 /** 307 * Decode and commit the SyncOp. 308 * 309 * @name RemoveOp.decodeAndCommit 310 * @function 311 * @private 312 * 313 * @param comp the Component the remove is happening on. 314 * @param sp the syncOp data structure to be decoded. 315 */ 316 RemoveOp.decodeAndCommit = function (comp, sp) { 317 if (comp !== null) { 318 var name = sp.n, 319 slot = comp.getSlot(name); 320 321 if (slot !== null) { 322 comp.remove({ 323 "slot": slot, 324 "cx": { commit: true, serverDecode: true } 325 }); 326 } 327 } 328 }; 329 330 /** 331 * @class Fire Topic SyncOp. 332 * 333 * @name FireTopicOp 334 * @extends SyncOp 335 * @inner 336 * @private 337 * 338 * @param comp the Component the Topic is being fired from. 339 * @param {baja.Slot} slot the Topic Slot. 340 * @param event the event to be fired (can be null). 341 */ 342 var FireTopicOp = function (comp, slot, event) { 343 FireTopicOp.$super.apply(this, arguments); 344 345 // Encode argument to a data structure 346 var a = { 347 nm: this.getId(), 348 h: comp.getHandle(), 349 n: slot.getName() 350 }; 351 352 if (event !== null) { 353 a.b = baja.bson.encodeValue(event); // Encode the value to BSON 354 } 355 356 this.$arg = a; 357 }.$extend(SyncOp); 358 359 FireTopicOp.id = "t"; 360 syncOps[FireTopicOp.id] = FireTopicOp; 361 362 /** 363 * Decode and commit the SyncOp. 364 * 365 * @private 366 * 367 * @param comp the Component the Topic is being fired from. 368 * @param sp the syncOp data structure to be decoded. 369 */ 370 FireTopicOp.decodeAndCommit = function (comp, sp) { 371 if (comp !== null) { 372 // Decode and fire the Component event 373 // TODO: Propogate amoungst Knobs 374 375 var name = sp.n, 376 slot = comp.getSlot(name), 377 display = "", 378 event = null; 379 380 if (sp.b !== undefined) { 381 event = bsonDecodeValue(sp.b, serverDecodeContext); 382 display = sp.b.d; 383 } 384 385 // Only fire this if the Topic Slot is loaded 386 if (slot !== null) { 387 388 // Fire the Topic on the Component 389 comp.fire({ 390 "slot": slot, 391 "value": event, 392 "cx": { commit: true, display: display, serverDecode: true } 393 }); 394 } 395 } 396 }; 397 398 /** 399 * @class Load a Component's Slots SyncOp. 400 * 401 * @name LoadOp 402 * @extends SyncOp 403 * @inner 404 * @private 405 */ 406 var LoadOp = function () { 407 LoadOp.$super.apply(this, arguments); 408 }.$extend(SyncOp); 409 410 LoadOp.id = "l"; 411 syncOps[LoadOp.id] = LoadOp; 412 413 /** 414 * Decode and commit the SyncOp. 415 * 416 * @name LoadOp.decodeAndCommit 417 * @function 418 * @private 419 * 420 * @param comp the Component to be loaded. 421 * @param sp the syncOp data structure to be decoded. 422 */ 423 LoadOp.decodeAndCommit = function (comp, sp) { 424 if (comp !== null) { 425 // Synchronize the two components together 426 syncComp(bsonDecodeValue(sp.b, serverDecodeContext), comp); 427 } 428 }; 429 430 /** 431 * @class Rename a dynamic Slot SyncOp. 432 * 433 * @name RenameOp 434 * @extends SyncOp 435 * @inner 436 * @private 437 * 438 * @param comp the Component the Topic is being fired from. 439 * @param {String} oldName the old name of the Slot. 440 * @param {String} newName the new name of the Slot. 441 */ 442 var RenameOp = function (comp, oldName, newName) { 443 RenameOp.$super.apply(this, arguments); 444 445 // Encode argument to a data structure 446 var a = { 447 nm: this.getId(), 448 h: comp.getHandle(), 449 o: oldName, 450 n: newName 451 }; 452 453 this.$arg = a; 454 455 }.$extend(SyncOp); 456 457 RenameOp.id = "r"; 458 syncOps[RenameOp.id] = RenameOp; 459 460 /** 461 * Decode and commit the SyncOp. 462 * 463 * @private 464 * 465 * @param comp the Component the rename will happen upon. 466 * @param sp the syncOp data structure to be decoded. 467 */ 468 RenameOp.decodeAndCommit = function (comp, sp) { 469 if (comp !== null) { 470 var name = sp.n, 471 oldName = sp.o, 472 displayName = sp.dn, 473 display = sp.d, 474 slot = comp.getSlot(oldName); 475 476 if (slot !== null) { 477 comp.rename({ 478 "slot": slot, 479 "newName": name, 480 "cx": { commit: true, displayName: displayName, display: display, serverDecode: true } 481 }); 482 } 483 } 484 }; 485 486 /** 487 * @class Reorder a Component's dynamic Slots SyncOp. 488 * 489 * @name ReorderOp 490 * @extends SyncOp 491 * @inner 492 * @private 493 * 494 * @param comp the Component the reorder is happening on. 495 * @param {Array} a String array of dynamic Property names that specifies the new order. 496 */ 497 var ReorderOp = function (comp, dynamicProperties) { 498 ReorderOp.$super.apply(this, arguments); 499 500 // Encode argument to a data structure 501 var a = { 502 nm: this.getId(), 503 h: comp.getHandle(), 504 o: dynamicProperties.join(";") 505 }; 506 507 this.$arg = a; 508 509 }.$extend(SyncOp); 510 511 ReorderOp.id = "o"; 512 syncOps[ReorderOp.id] = ReorderOp; 513 514 /** 515 * Decode and commit the SyncOp. 516 * 517 * @name ReorderOp.decodeAndCommit 518 * @function 519 * @private 520 * 521 * @param comp the Component the reorder will happen upon. 522 * @param sp the syncOp data structure to be decoded. 523 */ 524 ReorderOp.decodeAndCommit = function (comp, sp) { 525 if (comp !== null) { 526 var order = sp.o; 527 comp.reorder({ 528 "dynamicProperties": order.split(";"), 529 "cx": { commit: true, serverDecode: true } 530 }); 531 } 532 }; 533 534 /** 535 * @class Set Slot Flags SyncOp. 536 * 537 * @name SetFlagsOp 538 * @extends SyncOp 539 * @inner 540 * @private 541 * 542 * @param comp the Component for the slot the flags are being set upon. 543 * @param {baja.Slot} slot the target slot for the flags change. 544 * @param {Number} flags the new Slot flags. 545 */ 546 var SetFlagsOp = function (comp, slot, flags) { 547 SetFlagsOp.$super.apply(this, arguments); 548 549 // Encode argument to a data structure 550 var a = { 551 nm: this.getId(), 552 h: comp.getHandle(), 553 n: slot.getName(), 554 f: baja.Flags.encodeToString(flags) 555 }; 556 557 this.$arg = a; 558 559 }.$extend(SyncOp); 560 561 SetFlagsOp.id = "f"; 562 syncOps[SetFlagsOp.id] = SetFlagsOp; 563 564 /** 565 * Decode and commit the SyncOp. 566 * 567 * @private 568 * 569 * @param comp the Component the set flags op will happen upon. 570 * @param sp the syncOp data structure to be decoded. 571 */ 572 SetFlagsOp.decodeAndCommit = function (comp, sp) { 573 if (comp !== null) { 574 var name = sp.n, 575 flags = baja.Flags.decodeFromString(sp.f), 576 displayName = sp.dn, 577 display = sp.d; 578 579 comp.setFlags({ 580 "slot": name, 581 "flags": flags, 582 "cx": { commit: true, displayName: displayName, display: display, serverDecode: true } 583 }); 584 } 585 }; 586 587 /** 588 * @class Set dynamic Slot Facets SyncOp. 589 * 590 * @name SetFacetsOp 591 * @extends SyncOp 592 * @inner 593 * @private 594 * 595 * @param comp the Component for the slot the facets are being set upon. 596 * @param {baja.Slot} slot the target dynamic slot for the facets change. 597 * @param {baja.Facets} facets the new Slot facets. 598 */ 599 var SetFacetsOp = function (comp, slot, facets) { 600 SetFacetsOp.$super.apply(this, arguments); 601 602 // Encode argument to a data structure 603 var a = { 604 nm: this.getId(), 605 h: comp.getHandle(), 606 n: slot.getName(), 607 x: facets.encodeToString() 608 }; 609 610 this.$arg = a; 611 612 }.$extend(SyncOp); 613 614 SetFacetsOp.id = "x"; 615 syncOps[SetFacetsOp.id] = SetFacetsOp; 616 617 /** 618 * Decode and commit the SyncOp. 619 * 620 * @private 621 * 622 * @param comp the Component the set facets op will happen upon. 623 * @param sp the syncOp data structure to be decoded. 624 */ 625 SetFacetsOp.decodeAndCommit = function (comp, sp) { 626 if (comp !== null) { 627 var name = sp.n, 628 fcts = baja.Facets.DEFAULT.decodeFromString(bajaDef(sp.x, "")), 629 displayName = sp.dn, 630 display = sp.d; 631 632 comp.setFacets({ 633 "slot": name, 634 "facets": fcts, 635 "cx": { commit: true, displayName: displayName, display: display, serverDecode: true } 636 }); 637 } 638 }; 639 640 /** 641 * @class Add Knob SyncOp 642 * 643 * @name AddKnobOp 644 * @extends SyncOp 645 * @inner 646 * @private 647 */ 648 var AddKnobOp = function () { 649 AddKnobOp.$super.apply(this, arguments); 650 throw new Error("Unsupported"); 651 }.$extend(SyncOp); 652 653 AddKnobOp.id = "k"; 654 syncOps[AddKnobOp.id] = AddKnobOp; 655 656 /** 657 * Decode and commit the SyncOp. 658 * 659 * @name AddKnobOp.decodeAndCommit 660 * @function 661 * @private 662 * 663 * @param comp the Component the knob will be added too. 664 * @param sp the syncOp data structure to be decoded. 665 */ 666 AddKnobOp.decodeAndCommit = function (comp, sp) { 667 if (comp !== null) { 668 comp.$fw("installKnob", baja.bson.decodeKnob(sp.nk), { commit: true, serverDecode: true }); 669 } 670 }; 671 672 /** 673 * @class Remove Knob SyncOp. 674 * 675 * @name RemoveKnobOp 676 * @extends SyncOp 677 * @inner 678 * @private 679 */ 680 var RemoveKnobOp = function () { 681 RemoveKnobOp.$super.apply(this, arguments); 682 throw new Error("Unsupported"); 683 }.$extend(SyncOp); 684 685 RemoveKnobOp.id = "j"; 686 syncOps[RemoveKnobOp.id] = RemoveKnobOp; 687 688 /** 689 * Decode and commit the SyncOp. 690 * 691 * @private 692 * 693 * @param comp the Component the knob will be added too. 694 * @param sp the syncOp data structure to be decoded. 695 */ 696 RemoveKnobOp.decodeAndCommit = function (comp, sp) { 697 if (comp !== null) { 698 comp.$fw("uninstallKnob", sp.id, sp.ss, { commit: true, serverDecode: true }); 699 } 700 }; 701 702 //////////////////////////////////////////////////////////////// 703 // BOX Component Space Callbacks 704 //////////////////////////////////////////////////////////////// 705 706 /** 707 * @class BOX Callbacks plugs into a Component Space so it can make network calls. 708 * 709 * @name BoxCallbacks 710 * @inner 711 * @private 712 */ 713 var BoxCallbacks = function (space) { 714 this.$space = space; 715 }; 716 717 /** 718 * Load Slots. 719 * 720 * @private 721 * 722 * @param {String} ord 723 * @param {Number} depth 724 * @param {baja.comm.Callback} cb 725 */ 726 BoxCallbacks.prototype.loadSlots = function (ord, depth, cb) { 727 // Intermediate callback 728 var space = this.$space; 729 730 // Load Slots Argument 731 var arg = { 732 o: ord, 733 d: depth 734 }; 735 736 var failedCalled = false; 737 738 // Only call the ok if fail hasn't been called on the syncTo handler 739 cb.addOk(function (ok, fail, resp) { 740 if (!failedCalled) { 741 // Pass results of the committed syncOps into the result of the original callback 742 ok(); 743 } 744 }); 745 746 var loadSlotsCb = new Callback(baja.ok, cb.fail, cb.getBatch()); 747 748 // Mark that fail has been called 749 loadSlotsCb.addFail(function (ok, fail, err) { 750 failedCalled = true; 751 fail(err); 752 }); 753 754 // Make a call on the Server 755 baja.comm.serverHandlerCall(space.getAbsoluteOrd().toString(), 756 "loadSlots", 757 arg, 758 loadSlotsCb); 759 760 // Make a component space sync 761 this.poll(cb); 762 }; 763 764 /** 765 * Load Slot Path. 766 * 767 * @private 768 * 769 * @param {Array} slotPathInfo 770 * @param container 771 * @param {baja.comm.Callback} cb 772 * @param {Boolean} [importAsync] import any extra types asynchronously (true by default). 773 */ 774 BoxCallbacks.prototype.loadSlotPath = function (slotPathInfo, container, cb, importAsync) { 775 var space = this.$space; 776 777 importAsync = baja.def(importAsync, true); 778 779 // Intermediate callback 780 cb.addOk(function (ok, fail, resp) { 781 782 var newOk = function () { 783 // Attempt to load the Slot in the Space 784 space.$fw("commitSlotInfo", resp); 785 ok(); 786 }; 787 788 var newFail = function (err) { 789 fail(err); 790 }; 791 792 if (resp) { 793 794 // Pre-emptively scan the BSON for Types that don't exist yet or have Contracts loaded 795 // and request them in one network call 796 var unknownTypes = baja.bson.scanForUnknownTypes(resp), 797 importBatch = new baja.comm.Batch(); 798 799 if (unknownTypes.length > 0) { 800 baja.importTypes({ 801 "typeSpecs": unknownTypes, 802 "ok": newOk, 803 "fail": newFail, 804 "batch": importBatch 805 }); 806 807 if (importAsync) { 808 importBatch.commit(); 809 } 810 else { 811 importBatch.commitSync(); 812 } 813 } 814 else { 815 newOk(); 816 } 817 } 818 else { 819 newOk(); 820 } 821 }); 822 823 // Build up the argument to send to the Server 824 var arg = { 825 spi: slotPathInfo, 826 bo: container.getNavOrd().toString() 827 }; 828 829 // Make a call on the Server 830 baja.comm.serverHandlerCall(space.getAbsoluteOrd().toString(), 831 "loadSlotPath", 832 arg, 833 cb); 834 }; 835 836 /** 837 * Component Subscription. 838 * 839 * @private 840 * 841 * @param {Array} ords an array of ORDs to Components. 842 * @param {Boolean} [importAsync] make any Type and Contract imports asynchronous (false by default). 843 * @param {baja.comm.Callback} cb 844 */ 845 BoxCallbacks.prototype.subscribe = function (ords, cb, importAsync) { 846 strictArg(ords, Array); 847 importAsync = bajaDef(importAsync, false); 848 849 // Check there is something to subscribe too 850 if (ords.length === 0) { 851 throw new Error("Cannot Subscribe: nothing to subscribe too"); 852 } 853 854 // Intermediate callback 855 var space = this.$space; 856 cb.addOk(function (ok, fail, resp) { 857 var importOk = function () { 858 // Commit the sync ops 859 space.$fw("commitSyncOps", resp.e.ops); 860 861 // Pass back an array of handles that were subscribed 862 ok(resp.h); 863 }; 864 865 // Pre-emptively scan the BSON for Types that don't exist yet or have Contracts loaded 866 // and request them in one network call 867 var unknownTypes = baja.bson.scanForUnknownTypes(resp); 868 if (unknownTypes.length > 0) { 869 var importBatch = new baja.comm.Batch(); 870 baja.importTypes({ 871 "typeSpecs": unknownTypes, 872 "ok": importOk, 873 "fail": fail, 874 "batch": importBatch 875 }); 876 877 if (importAsync) { 878 importBatch.commit(); 879 } 880 else { 881 importBatch.commitSync(); 882 } 883 } 884 else { 885 importOk(); 886 } 887 }); 888 889 // Make a call on the Server 890 baja.comm.serverHandlerCall(space.getAbsoluteOrd().toString(), 891 "sub", 892 ords, 893 cb); 894 }; 895 896 /** 897 * Component Unsubscription. 898 * 899 * @private 900 * 901 * @param {Array} ords an array of Components to unsubscribe. 902 * @param {baja.comm.Callback} cb 903 */ 904 BoxCallbacks.prototype.unsubscribe = function (ords, cb) { 905 strictArg(ords, Array); 906 907 // Check there is something to subscribe too 908 if (ords.length === 0) { 909 throw new Error("Cannot Unsubscribe: nothing to unsubscribe too"); 910 } 911 912 var newOk = function (comp) { 913 try { 914 comp.$fw("fwUnsubscribed"); 915 } 916 catch (err) { 917 baja.error(err); 918 } 919 }; 920 921 // Intermediate callback 922 var space = this.$space; 923 cb.addOk(function (ok, fail, resp) { 924 // Call unsubscribed callbacks 925 var i; 926 for (i = 0; i < ords.length; ++i) { 927 baja.Ord.make(ords[i]).get({ 928 ok: newOk, 929 base: space 930 }); 931 } 932 933 ok(); 934 }); 935 936 // Make a call on the Server 937 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 938 "unsub", 939 ords, 940 cb); 941 }; 942 943 /** 944 * Invoke an Action. 945 * 946 * @private 947 * 948 * @param comp the Component the Action will be invoked upon. 949 * @param {baja.Slot} the Action Slot. 950 * @param val the argument for the Action (can be null). 951 * @param {baja.comm.Callback} cb 952 */ 953 BoxCallbacks.prototype.invokeAction = function (comp, action, val, cb) { 954 955 // Intermediate callback 956 cb.addOk(function (ok, fail, resp) { 957 // Decode the value returned 958 var v = null; 959 if (resp !== null) { 960 // TODO: scan response for unknown Types once batch end callback is fixed 961 v = bsonDecodeValue(resp, serverDecodeContext); 962 } 963 ok(v); 964 }); 965 966 var arg = { 967 h: comp.getHandle(), 968 a: action.getName() 969 }; 970 971 // Encode value if available 972 if (val !== null) { 973 arg.b = baja.bson.encodeValue(val); 974 } 975 976 // Make a call on the Server Side Component 977 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 978 "invokeAction", 979 arg, 980 cb); 981 }; 982 983 /** 984 * Get the Action Parameter Default Value. 985 * 986 * @private 987 * 988 * @param {baja.Component} comp 989 * @param {baja.Action} action 990 * @param {baja.comm.Callback} cb 991 */ 992 BoxCallbacks.prototype.getActionParameterDefault = function (comp, action, cb) { 993 // Intermediate callback to decode Action parameter default 994 cb.addOk(function (ok, fail, resp) { 995 // TODO: What about decoding Types in bulk here? 996 ok(resp === null ? null : bsonDecodeValue(resp, serverDecodeContext)); 997 }); 998 999 // Make a call to get the Action Parameter Default for this Slot 1000 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1001 "getActionParameterDefault", 1002 { "h": comp.getHandle(), "a": action.getName() }, 1003 cb); 1004 }; 1005 1006 /** 1007 * Invoke a Server Side Call. 1008 * 1009 * @private 1010 * 1011 * @param comp the Component for the Server Side Call. 1012 * @param {String} typeSpec 1013 * @param {String} methodName 1014 * @param val the argument for the Server Side Call (can be null). 1015 * @param {baja.comm.Callback} cb 1016 */ 1017 BoxCallbacks.prototype.serverSideCall = function (comp, typeSpec, methodName, val, cb) { 1018 // Add intermediate callback 1019 cb.addOk(function (ok, fail, resp) { 1020 if (resp !== null) { 1021 var importOk = function () { 1022 ok(bsonDecodeValue(resp, serverDecodeContext)); 1023 }; 1024 1025 // Pre-emptively scan the BSON for Types that don't exist yet or have Contracts loaded 1026 // and request them in one network call 1027 var unknownTypes = baja.bson.scanForUnknownTypes(resp); 1028 if (unknownTypes.length > 0) { 1029 var importBatch = new baja.comm.Batch(); 1030 baja.importTypes({ 1031 "typeSpecs": unknownTypes, 1032 "ok": importOk, 1033 "fail": fail, 1034 "batch": importBatch 1035 }); 1036 1037 if (cb.getBatch().isAsync()) { 1038 importBatch.commit(); 1039 } 1040 else { 1041 importBatch.commitSync(); 1042 } 1043 } 1044 else { 1045 importOk(); 1046 } 1047 } 1048 else { 1049 ok(null); 1050 } 1051 }); 1052 1053 // Arguments 1054 var arg = { 1055 h: comp.getHandle(), 1056 ts: typeSpec, 1057 m: methodName 1058 }; 1059 1060 // Encode value if available 1061 if (val !== null) { 1062 arg.b = baja.bson.encodeValue(val); 1063 } 1064 1065 // Make a call on the Server 1066 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1067 "serverSideCall", 1068 arg, 1069 cb); 1070 }; 1071 1072 /** 1073 * Poll the Server for events. 1074 * 1075 * @private 1076 * 1077 * @param {baja.comm.Callback} cb 1078 */ 1079 BoxCallbacks.prototype.poll = function (cb) { 1080 baja.comm.poll(cb); 1081 }; 1082 1083 /** 1084 * Convert a handle to a Slot Path. 1085 * 1086 * @private 1087 * 1088 * @param {String} handle 1089 * @param {baja.comm.Callback} cb 1090 */ 1091 BoxCallbacks.prototype.handleToPath = function (handle, cb) { 1092 // Intermediate callback to pass in SlotPath to callback 1093 cb.addOk(function (ok, fail, slotPathStr) { 1094 ok(new baja.SlotPath(slotPathStr)); 1095 }); 1096 1097 // Make a call on the Server Side Component to unsubscribe 1098 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1099 "handleToPath", 1100 handle, 1101 cb); 1102 }; 1103 1104 /** 1105 * Resolve a Service via its TypeSpec (moduleName:typeName). 1106 * 1107 * @private 1108 * 1109 * @param {String} typeSpec 1110 * @param {baja.comm.Callback} cb 1111 */ 1112 BoxCallbacks.prototype.getService = function (typeSpec, cb) { 1113 strictArg(typeSpec, String); 1114 1115 // Intermediate callback to resolve the SlotPath into the target Component 1116 var that = this; 1117 cb.addOk(function (ok, fail, slotPath) { 1118 // Resolve the SlotPath ORD 1119 baja.Ord.make(slotPath.toString()).get({ 1120 "base": that.$space, 1121 "ok": ok, 1122 "fail": fail 1123 }); 1124 }); 1125 1126 this.serviceToPath(typeSpec, cb); 1127 }; 1128 1129 /** 1130 * Resolve a Service to its SlotPath via a TypeSpec. 1131 * 1132 * @private 1133 * 1134 * @param {String} typeSpec 1135 * @param {baja.comm.Callback} cb 1136 */ 1137 BoxCallbacks.prototype.serviceToPath = function (typeSpec, cb) { 1138 // Intermediate callback to pass in SlotPath to callback 1139 cb.addOk(function (ok, fail, slotPathStr) { 1140 ok(new baja.SlotPath(slotPathStr)); 1141 }); 1142 1143 // Make a call on the Server Side Component to unsubscribe 1144 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1145 "serviceToPath", 1146 typeSpec, 1147 cb); 1148 }; 1149 1150 /** 1151 * Make a Link. 1152 * 1153 * @private 1154 * 1155 * @param {baja.Component} source Component for the link. 1156 * @param {baja.Slot} sourceSlot source Slot for the link. 1157 * @param {baja.Component} target Component for the link. 1158 * @param {baja.Slot} targetSlot target Slot for the link. 1159 * @param {baja.comm.Callback} cb 1160 */ 1161 BoxCallbacks.prototype.makeLink = function (source, sourceSlot, target, targetSlot, cb) { 1162 // Add intermediate callback 1163 cb.addOk(function (ok, fail, resp) { 1164 // TODO: Scan response for unknown Types 1165 ok(bsonDecodeValue(resp, serverDecodeContext)); 1166 }); 1167 1168 // Arguments 1169 var arg = { 1170 s: source.getHandle(), 1171 ss: sourceSlot.getName(), 1172 t: target.getHandle(), 1173 ts: targetSlot.getName() 1174 }; 1175 1176 // Make a call on the Server 1177 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1178 "makeLink", 1179 arg, 1180 cb); 1181 }; 1182 1183 /** 1184 * Get the Nav Children of a Component. 1185 * 1186 * @private 1187 * 1188 * @param {String} handle 1189 * @param {baja.comm.Callback} cb 1190 */ 1191 BoxCallbacks.prototype.getNavChildren = function (handle, cb) { 1192 // Intermediate callback to resolve Nav ORDs 1193 cb.addOk(function (ok, fail, navOrds) { 1194 // Resolve each of the Nav ORDs 1195 new baja.BatchResolve(navOrds).resolve({ 1196 ok: function () { 1197 // Pass the resolved Components to the callback handler 1198 ok(this.getTargetObjects()); 1199 }, 1200 fail: fail 1201 }); 1202 }); 1203 1204 // Make a call on the Server Side to get the Nav Children 1205 baja.comm.serverHandlerCall(this.$space.getAbsoluteOrd().toString(), 1206 "navChildren", 1207 handle, 1208 cb); 1209 }; 1210 1211 //////////////////////////////////////////////////////////////// 1212 // sendToMaster SyncOps 1213 //////////////////////////////////////////////////////////////// 1214 1215 /** 1216 * Server Add. 1217 * 1218 * @private 1219 * 1220 * @param comp the Component being added too. 1221 * @param {String} slotName 1222 * @param val the value to be added. 1223 * @param {Number} flags slot flags. 1224 * @param {baja.Facets} facets slot facets. 1225 * @param {baja.comm.Callback} cb 1226 */ 1227 BoxCallbacks.prototype.add = function (comp, slotName, val, flags, facets, cb) { 1228 // Add intermediate callback to pass back newly added Property 1229 cb.addOk(function (ok, fail, resp) { 1230 // Attempt to get newly added Property name from server response 1231 var newName = slotName; 1232 if (resp && resp instanceof Array && resp.length > 0 && resp[0].nn) { 1233 newName = resp[0].nn; 1234 } 1235 1236 // Please note: if the slot name had a wildcard in it, this won't work (i.e. 'test?') 1237 ok(comp.getSlot(newName)); 1238 }); 1239 1240 // Send the op to the Server 1241 new AddOp(comp, slotName, val, flags, facets).syncTo(comp.getComponentSpace(), cb); 1242 }; 1243 1244 /** 1245 * Server Set. 1246 * 1247 * @private 1248 * 1249 * @param comp the Component being added too. 1250 * @param {Array} propPath array of Property names used for the set. 1251 * @param val the value for the set. 1252 * @param {baja.comm.Callback} cb 1253 */ 1254 BoxCallbacks.prototype.set = function (comp, propPath, val, cb) { 1255 // Send the op to the Server 1256 new SetOp(comp, propPath, val).syncTo(comp.getComponentSpace(), cb); 1257 }; 1258 1259 /** 1260 * Server Remove. 1261 * 1262 * @private 1263 * 1264 * @param comp the Component being removed from. 1265 * @param {baja.Slot} slot the slot to be removed. 1266 * @param {baja.comm.Callback} cb 1267 */ 1268 BoxCallbacks.prototype.remove = function (comp, slot, cb) { 1269 // Send the op to the Server 1270 new RemoveOp(comp, slot).syncTo(comp.getComponentSpace(), cb); 1271 }; 1272 1273 /** 1274 * Server Rename. 1275 * 1276 * @private 1277 * 1278 * @param comp the Component the slot is being renamed on. 1279 * @param {String} oldName the old name of the slot. 1280 * @param {String} newName the new name of the slot. 1281 * @param {baja.comm.Callback} cb 1282 */ 1283 BoxCallbacks.prototype.rename = function (comp, oldName, newName, cb) { 1284 // Send the op to the Server 1285 new RenameOp(comp, oldName, newName).syncTo(comp.getComponentSpace(), cb); 1286 }; 1287 1288 /** 1289 * Server Reorder. 1290 * 1291 * @private 1292 * 1293 * @param comp the Component the dynamic slots are being reordered upon. 1294 * @param {Array} dynamicProperties an array of Property names that specify the new order. 1295 * @param {baja.comm.Callback} cb 1296 */ 1297 BoxCallbacks.prototype.reorder = function (comp, dynamicProperties, cb) { 1298 // Send the op to the Server 1299 new ReorderOp(comp, dynamicProperties).syncTo(comp.getComponentSpace(), cb); 1300 }; 1301 1302 /** 1303 * Server Set Flags. 1304 * 1305 * @private 1306 * 1307 * @param comp the Component for the slot the flags will be set upon. 1308 * @param {baja.Slot} slot the slot the flags are being set upon. 1309 * @param {Number} flags the new slot flags. 1310 * @param {baja.comm.Callback} cb 1311 */ 1312 BoxCallbacks.prototype.setFlags = function (comp, slot, flags, cb) { 1313 // Send the op to the Server 1314 new SetFlagsOp(comp, slot, flags).syncTo(comp.getComponentSpace(), cb); 1315 }; 1316 1317 /** 1318 * Server Set Facets. 1319 * 1320 * @private 1321 * 1322 * @param comp the Component for the slot the facets will be set upon. 1323 * @param {baja.Slot} slot the dynamic slot the facets are being set upon. 1324 * @param {baja.Facets} facets the new slot facets. 1325 * @param {baja.comm.Callback} cb 1326 */ 1327 BoxCallbacks.prototype.setFacets = function (comp, slot, facets, cb) { 1328 // Send the op to the Server 1329 new SetFacetsOp(comp, slot, facets).syncTo(comp.getComponentSpace(), cb); 1330 }; 1331 1332 /** 1333 * Server Topic Fire. 1334 * 1335 * @private 1336 * 1337 * @param comp the Component the Topic will be fired from. 1338 * @param {baja.Slot} slot the Topic Slot. 1339 * @param event the Topic event (can be null). 1340 * @param {baja.comm.Callback} cb 1341 */ 1342 BoxCallbacks.prototype.fire = function (comp, slot, event, cb) { 1343 // Send the op to the Server 1344 new FireTopicOp(comp, slot, event).syncTo(comp.getComponentSpace(), cb); 1345 }; 1346 1347 //////////////////////////////////////////////////////////////// 1348 // BOX Component Space 1349 //////////////////////////////////////////////////////////////// 1350 1351 /** 1352 * @class BOX Component Space. 1353 * <p> 1354 * A BOX Component Space is a Proxy Component Space that's linked to another 1355 * Component Space in another host elsewhere. 1356 * 1357 * @name baja.BoxComponentSpace 1358 * @extends baja.ComponentSpace 1359 * @private 1360 * 1361 * @param {String} name 1362 * @param {String} ordInSession 1363 * @param host 1364 */ 1365 baja.BoxComponentSpace = function (name, ordInSession, host) { 1366 baja.BoxComponentSpace.$super.apply(this, arguments); 1367 this.$callbacks = new BoxCallbacks(this); 1368 }.$extend(baja.ComponentSpace); 1369 1370 /** 1371 * Call to initialize a Component Space. 1372 * 1373 * @private 1374 * 1375 * @param {baja.comm.Batch} batch 1376 */ 1377 baja.BoxComponentSpace.prototype.init = function (batch) { 1378 1379 // Any events are sync ops so process then in the normal way 1380 var that = this; 1381 function eventHandler(events) { 1382 that.$fw("commitSyncOps", events.ops); 1383 } 1384 1385 try { 1386 // Make the server side Handler for this Component Space 1387 baja.comm.makeServerHandler(this.getAbsoluteOrd().toString(), // The id of the Server Session Handler to be created 1388 "box:ComponentSpaceSessionHandler", // Type Spec of the Server Session Handler 1389 this.getAbsoluteOrd().toString(), // Initial argument for the Server Session Handler 1390 eventHandler, 1391 new Callback(baja.ok, baja.fail, batch), 1392 /*makeInBatch*/true); 1393 1394 // Load Root Component of Station 1395 var cb = new Callback(function ok(resp) { 1396 1397 // Create the root of the Station 1398 that.$root = baja.$(resp.t); 1399 1400 // Set the core handle of the Station 1401 that.$root.$handle = resp.h; 1402 1403 // Mount the local Station root 1404 that.$fw("mount", that.$root); 1405 }, 1406 baja.fail, batch); 1407 1408 // Make a call on the Server Side Handler 1409 baja.comm.serverHandlerCall(this.getAbsoluteOrd().toString(), 1410 "loadRoot", 1411 /*Server Component Arg Call*/null, 1412 cb, 1413 /*makeInBatch*/true); 1414 } 1415 catch (err) { 1416 baja.fail(err); 1417 } 1418 }; 1419 1420 /** 1421 * Sync the Component Space. 1422 * <p> 1423 * This method will result in a network call to sync the master Space with this one. 1424 * <p> 1425 * An Object Literal is used for the method's arguments. 1426 * 1427 * @private 1428 * 1429 * @param {Object} [obj] the Object Literal for the method's arguments. 1430 * @param {Function} [obj.ok] the ok callback. Called once the Component Space has 1431 * been successfully synchronized with the Server. 1432 * @param {Function} [obj.fail] the fail callback. Called If the Component Space 1433 * can't be synchronized. 1434 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 1435 */ 1436 baja.BoxComponentSpace.prototype.sync = function (obj) { 1437 obj = baja.objectify(obj, "ok"); 1438 var cb = new Callback(obj.ok, obj.fail, obj.batch); 1439 try { 1440 this.$callbacks.poll(cb); 1441 } 1442 catch (err) { 1443 cb.fail(err); 1444 } 1445 }; 1446 1447 /** 1448 * Find the Component via its handle (null if not found). 1449 * <p> 1450 * An Object Literal is used for the method's arguments. 1451 * 1452 * @private 1453 * 1454 * @param {Object} [obj] the Object Literal for the method's arguments. 1455 * @param {Function} [obj.ok] the ok callback. Called if the Component is resolved. 1456 * The Component instance will be passed to this function. 1457 * @param {Function} [obj.fail] the fail callback. Call if there's an error or the Component 1458 * can't be resolved. 1459 * @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be batched into this object. 1460 */ 1461 baja.BoxComponentSpace.prototype.resolveByHandle = function (obj) { 1462 obj = baja.objectify(obj); 1463 1464 var handle = obj.handle, 1465 cb = new Callback(obj.ok, obj.fail, obj.batch), 1466 that = this; 1467 1468 try { 1469 var comp = this.findByHandle(handle); 1470 if (comp !== null) { 1471 cb.ok(comp); 1472 } 1473 else { 1474 1475 // Intermediate callback to resolve the SlotPath into the target Component 1476 cb.addOk(function (ok, fail, slotPath) { 1477 // Resolve the SlotPath ORD 1478 baja.Ord.make(slotPath.toString()).get({ 1479 "base": that, 1480 "ok": ok, 1481 "fail": fail 1482 }); 1483 }); 1484 1485 this.$callbacks.handleToPath(handle, cb); 1486 } 1487 } 1488 catch (err) { 1489 cb.fail(err); 1490 } 1491 }; 1492 1493 /** 1494 * Private fw method used by LoadOp for synchronizing two Components. 1495 * 1496 * @param from the from Component. 1497 * @param to the to Component. 1498 * 1499 * @private 1500 */ 1501 var emptyCommitCx = { commit: true, serverDecode: true, syncStructVals: true }; 1502 function syncComp(from, to) { 1503 1504 // Sanity check - must be same Type 1505 if (from.getType().getTypeSpec() !== to.getType().getTypeSpec()) { 1506 throw new Error("LoadOp Types differ: " + from.getType() + " - " + to.getType()); 1507 } 1508 1509 // Sanity check - must have same handle 1510 if (to.getHandle() !== from.getHandle()) { 1511 throw new Error("LoadOp Handle Error: " + from.getHandle() + " - " + to.getHandle()); 1512 } 1513 1514 // Update display name 1515 if (from.getPropertyInParent() !== null) { 1516 to.getPropertyInParent().$setDisplayName(from.getPropertyInParent().$getDisplayName()); 1517 to.getPropertyInParent().$setDisplay(from.getPropertyInParent().$getDisplay()); 1518 } 1519 1520 // Map over cached permissions 1521 to.$fw("setPermissions", from.$permissionsStr); 1522 1523 // If this component isn't loaded then don't try to sync its slots 1524 if (!from.$bPropsLoaded) { 1525 // TODO: Sync icon 1526 return; 1527 } 1528 1529 // Signal the broker properties are loaded 1530 to.$bPropsLoaded = true; 1531 1532 // Note down Properties before synchronization 1533 var pb = {}, // Properties before 1534 tslots = to.$map.$map, // Access internal OrderedMap instead of cursor for speed 1535 p; 1536 1537 for (p in tslots) { 1538 if (tslots.hasOwnProperty(p) && tslots[p].isProperty()) { 1539 pb[p] = tslots[p]; 1540 } 1541 } 1542 1543 var fslots = from.$map.$map, // Access internal OrderedMap for speed 1544 name, 1545 fromSlot, 1546 toSlot, 1547 fromFlags, 1548 fromValue, 1549 cx, 1550 reorder = false, 1551 reorderSlots; 1552 1553 for (p in fslots) { 1554 if (fslots.hasOwnProperty(p)) { 1555 1556 fromSlot = fslots[p]; 1557 name = fromSlot.getName(); 1558 toSlot = to.getSlot(name); 1559 fromFlags = from.getFlags(fromSlot); 1560 1561 if (!fromSlot.isFrozen()) { 1562 reorderSlots = reorderSlots || []; 1563 reorderSlots.push(name); 1564 } 1565 1566 // If to slot is not present then we need to add 1567 if (toSlot === null) { 1568 1569 // TODO: Handle display String 1570 cx = { commit: true, serverDecode: true, displayName: fromSlot.$getDisplayName(), display: fromSlot.$getDisplay() }; 1571 1572 fromValue = from.get(name); 1573 if (fromValue.$parent) { 1574 fromValue.$parent = null; // TODO: Hack to get around any parenting problems 1575 } 1576 to.add({ 1577 "slot": name, 1578 "value": fromValue, 1579 "flags": fromFlags, 1580 "facets": from.getFacets(fromSlot), 1581 "cx": cx 1582 }); 1583 1584 continue; 1585 } 1586 1587 // If there's already a dynamic slot on the 'to' Component then attempt a reorder 1588 if (!fromSlot.isFrozen()) { 1589 reorder = true; 1590 } 1591 1592 delete pb[name]; 1593 1594 // Set the flags if they differ 1595 if (fromFlags !== toSlot.getFlags()) { 1596 to.setFlags({ 1597 "slot": toSlot, 1598 "flags": fromFlags, 1599 "cx": emptyCommitCx 1600 }); 1601 } 1602 1603 if (!fromSlot.isProperty()) { 1604 continue; 1605 } 1606 1607 syncProps(from, fromSlot, to, toSlot); 1608 } 1609 } 1610 1611 // at this point if there were any props before that we didn't 1612 // sync we need to remove them now; since they weren't found 1613 // on "from" that means they have since been removed 1614 var removeName; 1615 for (removeName in pb) { 1616 if (pb.hasOwnProperty(removeName)) { 1617 to.remove({ 1618 "slot": pb[removeName], 1619 "cx": emptyCommitCx 1620 }); 1621 } 1622 } 1623 1624 // Sync Knobs 1625 var fromKnobs = from.$knobs, 1626 toKnobs = to.$knobs, 1627 installKnobs, uninstallKnobs, i, x; 1628 1629 if (fromKnobs) { 1630 installKnobs = []; 1631 1632 // Install any knobs missing from the to Component 1633 for (p in fromKnobs) { 1634 if (fromKnobs.hasOwnProperty(p)) { 1635 if (!toKnobs) { 1636 installKnobs.push(fromKnobs[p]); 1637 } 1638 else if (!toKnobs.hasOwnProperty(p)) { 1639 installKnobs.push(fromKnobs[p]); 1640 } 1641 } 1642 } 1643 } 1644 1645 if (toKnobs) { 1646 uninstallKnobs = []; 1647 1648 // Install any knobs missing from the to Component 1649 for (p in toKnobs) { 1650 if (toKnobs.hasOwnProperty(p)) { 1651 if (!fromKnobs) { 1652 uninstallKnobs.push(toKnobs[p]); 1653 } 1654 else if (!fromKnobs.hasOwnProperty(p)) { 1655 uninstallKnobs.push(toKnobs[p]); 1656 } 1657 } 1658 } 1659 } 1660 1661 if (installKnobs) { 1662 for (i = 0; i < installKnobs.length; ++i) { 1663 to.$fw("installKnob", installKnobs[i], emptyCommitCx); 1664 } 1665 } 1666 1667 if (uninstallKnobs) { 1668 for (x = 0; x < uninstallKnobs.length; ++x) { 1669 to.$fw("uninstallKnob", uninstallKnobs[x].getId(), uninstallKnobs[x].getSourceSlotName(), emptyCommitCx); 1670 } 1671 } 1672 1673 // Attempt reorder 1674 if (reorder && reorderSlots) { 1675 to.reorder({ 1676 dynamicProperties: reorderSlots, 1677 cx: emptyCommitCx 1678 }); 1679 } 1680 } 1681 1682 /** 1683 * Private fw method used by LoadOp for synchronizing Properties between two Components. 1684 * 1685 * @param from the from Component. 1686 * @param fromProp the from Component Property. 1687 * @param to the to Component. 1688 * @param toProp the to Component Property. 1689 * 1690 * @private 1691 */ 1692 function syncProps(from, fromProp, to, toProp) { 1693 // If neither of these Slots have been decoded then skip trying to sync! 1694 if (fromProp.isFrozen() && 1695 !fromProp.$isValueDecoded() && 1696 !toProp.$isValueDecoded() && 1697 fromProp.getType().getTypeSpec() === toProp.getType().getTypeSpec()) { 1698 return; 1699 } 1700 1701 // TODO: Update display string 1702 syncVal(from.get(fromProp), to, toProp, fromProp.$getDisplayName(), fromProp.$getDisplay()); 1703 } 1704 1705 /** 1706 * Private fw method used to synchronize a Property value in a Component. 1707 * 1708 * @param fromValue the from Value. 1709 * @param to the to Component. 1710 * @param toProp the to Component Property. 1711 * @param displayName the from display name. 1712 * @param display the from display. 1713 * 1714 * @private 1715 */ 1716 function syncVal(fromValue, to, toProp, displayName, display) { 1717 1718 var toValue = to.get(toProp), 1719 isFromValComp = fromValue.getType().isComponent(); 1720 1721 toProp.$setDisplayName(displayName); 1722 toProp.$setDisplay(display); 1723 // TODO: Update display string 1724 1725 // we need to do a full replace if: 1726 // - the type has changed 1727 // - the value is a simple 1728 // - we need to assign component handle 1729 if (fromValue.getType().getTypeSpec() !== toValue.getType().getTypeSpec() || 1730 fromValue.getType().isSimple() || 1731 (toValue.getType().isComponent() && toValue.getHandle() === null)) { 1732 1733 if (fromValue.$parent) { 1734 fromValue.$parent = null; // TODO: Hack to get around the fact that we haven't implemented newCopy yet 1735 } 1736 1737 // TODO: What about display name and display getting passed through here? 1738 to.set({ 1739 "slot": toProp, 1740 "value": fromValue, 1741 "cx": emptyCommitCx 1742 }); 1743 return; 1744 } 1745 1746 // Do a full sync if this is a Component 1747 if (isFromValComp) { 1748 syncComp(fromValue, toValue); 1749 return; 1750 } 1751 1752 // Otherwise this is a Struct so do a sync in place 1753 var fslots = fromValue.$map.$map, // Use internal OrderedMap instead of Cursors for speed 1754 nextFromProp, // From Property 1755 p; 1756 1757 for (p in fslots) { 1758 if (fslots.hasOwnProperty(p) && fslots[p].isProperty()) { 1759 nextFromProp = fslots[p]; 1760 syncProps(fromValue, nextFromProp, toValue, toValue.getSlot(nextFromProp.getName())); 1761 } 1762 } 1763 } 1764 1765 /** 1766 * Commit Slots to the Component Space. 1767 * 1768 * @param {Array} slotInfo. 1769 * 1770 * @private 1771 */ 1772 var commitSlotInfo = function (slotInfo) { 1773 var comp, cx, newVal, slot, bson, i; 1774 1775 for (i = 0; i < slotInfo.length; ++i) { 1776 1777 bson = slotInfo[i]; 1778 1779 // Attempt to find the Component 1780 comp = this.findByHandle(bson.h); 1781 1782 // Only load a singular Slot if the Component isn't already loaded 1783 // TODO: Ensure we sync with master before loadSlot is processed in 1784 // ORD resolution 1785 if (comp !== null && !comp.$bPropsLoaded) { 1786 1787 // What about mounting a Component??? 1788 1789 // Decode the Value 1790 newVal = bsonDecodeValue(bson.v, serverDecodeContext); 1791 1792 // Force any Component to be stubbed 1793 if (newVal.getType().isComponent()) { 1794 newVal.$bPropsLoaded = false; 1795 } 1796 1797 cx = { commit: true, serverDecode: true }; 1798 1799 // Add the display name if we've got one 1800 if (bson.dn) { 1801 cx.displayName = bson.dn; 1802 } 1803 1804 // Add the display string if we've got one 1805 if (bson.d) { 1806 cx.display = bson.d; 1807 } 1808 1809 // TODO: What if the Component is already fully loaded? 1810 1811 // Does the Slot currently exist? 1812 slot = comp.getSlot(bson.n); 1813 if (slot === null) { 1814 1815 // Add the Slot if it doesn't currently exist 1816 comp.add({ 1817 "slot": bson.n, 1818 "value": newVal, 1819 "flags": baja.Flags.decodeFromString(bajaDef(bson.f, "")), 1820 "facets": baja.Facets.DEFAULT.decodeFromString(bajaDef(bson.x, "")), 1821 "cx": cx 1822 }); 1823 } 1824 else { 1825 1826 // Synchronize the value 1827 syncVal(newVal, comp, slot, bson.dn, bson.d); 1828 } 1829 } 1830 } 1831 }; 1832 1833 /** 1834 * Commit the sync ops to the Component Space. 1835 * 1836 * @private 1837 * 1838 * @param {Array} syncOpsArray an array of Sync Ops to be committed to the Component Space. 1839 */ 1840 var commitSyncOps = function (syncOpsArray) { 1841 strictArg(syncOpsArray, Array); 1842 1843 // Commit all of the SyncOps 1844 var sp, // SyncOp 1845 comp, // Component, the Op happens on 1846 i; 1847 1848 for (i = 0; i < syncOpsArray.length; ++i) { 1849 sp = syncOpsArray[i]; 1850 1851 try { 1852 1853 // Get Component from SyncOp 1854 if (sp.h !== undefined) { // Was the handle encoded? 1855 comp = this.findByHandle(sp.h); 1856 1857 // Update the display name of the component 1858 if (comp && comp.getPropertyInParent() !== null) { 1859 comp.getPropertyInParent().$setDisplayName(sp.cdn); 1860 comp.getPropertyInParent().$setDisplay(sp.cd); 1861 } 1862 } 1863 else { 1864 comp = null; 1865 } 1866 1867 // Look up SyncOp, decode and Commit 1868 if (syncOps.hasOwnProperty(sp.nm) && 1869 typeof syncOps[sp.nm] === "function" && 1870 typeof syncOps[sp.nm].id === "string") { 1871 syncOps[sp.nm].decodeAndCommit(comp, sp); 1872 } 1873 } 1874 catch (err) { 1875 // Log any errors generated by committing sync ops 1876 baja.error(err); 1877 } 1878 } 1879 }; 1880 1881 /** 1882 * Private framework handler for a Component Space. 1883 * <p> 1884 * This is a private internal method for framework developers. 1885 * 1886 * @private 1887 */ 1888 baja.BoxComponentSpace.prototype.$fw = function (x, a, b, c) { 1889 if (x === "commitSyncOps") { 1890 // Process sync ops 1891 commitSyncOps.call(this, /*SyncOps*/a); 1892 } 1893 else if (x === "commitSlotInfo") { 1894 // Commit a singular Slot 1895 commitSlotInfo.call(this, /*Slot Information to Commit*/a); 1896 } 1897 else { 1898 // Else call super framework handler 1899 baja.BoxComponentSpace.$super.prototype.$fw.apply(this, arguments); 1900 } 1901 }; 1902 1903 }(baja));