[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('widget-parent', function (Y, NAME) { 9 10 /** 11 * Extension enabling a Widget to be a parent of another Widget. 12 * 13 * @module widget-parent 14 */ 15 16 var Lang = Y.Lang, 17 RENDERED = "rendered", 18 BOUNDING_BOX = "boundingBox"; 19 20 /** 21 * Widget extension providing functionality enabling a Widget to be a 22 * parent of another Widget. 23 * 24 * <p>In addition to the set of attributes supported by WidgetParent, the constructor 25 * configuration object can also contain a <code>children</code> which can be used 26 * to add child widgets to the parent during construction. The <code>children</code> 27 * property is an array of either child widget instances or child widget configuration 28 * objects, and is sugar for the <a href="#method_add">add</a> method. See the 29 * <a href="#method_add">add</a> for details on the structure of the child widget 30 * configuration object. 31 * @class WidgetParent 32 * @constructor 33 * @uses ArrayList 34 * @param {Object} config User configuration object. 35 */ 36 function Parent(config) { 37 38 /** 39 * Fires when a Widget is add as a child. The event object will have a 40 * 'child' property that returns a reference to the child Widget, as well 41 * as an 'index' property that returns a reference to the index specified 42 * when the add() method was called. 43 * <p> 44 * Subscribers to the "on" moment of this event, will be notified 45 * before a child is added. 46 * </p> 47 * <p> 48 * Subscribers to the "after" moment of this event, will be notified 49 * after a child is added. 50 * </p> 51 * 52 * @event addChild 53 * @preventable _defAddChildFn 54 * @param {EventFacade} e The Event Facade 55 */ 56 this.publish("addChild", { 57 defaultTargetOnly: true, 58 defaultFn: this._defAddChildFn 59 }); 60 61 62 /** 63 * Fires when a child Widget is removed. The event object will have a 64 * 'child' property that returns a reference to the child Widget, as well 65 * as an 'index' property that returns a reference child's ordinal position. 66 * <p> 67 * Subscribers to the "on" moment of this event, will be notified 68 * before a child is removed. 69 * </p> 70 * <p> 71 * Subscribers to the "after" moment of this event, will be notified 72 * after a child is removed. 73 * </p> 74 * 75 * @event removeChild 76 * @preventable _defRemoveChildFn 77 * @param {EventFacade} e The Event Facade 78 */ 79 this.publish("removeChild", { 80 defaultTargetOnly: true, 81 defaultFn: this._defRemoveChildFn 82 }); 83 84 this._items = []; 85 86 var children, 87 handle; 88 89 if (config && config.children) { 90 91 children = config.children; 92 93 handle = this.after("initializedChange", function (e) { 94 this._add(children); 95 handle.detach(); 96 }); 97 98 } 99 100 // Widget method overlap 101 Y.after(this._renderChildren, this, "renderUI"); 102 Y.after(this._bindUIParent, this, "bindUI"); 103 104 this.after("selectionChange", this._afterSelectionChange); 105 this.after("selectedChange", this._afterParentSelectedChange); 106 this.after("activeDescendantChange", this._afterActiveDescendantChange); 107 108 this._hDestroyChild = this.after("*:destroy", this._afterDestroyChild); 109 this.after("*:focusedChange", this._updateActiveDescendant); 110 111 } 112 113 Parent.ATTRS = { 114 115 /** 116 * @attribute defaultChildType 117 * @type {String|Object} 118 * 119 * @description String representing the default type of the children 120 * managed by this Widget. Can also supply default type as a constructor 121 * reference. 122 */ 123 defaultChildType: { 124 setter: function (val) { 125 126 var returnVal = Y.Attribute.INVALID_VALUE, 127 FnConstructor = Lang.isString(val) ? Y[val] : val; 128 129 if (Lang.isFunction(FnConstructor)) { 130 returnVal = FnConstructor; 131 } 132 133 return returnVal; 134 } 135 }, 136 137 /** 138 * @attribute activeDescendant 139 * @type Widget 140 * @readOnly 141 * 142 * @description Returns the Widget's currently focused descendant Widget. 143 */ 144 activeDescendant: { 145 readOnly: true 146 }, 147 148 /** 149 * @attribute multiple 150 * @type Boolean 151 * @default false 152 * @writeOnce 153 * 154 * @description Boolean indicating if multiple children can be selected at 155 * once. Whether or not multiple selection is enabled is always delegated 156 * to the value of the <code>multiple</code> attribute of the root widget 157 * in the object hierarchy. 158 */ 159 multiple: { 160 value: false, 161 validator: Lang.isBoolean, 162 writeOnce: true, 163 getter: function (value) { 164 var root = this.get("root"); 165 return (root && root != this) ? root.get("multiple") : value; 166 } 167 }, 168 169 170 /** 171 * @attribute selection 172 * @type {ArrayList|Widget} 173 * @readOnly 174 * 175 * @description Returns the currently selected child Widget. If the 176 * <code>mulitple</code> attribte is set to <code>true</code> will 177 * return an Y.ArrayList instance containing the currently selected 178 * children. If no children are selected, will return null. 179 */ 180 selection: { 181 readOnly: true, 182 setter: "_setSelection", 183 getter: function (value) { 184 var selection = Lang.isArray(value) ? 185 (new Y.ArrayList(value)) : value; 186 return selection; 187 } 188 }, 189 190 selected: { 191 setter: function (value) { 192 193 // Enforces selection behavior on for parent Widgets. Parent's 194 // selected attribute can be set to the following: 195 // 0 - Not selected 196 // 1 - Fully selected (all children are selected). In order for 197 // all children to be selected, multiple selection must be 198 // enabled. Therefore, you cannot set the "selected" attribute 199 // on a parent Widget to 1 unless multiple selection is enabled. 200 // 2 - Partially selected, meaning one ore more (but not all) 201 // children are selected. 202 203 var returnVal = value; 204 205 if (value === 1 && !this.get("multiple")) { 206 Y.log('The selected attribute can only be set to 1 if the "multiple" attribute is set to true.', "error", "widget"); 207 returnVal = Y.Attribute.INVALID_VALUE; 208 } 209 210 return returnVal; 211 } 212 } 213 214 }; 215 216 Parent.prototype = { 217 218 /** 219 * The destructor implementation for Parent widgets. Destroys all children. 220 * @method destructor 221 */ 222 destructor: function() { 223 this._destroyChildren(); 224 }, 225 226 /** 227 * Destroy event listener for each child Widget, responsible for removing 228 * the destroyed child Widget from the parent's internal array of children 229 * (_items property). 230 * 231 * @method _afterDestroyChild 232 * @protected 233 * @param {EventFacade} event The event facade for the attribute change. 234 */ 235 _afterDestroyChild: function (event) { 236 var child = event.target; 237 238 if (child.get("parent") == this) { 239 child.remove(); 240 } 241 }, 242 243 /** 244 * Attribute change listener for the <code>selection</code> 245 * attribute, responsible for setting the value of the 246 * parent's <code>selected</code> attribute. 247 * 248 * @method _afterSelectionChange 249 * @protected 250 * @param {EventFacade} event The event facade for the attribute change. 251 */ 252 _afterSelectionChange: function (event) { 253 254 if (event.target == this && event.src != this) { 255 256 var selection = event.newVal, 257 selectedVal = 0; // Not selected 258 259 260 if (selection) { 261 262 selectedVal = 2; // Assume partially selected, confirm otherwise 263 264 265 if (Y.instanceOf(selection, Y.ArrayList) && 266 (selection.size() === this.size())) { 267 268 selectedVal = 1; // Fully selected 269 270 } 271 272 } 273 274 this.set("selected", selectedVal, { src: this }); 275 276 } 277 }, 278 279 280 /** 281 * Attribute change listener for the <code>activeDescendant</code> 282 * attribute, responsible for setting the value of the 283 * parent's <code>activeDescendant</code> attribute. 284 * 285 * @method _afterActiveDescendantChange 286 * @protected 287 * @param {EventFacade} event The event facade for the attribute change. 288 */ 289 _afterActiveDescendantChange: function (event) { 290 var parent = this.get("parent"); 291 292 if (parent) { 293 parent._set("activeDescendant", event.newVal); 294 } 295 }, 296 297 /** 298 * Attribute change listener for the <code>selected</code> 299 * attribute, responsible for syncing the selected state of all children to 300 * match that of their parent Widget. 301 * 302 * 303 * @method _afterParentSelectedChange 304 * @protected 305 * @param {EventFacade} event The event facade for the attribute change. 306 */ 307 _afterParentSelectedChange: function (event) { 308 309 var value = event.newVal; 310 311 if (this == event.target && event.src != this && 312 (value === 0 || value === 1)) { 313 314 this.each(function (child) { 315 316 // Specify the source of this change as the parent so that 317 // value of the parent's "selection" attribute isn't 318 // recalculated 319 320 child.set("selected", value, { src: this }); 321 322 }, this); 323 324 } 325 326 }, 327 328 329 /** 330 * Default setter for <code>selection</code> attribute changes. 331 * 332 * @method _setSelection 333 * @protected 334 * @param child {Widget|Array} Widget or Array of Widget instances. 335 * @return {Widget|Array} Widget or Array of Widget instances. 336 */ 337 _setSelection: function (child) { 338 339 var selection = null, 340 selected; 341 342 if (this.get("multiple") && !this.isEmpty()) { 343 344 selected = []; 345 346 this.each(function (v) { 347 348 if (v.get("selected") > 0) { 349 selected.push(v); 350 } 351 352 }); 353 354 if (selected.length > 0) { 355 selection = selected; 356 } 357 358 } 359 else { 360 361 if (child.get("selected") > 0) { 362 selection = child; 363 } 364 365 } 366 367 return selection; 368 369 }, 370 371 372 /** 373 * Attribute change listener for the <code>selected</code> 374 * attribute of child Widgets, responsible for setting the value of the 375 * parent's <code>selection</code> attribute. 376 * 377 * @method _updateSelection 378 * @protected 379 * @param {EventFacade} event The event facade for the attribute change. 380 */ 381 _updateSelection: function (event) { 382 383 var child = event.target, 384 selection; 385 386 if (child.get("parent") == this) { 387 388 if (event.src != "_updateSelection") { 389 390 selection = this.get("selection"); 391 392 if (!this.get("multiple") && selection && event.newVal > 0) { 393 394 // Deselect the previously selected child. 395 // Set src equal to the current context to prevent 396 // unnecessary re-calculation of the selection. 397 398 selection.set("selected", 0, { src: "_updateSelection" }); 399 400 } 401 402 this._set("selection", child); 403 404 } 405 406 if (event.src == this) { 407 this._set("selection", child, { src: this }); 408 } 409 410 } 411 412 }, 413 414 /** 415 * Attribute change listener for the <code>focused</code> 416 * attribute of child Widgets, responsible for setting the value of the 417 * parent's <code>activeDescendant</code> attribute. 418 * 419 * @method _updateActiveDescendant 420 * @protected 421 * @param {EventFacade} event The event facade for the attribute change. 422 */ 423 _updateActiveDescendant: function (event) { 424 var activeDescendant = (event.newVal === true) ? event.target : null; 425 this._set("activeDescendant", activeDescendant); 426 }, 427 428 /** 429 * Creates an instance of a child Widget using the specified configuration. 430 * By default Widget instances will be created of the type specified 431 * by the <code>defaultChildType</code> attribute. Types can be explicitly 432 * defined via the <code>childType</code> property of the configuration object 433 * literal. The use of the <code>type</code> property has been deprecated, but 434 * will still be used as a fallback, if <code>childType</code> is not defined, 435 * for backwards compatibility. 436 * 437 * @method _createChild 438 * @protected 439 * @param config {Object} Object literal representing the configuration 440 * used to create an instance of a Widget. 441 */ 442 _createChild: function (config) { 443 444 var defaultType = this.get("defaultChildType"), 445 altType = config.childType || config.type, 446 child, 447 Fn, 448 FnConstructor; 449 450 if (altType) { 451 Fn = Lang.isString(altType) ? Y[altType] : altType; 452 } 453 454 if (Lang.isFunction(Fn)) { 455 FnConstructor = Fn; 456 } else if (defaultType) { 457 // defaultType is normalized to a function in it's setter 458 FnConstructor = defaultType; 459 } 460 461 if (FnConstructor) { 462 child = new FnConstructor(config); 463 } else { 464 Y.error("Could not create a child instance because its constructor is either undefined or invalid."); 465 } 466 467 return child; 468 469 }, 470 471 /** 472 * Default addChild handler 473 * 474 * @method _defAddChildFn 475 * @protected 476 * @param event {EventFacade} The Event object 477 * @param child {Widget} The Widget instance, or configuration 478 * object for the Widget to be added as a child. 479 * @param index {Number} Number representing the position at 480 * which the child will be inserted. 481 */ 482 _defAddChildFn: function (event) { 483 484 var child = event.child, 485 index = event.index, 486 children = this._items; 487 488 if (child.get("parent")) { 489 child.remove(); 490 } 491 492 if (Lang.isNumber(index)) { 493 children.splice(index, 0, child); 494 } 495 else { 496 children.push(child); 497 } 498 499 child._set("parent", this); 500 child.addTarget(this); 501 502 // Update index in case it got normalized after addition 503 // (e.g. user passed in 10, and there are only 3 items, the actual index would be 3. We don't want to pass 10 around in the event facade). 504 event.index = child.get("index"); 505 506 // TO DO: Remove in favor of using event bubbling 507 child.after("selectedChange", Y.bind(this._updateSelection, this)); 508 }, 509 510 511 /** 512 * Default removeChild handler 513 * 514 * @method _defRemoveChildFn 515 * @protected 516 * @param event {EventFacade} The Event object 517 * @param child {Widget} The Widget instance to be removed. 518 * @param index {Number} Number representing the index of the Widget to 519 * be removed. 520 */ 521 _defRemoveChildFn: function (event) { 522 523 var child = event.child, 524 index = event.index, 525 children = this._items; 526 527 if (child.get("focused")) { 528 child.blur(); // focused is readOnly, so use the public i/f to unset it 529 } 530 531 if (child.get("selected")) { 532 child.set("selected", 0); 533 } 534 535 children.splice(index, 1); 536 537 child.removeTarget(this); 538 child._oldParent = child.get("parent"); 539 child._set("parent", null); 540 }, 541 542 /** 543 * @method _add 544 * @protected 545 * @param child {Widget|Object} The Widget instance, or configuration 546 * object for the Widget to be added as a child. 547 * @param child {Array} Array of Widget instances, or configuration 548 * objects for the Widgets to be added as a children. 549 * @param index {Number} (Optional.) Number representing the position at 550 * which the child should be inserted. 551 * @description Adds a Widget as a child. If the specified Widget already 552 * has a parent it will be removed from its current parent before 553 * being added as a child. 554 * @return {Widget|Array} Successfully added Widget or Array containing the 555 * successfully added Widget instance(s). If no children where added, will 556 * will return undefined. 557 */ 558 _add: function (child, index) { 559 560 var children, 561 oChild, 562 returnVal; 563 564 565 if (Lang.isArray(child)) { 566 567 children = []; 568 569 Y.each(child, function (v, k) { 570 571 oChild = this._add(v, (index + k)); 572 573 if (oChild) { 574 children.push(oChild); 575 } 576 577 }, this); 578 579 580 if (children.length > 0) { 581 returnVal = children; 582 } 583 584 } 585 else { 586 587 if (Y.instanceOf(child, Y.Widget)) { 588 oChild = child; 589 } 590 else { 591 oChild = this._createChild(child); 592 } 593 594 if (oChild && this.fire("addChild", { child: oChild, index: index })) { 595 returnVal = oChild; 596 } 597 598 } 599 600 return returnVal; 601 602 }, 603 604 605 /** 606 * @method add 607 * @param child {Widget|Object} The Widget instance, or configuration 608 * object for the Widget to be added as a child. The configuration object 609 * for the child can include a <code>childType</code> property, which is either 610 * a constructor function or a string which names a constructor function on the 611 * Y instance (e.g. "Tab" would refer to Y.Tab) (<code>childType</code> used to be 612 * named <code>type</code>, support for which has been deprecated, but is still 613 * maintained for backward compatibility. <code>childType</code> takes precedence 614 * over <code>type</code> if both are defined. 615 * @param child {Array} Array of Widget instances, or configuration 616 * objects for the Widgets to be added as a children. 617 * @param index {Number} (Optional.) Number representing the position at 618 * which the child should be inserted. 619 * @description Adds a Widget as a child. If the specified Widget already 620 * has a parent it will be removed from its current parent before 621 * being added as a child. 622 * @return {ArrayList} Y.ArrayList containing the successfully added 623 * Widget instance(s). If no children where added, will return an empty 624 * Y.ArrayList instance. 625 */ 626 add: function () { 627 628 var added = this._add.apply(this, arguments), 629 children = added ? (Lang.isArray(added) ? added : [added]) : []; 630 631 return (new Y.ArrayList(children)); 632 633 }, 634 635 636 /** 637 * @method remove 638 * @param index {Number} (Optional.) Number representing the index of the 639 * child to be removed. 640 * @description Removes the Widget from its parent. Optionally, can remove 641 * a child by specifying its index. 642 * @return {Widget} Widget instance that was successfully removed, otherwise 643 * undefined. 644 */ 645 remove: function (index) { 646 647 var child = this._items[index], 648 returnVal; 649 650 if (child && this.fire("removeChild", { child: child, index: index })) { 651 returnVal = child; 652 } 653 654 return returnVal; 655 656 }, 657 658 659 /** 660 * @method removeAll 661 * @description Removes all of the children from the Widget. 662 * @return {ArrayList} Y.ArrayList instance containing Widgets that were 663 * successfully removed. If no children where removed, will return an empty 664 * Y.ArrayList instance. 665 */ 666 removeAll: function () { 667 668 var removed = [], 669 child; 670 671 Y.each(this._items.concat(), function () { 672 673 child = this.remove(0); 674 675 if (child) { 676 removed.push(child); 677 } 678 679 }, this); 680 681 return (new Y.ArrayList(removed)); 682 683 }, 684 685 /** 686 * Selects the child at the given index (zero-based). 687 * 688 * @method selectChild 689 * @param {Number} i the index of the child to be selected 690 */ 691 selectChild: function(i) { 692 this.item(i).set('selected', 1); 693 }, 694 695 /** 696 * Selects all children. 697 * 698 * @method selectAll 699 */ 700 selectAll: function () { 701 this.set("selected", 1); 702 }, 703 704 /** 705 * Deselects all children. 706 * 707 * @method deselectAll 708 */ 709 deselectAll: function () { 710 this.set("selected", 0); 711 }, 712 713 /** 714 * Updates the UI in response to a child being added. 715 * 716 * @method _uiAddChild 717 * @protected 718 * @param child {Widget} The child Widget instance to render. 719 * @param parentNode {Object} The Node under which the 720 * child Widget is to be rendered. 721 */ 722 _uiAddChild: function (child, parentNode) { 723 724 child.render(parentNode); 725 726 // TODO: Ideally this should be in Child's render UI. 727 728 var childBB = child.get("boundingBox"), 729 siblingBB, 730 nextSibling = child.next(false), 731 prevSibling; 732 733 // Insert or Append to last child. 734 735 // Avoiding index, and using the current sibling 736 // state (which should be accurate), means we don't have 737 // to worry about decorator elements which may be added 738 // to the _childContainer node. 739 740 if (nextSibling && nextSibling.get(RENDERED)) { 741 742 siblingBB = nextSibling.get(BOUNDING_BOX); 743 siblingBB.insert(childBB, "before"); 744 745 } else { 746 747 prevSibling = child.previous(false); 748 749 if (prevSibling && prevSibling.get(RENDERED)) { 750 751 siblingBB = prevSibling.get(BOUNDING_BOX); 752 siblingBB.insert(childBB, "after"); 753 754 } else if (!parentNode.contains(childBB)) { 755 756 // Based on pull request from andreas-karlsson 757 // https://github.com/yui/yui3/pull/25#issuecomment-2103536 758 759 // Account for case where a child was rendered independently of the 760 // parent-child framework, to a node outside of the parentNode, 761 // and there are no siblings. 762 763 parentNode.appendChild(childBB); 764 } 765 } 766 767 }, 768 769 /** 770 * Updates the UI in response to a child being removed. 771 * 772 * @method _uiRemoveChild 773 * @protected 774 * @param child {Widget} The child Widget instance to render. 775 */ 776 _uiRemoveChild: function (child) { 777 child.get("boundingBox").remove(); 778 }, 779 780 _afterAddChild: function (event) { 781 var child = event.child; 782 783 if (child.get("parent") == this) { 784 this._uiAddChild(child, this._childrenContainer); 785 } 786 }, 787 788 _afterRemoveChild: function (event) { 789 var child = event.child; 790 791 if (child._oldParent == this) { 792 this._uiRemoveChild(child); 793 } 794 }, 795 796 /** 797 * Sets up DOM and CustomEvent listeners for the parent widget. 798 * <p> 799 * This method in invoked after bindUI is invoked for the Widget class 800 * using YUI's aop infrastructure. 801 * </p> 802 * 803 * @method _bindUIParent 804 * @protected 805 */ 806 _bindUIParent: function () { 807 this.after("addChild", this._afterAddChild); 808 this.after("removeChild", this._afterRemoveChild); 809 }, 810 811 /** 812 * Renders all child Widgets for the parent. 813 * <p> 814 * This method in invoked after renderUI is invoked for the Widget class 815 * using YUI's aop infrastructure. 816 * </p> 817 * @method _renderChildren 818 * @protected 819 */ 820 _renderChildren: function () { 821 822 /** 823 * <p>By default WidgetParent will render it's children to the parent's content box.</p> 824 * 825 * <p>If the children need to be rendered somewhere else, the _childrenContainer property 826 * can be set to the Node which the children should be rendered to. This property should be 827 * set before the _renderChildren method is invoked, ideally in your renderUI method, 828 * as soon as you create the element to be rendered to.</p> 829 * 830 * @protected 831 * @property _childrenContainer 832 * @value The content box 833 * @type Node 834 */ 835 var renderTo = this._childrenContainer || this.get("contentBox"); 836 837 this._childrenContainer = renderTo; 838 839 this.each(function (child) { 840 child.render(renderTo); 841 }); 842 }, 843 844 /** 845 * Destroys all child Widgets for the parent. 846 * <p> 847 * This method is invoked before the destructor is invoked for the Widget 848 * class using YUI's aop infrastructure. 849 * </p> 850 * @method _destroyChildren 851 * @protected 852 */ 853 _destroyChildren: function () { 854 855 // Detach the handler responsible for removing children in 856 // response to destroying them since: 857 // 1) It is unnecessary/inefficient at this point since we are doing 858 // a batch destroy of all children. 859 // 2) Removing each child will affect our ability to iterate the 860 // children since the size of _items will be changing as we 861 // iterate. 862 this._hDestroyChild.detach(); 863 864 // Need to clone the _items array since 865 this.each(function (child) { 866 child.destroy(); 867 }); 868 } 869 870 }; 871 872 Y.augment(Parent, Y.ArrayList); 873 874 Y.WidgetParent = Parent; 875 876 877 }, '3.17.2', {"requires": ["arraylist", "base-build", "widget"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |