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