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