--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/widget-buttons/widget-buttons.js Tue Jul 16 14:29:46 2013 +0200
@@ -0,0 +1,1301 @@
+/*
+YUI 3.10.3 (build 2fb5187)
+Copyright 2013 Yahoo! Inc. All rights reserved.
+Licensed under the BSD License.
+http://yuilibrary.com/license/
+*/
+
+YUI.add('widget-buttons', function (Y, NAME) {
+
+/**
+Provides header/body/footer button support for Widgets that use the
+`WidgetStdMod` extension.
+
+@module widget-buttons
+@since 3.4.0
+**/
+
+var YArray = Y.Array,
+ YLang = Y.Lang,
+ YObject = Y.Object,
+
+ ButtonPlugin = Y.Plugin.Button,
+ Widget = Y.Widget,
+ WidgetStdMod = Y.WidgetStdMod,
+
+ getClassName = Y.ClassNameManager.getClassName,
+ isArray = YLang.isArray,
+ isNumber = YLang.isNumber,
+ isString = YLang.isString,
+ isValue = YLang.isValue;
+
+// Utility to determine if an object is a Y.Node instance, even if it was
+// created in a different YUI sandbox.
+function isNode(node) {
+ return !!node.getDOMNode;
+}
+
+/**
+Provides header/body/footer button support for Widgets that use the
+`WidgetStdMod` extension.
+
+This Widget extension makes it easy to declaratively configure a widget's
+buttons. It adds a `buttons` attribute along with button- accessor and mutator
+methods. All button nodes have the `Y.Plugin.Button` plugin applied.
+
+This extension also includes `HTML_PARSER` support to seed a widget's `buttons`
+from those which already exist in its DOM.
+
+@class WidgetButtons
+@extensionfor Widget
+@since 3.4.0
+**/
+function WidgetButtons() {
+ // Require `Y.WidgetStdMod`.
+ if (!this._stdModNode) {
+ Y.error('WidgetStdMod must be added to a Widget before WidgetButtons.');
+ }
+
+ // Has to be setup before the `initializer()`.
+ this._buttonsHandles = {};
+}
+
+WidgetButtons.ATTRS = {
+ /**
+ Collection containing a widget's buttons.
+
+ The collection is an Object which contains an Array of `Y.Node`s for every
+ `WidgetStdMod` section (header, body, footer) which has one or more buttons.
+ All button nodes have the `Y.Plugin.Button` plugin applied.
+
+ This attribute is very flexible in the values it will accept. `buttons` can
+ be specified as a single Array, or an Object of Arrays keyed to a particular
+ section.
+
+ All specified values will be normalized to this type of structure:
+
+ {
+ header: [...],
+ footer: [...]
+ }
+
+ A button can be specified as a `Y.Node`, config Object, or String name for a
+ predefined button on the `BUTTONS` prototype property. When a config Object
+ is provided, it will be merged with any defaults provided by a button with
+ the same `name` defined on the `BUTTONS` property.
+
+ See `addButton()` for the detailed list of configuration properties.
+
+ For convenience, a widget's buttons will always persist and remain rendered
+ after header/body/footer content updates. Buttons should be removed by
+ updating this attribute or using the `removeButton()` method.
+
+ @example
+ {
+ // Uses predefined "close" button by string name.
+ header: ['close'],
+
+ footer: [
+ {
+ name : 'cancel',
+ label : 'Cancel',
+ action: 'hide'
+ },
+
+ {
+ name : 'okay',
+ label : 'Okay',
+ isDefault: true,
+
+ events: {
+ click: function (e) {
+ this.hide();
+ }
+ }
+ }
+ ]
+ }
+
+ @attribute buttons
+ @type Object
+ @default {}
+ @since 3.4.0
+ **/
+ buttons: {
+ getter: '_getButtons',
+ setter: '_setButtons',
+ value : {}
+ },
+
+ /**
+ The current default button as configured through this widget's `buttons`.
+
+ A button can be configured as the default button in the following ways:
+
+ * As a config Object with an `isDefault` property:
+ `{label: 'Okay', isDefault: true}`.
+
+ * As a Node with a `data-default` attribute:
+ `<button data-default="true">Okay</button>`.
+
+ This attribute is **read-only**; anytime there are changes to this widget's
+ `buttons`, the `defaultButton` will be updated if needed.
+
+ **Note:** If two or more buttons are configured to be the default button,
+ the last one wins.
+
+ @attribute defaultButton
+ @type Node
+ @default null
+ @readOnly
+ @since 3.5.0
+ **/
+ defaultButton: {
+ readOnly: true,
+ value : null
+ }
+};
+
+/**
+CSS classes used by `WidgetButtons`.
+
+@property CLASS_NAMES
+@type Object
+@static
+@since 3.5.0
+**/
+WidgetButtons.CLASS_NAMES = {
+ button : getClassName('button'),
+ buttons: Widget.getClassName('buttons'),
+ primary: getClassName('button', 'primary')
+};
+
+WidgetButtons.HTML_PARSER = {
+ buttons: function (srcNode) {
+ return this._parseButtons(srcNode);
+ }
+};
+
+/**
+The list of button configuration properties which are specific to
+`WidgetButtons` and should not be passed to `Y.Plugin.Button.createNode()`.
+
+@property NON_BUTTON_NODE_CFG
+@type Array
+@static
+@since 3.5.0
+**/
+WidgetButtons.NON_BUTTON_NODE_CFG = [
+ 'action', 'classNames', 'context', 'events', 'isDefault', 'section'
+];
+
+WidgetButtons.prototype = {
+ // -- Public Properties ----------------------------------------------------
+
+ /**
+ Collection of predefined buttons mapped by name -> config.
+
+ These button configurations will serve as defaults for any button added to a
+ widget's buttons which have the same `name`.
+
+ See `addButton()` for a list of possible configuration values.
+
+ @property BUTTONS
+ @type Object
+ @default {}
+ @see addButton()
+ @since 3.5.0
+ **/
+ BUTTONS: {},
+
+ /**
+ The HTML template to use when creating the node which wraps all buttons of a
+ section. By default it will have the CSS class: "yui3-widget-buttons".
+
+ @property BUTTONS_TEMPLATE
+ @type String
+ @default "<span />"
+ @since 3.5.0
+ **/
+ BUTTONS_TEMPLATE: '<span />',
+
+ /**
+ The default section to render buttons in when no section is specified.
+
+ @property DEFAULT_BUTTONS_SECTION
+ @type String
+ @default Y.WidgetStdMod.FOOTER
+ @since 3.5.0
+ **/
+ DEFAULT_BUTTONS_SECTION: WidgetStdMod.FOOTER,
+
+ // -- Protected Properties -------------------------------------------------
+
+ /**
+ A map of button node `_yuid` -> event-handle for all button nodes which were
+ created by this widget.
+
+ @property _buttonsHandles
+ @type Object
+ @protected
+ @since 3.5.0
+ **/
+
+ /**
+ A map of this widget's `buttons`, both name -> button and
+ section:name -> button.
+
+ @property _buttonsMap
+ @type Object
+ @protected
+ @since 3.5.0
+ **/
+
+ /**
+ Internal reference to this widget's default button.
+
+ @property _defaultButton
+ @type Node
+ @protected
+ @since 3.5.0
+ **/
+
+ // -- Lifecycle Methods ----------------------------------------------------
+
+ initializer: function () {
+ // Creates button mappings and sets the `defaultButton`.
+ this._mapButtons(this.get('buttons'));
+ this._updateDefaultButton();
+
+ // Bound with `Y.bind()` to make more extensible.
+ this.after({
+ buttonsChange : Y.bind('_afterButtonsChange', this),
+ defaultButtonChange: Y.bind('_afterDefaultButtonChange', this)
+ });
+
+ Y.after(this._bindUIButtons, this, 'bindUI');
+ Y.after(this._syncUIButtons, this, 'syncUI');
+ },
+
+ destructor: function () {
+ // Detach all event subscriptions this widget added to its `buttons`.
+ YObject.each(this._buttonsHandles, function (handle) {
+ handle.detach();
+ });
+
+ delete this._buttonsHandles;
+ delete this._buttonsMap;
+ delete this._defaultButton;
+ },
+
+ // -- Public Methods -------------------------------------------------------
+
+ /**
+ Adds a button to this widget.
+
+ The new button node will have the `Y.Plugin.Button` plugin applied, be added
+ to this widget's `buttons`, and rendered in the specified `section` at the
+ specified `index` (or end of the section when no `index` is provided). If
+ the section does not exist, it will be created.
+
+ This fires the `buttonsChange` event and adds the following properties to
+ the event facade:
+
+ * `button`: The button node or config object to add.
+
+ * `section`: The `WidgetStdMod` section (header/body/footer) where the
+ button will be added.
+
+ * `index`: The index at which the button will be in the section.
+
+ * `src`: "add"
+
+ **Note:** The `index` argument will be passed to the Array `splice()`
+ method, therefore a negative value will insert the `button` that many items
+ from the end. The `index` property on the `buttonsChange` event facade is
+ the index at which the `button` was added.
+
+ @method addButton
+ @param {Node|Object|String} button The button to add. This can be a `Y.Node`
+ instance, config Object, or String name for a predefined button on the
+ `BUTTONS` prototype property. When a config Object is provided, it will
+ be merged with any defaults provided by any `srcNode` and/or a button
+ with the same `name` defined on the `BUTTONS` property. The following
+ are the possible configuration properties beyond what Node plugins
+ accept by default:
+ @param {Function|String} [button.action] The default handler that should
+ be called when the button is clicked. A String name of a Function that
+ exists on the `context` object can also be provided. **Note:**
+ Specifying a set of `events` will override this setting.
+ @param {String|String[]} [button.classNames] Additional CSS classes to add
+ to the button node.
+ @param {Object} [button.context=this] Context which any `events` or
+ `action` should be called with. Defaults to `this`, the widget.
+ **Note:** `e.target` will access the button node in the event handlers.
+ @param {Boolean} [button.disabled=false] Whether the button should be
+ disabled.
+ @param {String|Object} [button.events="click"] Event name, or set of
+ events and handlers to bind to the button node. **See:** `Y.Node.on()`,
+ this value is passed as the first argument to `on()`.
+ @param {Boolean} [button.isDefault=false] Whether the button is the
+ default button.
+ @param {String} [button.label] The visible text/value displayed in the
+ button.
+ @param {String} [button.name] A name which can later be used to reference
+ this button. If a button is defined on the `BUTTONS` property with this
+ same name, its configuration properties will be merged in as defaults.
+ @param {String} [button.section] The `WidgetStdMod` section (header, body,
+ footer) where the button should be added.
+ @param {Node} [button.srcNode] An existing Node to use for the button,
+ default values will be seeded from this node, but are overriden by any
+ values specified in the config object. By default a new <button>
+ node will be created.
+ @param {String} [button.template] A specific template to use when creating
+ a new button node (e.g. "<a />"). **Note:** Specifying a `srcNode`
+ will overide this.
+ @param {String} [section="footer"] The `WidgetStdMod` section
+ (header/body/footer) where the button should be added. This takes
+ precedence over the `button.section` configuration property.
+ @param {Number} [index] The index at which the button should be inserted. If
+ not specified, the button will be added to the end of the section. This
+ value is passed to the Array `splice()` method, therefore a negative
+ value will insert the `button` that many items from the end.
+ @chainable
+ @see Plugin.Button.createNode()
+ @since 3.4.0
+ **/
+ addButton: function (button, section, index) {
+ var buttons = this.get('buttons'),
+ sectionButtons, atIndex;
+
+ // Makes sure we have the full config object.
+ if (!isNode(button)) {
+ button = this._mergeButtonConfig(button);
+ section || (section = button.section);
+ }
+
+ section || (section = this.DEFAULT_BUTTONS_SECTION);
+ sectionButtons = buttons[section] || (buttons[section] = []);
+ isNumber(index) || (index = sectionButtons.length);
+
+ // Insert new button at the correct position.
+ sectionButtons.splice(index, 0, button);
+
+ // Determine the index at which the `button` now exists in the array.
+ atIndex = YArray.indexOf(sectionButtons, button);
+
+ this.set('buttons', buttons, {
+ button : button,
+ section: section,
+ index : atIndex,
+ src : 'add'
+ });
+
+ return this;
+ },
+
+ /**
+ Returns a button node from this widget's `buttons`.
+
+ @method getButton
+ @param {Number|String} name The string name or index of the button.
+ @param {String} [section="footer"] The `WidgetStdMod` section
+ (header/body/footer) where the button exists. Only applicable when
+ looking for a button by numerical index, or by name but scoped to a
+ particular section.
+ @return {Node} The button node.
+ @since 3.5.0
+ **/
+ getButton: function (name, section) {
+ if (!isValue(name)) { return; }
+
+ var map = this._buttonsMap,
+ buttons;
+
+ section || (section = this.DEFAULT_BUTTONS_SECTION);
+
+ // Supports `getButton(1, 'header')` signature.
+ if (isNumber(name)) {
+ buttons = this.get('buttons');
+ return buttons[section] && buttons[section][name];
+ }
+
+ // Looks up button by name or section:name.
+ return arguments.length > 1 ? map[section + ':' + name] : map[name];
+ },
+
+ /**
+ Removes a button from this widget.
+
+ The button will be removed from this widget's `buttons` and its DOM. Any
+ event subscriptions on the button which were created by this widget will be
+ detached. If the content section becomes empty after removing the button
+ node, then the section will also be removed.
+
+ This fires the `buttonsChange` event and adds the following properties to
+ the event facade:
+
+ * `button`: The button node to remove.
+
+ * `section`: The `WidgetStdMod` section (header/body/footer) where the
+ button should be removed from.
+
+ * `index`: The index at which the button exists in the section.
+
+ * `src`: "remove"
+
+ @method removeButton
+ @param {Node|Number|String} button The button to remove. This can be a
+ `Y.Node` instance, index, or String name of a button.
+ @param {String} [section="footer"] The `WidgetStdMod` section
+ (header/body/footer) where the button exists. Only applicable when
+ removing a button by numerical index, or by name but scoped to a
+ particular section.
+ @chainable
+ @since 3.5.0
+ **/
+ removeButton: function (button, section) {
+ if (!isValue(button)) { return this; }
+
+ var buttons = this.get('buttons'),
+ index;
+
+ // Shortcut if `button` is already an index which is needed for slicing.
+ if (isNumber(button)) {
+ section || (section = this.DEFAULT_BUTTONS_SECTION);
+ index = button;
+ button = buttons[section][index];
+ } else {
+ // Supports `button` being the string name.
+ if (isString(button)) {
+ // `getButton()` is called this way because its behavior is
+ // different based on the number of arguments.
+ button = this.getButton.apply(this, arguments);
+ }
+
+ // Determines the `section` and `index` at which the button exists.
+ YObject.some(buttons, function (sectionButtons, currentSection) {
+ index = YArray.indexOf(sectionButtons, button);
+
+ if (index > -1) {
+ section = currentSection;
+ return true;
+ }
+ });
+ }
+
+ // Button was found at an appropriate index.
+ if (button && index > -1) {
+ // Remove button from `section` array.
+ buttons[section].splice(index, 1);
+
+ this.set('buttons', buttons, {
+ button : button,
+ section: section,
+ index : index,
+ src : 'remove'
+ });
+ }
+
+ return this;
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+
+ /**
+ Binds UI event listeners. This method is inserted via AOP, and will execute
+ after `bindUI()`.
+
+ @method _bindUIButtons
+ @protected
+ @since 3.4.0
+ **/
+ _bindUIButtons: function () {
+ // Event handlers are bound with `bind()` to make them more extensible.
+ var afterContentChange = Y.bind('_afterContentChangeButtons', this);
+
+ this.after({
+ visibleChange : Y.bind('_afterVisibleChangeButtons', this),
+ headerContentChange: afterContentChange,
+ bodyContentChange : afterContentChange,
+ footerContentChange: afterContentChange
+ });
+ },
+
+ /**
+ Returns a button node based on the specified `button` node or configuration.
+
+ The button node will either be created via `Y.Plugin.Button.createNode()`,
+ or when `button` is specified as a node already, it will by `plug()`ed with
+ `Y.Plugin.Button`.
+
+ @method _createButton
+ @param {Node|Object} button Button node or configuration object.
+ @return {Node} The button node.
+ @protected
+ @since 3.5.0
+ **/
+ _createButton: function (button) {
+ var config, buttonConfig, nonButtonNodeCfg,
+ i, len, action, context, handle;
+
+ // Makes sure the exiting `Y.Node` instance is from this YUI sandbox and
+ // is plugged with `Y.Plugin.Button`.
+ if (isNode(button)) {
+ return Y.one(button.getDOMNode()).plug(ButtonPlugin);
+ }
+
+ // Merge `button` config with defaults and back-compat.
+ config = Y.merge({
+ context: this,
+ events : 'click',
+ label : button.value
+ }, button);
+
+ buttonConfig = Y.merge(config);
+ nonButtonNodeCfg = WidgetButtons.NON_BUTTON_NODE_CFG;
+
+ // Remove all non-button Node config props.
+ for (i = 0, len = nonButtonNodeCfg.length; i < len; i += 1) {
+ delete buttonConfig[nonButtonNodeCfg[i]];
+ }
+
+ // Create the button node using the button Node-only config.
+ button = ButtonPlugin.createNode(buttonConfig);
+
+ context = config.context;
+ action = config.action;
+
+ // Supports `action` as a String name of a Function on the `context`
+ // object.
+ if (isString(action)) {
+ action = Y.bind(action, context);
+ }
+
+ // Supports all types of crazy configs for event subscriptions and
+ // stores a reference to the returned `EventHandle`.
+ handle = button.on(config.events, action, context);
+ this._buttonsHandles[Y.stamp(button, true)] = handle;
+
+ // Tags the button with the configured `name` and `isDefault` settings.
+ button.setData('name', this._getButtonName(config));
+ button.setData('default', this._getButtonDefault(config));
+
+ // Add any CSS classnames to the button node.
+ YArray.each(YArray(config.classNames), button.addClass, button);
+
+ return button;
+ },
+
+ /**
+ Returns the buttons container for the specified `section`, passing a truthy
+ value for `create` will create the node if it does not already exist.
+
+ **Note:** It is up to the caller to properly insert the returned container
+ node into the content section.
+
+ @method _getButtonContainer
+ @param {String} section The `WidgetStdMod` section (header/body/footer).
+ @param {Boolean} create Whether the buttons container should be created if
+ it does not already exist.
+ @return {Node} The buttons container node for the specified `section`.
+ @protected
+ @see BUTTONS_TEMPLATE
+ @since 3.5.0
+ **/
+ _getButtonContainer: function (section, create) {
+ var sectionClassName = WidgetStdMod.SECTION_CLASS_NAMES[section],
+ buttonsClassName = WidgetButtons.CLASS_NAMES.buttons,
+ contentBox = this.get('contentBox'),
+ containerSelector, container;
+
+ // Search for an existing buttons container within the section.
+ containerSelector = '.' + sectionClassName + ' .' + buttonsClassName;
+ container = contentBox.one(containerSelector);
+
+ // Create the `container` if it doesn't already exist.
+ if (!container && create) {
+ container = Y.Node.create(this.BUTTONS_TEMPLATE);
+ container.addClass(buttonsClassName);
+ }
+
+ return container;
+ },
+
+ /**
+ Returns whether or not the specified `button` is configured to be the
+ default button.
+
+ When a button node is specified, the button's `getData()` method will be
+ used to determine if the button is configured to be the default. When a
+ button config object is specified, the `isDefault` prop will determine
+ whether the button is the default.
+
+ **Note:** `<button data-default="true"></button>` is supported via the
+ `button.getData('default')` API call.
+
+ @method _getButtonDefault
+ @param {Node|Object} button The button node or configuration object.
+ @return {Boolean} Whether the button is configured to be the default button.
+ @protected
+ @since 3.5.0
+ **/
+ _getButtonDefault: function (button) {
+ var isDefault = isNode(button) ?
+ button.getData('default') : button.isDefault;
+
+ if (isString(isDefault)) {
+ return isDefault.toLowerCase() === 'true';
+ }
+
+ return !!isDefault;
+ },
+
+ /**
+ Returns the name of the specified `button`.
+
+ When a button node is specified, the button's `getData('name')` method is
+ preferred, but will fallback to `get('name')`, and the result will determine
+ the button's name. When a button config object is specified, the `name` prop
+ will determine the button's name.
+
+ **Note:** `<button data-name="foo"></button>` is supported via the
+ `button.getData('name')` API call.
+
+ @method _getButtonName
+ @param {Node|Object} button The button node or configuration object.
+ @return {String} The name of the button.
+ @protected
+ @since 3.5.0
+ **/
+ _getButtonName: function (button) {
+ var name;
+
+ if (isNode(button)) {
+ name = button.getData('name') || button.get('name');
+ } else {
+ name = button && (button.name || button.type);
+ }
+
+ return name;
+ },
+
+ /**
+ Getter for the `buttons` attribute. A copy of the `buttons` object is
+ returned so the stored state cannot be modified by the callers of
+ `get('buttons')`.
+
+ This will recreate a copy of the `buttons` object, and each section array
+ (the button nodes are *not* copied/cloned.)
+
+ @method _getButtons
+ @param {Object} buttons The widget's current `buttons` state.
+ @return {Object} A copy of the widget's current `buttons` state.
+ @protected
+ @since 3.5.0
+ **/
+ _getButtons: function (buttons) {
+ var buttonsCopy = {};
+
+ // Creates a new copy of the `buttons` object.
+ YObject.each(buttons, function (sectionButtons, section) {
+ // Creates of copy of the array of button nodes.
+ buttonsCopy[section] = sectionButtons.concat();
+ });
+
+ return buttonsCopy;
+ },
+
+ /**
+ Adds the specified `button` to the buttons map (both name -> button and
+ section:name -> button), and sets the button as the default if it is
+ configured as the default button.
+
+ **Note:** If two or more buttons are configured with the same `name` and/or
+ configured to be the default button, the last one wins.
+
+ @method _mapButton
+ @param {Node} button The button node to map.
+ @param {String} section The `WidgetStdMod` section (header/body/footer).
+ @protected
+ @since 3.5.0
+ **/
+ _mapButton: function (button, section) {
+ var map = this._buttonsMap,
+ name = this._getButtonName(button),
+ isDefault = this._getButtonDefault(button);
+
+ if (name) {
+ // name -> button
+ map[name] = button;
+
+ // section:name -> button
+ map[section + ':' + name] = button;
+ }
+
+ isDefault && (this._defaultButton = button);
+ },
+
+ /**
+ Adds the specified `buttons` to the buttons map (both name -> button and
+ section:name -> button), and set the a button as the default if one is
+ configured as the default button.
+
+ **Note:** This will clear all previous button mappings and null-out any
+ previous default button! If two or more buttons are configured with the same
+ `name` and/or configured to be the default button, the last one wins.
+
+ @method _mapButtons
+ @param {Node[]} buttons The button nodes to map.
+ @protected
+ @since 3.5.0
+ **/
+ _mapButtons: function (buttons) {
+ this._buttonsMap = {};
+ this._defaultButton = null;
+
+ YObject.each(buttons, function (sectionButtons, section) {
+ var i, len;
+
+ for (i = 0, len = sectionButtons.length; i < len; i += 1) {
+ this._mapButton(sectionButtons[i], section);
+ }
+ }, this);
+ },
+
+ /**
+ Returns a copy of the specified `config` object merged with any defaults
+ provided by a `srcNode` and/or a predefined configuration for a button
+ with the same `name` on the `BUTTONS` property.
+
+ @method _mergeButtonConfig
+ @param {Object|String} config Button configuration object, or string name.
+ @return {Object} A copy of the button configuration object merged with any
+ defaults.
+ @protected
+ @since 3.5.0
+ **/
+ _mergeButtonConfig: function (config) {
+ var buttonConfig, defConfig, name, button, tagName, label;
+
+ // Makes sure `config` is an Object and a copy of the specified value.
+ config = isString(config) ? {name: config} : Y.merge(config);
+
+ // Seeds default values from the button node, if there is one.
+ if (config.srcNode) {
+ button = config.srcNode;
+ tagName = button.get('tagName').toLowerCase();
+ label = button.get(tagName === 'input' ? 'value' : 'text');
+
+ // Makes sure the button's current values override any defaults.
+ buttonConfig = {
+ disabled : !!button.get('disabled'),
+ isDefault: this._getButtonDefault(button),
+ name : this._getButtonName(button)
+ };
+
+ // Label should only be considered when not an empty string.
+ label && (buttonConfig.label = label);
+
+ // Merge `config` with `buttonConfig` values.
+ Y.mix(config, buttonConfig, false, null, 0, true);
+ }
+
+ name = this._getButtonName(config);
+ defConfig = this.BUTTONS && this.BUTTONS[name];
+
+ // Merge `config` with predefined default values.
+ if (defConfig) {
+ Y.mix(config, defConfig, false, null, 0, true);
+ }
+
+ return config;
+ },
+
+ /**
+ `HTML_PARSER` implementation for the `buttons` attribute.
+
+ **Note:** To determine a button node's name its `data-name` and `name`
+ attributes are examined. Whether the button should be the default is
+ determined by its `data-default` attribute.
+
+ @method _parseButtons
+ @param {Node} srcNode This widget's srcNode to search for buttons.
+ @return {null|Object} `buttons` Config object parsed from this widget's DOM.
+ @protected
+ @since 3.5.0
+ **/
+ _parseButtons: function (srcNode) {
+ var buttonSelector = '.' + WidgetButtons.CLASS_NAMES.button,
+ sections = ['header', 'body', 'footer'],
+ buttonsConfig = null;
+
+ YArray.each(sections, function (section) {
+ var container = this._getButtonContainer(section),
+ buttons = container && container.all(buttonSelector),
+ sectionButtons;
+
+ if (!buttons || buttons.isEmpty()) { return; }
+
+ sectionButtons = [];
+
+ // Creates a button config object for every button node found and
+ // adds it to the section. This way each button configuration can be
+ // merged with any defaults provided by predefined `BUTTONS`.
+ buttons.each(function (button) {
+ sectionButtons.push({srcNode: button});
+ });
+
+ buttonsConfig || (buttonsConfig = {});
+ buttonsConfig[section] = sectionButtons;
+ }, this);
+
+ return buttonsConfig;
+ },
+
+ /**
+ Setter for the `buttons` attribute. This processes the specified `config`
+ and returns a new `buttons` object which is stored as the new state; leaving
+ the original, specified `config` unmodified.
+
+ The button nodes will either be created via `Y.Plugin.Button.createNode()`,
+ or when a button is already a Node already, it will by `plug()`ed with
+ `Y.Plugin.Button`.
+
+ @method _setButtons
+ @param {Array|Object} config The `buttons` configuration to process.
+ @return {Object} The processed `buttons` object which represents the new
+ state.
+ @protected
+ @since 3.5.0
+ **/
+ _setButtons: function (config) {
+ var defSection = this.DEFAULT_BUTTONS_SECTION,
+ buttons = {};
+
+ function processButtons(buttonConfigs, currentSection) {
+ if (!isArray(buttonConfigs)) { return; }
+
+ var i, len, button, section;
+
+ for (i = 0, len = buttonConfigs.length; i < len; i += 1) {
+ button = buttonConfigs[i];
+ section = currentSection;
+
+ if (!isNode(button)) {
+ button = this._mergeButtonConfig(button);
+ section || (section = button.section);
+ }
+
+ // Always passes through `_createButton()` to make sure the node
+ // is decorated as a button.
+ button = this._createButton(button);
+
+ // Use provided `section` or fallback to the default section.
+ section || (section = defSection);
+
+ // Add button to the array of buttons for the specified section.
+ (buttons[section] || (buttons[section] = [])).push(button);
+ }
+ }
+
+ // Handle `config` being either an Array or Object of Arrays.
+ if (isArray(config)) {
+ processButtons.call(this, config);
+ } else {
+ YObject.each(config, processButtons, this);
+ }
+
+ return buttons;
+ },
+
+ /**
+ Syncs this widget's current button-related state to its DOM. This method is
+ inserted via AOP, and will execute after `syncUI()`.
+
+ @method _syncUIButtons
+ @protected
+ @since 3.4.0
+ **/
+ _syncUIButtons: function () {
+ this._uiSetButtons(this.get('buttons'));
+ this._uiSetDefaultButton(this.get('defaultButton'));
+ this._uiSetVisibleButtons(this.get('visible'));
+ },
+
+ /**
+ Inserts the specified `button` node into this widget's DOM at the specified
+ `section` and `index` and updates the section content.
+
+ The section and button container nodes will be created if they do not
+ already exist.
+
+ @method _uiInsertButton
+ @param {Node} button The button node to insert into this widget's DOM.
+ @param {String} section The `WidgetStdMod` section (header/body/footer).
+ @param {Number} index Index at which the `button` should be positioned.
+ @protected
+ @since 3.5.0
+ **/
+ _uiInsertButton: function (button, section, index) {
+ var buttonsClassName = WidgetButtons.CLASS_NAMES.button,
+ buttonContainer = this._getButtonContainer(section, true),
+ sectionButtons = buttonContainer.all('.' + buttonsClassName);
+
+ // Inserts the button node at the correct index.
+ buttonContainer.insertBefore(button, sectionButtons.item(index));
+
+ // Adds the button container to the section content.
+ this.setStdModContent(section, buttonContainer, 'after');
+ },
+
+ /**
+ Removes the button node from this widget's DOM and detaches any event
+ subscriptions on the button that were created by this widget. The section
+ content will be updated unless `{preserveContent: true}` is passed in the
+ `options`.
+
+ By default the button container node will be removed when this removes the
+ last button of the specified `section`; and if no other content remains in
+ the section node, it will also be removed.
+
+ @method _uiRemoveButton
+ @param {Node} button The button to remove and destroy.
+ @param {String} section The `WidgetStdMod` section (header/body/footer).
+ @param {Object} [options] Additional options.
+ @param {Boolean} [options.preserveContent=false] Whether the section
+ content should be updated.
+ @protected
+ @since 3.5.0
+ **/
+ _uiRemoveButton: function (button, section, options) {
+ var yuid = Y.stamp(button, this),
+ handles = this._buttonsHandles,
+ handle = handles[yuid],
+ buttonContainer, buttonClassName;
+
+ if (handle) {
+ handle.detach();
+ }
+
+ delete handles[yuid];
+
+ button.remove();
+
+ options || (options = {});
+
+ // Remove the button container and section nodes if needed.
+ if (!options.preserveContent) {
+ buttonContainer = this._getButtonContainer(section);
+ buttonClassName = WidgetButtons.CLASS_NAMES.button;
+
+ // Only matters if we have a button container which is empty.
+ if (buttonContainer &&
+ buttonContainer.all('.' + buttonClassName).isEmpty()) {
+
+ buttonContainer.remove();
+ this._updateContentButtons(section);
+ }
+ }
+ },
+
+ /**
+ Sets the current `buttons` state to this widget's DOM by rendering the
+ specified collection of `buttons` and updates the contents of each section
+ as needed.
+
+ Button nodes which already exist in the DOM will remain intact, or will be
+ moved if they should be in a new position. Old button nodes which are no
+ longer represented in the specified `buttons` collection will be removed,
+ and any event subscriptions on the button which were created by this widget
+ will be detached.
+
+ If the button nodes in this widget's DOM actually change, then each content
+ section will be updated (or removed) appropriately.
+
+ @method _uiSetButtons
+ @param {Object} buttons The current `buttons` state to visually represent.
+ @protected
+ @since 3.5.0
+ **/
+ _uiSetButtons: function (buttons) {
+ var buttonClassName = WidgetButtons.CLASS_NAMES.button,
+ sections = ['header', 'body', 'footer'];
+
+ YArray.each(sections, function (section) {
+ var sectionButtons = buttons[section] || [],
+ numButtons = sectionButtons.length,
+ buttonContainer = this._getButtonContainer(section, numButtons),
+ buttonsUpdated = false,
+ oldNodes, i, button, buttonIndex;
+
+ // When there's no button container, there are no new buttons or old
+ // buttons that we have to deal with for this section.
+ if (!buttonContainer) { return; }
+
+ oldNodes = buttonContainer.all('.' + buttonClassName);
+
+ for (i = 0; i < numButtons; i += 1) {
+ button = sectionButtons[i];
+ buttonIndex = oldNodes.indexOf(button);
+
+ // Buttons already rendered in the Widget should remain there or
+ // moved to their new index. New buttons will be added to the
+ // current `buttonContainer`.
+ if (buttonIndex > -1) {
+ // Remove button from existing buttons nodeList since its in
+ // the DOM already.
+ oldNodes.splice(buttonIndex, 1);
+
+ // Check that the button is at the right position, if not,
+ // move it to its new position.
+ if (buttonIndex !== i) {
+ // Using `i + 1` because the button should be at index
+ // `i`; it's inserted before the node which comes after.
+ buttonContainer.insertBefore(button, i + 1);
+ buttonsUpdated = true;
+ }
+ } else {
+ buttonContainer.appendChild(button);
+ buttonsUpdated = true;
+ }
+ }
+
+ // Safely removes the old button nodes which are no longer part of
+ // this widget's `buttons`.
+ oldNodes.each(function (button) {
+ this._uiRemoveButton(button, section, {preserveContent: true});
+ buttonsUpdated = true;
+ }, this);
+
+ // Remove leftover empty button containers and updated the StdMod
+ // content area.
+ if (numButtons === 0) {
+ buttonContainer.remove();
+ this._updateContentButtons(section);
+ return;
+ }
+
+ // Adds the button container to the section content.
+ if (buttonsUpdated) {
+ this.setStdModContent(section, buttonContainer, 'after');
+ }
+ }, this);
+ },
+
+ /**
+ Adds the "yui3-button-primary" CSS class to the new `defaultButton` and
+ removes it from the old default button.
+
+ @method _uiSetDefaultButton
+ @param {Node} newButton The new `defaultButton`.
+ @param {Node} oldButton The old `defaultButton`.
+ @protected
+ @since 3.5.0
+ **/
+ _uiSetDefaultButton: function (newButton, oldButton) {
+ var primaryClassName = WidgetButtons.CLASS_NAMES.primary;
+
+ if (newButton) { newButton.addClass(primaryClassName); }
+ if (oldButton) { oldButton.removeClass(primaryClassName); }
+ },
+
+ /**
+ Focuses this widget's `defaultButton` if there is one and this widget is
+ visible.
+
+ @method _uiSetVisibleButtons
+ @param {Boolean} visible Whether this widget is visible.
+ @protected
+ @since 3.5.0
+ **/
+ _uiSetVisibleButtons: function (visible) {
+ if (!visible) { return; }
+
+ var defaultButton = this.get('defaultButton');
+ if (defaultButton) {
+ defaultButton.focus();
+ }
+ },
+
+ /**
+ Removes the specified `button` from the buttons map (both name -> button and
+ section:name -> button), and nulls-out the `defaultButton` if it is
+ currently the default button.
+
+ @method _unMapButton
+ @param {Node} button The button node to remove from the buttons map.
+ @param {String} section The `WidgetStdMod` section (header/body/footer).
+ @protected
+ @since 3.5.0
+ **/
+ _unMapButton: function (button, section) {
+ var map = this._buttonsMap,
+ name = this._getButtonName(button),
+ sectionName;
+
+ // Only delete the map entry if the specified `button` is mapped to it.
+ if (name) {
+ // name -> button
+ if (map[name] === button) {
+ delete map[name];
+ }
+
+ // section:name -> button
+ sectionName = section + ':' + name;
+ if (map[sectionName] === button) {
+ delete map[sectionName];
+ }
+ }
+
+ // Clear the default button if its the specified `button`.
+ if (this._defaultButton === button) {
+ this._defaultButton = null;
+ }
+ },
+
+ /**
+ Updates the `defaultButton` attribute if it needs to be updated by comparing
+ its current value with the protected `_defaultButton` property.
+
+ @method _updateDefaultButton
+ @protected
+ @since 3.5.0
+ **/
+ _updateDefaultButton: function () {
+ var defaultButton = this._defaultButton;
+
+ if (this.get('defaultButton') !== defaultButton) {
+ this._set('defaultButton', defaultButton);
+ }
+ },
+
+ /**
+ Updates the content attribute which corresponds to the specified `section`.
+
+ The method updates the section's content to its current `childNodes`
+ (text and/or HTMLElement), or will null-out its contents if the section is
+ empty. It also specifies a `src` of `buttons` on the change event facade.
+
+ @method _updateContentButtons
+ @param {String} section The `WidgetStdMod` section (header/body/footer) to
+ update.
+ @protected
+ @since 3.5.0
+ **/
+ _updateContentButtons: function (section) {
+ // `childNodes` return text nodes and HTMLElements.
+ var sectionContent = this.getStdModNode(section).get('childNodes');
+
+ // Updates the section to its current contents, or null if it is empty.
+ this.set(section + 'Content', sectionContent.isEmpty() ? null :
+ sectionContent, {src: 'buttons'});
+ },
+
+ // -- Protected Event Handlers ---------------------------------------------
+
+ /**
+ Handles this widget's `buttonsChange` event which fires anytime the
+ `buttons` attribute is modified.
+
+ **Note:** This method special-cases the `buttons` modifications caused by
+ `addButton()` and `removeButton()`, both of which set the `src` property on
+ the event facade to "add" and "remove" respectively.
+
+ @method _afterButtonsChange
+ @param {EventFacade} e
+ @protected
+ @since 3.4.0
+ **/
+ _afterButtonsChange: function (e) {
+ var buttons = e.newVal,
+ section = e.section,
+ index = e.index,
+ src = e.src,
+ button;
+
+ // Special cases `addButton()` to only set and insert the new button.
+ if (src === 'add') {
+ // Make sure we have the button node.
+ button = buttons[section][index];
+
+ this._mapButton(button, section);
+ this._updateDefaultButton();
+ this._uiInsertButton(button, section, index);
+
+ return;
+ }
+
+ // Special cases `removeButton()` to only remove the specified button.
+ if (src === 'remove') {
+ // Button node already exists on the event facade.
+ button = e.button;
+
+ this._unMapButton(button, section);
+ this._updateDefaultButton();
+ this._uiRemoveButton(button, section);
+
+ return;
+ }
+
+ this._mapButtons(buttons);
+ this._updateDefaultButton();
+ this._uiSetButtons(buttons);
+ },
+
+ /**
+ Handles this widget's `headerContentChange`, `bodyContentChange`,
+ `footerContentChange` events by making sure the `buttons` remain rendered
+ after changes to the content areas.
+
+ These events are very chatty, so extra caution is taken to avoid doing extra
+ work or getting into an infinite loop.
+
+ @method _afterContentChangeButtons
+ @param {EventFacade} e
+ @protected
+ @since 3.5.0
+ **/
+ _afterContentChangeButtons: function (e) {
+ var src = e.src,
+ pos = e.stdModPosition,
+ replace = !pos || pos === WidgetStdMod.REPLACE;
+
+ // Only do work when absolutely necessary.
+ if (replace && src !== 'buttons' && src !== Widget.UI_SRC) {
+ this._uiSetButtons(this.get('buttons'));
+ }
+ },
+
+ /**
+ Handles this widget's `defaultButtonChange` event by adding the
+ "yui3-button-primary" CSS class to the new `defaultButton` and removing it
+ from the old default button.
+
+ @method _afterDefaultButtonChange
+ @param {EventFacade} e
+ @protected
+ @since 3.5.0
+ **/
+ _afterDefaultButtonChange: function (e) {
+ this._uiSetDefaultButton(e.newVal, e.prevVal);
+ },
+
+ /**
+ Handles this widget's `visibleChange` event by focusing the `defaultButton`
+ if there is one.
+
+ @method _afterVisibleChangeButtons
+ @param {EventFacade} e
+ @protected
+ @since 3.5.0
+ **/
+ _afterVisibleChangeButtons: function (e) {
+ this._uiSetVisibleButtons(e.newVal);
+ }
+};
+
+Y.WidgetButtons = WidgetButtons;
+
+
+}, '3.10.3', {"requires": ["button-plugin", "cssbutton", "widget-stdmod"]});