Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / widgets / CycleButton.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.CycleButton
9  * @extends Ext.SplitButton
10  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements.  The button automatically
11  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
12  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
13  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
14  * <pre><code>
15 var btn = new Ext.CycleButton({
16     showText: true,
17     prependText: 'View as ',
18     items: [{
19         text:'text only',
20         iconCls:'view-text',
21         checked:true
22     },{
23         text:'HTML',
24         iconCls:'view-html'
25     }],
26     changeHandler:function(btn, item){
27         Ext.Msg.alert('Change View', item.text);
28     }
29 });
30 </code></pre>
31  * @constructor
32  * Create a new split button
33  * @param {Object} config The config object
34  * @xtype cycle
35  */
36 Ext.CycleButton = Ext.extend(Ext.SplitButton, {
37     /**
38      * @cfg {Array} items An array of {@link Ext.menu.CheckItem} <b>config</b> objects to be used when creating the
39      * button's menu items (e.g., {text:'Foo', iconCls:'foo-icon'})
40      */
41     /**
42      * @cfg {Boolean} showText True to display the active item's text as the button text (defaults to false)
43      */
44     /**
45      * @cfg {String} prependText A static string to prepend before the active item's text when displayed as the
46      * button's text (only applies when showText = true, defaults to '')
47      */
48     /**
49      * @cfg {Function} changeHandler A callback function that will be invoked each time the active menu
50      * item in the button's menu has changed.  If this callback is not supplied, the SplitButton will instead
51      * fire the {@link #change} event on active item change.  The changeHandler function will be called with the
52      * following argument list: (SplitButton this, Ext.menu.CheckItem item)
53      */
54     /**
55      * @cfg {String} forceIcon A css class which sets an image to be used as the static icon for this button.  This
56      * icon will always be displayed regardless of which item is selected in the dropdown list.  This overrides the 
57      * default behavior of changing the button's icon to match the selected item's icon on change.
58      */
59     /**
60      * @property menu
61      * @type Menu
62      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the available choices.
63      */
64
65     // private
66     getItemText : function(item){
67         if(item && this.showText === true){
68             var text = '';
69             if(this.prependText){
70                 text += this.prependText;
71             }
72             text += item.text;
73             return text;
74         }
75         return undefined;
76     },
77
78     /**
79      * Sets the button's active menu item.
80      * @param {Ext.menu.CheckItem} item The item to activate
81      * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false)
82      */
83     setActiveItem : function(item, suppressEvent){
84         if(!Ext.isObject(item)){
85             item = this.menu.getComponent(item);
86         }
87         if(item){
88             if(!this.rendered){
89                 this.text = this.getItemText(item);
90                 this.iconCls = item.iconCls;
91             }else{
92                 var t = this.getItemText(item);
93                 if(t){
94                     this.setText(t);
95                 }
96                 this.setIconClass(item.iconCls);
97             }
98             this.activeItem = item;
99             if(!item.checked){
100                 item.setChecked(true, false);
101             }
102             if(this.forceIcon){
103                 this.setIconClass(this.forceIcon);
104             }
105             if(!suppressEvent){
106                 this.fireEvent('change', this, item);
107             }
108         }
109     },
110
111     /**
112      * Gets the currently active menu item.
113      * @return {Ext.menu.CheckItem} The active item
114      */
115     getActiveItem : function(){
116         return this.activeItem;
117     },
118
119     // private
120     initComponent : function(){
121         this.addEvents(
122             /**
123              * @event change
124              * Fires after the button's active menu item has changed.  Note that if a {@link #changeHandler} function
125              * is set on this CycleButton, it will be called instead on active item change and this change event will
126              * not be fired.
127              * @param {Ext.CycleButton} this
128              * @param {Ext.menu.CheckItem} item The menu item that was selected
129              */
130             "change"
131         );
132
133         if(this.changeHandler){
134             this.on('change', this.changeHandler, this.scope||this);
135             delete this.changeHandler;
136         }
137
138         this.itemCount = this.items.length;
139
140         this.menu = {cls:'x-cycle-menu', items:[]};
141         var checked = 0;
142         Ext.each(this.items, function(item, i){
143             Ext.apply(item, {
144                 group: item.group || this.id,
145                 itemIndex: i,
146                 checkHandler: this.checkHandler,
147                 scope: this,
148                 checked: item.checked || false
149             });
150             this.menu.items.push(item);
151             if(item.checked){
152                 checked = i;
153             }
154         }, this);
155         Ext.CycleButton.superclass.initComponent.call(this);
156         this.on('click', this.toggleSelected, this);
157         this.setActiveItem(checked, true);
158     },
159
160     // private
161     checkHandler : function(item, pressed){
162         if(pressed){
163             this.setActiveItem(item);
164         }
165     },
166
167     /**
168      * This is normally called internally on button click, but can be called externally to advance the button's
169      * active item programmatically to the next one in the menu.  If the current item is the last one in the menu
170      * the active item will be set to the first item in the menu.
171      */
172     toggleSelected : function(){
173         var m = this.menu;
174         m.render();
175         // layout if we haven't before so the items are active
176         if(!m.hasLayout){
177             m.doLayout();
178         }
179         
180         var nextIdx, checkItem;
181         for (var i = 1; i < this.itemCount; i++) {
182             nextIdx = (this.activeItem.itemIndex + i) % this.itemCount;
183             // check the potential item
184             checkItem = m.items.itemAt(nextIdx);
185             // if its not disabled then check it.
186             if (!checkItem.disabled) {
187                 checkItem.setChecked(true);
188                 break;
189             }
190         }
191     }
192 });
193 Ext.reg('cycle', Ext.CycleButton);