widget-buttons.js revision 3adb0d80f3aad0b2be2fd6c8a45091d1e9fb5ec7
/**
`WidgetStdMod` extension.
@module widget-buttons
@since 3.4.0
**/
var YArray = Y.Array,
YObject = Y.Object,
// TODOs:
//
// * Styling to add spacing between buttons?
// * Clean up other TODOs :)
//
/**
`WidgetStdMod` extension.
Widget extension that makes it easy to declaratively configure a widget's
buttons. This adds a `buttons` attribute along with button- accessor and mutator
methods.
TODO: Add note about HTML_PARSER feature.
@class WidgetButtons
@extensionfor Widget
@since 3.4.0
**/
function WidgetButtons() {
// Require `Y.WidgetStdMod`.
if (!this._stdModNode) {
Y.error('WidgetStdMod must be added to the Widget before WidgetButtons.');
}
}
WidgetButtons.ATTRS = {
/**
Collection containing this 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.
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 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.
@example
{
// Uses predefined "close" button by String name.
header: ['close'],
footer: [
{
name : 'cancel',
label : 'Cancel',
action: function (e) {
this.hide();
}
},
{
name : 'okay',
label : 'Okay',
events: {
click: function (e) {
this.hide();
}
}
}
]
}
@attribute buttons
@type Object
@default {}
**/
buttons: {
getter: '_getButtons',
setter: '_setButtons',
value : {}
}
};
/**
CSS class-names used by `WidgetButtons`.
@property CLASS_NAMES
@type Object
@static
**/
// TODO: Should this be `BUTTONS_CLASS_NAMES`?
};
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
**/
'action', 'classNames', 'context','events', 'isDefault', 'section'
];
// -- Public Properties ----------------------------------------------------
/**
Collection of predefined buttons mapped from name => config.
See `addButton()` for a list of possible configuration values.
@property BUTTONS
@type Object
@default {}
@since 3.5.0
**/
BUTTONS: {},
/**
Template which wraps all buttons of a section. This is useful for styling,
by default it will have the CSS class: "yui3-widget-buttons".
@property BUTTONS_TEMPLATE
@type String
@default "<span />"
**/
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
**/
// -- Lifecycle Methods ----------------------------------------------------
initializer: function () {
// Creates `name` -> `button` mapping and sets the `_defaultButton`.
},
destructor: function () {
delete this._buttonsMap;
delete this._defaultButton;
},
// -- Public Methods -------------------------------------------------------
/**
Adds a button to this widget.
@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 a button with the same `name`
defined on the `BUTTONS` property. The following are the possible
configuration properties:
@param {Function} [button.action] The default handlers that should be
called when the button is clicked. **Note:** Specifying an `events`
configuration will most likely override this.
@param {String|String[]} [button.classNames] Additional CSS class-names
which would be added 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 {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.
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 {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.
@chainable
**/
}
// Always passes through `_createButton()` to make sure the Node is
// decorated as a button-Node.
// Insert new button at the correct position.
src : 'add'
});
return this;
},
/**
Retrieves a button.
@method getButton
@param {Number|String} name The String-name or index of a button.
@param {String} [section="footer"] The `WidgetStdMod` section
looking for a button by numerical index.
@return {Node} The button node.
@since 3.5.0
**/
var buttons;
// Supports `getButton(1, 'header')` signature.
}
return this._buttonsMap[name];
},
/**
Retrieves the default button.
@method getDefaultButton
@return {Node} The default button node.
@since 3.5.0
**/
getDefaultButton: function () {
return this._defaultButton;
},
/**
Removes a button from this widget.
@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
removing a button by numerical index.
@chainable
@since 3.5.0
**/
// Shortcut if `button` is already an index which is needed for slicing.
} else {
// Supports `button` being the String name.
// Determines the `section` and `index` at which the button exists.
if (index > -1) {
return true;
}
});
}
// Remove button from `section` Array.
src : 'remove'
});
return this;
},
// -- Protected Methods ----------------------------------------------------
/**
Binds event listeners. AOP'd after `bindUI`.
@method _bindUIButtons
@protected
**/
_bindUIButtons: function () {
// Bound with `bind()` to more more extensible.
},
/**
Returns a button-Node based on the specified `button` or configuration.
TODO: Add note about it binding events, and all that jazz.
@method _createButton
@param {Node|Object} button Button Node or configuration object.
@return {Node} The button Node.
@protected
**/
_createButton: function (button) {
}
// Merge `button` config with defaults and back-compat.
context: this,
events : 'click',
}, button);
// Remove all non button-Node config props.
delete buttonConfig[nonButtonNodeCfg[i]];
}
// Create the buton-Node using the button-Node-only config.
// Add any CSS classnames to the button Node.
// Supports all types of crazy configs for event subscriptions.
// Tags the Node with the configured `name` and `isDefault` setting.
return button;
},
/**
Returns the buttons container for the specified section, and will create it
if it does not already exist.
@method _getButtonContainer
@return {Node} The buttons container node for the specified `section`.
@protected
@see BUTTONS_TEMPLATE
**/
_getButtonContainer: function (section) {
// Create the `container` if it doesn't already exist.
if (!container) {
}
return container;
},
/**
Returns whether or not the specified `button` is configured to be default
button.
TODO: Add note about using `Y.Node.getData()`.
@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
**/
_getButtonDefault: function (button) {
}
return !!isDefault;
},
/**
Returns the name of the specified `button`.
TODO: Add note about using `Y.Node.getData()` and `Y.Node.get('name')`.
@method _getButtonName
@param {Node|Object} button The button node or configuration Object.
@return {String} The name of the button.
@protected
**/
_getButtonName: function (button) {
var name;
} else {
}
return name;
},
/**
Getter for the `buttons` attribute. Returns a copy of the `buttons` object
so the original state cannot be modified by callers of `get('buttons')`.
This will recreate a copy of the `buttons` object, and each section Array.
@method _getButtons
@param {Object} buttons The widget's current `buttons` state.
@return {Object} A copy of the widget's current `buttons` state.
@protected
**/
_getButtons: function (buttons) {
var buttonsCopy = {};
// Creates a new copy of the `buttons` object.
// Creates of copy of the Array of button nodes.
});
return buttonsCopy;
},
/**
Adds the specified `button` to the buttons map (name => button), and set the
button as the default if it is configured as the default button.
configured to be the default button, the last one wins.
@method _mapButton
@param {Node} button The button node to map.
@protected
**/
_mapButton: function (button) {
},
/**
Adds the specified `buttons` to the buttons map (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
@method _mapButtons
@param {Node[]} buttons The button nodes to map.
@protected
**/
_mapButtons: function (buttons) {
this._buttonsMap = {};
this._defaultButton = null;
}, this);
},
/**
Merges the specified `config` with the 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 button configuration object merged with any defaults.
@protected
**/
_mergeButtonConfig: function (config) {
// Merge button `config` with defaults.
if (defConfig) {
}
return config;
},
/**
`HTML_PARSER` implementation for the `buttons` attribute.
@method _parseButtons
@param {Node} srcNode This widget's srcNode to search for buttons.
@return {Object} `buttons` Config object parsed from this widget's DOM.
@protected
**/
_parseButtons: function (srcNode) {
var buttonsConfig = null,
// Hoisted support function out of for-loop.
function addButtonToSection(button) {
}
sectionButtons = [];
buttonsConfig || (buttonsConfig = {});
}
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.
@method _setButtons
@param {Array|Object} config The `buttons` configuration to process.
@return {Object} The processed `buttons` Object which represents the new
state.
@protected
**/
_setButtons: function (config) {
var defSection = this.DEFAULT_BUTTONS_SECTION,
buttons = {};
if (!isArray(buttonConfigs)) { return; }
button = buttonConfigs[i];
}
// Always passes through `_createButton()` to make sure the Node
// is decorated as a button-Node.
// Use provided `section` or fallback to the default section.
// Add button to the Array of buttons for the specified section.
}
}
// Handle `config` being either an Array or Object of Arrays.
} else {
}
return buttons;
},
/**
Syncs the current `buttons` state to this widget's DOM. AOP'd after
`syncUI`.
@method _syncUIButtons
@protected
**/
_syncUIButtons: function () {
},
/**
Inserts the specified `button` node into this widget's DOM at the specified
`section` and `index`.
@method _uiInsertButton
@param {Node} button The button node to insert into this widget's DOM.
@param {Number} index Index at which the `button` should be positioned.
@protected
**/
// Inserts the button node at the correct index.
},
/**
Removes and destroys a button node from this widget's DOM.
@method _uiRemoveButton
@param {Node} button The button to remove and destroy.
@protected
**/
_uiRemoveButton: function (button) {
},
/**
Sets the current `buttons` state to this widget's DOM by rendering the
specified collection of `buttons`.
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
destroyed.
@method _uiSetButtons
@param {Object} buttons The current `buttons` state to visually represent.
@protected
**/
_uiSetButtons: function (buttons) {
button = sectionButtons[i];
// 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.
// 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.
}
} else {
}
}
// Removes and destroys the old button nodes which are no longer
// part of this Widget's `buttons`.
buttonNodes.remove(true);
}, this);
},
/**
Focuses this widget's default button if there is one and this widget is
visible.
@method _uiSetVisibleButtons
@param {Boolean} visible Whether this widget is visible.
@protected
**/
_uiSetVisibleButtons: function (visible) {
if (!visible) { return; }
var defaultButton = this.getDefaultButton();
if (defaultButton) {
}
},
/**
Removes the specified `button` to the buttons map, and nulls-out the default
button if it is currently the default button.
@method _unMapButton
@param {Node} button The button node to remove from the buttons map.
@protected
**/
_unMapButton: function (button) {
var map = this._buttonsMap,
// Only delete the map entry if the specified `button` is mapped to it.
}
// Clear the default button if its the specified `button`.
},
// -- 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
**/
_afterButtonsChange: function (e) {
// Special cases `addButton()` to only set and insert the new button.
if (src === 'add') {
this._mapButton(button);
return;
}
// Special cases `removeButton()` to only remove the specified button.
if (src === 'remove') {
this._unMapButton(button);
this._uiRemoveButton(button);
return;
}
this._mapButtons(buttons);
this._uiSetButtons(buttons);
},
/**
Handles this widget's `visibleChange` event by focusing the default button
if there is one.
@method _afterVisibleChangeButtons
@param {EventFacade} e
@protected
**/
_afterVisibleChangeButtons: function (e) {
this._uiSetVisibleButtons(e.newVal);
}
};