commit extjs-2.2.1
[extjs.git] / source / widgets / form / TextField.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  * @class Ext.form.TextField\r
11  * @extends Ext.form.Field\r
12  * Basic text field.  Can be used as a direct replacement for traditional text inputs, or as the base\r
13  * class for more sophisticated input controls (like {@link Ext.form.TextArea} and {@link Ext.form.ComboBox}).\r
14  * @constructor\r
15  * Creates a new TextField\r
16  * @param {Object} config Configuration options\r
17  */\r
18 Ext.form.TextField = Ext.extend(Ext.form.Field,  {\r
19     /**\r
20      * @cfg {String} vtypeText A custom error message to display in place of the default message provided\r
21      * for the {@link #vtype} currently set for this field (defaults to '').  Only applies if vtype is set, else ignored.\r
22      */\r
23     /**\r
24      * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value before validation (defaults to null).\r
25      */\r
26     /**\r
27      * @cfg {Boolean} grow True if this field should automatically grow and shrink to its content\r
28      */\r
29     grow : false,\r
30     /**\r
31      * @cfg {Number} growMin The minimum width to allow when grow = true (defaults to 30)\r
32      */\r
33     growMin : 30,\r
34     /**\r
35      * @cfg {Number} growMax The maximum width to allow when grow = true (defaults to 800)\r
36      */\r
37     growMax : 800,\r
38     /**\r
39      * @cfg {String} vtype A validation type name as defined in {@link Ext.form.VTypes} (defaults to null)\r
40      */\r
41     vtype : null,\r
42     /**\r
43      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that don't match\r
44      * (defaults to null)\r
45      */\r
46     maskRe : null,\r
47     /**\r
48      * @cfg {Boolean} disableKeyFilter True to disable input keystroke filtering (defaults to false)\r
49      */\r
50     disableKeyFilter : false,\r
51     /**\r
52      * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to true)\r
53      */\r
54     allowBlank : true,\r
55     /**\r
56      * @cfg {Number} minLength Minimum input field length required (defaults to 0)\r
57      */\r
58     minLength : 0,\r
59     /**\r
60      * @cfg {Number} maxLength Maximum input field length allowed (defaults to Number.MAX_VALUE)\r
61      */\r
62     maxLength : Number.MAX_VALUE,\r
63     /**\r
64      * @cfg {String} minLengthText Error text to display if the minimum length validation fails (defaults to\r
65      * "The minimum length for this field is {minLength}")\r
66      */\r
67     minLengthText : "The minimum length for this field is {0}",\r
68     /**\r
69      * @cfg {String} maxLengthText Error text to display if the maximum length validation fails (defaults to\r
70      * "The maximum length for this field is {maxLength}")\r
71      */\r
72     maxLengthText : "The maximum length for this field is {0}",\r
73     /**\r
74      * @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives\r
75      * input focus (defaults to false)\r
76      */\r
77     selectOnFocus : false,\r
78     /**\r
79      * @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")\r
80      */\r
81     blankText : "This field is required",\r
82     /**\r
83      * @cfg {Function} validator A custom validation function to be called during field validation (defaults to null).\r
84      * If specified, this function will be called only after the built-in validations ({@link #allowBlank}, {@link #minLength},\r
85      * {@link #maxLength}) and any configured {@link #vtype} all return true. This function will be passed the current field\r
86      * value and expected to return boolean true if the value is valid or a string error message if invalid.\r
87      */\r
88     validator : null,\r
89     /**\r
90      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation (defaults to null).\r
91      * If available, this regex will be evaluated only after the basic validators all return true, and will be passed the\r
92      * current field value.  If the test fails, the field will be marked invalid using {@link #regexText}.\r
93      */\r
94     regex : null,\r
95     /**\r
96      * @cfg {String} regexText The error text to display if {@link #regex} is used and the test fails during\r
97      * validation (defaults to "")\r
98      */\r
99     regexText : "",\r
100     /**\r
101      * @cfg {String} emptyText The default text to place into an empty field (defaults to null). Note that this\r
102      * value will be submitted to the server if this field is enabled and configured with a {@link #name}.\r
103      */\r
104     emptyText : null,\r
105     /**\r
106      * @cfg {String} emptyClass The CSS class to apply to an empty field to style the {@link #emptyText} (defaults to\r
107      * 'x-form-empty-field').  This class is automatically added and removed as needed depending on the current field value.\r
108      */\r
109     emptyClass : 'x-form-empty-field',\r
110 \r
111     /**\r
112      * @cfg {Boolean} enableKeyEvents True to enable the proxying of key events for the HTML input field (defaults to false)\r
113      */\r
114 \r
115     initComponent : function(){\r
116         Ext.form.TextField.superclass.initComponent.call(this);\r
117         this.addEvents(\r
118             /**\r
119              * @event autosize\r
120              * Fires when the autosize function is triggered.  The field may or may not have actually changed size\r
121              * according to the default logic, but this event provides a hook for the developer to apply additional\r
122              * logic at runtime to resize the field if needed.\r
123              * @param {Ext.form.Field} this This text field\r
124              * @param {Number} width The new field width\r
125              */\r
126             'autosize',\r
127 \r
128             /**\r
129              * @event keydown\r
130              * Keydown input field event. This event only fires if enableKeyEvents is set to true.\r
131              * @param {Ext.form.TextField} this This text field\r
132              * @param {Ext.EventObject} e\r
133              */\r
134             'keydown',\r
135             /**\r
136              * @event keyup\r
137              * Keyup input field event. This event only fires if enableKeyEvents is set to true.\r
138              * @param {Ext.form.TextField} this This text field\r
139              * @param {Ext.EventObject} e\r
140              */\r
141             'keyup',\r
142             /**\r
143              * @event keypress\r
144              * Keypress input field event. This event only fires if enableKeyEvents is set to true.\r
145              * @param {Ext.form.TextField} this This text field\r
146              * @param {Ext.EventObject} e\r
147              */\r
148             'keypress'\r
149         );\r
150     },\r
151 \r
152     // private\r
153     initEvents : function(){\r
154         Ext.form.TextField.superclass.initEvents.call(this);\r
155         if(this.validationEvent == 'keyup'){\r
156             this.validationTask = new Ext.util.DelayedTask(this.validate, this);\r
157             this.el.on('keyup', this.filterValidation, this);\r
158         }\r
159         else if(this.validationEvent !== false){\r
160             this.el.on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});\r
161         }\r
162         if(this.selectOnFocus || this.emptyText){\r
163             this.on("focus", this.preFocus, this);\r
164             this.el.on('mousedown', function(){\r
165                 if(!this.hasFocus){\r
166                     this.el.on('mouseup', function(e){\r
167                         e.preventDefault();\r
168                     }, this, {single:true});\r
169                 }\r
170             }, this);\r
171             if(this.emptyText){\r
172                 this.on('blur', this.postBlur, this);\r
173                 this.applyEmptyText();\r
174             }\r
175         }\r
176         if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){\r
177             this.el.on("keypress", this.filterKeys, this);\r
178         }\r
179         if(this.grow){\r
180             this.el.on("keyup", this.onKeyUpBuffered,  this, {buffer:50});\r
181             this.el.on("click", this.autoSize,  this);\r
182         }\r
183 \r
184         if(this.enableKeyEvents){\r
185             this.el.on("keyup", this.onKeyUp, this);\r
186             this.el.on("keydown", this.onKeyDown, this);\r
187             this.el.on("keypress", this.onKeyPress, this);\r
188         }\r
189     },\r
190 \r
191     processValue : function(value){\r
192         if(this.stripCharsRe){\r
193             var newValue = value.replace(this.stripCharsRe, '');\r
194             if(newValue !== value){\r
195                 this.setRawValue(newValue);\r
196                 return newValue;\r
197             }\r
198         }\r
199         return value;\r
200     },\r
201 \r
202     filterValidation : function(e){\r
203         if(!e.isNavKeyPress()){\r
204             this.validationTask.delay(this.validationDelay);\r
205         }\r
206     },\r
207     \r
208     //private\r
209     onDisable: function(){\r
210         Ext.form.TextField.superclass.onDisable.call(this);\r
211         if(Ext.isIE){\r
212             this.el.dom.unselectable = 'on';\r
213         }\r
214     },\r
215     \r
216     //private\r
217     onEnable: function(){\r
218         Ext.form.TextField.superclass.onEnable.call(this);\r
219         if(Ext.isIE){\r
220             this.el.dom.unselectable = '';\r
221         }\r
222     },\r
223 \r
224     // private\r
225     onKeyUpBuffered : function(e){\r
226         if(!e.isNavKeyPress()){\r
227             this.autoSize();\r
228         }\r
229     },\r
230 \r
231     // private\r
232     onKeyUp : function(e){\r
233         this.fireEvent('keyup', this, e);\r
234     },\r
235 \r
236     // private\r
237     onKeyDown : function(e){\r
238         this.fireEvent('keydown', this, e);\r
239     },\r
240 \r
241     // private\r
242     onKeyPress : function(e){\r
243         this.fireEvent('keypress', this, e);\r
244     },\r
245 \r
246     /**\r
247      * Resets the current field value to the originally-loaded value and clears any validation messages.\r
248      * Also adds emptyText and emptyClass if the original value was blank.\r
249      */\r
250     reset : function(){\r
251         Ext.form.TextField.superclass.reset.call(this);\r
252         this.applyEmptyText();\r
253     },\r
254 \r
255     applyEmptyText : function(){\r
256         if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){\r
257             this.setRawValue(this.emptyText);\r
258             this.el.addClass(this.emptyClass);\r
259         }\r
260     },\r
261 \r
262     // private\r
263     preFocus : function(){\r
264         if(this.emptyText){\r
265             if(this.el.dom.value == this.emptyText){\r
266                 this.setRawValue('');\r
267             }\r
268             this.el.removeClass(this.emptyClass);\r
269         }\r
270         if(this.selectOnFocus){\r
271             this.el.dom.select();\r
272         }\r
273     },\r
274 \r
275     // private\r
276     postBlur : function(){\r
277         this.applyEmptyText();\r
278     },\r
279 \r
280     // private\r
281     filterKeys : function(e){\r
282         if(e.ctrlKey){\r
283             return;\r
284         }\r
285         var k = e.getKey();\r
286         if(Ext.isGecko && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){\r
287             return;\r
288         }\r
289         var c = e.getCharCode(), cc = String.fromCharCode(c);\r
290         if(!Ext.isGecko && e.isSpecialKey() && !cc){\r
291             return;\r
292         }\r
293         if(!this.maskRe.test(cc)){\r
294             e.stopEvent();\r
295         }\r
296     },\r
297 \r
298     setValue : function(v){\r
299         if(this.emptyText && this.el && v !== undefined && v !== null && v !== ''){\r
300             this.el.removeClass(this.emptyClass);\r
301         }\r
302         Ext.form.TextField.superclass.setValue.apply(this, arguments);\r
303         this.applyEmptyText();\r
304         this.autoSize();\r
305     },\r
306 \r
307     /**\r
308      * Validates a value according to the field's validation rules and marks the field as invalid\r
309      * if the validation fails\r
310      * @param {Mixed} value The value to validate\r
311      * @return {Boolean} True if the value is valid, else false\r
312      */\r
313     validateValue : function(value){\r
314         if(value.length < 1 || value === this.emptyText){ // if it's blank\r
315              if(this.allowBlank){\r
316                  this.clearInvalid();\r
317                  return true;\r
318              }else{\r
319                  this.markInvalid(this.blankText);\r
320                  return false;\r
321              }\r
322         }\r
323         if(value.length < this.minLength){\r
324             this.markInvalid(String.format(this.minLengthText, this.minLength));\r
325             return false;\r
326         }\r
327         if(value.length > this.maxLength){\r
328             this.markInvalid(String.format(this.maxLengthText, this.maxLength));\r
329             return false;\r
330         }\r
331         if(this.vtype){\r
332             var vt = Ext.form.VTypes;\r
333             if(!vt[this.vtype](value, this)){\r
334                 this.markInvalid(this.vtypeText || vt[this.vtype +'Text']);\r
335                 return false;\r
336             }\r
337         }\r
338         if(typeof this.validator == "function"){\r
339             var msg = this.validator(value);\r
340             if(msg !== true){\r
341                 this.markInvalid(msg);\r
342                 return false;\r
343             }\r
344         }\r
345         if(this.regex && !this.regex.test(value)){\r
346             this.markInvalid(this.regexText);\r
347             return false;\r
348         }\r
349         return true;\r
350     },\r
351 \r
352     /**\r
353      * Selects text in this field\r
354      * @param {Number} start (optional) The index where the selection should start (defaults to 0)\r
355      * @param {Number} end (optional) The index where the selection should end (defaults to the text length)\r
356      */\r
357     selectText : function(start, end){\r
358         var v = this.getRawValue();\r
359         var doFocus = false;\r
360         if(v.length > 0){\r
361             start = start === undefined ? 0 : start;\r
362             end = end === undefined ? v.length : end;\r
363             var d = this.el.dom;\r
364             if(d.setSelectionRange){\r
365                 d.setSelectionRange(start, end);\r
366             }else if(d.createTextRange){\r
367                 var range = d.createTextRange();\r
368                 range.moveStart("character", start);\r
369                 range.moveEnd("character", end-v.length);\r
370                 range.select();\r
371             }\r
372             doFocus = Ext.isGecko || Ext.isOpera;\r
373         }else{\r
374             doFocus = true;\r
375         }\r
376         if(doFocus){\r
377             this.focus();\r
378         }\r
379     },\r
380 \r
381     /**\r
382      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.\r
383      * This only takes effect if grow = true, and fires the {@link #autosize} event.\r
384      */\r
385     autoSize : function(){\r
386         if(!this.grow || !this.rendered){\r
387             return;\r
388         }\r
389         if(!this.metrics){\r
390             this.metrics = Ext.util.TextMetrics.createInstance(this.el);\r
391         }\r
392         var el = this.el;\r
393         var v = el.dom.value;\r
394         var d = document.createElement('div');\r
395         d.appendChild(document.createTextNode(v));\r
396         v = d.innerHTML;\r
397         Ext.removeNode(d);\r
398         d = null;\r
399         v += "&#160;";\r
400         var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));\r
401         this.el.setWidth(w);\r
402         this.fireEvent("autosize", this, w);\r
403     }\r
404 });\r
405 Ext.reg('textfield', Ext.form.TextField);\r