tweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloud
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
16 KiB
512 lines
16 KiB
/** |
|
* SegmentedButton is a container for a group of {@link Ext.button.Button Button}s. You |
|
* may populate the segmented button's children by adding buttons to the items config. The segmented |
|
* button's children enjoy the same customizations as regular buttons, such as |
|
* menu, tooltip, etc. You can see usages of the various configuration |
|
* possibilities in the example below. |
|
* |
|
* @example @preview |
|
* Ext.create('Ext.button.Segmented', { |
|
* renderTo: Ext.getBody(), |
|
* allowMultiple: true, |
|
* items: [{ |
|
* text: 'Segment Item 1', |
|
* menu: [{ |
|
* text: 'Menu Item 1' |
|
* }] |
|
* },{ |
|
* text: 'Segment Item 2', |
|
* tooltip: 'My custom tooltip' |
|
* },{ |
|
* text: 'Segment Item 3' |
|
* }], |
|
* listeners: { |
|
* toggle: function(container, button, pressed) { |
|
* console.log("User toggled the '" + button.text + "' button: " + (pressed ? 'on' : 'off')); |
|
* } |
|
* } |
|
* }); |
|
* |
|
*/ |
|
Ext.define('Ext.button.Segmented', { |
|
extend: 'Ext.container.Container', |
|
xtype: 'segmentedbutton', |
|
requires: [ 'Ext.button.Button' ], |
|
|
|
config: { |
|
/** |
|
* @cfg {Boolean} |
|
* Allow toggling the pressed state of each button. |
|
* Only applicable when {@link #allowMultiple} is `false`. |
|
*/ |
|
allowDepress: false, |
|
|
|
/** |
|
* @cfg {Boolean} |
|
* Allow multiple pressed buttons. |
|
*/ |
|
allowMultiple: false, |
|
|
|
/** |
|
* @cfg {Boolean} |
|
* True to enable pressed/not pressed toggling. |
|
*/ |
|
allowToggle: true, |
|
|
|
/** |
|
* @cfg {Boolean} |
|
* True to align the buttons vertically |
|
*/ |
|
vertical: false, |
|
|
|
/** |
|
* @cfg {String} |
|
* Default {@link Ext.Component#ui ui} to use for buttons in this segmented button. |
|
* Buttons can override this default by specifying their own UI |
|
*/ |
|
defaultUI: 'default' |
|
}, |
|
|
|
beforeRenderConfig: { |
|
/** |
|
* @cfg {String/Number/String[]/Number[]} |
|
* The value of this button. When {@link #allowMultiple} is `false`, value is a |
|
* String or Number. When {@link #allowMultiple is `true`, value is an array |
|
* of values. A value corresponds to a child button's {@link Ext.button.Button#value |
|
* value}, or its index if no child button values match the given value. |
|
* |
|
* Using the `value` config of the child buttons with single toggle: |
|
* |
|
* @example |
|
* var button = Ext.create('Ext.button.Segmented', { |
|
* renderTo: Ext.getBody(), |
|
* value: 'optTwo', // begin with "Option Two" selected |
|
* items: [{ |
|
* text: 'Option One', |
|
* value: 'optOne' |
|
* }, { |
|
* text: 'Option Two', |
|
* value: 'optTwo' |
|
* }, { |
|
* text: 'Option Three', |
|
* value: 'optThree' |
|
* }] |
|
* }); |
|
* |
|
* console.log(button.getValue()); // optTwo |
|
* |
|
* // Sets the value to optOne, and sets the pressed state of the "Option One" button |
|
* button.setValue('optOne'); |
|
* |
|
* console.log(button.getValue()); // optOne |
|
* |
|
* Using multiple toggle, and index-based values: |
|
* |
|
* @example |
|
* var button = Ext.create('Ext.button.Segmented', { |
|
* renderTo: Ext.getBody(), |
|
* allowMultiple: true |
|
* value: [1, 2], // begin with "Option Two" and "Option Three" selected |
|
* items: [{ |
|
* text: 'Option One' |
|
* }, { |
|
* text: 'Option Two' |
|
* }, { |
|
* text: 'Option Three' |
|
* }] |
|
* }); |
|
* |
|
* // Sets value to [0, 2], and sets pressed state of "Option One" and "Option Three" |
|
* button.setValue([0, 2]); |
|
* |
|
* console.log(button.getValue()); // [0, 2] |
|
* |
|
* // Remove all pressed buttons, and set value to null |
|
* button.setValue(null); |
|
*/ |
|
value: undefined |
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
defaultBindProperty: 'value', |
|
|
|
publishes: ['value'], |
|
twoWayBindable: ['value'], |
|
|
|
layout: 'segmentedbutton', |
|
defaultType: 'button', |
|
maskOnDisable: false, |
|
isSegmentedButton: true, |
|
|
|
baseCls: Ext.baseCSSPrefix + 'segmented-button', |
|
itemCls: Ext.baseCSSPrefix + 'segmented-button-item', |
|
// private |
|
_firstCls: Ext.baseCSSPrefix + 'segmented-button-first', |
|
// private |
|
_lastCls: Ext.baseCSSPrefix + 'segmented-button-last', |
|
// private |
|
_middleCls: Ext.baseCSSPrefix + 'segmented-button-middle', |
|
|
|
/** |
|
* @event toggle |
|
* Fires when any child button's pressed state has changed. |
|
* @param {Ext.button.Segmented} this |
|
* @param {Ext.button.Button} button The toggled button. |
|
* @param {Boolean} isPressed `true` to indicate if the button was pressed. |
|
*/ |
|
|
|
applyValue: function(value, oldValue) { |
|
var me = this, |
|
allowMultiple = me.getAllowMultiple(), |
|
buttonValue, button, values, oldValues, items, i, ln; |
|
|
|
values = (value instanceof Array) ? value : (value == null) ? [] : [value]; |
|
oldValues = (oldValue instanceof Array) ? oldValue : |
|
(oldValue == null) ? [] : [oldValue]; |
|
|
|
// Set a flag to tell our toggle listener not to respond to the buttons' toggle |
|
// events while we are applying the value. |
|
me._isApplyingValue = true; |
|
|
|
if (!me.rendered) { |
|
// first time - add values of buttons with an initial config of pressed:true |
|
items = me.items.items; |
|
for (i = 0, ln = items.length; i < ln; i++) { |
|
button = items[i]; |
|
if (button.pressed) { |
|
buttonValue = button.value; |
|
if (buttonValue == null) { |
|
buttonValue = me.items.indexOf(button); |
|
} |
|
|
|
if (!Ext.Array.contains(values, buttonValue)) { |
|
values.push(buttonValue); |
|
} |
|
} |
|
} |
|
} |
|
|
|
ln = values.length; |
|
|
|
//<debug> |
|
if (ln > 1 && !allowMultiple) { |
|
Ext.Error.raise('Cannot set multiple values when allowMultiple is false'); |
|
} |
|
//</debug> |
|
|
|
// press all buttons corresponding to the values |
|
for (i = 0; i < ln; i++) { |
|
value = values[i]; |
|
button = me._lookupButtonByValue(value); |
|
|
|
if (button) { |
|
buttonValue = button.value; |
|
|
|
if ((buttonValue != null) && buttonValue !== value) { |
|
// button has a value, but it was matched by index. |
|
// transform the index into the button value |
|
values[i] = buttonValue; |
|
} |
|
|
|
if (!button.pressed) { |
|
button.setPressed(true); |
|
} |
|
} |
|
//<debug> |
|
else { |
|
// no matched button. fail. |
|
Ext.Error.raise("Invalid value '" + value + "' for segmented button: '" + me.id + "'"); |
|
} |
|
//</debug> |
|
} |
|
|
|
value = allowMultiple ? values : ln ? values[0] : null; |
|
|
|
// unpress buttons for the old values, if they do not exist in the new values array |
|
for (i = 0, ln = oldValues.length; i < ln; i++) { |
|
oldValue = oldValues[i]; |
|
if (!Ext.Array.contains(values, oldValue)) { |
|
me._lookupButtonByValue(oldValue).setPressed(false); |
|
} |
|
} |
|
|
|
me._isApplyingValue = false; |
|
|
|
return value; |
|
}, |
|
|
|
beforeRender: function() { |
|
var me = this; |
|
|
|
me.addCls(me.baseCls + me._getClsSuffix()); |
|
me._syncItemClasses(true); |
|
me.callParent(); |
|
}, |
|
|
|
onAdd: function(item) { |
|
var me = this, |
|
syncItemClasses = '_syncItemClasses'; |
|
|
|
//<debug> |
|
var items = me.items.items, |
|
ln = items.length, |
|
i = 0, |
|
value, defaultUI; |
|
|
|
if (item.ui === 'default' && !item.hasOwnProperty('ui')) { |
|
defaultUI = me.getDefaultUI(); |
|
if (defaultUI !== 'default') { |
|
item.ui = defaultUI; |
|
} |
|
} |
|
|
|
for(; i < ln; i++) { |
|
if (items[i] !== item) { |
|
value = items[i].value; |
|
if (value != null && value === item.value) { |
|
Ext.Error.raise("Segmented button '" + me.id + |
|
"' cannot contain multiple items with value: '" + value + "'"); |
|
} |
|
} |
|
} |
|
//</debug> |
|
|
|
me.mon(item, { |
|
hide: syncItemClasses, |
|
show: syncItemClasses, |
|
toggle: '_onItemToggle', |
|
scope: me |
|
}); |
|
|
|
if (me.getAllowToggle()) { |
|
item.enableToggle = true; |
|
if (!me.getAllowMultiple()) { |
|
item.toggleGroup = me.getId(); |
|
item.allowDepress = me.getAllowDepress(); |
|
} |
|
} |
|
|
|
item.addCls(me.itemCls + me._getClsSuffix()); |
|
|
|
me._syncItemClasses(); |
|
me.callParent([item]); |
|
}, |
|
|
|
onRemove: function(item) { |
|
var me = this; |
|
|
|
item.removeCls(me.itemCls + me._getClsSuffix()); |
|
me._syncItemClasses(); |
|
me.callParent([item]); |
|
}, |
|
|
|
beforeLayout: function() { |
|
if (Ext.isChrome) { |
|
// workaround for a chrome bug with table-layout:fixed elements where the element |
|
// is layed out with 0 width, for example, in the following test case, without |
|
// this workaround the segmented button has 0 width in chrome: |
|
// |
|
// Ext.create({ |
|
// renderTo: document.body, |
|
// xtype: 'toolbar', |
|
// items: [{ |
|
// xtype: 'segmentedbutton', |
|
// items: [{ |
|
// text: 'Foo' |
|
// }] |
|
// }] |
|
// }); |
|
// |
|
// reading offsetWidth corrects the issue. |
|
this.el.dom.offsetWidth; // jshint ignore:line |
|
} |
|
this.callParent(); |
|
}, |
|
|
|
updateDefaultUI: function(defaultUI) { |
|
var items = this.items, |
|
item, i, ln; |
|
|
|
if (this.rendered) { |
|
Ext.Error.raise("Changing the ui config of a segmented button after render is not supported."); |
|
} else if (items) { |
|
if (items.items) { // Mixed collection already created |
|
items = items.items; |
|
} |
|
for (i = 0, ln = items.length; i < ln; i++) { |
|
item = items[i]; |
|
if (item.ui === 'default' && defaultUI !== 'default' && !item.hasOwnProperty('ui') ) { |
|
items[i].ui = defaultUI; |
|
} |
|
} |
|
} |
|
}, |
|
|
|
//<debug> |
|
updateAllowDepress: function(newAllowDepress, oldAllowDepress) { |
|
if (this.rendered && (newAllowDepress !== oldAllowDepress)) { |
|
Ext.Error.raise("Changing the allowDepress config of a segmented button after render is not supported."); |
|
} |
|
}, |
|
|
|
updateAllowMultiple: function(newAllowMultiple, oldAllowMultiple) { |
|
if (this.rendered && (newAllowMultiple !== oldAllowMultiple)) { |
|
Ext.Error.raise("Changing the allowMultiple config of a segmented button after render is not supported."); |
|
} |
|
}, |
|
|
|
updateAllowToggle: function(newAllowToggle, oldAllowToggle) { |
|
if (this.rendered && (newAllowToggle !== oldAllowToggle)) { |
|
Ext.Error.raise("Changing the allowToggle config of a segmented button after render is not supported."); |
|
} |
|
}, |
|
|
|
updateVertical: function(newVertical, oldVertical) { |
|
if (this.rendered && (newVertical !== oldVertical)) { |
|
Ext.Error.raise("Changing the orientation of a segmented button after render is not supported."); |
|
} |
|
}, |
|
//</debug> |
|
|
|
privates: { |
|
_getClsSuffix: function() { |
|
return this.getVertical() ? '-vertical' : '-horizontal'; |
|
}, |
|
|
|
// rtl hook |
|
_getFirstCls: function() { |
|
return this._firstCls; |
|
}, |
|
|
|
// rtl hook |
|
_getLastCls: function() { |
|
return this._lastCls; |
|
}, |
|
|
|
/** |
|
* Looks up a child button by its value |
|
* @private |
|
* @param {String/Number} value The button's value or index |
|
* @return {Ext.button.Button} |
|
*/ |
|
_lookupButtonByValue: function(value) { |
|
var items = this.items.items, |
|
ln = items.length, |
|
i = 0, |
|
button = null, |
|
buttonValue, btn; |
|
|
|
for (; i < ln; i++) { |
|
btn = items[i]; |
|
buttonValue = btn.value; |
|
if ((buttonValue != null) && buttonValue === value) { |
|
button = btn; |
|
break; |
|
} |
|
} |
|
|
|
if (!button && typeof value === 'number') { |
|
// no button matched by value, assume value is an index |
|
button = items[value]; |
|
} |
|
|
|
return button; |
|
}, |
|
|
|
/** |
|
* Handles the "toggle" event of the child buttons. |
|
* @private |
|
* @param {Ext.button.Button} button |
|
* @param {Boolean} pressed |
|
*/ |
|
_onItemToggle: function(button, pressed) { |
|
if (this._isApplyingValue) { |
|
return; |
|
} |
|
var me = this, |
|
Array = Ext.Array, |
|
allowMultiple = me.allowMultiple, |
|
buttonValue = (button.value != null) ? button.value : me.items.indexOf(button), |
|
value = me.getValue(), |
|
valueIndex; |
|
|
|
if (allowMultiple) { |
|
valueIndex = Array.indexOf(value, buttonValue); |
|
} |
|
|
|
if (pressed) { |
|
if (allowMultiple) { |
|
if (valueIndex === -1) { |
|
value.push(buttonValue); |
|
} |
|
} else { |
|
value = buttonValue; |
|
} |
|
} else { |
|
if (allowMultiple) { |
|
if (valueIndex > -1) { |
|
value.splice(valueIndex, 1); |
|
} |
|
} else if (value === buttonValue) { |
|
value = null; |
|
} |
|
} |
|
|
|
me.setValue(value); |
|
|
|
me.fireEvent('toggle', me, button, pressed); |
|
}, |
|
|
|
/** |
|
* Synchronizes the "first", "last", and "middle" css classes when buttons are |
|
* added, removed, shown, or hidden |
|
* @private |
|
* @param {Boolean} force force sync even if not rendered. |
|
*/ |
|
_syncItemClasses: function(force) { |
|
var me = this, |
|
firstCls, middleCls, lastCls, items, ln, visibleItems, item, i; |
|
|
|
if (!force && !me.rendered) { |
|
return; |
|
} |
|
|
|
firstCls = me._getFirstCls(); |
|
middleCls = me._middleCls; |
|
lastCls = me._getLastCls(); |
|
items = me.items.items; |
|
ln = items.length; |
|
visibleItems = []; |
|
|
|
for (i = 0; i < ln; i++) { |
|
item = items[i]; |
|
if (!item.hidden) { |
|
visibleItems.push(item); |
|
} |
|
} |
|
|
|
ln = visibleItems.length; |
|
|
|
//remove all existing classes from visible items |
|
for (i = 0; i < ln; i++) { |
|
visibleItems[i].removeCls([ firstCls, middleCls, lastCls ]); |
|
} |
|
|
|
// do not add any classes if there is only one item (no border removal needed) |
|
if (ln > 1) { |
|
//add firstCls to the first visible button |
|
visibleItems[0].addCls(firstCls); |
|
|
|
//add middleCls to all visible buttons in between |
|
for (i = 1; i < ln - 1; i++) { |
|
visibleItems[i].addCls(middleCls); |
|
} |
|
|
|
//add lastCls to the first visible button |
|
visibleItems[ln - 1].addCls(lastCls); |
|
} |
|
} |
|
} |
|
});
|
|
|