[ 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 returnVal = Y.Attribute.INVALID_VALUE; 207 } 208 209 return returnVal; 210 } 211 } 212 213 }; 214 215 Parent.prototype = { 216 217 /** 218 * The destructor implementation for Parent widgets. Destroys all children. 219 * @method destructor 220 */ 221 destructor: function() { 222 this._destroyChildren(); 223 }, 224 225 /** 226 * Destroy event listener for each child Widget, responsible for removing 227 * the destroyed child Widget from the parent's internal array of children 228 * (_items property). 229 * 230 * @method _afterDestroyChild 231 * @protected 232 * @param {EventFacade} event The event facade for the attribute change. 233 */ 234 _afterDestroyChild: function (event) { 235 var child = event.target; 236 237 if (child.get("parent") == this) { 238 child.remove(); 239 } 240 }, 241 242 /** 243 * Attribute change listener for the <code>selection</code> 244 * attribute, responsible for setting the value of the 245 * parent's <code>selected</code> attribute. 246 * 247 * @method _afterSelectionChange 248 * @protected 249 * @param {EventFacade} event The event facade for the attribute change. 250 */ 251 _afterSelectionChange: function (event) { 252 253 if (event.target == this && event.src != this) { 254 255 var selection = event.newVal, 256 selectedVal = 0; // Not selected 257 258 259 if (selection) { 260 261 selectedVal = 2; // Assume partially selected, confirm otherwise 262 263 264 if (Y.instanceOf(selection, Y.ArrayList) && 265 (selection.size() === this.size())) { 266 267 selectedVal = 1; // Fully selected 268 269 } 270 271 } 272 273 this.set("selected", selectedVal, { src: this }); 274 275 } 276 }, 277 278 279 /** 280 * Attribute change listener for the <code>activeDescendant</code> 281 * attribute, responsible for setting the value of the 282 * parent's <code>activeDescendant</code> attribute. 283 * 284 * @method _afterActiveDescendantChange 285 * @protected 286 * @param {EventFacade} event The event facade for the attribute change. 287 */ 288 _afterActiveDescendantChange: function (event) { 289 var parent = this.get("parent"); 290 291 if (parent) { 292 parent._set("activeDescendant", event.newVal); 293 } 294 }, 295 296 /** 297 * Attribute change listener for the <code>selected</code> 298 * attribute, responsible for syncing the selected state of all children to 299 * match that of their parent Widget. 300 * 301 * 302 * @method _afterParentSelectedChange 303 * @protected 304 * @param {EventFacade} event The event facade for the attribute change. 305 */ 306 _afterParentSelectedChange: function (event) { 307 308 var value = event.newVal; 309 310 if (this == event.target && event.src != this && 311 (value === 0 || value === 1)) { 312 313 this.each(function (child) { 314 315 // Specify the source of this change as the parent so that 316 // value of the parent's "selection" attribute isn't 317 // recalculated 318 319 child.set("selected", value, { src: this }); 320 321 }, this); 322 323 } 324 325 }, 326 327 328 /** 329 * Default setter for <code>selection</code> attribute changes. 330 * 331 * @method _setSelection 332 * @protected 333 * @param child {Widget|Array} Widget or Array of Widget instances. 334 * @return {Widget|Array} Widget or Array of Widget instances. 335 */ 336 _setSelection: function (child) { 337 338 var selection = null, 339 selected; 340 341 if (this.get("multiple") && !this.isEmpty()) { 342 343 selected = []; 344 345 this.each(function (v) { 346 347 if (v.get("selected") > 0) { 348 selected.push(v); 349 } 350 351 }); 352 353 if (selected.length > 0) { 354 selection = selected; 355 } 356 357 } 358 else { 359 360 if (child.get("selected") > 0) { 361 selection = child; 362 } 363 364 } 365 366 return selection; 367 368 }, 369 370 371 /** 372 * Attribute change listener for the <code>selected</code> 373 * attribute of child Widgets, responsible for setting the value of the 374 * parent's <code>selection</code> attribute. 375 * 376 * @method _updateSelection 377 * @protected 378 * @param {EventFacade} event The event facade for the attribute change. 379 */ 380 _updateSelection: function (event) { 381 382 var child = event.target, 383 selection; 384 385 if (child.get("parent") == this) { 386 387 if (event.src != "_updateSelection") { 388 389 selection = this.get("selection"); 390 391 if (!this.get("multiple") && selection && event.newVal > 0) { 392 393 // Deselect the previously selected child. 394 // Set src equal to the current context to prevent 395 // unnecessary re-calculation of the selection. 396 397 selection.set("selected", 0, { src: "_updateSelection" }); 398 399 } 400 401 this._set("selection", child); 402 403 } 404 405 if (event.src == this) { 406 this._set("selection", child, { src: this }); 407 } 408 409 } 410 411 }, 412 413 /** 414 * Attribute change listener for the <code>focused</code> 415 * attribute of child Widgets, responsible for setting the value of the 416 * parent's <code>activeDescendant</code> attribute. 417 * 418 * @method _updateActiveDescendant 419 * @protected 420 * @param {EventFacade} event The event facade for the attribute change. 421 */ 422 _updateActiveDescendant: function (event) { 423 var activeDescendant = (event.newVal === true) ? event.target : null; 424 this._set("activeDescendant", activeDescendant); 425 }, 426 427 /** 428 * Creates an instance of a child Widget using the specified configuration. 429 * By default Widget instances will be created of the type specified 430 * by the <code>defaultChildType</code> attribute. Types can be explicitly 431 * defined via the <code>childType</code> property of the configuration object 432 * literal. The use of the <code>type</code> property has been deprecated, but 433 * will still be used as a fallback, if <code>childType</code> is not defined, 434 * for backwards compatibility. 435 * 436 * @method _createChild 437 * @protected 438 * @param config {Object} Object literal representing the configuration 439 * used to create an instance of a Widget. 440 */ 441 _createChild: function (config) { 442 443 var defaultType = this.get("defaultChildType"), 444 altType = config.childType || config.type, 445 child, 446 Fn, 447 FnConstructor; 448 449 if (altType) { 450 Fn = Lang.isString(altType) ? Y[altType] : altType; 451 } 452 453 if (Lang.isFunction(Fn)) { 454 FnConstructor = Fn; 455 } else if (defaultType) { 456 // defaultType is normalized to a function in it's setter 457 FnConstructor = defaultType; 458 } 459 460 if (FnConstructor) { 461 child = new FnConstructor(config); 462 } else { 463 Y.error("Could not create a child instance because its constructor is either undefined or invalid."); 464 } 465 466 return child; 467 468 }, 469 470 /** 471 * Default addChild handler 472 * 473 * @method _defAddChildFn 474 * @protected 475 * @param event {EventFacade} The Event object 476 * @param child {Widget} The Widget instance, or configuration 477 * object for the Widget to be added as a child. 478 * @param index {Number} Number representing the position at 479 * which the child will be inserted. 480 */ 481 _defAddChildFn: function (event) { 482 483 var child = event.child, 484 index = event.index, 485 children = this._items; 486 487 if (child.get("parent")) { 488 child.remove(); 489 } 490 491 if (Lang.isNumber(index)) { 492 children.splice(index, 0, child); 493 } 494 else { 495 children.push(child); 496 } 497 498 child._set("parent", this); 499 child.addTarget(this); 500 501 // Update index in case it got normalized after addition 502 // (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). 503 event.index = child.get("index"); 504 505 // TO DO: Remove in favor of using event bubbling 506 child.after("selectedChange", Y.bind(this._updateSelection, this)); 507 }, 508 509 510 /** 511 * Default removeChild handler 512 * 513 * @method _defRemoveChildFn 514 * @protected 515 * @param event {EventFacade} The Event object 516 * @param child {Widget} The Widget instance to be removed. 517 * @param index {Number} Number representing the index of the Widget to 518 * be removed. 519 */ 520 _defRemoveChildFn: function (event) { 521 522 var child = event.child, 523 index = event.index, 524 children = this._items; 525 526 if (child.get("focused")) { 527 child.blur(); // focused is readOnly, so use the public i/f to unset it 528 } 529 530 if (child.get("selected")) { 531 child.set("selected", 0); 532 } 533 534 children.splice(index, 1); 535 536 child.removeTarget(this); 537 child._oldParent = child.get("parent"); 538 child._set("parent", null); 539 }, 540 541 /** 542 * @method _add 543 * @protected 544 * @param child {Widget|Object} The Widget instance, or configuration 545 * object for the Widget to be added as a child. 546 * @param child {Array} Array of Widget instances, or configuration 547 * objects for the Widgets to be added as a children. 548 * @param index {Number} (Optional.) Number representing the position at 549 * which the child should be inserted. 550 * @description Adds a Widget as a child. If the specified Widget already 551 * has a parent it will be removed from its current parent before 552 * being added as a child. 553 * @return {Widget|Array} Successfully added Widget or Array containing the 554 * successfully added Widget instance(s). If no children where added, will 555 * will return undefined. 556 */ 557 _add: function (child, index) { 558 559 var children, 560 oChild, 561 returnVal; 562 563 564 if (Lang.isArray(child)) { 565 566 children = []; 567 568 Y.each(child, function (v, k) { 569 570 oChild = this._add(v, (index + k)); 571 572 if (oChild) { 573 children.push(oChild); 574 } 575 576 }, this); 577 578 579 if (children.length > 0) { 580 returnVal = children; 581 } 582 583 } 584 else { 585 586 if (Y.instanceOf(child, Y.Widget)) { 587 oChild = child; 588 } 589 else { 590 oChild = this._createChild(child); 591 } 592 593 if (oChild && this.fire("addChild", { child: oChild, index: index })) { 594 returnVal = oChild; 595 } 596 597 } 598 599 return returnVal; 600 601 }, 602 603 604 /** 605 * @method add 606 * @param child {Widget|Object} The Widget instance, or configuration 607 * object for the Widget to be added as a child. The configuration object 608 * for the child can include a <code>childType</code> property, which is either 609 * a constructor function or a string which names a constructor function on the 610 * Y instance (e.g. "Tab" would refer to Y.Tab) (<code>childType</code> used to be 611 * named <code>type</code>, support for which has been deprecated, but is still 612 * maintained for backward compatibility. <code>childType</code> takes precedence 613 * over <code>type</code> if both are defined. 614 * @param child {Array} Array of Widget instances, or configuration 615 * objects for the Widgets to be added as a children. 616 * @param index {Number} (Optional.) Number representing the position at 617 * which the child should be inserted. 618 * @description Adds a Widget as a child. If the specified Widget already 619 * has a parent it will be removed from its current parent before 620 * being added as a child. 621 * @return {ArrayList} Y.ArrayList containing the successfully added 622 * Widget instance(s). If no children where added, will return an empty 623 * Y.ArrayList instance. 624 */ 625 add: function () { 626 627 var added = this._add.apply(this, arguments), 628 children = added ? (Lang.isArray(added) ? added : [added]) : []; 629 630 return (new Y.ArrayList(children)); 631 632 }, 633 634 635 /** 636 * @method remove 637 * @param index {Number} (Optional.) Number representing the index of the 638 * child to be removed. 639 * @description Removes the Widget from its parent. Optionally, can remove 640 * a child by specifying its index. 641 * @return {Widget} Widget instance that was successfully removed, otherwise 642 * undefined. 643 */ 644 remove: function (index) { 645 646 var child = this._items[index], 647 returnVal; 648 649 if (child && this.fire("removeChild", { child: child, index: index })) { 650 returnVal = child; 651 } 652 653 return returnVal; 654 655 }, 656 657 658 /** 659 * @method removeAll 660 * @description Removes all of the children from the Widget. 661 * @return {ArrayList} Y.ArrayList instance containing Widgets that were 662 * successfully removed. If no children where removed, will return an empty 663 * Y.ArrayList instance. 664 */ 665 removeAll: function () { 666 667 var removed = [], 668 child; 669 670 Y.each(this._items.concat(), function () { 671 672 child = this.remove(0); 673 674 if (child) { 675 removed.push(child); 676 } 677 678 }, this); 679 680 return (new Y.ArrayList(removed)); 681 682 }, 683 684 /** 685 * Selects the child at the given index (zero-based). 686 * 687 * @method selectChild 688 * @param {Number} i the index of the child to be selected 689 */ 690 selectChild: function(i) { 691 this.item(i).set('selected', 1); 692 }, 693 694 /** 695 * Selects all children. 696 * 697 * @method selectAll 698 */ 699 selectAll: function () { 700 this.set("selected", 1); 701 }, 702 703 /** 704 * Deselects all children. 705 * 706 * @method deselectAll 707 */ 708 deselectAll: function () { 709 this.set("selected", 0); 710 }, 711 712 /** 713 * Updates the UI in response to a child being added. 714 * 715 * @method _uiAddChild 716 * @protected 717 * @param child {Widget} The child Widget instance to render. 718 * @param parentNode {Object} The Node under which the 719 * child Widget is to be rendered. 720 */ 721 _uiAddChild: function (child, parentNode) { 722 723 child.render(parentNode); 724 725 // TODO: Ideally this should be in Child's render UI. 726 727 var childBB = child.get("boundingBox"), 728 siblingBB, 729 nextSibling = child.next(false), 730 prevSibling; 731 732 // Insert or Append to last child. 733 734 // Avoiding index, and using the current sibling 735 // state (which should be accurate), means we don't have 736 // to worry about decorator elements which may be added 737 // to the _childContainer node. 738 739 if (nextSibling && nextSibling.get(RENDERED)) { 740 741 siblingBB = nextSibling.get(BOUNDING_BOX); 742 siblingBB.insert(childBB, "before"); 743 744 } else { 745 746 prevSibling = child.previous(false); 747 748 if (prevSibling && prevSibling.get(RENDERED)) { 749 750 siblingBB = prevSibling.get(BOUNDING_BOX); 751 siblingBB.insert(childBB, "after"); 752 753 } else if (!parentNode.contains(childBB)) { 754 755 // Based on pull request from andreas-karlsson 756 // https://github.com/yui/yui3/pull/25#issuecomment-2103536 757 758 // Account for case where a child was rendered independently of the 759 // parent-child framework, to a node outside of the parentNode, 760 // and there are no siblings. 761 762 parentNode.appendChild(childBB); 763 } 764 } 765 766 }, 767 768 /** 769 * Updates the UI in response to a child being removed. 770 * 771 * @method _uiRemoveChild 772 * @protected 773 * @param child {Widget} The child Widget instance to render. 774 */ 775 _uiRemoveChild: function (child) { 776 child.get("boundingBox").remove(); 777 }, 778 779 _afterAddChild: function (event) { 780 var child = event.child; 781 782 if (child.get("parent") == this) { 783 this._uiAddChild(child, this._childrenContainer); 784 } 785 }, 786 787 _afterRemoveChild: function (event) { 788 var child = event.child; 789 790 if (child._oldParent == this) { 791 this._uiRemoveChild(child); 792 } 793 }, 794 795 /** 796 * Sets up DOM and CustomEvent listeners for the parent widget. 797 * <p> 798 * This method in invoked after bindUI is invoked for the Widget class 799 * using YUI's aop infrastructure. 800 * </p> 801 * 802 * @method _bindUIParent 803 * @protected 804 */ 805 _bindUIParent: function () { 806 this.after("addChild", this._afterAddChild); 807 this.after("removeChild", this._afterRemoveChild); 808 }, 809 810 /** 811 * Renders all child Widgets for the parent. 812 * <p> 813 * This method in invoked after renderUI is invoked for the Widget class 814 * using YUI's aop infrastructure. 815 * </p> 816 * @method _renderChildren 817 * @protected 818 */ 819 _renderChildren: function () { 820 821 /** 822 * <p>By default WidgetParent will render it's children to the parent's content box.</p> 823 * 824 * <p>If the children need to be rendered somewhere else, the _childrenContainer property 825 * can be set to the Node which the children should be rendered to. This property should be 826 * set before the _renderChildren method is invoked, ideally in your renderUI method, 827 * as soon as you create the element to be rendered to.</p> 828 * 829 * @protected 830 * @property _childrenContainer 831 * @value The content box 832 * @type Node 833 */ 834 var renderTo = this._childrenContainer || this.get("contentBox"); 835 836 this._childrenContainer = renderTo; 837 838 this.each(function (child) { 839 child.render(renderTo); 840 }); 841 }, 842 843 /** 844 * Destroys all child Widgets for the parent. 845 * <p> 846 * This method is invoked before the destructor is invoked for the Widget 847 * class using YUI's aop infrastructure. 848 * </p> 849 * @method _destroyChildren 850 * @protected 851 */ 852 _destroyChildren: function () { 853 854 // Detach the handler responsible for removing children in 855 // response to destroying them since: 856 // 1) It is unnecessary/inefficient at this point since we are doing 857 // a batch destroy of all children. 858 // 2) Removing each child will affect our ability to iterate the 859 // children since the size of _items will be changing as we 860 // iterate. 861 this._hDestroyChild.detach(); 862 863 // Need to clone the _items array since 864 this.each(function (child) { 865 child.destroy(); 866 }); 867 } 868 869 }; 870 871 Y.augment(Parent, Y.ArrayList); 872 873 Y.WidgetParent = Parent; 874 875 876 }, '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 |