Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / button / Button.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.button.Button
17  * @extends Ext.Component
18
19 Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
20 {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
21 and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
22 a user clicks the button, or use {@link #listeners listeners} for other events such as
23 {@link #mouseover mouseover}.
24
25 {@img Ext.button.Button/Ext.button.Button1.png Ext.button.Button component}
26 Example usage:
27
28     Ext.create('Ext.Button', {
29         text: 'Click me',
30         renderTo: Ext.getBody(),        
31         handler: function() {
32             alert('You clicked the button!')
33         }
34     });
35
36 The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler} method.
37 Example usage:
38
39     Ext.create('Ext.Button', {
40         text    : 'Dyanmic Handler Button',
41         renderTo: Ext.getBody(),
42         handler : function() {
43             //this button will spit out a different number every time you click it.
44             //so firstly we must check if that number is already set:
45             if (this.clickCount) {
46                 //looks like the property is already set, so lets just add 1 to that number and alert the user
47                 this.clickCount++;
48                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
49             } else {
50                 //if the clickCount property is not set, we will set it and alert the user
51                 this.clickCount = 1;
52                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
53             }
54         }
55     });
56
57 A button within a container:
58
59     Ext.create('Ext.Container', {
60         renderTo: Ext.getBody(),
61         items   : [
62             {
63                 xtype: 'button',
64                 text : 'My Button'
65             }
66         ]
67     });
68
69 A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
70 * `'small'`
71 * `'medium'`
72 * `'large'`
73
74 {@img Ext.button.Button/Ext.button.Button2.png Ext.button.Button component}
75 Example usage:
76
77     Ext.create('Ext.Button', {
78         renderTo: document.body,
79         text    : 'Click me',
80         scale   : 'large'
81     });
82
83 Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
84 {@img Ext.button.Button/Ext.button.Button3.png Ext.button.Button component}
85 Example usage:
86
87     Ext.create('Ext.Button', {
88         renderTo: Ext.getBody(),
89         text: 'Click Me',
90         enableToggle: true
91     });
92
93 You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration can either be a reference to a {@link Ext.menu.Menu menu}
94 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.
95 You can change the alignment of the arrow using the {@link #arrowAlign} configuration on button.
96 {@img Ext.button.Button/Ext.button.Button4.png Ext.button.Button component}
97 Example usage:
98
99     Ext.create('Ext.Button', {
100         text      : 'Menu button',
101         renderTo  : Ext.getBody(),        
102         arrowAlign: 'bottom',
103         menu      : [
104             {text: 'Item 1'},
105             {text: 'Item 2'},
106             {text: 'Item 3'},
107             {text: 'Item 4'}
108         ]
109     });
110
111 Using listeners, you can easily listen to events fired by any component, using the {@link #listeners} configuration or using the {@link #addListener} method.
112 Button has a variety of different listeners:
113 * `click`
114 * `toggle`
115 * `mouseover`
116 * `mouseout`
117 * `mouseshow`
118 * `menuhide`
119 * `menutriggerover`
120 * `menutriggerout`
121
122 Example usage:
123
124     Ext.create('Ext.Button', {
125         text     : 'Button',
126         renderTo : Ext.getBody(),
127         listeners: {
128             click: function() {
129                 //this == the button, as we are in the local scope
130                 this.setText('I was clicked!');
131             },
132             mouseover: function() {
133                 //set a new config which says we moused over, if not already set
134                 if (!this.mousedOver) {
135                     this.mousedOver = true;
136                     alert('You moused over a button!\n\nI wont do this again.');
137                 }
138             }
139         }
140     });
141
142  * @markdown
143  * @docauthor Robert Dougan <rob@sencha.com>
144  */
145 Ext.define('Ext.button.Button', {
146
147     /* Begin Definitions */
148     alias: 'widget.button',
149     extend: 'Ext.Component',
150
151     requires: [
152         'Ext.menu.Manager',
153         'Ext.util.ClickRepeater',
154         'Ext.layout.component.Button',
155         'Ext.util.TextMetrics',
156         'Ext.util.KeyMap'
157     ],
158
159     alternateClassName: 'Ext.Button',
160     /* End Definitions */
161
162     isButton: true,
163     componentLayout: 'button',
164
165     /**
166      * Read-only. True if this button is hidden
167      * @type Boolean
168      */
169     hidden: false,
170
171     /**
172      * Read-only. True if this button is disabled
173      * @type Boolean
174      */
175     disabled: false,
176
177     /**
178      * Read-only. True if this button is pressed (only if enableToggle = true)
179      * @type Boolean
180      */
181     pressed: false,
182
183     /**
184      * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
185      */
186
187     /**
188      * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
189      * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
190      */
191
192     /**
193      * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
194      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
195      * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
196      * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
197      * </ul></div>
198      */
199
200     /**
201      * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
202      * See also {@link Ext.panel.Panel}.<tt>{@link Ext.panel.Panel#minButtonWidth minButtonWidth}</tt>.
203      */
204
205     /**
206      * @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
207      */
208
209     /**
210      * @cfg {Boolean} hidden True to start hidden (defaults to false)
211      */
212
213     /**
214      * @cfg {Boolean} disabled True to start disabled (defaults to false)
215      */
216
217     /**
218      * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
219      */
220
221     /**
222      * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
223      */
224
225     /**
226      * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
227      * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
228      */
229
230     /**
231      * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
232      */
233
234     /**
235      * @cfg {Boolean} allowDepress
236      * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
237      */
238
239     /**
240      * @cfg {Boolean} enableToggle
241      * True to enable pressed/not pressed toggling (defaults to false)
242      */
243     enableToggle: false,
244
245     /**
246      * @cfg {Function} toggleHandler
247      * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
248      * <li><b>button</b> : Ext.button.Button<div class="sub-desc">this Button object</div></li>
249      * <li><b>state</b> : Boolean<div class="sub-desc">The next state of the Button, true means pressed.</div></li>
250      * </ul>
251      */
252
253     /**
254      * @cfg {Mixed} menu
255      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
256      */
257
258     /**
259      * @cfg {String} menuAlign
260      * The position to align the menu to (see {@link Ext.core.Element#alignTo} for more details, defaults to 'tl-bl?').
261      */
262     menuAlign: 'tl-bl?',
263
264     /**
265      * @cfg {String} overflowText If used in a {@link Ext.toolbar.Toolbar Toolbar}, the
266      * text to be used if this item is shown in the overflow menu. See also
267      * {@link Ext.toolbar.Item}.<code>{@link Ext.toolbar.Item#overflowText overflowText}</code>.
268      */
269
270     /**
271      * @cfg {String} iconCls
272      * A css class which sets a background image to be used as the icon for this button
273      */
274
275     /**
276      * @cfg {String} type
277      * submit, reset or button - defaults to 'button'
278      */
279     type: 'button',
280
281     /**
282      * @cfg {String} clickEvent
283      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
284      * Defaults to <tt>'click'</tt>.
285      */
286     clickEvent: 'click',
287     
288     /**
289      * @cfg {Boolean} preventDefault
290      * True to prevent the default action when the {@link #clickEvent} is processed. Defaults to true.
291      */
292     preventDefault: true,
293
294     /**
295      * @cfg {Boolean} handleMouseEvents
296      * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
297      */
298     handleMouseEvents: true,
299
300     /**
301      * @cfg {String} tooltipType
302      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
303      */
304     tooltipType: 'qtip',
305
306     /**
307      * @cfg {String} baseCls
308      * The base CSS class to add to all buttons. (Defaults to 'x-btn')
309      */
310     baseCls: Ext.baseCSSPrefix + 'btn',
311
312     /**
313      * @cfg {String} pressedCls
314      * The CSS class to add to a button when it is in the pressed state. (Defaults to 'x-btn-pressed')
315      */
316     pressedCls: 'pressed',
317     
318     /**
319      * @cfg {String} overCls
320      * The CSS class to add to a button when it is in the over (hovered) state. (Defaults to 'x-btn-over')
321      */
322     overCls: 'over',
323     
324     /**
325      * @cfg {String} focusCls
326      * The CSS class to add to a button when it is in the focussed state. (Defaults to 'x-btn-focus')
327      */
328     focusCls: 'focus',
329     
330     /**
331      * @cfg {String} menuActiveCls
332      * The CSS class to add to a button when it's menu is active. (Defaults to 'x-btn-menu-active')
333      */
334     menuActiveCls: 'menu-active',
335     
336     /**
337      * @cfg {Object} baseParams
338      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
339      */
340     
341     /**
342      * @cfg {Object} params
343      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
344      * Any params override {@link #baseParams}. New params can be set using the {@link #setParams} method.
345      */
346
347     ariaRole: 'button',
348
349     // inherited
350     renderTpl:
351         '<em class="{splitCls}">' +
352             '<tpl if="href">' +
353                 '<a href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
354                     '<span class="{baseCls}-inner">' +
355                         '{text}' +
356                     '</span>' +
357                         '<span class="{baseCls}-icon"></span>' +
358                 '</a>' +
359             '</tpl>' +
360             '<tpl if="!href">' +
361                 '<button type="{type}" hidefocus="true"' +
362                     // the autocomplete="off" is required to prevent Firefox from remembering
363                     // the button's disabled state between page reloads.
364                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
365                     '<span class="{baseCls}-inner" style="{innerSpanStyle}">' +
366                         '{text}' +
367                     '</span>' +
368                     '<span class="{baseCls}-icon"></span>' +
369                 '</button>' +
370             '</tpl>' +
371         '</em>' ,
372
373     /**
374      * @cfg {String} scale
375      * <p>(Optional) The size of the Button. Three values are allowed:</p>
376      * <ul class="mdetail-params">
377      * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
378      * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
379      * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
380      * </ul>
381      * <p>Defaults to <b><tt>'small'</tt></b>.</p>
382      */
383     scale: 'small',
384     
385     /**
386      * @private An array of allowed scales.
387      */
388     allowedScales: ['small', 'medium', 'large'],
389     
390     /**
391      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the
392      * <code>{@link #handler}</code> and <code>{@link #toggleHandler}</code> is
393      * executed. Defaults to this Button.
394      */
395
396     /**
397      * @cfg {String} iconAlign
398      * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
399      * <ul class="mdetail-params">
400      * <li>'top'<div class="sub-desc"></div></li>
401      * <li>'right'<div class="sub-desc"></div></li>
402      * <li>'bottom'<div class="sub-desc"></div></li>
403      * <li>'left'<div class="sub-desc"></div></li>
404      * </ul>
405      * <p>Defaults to <b><tt>'left'</tt></b>.</p>
406      */
407     iconAlign: 'left',
408
409     /**
410      * @cfg {String} arrowAlign
411      * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
412      * Two values are allowed:</p>
413      * <ul class="mdetail-params">
414      * <li>'right'<div class="sub-desc"></div></li>
415      * <li>'bottom'<div class="sub-desc"></div></li>
416      * </ul>
417      * <p>Defaults to <b><tt>'right'</tt></b>.</p>
418      */
419     arrowAlign: 'right',
420
421     /**
422      * @cfg {String} arrowCls
423      * <p>(Optional) The className used for the inner arrow element if the button has a menu.</p>
424      */
425     arrowCls: 'arrow',
426
427     /**
428      * @cfg {Ext.Template} template (Optional)
429      * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
430      * Instances, or subclasses which need a different DOM structure may provide a different
431      * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
432      * @type Ext.Template
433      * @property template
434      */
435
436     /**
437      * @cfg {String} cls
438      * A CSS class string to apply to the button's main element.
439      */
440
441     /**
442      * @property menu
443      * @type Menu
444      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
445      */
446
447     /**
448      * @cfg {Boolean} autoWidth
449      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content.
450      * If the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent
451      * the button from doing this automatic sizing.
452      * Defaults to <tt>undefined</tt>.
453      */
454      
455     maskOnDisable: false,
456
457     // inherit docs
458     initComponent: function() {
459         var me = this;
460         me.callParent(arguments);
461
462         me.addEvents(
463             /**
464              * @event click
465              * Fires when this button is clicked
466              * @param {Button} this
467              * @param {EventObject} e The click event
468              */
469             'click',
470
471             /**
472              * @event toggle
473              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
474              * @param {Button} this
475              * @param {Boolean} pressed
476              */
477             'toggle',
478
479             /**
480              * @event mouseover
481              * Fires when the mouse hovers over the button
482              * @param {Button} this
483              * @param {Event} e The event object
484              */
485             'mouseover',
486
487             /**
488              * @event mouseout
489              * Fires when the mouse exits the button
490              * @param {Button} this
491              * @param {Event} e The event object
492              */
493             'mouseout',
494
495             /**
496              * @event menushow
497              * If this button has a menu, this event fires when it is shown
498              * @param {Button} this
499              * @param {Menu} menu
500              */
501             'menushow',
502
503             /**
504              * @event menuhide
505              * If this button has a menu, this event fires when it is hidden
506              * @param {Button} this
507              * @param {Menu} menu
508              */
509             'menuhide',
510
511             /**
512              * @event menutriggerover
513              * If this button has a menu, this event fires when the mouse enters the menu triggering element
514              * @param {Button} this
515              * @param {Menu} menu
516              * @param {EventObject} e
517              */
518             'menutriggerover',
519
520             /**
521              * @event menutriggerout
522              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
523              * @param {Button} this
524              * @param {Menu} menu
525              * @param {EventObject} e
526              */
527             'menutriggerout'
528         );
529
530         if (me.menu) {
531             // Flag that we'll have a splitCls
532             me.split = true;
533
534             // retrieve menu by id or instantiate instance if needed
535             me.menu = Ext.menu.Manager.get(me.menu);
536             me.menu.ownerCt = me;
537         }
538
539         // Accept url as a synonym for href
540         if (me.url) {
541             me.href = me.url;
542         }
543
544         // preventDefault defaults to false for links
545         if (me.href && !me.hasOwnProperty('preventDefault')) {
546             me.preventDefault = false;
547         }
548
549         if (Ext.isString(me.toggleGroup)) {
550             me.enableToggle = true;
551         }
552
553     },
554
555     // private
556     initAria: function() {
557         this.callParent();
558         var actionEl = this.getActionEl();
559         if (this.menu) {
560             actionEl.dom.setAttribute('aria-haspopup', true);
561         }
562     },
563
564     // inherit docs
565     getActionEl: function() {
566         return this.btnEl;
567     },
568
569     // inherit docs
570     getFocusEl: function() {
571         return this.btnEl;
572     },
573
574     // private
575     setButtonCls: function() {
576         var me = this,
577             el = me.el,
578             cls = [];
579
580         if (me.useSetClass) {
581             if (!Ext.isEmpty(me.oldCls)) {
582                 me.removeClsWithUI(me.oldCls);
583                 me.removeClsWithUI(me.pressedCls);
584             }
585             
586             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
587             if (me.iconCls || me.icon) {
588                 if (me.text) {
589                     cls.push('icon-text-' + me.iconAlign);
590                 } else {
591                     cls.push('icon');
592                 }
593             } else if (me.text) {
594                 cls.push('noicon');
595             }
596             
597             me.oldCls = cls;
598             me.addClsWithUI(cls);
599             me.addClsWithUI(me.pressed ? me.pressedCls : null);
600         }
601     },
602     
603     // private
604     onRender: function(ct, position) {
605         // classNames for the button
606         var me = this,
607             repeater, btn;
608             
609         // Apply the renderData to the template args
610         Ext.applyIf(me.renderData, me.getTemplateArgs());
611
612         // Extract the button and the button wrapping element
613         Ext.applyIf(me.renderSelectors, {
614             btnEl  : me.href ? 'a' : 'button',
615             btnWrap: 'em',
616             btnInnerEl: '.' + me.baseCls + '-inner',
617             btnIconEl: '.'+ me.baseCls + '-icon'
618         });
619         
620         if (me.scale) {
621             me.ui = me.ui + '-' + me.scale;
622         }
623
624         // Render internal structure
625         me.callParent(arguments);
626
627         // If it is a split button + has a toolip for the arrow
628         if (me.split && me.arrowTooltip) {
629             me.arrowEl.dom[me.tooltipType] = me.arrowTooltip;
630         }
631
632         // Add listeners to the focus and blur events on the element
633         me.mon(me.btnEl, {
634             scope: me,
635             focus: me.onFocus,
636             blur : me.onBlur
637         });
638
639         // Set btn as a local variable for easy access
640         btn = me.el;
641
642         if (me.icon) {
643             me.setIcon(me.icon);
644         }
645
646         if (me.iconCls) {
647             me.setIconCls(me.iconCls);
648         }
649
650         if (me.tooltip) {
651             me.setTooltip(me.tooltip, true);
652         }
653
654         // Add the mouse events to the button
655         if (me.handleMouseEvents) {
656             me.mon(btn, {
657                 scope: me,
658                 mouseover: me.onMouseOver,
659                 mouseout: me.onMouseOut,
660                 mousedown: me.onMouseDown
661             });
662
663             if (me.split) {
664                 me.mon(btn, {
665                     mousemove: me.onMouseMove,
666                     scope: me
667                 });
668             }
669         }
670
671         // Check if the button has a menu
672         if (me.menu) {
673             me.mon(me.menu, {
674                 scope: me,
675                 show: me.onMenuShow,
676                 hide: me.onMenuHide
677             });
678
679             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
680                 key: Ext.EventObject.DOWN,
681                 handler: me.onDownKey,
682                 scope: me
683             });
684         }
685
686         // Check if it is a repeat button
687         if (me.repeat) {
688             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
689             me.mon(repeater, 'click', me.onRepeatClick, me);
690         } else {
691             me.mon(btn, me.clickEvent, me.onClick, me);
692         }
693
694         // Register the button in the toggle manager
695         Ext.ButtonToggleManager.register(me);
696     },
697
698     /**
699      * <p>This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used
700      * to create this Button's DOM structure.</p>
701      * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
702      * own implementation of this method.</p>
703      * <p>The default implementation which provides data for the default {@link #template} returns an Object containing the
704      * following properties:</p><div class="mdetail-params"><ul>
705      * <li><code>type</code> : The &lt;button&gt;'s {@link #type}</li>
706      * <li><code>splitCls</code> : A CSS class to determine the presence and position of an arrow icon. (<code>'x-btn-arrow'</code> or <code>'x-btn-arrow-bottom'</code> or <code>''</code>)</li>
707      * <li><code>cls</code> : A CSS class name applied to the Button's main &lt;tbody&gt; element which determines the button's scale and icon alignment.</li>
708      * <li><code>text</code> : The {@link #text} to display ion the Button.</li>
709      * <li><code>tabIndex</code> : The tab index within the input flow.</li>
710      * </ul></div>
711      * @return {Array} Substitution data for a Template.
712     */
713     getTemplateArgs: function() {
714         var me = this,
715             persistentPadding = me.getPersistentBtnPadding(),
716             innerSpanStyle = '';
717
718         // Create negative margin offsets to counteract persistent button padding if needed
719         if (Math.max.apply(Math, persistentPadding) > 0) {
720             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
721                 return -pad + 'px';
722             }).join(' ');
723         }
724
725         return {
726             href     : me.getHref(),
727             target   : me.target || '_blank',
728             type     : me.type,
729             splitCls : me.getSplitCls(),
730             cls      : me.cls,
731             text     : me.text || '&#160;',
732             tabIndex : me.tabIndex,
733             innerSpanStyle: innerSpanStyle
734         };
735     },
736
737     /**
738      * @private
739      * If there is a configured href for this Button, returns the href with parameters appended.
740      * @returns The href string with parameters appended.
741      */
742     getHref: function() {
743         var me = this,
744             params = Ext.apply({}, me.baseParams);
745             
746         // write baseParams first, then write any params
747         params = Ext.apply(params, me.params);
748         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
749     },
750
751     /**
752      * <p><b>Only valid if the Button was originally configured with a {@link #url}</b></p>
753      * <p>Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.</p>
754      * @param {Object} params Parameters to use in the href URL.
755      */
756     setParams: function(params) {
757         this.params = params;
758         this.btnEl.dom.href = this.getHref();
759     },
760
761     getSplitCls: function() {
762         var me = this;
763         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
764     },
765
766     // private
767     afterRender: function() {
768         var me = this;
769         me.useSetClass = true;
770         me.setButtonCls();
771         me.doc = Ext.getDoc();
772         this.callParent(arguments);
773     },
774
775     /**
776      * Sets the CSS class that provides a background image to use as the button's icon.  This method also changes
777      * the value of the {@link #iconCls} config internally.
778      * @param {String} cls The CSS class providing the icon image
779      * @return {Ext.button.Button} this
780      */
781     setIconCls: function(cls) {
782         var me = this,
783             btnIconEl = me.btnIconEl;
784         if (btnIconEl) {
785             // Remove the previous iconCls from the button
786             btnIconEl.removeCls(me.iconCls);
787             btnIconEl.addCls(cls || '');
788             me.setButtonCls();
789         }
790         me.iconCls = cls;
791         return me;
792     },
793
794     /**
795      * Sets the tooltip for this Button.
796      * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
797      * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
798      * <li><b>Object</b> : A configuration object for {@link Ext.tip.QuickTipManager#register}.</li>
799      * </ul></div>
800      * @return {Ext.button.Button} this
801      */
802     setTooltip: function(tooltip, initial) {
803         var me = this;
804
805         if (me.rendered) {
806             if (!initial) {
807                 me.clearTip();
808             }
809             if (Ext.isObject(tooltip)) {
810                 Ext.tip.QuickTipManager.register(Ext.apply({
811                     target: me.btnEl.id
812                 },
813                 tooltip));
814                 me.tooltip = tooltip;
815             } else {
816                 me.btnEl.dom.setAttribute('data-' + this.tooltipType, tooltip);
817             }
818         } else {
819             me.tooltip = tooltip;
820         }
821         return me;
822     },
823
824     // private
825     getRefItems: function(deep){
826         var menu = this.menu,
827             items;
828
829         if (menu) {
830             items = menu.getRefItems(deep);
831             items.unshift(menu);
832         }
833         return items || [];
834     },
835
836     // private
837     clearTip: function() {
838         if (Ext.isObject(this.tooltip)) {
839             Ext.tip.QuickTipManager.unregister(this.btnEl);
840         }
841     },
842
843     // private
844     beforeDestroy: function() {
845         var me = this;
846         if (me.rendered) {
847             me.clearTip();
848         }
849         if (me.menu && me.destroyMenu !== false) {
850             Ext.destroy(me.btnEl, me.btnInnerEl, me.menu);
851         }
852         Ext.destroy(me.repeater);
853     },
854
855     // private
856     onDestroy: function() {
857         var me = this;
858         if (me.rendered) {
859             me.doc.un('mouseover', me.monitorMouseOver, me);
860             me.doc.un('mouseup', me.onMouseUp, me);
861             delete me.doc;
862             delete me.btnEl;
863             delete me.btnInnerEl;
864             Ext.ButtonToggleManager.unregister(me);
865             
866             Ext.destroy(me.keyMap);
867             delete me.keyMap;
868         }
869         me.callParent();
870     },
871
872     /**
873      * Assigns this Button's click handler
874      * @param {Function} handler The function to call when the button is clicked
875      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
876      * Defaults to this Button.
877      * @return {Ext.button.Button} this
878      */
879     setHandler: function(handler, scope) {
880         this.handler = handler;
881         this.scope = scope;
882         return this;
883     },
884
885     /**
886      * Sets this Button's text
887      * @param {String} text The button text
888      * @return {Ext.button.Button} this
889      */
890     setText: function(text) {
891         var me = this;
892         me.text = text;
893         if (me.el) {
894             me.btnInnerEl.update(text || '&#160;');
895             me.setButtonCls();
896         }
897         me.doComponentLayout();
898         return me;
899     },
900
901     /**
902      * Sets the background image (inline style) of the button.  This method also changes
903      * the value of the {@link #icon} config internally.
904      * @param {String} icon The path to an image to display in the button
905      * @return {Ext.button.Button} this
906      */
907     setIcon: function(icon) {
908         var me = this,
909             btnInnerEl = me.btnInnerEl;
910         me.icon = icon;
911         if (btnInnerEl) {
912             btnInnerEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
913             me.setButtonCls();
914         }
915         return me;
916     },
917
918     /**
919      * Gets the text for this Button
920      * @return {String} The button text
921      */
922     getText: function() {
923         return this.text;
924     },
925
926     /**
927      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
928      * @param {Boolean} state (optional) Force a particular state
929      * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
930      * @return {Ext.button.Button} this
931      */
932     toggle: function(state, suppressEvent) {
933         var me = this;
934         state = state === undefined ? !me.pressed: !!state;
935         if (state !== me.pressed) {
936             if (me.rendered) {
937                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
938             }
939             me.btnEl.dom.setAttribute('aria-pressed', state);
940             me.pressed = state;
941             if (!suppressEvent) {
942                 me.fireEvent('toggle', me, state);
943                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
944             }
945         }
946         return me;
947     },
948
949     /**
950      * Show this button's menu (if it has one)
951      */
952     showMenu: function() {
953         var me = this;
954         if (me.rendered && me.menu) {
955             if (me.tooltip) {
956                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
957             }
958             if (me.menu.isVisible()) {
959                 me.menu.hide();
960             }
961
962             me.menu.showBy(me.el, me.menuAlign);
963         }
964         return me;
965     },
966
967     /**
968      * Hide this button's menu (if it has one)
969      */
970     hideMenu: function() {
971         if (this.hasVisibleMenu()) {
972             this.menu.hide();
973         }
974         return this;
975     },
976
977     /**
978      * Returns true if the button has a menu and it is visible
979      * @return {Boolean}
980      */
981     hasVisibleMenu: function() {
982         var menu = this.menu;
983         return menu && menu.rendered && menu.isVisible();
984     },
985
986     // private
987     onRepeatClick: function(repeat, e) {
988         this.onClick(e);
989     },
990
991     // private
992     onClick: function(e) {
993         var me = this;
994         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
995             e.preventDefault();
996         }
997         if (e.button !== 0) {
998             return;
999         }
1000         if (!me.disabled) {
1001             if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
1002                 me.toggle();
1003             }
1004             if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
1005                 me.showMenu();
1006             }
1007             me.fireEvent('click', me, e);
1008             if (me.handler) {
1009                 me.handler.call(me.scope || me, me, e);
1010             }
1011             me.onBlur();
1012         }
1013     },
1014
1015     /**
1016      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
1017      * The targets are interrogated to see what is being entered from where.
1018      * @param e
1019      */
1020     onMouseOver: function(e) {
1021         var me = this;
1022         if (!me.disabled && !e.within(me.el, true, true)) {
1023             me.onMouseEnter(e);
1024         }
1025     },
1026
1027     /**
1028      * @private mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
1029      * or the mouse leaves the encapsulating element.
1030      * The targets are interrogated to see what is being exited to where.
1031      * @param e
1032      */
1033     onMouseOut: function(e) {
1034         var me = this;
1035         if (!e.within(me.el, true, true)) {
1036             if (me.overMenuTrigger) {
1037                 me.onMenuTriggerOut(e);
1038             }
1039             me.onMouseLeave(e);
1040         }
1041     },
1042
1043     /**
1044      * @private mousemove handler called when the mouse moves anywhere within the encapsulating element.
1045      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
1046      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
1047      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
1048      * events when needed. In the future we should consider making the trigger a separate element that
1049      * is absolutely positioned and sized over the trigger area.
1050      */
1051     onMouseMove: function(e) {
1052         var me = this,
1053             el = me.el,
1054             over = me.overMenuTrigger,
1055             overlap, btnSize;
1056
1057         if (me.split) {
1058             if (me.arrowAlign === 'right') {
1059                 overlap = e.getX() - el.getX();
1060                 btnSize = el.getWidth();
1061             } else {
1062                 overlap = e.getY() - el.getY();
1063                 btnSize = el.getHeight();
1064             }
1065
1066             if (overlap > (btnSize - me.getTriggerSize())) {
1067                 if (!over) {
1068                     me.onMenuTriggerOver(e);
1069                 }
1070             } else {
1071                 if (over) {
1072                     me.onMenuTriggerOut(e);
1073                 }
1074             }
1075         }
1076     },
1077
1078     /**
1079      * @private Measures the size of the trigger area for menu and split buttons. Will be a width for
1080      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
1081      */
1082     getTriggerSize: function() {
1083         var me = this,
1084             size = me.triggerSize,
1085             side, sideFirstLetter, undef;
1086             
1087         if (size === undef) {
1088             side = me.arrowAlign;
1089             sideFirstLetter = side.charAt(0);
1090             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
1091         }
1092         return size;
1093     },
1094
1095     /**
1096      * @private virtual mouseenter handler called when it is detected that the mouseout event
1097      * signified the mouse entering the encapsulating element.
1098      * @param e
1099      */
1100     onMouseEnter: function(e) {
1101         var me = this;
1102         me.addClsWithUI(me.overCls);
1103         me.fireEvent('mouseover', me, e);
1104     },
1105
1106     /**
1107      * @private virtual mouseleave handler called when it is detected that the mouseover event
1108      * signified the mouse entering the encapsulating element.
1109      * @param e
1110      */
1111     onMouseLeave: function(e) {
1112         var me = this;
1113         me.removeClsWithUI(me.overCls);
1114         me.fireEvent('mouseout', me, e);
1115     },
1116
1117     /**
1118      * @private virtual mouseenter handler called when it is detected that the mouseover event
1119      * signified the mouse entering the arrow area of the button - the <em>.
1120      * @param e
1121      */
1122     onMenuTriggerOver: function(e) {
1123         var me = this;
1124         me.overMenuTrigger = true;
1125         me.fireEvent('menutriggerover', me, me.menu, e);
1126     },
1127
1128     /**
1129      * @private virtual mouseleave handler called when it is detected that the mouseout event
1130      * signified the mouse leaving the arrow area of the button - the <em>.
1131      * @param e
1132      */
1133     onMenuTriggerOut: function(e) {
1134         var me = this;
1135         delete me.overMenuTrigger;
1136         me.fireEvent('menutriggerout', me, me.menu, e);
1137     },
1138     
1139     // inherit docs
1140     enable : function(silent) {
1141         var me = this;
1142
1143         me.callParent(arguments);
1144         
1145         me.removeClsWithUI('disabled');
1146
1147         return me;
1148     },
1149
1150     // inherit docs
1151     disable : function(silent) {
1152         var me = this;
1153         
1154         me.callParent(arguments);
1155         
1156         me.addClsWithUI('disabled');
1157
1158         return me;
1159     },
1160     
1161     /**
1162      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
1163      * @param {String} scale The scale to change to.
1164      */
1165     setScale: function(scale) {
1166         var me = this,
1167             ui = me.ui.replace('-' + me.scale, '');
1168         
1169         //check if it is an allowed scale
1170         if (!Ext.Array.contains(me.allowedScales, scale)) {
1171             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
1172         }
1173         
1174         me.scale = scale;
1175         me.setUI(ui);
1176     },
1177     
1178     // inherit docs
1179     setUI: function(ui) {
1180         var me = this;
1181         
1182         //we need to append the scale to the UI, if not already done
1183         if (me.scale && !ui.match(me.scale)) {
1184             ui = ui + '-' + me.scale;
1185         }
1186         
1187         me.callParent([ui]);
1188         
1189         // Set all the state classNames, as they need to include the UI
1190         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
1191     },
1192     
1193     // private
1194     onFocus: function(e) {
1195         var me = this;
1196         if (!me.disabled) {
1197             me.addClsWithUI(me.focusCls);
1198         }
1199     },
1200
1201     // private
1202     onBlur: function(e) {
1203         var me = this;
1204         me.removeClsWithUI(me.focusCls);
1205     },
1206
1207     // private
1208     onMouseDown: function(e) {
1209         var me = this;
1210         if (!me.disabled && e.button === 0) {
1211             me.addClsWithUI(me.pressedCls);
1212             me.doc.on('mouseup', me.onMouseUp, me);
1213         }
1214     },
1215     // private
1216     onMouseUp: function(e) {
1217         var me = this;
1218         if (e.button === 0) {
1219             if (!me.pressed) {
1220                 me.removeClsWithUI(me.pressedCls);
1221             }
1222             me.doc.un('mouseup', me.onMouseUp, me);
1223         }
1224     },
1225     // private
1226     onMenuShow: function(e) {
1227         var me = this;
1228         me.ignoreNextClick = 0;
1229         me.addClsWithUI(me.menuActiveCls);
1230         me.fireEvent('menushow', me, me.menu);
1231     },
1232
1233     // private
1234     onMenuHide: function(e) {
1235         var me = this;
1236         me.removeClsWithUI(me.menuActiveCls);
1237         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
1238         me.fireEvent('menuhide', me, me.menu);
1239     },
1240
1241     // private
1242     restoreClick: function() {
1243         this.ignoreNextClick = 0;
1244     },
1245
1246     // private
1247     onDownKey: function() {
1248         var me = this;
1249
1250         if (!me.disabled) {
1251             if (me.menu) {
1252                 me.showMenu();
1253             }
1254         }
1255     },
1256
1257     /**
1258      * @private Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
1259      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
1260      * @return Array [top, right, bottom, left]
1261      */
1262     getPersistentBtnPadding: function() {
1263         var cls = Ext.button.Button,
1264             padding = cls.persistentPadding,
1265             btn, leftTop, btnEl, btnInnerEl;
1266
1267         if (!padding) {
1268             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
1269
1270             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
1271                 // Create auto-size button offscreen and measure its insides
1272                 btn = Ext.create('Ext.button.Button', {
1273                     renderTo: Ext.getBody(),
1274                     text: 'test',
1275                     style: 'position:absolute;top:-999px;'
1276                 });
1277                 btnEl = btn.btnEl;
1278                 btnInnerEl = btn.btnInnerEl;
1279                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
1280
1281                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
1282                 padding[0] = leftTop[1];
1283                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
1284                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
1285                 padding[3] = leftTop[0];
1286
1287                 btn.destroy();
1288             }
1289         }
1290
1291         return padding;
1292     }
1293
1294 }, function() {
1295     var groups = {},
1296         g, i, l;
1297
1298     function toggleGroup(btn, state) {
1299         if (state) {
1300             g = groups[btn.toggleGroup];
1301             for (i = 0, l = g.length; i < l; i++) {
1302                 if (g[i] !== btn) {
1303                     g[i].toggle(false);
1304                 }
1305             }
1306         }
1307     }
1308     // Private utility class used by Button
1309     Ext.ButtonToggleManager = {
1310         register: function(btn) {
1311             if (!btn.toggleGroup) {
1312                 return;
1313             }
1314             var group = groups[btn.toggleGroup];
1315             if (!group) {
1316                 group = groups[btn.toggleGroup] = [];
1317             }
1318             group.push(btn);
1319             btn.on('toggle', toggleGroup);
1320         },
1321
1322         unregister: function(btn) {
1323             if (!btn.toggleGroup) {
1324                 return;
1325             }
1326             var group = groups[btn.toggleGroup];
1327             if (group) {
1328                 Ext.Array.remove(group, btn);
1329                 btn.un('toggle', toggleGroup);
1330             }
1331         },
1332
1333         /**
1334         * Gets the pressed button in the passed group or null
1335         * @param {String} group
1336         * @return Button
1337         */
1338         getPressed: function(group) {
1339             var g = groups[group],
1340                 i = 0,
1341                 len;
1342             if (g) {
1343                 for (len = g.length; i < len; i++) {
1344                     if (g[i].pressed === true) {
1345                         return g[i];
1346                     }
1347                 }
1348             }
1349             return null;
1350         }
1351     };
1352 });
1353