]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/button/Segmented.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / button / Segmented.js
CommitLineData
6527f429
DM
1/**\r
2 * SegmentedButton is a container for a group of {@link Ext.button.Button Button}s. You \r
3 * may populate the segmented button's children by adding buttons to the items config. The segmented \r
4 * button's children enjoy the same customizations as regular buttons, such as \r
5 * menu, tooltip, etc. You can see usages of the various configuration\r
6 * possibilities in the example below.\r
7 *\r
8 * @example @preview \r
9 * Ext.create('Ext.button.Segmented', { \r
10 * renderTo: Ext.getBody(),\r
11 * allowMultiple: true,\r
12 * items: [{\r
13 * text: 'Segment Item 1',\r
14 * menu: [{\r
15 * text: 'Menu Item 1'\r
16 * }]\r
17 * },{\r
18 * text: 'Segment Item 2',\r
19 * tooltip: 'My custom tooltip'\r
20 * },{\r
21 * text: 'Segment Item 3'\r
22 * }],\r
23 * listeners: {\r
24 * toggle: function(container, button, pressed) {\r
25 * console.log("User toggled the '" + button.text + "' button: " + (pressed ? 'on' : 'off'));\r
26 * }\r
27 * }\r
28 * });\r
29 * \r
30 */\r
31Ext.define('Ext.button.Segmented', {\r
32 extend: 'Ext.container.Container',\r
33 xtype: 'segmentedbutton',\r
34 requires: [ \r
35 'Ext.button.Button',\r
36 'Ext.layout.container.SegmentedButton'\r
37 ],\r
38\r
39 config: {\r
40 /**\r
41 * @cfg {Boolean}\r
42 * Allow toggling the pressed state of each button.\r
43 * Only applicable when {@link #allowMultiple} is `false`.\r
44 */\r
45 allowDepress: false,\r
46\r
47 /**\r
48 * @cfg {Boolean}\r
49 * Allow multiple pressed buttons.\r
50 */\r
51 allowMultiple: false,\r
52\r
53 /**\r
54 * @cfg {Boolean}\r
55 * If {@link #allowMultiple} is `true`, this config may be set to `true` to indicate that at least\r
56 * one button in the set must remain pressed at all times.\r
57 *\r
58 * If no {@link #value} is configured, and no child buttons are configured `pressed`, the first child\r
59 * button is set `pressed: true`\r
60 */\r
61 forceSelection: false,\r
62\r
63 /**\r
64 * @cfg {Boolean}\r
65 * True to enable pressed/not pressed toggling.\r
66 */\r
67 allowToggle: true,\r
68\r
69 /**\r
70 * @cfg {Boolean}\r
71 * True to align the buttons vertically\r
72 */\r
73 vertical: false,\r
74\r
75 /**\r
76 * @cfg {String}\r
77 * Default {@link Ext.Component#ui ui} to use for buttons in this segmented button.\r
78 * Buttons can override this default by specifying their own UI\r
79 */\r
80 defaultUI: 'default'\r
81 },\r
82\r
83 beforeRenderConfig: {\r
84 /**\r
85 * @cfg {String/Number/String[]/Number[]}\r
86 * @accessor\r
87 * The value of this button. When {@link #allowMultiple} is `false`, value is a\r
88 * String or Number. When {@link #allowMultiple is `true`, value is an array\r
89 * of values. A value corresponds to a child button's {@link Ext.button.Button#value\r
90 * value}, or its index if no child button values match the given value.\r
91 *\r
92 * Using the `value` config of the child buttons with single toggle:\r
93 *\r
94 * @example\r
95 * var button = Ext.create('Ext.button.Segmented', {\r
96 * renderTo: Ext.getBody(),\r
97 * value: 'optTwo', // begin with "Option Two" selected\r
98 * items: [{\r
99 * text: 'Option One',\r
100 * value: 'optOne'\r
101 * }, {\r
102 * text: 'Option Two',\r
103 * value: 'optTwo'\r
104 * }, {\r
105 * text: 'Option Three',\r
106 * value: 'optThree'\r
107 * }]\r
108 * });\r
109 *\r
110 * console.log(button.getValue()); // optTwo\r
111 *\r
112 * // Sets the value to optOne, and sets the pressed state of the "Option One" button\r
113 * button.setValue('optOne');\r
114 *\r
115 * console.log(button.getValue()); // optOne\r
116 *\r
117 * Using multiple toggle, and index-based values:\r
118 *\r
119 * @example\r
120 * var button = Ext.create('Ext.button.Segmented', {\r
121 * renderTo: Ext.getBody(),\r
122 * allowMultiple: true\r
123 * value: [1, 2], // begin with "Option Two" and "Option Three" selected\r
124 * items: [{\r
125 * text: 'Option One'\r
126 * }, {\r
127 * text: 'Option Two'\r
128 * }, {\r
129 * text: 'Option Three'\r
130 * }]\r
131 * });\r
132 *\r
133 * // Sets value to [0, 2], and sets pressed state of "Option One" and "Option Three"\r
134 * button.setValue([0, 2]);\r
135 *\r
136 * console.log(button.getValue()); // [0, 2]\r
137 *\r
138 * // Remove all pressed buttons, and set value to null\r
139 * button.setValue(null);\r
140 */\r
141 value: undefined\r
142 },\r
143\r
144 /**\r
145 * @inheritdoc\r
146 */\r
147 defaultBindProperty: 'value',\r
148\r
149 publishes: ['value'],\r
150 twoWayBindable: ['value'],\r
151\r
152 layout: 'segmentedbutton',\r
153 defaultType: 'button',\r
154 maskOnDisable: false,\r
155 isSegmentedButton: true,\r
156\r
157 baseCls: Ext.baseCSSPrefix + 'segmented-button',\r
158 itemCls: Ext.baseCSSPrefix + 'segmented-button-item',\r
159\r
160 /**\r
161 * @private\r
162 */\r
163 _firstCls: Ext.baseCSSPrefix + 'segmented-button-first',\r
164\r
165 /**\r
166 * @private\r
167 */\r
168 _lastCls: Ext.baseCSSPrefix + 'segmented-button-last',\r
169\r
170 /**\r
171 * @private\r
172 */\r
173 _middleCls: Ext.baseCSSPrefix + 'segmented-button-middle',\r
174\r
175 /**\r
176 * @event toggle\r
177 * Fires when any child button's pressed state has changed.\r
178 * @param {Ext.button.Segmented} this\r
179 * @param {Ext.button.Button} button The toggled button.\r
180 * @param {Boolean} isPressed `true` to indicate if the button was pressed.\r
181 */\r
182\r
183 /**\r
184 * @event change\r
185 * Fires when any child button's pressed state has changed and caused the value to change.\r
186 * @param {Ext.button.Segmented} this\r
187 * @param {Array} newValue The new value.\r
188 * @param {Array} oldValue The old value.\r
189 */\r
190\r
191 applyValue: function(value, oldValue) {\r
192 var me = this,\r
193 allowMultiple = me.getAllowMultiple(),\r
194 buttonValue, button, values, oldValues, items, i, ln, hasPressed;\r
195\r
196 values = (value instanceof Array) ? value : (value == null) ? [] : [value];\r
197 oldValues = (oldValue instanceof Array) ? oldValue :\r
198 (oldValue == null) ? [] : [oldValue];\r
199\r
200 // Set a flag to tell our toggle listener not to respond to the buttons' toggle\r
201 // events while we are applying the value.\r
202 me._isApplyingValue = true;\r
203\r
204 if (!me.rendered) {\r
205 // first time - add values of buttons with an initial config of pressed:true\r
206 items = me.items.items;\r
207 for (i = items.length - 1; i >= 0; i--) {\r
208 button = items[i];\r
209\r
210 // If we've got back to zero with no pressed buttons and have forceSelection,\r
211 // then make button zero pressed.\r
212 if (me.forceSelection && !i && !hasPressed) {\r
213 button.pressed = true;\r
214 }\r
215 if (button.pressed) {\r
216 hasPressed = true;\r
217 buttonValue = button.value;\r
218 if (buttonValue == null) {\r
219 buttonValue = me.items.indexOf(button);\r
220 }\r
221\r
222 // We're looping backwards, unshift the values into the front of the array\r
223 if (!Ext.Array.contains(values, buttonValue)) {\r
224 values.unshift(buttonValue);\r
225 }\r
226 }\r
227 }\r
228 }\r
229\r
230 ln = values.length;\r
231\r
232 //<debug>\r
233 if (ln > 1 && !allowMultiple) {\r
234 Ext.raise('Cannot set multiple values when allowMultiple is false');\r
235 }\r
236 //</debug>\r
237\r
238 // press all buttons corresponding to the values\r
239 for (i = 0; i < ln; i++) {\r
240 value = values[i];\r
241 button = me._lookupButtonByValue(value);\r
242\r
243 if (button) {\r
244 buttonValue = button.value;\r
245\r
246 if ((buttonValue != null) && buttonValue !== value) {\r
247 // button has a value, but it was matched by index.\r
248 // transform the index into the button value\r
249 values[i] = buttonValue;\r
250 }\r
251\r
252 if (!button.pressed) {\r
253 button.setPressed(true);\r
254 }\r
255 }\r
256 //<debug>\r
257 else {\r
258 // no matched button. fail.\r
259 Ext.raise("Invalid value '" + value + "' for segmented button: '" + me.id + "'");\r
260 }\r
261 //</debug>\r
262 }\r
263\r
264 value = allowMultiple ? values : ln ? values[0] : null;\r
265\r
266 // unpress buttons for the old values, if they do not exist in the new values array\r
267 for (i = 0, ln = oldValues.length; i < ln; i++) {\r
268 oldValue = oldValues[i];\r
269 if (!Ext.Array.contains(values, oldValue)) {\r
270 me._lookupButtonByValue(oldValue).setPressed(false);\r
271 }\r
272 }\r
273\r
274 me._isApplyingValue = false;\r
275\r
276 if (me.hasListeners.change && !Ext.Array.equals(values, oldValues)) {\r
277 me.fireEvent('change', me, values, oldValues);\r
278 }\r
279 return value;\r
280 },\r
281\r
282 beforeRender: function() {\r
283 var me = this;\r
284\r
285 me.addCls(me.baseCls + me._getClsSuffix());\r
286 me._syncItemClasses(true);\r
287 me.callParent();\r
288 },\r
289\r
290 onAdd: function(item) {\r
291 var me = this,\r
292 syncItemClasses = '_syncItemClasses';\r
293\r
294 //<debug>\r
295 var items = me.items.items,\r
296 ln = items.length,\r
297 i = 0,\r
298 value, defaultUI;\r
299\r
300 if (item.ui === 'default' && !item.hasOwnProperty('ui')) {\r
301 defaultUI = me.getDefaultUI();\r
302 if (defaultUI !== 'default') {\r
303 item.ui = defaultUI;\r
304 }\r
305 }\r
306\r
307 for(; i < ln; i++) {\r
308 if (items[i] !== item) {\r
309 value = items[i].value;\r
310 if (value != null && value === item.value) {\r
311 Ext.raise("Segmented button '" + me.id +\r
312 "' cannot contain multiple items with value: '" + value + "'");\r
313 }\r
314 }\r
315 }\r
316 //</debug>\r
317\r
318 me.mon(item, {\r
319 hide: syncItemClasses,\r
320 show: syncItemClasses,\r
321 beforetoggle: '_onBeforeItemToggle',\r
322 toggle: '_onItemToggle',\r
323 scope: me\r
324 });\r
325\r
326 if (me.getAllowToggle()) {\r
327 item.enableToggle = true;\r
328 if (!me.getAllowMultiple()) {\r
329 item.toggleGroup = me.getId();\r
330 item.allowDepress = me.getAllowDepress();\r
331 }\r
332 }\r
333\r
334 item.addCls(me.itemCls + me._getClsSuffix());\r
335\r
336 me._syncItemClasses();\r
337 me.callParent([item]);\r
338 },\r
339\r
340 onRemove: function(item) {\r
341 var me = this;\r
342\r
343 item.removeCls(me.itemCls + me._getClsSuffix());\r
344 me._syncItemClasses();\r
345 me.callParent([item]);\r
346 },\r
347\r
348 beforeLayout: function() {\r
349 if (Ext.isChrome) {\r
350 // workaround for a chrome bug with table-layout:fixed elements where the element\r
351 // is layed out with 0 width, for example, in the following test case, without\r
352 // this workaround the segmented button has 0 width in chrome:\r
353 //\r
354 // Ext.create({\r
355 // renderTo: document.body,\r
356 // xtype: 'toolbar',\r
357 // items: [{\r
358 // xtype: 'segmentedbutton',\r
359 // items: [{\r
360 // text: 'Foo'\r
361 // }]\r
362 // }]\r
363 // });\r
364 //\r
365 // reading offsetWidth corrects the issue.\r
366 this.el.dom.offsetWidth; // jshint ignore:line\r
367 }\r
368 this.callParent();\r
369 },\r
370\r
371 updateDefaultUI: function(defaultUI) {\r
372 var items = this.items,\r
373 item, i, ln;\r
374\r
375 if (this.rendered) {\r
376 Ext.raise("Changing the ui config of a segmented button after render is not supported.");\r
377 } else if (items) {\r
378 if (items.items) { // Mixed collection already created\r
379 items = items.items;\r
380 }\r
381 for (i = 0, ln = items.length; i < ln; i++) {\r
382 item = items[i];\r
383 if (item.ui === 'default' && defaultUI !== 'default' && !item.hasOwnProperty('ui') ) {\r
384 items[i].ui = defaultUI;\r
385 }\r
386 }\r
387 }\r
388 },\r
389\r
390 //<debug>\r
391 updateAllowDepress: function(newAllowDepress, oldAllowDepress) {\r
392 if (this.rendered && (newAllowDepress !== oldAllowDepress)) {\r
393 Ext.raise("Changing the allowDepress config of a segmented button after render is not supported.");\r
394 }\r
395 },\r
396\r
397 updateAllowMultiple: function(newAllowMultiple, oldAllowMultiple) {\r
398 if (this.rendered && (newAllowMultiple !== oldAllowMultiple)) {\r
399 Ext.raise("Changing the allowMultiple config of a segmented button after render is not supported.");\r
400 }\r
401 },\r
402\r
403 updateAllowToggle: function(newAllowToggle, oldAllowToggle) {\r
404 if (this.rendered && (newAllowToggle !== oldAllowToggle)) {\r
405 Ext.raise("Changing the allowToggle config of a segmented button after render is not supported.");\r
406 }\r
407 },\r
408\r
409 updateVertical: function(newVertical, oldVertical) {\r
410 if (this.rendered && (newVertical !== oldVertical)) {\r
411 Ext.raise("Changing the orientation of a segmented button after render is not supported.");\r
412 }\r
413 },\r
414 //</debug>\r
415\r
416 privates: {\r
417 _getClsSuffix: function() {\r
418 return this.getVertical() ? '-vertical' : '-horizontal';\r
419 },\r
420\r
421 // rtl hook\r
422 _getFirstCls: function() {\r
423 return this._firstCls;\r
424 },\r
425\r
426 // rtl hook\r
427 _getLastCls: function() {\r
428 return this._lastCls;\r
429 },\r
430\r
431 /**\r
432 * Looks up a child button by its value\r
433 * @private\r
434 * @param {String/Number} value The button's value or index\r
435 * @return {Ext.button.Button}\r
436 */\r
437 _lookupButtonByValue: function(value) {\r
438 var items = this.items.items,\r
439 ln = items.length,\r
440 i = 0,\r
441 button = null,\r
442 buttonValue, btn;\r
443\r
444 for (; i < ln; i++) {\r
445 btn = items[i];\r
446 buttonValue = btn.value;\r
447 if ((buttonValue != null) && buttonValue === value) {\r
448 button = btn;\r
449 break;\r
450 }\r
451 }\r
452\r
453 if (!button && typeof value === 'number') {\r
454 // no button matched by value, assume value is an index\r
455 button = items[value];\r
456 }\r
457\r
458 return button;\r
459 },\r
460 \r
461 _onBeforeItemToggle: function(button, pressed) {\r
462 // If we allow multiple selections, and we are forcing a selection, and we are unpressing\r
463 // and we only have one value, then veto this. we are not allowing the selection length\r
464 // to fall to zero.\r
465 if (this.allowMultiple && this.forceSelection && !pressed && this.getValue().length === 1) {\r
466 return false;\r
467 }\r
468 },\r
469\r
470 /**\r
471 * Handles the "toggle" event of the child buttons.\r
472 * @private\r
473 * @param {Ext.button.Button} button\r
474 * @param {Boolean} pressed\r
475 */\r
476 _onItemToggle: function(button, pressed) {\r
477 if (this._isApplyingValue) {\r
478 return;\r
479 }\r
480 var me = this,\r
481 Array = Ext.Array,\r
482 allowMultiple = me.allowMultiple,\r
483 buttonValue = (button.value != null) ? button.value : me.items.indexOf(button),\r
484 value = me.getValue(),\r
485 valueIndex;\r
486\r
487 if (allowMultiple) {\r
488 valueIndex = Array.indexOf(value, buttonValue);\r
489 }\r
490\r
491 if (pressed) {\r
492 if (allowMultiple) {\r
493 if (valueIndex === -1) {\r
494 // We must not mutate our value property here\r
495 value = Array.slice(value);\r
496 value.push(buttonValue);\r
497 }\r
498 } else {\r
499 value = buttonValue;\r
500 }\r
501 } else {\r
502 if (allowMultiple) {\r
503 if (valueIndex > -1) {\r
504 // We must not mutate our value property here\r
505 value = Array.slice(value);\r
506 value.splice(valueIndex, 1);\r
507 }\r
508 } else if (value === buttonValue) {\r
509 value = null;\r
510 }\r
511 }\r
512\r
513 me.setValue(value);\r
514\r
515 me.fireEvent('toggle', me, button, pressed);\r
516 },\r
517\r
518 /**\r
519 * Synchronizes the "first", "last", and "middle" css classes when buttons are\r
520 * added, removed, shown, or hidden\r
521 * @private\r
522 * @param {Boolean} force force sync even if not rendered.\r
523 */\r
524 _syncItemClasses: function(force) {\r
525 var me = this,\r
526 firstCls, middleCls, lastCls, items, ln, visibleItems, item, i;\r
527\r
528 if (!force && !me.rendered) {\r
529 return;\r
530 }\r
531\r
532 firstCls = me._getFirstCls();\r
533 middleCls = me._middleCls;\r
534 lastCls = me._getLastCls();\r
535 items = me.items.items;\r
536 ln = items.length;\r
537 visibleItems = [];\r
538\r
539 for (i = 0; i < ln; i++) {\r
540 item = items[i];\r
541 if (!item.hidden) {\r
542 visibleItems.push(item);\r
543 }\r
544 }\r
545\r
546 ln = visibleItems.length;\r
547\r
548 //remove all existing classes from visible items\r
549 for (i = 0; i < ln; i++) {\r
550 visibleItems[i].removeCls([ firstCls, middleCls, lastCls ]);\r
551 }\r
552\r
553 // do not add any classes if there is only one item (no border removal needed)\r
554 if (ln > 1) {\r
555 //add firstCls to the first visible button\r
556 visibleItems[0].addCls(firstCls);\r
557\r
558 //add middleCls to all visible buttons in between\r
559 for (i = 1; i < ln - 1; i++) {\r
560 visibleItems[i].addCls(middleCls);\r
561 }\r
562\r
563 //add lastCls to the first visible button\r
564 visibleItems[ln - 1].addCls(lastCls);\r
565 }\r
566 }\r
567 }\r
568});\r