Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / widgets / form / TriggerField.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.form.TriggerField
9  * @extends Ext.form.TextField
10  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
11  * The trigger has no default action, so you must assign a function to implement the trigger click handler by
12  * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox
13  * for which you can provide a custom implementation.  For example:
14  * <pre><code>
15 var trigger = new Ext.form.TriggerField();
16 trigger.onTriggerClick = myTriggerFn;
17 trigger.applyToMarkup('my-field');
18 </code></pre>
19  *
20  * However, in general you will most likely want to use TriggerField as the base class for a reusable component.
21  * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this.
22  * 
23  * @constructor
24  * Create a new TriggerField.
25  * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied
26  * to the base TextField)
27  * @xtype trigger
28  */
29 Ext.form.TriggerField = Ext.extend(Ext.form.TextField,  {
30     /**
31      * @cfg {String} triggerClass
32      * An additional CSS class used to style the trigger button.  The trigger will always get the
33      * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
34      */
35     /**
36      * @cfg {Mixed} triggerConfig
37      * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the
38      * trigger element for this Field. (Optional).</p>
39      * <p>Specify this when you need a customized element to act as the trigger button for a TriggerField.</p>
40      * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning
41      * and appearance of the trigger.  Defaults to:</p>
42      * <pre><code>{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}</code></pre>
43      */
44     /**
45      * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
46      * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
47      * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details.  Defaults to:</p>
48      * <pre><code>{tag: "input", type: "text", size: "16", autocomplete: "off"}</code></pre>
49      */
50     defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"},
51     /**
52      * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
53      * text field (defaults to <tt>false</tt>)
54      */
55     hideTrigger:false,
56     /**
57      * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field,
58      * the field will only respond to a click on the trigger to set the value. (defaults to <tt>true</tt>)
59      */
60     editable: true,
61     /**
62      * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to
63      * <tt>x-trigger-wrap-focus</tt>.
64      */
65     wrapFocusClass: 'x-trigger-wrap-focus',
66     /**
67      * @hide 
68      * @method autoSize
69      */
70     autoSize: Ext.emptyFn,
71     // private
72     monitorTab : true,
73     // private
74     deferHeight : true,
75     // private
76     mimicing : false,
77     
78     actionMode: 'wrap',
79
80     // private
81     onResize : function(w, h){
82         Ext.form.TriggerField.superclass.onResize.call(this, w, h);
83         if(typeof w == 'number'){
84             this.el.setWidth(this.adjustWidth('input', w - this.trigger.getWidth()));
85         }
86         this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
87     },
88
89     // private
90     adjustSize : Ext.BoxComponent.prototype.adjustSize,
91
92     // private
93     getResizeEl : function(){
94         return this.wrap;
95     },
96
97     // private
98     getPositionEl : function(){
99         return this.wrap;
100     },
101
102     // private
103     alignErrorIcon : function(){
104         if(this.wrap){
105             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
106         }
107     },
108
109     // private
110     onRender : function(ct, position){
111         Ext.form.TriggerField.superclass.onRender.call(this, ct, position);
112
113         this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
114         this.trigger = this.wrap.createChild(this.triggerConfig ||
115                 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
116         if(this.hideTrigger){
117             this.trigger.setDisplayed(false);
118         }
119         this.initTrigger();
120         if(!this.width){
121             this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
122         }
123         if(!this.editable){
124             this.editable = true;
125             this.setEditable(false);
126         }
127     },
128
129     afterRender : function(){
130         Ext.form.TriggerField.superclass.afterRender.call(this);
131     },
132
133     // private
134     initTrigger : function(){
135         this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true});
136         this.trigger.addClassOnOver('x-form-trigger-over');
137         this.trigger.addClassOnClick('x-form-trigger-click');
138     },
139
140     // private
141     onDestroy : function(){
142                 Ext.destroy(this.trigger, this.wrap);
143         if (this.mimicing){
144             Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
145         }
146         Ext.form.TriggerField.superclass.onDestroy.call(this);
147     },
148
149     // private
150     onFocus : function(){
151         Ext.form.TriggerField.superclass.onFocus.call(this);
152         if(!this.mimicing){
153             this.wrap.addClass(this.wrapFocusClass);
154             this.mimicing = true;
155             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {delay: 10});
156             if(this.monitorTab){
157                 this.el.on('keydown', this.checkTab, this);
158             }
159         }
160     },
161
162     // private
163     checkTab : function(e){
164         if(e.getKey() == e.TAB){
165             this.triggerBlur();
166         }
167     },
168
169     // private
170     onBlur : function(){
171         // do nothing
172     },
173
174     // private
175     mimicBlur : function(e){
176         if(!this.wrap.contains(e.target) && this.validateBlur(e)){
177             this.triggerBlur();
178         }
179     },
180
181     // private
182     triggerBlur : function(){
183         this.mimicing = false;
184         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
185         if(this.monitorTab && this.el){
186             this.el.un("keydown", this.checkTab, this);
187         }
188         Ext.form.TriggerField.superclass.onBlur.call(this);
189         if(this.wrap){
190             this.wrap.removeClass(this.wrapFocusClass);
191         }
192     },
193
194     beforeBlur : Ext.emptyFn, 
195     
196     /**
197      * Allow or prevent the user from directly editing the field text.  If false is passed,
198      * the user will only be able to modify the field using the trigger.  This method
199      * is the runtime equivalent of setting the 'editable' config option at config time.
200      * @param {Boolean} value True to allow the user to directly edit the field text
201      */
202     setEditable : function(value){
203         if(value == this.editable){
204             return;
205         }
206         this.editable = value;
207         if(!value){
208             this.el.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true);
209         }else{
210             this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick,  this).dom.removeAttribute('readOnly');
211         }
212     },
213
214     // private
215     // This should be overriden by any subclass that needs to check whether or not the field can be blurred.
216     validateBlur : function(e){
217         return true;
218     },
219
220     /**
221      * The function that should handle the trigger's click event.  This method does nothing by default
222      * until overridden by an implementing function.  See Ext.form.ComboBox and Ext.form.DateField for
223      * sample implementations.
224      * @method
225      * @param {EventObject} e
226      */
227     onTriggerClick : Ext.emptyFn
228
229     /**
230      * @cfg {Boolean} grow @hide
231      */
232     /**
233      * @cfg {Number} growMin @hide
234      */
235     /**
236      * @cfg {Number} growMax @hide
237      */
238 });
239
240 /**
241  * @class Ext.form.TwinTriggerField
242  * @extends Ext.form.TriggerField
243  * TwinTriggerField is not a public class to be used directly.  It is meant as an abstract base class
244  * to be extended by an implementing class.  For an example of implementing this class, see the custom
245  * SearchField implementation here:
246  * <a href="http://extjs.com/deploy/ext/examples/form/custom.html">http://extjs.com/deploy/ext/examples/form/custom.html</a>
247  */
248 Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, {
249     /**
250      * @cfg {Mixed} triggerConfig
251      * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements
252      * for this Field. (Optional).</p>
253      * <p>Specify this when you need a customized element to contain the two trigger elements for this Field.
254      * Each trigger element must be marked by the CSS class <tt>x-form-trigger</tt> (also see
255      * <tt>{@link #trigger1Class}</tt> and <tt>{@link #trigger2Class}</tt>).</p>
256      * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing,
257      * positioning and appearance of the triggers.</p>
258      */
259     /**
260      * @cfg {String} trigger1Class
261      * An additional CSS class used to style the trigger button.  The trigger will always get the
262      * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
263      */
264     /**
265      * @cfg {String} trigger2Class
266      * An additional CSS class used to style the trigger button.  The trigger will always get the
267      * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
268      */
269
270     initComponent : function(){
271         Ext.form.TwinTriggerField.superclass.initComponent.call(this);
272
273         this.triggerConfig = {
274             tag:'span', cls:'x-form-twin-triggers', cn:[
275             {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
276             {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
277         ]};
278     },
279
280     getTrigger : function(index){
281         return this.triggers[index];
282     },
283
284     initTrigger : function(){
285         var ts = this.trigger.select('.x-form-trigger', true);
286         this.wrap.setStyle('overflow', 'hidden');
287         var triggerField = this;
288         ts.each(function(t, all, index){
289             t.hide = function(){
290                 var w = triggerField.wrap.getWidth();
291                 this.dom.style.display = 'none';
292                 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
293             };
294             t.show = function(){
295                 var w = triggerField.wrap.getWidth();
296                 this.dom.style.display = '';
297                 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
298             };
299             var triggerIndex = 'Trigger'+(index+1);
300
301             if(this['hide'+triggerIndex]){
302                 t.dom.style.display = 'none';
303             }
304             this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
305             t.addClassOnOver('x-form-trigger-over');
306             t.addClassOnClick('x-form-trigger-click');
307         }, this);
308         this.triggers = ts.elements;
309     },
310
311     /**
312      * The function that should handle the trigger's click event.  This method does nothing by default
313      * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
314      * for additional information.  
315      * @method
316      * @param {EventObject} e
317      */
318     onTrigger1Click : Ext.emptyFn,
319     /**
320      * The function that should handle the trigger's click event.  This method does nothing by default
321      * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
322      * for additional information.  
323      * @method
324      * @param {EventObject} e
325      */
326     onTrigger2Click : Ext.emptyFn
327 });
328 Ext.reg('trigger', Ext.form.TriggerField);