provide installation instructions
[extjs.git] / source / widgets / Button.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 \r
10 /**\r
11  * @class Ext.Button\r
12  * @extends Ext.Component\r
13  * Simple Button class\r
14  * @cfg {String} text The button text\r
15  * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image\r
16  * CSS property of the button by default, so if you want a mixed icon/text button, set cls:"x-btn-text-icon")\r
17  * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event)\r
18  * @cfg {Object} scope The scope of the handler\r
19  * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width)\r
20  * @cfg {String/Object} tooltip The tooltip for the button - can be a string or QuickTips config object\r
21  * @cfg {Boolean} hidden True to start hidden (defaults to false)\r
22  * @cfg {Boolean} disabled True to start disabled (defaults to false)\r
23  * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)\r
24  * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)\r
25  * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be\r
26   an {@link Ext.util.ClickRepeater} config object (defaults to false).\r
27  * @constructor\r
28  * Create a new button\r
29  * @param {Object} config The config object\r
30  */\r
31 Ext.Button = Ext.extend(Ext.Component, {\r
32     /**\r
33      * Read-only. True if this button is hidden\r
34      * @type Boolean\r
35      */\r
36     hidden : false,\r
37     /**\r
38      * Read-only. True if this button is disabled\r
39      * @type Boolean\r
40      */\r
41     disabled : false,\r
42     /**\r
43      * Read-only. True if this button is pressed (only if enableToggle = true)\r
44      * @type Boolean\r
45      */\r
46     pressed : false,\r
47     /**\r
48      * The Button's owner {@link Ext.Panel} (defaults to undefined, and is set automatically when\r
49      * the Button is added to a container).  Read-only.\r
50      * @type Ext.Panel\r
51      * @property ownerCt\r
52      */\r
53 \r
54     /**\r
55      * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)\r
56      */\r
57 \r
58     /**\r
59      * @cfg {Boolean} allowDepress\r
60      * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.\r
61      */\r
62 \r
63     /**\r
64      * @cfg {Boolean} enableToggle\r
65      * True to enable pressed/not pressed toggling (defaults to false)\r
66      */\r
67     enableToggle: false,\r
68     /**\r
69      * @cfg {Function} toggleHandler\r
70      * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">\r
71      * <li><b>button</b> : Ext.Button<div class="sub-desc">this Button object</div></li>\r
72      * <li><b>state</b> : Boolean<div class="sub-desc">The next state if the Button, true means pressed.</div></li>\r
73      * </ul>\r
74      */\r
75     /**\r
76      * @cfg {Mixed} menu\r
77      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).\r
78      */\r
79     /**\r
80      * @cfg {String} menuAlign\r
81      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details, defaults to 'tl-bl?').\r
82      */\r
83     menuAlign : "tl-bl?",\r
84 \r
85     /**\r
86      * @cfg {String} iconCls\r
87      * A css class which sets a background image to be used as the icon for this button\r
88      */\r
89     /**\r
90      * @cfg {String} type\r
91      * submit, reset or button - defaults to 'button'\r
92      */\r
93     type : 'button',\r
94 \r
95     // private\r
96     menuClassTarget: 'tr',\r
97 \r
98     /**\r
99      * @cfg {String} clickEvent\r
100      * The type of event to map to the button's event handler (defaults to 'click')\r
101      */\r
102     clickEvent : 'click',\r
103 \r
104     /**\r
105      * @cfg {Boolean} handleMouseEvents\r
106      * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)\r
107      */\r
108     handleMouseEvents : true,\r
109 \r
110     /**\r
111      * @cfg {String} tooltipType\r
112      * The type of tooltip to use. Either "qtip" (default) for QuickTips or "title" for title attribute.\r
113      */\r
114     tooltipType : 'qtip',\r
115 \r
116     /**\r
117      * @cfg {String} buttonSelector\r
118      * <p>(Optional) A {@link Ext.DomQuery DomQuery} selector which is used to extract the active, clickable element from the\r
119      * DOM structure created.</p>\r
120      * <p>When a custom {@link #template} is used, you  must ensure that this selector results in the selection of\r
121      * a focussable element.</p>\r
122      * <p>Defaults to <b><tt>"button:first-child"</tt></b>.</p>\r
123      */\r
124     buttonSelector : "button:first-child",\r
125 \r
126     /**\r
127      * @cfg {Ext.Template} template\r
128      * (Optional) An {@link Ext.Template} with which to create the Button's main element. This Template must\r
129      * contain numeric substitution parameter 0 if it is to display the text property. Changing the template could\r
130      * require code modifications if required elements (e.g. a button) aren't present.\r
131      */\r
132     /**\r
133      * @cfg {String} cls\r
134      * A CSS class string to apply to the button's main element.\r
135      */\r
136 \r
137     initComponent : function(){\r
138         Ext.Button.superclass.initComponent.call(this);\r
139 \r
140         this.addEvents(\r
141             /**\r
142              * @event click\r
143              * Fires when this button is clicked\r
144              * @param {Button} this\r
145              * @param {EventObject} e The click event\r
146              */\r
147             "click",\r
148             /**\r
149              * @event toggle\r
150              * Fires when the "pressed" state of this button changes (only if enableToggle = true)\r
151              * @param {Button} this\r
152              * @param {Boolean} pressed\r
153              */\r
154             "toggle",\r
155             /**\r
156              * @event mouseover\r
157              * Fires when the mouse hovers over the button\r
158              * @param {Button} this\r
159              * @param {Event} e The event object\r
160              */\r
161             'mouseover',\r
162             /**\r
163              * @event mouseout\r
164              * Fires when the mouse exits the button\r
165              * @param {Button} this\r
166              * @param {Event} e The event object\r
167              */\r
168             'mouseout',\r
169             /**\r
170              * @event menushow\r
171              * If this button has a menu, this event fires when it is shown\r
172              * @param {Button} this\r
173              * @param {Menu} menu\r
174              */\r
175             'menushow',\r
176             /**\r
177              * @event menuhide\r
178              * If this button has a menu, this event fires when it is hidden\r
179              * @param {Button} this\r
180              * @param {Menu} menu\r
181              */\r
182             'menuhide',\r
183             /**\r
184              * @event menutriggerover\r
185              * If this button has a menu, this event fires when the mouse enters the menu triggering element\r
186              * @param {Button} this\r
187              * @param {Menu} menu\r
188              * @param {EventObject} e\r
189              */\r
190             'menutriggerover',\r
191             /**\r
192              * @event menutriggerout\r
193              * If this button has a menu, this event fires when the mouse leaves the menu triggering element\r
194              * @param {Button} this\r
195              * @param {Menu} menu\r
196              * @param {EventObject} e\r
197              */\r
198             'menutriggerout'\r
199         );\r
200         if(this.menu){\r
201             this.menu = Ext.menu.MenuMgr.get(this.menu);\r
202         }\r
203         if(typeof this.toggleGroup === 'string'){\r
204             this.enableToggle = true;\r
205         }\r
206     },\r
207 \r
208     // private\r
209     onRender : function(ct, position){\r
210         if(!this.template){\r
211             if(!Ext.Button.buttonTemplate){\r
212                 // hideous table template\r
213                 Ext.Button.buttonTemplate = new Ext.Template(\r
214                     '<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap"><tbody><tr>',\r
215                     '<td class="x-btn-left"><i>&#160;</i></td><td class="x-btn-center"><em unselectable="on"><button class="x-btn-text" type="{1}">{0}</button></em></td><td class="x-btn-right"><i>&#160;</i></td>',\r
216                     "</tr></tbody></table>");\r
217             }\r
218             this.template = Ext.Button.buttonTemplate;\r
219         }\r
220         var btn, targs = [this.text || '&#160;', this.type];\r
221 \r
222         if(position){\r
223             btn = this.template.insertBefore(position, targs, true);\r
224         }else{\r
225             btn = this.template.append(ct, targs, true);\r
226         }\r
227         var btnEl = btn.child(this.buttonSelector);\r
228         btnEl.on('focus', this.onFocus, this);\r
229         btnEl.on('blur', this.onBlur, this);\r
230 \r
231         this.initButtonEl(btn, btnEl);\r
232 \r
233         if(this.menu){\r
234             this.el.child(this.menuClassTarget).addClass("x-btn-with-menu");\r
235         }\r
236         Ext.ButtonToggleMgr.register(this);\r
237     },\r
238 \r
239     // private\r
240     initButtonEl : function(btn, btnEl){\r
241 \r
242         this.el = btn;\r
243         btn.addClass("x-btn");\r
244 \r
245         if(this.id){\r
246             this.el.dom.id = this.el.id = this.id;\r
247         }\r
248         if(this.icon){\r
249             btnEl.setStyle('background-image', 'url(' +this.icon +')');\r
250         }\r
251         if(this.iconCls){\r
252             btnEl.addClass(this.iconCls);\r
253             if(!this.cls){\r
254                 btn.addClass(this.text ? 'x-btn-text-icon' : 'x-btn-icon');\r
255             }\r
256         }\r
257         if(this.tabIndex !== undefined){\r
258             btnEl.dom.tabIndex = this.tabIndex;\r
259         }\r
260         if(this.tooltip){\r
261             if(typeof this.tooltip == 'object'){\r
262                 Ext.QuickTips.register(Ext.apply({\r
263                       target: btnEl.id\r
264                 }, this.tooltip));\r
265             } else {\r
266                 btnEl.dom[this.tooltipType] = this.tooltip;\r
267             }\r
268         }\r
269 \r
270         if(this.pressed){\r
271             this.el.addClass("x-btn-pressed");\r
272         }\r
273 \r
274         if(this.handleMouseEvents){\r
275             btn.on("mouseover", this.onMouseOver, this);\r
276             // new functionality for monitoring on the document level\r
277             //btn.on("mouseout", this.onMouseOut, this);\r
278             btn.on("mousedown", this.onMouseDown, this);\r
279         }\r
280 \r
281         if(this.menu){\r
282             this.menu.on("show", this.onMenuShow, this);\r
283             this.menu.on("hide", this.onMenuHide, this);\r
284         }\r
285 \r
286         if(this.repeat){\r
287             var repeater = new Ext.util.ClickRepeater(btn,\r
288                 typeof this.repeat == "object" ? this.repeat : {}\r
289             );\r
290             repeater.on("click", this.onClick,  this);\r
291         }\r
292 \r
293         btn.on(this.clickEvent, this.onClick, this);\r
294     },\r
295 \r
296     // private\r
297     afterRender : function(){\r
298         Ext.Button.superclass.afterRender.call(this);\r
299         if(Ext.isIE6){\r
300             this.autoWidth.defer(1, this);\r
301         }else{\r
302             this.autoWidth();\r
303         }\r
304     },\r
305 \r
306     /**\r
307      * Sets the CSS class that provides a background image to use as the button's icon.  This method also changes\r
308      * the value of the {@link iconCls} config internally.\r
309      * @param {String} cls The CSS class providing the icon image\r
310      */\r
311     setIconClass : function(cls){\r
312         if(this.el){\r
313             this.el.child(this.buttonSelector).replaceClass(this.iconCls, cls);\r
314         }\r
315         this.iconCls = cls;\r
316     },\r
317 \r
318     // private\r
319     beforeDestroy: function(){\r
320         if(this.rendered){\r
321             var btnEl = this.el.child(this.buttonSelector);\r
322             if(btnEl){\r
323                 if(this.tooltip){\r
324                     Ext.QuickTips.unregister(btnEl);\r
325                 }\r
326                 btnEl.removeAllListeners();\r
327             }\r
328             }\r
329         if(this.menu){\r
330             Ext.destroy(this.menu);\r
331         }\r
332     },\r
333 \r
334     // private\r
335     onDestroy : function(){\r
336         if(this.rendered){\r
337             Ext.ButtonToggleMgr.unregister(this);\r
338         }\r
339     },\r
340 \r
341     // private\r
342     autoWidth : function(){\r
343         if(this.el){\r
344             this.el.setWidth("auto");\r
345             if(Ext.isIE7 && Ext.isStrict){\r
346                 var ib = this.el.child(this.buttonSelector);\r
347                 if(ib && ib.getWidth() > 20){\r
348                     ib.clip();\r
349                     ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));\r
350                 }\r
351             }\r
352             if(this.minWidth){\r
353                 if(this.el.getWidth() < this.minWidth){\r
354                     this.el.setWidth(this.minWidth);\r
355                 }\r
356             }\r
357         }\r
358     },\r
359 \r
360     /**\r
361      * Assigns this button's click handler\r
362      * @param {Function} handler The function to call when the button is clicked\r
363      * @param {Object} scope (optional) Scope for the function passed in\r
364      */\r
365     setHandler : function(handler, scope){\r
366         this.handler = handler;\r
367         this.scope = scope;\r
368     },\r
369 \r
370     /**\r
371      * Sets this button's text\r
372      * @param {String} text The button text\r
373      */\r
374     setText : function(text){\r
375         this.text = text;\r
376         if(this.el){\r
377             this.el.child("td.x-btn-center " + this.buttonSelector).update(text);\r
378         }\r
379         this.autoWidth();\r
380     },\r
381 \r
382     /**\r
383      * Gets the text for this button\r
384      * @return {String} The button text\r
385      */\r
386     getText : function(){\r
387         return this.text;\r
388     },\r
389 \r
390     /**\r
391      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.\r
392      * @param {Boolean} state (optional) Force a particular state\r
393      */\r
394     toggle : function(state){\r
395         state = state === undefined ? !this.pressed : state;\r
396         if(state != this.pressed){\r
397             if(state){\r
398                 this.el.addClass("x-btn-pressed");\r
399                 this.pressed = true;\r
400                 this.fireEvent("toggle", this, true);\r
401             }else{\r
402                 this.el.removeClass("x-btn-pressed");\r
403                 this.pressed = false;\r
404                 this.fireEvent("toggle", this, false);\r
405             }\r
406             if(this.toggleHandler){\r
407                 this.toggleHandler.call(this.scope || this, this, state);\r
408             }\r
409         }\r
410     },\r
411 \r
412     /**\r
413      * Focus the button\r
414      */\r
415     focus : function(){\r
416         this.el.child(this.buttonSelector).focus();\r
417     },\r
418 \r
419     // private\r
420     onDisable : function(){\r
421         if(this.el){\r
422             if(!Ext.isIE6 || !this.text){\r
423                 this.el.addClass(this.disabledClass);\r
424             }\r
425             this.el.dom.disabled = true;\r
426         }\r
427         this.disabled = true;\r
428     },\r
429 \r
430     // private\r
431     onEnable : function(){\r
432         if(this.el){\r
433             if(!Ext.isIE6 || !this.text){\r
434                 this.el.removeClass(this.disabledClass);\r
435             }\r
436             this.el.dom.disabled = false;\r
437         }\r
438         this.disabled = false;\r
439     },\r
440 \r
441     /**\r
442      * Show this button's menu (if it has one)\r
443      */\r
444     showMenu : function(){\r
445         if(this.menu){\r
446             this.menu.show(this.el, this.menuAlign);\r
447         }\r
448         return this;\r
449     },\r
450 \r
451     /**\r
452      * Hide this button's menu (if it has one)\r
453      */\r
454     hideMenu : function(){\r
455         if(this.menu){\r
456             this.menu.hide();\r
457         }\r
458         return this;\r
459     },\r
460 \r
461     /**\r
462      * Returns true if the button has a menu and it is visible\r
463      * @return {Boolean}\r
464      */\r
465     hasVisibleMenu : function(){\r
466         return this.menu && this.menu.isVisible();\r
467     },\r
468 \r
469     // private\r
470     onClick : function(e){\r
471         if(e){\r
472             e.preventDefault();\r
473         }\r
474         if(e.button != 0){\r
475             return;\r
476         }\r
477         if(!this.disabled){\r
478             if(this.enableToggle && (this.allowDepress !== false || !this.pressed)){\r
479                 this.toggle();\r
480             }\r
481             if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){\r
482                 this.showMenu();\r
483             }\r
484             this.fireEvent("click", this, e);\r
485             if(this.handler){\r
486                 //this.el.removeClass("x-btn-over");\r
487                 this.handler.call(this.scope || this, this, e);\r
488             }\r
489         }\r
490     },\r
491 \r
492     // private\r
493     isMenuTriggerOver : function(e, internal){\r
494         return this.menu && !internal;\r
495     },\r
496 \r
497     // private\r
498     isMenuTriggerOut : function(e, internal){\r
499         return this.menu && !internal;\r
500     },\r
501 \r
502     // private\r
503     onMouseOver : function(e){\r
504         if(!this.disabled){\r
505             var internal = e.within(this.el,  true);\r
506             if(!internal){\r
507                 this.el.addClass("x-btn-over");\r
508                 if(!this.monitoringMouseOver){\r
509                     Ext.getDoc().on('mouseover', this.monitorMouseOver, this);\r
510                     this.monitoringMouseOver = true;\r
511                 }\r
512                 this.fireEvent('mouseover', this, e);\r
513             }\r
514             if(this.isMenuTriggerOver(e, internal)){\r
515                 this.fireEvent('menutriggerover', this, this.menu, e);\r
516             }\r
517         }\r
518     },\r
519 \r
520     // private\r
521     monitorMouseOver : function(e){\r
522         if(e.target != this.el.dom && !e.within(this.el)){\r
523             if(this.monitoringMouseOver){\r
524                 Ext.getDoc().un('mouseover', this.monitorMouseOver, this);\r
525                 this.monitoringMouseOver = false;\r
526             }\r
527             this.onMouseOut(e);\r
528         }\r
529     },\r
530 \r
531     // private\r
532     onMouseOut : function(e){\r
533         var internal = e.within(this.el) && e.target != this.el.dom;\r
534         this.el.removeClass("x-btn-over");\r
535         this.fireEvent('mouseout', this, e);\r
536         if(this.isMenuTriggerOut(e, internal)){\r
537             this.fireEvent('menutriggerout', this, this.menu, e);\r
538         }\r
539     },\r
540     // private\r
541     onFocus : function(e){\r
542         if(!this.disabled){\r
543             this.el.addClass("x-btn-focus");\r
544         }\r
545     },\r
546     // private\r
547     onBlur : function(e){\r
548         this.el.removeClass("x-btn-focus");\r
549     },\r
550 \r
551     // private\r
552     getClickEl : function(e, isUp){\r
553        return this.el;\r
554     },\r
555 \r
556     // private\r
557     onMouseDown : function(e){\r
558         if(!this.disabled && e.button == 0){\r
559             this.getClickEl(e).addClass("x-btn-click");\r
560             Ext.getDoc().on('mouseup', this.onMouseUp, this);\r
561         }\r
562     },\r
563     // private\r
564     onMouseUp : function(e){\r
565         if(e.button == 0){\r
566             this.getClickEl(e, true).removeClass("x-btn-click");\r
567             Ext.getDoc().un('mouseup', this.onMouseUp, this);\r
568         }\r
569     },\r
570     // private\r
571     onMenuShow : function(e){\r
572         this.ignoreNextClick = 0;\r
573         this.el.addClass("x-btn-menu-active");\r
574         this.fireEvent('menushow', this, this.menu);\r
575     },\r
576     // private\r
577     onMenuHide : function(e){\r
578         this.el.removeClass("x-btn-menu-active");\r
579         this.ignoreNextClick = this.restoreClick.defer(250, this);\r
580         this.fireEvent('menuhide', this, this.menu);\r
581     },\r
582 \r
583     // private\r
584     restoreClick : function(){\r
585         this.ignoreNextClick = 0;\r
586     }\r
587 \r
588 \r
589 \r
590     /**\r
591      * @cfg {String} autoEl @hide\r
592      */\r
593 });\r
594 Ext.reg('button', Ext.Button);\r
595 \r
596 // Private utility class used by Button\r
597 Ext.ButtonToggleMgr = function(){\r
598    var groups = {};\r
599 \r
600    function toggleGroup(btn, state){\r
601        if(state){\r
602            var g = groups[btn.toggleGroup];\r
603            for(var i = 0, l = g.length; i < l; i++){\r
604                if(g[i] != btn){\r
605                    g[i].toggle(false);\r
606                }\r
607            }\r
608        }\r
609    }\r
610 \r
611    return {\r
612        register : function(btn){\r
613            if(!btn.toggleGroup){\r
614                return;\r
615            }\r
616            var g = groups[btn.toggleGroup];\r
617            if(!g){\r
618                g = groups[btn.toggleGroup] = [];\r
619            }\r
620            g.push(btn);\r
621            btn.on("toggle", toggleGroup);\r
622        },\r
623 \r
624        unregister : function(btn){\r
625            if(!btn.toggleGroup){\r
626                return;\r
627            }\r
628            var g = groups[btn.toggleGroup];\r
629            if(g){\r
630                g.remove(btn);\r
631                btn.un("toggle", toggleGroup);\r
632            }\r
633        }\r
634    };\r
635 }();