slackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangouts
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.
1864 lines
58 KiB
1864 lines
58 KiB
/** |
|
* Create simple buttons with this component. Customizations include {@link #iconAlign aligned} |
|
* {@link #iconCls icons}, {@link #cfg-menu dropdown menus}, {@link #tooltip tooltips} |
|
* and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when |
|
* a user clicks the button, or use {@link #listeners listeners} for other events such as |
|
* {@link #mouseover mouseover}. Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* text: 'Click me', |
|
* renderTo: Ext.getBody(), |
|
* handler: function() { |
|
* alert('You clicked the button!'); |
|
* } |
|
* }); |
|
* |
|
* The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler} |
|
* method. Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* text : 'Dynamic Handler Button', |
|
* renderTo: Ext.getBody(), |
|
* handler : function() { |
|
* // this button will spit out a different number every time you click it. |
|
* // so firstly we must check if that number is already set: |
|
* if (this.clickCount) { |
|
* // looks like the property is already set, so lets just add 1 to that number and alert the user |
|
* this.clickCount++; |
|
* alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..'); |
|
* } else { |
|
* // if the clickCount property is not set, we will set it and alert the user |
|
* this.clickCount = 1; |
|
* alert('You just clicked the button for the first time!\n\nTry pressing it again..'); |
|
* } |
|
* } |
|
* }); |
|
* |
|
* A button within a container: |
|
* |
|
* @example |
|
* Ext.create('Ext.Container', { |
|
* renderTo: Ext.getBody(), |
|
* items : [ |
|
* { |
|
* xtype: 'button', |
|
* text : 'My Button' |
|
* } |
|
* ] |
|
* }); |
|
* |
|
* A useful option of Button is the {@link #scale} configuration. This configuration has three different options: |
|
* |
|
* - `'small'` |
|
* - `'medium'` |
|
* - `'large'` |
|
* |
|
* Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* renderTo: document.body, |
|
* text : 'Click me', |
|
* scale : 'large' |
|
* }); |
|
* |
|
* Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`. |
|
* Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* renderTo: Ext.getBody(), |
|
* text: 'Click Me', |
|
* enableToggle: true |
|
* }); |
|
* |
|
* You can assign a menu to a button by using the {@link #cfg-menu} configuration. This standard configuration |
|
* can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a |
|
* {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically |
|
* added to the button. You can change the alignment of the arrow using the {@link #arrowAlign} configuration |
|
* on button. Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* text : 'Menu button', |
|
* renderTo : Ext.getBody(), |
|
* arrowAlign: 'bottom', |
|
* menu : [ |
|
* {text: 'Item 1'}, |
|
* {text: 'Item 2'}, |
|
* {text: 'Item 3'}, |
|
* {text: 'Item 4'} |
|
* ] |
|
* }); |
|
* |
|
* Using listeners, you can easily listen to events fired by any component, using the {@link #listeners} |
|
* configuration or using the {@link #addListener} method. Button has a variety of different listeners: |
|
* |
|
* - `click` |
|
* - `toggle` |
|
* - `mouseover` |
|
* - `mouseout` |
|
* - `mouseshow` |
|
* - `menuhide` |
|
* - `menutriggerover` |
|
* - `menutriggerout` |
|
* |
|
* Example usage: |
|
* |
|
* @example |
|
* Ext.create('Ext.Button', { |
|
* text : 'Button', |
|
* renderTo : Ext.getBody(), |
|
* listeners: { |
|
* click: function() { |
|
* // this == the button, as we are in the local scope |
|
* this.setText('I was clicked!'); |
|
* }, |
|
* mouseover: function() { |
|
* // set a new config which says we moused over, if not already set |
|
* if (!this.mousedOver) { |
|
* this.mousedOver = true; |
|
* alert('You moused over a button!\n\nI wont do this again.'); |
|
* } |
|
* } |
|
* } |
|
* }); |
|
*/ |
|
Ext.define('Ext.button.Button', { |
|
|
|
/* Begin Definitions */ |
|
alias: 'widget.button', |
|
extend: 'Ext.Component', |
|
requires: [ |
|
'Ext.dom.ButtonElement', |
|
'Ext.button.Manager', |
|
'Ext.menu.Manager', |
|
'Ext.util.ClickRepeater', |
|
'Ext.util.TextMetrics', |
|
'Ext.util.KeyMap' |
|
], |
|
|
|
mixins: [ |
|
'Ext.mixin.Queryable' |
|
], |
|
|
|
alternateClassName: 'Ext.Button', |
|
|
|
config: { |
|
/** |
|
* @cfg {String} iconAlign |
|
* The side of the Button box to render the icon. Four values are allowed: |
|
* |
|
* - 'top' |
|
* - 'right' |
|
* - 'bottom' |
|
* - 'left' |
|
*/ |
|
iconAlign: 'left', |
|
|
|
/** |
|
* @cfg {String} |
|
* The button text to be used as innerHTML (html tags are accepted). |
|
*/ |
|
text: null, |
|
|
|
/** |
|
* @cfg {String} |
|
* The text alignment for this button (center, left, right). |
|
*/ |
|
textAlign: 'center', |
|
|
|
/** |
|
* @cfg {Boolean} |
|
* `false` to hide the button arrow. Only applicable for {@link Ext.button.Split |
|
* Split Buttons} and buttons configured with a {@link #cfg-menu}. |
|
*/ |
|
arrowVisible: true |
|
}, |
|
|
|
/* End Definitions */ |
|
|
|
/* |
|
* @property {Boolean} |
|
* `true` in this class to identify an object as an instantiated Button, or subclass thereof. |
|
*/ |
|
isButton: true, |
|
|
|
//<feature legacyBrowser> |
|
/** |
|
* @property {Boolean} |
|
* @private |
|
* `true` to keep height of the frame's "MC" element in sync. This is needed in IE8 |
|
* so that the button's inner element(s) can use height:100% to fill the button when |
|
* it not in shrinkWrap mode |
|
*/ |
|
_syncFrameHeight: true, |
|
//</feature> |
|
|
|
// private, readonly |
|
liquidLayout: true, |
|
|
|
/** |
|
* @property {Boolean} hidden |
|
* True if this button is hidden. |
|
* @readonly |
|
*/ |
|
hidden: false, |
|
|
|
/** |
|
* @property {Boolean} disabled |
|
* True if this button is disabled. |
|
* @readonly |
|
*/ |
|
disabled: false, |
|
|
|
/** |
|
* @property {Boolean} pressed |
|
* True if this button is pressed (only if enableToggle = true). |
|
* @readonly |
|
*/ |
|
pressed: false, |
|
|
|
/** |
|
* @cfg {String} icon |
|
* The path to an image to display in the button. |
|
* |
|
* There are no default icons that come with Ext JS. |
|
*/ |
|
|
|
/** |
|
* @cfg {Function/String} handler |
|
* A function called when the button is clicked (can be used instead of click event). |
|
* |
|
* See also {@link #clickEvent} |
|
* @param {Ext.button.Button} button This button. |
|
* @param {Ext.event.Event} e The click event. |
|
* @declarativeHandler |
|
*/ |
|
|
|
/** |
|
* @cfg {Number} minWidth |
|
* The minimum width for this button (used to give a set of buttons a common width). |
|
* See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}. |
|
*/ |
|
|
|
/** |
|
* @cfg {String/Object} tooltip |
|
* The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or |
|
* QuickTips config object. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [hidden=false] |
|
* True to start hidden. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [disabled=false] |
|
* True to start disabled. |
|
*/ |
|
|
|
/** |
|
* @cfg padding |
|
* @inheritdoc |
|
* @removed Use the $button-*-padding CSS Vars within a custom theme instead. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [pressed=false] |
|
* True to start pressed (only if enableToggle = true) |
|
*/ |
|
|
|
/** |
|
* @cfg {String} toggleGroup |
|
* The group this toggle button is a member of (only 1 per group can be pressed). If a toggleGroup |
|
* is specified, the {@link #enableToggle} configuration will automatically be set to true. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean/Object} [repeat=false] |
|
* True to repeat fire the click event while the mouse is down. This can also be a |
|
* {@link Ext.util.ClickRepeater ClickRepeater} config object. |
|
*/ |
|
|
|
/** |
|
* @cfg {Number} tabIndex |
|
* Sets a DOM tabIndex for this button. tabIndex may be set to `-1` in order to remove |
|
* the button from the tab rotation. |
|
*/ |
|
tabIndex: 0, |
|
|
|
/** |
|
* @cfg {Boolean} [allowDepress=true] |
|
* False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [enableToggle=false] |
|
* True to enable pressed/not pressed toggling. If a {@link #toggleGroup} is specified, this |
|
* option will be set to true. |
|
*/ |
|
enableToggle: false, |
|
|
|
/** |
|
* @cfg {Function/String} toggleHandler |
|
* Function called when a Button with {@link #enableToggle} set to true is clicked. |
|
* @cfg {Ext.button.Button} toggleHandler.button This button. |
|
* @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed. |
|
* @declarativeHandler |
|
*/ |
|
|
|
/** |
|
* @cfg {Ext.menu.Menu/String/Object} menu |
|
* Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} menuAlign |
|
* The position to align the menu to (see {@link Ext.util.Positionable#alignTo} for more details). |
|
*/ |
|
menuAlign: 'tl-bl?', |
|
|
|
/** |
|
* @cfg {Boolean} showEmptyMenu |
|
* True to force an attached {@link #cfg-menu} with no items to be shown when clicking |
|
* this button. By default, the menu will not show if it is empty. |
|
*/ |
|
showEmptyMenu: false, |
|
|
|
/** |
|
* @cfg {String} overflowText |
|
* If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu. |
|
* See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} iconCls |
|
* A css class which sets a background image to be used as the icon for this button. |
|
* |
|
* There are no default icon classes that come with Ext JS. |
|
*/ |
|
|
|
/** |
|
* @cfg {Number/String} glyph |
|
* A numeric unicode character code to use as the icon for this button. The default |
|
* font-family for glyphs can be set globally using |
|
* {@link Ext#setGlyphFontFamily Ext.setGlyphFontFamily()}. Alternatively, this |
|
* config option accepts a string with the charCode and font-family separated by the |
|
* `@` symbol. For example '65@My Font Family'. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} clickEvent |
|
* The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu). |
|
*/ |
|
clickEvent: 'click', |
|
|
|
/** |
|
* @cfg {Boolean} preventDefault |
|
* `true` to prevent the default action when the {@link #clickEvent} is processed. |
|
*/ |
|
preventDefault: true, |
|
|
|
/** |
|
* @cfg {Boolean} handleMouseEvents |
|
* False to disable visual cues on mouseover, mouseout and mousedown. |
|
*/ |
|
handleMouseEvents: true, |
|
|
|
/** |
|
* @cfg {String} tooltipType |
|
* The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute. |
|
*/ |
|
tooltipType: 'qtip', |
|
|
|
/** |
|
* @cfg {String} [baseCls='x-btn'] |
|
* The base CSS class to add to all buttons. |
|
*/ |
|
baseCls: Ext.baseCSSPrefix + 'btn', |
|
|
|
/** |
|
* @cfg {String} href |
|
* The URL to open when the button is clicked. Specifying this config causes the Button to be |
|
* rendered with the specified URL as the `href` attribute of its `<a>` Element. |
|
* |
|
* This is better than specifying a click handler of |
|
* |
|
* function() { window.location = "http://www.sencha.com" } |
|
* |
|
* because the UI will provide meaningful hints to the user as to what to expect upon clicking |
|
* the button, and will also allow the user to open in a new tab or window, bookmark or drag the URL, or directly save |
|
* the URL stream to disk. |
|
* |
|
* See also the {@link #hrefTarget} config. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [hrefTarget="_blank"] |
|
* The target attribute to use for the underlying anchor. Only used if the {@link #href} |
|
* property is specified. |
|
*/ |
|
hrefTarget: '_blank', |
|
|
|
/** |
|
* @cfg {Boolean} [destroyMenu=true] |
|
* Whether or not to destroy any associated menu when this button is destroyed. |
|
* In addition, a value of `true` for this config will destroy the currently bound menu when a new |
|
* menu is set in {@link #setMenu} unless overridden by that method's destroyMenu function argument. |
|
*/ |
|
destroyMenu: true, |
|
|
|
/** |
|
* @cfg {Object} baseParams |
|
* An object literal of parameters to pass to the url when the {@link #href} property is specified. |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} params |
|
* An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params |
|
* override {@link #baseParams}. New params can be set using the {@link #setParams} method. |
|
*/ |
|
|
|
/** |
|
* @cfg {String/Number} value |
|
* The value of this button. Only applicable when used as an item of a {@link Ext.button.Segmented Segmented Button}. |
|
*/ |
|
|
|
focusable: true, |
|
ariaRole: 'button', |
|
|
|
defaultBindProperty: 'text', |
|
|
|
childEls: [ |
|
'btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl' |
|
], |
|
|
|
publishes: { |
|
pressed:1 |
|
}, |
|
|
|
// private |
|
_btnWrapCls: Ext.baseCSSPrefix + 'btn-wrap', |
|
_btnCls: Ext.baseCSSPrefix + 'btn-button', |
|
_baseIconCls: Ext.baseCSSPrefix + 'btn-icon-el', |
|
_glyphCls: Ext.baseCSSPrefix + 'btn-glyph', |
|
_innerCls: Ext.baseCSSPrefix + 'btn-inner', |
|
_textCls: Ext.baseCSSPrefix + 'btn-text', |
|
_noTextCls: Ext.baseCSSPrefix + 'btn-no-text', |
|
_hasIconCls: Ext.baseCSSPrefix + 'btn-icon', |
|
_pressedCls: Ext.baseCSSPrefix + 'btn-pressed', |
|
overCls: Ext.baseCSSPrefix + 'btn-over', |
|
_disabledCls: Ext.baseCSSPrefix + 'btn-disabled', |
|
_menuActiveCls: Ext.baseCSSPrefix + 'btn-menu-active', |
|
//<feature legacyBrowser> |
|
// extra class to work around broken display:table impl in opera12 |
|
_operaArrowCls: Ext.baseCSSPrefix + 'opera12m-btn-arrow', |
|
//</feature> |
|
|
|
// We have to keep "unselectable" attribute on all elements because it's not inheritable. |
|
// Without it, clicking anywhere on a button disrupts current selection and cursor position |
|
// in HtmlEditor. |
|
renderTpl: |
|
'<span id="{id}-btnWrap" data-ref="btnWrap" role="presentation" unselectable="on" style="{btnWrapStyle}" ' + |
|
'class="{btnWrapCls} {btnWrapCls}-{ui} {splitCls}{childElCls}">' + |
|
'<span id="{id}-btnEl" data-ref="btnEl" role="presentation" unselectable="on" style="{btnElStyle}" ' + |
|
'class="{btnCls} {btnCls}-{ui} {textCls} {noTextCls} {hasIconCls} ' + |
|
'{iconAlignCls} {textAlignCls} {btnElAutoHeightCls}{childElCls}">' + |
|
'<tpl if="iconBeforeText">{[values.$comp.renderIcon(values)]}</tpl>' + |
|
'<span id="{id}-btnInnerEl" data-ref="btnInnerEl" unselectable="on" ' + |
|
'class="{innerCls} {innerCls}-{ui}{childElCls}">{text}</span>' + |
|
'<tpl if="!iconBeforeText">{[values.$comp.renderIcon(values)]}</tpl>' + |
|
'</span>' + |
|
'</span>' + |
|
'{[values.$comp.getAfterMarkup ? values.$comp.getAfterMarkup(values) : ""]}' + |
|
// if "closable" (tab) add a close element icon |
|
'<tpl if="closable">' + |
|
'<span id="{id}-closeEl" data-ref="closeEl" class="{baseCls}-close-btn">' + |
|
'<tpl if="closeText">' + |
|
' {closeText}' + |
|
'</tpl>' + |
|
'</span>' + |
|
'</tpl>', |
|
|
|
iconTpl: |
|
'<span id="{id}-btnIconEl" data-ref="btnIconEl" role="presentation" unselectable="on" class="{baseIconCls} ' + |
|
'{baseIconCls}-{ui} {iconCls} {glyphCls}{childElCls}" style="' + |
|
'<tpl if="iconUrl">background-image:url({iconUrl});</tpl>' + |
|
'<tpl if="glyph && glyphFontFamily">font-family:{glyphFontFamily};</tpl>">' + |
|
'<tpl if="glyph">&#{glyph};</tpl><tpl if="iconCls || iconUrl"> </tpl>' + |
|
'</span>', |
|
|
|
/** |
|
* @cfg {"small"/"medium"/"large"} scale |
|
* The size of the Button. Three values are allowed: |
|
* |
|
* - 'small' - Results in the button element being 16px high. |
|
* - 'medium' - Results in the button element being 24px high. |
|
* - 'large' - Results in the button element being 32px high. |
|
*/ |
|
scale: 'small', |
|
|
|
/** |
|
* @private |
|
* An array of allowed scales. |
|
*/ |
|
allowedScales: ['small', 'medium', 'large'], |
|
|
|
/** |
|
* @cfg {Object} scope |
|
* The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed. |
|
* Defaults to this Button. |
|
*/ |
|
|
|
|
|
/** |
|
* @cfg {String} arrowAlign |
|
* The side of the Button box to render the arrow if the button has an associated {@link #cfg-menu}. Two |
|
* values are allowed: |
|
* |
|
* - 'right' |
|
* - 'bottom' |
|
*/ |
|
arrowAlign: 'right', |
|
|
|
/** |
|
* @cfg {String} arrowCls |
|
* The className used for the inner arrow element if the button has a menu. |
|
*/ |
|
arrowCls: 'arrow', |
|
|
|
/** |
|
* @property {Ext.Template} template |
|
* A {@link Ext.Template Template} used to create the Button's DOM structure. |
|
* |
|
* Instances, or subclasses which need a different DOM structure may provide a different template layout in |
|
* conjunction with an implementation of {@link #getTemplateArgs}. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} cls |
|
* A CSS class string to apply to the button's main element. |
|
*/ |
|
|
|
/** |
|
* @property {Ext.menu.Menu} menu |
|
* The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #cfg-menu} config |
|
* option. |
|
*/ |
|
|
|
maskOnDisable: false, |
|
|
|
shrinkWrap: 3, |
|
|
|
frame: true, |
|
|
|
autoEl: { |
|
tag: 'a', |
|
hidefocus: 'on', |
|
unselectable: 'on' |
|
}, |
|
|
|
hasFrameTable: function () { |
|
// Instead of browser sniffing, it's easier to check for the presence of frameTable. |
|
// If present, we know that it's a browser that doesn't support CSS3BorderRadius. |
|
return this.href && this.frameTable; |
|
}, |
|
|
|
frameTableListener: function () { |
|
if (!this.disabled) { |
|
this.doNavigate(); |
|
} |
|
}, |
|
|
|
doNavigate: function () { |
|
// Non-HTML5 browsers don't support a block element inside an A tag. |
|
// http://stackoverflow.com/questions/5682048/putting-a-table-inside-a-hyperlink-not-working-in-ie |
|
// Note use this.getHref() to append any params to the url. |
|
if (this.hrefTarget === '_blank') { |
|
window.open(this.getHref(), this.hrefTarget); |
|
} else { |
|
location.href = this.getHref(); |
|
} |
|
}, |
|
|
|
// A reusable object used by getTriggerRegion to avoid excessive object creation. |
|
_triggerRegion: {}, |
|
|
|
/** |
|
* @event click |
|
* Fires when this button is clicked, before the configured {@link #handler} is invoked. Execution of the |
|
* {@link #handler} may be vetoed by returning <code>false</code> to this event. |
|
* @param {Ext.button.Button} this |
|
* @param {Event} e The click event |
|
*/ |
|
|
|
/** |
|
* @event toggle |
|
* Fires when the 'pressed' state of this button changes (only if enableToggle = true) |
|
* @param {Ext.button.Button} this |
|
* @param {Boolean} pressed |
|
*/ |
|
|
|
/** |
|
* @event mouseover |
|
* Fires when the mouse hovers over the button |
|
* @param {Ext.button.Button} this |
|
* @param {Event} e The event object |
|
*/ |
|
|
|
/** |
|
* @event mouseout |
|
* Fires when the mouse exits the button |
|
* @param {Ext.button.Button} this |
|
* @param {Event} e The event object |
|
*/ |
|
|
|
/** |
|
* @event menushow |
|
* If this button has a menu, this event fires when it is shown |
|
* @param {Ext.button.Button} this |
|
* @param {Ext.menu.Menu} menu |
|
*/ |
|
|
|
/** |
|
* @event menuhide |
|
* If this button has a menu, this event fires when it is hidden |
|
* @param {Ext.button.Button} this |
|
* @param {Ext.menu.Menu} menu |
|
*/ |
|
|
|
/** |
|
* @event menutriggerover |
|
* If this button has a menu, this event fires when the mouse enters the menu triggering element |
|
* @param {Ext.button.Button} this |
|
* @param {Ext.menu.Menu} menu |
|
* @param {Event} e |
|
*/ |
|
|
|
/** |
|
* @event menutriggerout |
|
* If this button has a menu, this event fires when the mouse leaves the menu triggering element |
|
* @param {Ext.button.Button} this |
|
* @param {Ext.menu.Menu} menu |
|
* @param {Event} e |
|
*/ |
|
|
|
/** |
|
* @event textchange |
|
* Fired when the button's text is changed by the {@link #setText} method. |
|
* @param {Ext.button.Button} this |
|
* @param {String} oldText |
|
* @param {String} newText |
|
*/ |
|
|
|
/** |
|
* @event iconchange |
|
* Fired when the button's icon is changed by the {@link #setIcon} or {@link #setIconCls} methods. |
|
* @param {Ext.button.Button} this |
|
* @param {String} oldIcon |
|
* @param {String} newIcon |
|
*/ |
|
|
|
/** |
|
* @event glyphchange |
|
* Fired when the button's glyph is changed by the {@link #setGlyph} method. |
|
* @param {Ext.button.Button} this |
|
* @param {Number/String} newGlyph |
|
* @param {Number/String} oldGlyph |
|
*/ |
|
|
|
initComponent: function() { |
|
var me = this; |
|
|
|
// Ensure no selection happens |
|
me.addCls(Ext.baseCSSPrefix + 'unselectable'); |
|
|
|
//<feature legacyBrowser> |
|
if (Ext.isOpera12m && (me.split || me.menu) && me.getArrowVisible()) { |
|
me.addCls(me._operaArrowCls + '-' + me.arrowAlign); |
|
} |
|
//</feature> |
|
|
|
me.callParent(); |
|
|
|
if (me.menu) { |
|
// Flag that we'll have a splitCls |
|
me.split = true; |
|
me.setMenu(me.menu, /*destroyMenu*/false, true); |
|
} |
|
|
|
// Accept url as a synonym for href |
|
if (me.url) { |
|
me.href = me.url; |
|
} |
|
|
|
// preventDefault defaults to false for links |
|
me.configuredWithPreventDefault = me.hasOwnProperty('preventDefault'); |
|
if (me.href && !me.configuredWithPreventDefault) { |
|
me.preventDefault = false; |
|
} |
|
|
|
if (Ext.isString(me.toggleGroup) && me.toggleGroup !== '') { |
|
me.enableToggle = true; |
|
} |
|
|
|
if (me.html && !me.text) { |
|
me.text = me.html; |
|
delete me.html; |
|
} |
|
}, |
|
|
|
getElConfig: function() { |
|
var me = this, |
|
config = me.callParent(), |
|
href = me.getHref(), |
|
hrefTarget = me.hrefTarget; |
|
|
|
if (config.tag === 'a') { |
|
if (!me.disabled) { |
|
config.tabIndex = me.tabIndex; |
|
} |
|
if (href) { |
|
// https://sencha.jira.com/browse/EXTJS-11964 |
|
// Disabled links are clickable on iPad, and right clickable on desktop browsers. |
|
// The only way to completely disable navigation is removing the href |
|
if (!me.disabled) { |
|
config.href = href; |
|
if (hrefTarget) { |
|
config.target = hrefTarget; |
|
} |
|
} |
|
} |
|
} |
|
return config; |
|
}, |
|
|
|
beforeRender: function() { |
|
this.callParent(); |
|
|
|
if (this.pressed) { |
|
this.addCls(this._pressedCls); |
|
} |
|
}, |
|
|
|
initRenderData: function () { |
|
return Ext.apply(this.callParent(), this.getTemplateArgs()); |
|
}, |
|
|
|
/** |
|
* Get the {@link #cfg-menu} for this button. |
|
* @return {Ext.menu.Menu} The menu. `null` if no menu is configured. |
|
*/ |
|
getMenu: function() { |
|
return this.menu || null; |
|
}, |
|
|
|
/** |
|
* Sets a new menu for this button. Pass a falsy value to unset the current menu. |
|
* To destroy the previous menu for this button, explicitly pass `false` as the second argument. If this is not set, the destroy will depend on the |
|
* value of {@link #cfg-destroyMenu}. |
|
* |
|
* @param {Ext.menu.Menu/String/Object/null} menu Accepts a menu component, a menu id or a menu config. |
|
* @param {Boolean} destroyMenu By default, will destroy the previous set menu and remove it from the menu manager. Pass `false` to prevent the destroy. |
|
*/ |
|
setMenu: function (menu, destroyMenu, /* private */ initial) { |
|
var me = this, |
|
oldMenu = me.menu, |
|
instanced; |
|
|
|
if (oldMenu && !initial) { |
|
if (destroyMenu !== false && me.destroyMenu) { |
|
oldMenu.destroy(); |
|
} |
|
oldMenu.ownerCmp = null; |
|
} |
|
|
|
if (menu) { |
|
instanced = menu.isMenu; |
|
// Retrieve menu by id or instantiate instance if needed. |
|
menu = Ext.menu.Manager.get(menu, { |
|
// Use ownerCmp as the upward link. Menus *must have no ownerCt* - they are global floaters. |
|
// Upward navigation is done using the up() method. |
|
ownerCmp: me |
|
}); |
|
// We need to forcibly set this here because we could be passed an existing menu, which means |
|
// the config above won't get applied during creation. |
|
menu.setOwnerCmp(me, instanced); |
|
|
|
// Menu can't reshow within 250ms of being hidden. |
|
// Likewise, must set here in case an instantiated Menu is passed. |
|
// This is so that clicking on this button when the menu is visible |
|
// leaves the menu hidden. Mousedown hides it, and the click caused by |
|
// mouseup should not reshow. |
|
menu.menuClickBuffer = 250; |
|
|
|
me.mon(menu, { |
|
scope: me, |
|
show: me.onMenuShow, |
|
hide: me.onMenuHide |
|
}); |
|
|
|
// If the button wasn't initially configured with a menu or has previously been unset then we need |
|
// to poke the split classes onto the btnWrap dom element. |
|
if (!oldMenu && me.getArrowVisible()) { |
|
me.split = true; |
|
if (me.rendered) { |
|
me._addSplitCls(); |
|
me.updateLayout(); |
|
} |
|
} |
|
|
|
me.menu = menu; |
|
} else { |
|
if (me.rendered) { |
|
me._removeSplitCls(); |
|
me.updateLayout(); |
|
} |
|
|
|
me.split = false; |
|
me.menu = null; |
|
} |
|
}, |
|
|
|
// @private |
|
onRender: function() { |
|
var me = this, |
|
addOnclick, |
|
btn, |
|
btnListeners; |
|
|
|
me.callParent(arguments); |
|
|
|
// Set btn as a local variable for easy access |
|
btn = me.el; |
|
|
|
if (me.tooltip) { |
|
me.setTooltip(me.tooltip, true); |
|
} |
|
|
|
// Add the mouse events to the button |
|
if (me.handleMouseEvents) { |
|
btnListeners = { |
|
scope: me, |
|
mouseover: me.onMouseOver, |
|
mouseout: me.onMouseOut, |
|
mousedown: me.onMouseDown |
|
}; |
|
if (me.split) { |
|
btnListeners.mousemove = me.onMouseMove; |
|
} |
|
} else { |
|
btnListeners = { |
|
scope: me |
|
}; |
|
} |
|
|
|
// Touch start events must be preventDefaulted when in disabled state |
|
if (Ext.supports.Touch) { |
|
btnListeners.touchstart = me.onTouchStart; |
|
} |
|
|
|
// Check if the button has a menu |
|
if (me.menu) { |
|
me.keyMap = new Ext.util.KeyMap({ |
|
target: me.el, |
|
key: Ext.event.Event.prototype.DOWN, |
|
handler: me.onDownKey, |
|
scope: me |
|
}); |
|
} |
|
|
|
// Check if it is a repeat button |
|
if (me.repeat) { |
|
me.mon(new Ext.util.ClickRepeater(btn, Ext.isObject(me.repeat) ? me.repeat: {}), 'click', me.onRepeatClick, me); |
|
} else { |
|
|
|
// If the activation event already has a handler, make a note to add the handler later |
|
if (btnListeners[me.clickEvent]) { |
|
addOnclick = true; |
|
} else { |
|
btnListeners[me.clickEvent] = me.onClick; |
|
} |
|
} |
|
|
|
// Add whatever button listeners we need |
|
me.mon(btn, btnListeners); |
|
|
|
if (me.hasFrameTable()) { |
|
me.mon(me.frameTable, 'click', me.frameTableListener, me); |
|
} |
|
|
|
// If the listeners object had an entry for our clickEvent, add a listener now |
|
if (addOnclick) { |
|
me.mon(btn, me.clickEvent, me.onClick, me); |
|
} |
|
|
|
Ext.button.Manager.register(me); |
|
}, |
|
|
|
onFocusLeave: function(e) { |
|
this.callParent([e]); |
|
if (this.menu) { |
|
this.menu.hide(); |
|
} |
|
}, |
|
|
|
/** |
|
* This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to |
|
* create this Button's DOM structure. |
|
* |
|
* Instances or subclasses which use a different Template to create a different DOM structure may need to provide |
|
* their own implementation of this method. |
|
* @protected |
|
* |
|
* @return {Object} Substitution data for a Template. The default implementation which provides data for the default |
|
* {@link #template} returns an Object containing the following properties: |
|
* @return {String} return.innerCls A CSS class to apply to the button's text element. |
|
* @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon. |
|
* (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`) |
|
* @return {String} return.iconUrl The url for the button icon. |
|
* @return {String} return.iconCls The CSS class for the button icon. |
|
* @return {String} return.glyph The glyph to use as the button icon. |
|
* @return {String} return.glyphCls The CSS class to use for the glyph element. |
|
* @return {String} return.glyphFontFamily The CSS font-family to use for the glyph element. |
|
* @return {String} return.text The {@link #text} to display ion the Button. |
|
*/ |
|
getTemplateArgs: function() { |
|
var me = this, |
|
btnCls = me._btnCls, |
|
baseIconCls = me._baseIconCls, |
|
iconAlign = me.getIconAlign(), |
|
glyph = me.glyph, |
|
glyphFontFamily = Ext._glyphFontFamily, |
|
text = me.text, |
|
hasIcon = me._hasIcon(), |
|
hasIconCls = me._hasIconCls, |
|
glyphParts; |
|
|
|
if (typeof glyph === 'string') { |
|
glyphParts = glyph.split('@'); |
|
glyph = glyphParts[0]; |
|
glyphFontFamily = glyphParts[1]; |
|
} |
|
|
|
return { |
|
innerCls: me._innerCls, |
|
splitCls: me.getArrowVisible() ? me.getSplitCls() : '', |
|
iconUrl: me.icon, |
|
iconCls: me.iconCls, |
|
glyph: glyph, |
|
glyphCls: glyph ? me._glyphCls : '', |
|
glyphFontFamily: glyphFontFamily, |
|
text: text || ' ', |
|
closeText: me.closeText, |
|
textCls: text ? me._textCls : '', |
|
noTextCls: text ? '' : me._noTextCls, |
|
hasIconCls: hasIcon ? hasIconCls : '', |
|
btnWrapCls: me._btnWrapCls, |
|
btnWrapStyle: me.width ? 'table-layout:fixed;' : '', |
|
btnElStyle: me.height ? 'height:auto;' : '', |
|
btnCls: btnCls, |
|
baseIconCls: baseIconCls, |
|
iconBeforeText: iconAlign === 'left' || iconAlign === 'top', |
|
iconAlignCls: hasIcon ? (hasIconCls + '-' + iconAlign) : '', |
|
textAlignCls: btnCls + '-' + me.getTextAlign() |
|
}; |
|
}, |
|
|
|
renderIcon: function(values) { |
|
return this.getTpl('iconTpl').apply(values); |
|
}, |
|
|
|
/** |
|
* Sets the href of the embedded anchor element to the passed URL. |
|
* |
|
* Also appends any configured {@link #cfg-baseParams} and parameters set through {@link #setParams}. |
|
* @param {String} href The URL to set in the anchor element. |
|
* |
|
*/ |
|
setHref: function(href) { |
|
var me = this, |
|
hrefTarget = me.hrefTarget, |
|
dom; |
|
|
|
me.href = href; |
|
|
|
if (!me.configuredWithPreventDefault) { |
|
me.preventDefault = !href; |
|
} |
|
|
|
if (me.rendered) { |
|
dom = me.el.dom; |
|
// https://sencha.jira.com/browse/EXTJS-11964 |
|
// Disabled links are clickable on iPad, and right clickable on desktop browsers. |
|
// The only way to completely disable navigation is removing the href |
|
if (!href || me.disabled) { |
|
dom.removeAttribute('href'); |
|
dom.removeAttribute('hrefTarget'); |
|
} else { |
|
dom.href = me.getHref(); |
|
if (hrefTarget) { |
|
dom.target = hrefTarget; |
|
} |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* If there is a configured href for this Button, returns the href with parameters appended. |
|
* @return {String/Boolean} The href string with parameters appended. |
|
*/ |
|
getHref: function() { |
|
var me = this, |
|
href = me.href; |
|
|
|
return href ? Ext.urlAppend(href, Ext.Object.toQueryString(Ext.apply({}, me.params, me.baseParams))) : false; |
|
}, |
|
|
|
/** |
|
* Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured. |
|
* |
|
* var button = Ext.create('Ext.button.Button', { |
|
* renderTo : document.body, |
|
* text : 'Open', |
|
* href : 'http://www.sencha.com', |
|
* baseParams : { |
|
* foo : 'bar' |
|
* } |
|
* }); |
|
* |
|
* button.setParams({ |
|
* company : 'Sencha' |
|
* }); |
|
* |
|
* When clicked, this button will open a new window with the url http://www.sencha.com/?foo=bar&company=Sencha because |
|
* the button was configured with the {@link #baseParams} to have `foo` = `'bar'` |
|
* and then used {@link #setParams} to set the `company` parameter to `'Sencha'`. |
|
* |
|
* **Only valid if the Button was originally configured with a {@link #href}** |
|
* |
|
* @param {Object} params Parameters to use in the href URL. |
|
*/ |
|
setParams: function(params) { |
|
var me = this, |
|
dom; |
|
|
|
me.params = params; |
|
|
|
// https://sencha.jira.com/browse/EXTJS-11964 |
|
// Disabled links are clickable on iPad, and right clickable on desktop browsers. |
|
// The only way to completely disable navigation is removing the href |
|
if (me.rendered) { |
|
dom = me.el.dom; |
|
if (me.disabled) { |
|
dom.removeAttribute('href'); |
|
} else { |
|
dom.href = me.getHref() || ''; |
|
} |
|
} |
|
}, |
|
|
|
getSplitCls: function() { |
|
var me = this; |
|
return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : ''; |
|
}, |
|
|
|
/** |
|
* Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon} |
|
* config internally. |
|
* @param {String} icon The path to an image to display in the button |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setIcon: function(icon) { |
|
icon = icon || ''; |
|
var me = this, |
|
btnIconEl = me.btnIconEl, |
|
oldIcon = me.icon || ''; |
|
|
|
me.icon = icon; |
|
if (icon !== oldIcon) { |
|
if (btnIconEl) { |
|
btnIconEl.setStyle('background-image', icon ? 'url(' + icon + ')': ''); |
|
me._syncHasIconCls(); |
|
if (me.didIconStateChange(oldIcon, icon)) { |
|
me.updateLayout(); |
|
} |
|
} |
|
me.fireEvent('iconchange', me, oldIcon, icon); |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets the CSS class that provides a background image to use as the button's icon. This method also changes the |
|
* value of the {@link #iconCls} config internally. |
|
* @param {String} cls The CSS class providing the icon image |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setIconCls: function(cls) { |
|
cls = cls || ''; |
|
var me = this, |
|
btnIconEl = me.btnIconEl, |
|
oldCls = me.iconCls || ''; |
|
|
|
me.iconCls = cls; |
|
if (oldCls !== cls) { |
|
if (btnIconEl) { |
|
// Remove the previous iconCls from the button |
|
btnIconEl.removeCls(oldCls); |
|
btnIconEl.addCls(cls); |
|
me._syncHasIconCls(); |
|
if (me.didIconStateChange(oldCls, cls)) { |
|
me.updateLayout(); |
|
} |
|
} |
|
me.fireEvent('iconchange', me, oldCls, cls); |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets this button's glyph |
|
* @param {Number/String} glyph the numeric charCode or string charCode/font-family. |
|
* This parameter expects a format consistent with that of {@link #glyph} |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setGlyph: function(glyph) { |
|
glyph = glyph || 0; |
|
var me = this, |
|
btnIconEl = me.btnIconEl, |
|
oldGlyph = me.glyph, |
|
glyphCls = me._glyphCls, |
|
fontFamily, glyphParts; |
|
|
|
me.glyph = glyph; |
|
|
|
if (btnIconEl) { |
|
if (typeof glyph === 'string') { |
|
glyphParts = glyph.split('@'); |
|
glyph = glyphParts[0]; |
|
fontFamily = glyphParts[1] || Ext._glyphFontFamily; |
|
} |
|
|
|
if (!glyph) { |
|
btnIconEl.dom.innerHTML = ''; |
|
btnIconEl.removeCls(glyphCls); |
|
} else if (oldGlyph !== glyph) { |
|
btnIconEl.dom.innerHTML = '&#' + glyph + ';'; |
|
btnIconEl.addCls(glyphCls); |
|
} |
|
|
|
if (fontFamily) { |
|
btnIconEl.setStyle('font-family', fontFamily); |
|
} |
|
me._syncHasIconCls(); |
|
if (me.didIconStateChange(oldGlyph, glyph)) { |
|
me.updateLayout(); |
|
} |
|
} |
|
|
|
me.fireEvent('glyphchange', me, me.glyph, oldGlyph); |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets the tooltip for this Button. |
|
* |
|
* @param {String/Object} tooltip This may be: |
|
* |
|
* - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip |
|
* - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}. |
|
* |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setTooltip: function(tooltip, initial) { |
|
var me = this; |
|
|
|
if (me.rendered) { |
|
if (!initial || !tooltip) { |
|
me.clearTip(); |
|
} |
|
if (tooltip) { |
|
if (Ext.quickTipsActive && Ext.isObject(tooltip)) { |
|
Ext.tip.QuickTipManager.register(Ext.apply({ |
|
target: me.el.id |
|
}, |
|
tooltip)); |
|
me.tooltip = tooltip; |
|
} else { |
|
me.el.dom.setAttribute(me.getTipAttr(), tooltip); |
|
} |
|
} |
|
} else { |
|
me.tooltip = tooltip; |
|
} |
|
return me; |
|
}, |
|
|
|
updateIconAlign: function(align, oldAlign) { |
|
var me = this, |
|
btnEl, btnIconEl, hasIconCls; |
|
|
|
if (me.rendered) { |
|
btnEl = me.btnEl; |
|
btnIconEl = me.btnIconEl; |
|
hasIconCls = me._hasIconCls; |
|
|
|
if (oldAlign) { |
|
btnEl.removeCls(hasIconCls + '-' + oldAlign); |
|
} |
|
btnEl.addCls(hasIconCls + '-' + align); |
|
|
|
// move the iconWrap to the correct position in the dom - before the btnInnerEl |
|
// for top/left alignments, and after the btnInnerEl for right/bottom |
|
if (align === 'top' || align === 'left') { |
|
btnEl.insertFirst(btnIconEl); |
|
} else { |
|
btnEl.appendChild(btnIconEl); |
|
} |
|
me.updateLayout(); |
|
} |
|
}, |
|
|
|
updateTextAlign: function(align, oldAlign) { |
|
var me = this, |
|
btnEl = me.btnEl, |
|
btnCls = me._btnCls; |
|
|
|
if (me.rendered) { |
|
btnEl.removeCls(btnCls + '-' + oldAlign); |
|
btnEl.addCls(btnCls + '-' + align); |
|
} |
|
}, |
|
|
|
getTipAttr: function(){ |
|
return this.tooltipType === 'qtip' ? 'data-qtip' : 'title'; |
|
}, |
|
|
|
// @private |
|
getRefItems: function(deep){ |
|
var menu = this.menu, |
|
items; |
|
|
|
if (menu) { |
|
items = menu.getRefItems(deep); |
|
items.unshift(menu); |
|
} |
|
return items || []; |
|
}, |
|
|
|
// @private |
|
clearTip: function() { |
|
var me = this, |
|
el = me.el; |
|
|
|
if (Ext.quickTipsActive && Ext.isObject(me.tooltip)) { |
|
Ext.tip.QuickTipManager.unregister(el); |
|
} else { |
|
el.dom.removeAttribute(me.getTipAttr()); |
|
} |
|
}, |
|
|
|
// @private |
|
beforeDestroy: function() { |
|
var me = this; |
|
|
|
if (me.rendered) { |
|
me.clearTip(); |
|
} |
|
|
|
Ext.destroy(me.repeater); |
|
me.callParent(); |
|
}, |
|
|
|
// @private |
|
onDestroy: function() { |
|
var me = this, |
|
menu = me.menu; |
|
|
|
if (me.rendered) { |
|
Ext.destroy(me.keyMap); |
|
delete me.keyMap; |
|
} |
|
|
|
if (menu && me.destroyMenu) { |
|
me.menu = Ext.destroy(menu); |
|
} |
|
|
|
Ext.button.Manager.unregister(me); |
|
me.callParent(); |
|
}, |
|
|
|
/** |
|
* Assigns this Button's click handler |
|
* @param {Function} handler The function to call when the button is clicked |
|
* @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. |
|
* Defaults to this Button. |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setHandler: function(handler, scope) { |
|
this.handler = handler; |
|
if (arguments.length > 1) { |
|
this.scope = scope; |
|
} |
|
return this; |
|
}, |
|
|
|
updateText: function(text, oldText) { |
|
// Coerce to string. Maybe set to a numeric value. |
|
text = text == null ? '' : String(text); |
|
oldText = oldText || ''; |
|
|
|
var me = this, |
|
btnInnerEl = me.btnInnerEl, |
|
btnEl = me.btnEl; |
|
|
|
if (me.rendered) { |
|
btnInnerEl.setHtml(text || ' '); |
|
btnEl[text ? 'addCls' : 'removeCls'](me._textCls); |
|
btnEl[text ? 'removeCls' : 'addCls'](me._noTextCls); |
|
me.updateLayout(); |
|
} |
|
me.fireEvent('textchange', me, oldText, text); |
|
}, |
|
|
|
/** |
|
* Checks if the icon/iconCls changed from being empty to having a value, or having a value to being empty. |
|
* @private |
|
* @param {String} old The old icon/iconCls |
|
* @param {String} current The current icon/iconCls |
|
* @return {Boolean} True if the icon state changed |
|
*/ |
|
didIconStateChange: function(old, current) { |
|
var currentEmpty = Ext.isEmpty(current); |
|
return Ext.isEmpty(old) ? !currentEmpty : currentEmpty; |
|
}, |
|
|
|
/** |
|
* Sets the `pressed` state of this button. |
|
* @param {Boolean} [pressed=true] Pass `false` to clear the `pressed` state. |
|
* @return {Ext.button.Button} this |
|
*/ |
|
setPressed: function (pressed) { |
|
return this.toggle(pressed !== false); |
|
}, |
|
|
|
/** |
|
* If a state it passed, it becomes the pressed state otherwise the current state is toggled. |
|
* @param {Boolean} [state] Force a particular state |
|
* @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method. |
|
* @return {Ext.button.Button} this |
|
*/ |
|
toggle: function(state, suppressEvent) { |
|
var me = this; |
|
state = state === undefined ? !me.pressed: !!state; |
|
if (state !== me.pressed) { |
|
me[state ? 'addCls': 'removeCls'](me._pressedCls); |
|
me.pressed = state; |
|
if (!suppressEvent) { |
|
me.fireEvent('toggle', me, state); |
|
Ext.callback(me.toggleHandler, me.scope, [me, state], 0, me); |
|
|
|
if (me.reference && me.publishState) { |
|
me.publishState('pressed', state); |
|
} |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
maybeShowMenu: function(e) { |
|
if (this.menu) { |
|
this.showMenu(e); |
|
} |
|
}, |
|
|
|
/** |
|
* Shows this button's menu (if it has one) |
|
*/ |
|
showMenu: function(/* private */ clickEvent) { |
|
var me = this, |
|
menu = me.menu, |
|
isPointerEvent = !clickEvent || clickEvent.pointerType; |
|
|
|
if (menu && me.rendered) { |
|
if (me.tooltip && Ext.quickTipsActive && me.getTipAttr() !== 'title') { |
|
Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.el); |
|
} |
|
if (menu.isVisible()) { |
|
// Click/tap toggles the menu visibility. |
|
if (isPointerEvent) { |
|
menu.hide(); |
|
} else { |
|
menu.focus(); |
|
} |
|
} |
|
else if (!clickEvent || me.showEmptyMenu || menu.items.getCount() > 0) { |
|
// Pointer-invoked menus do not auto focus, key invoked ones do. |
|
menu.autoFocus = !isPointerEvent; |
|
menu.showBy(me.el, me.menuAlign); |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Hides this button's menu (if it has one) |
|
*/ |
|
hideMenu: function() { |
|
if (this.hasVisibleMenu()) { |
|
this.menu.hide(); |
|
} |
|
return this; |
|
}, |
|
|
|
/** |
|
* Returns true if the button has a menu and it is visible |
|
* @return {Boolean} |
|
*/ |
|
hasVisibleMenu: function() { |
|
var menu = this.menu; |
|
return menu && menu.rendered && menu.isVisible(); |
|
}, |
|
|
|
// @private |
|
onRepeatClick: function(repeat, e) { |
|
this.onClick(e); |
|
}, |
|
|
|
onTouchStart: function(e) { |
|
this.doPreventDefault(e); |
|
}, |
|
|
|
// @private |
|
onClick: function(e) { |
|
var me = this; |
|
me.doPreventDefault(e); |
|
|
|
// Can be triggered by ENTER or SPACE keydown events which set the button property. |
|
// Only veto event handling if it's a mouse event with an alternative button. |
|
// Checking e.button for a truthy value (instead of != 0) also allows touch events |
|
// (tap) to continue, as they do not have a button property defined. |
|
if (e.type !== 'keydown' && e.button) { |
|
return; |
|
} |
|
if (!me.disabled) { |
|
me.doToggle(); |
|
me.maybeShowMenu(e); |
|
me.fireHandler(e); |
|
} |
|
}, |
|
|
|
doPreventDefault: function(e) { |
|
if (e && (this.preventDefault || (this.disabled && this.getHref()))) { |
|
e.preventDefault(); |
|
} |
|
}, |
|
|
|
fireHandler: function(e) { |
|
var me = this; |
|
|
|
// Click may have destroyed the button |
|
if (me.fireEvent('click', me, e) !== false && !me.isDestroyed) { |
|
Ext.callback(me.handler, me.scope, [me, e], 0, me); |
|
} |
|
}, |
|
|
|
doToggle: function() { |
|
var me = this; |
|
if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) { |
|
me.toggle(); |
|
} |
|
}, |
|
|
|
/** |
|
* @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element. |
|
* The targets are interrogated to see what is being entered from where. |
|
* @param e |
|
*/ |
|
onMouseOver: function(e) { |
|
var me = this; |
|
if (!me.disabled && !e.within(me.el, true, true)) { |
|
me.onMouseEnter(e); |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* mouseout handler called when a mouseout event occurs anywhere within the encapsulating element - |
|
* or the mouse leaves the encapsulating element. |
|
* The targets are interrogated to see what is being exited to where. |
|
* @param e |
|
*/ |
|
onMouseOut: function(e) { |
|
var me = this; |
|
if (!e.within(me.el, true, true)) { |
|
if (me.overMenuTrigger) { |
|
me.onMenuTriggerOut(e); |
|
} |
|
me.onMouseLeave(e); |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* mousemove handler called when the mouse moves anywhere within the encapsulating element. |
|
* The position is checked to determine if the mouse is entering or leaving the trigger area. Using |
|
* mousemove to check this is more resource intensive than we'd like, but it is necessary because |
|
* the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out |
|
* events when needed. In the future we should consider making the trigger a separate element that |
|
* is absolutely positioned and sized over the trigger area. |
|
*/ |
|
onMouseMove: function(e) { |
|
var me = this, |
|
over = me.overMenuTrigger; |
|
|
|
if (me.split) { |
|
if (me.isWithinTrigger(e)) { |
|
if (!over) { |
|
me.onMenuTriggerOver(e); |
|
} |
|
} else if (over) { |
|
me.onMenuTriggerOut(e); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* @protected |
|
* Returns true if the passed event's x/y coordinates are within the trigger region |
|
* @param {Ext.event.Event} e |
|
*/ |
|
isWithinTrigger: function(e) { |
|
var me = this, |
|
el = me.el, |
|
overPosition, triggerRegion; |
|
|
|
overPosition = (me.arrowAlign === 'right') ? e.getX() - me.getX() : e.getY() - el.getY(); |
|
triggerRegion = me.getTriggerRegion(); |
|
return overPosition > triggerRegion.begin && overPosition < triggerRegion.end; |
|
}, |
|
|
|
/** |
|
* @private |
|
* Returns an object containing `begin` and `end` properties that indicate the |
|
* left/right bounds of a right trigger or the top/bottom bounds of a bottom trigger. |
|
* @return {Object} |
|
*/ |
|
getTriggerRegion: function() { |
|
var me = this, |
|
region = me._triggerRegion, |
|
isRight = me.arrowAlign === 'right', |
|
getEnd = isRight ? 'getRight' : 'getBottom', |
|
btnSize = isRight ? me.getWidth() : me.getHeight(); |
|
|
|
region.begin = btnSize - (me.el[getEnd]() - me.btnEl[getEnd]()); |
|
region.end = btnSize; |
|
return region; |
|
}, |
|
|
|
/** |
|
* @private |
|
* virtual mouseenter handler called when it is detected that the mouseout event |
|
* signified the mouse entering the encapsulating element. |
|
* @param e |
|
*/ |
|
onMouseEnter: function(e) { |
|
// overCls is handled by Component |
|
this.fireEvent('mouseover', this, e); |
|
}, |
|
|
|
/** |
|
* @private |
|
* virtual mouseleave handler called when it is detected that the mouseover event |
|
* signified the mouse entering the encapsulating element. |
|
* @param e |
|
*/ |
|
onMouseLeave: function(e) { |
|
// overCls is handled by Component |
|
this.fireEvent('mouseout', this, e); |
|
}, |
|
|
|
/** |
|
* @private |
|
* virtual mouseenter handler called when it is detected that the mouseover event |
|
* signified the mouse entering the arrow area of the button - the `<em>`. |
|
* @param e |
|
*/ |
|
onMenuTriggerOver: function(e) { |
|
var me = this, |
|
arrowTip = me.arrowTooltip; |
|
|
|
me.overMenuTrigger = true; |
|
// We don't have a separate arrow element, so we only add the tip attribute if |
|
// we're over that part of the button |
|
if (me.split && arrowTip) { |
|
me.btnWrap.dom.setAttribute(me.getTipAttr(), arrowTip); |
|
} |
|
me.fireEvent('menutriggerover', me, me.menu, e); |
|
}, |
|
|
|
/** |
|
* @private |
|
* virtual mouseleave handler called when it is detected that the mouseout event |
|
* signified the mouse leaving the arrow area of the button - the `<em>`. |
|
* @param e |
|
*/ |
|
onMenuTriggerOut: function(e) { |
|
var me = this; |
|
delete me.overMenuTrigger; |
|
// See onMenuTriggerOver |
|
if (me.split && me.arrowTooltip) { |
|
me.btnWrap.dom.setAttribute(me.getTipAttr(), ''); |
|
} |
|
me.fireEvent('menutriggerout', me, me.menu, e); |
|
}, |
|
|
|
enable: function(silent) { |
|
var me = this, |
|
href = me.href, |
|
hrefTarget = me.hrefTarget, |
|
dom; |
|
|
|
me.callParent(arguments); |
|
|
|
me.removeCls(me._disabledCls); |
|
if (me.rendered) { |
|
dom = me.el.dom; |
|
dom.setAttribute('tabindex', me.tabIndex); |
|
|
|
// https://sencha.jira.com/browse/EXTJS-11964 |
|
// Disabled links are clickable on iPad, and right clickable on desktop browsers. |
|
// The only way to completely disable navigation is removing the href |
|
if (href) { |
|
dom.href = href; |
|
} |
|
if (hrefTarget) { |
|
dom.target = hrefTarget; |
|
} |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
disable: function(silent) { |
|
var me = this, |
|
dom; |
|
|
|
me.callParent(arguments); |
|
|
|
me.addCls(me._disabledCls); |
|
me.removeCls(me.overCls); |
|
if (me.rendered) { |
|
dom = me.el.dom; |
|
dom.removeAttribute('tabindex'); |
|
|
|
// https://sencha.jira.com/browse/EXTJS-11964 |
|
// Disabled links are clickable on iPad, and right clickable on desktop browsers. |
|
// The only way to completely disable navigation is clearing the href |
|
if (me.href) { |
|
dom.removeAttribute('href'); |
|
} |
|
if (me.hrefTarget) { |
|
dom.removeAttribute('target'); |
|
} |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Method to change the scale of the button. See {@link #scale} for allowed configurations. |
|
* @param {String} scale The scale to change to. |
|
*/ |
|
setScale: function(scale) { |
|
var me = this, |
|
ui = me.ui.replace('-' + me.scale, ''); |
|
|
|
//check if it is an allowed scale |
|
if (!Ext.Array.contains(me.allowedScales, scale)) { |
|
throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')'); |
|
} |
|
|
|
me.scale = scale; |
|
me.setUI(ui); |
|
}, |
|
|
|
setUI: function(ui) { |
|
var me = this; |
|
|
|
//we need to append the scale to the UI, if not already done |
|
if (me.scale && !ui.match(me.scale)) { |
|
ui = ui + '-' + me.scale; |
|
} |
|
|
|
me.callParent([ui]); |
|
}, |
|
|
|
|
|
// @private |
|
onMouseDown: function(e) { |
|
var me = this; |
|
|
|
if (Ext.isIE || e.pointerType === 'touch') { |
|
// In IE the use of unselectable on the button's elements causes the element |
|
// to not receive focus, even when it is directly clicked. |
|
// On Touch devices, we need to explicitly focus on touchstart. |
|
Ext.defer(function() { |
|
// Deferred to give other mousedown handlers the chance to preventDefault |
|
if (!e.defaultPrevented) { |
|
var focusEl = me.getFocusEl(); |
|
|
|
// The component might be destroyed by this time |
|
if (focusEl) { |
|
focusEl.focus(); |
|
} |
|
} |
|
}, 1); |
|
} |
|
|
|
if (!me.disabled && e.button === 0) { |
|
Ext.button.Manager.onButtonMousedown(me, e); |
|
me.addCls(me._pressedCls); |
|
} |
|
}, |
|
|
|
// @private |
|
onMouseUp: function(e) { |
|
var me = this; |
|
|
|
// If the external mouseup listener of the ButtonManager fires after the button has been destroyed, ignore. |
|
if (!me.isDestroyed && e.button === 0) { |
|
if (!me.pressed) { |
|
me.removeCls(me._pressedCls); |
|
} |
|
} |
|
}, |
|
|
|
// @private |
|
onMenuShow: function() { |
|
var me = this; |
|
me.addCls(me._menuActiveCls); |
|
me.fireEvent('menushow', me, me.menu); |
|
}, |
|
|
|
// @private |
|
onMenuHide: function(e) { |
|
var me = this; |
|
|
|
me.removeCls(me._menuActiveCls); |
|
me.fireEvent('menuhide', me, me.menu); |
|
}, |
|
|
|
// @private |
|
onDownKey: function(k, e) { |
|
var me = this; |
|
|
|
if (me.menu && !me.disabled) { |
|
me.showMenu(e); |
|
e.stopEvent(); |
|
return false; |
|
} |
|
}, |
|
|
|
updateArrowVisible: function(visible) { |
|
var me = this; |
|
|
|
if (me.rendered) { |
|
if (visible) { |
|
if (me.menu || me.isSplitButton) { |
|
me.split = true; |
|
me._addSplitCls(); |
|
} |
|
} else { |
|
me._removeSplitCls(); |
|
me.split = false; |
|
} |
|
} |
|
|
|
return visible; |
|
}, |
|
|
|
privates: { |
|
addOverCls: function() { |
|
if (!this.disabled) { |
|
this.addCls(this.overCls); |
|
} |
|
}, |
|
|
|
_addSplitCls: function() { |
|
var me = this; |
|
|
|
me.btnWrap.addCls(me.getSplitCls()); |
|
|
|
//<feature legacyBrowser> |
|
if (Ext.isOpera12m) { |
|
me.addCls(me._operaArrowCls + '-' + me.arrowAlign); |
|
} |
|
//</feature> |
|
}, |
|
|
|
getFocusEl: function() { |
|
return this.el; |
|
}, |
|
|
|
/** |
|
* @private |
|
* @override |
|
* Needed for when widget is rendered into a grid cell. The class to add to the cell element. |
|
* Override needed to add scale to the mix which is part of the ui name in the |
|
* mixin and the CSS rule. |
|
*/ |
|
getTdCls: function() { |
|
return Ext.baseCSSPrefix + 'button-' + this.ui + '-' + this.scale + '-cell'; |
|
}, |
|
|
|
removeOverCls: function() { |
|
this.removeCls(this.overCls); |
|
}, |
|
|
|
_removeSplitCls: function() { |
|
var me = this; |
|
|
|
me.btnWrap.removeCls(me.getSplitCls()); |
|
|
|
//<feature legacyBrowser> |
|
if (Ext.isOpera12m) { |
|
me.removeCls(me._operaArrowCls + '-' + me.arrowAlign); |
|
} |
|
//</feature> |
|
}, |
|
|
|
_syncHasIconCls: function() { |
|
var me = this, |
|
btnEl = me.btnEl, |
|
hasIconCls = me._hasIconCls; |
|
|
|
if (btnEl) { |
|
btnEl[me._hasIcon() ? 'addCls' : 'removeCls']([ |
|
hasIconCls, |
|
hasIconCls + '-' + me.iconAlign |
|
]); |
|
} |
|
}, |
|
|
|
/** |
|
* Returns true if this button has an icon (either icon, iconCls, or glyph) |
|
* @return {Boolean} |
|
* @private |
|
*/ |
|
_hasIcon: function() { |
|
return !!(this.icon || this.iconCls || this.glyph); |
|
}, |
|
|
|
wrapPrimaryEl: function(dom) { |
|
this.el = new Ext.dom.ButtonElement(dom); |
|
this.callParent([dom]); |
|
} |
|
} |
|
});
|
|
|