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