[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/widget-buttons/ -> widget-buttons.js (source)

   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 &lt;button&gt;
 352          node will be created.
 353        @param {String} [button.template] A specific template to use when creating
 354          a new button node (e.g. "&lt;a /&gt;"). **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"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1