[ 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-buttons', function (Y, NAME) { 9 10 /** 11 Provides header/body/footer button support for Widgets that use the 12 `WidgetStdMod` extension. 13 14 @module widget-buttons 15 @since 3.4.0 16 **/ 17 18 var YArray = Y.Array, 19 YLang = Y.Lang, 20 YObject = Y.Object, 21 22 ButtonPlugin = Y.Plugin.Button, 23 Widget = Y.Widget, 24 WidgetStdMod = Y.WidgetStdMod, 25 26 getClassName = Y.ClassNameManager.getClassName, 27 isArray = YLang.isArray, 28 isNumber = YLang.isNumber, 29 isString = YLang.isString, 30 isValue = YLang.isValue; 31 32 // Utility to determine if an object is a Y.Node instance, even if it was 33 // created in a different YUI sandbox. 34 function isNode(node) { 35 return !!node.getDOMNode; 36 } 37 38 /** 39 Provides header/body/footer button support for Widgets that use the 40 `WidgetStdMod` extension. 41 42 This Widget extension makes it easy to declaratively configure a widget's 43 buttons. It adds a `buttons` attribute along with button- accessor and mutator 44 methods. All button nodes have the `Y.Plugin.Button` plugin applied. 45 46 This extension also includes `HTML_PARSER` support to seed a widget's `buttons` 47 from those which already exist in its DOM. 48 49 @class WidgetButtons 50 @extensionfor Widget 51 @since 3.4.0 52 **/ 53 function WidgetButtons() { 54 // Has to be setup before the `initializer()`. 55 this._buttonsHandles = {}; 56 } 57 58 WidgetButtons.ATTRS = { 59 /** 60 Collection containing a widget's buttons. 61 62 The collection is an Object which contains an Array of `Y.Node`s for every 63 `WidgetStdMod` section (header, body, footer) which has one or more buttons. 64 All button nodes have the `Y.Plugin.Button` plugin applied. 65 66 This attribute is very flexible in the values it will accept. `buttons` can 67 be specified as a single Array, or an Object of Arrays keyed to a particular 68 section. 69 70 All specified values will be normalized to this type of structure: 71 72 { 73 header: [...], 74 footer: [...] 75 } 76 77 A button can be specified as a `Y.Node`, config Object, or String name for a 78 predefined button on the `BUTTONS` prototype property. When a config Object 79 is provided, it will be merged with any defaults provided by a button with 80 the same `name` defined on the `BUTTONS` property. 81 82 See `addButton()` for the detailed list of configuration properties. 83 84 For convenience, a widget's buttons will always persist and remain rendered 85 after header/body/footer content updates. Buttons should be removed by 86 updating this attribute or using the `removeButton()` method. 87 88 @example 89 { 90 // Uses predefined "close" button by string name. 91 header: ['close'], 92 93 footer: [ 94 { 95 name : 'cancel', 96 label : 'Cancel', 97 action: 'hide' 98 }, 99 100 { 101 name : 'okay', 102 label : 'Okay', 103 isDefault: true, 104 105 events: { 106 click: function (e) { 107 this.hide(); 108 } 109 } 110 } 111 ] 112 } 113 114 @attribute buttons 115 @type Object 116 @default {} 117 @since 3.4.0 118 **/ 119 buttons: { 120 getter: '_getButtons', 121 setter: '_setButtons', 122 value : {} 123 }, 124 125 /** 126 The current default button as configured through this widget's `buttons`. 127 128 A button can be configured as the default button in the following ways: 129 130 * As a config Object with an `isDefault` property: 131 `{label: 'Okay', isDefault: true}`. 132 133 * As a Node with a `data-default` attribute: 134 `<button data-default="true">Okay</button>`. 135 136 This attribute is **read-only**; anytime there are changes to this widget's 137 `buttons`, the `defaultButton` will be updated if needed. 138 139 **Note:** If two or more buttons are configured to be the default button, 140 the last one wins. 141 142 @attribute defaultButton 143 @type Node 144 @default null 145 @readOnly 146 @since 3.5.0 147 **/ 148 defaultButton: { 149 readOnly: true, 150 value : null 151 } 152 }; 153 154 /** 155 CSS classes used by `WidgetButtons`. 156 157 @property CLASS_NAMES 158 @type Object 159 @static 160 @since 3.5.0 161 **/ 162 WidgetButtons.CLASS_NAMES = { 163 button : getClassName('button'), 164 buttons: Widget.getClassName('buttons'), 165 primary: getClassName('button', 'primary') 166 }; 167 168 WidgetButtons.HTML_PARSER = { 169 buttons: function (srcNode) { 170 return this._parseButtons(srcNode); 171 } 172 }; 173 174 /** 175 The list of button configuration properties which are specific to 176 `WidgetButtons` and should not be passed to `Y.Plugin.Button.createNode()`. 177 178 @property NON_BUTTON_NODE_CFG 179 @type Array 180 @static 181 @since 3.5.0 182 **/ 183 WidgetButtons.NON_BUTTON_NODE_CFG = [ 184 'action', 'classNames', 'context', 'events', 'isDefault', 'section' 185 ]; 186 187 WidgetButtons.prototype = { 188 // -- Public Properties ---------------------------------------------------- 189 190 /** 191 Collection of predefined buttons mapped by name -> config. 192 193 These button configurations will serve as defaults for any button added to a 194 widget's buttons which have the same `name`. 195 196 See `addButton()` for a list of possible configuration values. 197 198 @property BUTTONS 199 @type Object 200 @default {} 201 @see addButton() 202 @since 3.5.0 203 **/ 204 BUTTONS: {}, 205 206 /** 207 The HTML template to use when creating the node which wraps all buttons of a 208 section. By default it will have the CSS class: "yui3-widget-buttons". 209 210 @property BUTTONS_TEMPLATE 211 @type String 212 @default "<span />" 213 @since 3.5.0 214 **/ 215 BUTTONS_TEMPLATE: '<span />', 216 217 /** 218 The default section to render buttons in when no section is specified. 219 220 @property DEFAULT_BUTTONS_SECTION 221 @type String 222 @default Y.WidgetStdMod.FOOTER 223 @since 3.5.0 224 **/ 225 DEFAULT_BUTTONS_SECTION: WidgetStdMod.FOOTER, 226 227 // -- Protected Properties ------------------------------------------------- 228 229 /** 230 A map of button node `_yuid` -> event-handle for all button nodes which were 231 created by this widget. 232 233 @property _buttonsHandles 234 @type Object 235 @protected 236 @since 3.5.0 237 **/ 238 239 /** 240 A map of this widget's `buttons`, both name -> button and 241 section:name -> button. 242 243 @property _buttonsMap 244 @type Object 245 @protected 246 @since 3.5.0 247 **/ 248 249 /** 250 Internal reference to this widget's default button. 251 252 @property _defaultButton 253 @type Node 254 @protected 255 @since 3.5.0 256 **/ 257 258 // -- Lifecycle Methods ---------------------------------------------------- 259 260 initializer: function () { 261 // Require `Y.WidgetStdMod`. 262 if (!this._stdModNode) { 263 Y.error('WidgetStdMod must be added to a Widget before WidgetButtons.'); 264 } 265 266 // Creates button mappings and sets the `defaultButton`. 267 this._mapButtons(this.get('buttons')); 268 this._updateDefaultButton(); 269 270 // Bound with `Y.bind()` to make more extensible. 271 this.after({ 272 buttonsChange : Y.bind('_afterButtonsChange', this), 273 defaultButtonChange: Y.bind('_afterDefaultButtonChange', this) 274 }); 275 276 Y.after(this._bindUIButtons, this, 'bindUI'); 277 Y.after(this._syncUIButtons, this, 'syncUI'); 278 }, 279 280 destructor: function () { 281 // Detach all event subscriptions this widget added to its `buttons`. 282 YObject.each(this._buttonsHandles, function (handle) { 283 handle.detach(); 284 }); 285 286 delete this._buttonsHandles; 287 delete this._buttonsMap; 288 delete this._defaultButton; 289 }, 290 291 // -- Public Methods ------------------------------------------------------- 292 293 /** 294 Adds a button to this widget. 295 296 The new button node will have the `Y.Plugin.Button` plugin applied, be added 297 to this widget's `buttons`, and rendered in the specified `section` at the 298 specified `index` (or end of the section when no `index` is provided). If 299 the section does not exist, it will be created. 300 301 This fires the `buttonsChange` event and adds the following properties to 302 the event facade: 303 304 * `button`: The button node or config object to add. 305 306 * `section`: The `WidgetStdMod` section (header/body/footer) where the 307 button will be added. 308 309 * `index`: The index at which the button will be in the section. 310 311 * `src`: "add" 312 313 **Note:** The `index` argument will be passed to the Array `splice()` 314 method, therefore a negative value will insert the `button` that many items 315 from the end. The `index` property on the `buttonsChange` event facade is 316 the index at which the `button` was added. 317 318 @method addButton 319 @param {Node|Object|String} button The button to add. This can be a `Y.Node` 320 instance, config Object, or String name for a predefined button on the 321 `BUTTONS` prototype property. When a config Object is provided, it will 322 be merged with any defaults provided by any `srcNode` and/or a button 323 with the same `name` defined on the `BUTTONS` property. The following 324 are the possible configuration properties beyond what Node plugins 325 accept by default: 326 @param {Function|String} [button.action] The default handler that should 327 be called when the button is clicked. A String name of a Function that 328 exists on the `context` object can also be provided. **Note:** 329 Specifying a set of `events` will override this setting. 330 @param {String|String[]} [button.classNames] Additional CSS classes to add 331 to the button node. 332 @param {Object} [button.context=this] Context which any `events` or 333 `action` should be called with. Defaults to `this`, the widget. 334 **Note:** `e.target` will access the button node in the event handlers. 335 @param {Boolean} [button.disabled=false] Whether the button should be 336 disabled. 337 @param {String|Object} [button.events="click"] Event name, or set of 338 events and handlers to bind to the button node. **See:** `Y.Node.on()`, 339 this value is passed as the first argument to `on()`. 340 @param {Boolean} [button.isDefault=false] Whether the button is the 341 default button. 342 @param {String} [button.label] The visible text/value displayed in the 343 button. 344 @param {String} [button.name] A name which can later be used to reference 345 this button. If a button is defined on the `BUTTONS` property with this 346 same name, its configuration properties will be merged in as defaults. 347 @param {String} [button.section] The `WidgetStdMod` section (header, body, 348 footer) where the button should be added. 349 @param {Node} [button.srcNode] An existing Node to use for the button, 350 default values will be seeded from this node, but are overriden by any 351 values specified in the config object. By default a new <button> 352 node will be created. 353 @param {String} [button.template] A specific template to use when creating 354 a new button node (e.g. "<a />"). **Note:** Specifying a `srcNode` 355 will overide this. 356 @param {String} [section="footer"] The `WidgetStdMod` section 357 (header/body/footer) where the button should be added. This takes 358 precedence over the `button.section` configuration property. 359 @param {Number} [index] The index at which the button should be inserted. If 360 not specified, the button will be added to the end of the section. This 361 value is passed to the Array `splice()` method, therefore a negative 362 value will insert the `button` that many items from the end. 363 @chainable 364 @see Plugin.Button.createNode() 365 @since 3.4.0 366 **/ 367 addButton: function (button, section, index) { 368 var buttons = this.get('buttons'), 369 sectionButtons, atIndex; 370 371 // Makes sure we have the full config object. 372 if (!isNode(button)) { 373 button = this._mergeButtonConfig(button); 374 section || (section = button.section); 375 } 376 377 section || (section = this.DEFAULT_BUTTONS_SECTION); 378 sectionButtons = buttons[section] || (buttons[section] = []); 379 isNumber(index) || (index = sectionButtons.length); 380 381 // Insert new button at the correct position. 382 sectionButtons.splice(index, 0, button); 383 384 // Determine the index at which the `button` now exists in the array. 385 atIndex = YArray.indexOf(sectionButtons, button); 386 387 this.set('buttons', buttons, { 388 button : button, 389 section: section, 390 index : atIndex, 391 src : 'add' 392 }); 393 394 return this; 395 }, 396 397 /** 398 Returns a button node from this widget's `buttons`. 399 400 @method getButton 401 @param {Number|String} name The string name or index of the button. 402 @param {String} [section="footer"] The `WidgetStdMod` section 403 (header/body/footer) where the button exists. Only applicable when 404 looking for a button by numerical index, or by name but scoped to a 405 particular section. 406 @return {Node} The button node. 407 @since 3.5.0 408 **/ 409 getButton: function (name, section) { 410 if (!isValue(name)) { return; } 411 412 var map = this._buttonsMap, 413 buttons; 414 415 section || (section = this.DEFAULT_BUTTONS_SECTION); 416 417 // Supports `getButton(1, 'header')` signature. 418 if (isNumber(name)) { 419 buttons = this.get('buttons'); 420 return buttons[section] && buttons[section][name]; 421 } 422 423 // Looks up button by name or section:name. 424 return arguments.length > 1 ? map[section + ':' + name] : map[name]; 425 }, 426 427 /** 428 Removes a button from this widget. 429 430 The button will be removed from this widget's `buttons` and its DOM. Any 431 event subscriptions on the button which were created by this widget will be 432 detached. If the content section becomes empty after removing the button 433 node, then the section will also be removed. 434 435 This fires the `buttonsChange` event and adds the following properties to 436 the event facade: 437 438 * `button`: The button node to remove. 439 440 * `section`: The `WidgetStdMod` section (header/body/footer) where the 441 button should be removed from. 442 443 * `index`: The index at which the button exists in the section. 444 445 * `src`: "remove" 446 447 @method removeButton 448 @param {Node|Number|String} button The button to remove. This can be a 449 `Y.Node` instance, index, or String name of a button. 450 @param {String} [section="footer"] The `WidgetStdMod` section 451 (header/body/footer) where the button exists. Only applicable when 452 removing a button by numerical index, or by name but scoped to a 453 particular section. 454 @chainable 455 @since 3.5.0 456 **/ 457 removeButton: function (button, section) { 458 if (!isValue(button)) { return this; } 459 460 var buttons = this.get('buttons'), 461 index; 462 463 // Shortcut if `button` is already an index which is needed for slicing. 464 if (isNumber(button)) { 465 section || (section = this.DEFAULT_BUTTONS_SECTION); 466 index = button; 467 button = buttons[section][index]; 468 } else { 469 // Supports `button` being the string name. 470 if (isString(button)) { 471 // `getButton()` is called this way because its behavior is 472 // different based on the number of arguments. 473 button = this.getButton.apply(this, arguments); 474 } 475 476 // Determines the `section` and `index` at which the button exists. 477 YObject.some(buttons, function (sectionButtons, currentSection) { 478 index = YArray.indexOf(sectionButtons, button); 479 480 if (index > -1) { 481 section = currentSection; 482 return true; 483 } 484 }); 485 } 486 487 // Button was found at an appropriate index. 488 if (button && index > -1) { 489 // Remove button from `section` array. 490 buttons[section].splice(index, 1); 491 492 this.set('buttons', buttons, { 493 button : button, 494 section: section, 495 index : index, 496 src : 'remove' 497 }); 498 } 499 500 return this; 501 }, 502 503 // -- Protected Methods ---------------------------------------------------- 504 505 /** 506 Binds UI event listeners. This method is inserted via AOP, and will execute 507 after `bindUI()`. 508 509 @method _bindUIButtons 510 @protected 511 @since 3.4.0 512 **/ 513 _bindUIButtons: function () { 514 // Event handlers are bound with `bind()` to make them more extensible. 515 var afterContentChange = Y.bind('_afterContentChangeButtons', this); 516 517 this.after({ 518 visibleChange : Y.bind('_afterVisibleChangeButtons', this), 519 headerContentChange: afterContentChange, 520 bodyContentChange : afterContentChange, 521 footerContentChange: afterContentChange 522 }); 523 }, 524 525 /** 526 Returns a button node based on the specified `button` node or configuration. 527 528 The button node will either be created via `Y.Plugin.Button.createNode()`, 529 or when `button` is specified as a node already, it will by `plug()`ed with 530 `Y.Plugin.Button`. 531 532 @method _createButton 533 @param {Node|Object} button Button node or configuration object. 534 @return {Node} The button node. 535 @protected 536 @since 3.5.0 537 **/ 538 _createButton: function (button) { 539 var config, buttonConfig, nonButtonNodeCfg, 540 i, len, action, context, handle; 541 542 // Makes sure the exiting `Y.Node` instance is from this YUI sandbox and 543 // is plugged with `Y.Plugin.Button`. 544 if (isNode(button)) { 545 return Y.one(button.getDOMNode()).plug(ButtonPlugin); 546 } 547 548 // Merge `button` config with defaults and back-compat. 549 config = Y.merge({ 550 context: this, 551 events : 'click', 552 label : button.value 553 }, button); 554 555 buttonConfig = Y.merge(config); 556 nonButtonNodeCfg = WidgetButtons.NON_BUTTON_NODE_CFG; 557 558 // Remove all non-button Node config props. 559 for (i = 0, len = nonButtonNodeCfg.length; i < len; i += 1) { 560 delete buttonConfig[nonButtonNodeCfg[i]]; 561 } 562 563 // Create the button node using the button Node-only config. 564 button = ButtonPlugin.createNode(buttonConfig); 565 566 context = config.context; 567 action = config.action; 568 569 // Supports `action` as a String name of a Function on the `context` 570 // object. 571 if (isString(action)) { 572 action = Y.bind(action, context); 573 } 574 575 // Supports all types of crazy configs for event subscriptions and 576 // stores a reference to the returned `EventHandle`. 577 handle = button.on(config.events, action, context); 578 this._buttonsHandles[Y.stamp(button, true)] = handle; 579 580 // Tags the button with the configured `name` and `isDefault` settings. 581 button.setData('name', this._getButtonName(config)); 582 button.setData('default', this._getButtonDefault(config)); 583 584 // Add any CSS classnames to the button node. 585 YArray.each(YArray(config.classNames), button.addClass, button); 586 587 return button; 588 }, 589 590 /** 591 Returns the buttons container for the specified `section`, passing a truthy 592 value for `create` will create the node if it does not already exist. 593 594 **Note:** It is up to the caller to properly insert the returned container 595 node into the content section. 596 597 @method _getButtonContainer 598 @param {String} section The `WidgetStdMod` section (header/body/footer). 599 @param {Boolean} create Whether the buttons container should be created if 600 it does not already exist. 601 @return {Node} The buttons container node for the specified `section`. 602 @protected 603 @see BUTTONS_TEMPLATE 604 @since 3.5.0 605 **/ 606 _getButtonContainer: function (section, create) { 607 var sectionClassName = WidgetStdMod.SECTION_CLASS_NAMES[section], 608 buttonsClassName = WidgetButtons.CLASS_NAMES.buttons, 609 contentBox = this.get('contentBox'), 610 containerSelector, container; 611 612 // Search for an existing buttons container within the section. 613 containerSelector = '.' + sectionClassName + ' .' + buttonsClassName; 614 container = contentBox.one(containerSelector); 615 616 // Create the `container` if it doesn't already exist. 617 if (!container && create) { 618 container = Y.Node.create(this.BUTTONS_TEMPLATE); 619 container.addClass(buttonsClassName); 620 } 621 622 return container; 623 }, 624 625 /** 626 Returns whether or not the specified `button` is configured to be the 627 default button. 628 629 When a button node is specified, the button's `getData()` method will be 630 used to determine if the button is configured to be the default. When a 631 button config object is specified, the `isDefault` prop will determine 632 whether the button is the default. 633 634 **Note:** `<button data-default="true"></button>` is supported via the 635 `button.getData('default')` API call. 636 637 @method _getButtonDefault 638 @param {Node|Object} button The button node or configuration object. 639 @return {Boolean} Whether the button is configured to be the default button. 640 @protected 641 @since 3.5.0 642 **/ 643 _getButtonDefault: function (button) { 644 var isDefault = isNode(button) ? 645 button.getData('default') : button.isDefault; 646 647 if (isString(isDefault)) { 648 return isDefault.toLowerCase() === 'true'; 649 } 650 651 return !!isDefault; 652 }, 653 654 /** 655 Returns the name of the specified `button`. 656 657 When a button node is specified, the button's `getData('name')` method is 658 preferred, but will fallback to `get('name')`, and the result will determine 659 the button's name. When a button config object is specified, the `name` prop 660 will determine the button's name. 661 662 **Note:** `<button data-name="foo"></button>` is supported via the 663 `button.getData('name')` API call. 664 665 @method _getButtonName 666 @param {Node|Object} button The button node or configuration object. 667 @return {String} The name of the button. 668 @protected 669 @since 3.5.0 670 **/ 671 _getButtonName: function (button) { 672 var name; 673 674 if (isNode(button)) { 675 name = button.getData('name') || button.get('name'); 676 } else { 677 name = button && (button.name || button.type); 678 } 679 680 return name; 681 }, 682 683 /** 684 Getter for the `buttons` attribute. A copy of the `buttons` object is 685 returned so the stored state cannot be modified by the callers of 686 `get('buttons')`. 687 688 This will recreate a copy of the `buttons` object, and each section array 689 (the button nodes are *not* copied/cloned.) 690 691 @method _getButtons 692 @param {Object} buttons The widget's current `buttons` state. 693 @return {Object} A copy of the widget's current `buttons` state. 694 @protected 695 @since 3.5.0 696 **/ 697 _getButtons: function (buttons) { 698 var buttonsCopy = {}; 699 700 // Creates a new copy of the `buttons` object. 701 YObject.each(buttons, function (sectionButtons, section) { 702 // Creates of copy of the array of button nodes. 703 buttonsCopy[section] = sectionButtons.concat(); 704 }); 705 706 return buttonsCopy; 707 }, 708 709 /** 710 Adds the specified `button` to the buttons map (both name -> button and 711 section:name -> button), and sets the button as the default if it is 712 configured as the default button. 713 714 **Note:** If two or more buttons are configured with the same `name` and/or 715 configured to be the default button, the last one wins. 716 717 @method _mapButton 718 @param {Node} button The button node to map. 719 @param {String} section The `WidgetStdMod` section (header/body/footer). 720 @protected 721 @since 3.5.0 722 **/ 723 _mapButton: function (button, section) { 724 var map = this._buttonsMap, 725 name = this._getButtonName(button), 726 isDefault = this._getButtonDefault(button); 727 728 if (name) { 729 // name -> button 730 map[name] = button; 731 732 // section:name -> button 733 map[section + ':' + name] = button; 734 } 735 736 isDefault && (this._defaultButton = button); 737 }, 738 739 /** 740 Adds the specified `buttons` to the buttons map (both name -> button and 741 section:name -> button), and set the a button as the default if one is 742 configured as the default button. 743 744 **Note:** This will clear all previous button mappings and null-out any 745 previous default button! If two or more buttons are configured with the same 746 `name` and/or configured to be the default button, the last one wins. 747 748 @method _mapButtons 749 @param {Node[]} buttons The button nodes to map. 750 @protected 751 @since 3.5.0 752 **/ 753 _mapButtons: function (buttons) { 754 this._buttonsMap = {}; 755 this._defaultButton = null; 756 757 YObject.each(buttons, function (sectionButtons, section) { 758 var i, len; 759 760 for (i = 0, len = sectionButtons.length; i < len; i += 1) { 761 this._mapButton(sectionButtons[i], section); 762 } 763 }, this); 764 }, 765 766 /** 767 Returns a copy of the specified `config` object merged with any defaults 768 provided by a `srcNode` and/or a predefined configuration for a button 769 with the same `name` on the `BUTTONS` property. 770 771 @method _mergeButtonConfig 772 @param {Object|String} config Button configuration object, or string name. 773 @return {Object} A copy of the button configuration object merged with any 774 defaults. 775 @protected 776 @since 3.5.0 777 **/ 778 _mergeButtonConfig: function (config) { 779 var buttonConfig, defConfig, name, button, tagName, label; 780 781 // Makes sure `config` is an Object and a copy of the specified value. 782 config = isString(config) ? {name: config} : Y.merge(config); 783 784 // Seeds default values from the button node, if there is one. 785 if (config.srcNode) { 786 button = config.srcNode; 787 tagName = button.get('tagName').toLowerCase(); 788 label = button.get(tagName === 'input' ? 'value' : 'text'); 789 790 // Makes sure the button's current values override any defaults. 791 buttonConfig = { 792 disabled : !!button.get('disabled'), 793 isDefault: this._getButtonDefault(button), 794 name : this._getButtonName(button) 795 }; 796 797 // Label should only be considered when not an empty string. 798 label && (buttonConfig.label = label); 799 800 // Merge `config` with `buttonConfig` values. 801 Y.mix(config, buttonConfig, false, null, 0, true); 802 } 803 804 name = this._getButtonName(config); 805 defConfig = this.BUTTONS && this.BUTTONS[name]; 806 807 // Merge `config` with predefined default values. 808 if (defConfig) { 809 Y.mix(config, defConfig, false, null, 0, true); 810 } 811 812 return config; 813 }, 814 815 /** 816 `HTML_PARSER` implementation for the `buttons` attribute. 817 818 **Note:** To determine a button node's name its `data-name` and `name` 819 attributes are examined. Whether the button should be the default is 820 determined by its `data-default` attribute. 821 822 @method _parseButtons 823 @param {Node} srcNode This widget's srcNode to search for buttons. 824 @return {null|Object} `buttons` Config object parsed from this widget's DOM. 825 @protected 826 @since 3.5.0 827 **/ 828 _parseButtons: function (srcNode) { 829 var buttonSelector = '.' + WidgetButtons.CLASS_NAMES.button, 830 sections = ['header', 'body', 'footer'], 831 buttonsConfig = null; 832 833 YArray.each(sections, function (section) { 834 var container = this._getButtonContainer(section), 835 buttons = container && container.all(buttonSelector), 836 sectionButtons; 837 838 if (!buttons || buttons.isEmpty()) { return; } 839 840 sectionButtons = []; 841 842 // Creates a button config object for every button node found and 843 // adds it to the section. This way each button configuration can be 844 // merged with any defaults provided by predefined `BUTTONS`. 845 buttons.each(function (button) { 846 sectionButtons.push({srcNode: button}); 847 }); 848 849 buttonsConfig || (buttonsConfig = {}); 850 buttonsConfig[section] = sectionButtons; 851 }, this); 852 853 return buttonsConfig; 854 }, 855 856 /** 857 Setter for the `buttons` attribute. This processes the specified `config` 858 and returns a new `buttons` object which is stored as the new state; leaving 859 the original, specified `config` unmodified. 860 861 The button nodes will either be created via `Y.Plugin.Button.createNode()`, 862 or when a button is already a Node already, it will by `plug()`ed with 863 `Y.Plugin.Button`. 864 865 @method _setButtons 866 @param {Array|Object} config The `buttons` configuration to process. 867 @return {Object} The processed `buttons` object which represents the new 868 state. 869 @protected 870 @since 3.5.0 871 **/ 872 _setButtons: function (config) { 873 var defSection = this.DEFAULT_BUTTONS_SECTION, 874 buttons = {}; 875 876 function processButtons(buttonConfigs, currentSection) { 877 if (!isArray(buttonConfigs)) { return; } 878 879 var i, len, button, section; 880 881 for (i = 0, len = buttonConfigs.length; i < len; i += 1) { 882 button = buttonConfigs[i]; 883 section = currentSection; 884 885 if (!isNode(button)) { 886 button = this._mergeButtonConfig(button); 887 section || (section = button.section); 888 } 889 890 // Always passes through `_createButton()` to make sure the node 891 // is decorated as a button. 892 button = this._createButton(button); 893 894 // Use provided `section` or fallback to the default section. 895 section || (section = defSection); 896 897 // Add button to the array of buttons for the specified section. 898 (buttons[section] || (buttons[section] = [])).push(button); 899 } 900 } 901 902 // Handle `config` being either an Array or Object of Arrays. 903 if (isArray(config)) { 904 processButtons.call(this, config); 905 } else { 906 YObject.each(config, processButtons, this); 907 } 908 909 return buttons; 910 }, 911 912 /** 913 Syncs this widget's current button-related state to its DOM. This method is 914 inserted via AOP, and will execute after `syncUI()`. 915 916 @method _syncUIButtons 917 @protected 918 @since 3.4.0 919 **/ 920 _syncUIButtons: function () { 921 this._uiSetButtons(this.get('buttons')); 922 this._uiSetDefaultButton(this.get('defaultButton')); 923 this._uiSetVisibleButtons(this.get('visible')); 924 }, 925 926 /** 927 Inserts the specified `button` node into this widget's DOM at the specified 928 `section` and `index` and updates the section content. 929 930 The section and button container nodes will be created if they do not 931 already exist. 932 933 @method _uiInsertButton 934 @param {Node} button The button node to insert into this widget's DOM. 935 @param {String} section The `WidgetStdMod` section (header/body/footer). 936 @param {Number} index Index at which the `button` should be positioned. 937 @protected 938 @since 3.5.0 939 **/ 940 _uiInsertButton: function (button, section, index) { 941 var buttonsClassName = WidgetButtons.CLASS_NAMES.button, 942 buttonContainer = this._getButtonContainer(section, true), 943 sectionButtons = buttonContainer.all('.' + buttonsClassName); 944 945 // Inserts the button node at the correct index. 946 buttonContainer.insertBefore(button, sectionButtons.item(index)); 947 948 // Adds the button container to the section content. 949 this.setStdModContent(section, buttonContainer, 'after'); 950 }, 951 952 /** 953 Removes the button node from this widget's DOM and detaches any event 954 subscriptions on the button that were created by this widget. The section 955 content will be updated unless `{preserveContent: true}` is passed in the 956 `options`. 957 958 By default the button container node will be removed when this removes the 959 last button of the specified `section`; and if no other content remains in 960 the section node, it will also be removed. 961 962 @method _uiRemoveButton 963 @param {Node} button The button to remove and destroy. 964 @param {String} section The `WidgetStdMod` section (header/body/footer). 965 @param {Object} [options] Additional options. 966 @param {Boolean} [options.preserveContent=false] Whether the section 967 content should be updated. 968 @protected 969 @since 3.5.0 970 **/ 971 _uiRemoveButton: function (button, section, options) { 972 var yuid = Y.stamp(button, this), 973 handles = this._buttonsHandles, 974 handle = handles[yuid], 975 buttonContainer, buttonClassName; 976 977 if (handle) { 978 handle.detach(); 979 } 980 981 delete handles[yuid]; 982 983 button.remove(); 984 985 options || (options = {}); 986 987 // Remove the button container and section nodes if needed. 988 if (!options.preserveContent) { 989 buttonContainer = this._getButtonContainer(section); 990 buttonClassName = WidgetButtons.CLASS_NAMES.button; 991 992 // Only matters if we have a button container which is empty. 993 if (buttonContainer && 994 buttonContainer.all('.' + buttonClassName).isEmpty()) { 995 996 buttonContainer.remove(); 997 this._updateContentButtons(section); 998 } 999 } 1000 }, 1001 1002 /** 1003 Sets the current `buttons` state to this widget's DOM by rendering the 1004 specified collection of `buttons` and updates the contents of each section 1005 as needed. 1006 1007 Button nodes which already exist in the DOM will remain intact, or will be 1008 moved if they should be in a new position. Old button nodes which are no 1009 longer represented in the specified `buttons` collection will be removed, 1010 and any event subscriptions on the button which were created by this widget 1011 will be detached. 1012 1013 If the button nodes in this widget's DOM actually change, then each content 1014 section will be updated (or removed) appropriately. 1015 1016 @method _uiSetButtons 1017 @param {Object} buttons The current `buttons` state to visually represent. 1018 @protected 1019 @since 3.5.0 1020 **/ 1021 _uiSetButtons: function (buttons) { 1022 var buttonClassName = WidgetButtons.CLASS_NAMES.button, 1023 sections = ['header', 'body', 'footer']; 1024 1025 YArray.each(sections, function (section) { 1026 var sectionButtons = buttons[section] || [], 1027 numButtons = sectionButtons.length, 1028 buttonContainer = this._getButtonContainer(section, numButtons), 1029 buttonsUpdated = false, 1030 oldNodes, i, button, buttonIndex; 1031 1032 // When there's no button container, there are no new buttons or old 1033 // buttons that we have to deal with for this section. 1034 if (!buttonContainer) { return; } 1035 1036 oldNodes = buttonContainer.all('.' + buttonClassName); 1037 1038 for (i = 0; i < numButtons; i += 1) { 1039 button = sectionButtons[i]; 1040 buttonIndex = oldNodes.indexOf(button); 1041 1042 // Buttons already rendered in the Widget should remain there or 1043 // moved to their new index. New buttons will be added to the 1044 // current `buttonContainer`. 1045 if (buttonIndex > -1) { 1046 // Remove button from existing buttons nodeList since its in 1047 // the DOM already. 1048 oldNodes.splice(buttonIndex, 1); 1049 1050 // Check that the button is at the right position, if not, 1051 // move it to its new position. 1052 if (buttonIndex !== i) { 1053 // Using `i + 1` because the button should be at index 1054 // `i`; it's inserted before the node which comes after. 1055 buttonContainer.insertBefore(button, i + 1); 1056 buttonsUpdated = true; 1057 } 1058 } else { 1059 buttonContainer.appendChild(button); 1060 buttonsUpdated = true; 1061 } 1062 } 1063 1064 // Safely removes the old button nodes which are no longer part of 1065 // this widget's `buttons`. 1066 oldNodes.each(function (button) { 1067 this._uiRemoveButton(button, section, {preserveContent: true}); 1068 buttonsUpdated = true; 1069 }, this); 1070 1071 // Remove leftover empty button containers and updated the StdMod 1072 // content area. 1073 if (numButtons === 0) { 1074 buttonContainer.remove(); 1075 this._updateContentButtons(section); 1076 return; 1077 } 1078 1079 // Adds the button container to the section content. 1080 if (buttonsUpdated) { 1081 this.setStdModContent(section, buttonContainer, 'after'); 1082 } 1083 }, this); 1084 }, 1085 1086 /** 1087 Adds the "yui3-button-primary" CSS class to the new `defaultButton` and 1088 removes it from the old default button. 1089 1090 @method _uiSetDefaultButton 1091 @param {Node} newButton The new `defaultButton`. 1092 @param {Node} oldButton The old `defaultButton`. 1093 @protected 1094 @since 3.5.0 1095 **/ 1096 _uiSetDefaultButton: function (newButton, oldButton) { 1097 var primaryClassName = WidgetButtons.CLASS_NAMES.primary; 1098 1099 if (newButton) { newButton.addClass(primaryClassName); } 1100 if (oldButton) { oldButton.removeClass(primaryClassName); } 1101 }, 1102 1103 /** 1104 Focuses this widget's `defaultButton` if there is one and this widget is 1105 visible. 1106 1107 @method _uiSetVisibleButtons 1108 @param {Boolean} visible Whether this widget is visible. 1109 @protected 1110 @since 3.5.0 1111 **/ 1112 _uiSetVisibleButtons: function (visible) { 1113 if (!visible) { return; } 1114 1115 var defaultButton = this.get('defaultButton'); 1116 if (defaultButton) { 1117 defaultButton.focus(); 1118 } 1119 }, 1120 1121 /** 1122 Removes the specified `button` from the buttons map (both name -> button and 1123 section:name -> button), and nulls-out the `defaultButton` if it is 1124 currently the default button. 1125 1126 @method _unMapButton 1127 @param {Node} button The button node to remove from the buttons map. 1128 @param {String} section The `WidgetStdMod` section (header/body/footer). 1129 @protected 1130 @since 3.5.0 1131 **/ 1132 _unMapButton: function (button, section) { 1133 var map = this._buttonsMap, 1134 name = this._getButtonName(button), 1135 sectionName; 1136 1137 // Only delete the map entry if the specified `button` is mapped to it. 1138 if (name) { 1139 // name -> button 1140 if (map[name] === button) { 1141 delete map[name]; 1142 } 1143 1144 // section:name -> button 1145 sectionName = section + ':' + name; 1146 if (map[sectionName] === button) { 1147 delete map[sectionName]; 1148 } 1149 } 1150 1151 // Clear the default button if its the specified `button`. 1152 if (this._defaultButton === button) { 1153 this._defaultButton = null; 1154 } 1155 }, 1156 1157 /** 1158 Updates the `defaultButton` attribute if it needs to be updated by comparing 1159 its current value with the protected `_defaultButton` property. 1160 1161 @method _updateDefaultButton 1162 @protected 1163 @since 3.5.0 1164 **/ 1165 _updateDefaultButton: function () { 1166 var defaultButton = this._defaultButton; 1167 1168 if (this.get('defaultButton') !== defaultButton) { 1169 this._set('defaultButton', defaultButton); 1170 } 1171 }, 1172 1173 /** 1174 Updates the content attribute which corresponds to the specified `section`. 1175 1176 The method updates the section's content to its current `childNodes` 1177 (text and/or HTMLElement), or will null-out its contents if the section is 1178 empty. It also specifies a `src` of `buttons` on the change event facade. 1179 1180 @method _updateContentButtons 1181 @param {String} section The `WidgetStdMod` section (header/body/footer) to 1182 update. 1183 @protected 1184 @since 3.5.0 1185 **/ 1186 _updateContentButtons: function (section) { 1187 // `childNodes` return text nodes and HTMLElements. 1188 var sectionContent = this.getStdModNode(section).get('childNodes'); 1189 1190 // Updates the section to its current contents, or null if it is empty. 1191 this.set(section + 'Content', sectionContent.isEmpty() ? null : 1192 sectionContent, {src: 'buttons'}); 1193 }, 1194 1195 // -- Protected Event Handlers --------------------------------------------- 1196 1197 /** 1198 Handles this widget's `buttonsChange` event which fires anytime the 1199 `buttons` attribute is modified. 1200 1201 **Note:** This method special-cases the `buttons` modifications caused by 1202 `addButton()` and `removeButton()`, both of which set the `src` property on 1203 the event facade to "add" and "remove" respectively. 1204 1205 @method _afterButtonsChange 1206 @param {EventFacade} e 1207 @protected 1208 @since 3.4.0 1209 **/ 1210 _afterButtonsChange: function (e) { 1211 var buttons = e.newVal, 1212 section = e.section, 1213 index = e.index, 1214 src = e.src, 1215 button; 1216 1217 // Special cases `addButton()` to only set and insert the new button. 1218 if (src === 'add') { 1219 // Make sure we have the button node. 1220 button = buttons[section][index]; 1221 1222 this._mapButton(button, section); 1223 this._updateDefaultButton(); 1224 this._uiInsertButton(button, section, index); 1225 1226 return; 1227 } 1228 1229 // Special cases `removeButton()` to only remove the specified button. 1230 if (src === 'remove') { 1231 // Button node already exists on the event facade. 1232 button = e.button; 1233 1234 this._unMapButton(button, section); 1235 this._updateDefaultButton(); 1236 this._uiRemoveButton(button, section); 1237 1238 return; 1239 } 1240 1241 this._mapButtons(buttons); 1242 this._updateDefaultButton(); 1243 this._uiSetButtons(buttons); 1244 }, 1245 1246 /** 1247 Handles this widget's `headerContentChange`, `bodyContentChange`, 1248 `footerContentChange` events by making sure the `buttons` remain rendered 1249 after changes to the content areas. 1250 1251 These events are very chatty, so extra caution is taken to avoid doing extra 1252 work or getting into an infinite loop. 1253 1254 @method _afterContentChangeButtons 1255 @param {EventFacade} e 1256 @protected 1257 @since 3.5.0 1258 **/ 1259 _afterContentChangeButtons: function (e) { 1260 var src = e.src, 1261 pos = e.stdModPosition, 1262 replace = !pos || pos === WidgetStdMod.REPLACE; 1263 1264 // Only do work when absolutely necessary. 1265 if (replace && src !== 'buttons' && src !== Widget.UI_SRC) { 1266 this._uiSetButtons(this.get('buttons')); 1267 } 1268 }, 1269 1270 /** 1271 Handles this widget's `defaultButtonChange` event by adding the 1272 "yui3-button-primary" CSS class to the new `defaultButton` and removing it 1273 from the old default button. 1274 1275 @method _afterDefaultButtonChange 1276 @param {EventFacade} e 1277 @protected 1278 @since 3.5.0 1279 **/ 1280 _afterDefaultButtonChange: function (e) { 1281 this._uiSetDefaultButton(e.newVal, e.prevVal); 1282 }, 1283 1284 /** 1285 Handles this widget's `visibleChange` event by focusing the `defaultButton` 1286 if there is one. 1287 1288 @method _afterVisibleChangeButtons 1289 @param {EventFacade} e 1290 @protected 1291 @since 3.5.0 1292 **/ 1293 _afterVisibleChangeButtons: function (e) { 1294 this._uiSetVisibleButtons(e.newVal); 1295 } 1296 }; 1297 1298 Y.WidgetButtons = WidgetButtons; 1299 1300 1301 }, '3.17.2', {"requires": ["button-plugin", "cssbutton", "widget-stdmod"]});
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 |