Roo/bootstrap/Input.js
[roojs1] / Roo / bootstrap / Input.js
1 /*
2  * - LGPL
3  *
4  * Input
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.Input
10  * @extends Roo.bootstrap.Component
11  * Bootstrap Input class
12  * @cfg {Boolean} disabled is it disabled
13  * @cfg {String} inputType button | checkbox | email | file | hidden | image | number | password | radio | range | reset | search | submit | text
14  * @cfg {String} name name of the input
15  * @cfg {string} fieldLabel - the label associated
16  * @cfg {string} placeholder - placeholder to put in text.
17  * @cfg {string}  before - input group add on before
18  * @cfg {string} after - input group add on after
19  * @cfg {string} size - (lg|sm) or leave empty..
20  * @cfg {Number} xs colspan out of 12 for mobile-sized screens
21  * @cfg {Number} sm colspan out of 12 for tablet-sized screens
22  * @cfg {Number} md colspan out of 12 for computer-sized screens
23  * @cfg {Number} lg colspan out of 12 for large computer-sized screens
24  * @cfg {string} value default value of the input
25  * @cfg {Number} labelWidth set the width of label 
26  * @cfg {Number} labellg set the width of label (1-12)
27  * @cfg {Number} labelmd set the width of label (1-12)
28  * @cfg {Number} labelsm set the width of label (1-12)
29  * @cfg {Number} labelxs set the width of label (1-12)
30  * @cfg {String} labelAlign (top|left)
31  * @cfg {Boolean} readOnly Specifies that the field should be read-only
32  * @cfg {String} autocomplete - default is new-password see: https://developers.google.com/web/fundamentals/input/form/label-and-name-inputs?hl=en
33  * @cfg {String} indicatorpos (left|right) default left
34
35  * @cfg {String} align (left|center|right) Default left
36  * @cfg {Boolean} forceFeedback (true|false) Default false
37  * 
38  * 
39  * 
40  * 
41  * @constructor
42  * Create a new Input
43  * @param {Object} config The config object
44  */
45
46 Roo.bootstrap.Input = function(config){
47     
48     Roo.bootstrap.Input.superclass.constructor.call(this, config);
49     
50     this.addEvents({
51         /**
52          * @event focus
53          * Fires when this field receives input focus.
54          * @param {Roo.form.Field} this
55          */
56         focus : true,
57         /**
58          * @event blur
59          * Fires when this field loses input focus.
60          * @param {Roo.form.Field} this
61          */
62         blur : true,
63         /**
64          * @event specialkey
65          * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
66          * {@link Roo.EventObject#getKey} to determine which key was pressed.
67          * @param {Roo.form.Field} this
68          * @param {Roo.EventObject} e The event object
69          */
70         specialkey : true,
71         /**
72          * @event change
73          * Fires just before the field blurs if the field value has changed.
74          * @param {Roo.form.Field} this
75          * @param {Mixed} newValue The new value
76          * @param {Mixed} oldValue The original value
77          */
78         change : true,
79         /**
80          * @event invalid
81          * Fires after the field has been marked as invalid.
82          * @param {Roo.form.Field} this
83          * @param {String} msg The validation message
84          */
85         invalid : true,
86         /**
87          * @event valid
88          * Fires after the field has been validated with no errors.
89          * @param {Roo.form.Field} this
90          */
91         valid : true,
92          /**
93          * @event keyup
94          * Fires after the key up
95          * @param {Roo.form.Field} this
96          * @param {Roo.EventObject}  e The event Object
97          */
98         keyup : true
99     });
100 };
101
102 Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
103      /**
104      * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
105       automatic validation (defaults to "keyup").
106      */
107     validationEvent : "keyup",
108      /**
109      * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
110      */
111     validateOnBlur : true,
112     /**
113      * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation is initiated (defaults to 250)
114      */
115     validationDelay : 250,
116      /**
117      * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus")
118      */
119     focusClass : "x-form-focus",  // not needed???
120     
121        
122     /**
123      * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid")
124      */
125     invalidClass : "has-warning",
126     
127     /**
128      * @cfg {String} validClass The CSS class to use when marking a field valid (defaults to "x-form-invalid")
129      */
130     validClass : "has-success",
131     
132     /**
133      * @cfg {Boolean} hasFeedback (true|false) default true
134      */
135     hasFeedback : true,
136     
137     /**
138      * @cfg {String} invalidFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
139      */
140     invalidFeedbackClass : "glyphicon-warning-sign",
141     
142     /**
143      * @cfg {String} validFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
144      */
145     validFeedbackClass : "glyphicon-ok",
146     
147     /**
148      * @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives input focus (defaults to false)
149      */
150     selectOnFocus : false,
151     
152      /**
153      * @cfg {String} maskRe An input mask regular expression that will be used to filter keystrokes that don't match (defaults to null)
154      */
155     maskRe : null,
156        /**
157      * @cfg {String} vtype A validation type name as defined in {@link Roo.form.VTypes} (defaults to null)
158      */
159     vtype : null,
160     
161       /**
162      * @cfg {Boolean} disableKeyFilter True to disable input keystroke filtering (defaults to false)
163      */
164     disableKeyFilter : false,
165     
166        /**
167      * @cfg {Boolean} disabled True to disable the field (defaults to false).
168      */
169     disabled : false,
170      /**
171      * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to true)
172      */
173     allowBlank : true,
174     /**
175      * @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")
176      */
177     blankText : "Please complete this mandatory field",
178     
179      /**
180      * @cfg {Number} minLength Minimum input field length required (defaults to 0)
181      */
182     minLength : 0,
183     /**
184      * @cfg {Number} maxLength Maximum input field length allowed (defaults to Number.MAX_VALUE)
185      */
186     maxLength : Number.MAX_VALUE,
187     /**
188      * @cfg {String} minLengthText Error text to display if the minimum length validation fails (defaults to "The minimum length for this field is {minLength}")
189      */
190     minLengthText : "The minimum length for this field is {0}",
191     /**
192      * @cfg {String} maxLengthText Error text to display if the maximum length validation fails (defaults to "The maximum length for this field is {maxLength}")
193      */
194     maxLengthText : "The maximum length for this field is {0}",
195   
196     
197     /**
198      * @cfg {Function} validator A custom validation function to be called during field validation (defaults to null).
199      * If available, this function will be called only after the basic validators all return true, and will be passed the
200      * current field value and expected to return boolean true if the value is valid or a string error message if invalid.
201      */
202     validator : null,
203     /**
204      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation (defaults to null).
205      * If available, this regex will be evaluated only after the basic validators all return true, and will be passed the
206      * current field value.  If the test fails, the field will be marked invalid using {@link #regexText}.
207      */
208     regex : null,
209     /**
210      * @cfg {String} regexText The error text to display if {@link #regex} is used and the test fails during validation (defaults to "")
211      */
212     regexText : "",
213     
214     autocomplete: false,
215     
216     
217     fieldLabel : '',
218     inputType : 'text',
219     
220     name : false,
221     placeholder: false,
222     before : false,
223     after : false,
224     size : false,
225     hasFocus : false,
226     preventMark: false,
227     isFormField : true,
228     value : '',
229     labelWidth : 2,
230     labelAlign : false,
231     readOnly : false,
232     align : false,
233     formatedValue : false,
234     forceFeedback : false,
235     
236     indicatorpos : 'left',
237     
238     labellg : 0,
239     labelmd : 0,
240     labelsm : 0,
241     labelxs : 0,
242     
243     parentLabelAlign : function()
244     {
245         var parent = this;
246         while (parent.parent()) {
247             parent = parent.parent();
248             if (typeof(parent.labelAlign) !='undefined') {
249                 return parent.labelAlign;
250             }
251         }
252         return 'left';
253         
254     },
255     
256     getAutoCreate : function()
257     {
258         var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
259         
260         var id = Roo.id();
261         
262         var cfg = {};
263         
264         if(this.inputType != 'hidden'){
265             cfg.cls = 'form-group' //input-group
266         }
267         
268         var input =  {
269             tag: 'input',
270             id : id,
271             type : this.inputType,
272             value : this.value,
273             cls : 'form-control',
274             placeholder : this.placeholder || '',
275             autocomplete : this.autocomplete || 'new-password'
276         };
277         
278         if(this.align){
279             input.style = (typeof(input.style) == 'undefined') ? ('text-align:' + this.align) : (input.style + 'text-align:' + this.align);
280         }
281         
282         if(this.maxLength && this.maxLength != Number.MAX_VALUE){
283             input.maxLength = this.maxLength;
284         }
285         
286         if (this.disabled) {
287             input.disabled=true;
288         }
289         
290         if (this.readOnly) {
291             input.readonly=true;
292         }
293         
294         if (this.name) {
295             input.name = this.name;
296         }
297         
298         if (this.size) {
299             input.cls += ' input-' + this.size;
300         }
301         
302         var settings=this;
303         ['xs','sm','md','lg'].map(function(size){
304             if (settings[size]) {
305                 cfg.cls += ' col-' + size + '-' + settings[size];
306             }
307         });
308         
309         var inputblock = input;
310         
311         var feedback = {
312             tag: 'span',
313             cls: 'glyphicon form-control-feedback'
314         };
315             
316         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
317             
318             inputblock = {
319                 cls : 'has-feedback',
320                 cn :  [
321                     input,
322                     feedback
323                 ] 
324             };  
325         }
326         
327         if (this.before || this.after) {
328             
329             inputblock = {
330                 cls : 'input-group',
331                 cn :  [] 
332             };
333             
334             if (this.before && typeof(this.before) == 'string') {
335                 
336                 inputblock.cn.push({
337                     tag :'span',
338                     cls : 'roo-input-before input-group-addon',
339                     html : this.before
340                 });
341             }
342             if (this.before && typeof(this.before) == 'object') {
343                 this.before = Roo.factory(this.before);
344                 
345                 inputblock.cn.push({
346                     tag :'span',
347                     cls : 'roo-input-before input-group-' +
348                         (this.before.xtype == 'Button' ? 'btn' : 'addon')  //?? what about checkboxes - that looks like a bit of a hack thought? 
349                 });
350             }
351             
352             inputblock.cn.push(input);
353             
354             if (this.after && typeof(this.after) == 'string') {
355                 inputblock.cn.push({
356                     tag :'span',
357                     cls : 'roo-input-after input-group-addon',
358                     html : this.after
359                 });
360             }
361             if (this.after && typeof(this.after) == 'object') {
362                 this.after = Roo.factory(this.after);
363                 
364                 inputblock.cn.push({
365                     tag :'span',
366                     cls : 'roo-input-after input-group-' +
367                         (this.after.xtype == 'Button' ? 'btn' : 'addon')  //?? what about checkboxes - that looks like a bit of a hack thought? 
368                 });
369             }
370             
371             if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
372                 inputblock.cls += ' has-feedback';
373                 inputblock.cn.push(feedback);
374             }
375         };
376         
377         if (align ==='left' && this.fieldLabel.length) {
378             
379             // cfg.cls += ' roo-form-group-input ';
380             
381             cfg.cn = [
382                 {
383                     tag : 'i',
384                     cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
385                     tooltip : 'This field is required'
386                 },
387                 {
388                     tag: 'label',
389                     'for' :  id,
390                     cls : 'control-label',
391                     html : this.fieldLabel
392
393                 },
394                 {
395                     cls : "", 
396                     cn: [
397                         inputblock
398                     ]
399                 }
400             ];
401             
402             var labelCfg = cfg.cn[1];
403             var contentCfg = cfg.cn[2];
404             
405             if(this.indicatorpos == 'right'){
406                 cfg.cn = [
407                     {
408                         tag: 'label',
409                         'for' :  id,
410                         cls : 'control-label',
411                         html : this.fieldLabel
412
413                     },
414                     {
415                         tag : 'i',
416                         cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
417                         tooltip : 'This field is required'
418                     },
419                     {
420                         cls : "",
421                         cn: [
422                             inputblock
423                         ]
424                     }
425
426                 ];
427                 
428                 labelCfg = cfg.cn[0];
429                 contentCfg = cfg.cn[2];
430             
431             }
432             
433             if(this.labelWidth > 12){
434                 labelCfg.style = "width: " + this.labelWidth + 'px';
435             }
436             
437             if(this.labelWidth < 13 && this.labelmd == 0){
438                 this.labelmd = this.labelWidth;
439             }
440             
441             if(this.labellg > 0){
442                 labelCfg.cls += ' col-lg-' + this.labellg;
443                 contentCfg.cls += ' col-lg-' + (12 - this.labellg);
444             }
445             
446             if(this.labelmd > 0){
447                 labelCfg.cls += ' col-md-' + this.labelmd;
448                 contentCfg.cls += ' col-md-' + (12 - this.labelmd);
449             }
450             
451             if(this.labelsm > 0){
452                 labelCfg.cls += ' col-sm-' + this.labelsm;
453                 contentCfg.cls += ' col-sm-' + (12 - this.labelsm);
454             }
455             
456             if(this.labelxs > 0){
457                 labelCfg.cls += ' col-xs-' + this.labelxs;
458                 contentCfg.cls += ' col-xs-' + (12 - this.labelxs);
459             }
460             
461             
462         } else if ( this.fieldLabel.length) {
463                 
464             cfg.cn = [
465                 {
466                     tag : 'i',
467                     cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
468                     tooltip : 'This field is required'
469                 },
470                 {
471                     tag: 'label',
472                    //cls : 'input-group-addon',
473                     html : this.fieldLabel
474
475                 },
476
477                inputblock
478
479            ];
480            
481            if(this.indicatorpos == 'right'){
482                 
483                 cfg.cn = [
484                     {
485                         tag: 'label',
486                        //cls : 'input-group-addon',
487                         html : this.fieldLabel
488
489                     },
490                     {
491                         tag : 'i',
492                         cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
493                         tooltip : 'This field is required'
494                     },
495
496                    inputblock
497
498                ];
499
500             }
501
502         } else {
503             
504             cfg.cn = [
505
506                     inputblock
507
508             ];
509                 
510                 
511         };
512         
513         if (this.parentType === 'Navbar' &&  this.parent().bar) {
514            cfg.cls += ' navbar-form';
515         }
516         
517         if (this.parentType === 'NavGroup') {
518            cfg.cls += ' navbar-form';
519            cfg.tag = 'li';
520         }
521         
522         return cfg;
523         
524     },
525     /**
526      * return the real input element.
527      */
528     inputEl: function ()
529     {
530         return this.el.select('input.form-control',true).first();
531     },
532     
533     tooltipEl : function()
534     {
535         return this.inputEl();
536     },
537     
538     indicatorEl : function()
539     {
540         var indicator = this.el.select('i.roo-required-indicator',true).first();
541         
542         if(!indicator){
543             return false;
544         }
545         
546         return indicator;
547         
548     },
549     
550     setDisabled : function(v)
551     {
552         var i  = this.inputEl().dom;
553         if (!v) {
554             i.removeAttribute('disabled');
555             return;
556             
557         }
558         i.setAttribute('disabled','true');
559     },
560     initEvents : function()
561     {
562           
563         this.inputEl().on("keydown" , this.fireKey,  this);
564         this.inputEl().on("focus", this.onFocus,  this);
565         this.inputEl().on("blur", this.onBlur,  this);
566         
567         this.inputEl().relayEvent('keyup', this);
568         
569         this.indicator = this.indicatorEl();
570         
571         if(this.indicator){
572             this.indicator.setVisibilityMode(Roo.Element.DISPLAY);
573             this.indicator.hide();
574         }
575  
576         // reference to original value for reset
577         this.originalValue = this.getValue();
578         //Roo.form.TextField.superclass.initEvents.call(this);
579         if(this.validationEvent == 'keyup'){
580             this.validationTask = new Roo.util.DelayedTask(this.validate, this);
581             this.inputEl().on('keyup', this.filterValidation, this);
582         }
583         else if(this.validationEvent !== false){
584             this.inputEl().on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
585         }
586         
587         if(this.selectOnFocus){
588             this.on("focus", this.preFocus, this);
589             
590         }
591         if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
592             this.inputEl().on("keypress", this.filterKeys, this);
593         } else {
594             this.inputEl().relayEvent('keypress', this);
595         }
596        /* if(this.grow){
597             this.el.on("keyup", this.onKeyUp,  this, {buffer:50});
598             this.el.on("click", this.autoSize,  this);
599         }
600         */
601         if(this.inputEl().is('input[type=password]') && Roo.isSafari){
602             this.inputEl().on('keydown', this.SafariOnKeyDown, this);
603         }
604         
605         if (typeof(this.before) == 'object') {
606             this.before.render(this.el.select('.roo-input-before',true).first());
607         }
608         if (typeof(this.after) == 'object') {
609             this.after.render(this.el.select('.roo-input-after',true).first());
610         }
611         
612         
613     },
614     filterValidation : function(e){
615         if(!e.isNavKeyPress()){
616             this.validationTask.delay(this.validationDelay);
617         }
618     },
619      /**
620      * Validates the field value
621      * @return {Boolean} True if the value is valid, else false
622      */
623     validate : function(){
624         //if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
625         if(this.disabled || this.validateValue(this.getRawValue())){
626             this.markValid();
627             return true;
628         }
629         
630         this.markInvalid();
631         return false;
632     },
633     
634     
635     /**
636      * Validates a value according to the field's validation rules and marks the field as invalid
637      * if the validation fails
638      * @param {Mixed} value The value to validate
639      * @return {Boolean} True if the value is valid, else false
640      */
641     validateValue : function(value){
642         if(value.length < 1)  { // if it's blank
643             if(this.allowBlank){
644                 return true;
645             }
646             return false;
647         }
648         
649         if(value.length < this.minLength){
650             return false;
651         }
652         if(value.length > this.maxLength){
653             return false;
654         }
655         if(this.vtype){
656             var vt = Roo.form.VTypes;
657             if(!vt[this.vtype](value, this)){
658                 return false;
659             }
660         }
661         if(typeof this.validator == "function"){
662             var msg = this.validator(value);
663             if(msg !== true){
664                 return false;
665             }
666         }
667         
668         if(this.regex && !this.regex.test(value)){
669             return false;
670         }
671         
672         return true;
673     },
674
675     
676     
677      // private
678     fireKey : function(e){
679         //Roo.log('field ' + e.getKey());
680         if(e.isNavKeyPress()){
681             this.fireEvent("specialkey", this, e);
682         }
683     },
684     focus : function (selectText){
685         if(this.rendered){
686             this.inputEl().focus();
687             if(selectText === true){
688                 this.inputEl().dom.select();
689             }
690         }
691         return this;
692     } ,
693     
694     onFocus : function(){
695         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
696            // this.el.addClass(this.focusClass);
697         }
698         if(!this.hasFocus){
699             this.hasFocus = true;
700             this.startValue = this.getValue();
701             this.fireEvent("focus", this);
702         }
703     },
704     
705     beforeBlur : Roo.emptyFn,
706
707     
708     // private
709     onBlur : function(){
710         this.beforeBlur();
711         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
712             //this.el.removeClass(this.focusClass);
713         }
714         this.hasFocus = false;
715         if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
716             this.validate();
717         }
718         var v = this.getValue();
719         if(String(v) !== String(this.startValue)){
720             this.fireEvent('change', this, v, this.startValue);
721         }
722         this.fireEvent("blur", this);
723     },
724     
725     /**
726      * Resets the current field value to the originally loaded value and clears any validation messages
727      */
728     reset : function(){
729         this.setValue(this.originalValue);
730         this.validate();
731     },
732      /**
733      * Returns the name of the field
734      * @return {Mixed} name The name field
735      */
736     getName: function(){
737         return this.name;
738     },
739      /**
740      * Returns the normalized data value (undefined or emptyText will be returned as '').  To return the raw value see {@link #getRawValue}.
741      * @return {Mixed} value The field value
742      */
743     getValue : function(){
744         
745         var v = this.inputEl().getValue();
746         
747         return v;
748     },
749     /**
750      * Returns the raw data value which may or may not be a valid, defined value.  To return a normalized value see {@link #getValue}.
751      * @return {Mixed} value The field value
752      */
753     getRawValue : function(){
754         var v = this.inputEl().getValue();
755         
756         return v;
757     },
758     
759     /**
760      * Sets the underlying DOM field's value directly, bypassing validation.  To set the value with validation see {@link #setValue}.
761      * @param {Mixed} value The value to set
762      */
763     setRawValue : function(v){
764         return this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
765     },
766     
767     selectText : function(start, end){
768         var v = this.getRawValue();
769         if(v.length > 0){
770             start = start === undefined ? 0 : start;
771             end = end === undefined ? v.length : end;
772             var d = this.inputEl().dom;
773             if(d.setSelectionRange){
774                 d.setSelectionRange(start, end);
775             }else if(d.createTextRange){
776                 var range = d.createTextRange();
777                 range.moveStart("character", start);
778                 range.moveEnd("character", v.length-end);
779                 range.select();
780             }
781         }
782     },
783     
784     /**
785      * Sets a data value into the field and validates it.  To set the value directly without validation see {@link #setRawValue}.
786      * @param {Mixed} value The value to set
787      */
788     setValue : function(v){
789         this.value = v;
790         if(this.rendered){
791             this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
792             this.validate();
793         }
794     },
795     
796     /*
797     processValue : function(value){
798         if(this.stripCharsRe){
799             var newValue = value.replace(this.stripCharsRe, '');
800             if(newValue !== value){
801                 this.setRawValue(newValue);
802                 return newValue;
803             }
804         }
805         return value;
806     },
807   */
808     preFocus : function(){
809         
810         if(this.selectOnFocus){
811             this.inputEl().dom.select();
812         }
813     },
814     filterKeys : function(e){
815         var k = e.getKey();
816         if(!Roo.isIE && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
817             return;
818         }
819         var c = e.getCharCode(), cc = String.fromCharCode(c);
820         if(Roo.isIE && (e.isSpecialKey() || !cc)){
821             return;
822         }
823         if(!this.maskRe.test(cc)){
824             e.stopEvent();
825         }
826     },
827      /**
828      * Clear any invalid styles/messages for this field
829      */
830     clearInvalid : function(){
831         
832         if(!this.el || this.preventMark){ // not rendered
833             return;
834         }
835         
836         if(this.indicator){
837             this.indicator.hide();
838         }
839         
840         this.el.removeClass(this.invalidClass);
841         
842         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
843             
844             var feedback = this.el.select('.form-control-feedback', true).first();
845             
846             if(feedback){
847                 this.el.select('.form-control-feedback', true).first().removeClass(this.invalidFeedbackClass);
848             }
849             
850         }
851         
852         this.fireEvent('valid', this);
853     },
854     
855      /**
856      * Mark this field as valid
857      */
858     markValid : function()
859     {
860         if(!this.el  || this.preventMark){ // not rendered
861             return;
862         }
863         
864         this.el.removeClass([this.invalidClass, this.validClass]);
865         
866         var feedback = this.el.select('.form-control-feedback', true).first();
867             
868         if(feedback){
869             this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
870         }
871
872         if(this.disabled){
873             return;
874         }
875         
876         if(this.allowBlank && !this.getRawValue().length){
877             return;
878         }
879         
880         if(this.indicator){
881             this.indicator.hide();
882         }
883         
884         this.el.addClass(this.validClass);
885         
886         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank && (this.getValue().length || this.forceFeedback)){
887             
888             var feedback = this.el.select('.form-control-feedback', true).first();
889             
890             if(feedback){
891                 this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
892                 this.el.select('.form-control-feedback', true).first().addClass([this.validFeedbackClass]);
893             }
894             
895         }
896         
897         this.fireEvent('valid', this);
898     },
899     
900      /**
901      * Mark this field as invalid
902      * @param {String} msg The validation message
903      */
904     markInvalid : function(msg)
905     {
906         if(!this.el  || this.preventMark){ // not rendered
907             return;
908         }
909         
910         this.el.removeClass([this.invalidClass, this.validClass]);
911         
912         var feedback = this.el.select('.form-control-feedback', true).first();
913             
914         if(feedback){
915             this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
916         }
917
918         if(this.disabled){
919             return;
920         }
921         
922         if(this.allowBlank && !this.getRawValue().length){
923             return;
924         }
925         
926         if(this.indicator){
927             this.indicator.show();
928         }
929         
930         this.el.addClass(this.invalidClass);
931         
932         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
933             
934             var feedback = this.el.select('.form-control-feedback', true).first();
935             
936             if(feedback){
937                 this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
938                 
939                 if(this.getValue().length || this.forceFeedback){
940                     this.el.select('.form-control-feedback', true).first().addClass([this.invalidFeedbackClass]);
941                 }
942                 
943             }
944             
945         }
946         
947         this.fireEvent('invalid', this, msg);
948     },
949     // private
950     SafariOnKeyDown : function(event)
951     {
952         // this is a workaround for a password hang bug on chrome/ webkit.
953         if (this.inputEl().dom.type != 'password') {
954             return;
955         }
956         
957         var isSelectAll = false;
958         
959         if(this.inputEl().dom.selectionEnd > 0){
960             isSelectAll = (this.inputEl().dom.selectionEnd - this.inputEl().dom.selectionStart - this.getValue().length == 0) ? true : false;
961         }
962         if(((event.getKey() == 8 || event.getKey() == 46) && this.getValue().length ==1)){ // backspace and delete key
963             event.preventDefault();
964             this.setValue('');
965             return;
966         }
967         
968         if(isSelectAll  && event.getCharCode() > 31 && !event.ctrlKey) { // not backspace and delete key (or ctrl-v)
969             
970             event.preventDefault();
971             // this is very hacky as keydown always get's upper case.
972             //
973             var cc = String.fromCharCode(event.getCharCode());
974             this.setValue( event.shiftKey ?  cc : cc.toLowerCase());
975             
976         }
977     },
978     adjustWidth : function(tag, w){
979         tag = tag.toLowerCase();
980         if(typeof w == 'number' && Roo.isStrict && !Roo.isSafari){
981             if(Roo.isIE && (tag == 'input' || tag == 'textarea')){
982                 if(tag == 'input'){
983                     return w + 2;
984                 }
985                 if(tag == 'textarea'){
986                     return w-2;
987                 }
988             }else if(Roo.isOpera){
989                 if(tag == 'input'){
990                     return w + 2;
991                 }
992                 if(tag == 'textarea'){
993                     return w-2;
994                 }
995             }
996         }
997         return w;
998     }
999     
1000 });
1001
1002