9 * @class Roo.bootstrap.form.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 * @cfg {String} capture (user|camera) use for file input only. (default empty)
35 * @cfg {String} accept (image|video|audio) use for file input only. (default empty)
36 * @cfg {Boolean} preventMark Do not show tick or cross if error/success
37 * @cfg {Roo.bootstrap.Button} before Button to show before
38 * @cfg {Roo.bootstrap.Button} afterButton to show before
39 * @cfg {String} align (left|center|right) Default left
40 * @cfg {Boolean} forceFeedback (true|false) Default false
44 * @param {Object} config The config object
47 Roo.bootstrap.form.Input = function(config){
49 Roo.bootstrap.form.Input.superclass.constructor.call(this, config);
54 * Fires when this field receives input focus.
55 * @param {Roo.form.Field} this
60 * Fires when this field loses input focus.
61 * @param {Roo.form.Field} this
66 * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
67 * {@link Roo.EventObject#getKey} to determine which key was pressed.
68 * @param {Roo.form.Field} this
69 * @param {Roo.EventObject} e The event object
74 * Fires just before the field blurs if the field value has changed.
75 * @param {Roo.form.Field} this
76 * @param {Mixed} newValue The new value
77 * @param {Mixed} oldValue The original value
82 * Fires after the field has been marked as invalid.
83 * @param {Roo.form.Field} this
84 * @param {String} msg The validation message
89 * Fires after the field has been validated with no errors.
90 * @param {Roo.form.Field} this
95 * Fires after the key up
96 * @param {Roo.form.Field} this
97 * @param {Roo.EventObject} e The event Object
102 * Fires after the user pastes into input
103 * @param {Roo.form.Field} this
104 * @param {Roo.EventObject} e The event Object
110 Roo.extend(Roo.bootstrap.form.Input, Roo.bootstrap.Component, {
112 * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
113 automatic validation (defaults to "keyup").
115 validationEvent : "keyup",
117 * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
119 validateOnBlur : true,
121 * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation is initiated (defaults to 250)
123 validationDelay : 250,
125 * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus")
127 focusClass : "x-form-focus", // not needed???
131 * @cfg {String} invalidClass DEPRICATED - code uses BS4 - is-valid / is-invalid
133 invalidClass : "has-warning",
136 * @cfg {String} validClass DEPRICATED - code uses BS4 - is-valid / is-invalid
138 validClass : "has-success",
141 * @cfg {Boolean} hasFeedback (true|false) default true
146 * @cfg {String} invalidFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
148 invalidFeedbackClass : "glyphicon-warning-sign",
151 * @cfg {String} validFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
153 validFeedbackClass : "glyphicon-ok",
156 * @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives input focus (defaults to false)
158 selectOnFocus : false,
161 * @cfg {String} maskRe An input mask regular expression that will be used to filter keystrokes that don't match (defaults to null)
165 * @cfg {String} vtype A validation type name as defined in {@link Roo.form.VTypes} (defaults to null)
170 * @cfg {Boolean} disableKeyFilter True to disable input keystroke filtering (defaults to false)
172 disableKeyFilter : false,
175 * @cfg {Boolean} disabled True to disable the field (defaults to false).
179 * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to true)
183 * @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")
185 blankText : "Please complete this mandatory field",
188 * @cfg {Number} minLength Minimum input field length required (defaults to 0)
192 * @cfg {Number} maxLength Maximum input field length allowed (defaults to Number.MAX_VALUE)
194 maxLength : Number.MAX_VALUE,
196 * @cfg {String} minLengthText Error text to display if the minimum length validation fails (defaults to "The minimum length for this field is {minLength}")
198 minLengthText : "The minimum length for this field is {0}",
200 * @cfg {String} maxLengthText Error text to display if the maximum length validation fails (defaults to "The maximum length for this field is {maxLength}")
202 maxLengthText : "The maximum length for this field is {0}",
206 * @cfg {Function} validator A custom validation function to be called during field validation (defaults to null).
207 * If available, this function will be called only after the basic validators all return true, and will be passed the
208 * current field value and expected to return boolean true if the value is valid or a string error message if invalid.
212 * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation (defaults to null).
213 * If available, this regex will be evaluated only after the basic validators all return true, and will be passed the
214 * current field value. If the test fails, the field will be marked invalid using {@link #regexText}.
218 * @cfg {String} regexText -- Depricated - use Invalid Text
223 * @cfg {String} invalidText The error text to display if {@link #validator} test fails during validation (defaults to "")
248 formatedValue : false,
249 forceFeedback : false,
251 indicatorpos : 'left',
261 parentLabelAlign : function()
264 while (parent.parent()) {
265 parent = parent.parent();
266 if (typeof(parent.labelAlign) !='undefined') {
267 return parent.labelAlign;
274 getAutoCreate : function()
281 if(this.inputType != 'hidden'){
282 cfg.cls = 'form-group' //input-group
288 type : this.inputType,
290 cls : 'form-control',
291 placeholder : this.placeholder || '',
292 autocomplete : this.autocomplete || 'new-password'
294 if (this.inputType == 'file') {
295 input.style = 'overflow:hidden'; // why not in CSS?
298 if(this.capture.length){
299 input.capture = this.capture;
302 if(this.accept.length){
303 input.accept = this.accept + "/*";
307 input.style = (typeof(input.style) == 'undefined') ? ('text-align:' + this.align) : (input.style + 'text-align:' + this.align);
310 if(this.maxLength && this.maxLength != Number.MAX_VALUE){
311 input.maxLength = this.maxLength;
323 input.name = this.name;
327 input.cls += ' input-' + this.size;
331 ['xs','sm','md','lg'].map(function(size){
332 if (settings[size]) {
333 cfg.cls += ' col-' + size + '-' + settings[size];
337 var inputblock = input;
341 cls: 'glyphicon form-control-feedback'
344 if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
347 cls : 'has-feedback',
355 if (this.before || this.after) {
362 if (this.before && typeof(this.before) == 'string') {
366 cls : 'roo-input-before input-group-addon input-group-prepend input-group-text',
370 if (this.before && typeof(this.before) == 'object') {
371 this.before = Roo.factory(this.before);
375 cls : 'roo-input-before input-group-prepend input-group-' +
376 (this.before.xtype == 'Button' ? 'btn' : 'addon') //?? what about checkboxes - that looks like a bit of a hack thought?
380 inputblock.cn.push(input);
382 if (this.after && typeof(this.after) == 'string') {
385 cls : 'roo-input-after input-group-append input-group-text input-group-addon',
389 if (this.after && typeof(this.after) == 'object') {
390 this.after = Roo.factory(this.after);
394 cls : 'roo-input-after input-group-append input-group-' +
395 (this.after.xtype == 'Button' ? 'btn' : 'addon') //?? what about checkboxes - that looks like a bit of a hack thought?
399 if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
400 inputblock.cls += ' has-feedback';
401 inputblock.cn.push(feedback);
407 cfg = this.getAutoCreateLabel( cfg, inputblock );
412 if (this.parentType === 'Navbar' && this.parent().bar) {
413 cfg.cls += ' navbar-form';
416 if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) {
417 // on BS4 we do this only if not form
418 cfg.cls += ' navbar-form';
426 * autocreate the label - also used by textara... ?? and others?
428 getAutoCreateLabel : function( cfg, inputblock )
430 var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
434 cls : 'roo-required-indicator ' + (this.indicatorpos == 'right' ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star',
435 tooltip : 'This field is required'
437 if (this.allowBlank ) {
438 indicator.style = this.allowBlank ? ' display:none' : '';
440 if (align ==='left' && this.fieldLabel.length) {
442 cfg.cls += ' roo-form-group-label-left' + (Roo.bootstrap.version == 4 ? ' row' : '');
449 cls : 'control-label col-form-label',
450 html : this.fieldLabel
461 var labelCfg = cfg.cn[1];
462 var contentCfg = cfg.cn[2];
464 if(this.indicatorpos == 'right'){
469 cls : 'control-label col-form-label',
473 html : this.fieldLabel
487 labelCfg = cfg.cn[0];
488 contentCfg = cfg.cn[1];
492 if(this.labelWidth > 12){
493 labelCfg.style = "width: " + this.labelWidth + 'px';
496 if(this.labelWidth < 13 && this.labelmd == 0){
497 this.labellg = this.labellg > 0 ? this.labellg : this.labelWidth;
500 if(this.labellg > 0){
501 labelCfg.cls += ' col-lg-' + this.labellg;
502 contentCfg.cls += ' col-lg-' + (12 - this.labellg);
505 if(this.labelmd > 0){
506 labelCfg.cls += ' col-md-' + this.labelmd;
507 contentCfg.cls += ' col-md-' + (12 - this.labelmd);
510 if(this.labelsm > 0){
511 labelCfg.cls += ' col-sm-' + this.labelsm;
512 contentCfg.cls += ' col-sm-' + (12 - this.labelsm);
515 if(this.labelxs > 0){
516 labelCfg.cls += ' col-xs-' + this.labelxs;
517 contentCfg.cls += ' col-xs-' + (12 - this.labelxs);
521 } else if ( this.fieldLabel.length) {
528 cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
529 tooltip : 'This field is required',
530 style : this.allowBlank ? ' display:none' : ''
534 //cls : 'input-group-addon',
535 html : this.fieldLabel
543 if(this.indicatorpos == 'right'){
548 //cls : 'input-group-addon',
549 html : this.fieldLabel
554 cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
555 tooltip : 'This field is required',
556 style : this.allowBlank ? ' display:none' : ''
580 * return the real input element.
584 return this.el.select('input.form-control',true).first();
587 tooltipEl : function()
589 return this.inputEl();
592 indicatorEl : function()
594 if (Roo.bootstrap.version == 4) {
595 return false; // not enabled in v4 yet.
598 var indicator = this.el.select('i.roo-required-indicator',true).first();
608 setDisabled : function(v)
610 var i = this.inputEl().dom;
612 i.removeAttribute('disabled');
616 i.setAttribute('disabled','true');
618 initEvents : function()
621 this.inputEl().on("keydown" , this.fireKey, this);
622 this.inputEl().on("focus", this.onFocus, this);
623 this.inputEl().on("blur", this.onBlur, this);
625 this.inputEl().relayEvent('keyup', this);
626 this.inputEl().relayEvent('paste', this);
628 this.indicator = this.indicatorEl();
631 this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible'); // changed from invisible??? -
634 // reference to original value for reset
635 this.originalValue = this.getValue();
636 //Roo.form.TextField.superclass.initEvents.call(this);
637 if(this.validationEvent == 'keyup'){
638 this.validationTask = new Roo.util.DelayedTask(this.validate, this);
639 this.inputEl().on('keyup', this.filterValidation, this);
641 else if(this.validationEvent !== false){
642 this.inputEl().on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
645 if(this.selectOnFocus){
646 this.on("focus", this.preFocus, this);
649 if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
650 this.inputEl().on("keypress", this.filterKeys, this);
652 this.inputEl().relayEvent('keypress', this);
655 this.el.on("keyup", this.onKeyUp, this, {buffer:50});
656 this.el.on("click", this.autoSize, this);
659 if(this.inputEl().is('input[type=password]') && Roo.isSafari){
660 this.inputEl().on('keydown', this.SafariOnKeyDown, this);
663 if (typeof(this.before) == 'object') {
664 this.before.render(this.el.select('.roo-input-before',true).first());
666 if (typeof(this.after) == 'object') {
667 this.after.render(this.el.select('.roo-input-after',true).first());
670 this.inputEl().on('change', this.onChange, this);
673 filterValidation : function(e){
674 if(!e.isNavKeyPress()){
675 this.validationTask.delay(this.validationDelay);
679 * Validates the field value
680 * @return {Boolean} True if the value is valid, else false
682 validate : function(){
683 //if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
684 if(this.disabled || this.validateValue(this.getRawValue())){
695 * Validates a value according to the field's validation rules and marks the field as invalid
696 * if the validation fails
697 * @param {Mixed} value The value to validate
698 * @return {Boolean} True if the value is valid, else false
700 validateValue : function(value)
702 if(this.getVisibilityEl().hasClass('hidden')){
706 if(value.length < 1) { // if it's blank
713 if(value.length < this.minLength){
716 if(value.length > this.maxLength){
720 var vt = Roo.form.VTypes;
721 if(!vt[this.vtype](value, this)){
725 if(typeof this.validator == "function"){
726 var msg = this.validator(value);
727 if (typeof(msg) == 'string') {
728 this.invalidText = msg;
735 if(this.regex && !this.regex.test(value)){
743 fireKey : function(e){
744 //Roo.log('field ' + e.getKey());
745 if(e.isNavKeyPress()){
746 this.fireEvent("specialkey", this, e);
749 focus : function (selectText){
751 this.inputEl().focus();
752 if(selectText === true){
753 this.inputEl().dom.select();
759 onFocus : function(){
760 if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
761 // this.el.addClass(this.focusClass);
764 this.hasFocus = true;
765 this.startValue = this.getValue();
766 this.fireEvent("focus", this);
770 beforeBlur : Roo.emptyFn,
776 if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
777 //this.el.removeClass(this.focusClass);
779 this.hasFocus = false;
780 if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
783 var v = this.getValue();
784 if(String(v) !== String(this.startValue)){
785 this.fireEvent('change', this, v, this.startValue);
787 this.fireEvent("blur", this);
790 onChange : function(e)
792 var v = this.getValue();
793 if(String(v) !== String(this.startValue)){
794 this.fireEvent('change', this, v, this.startValue);
800 * Resets the current field value to the originally loaded value and clears any validation messages
803 this.setValue(this.originalValue);
807 * Returns the name of the field
808 * @return {Mixed} name The name field
814 * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
815 * @return {Mixed} value The field value
817 getValue : function(){
819 var v = this.inputEl().getValue();
824 * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
825 * @return {Mixed} value The field value
827 getRawValue : function(){
828 var v = this.inputEl().getValue();
834 * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}.
835 * @param {Mixed} value The value to set
837 setRawValue : function(v){
838 return this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
841 selectText : function(start, end){
842 var v = this.getRawValue();
844 start = start === undefined ? 0 : start;
845 end = end === undefined ? v.length : end;
846 var d = this.inputEl().dom;
847 if(d.setSelectionRange){
848 d.setSelectionRange(start, end);
849 }else if(d.createTextRange){
850 var range = d.createTextRange();
851 range.moveStart("character", start);
852 range.moveEnd("character", v.length-end);
859 * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
860 * @param {Mixed} value The value to set
862 setValue : function(v){
867 this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
873 processValue : function(value){
874 if(this.stripCharsRe){
875 var newValue = value.replace(this.stripCharsRe, '');
876 if(newValue !== value){
877 this.setRawValue(newValue);
884 preFocus : function(){
886 if(this.selectOnFocus){
887 this.inputEl().dom.select();
890 filterKeys : function(e){
892 if(!Roo.isIE && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
895 var c = e.getCharCode(), cc = String.fromCharCode(c);
896 if(Roo.isIE && (e.isSpecialKey() || !cc)){
899 if(!this.maskRe.test(cc)){
904 * Clear any invalid styles/messages for this field
906 clearInvalid : function(){
908 if(!this.el || this.preventMark){ // not rendered
913 this.el.removeClass([this.invalidClass, 'is-invalid']);
915 if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
917 var feedback = this.el.select('.form-control-feedback', true).first();
920 this.el.select('.form-control-feedback', true).first().removeClass(this.invalidFeedbackClass);
926 this.indicator.removeClass('visible');
927 this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
930 this.fireEvent('valid', this);
934 * Mark this field as valid
936 markValid : function()
938 if(!this.el || this.preventMark){ // not rendered...
942 this.el.removeClass([this.invalidClass, this.validClass]);
943 this.inputEl().removeClass(['is-valid', 'is-invalid']);
945 var feedback = this.el.select('.form-control-feedback', true).first();
948 this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
952 this.indicator.removeClass('visible');
953 this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
961 if(this.allowBlank && !this.getRawValue().length){
964 if (Roo.bootstrap.version == 3) {
965 this.el.addClass(this.validClass);
967 this.inputEl().addClass('is-valid');
970 if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank && (this.getValue().length || this.forceFeedback)){
972 var feedback = this.el.select('.form-control-feedback', true).first();
975 this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
976 this.el.select('.form-control-feedback', true).first().addClass([this.validFeedbackClass]);
981 this.fireEvent('valid', this);
985 * Mark this field as invalid
986 * @param {String} msg The validation message
988 markInvalid : function(msg)
990 if(!this.el || this.preventMark){ // not rendered
994 this.el.removeClass([this.invalidClass, this.validClass]);
995 this.inputEl().removeClass(['is-valid', 'is-invalid']);
997 var feedback = this.el.select('.form-control-feedback', true).first();
1000 this.el.select('.form-control-feedback', true).first().removeClass(
1001 [this.invalidFeedbackClass, this.validFeedbackClass]);
1008 if(this.allowBlank && !this.getRawValue().length){
1013 this.indicator.removeClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
1014 this.indicator.addClass('visible');
1016 if (Roo.bootstrap.version == 3) {
1017 this.el.addClass(this.invalidClass);
1019 this.inputEl().addClass('is-invalid');
1024 if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
1026 var feedback = this.el.select('.form-control-feedback', true).first();
1029 this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
1031 if(this.getValue().length || this.forceFeedback){
1032 this.el.select('.form-control-feedback', true).first().addClass([this.invalidFeedbackClass]);
1039 this.fireEvent('invalid', this, msg);
1042 SafariOnKeyDown : function(event)
1044 // this is a workaround for a password hang bug on chrome/ webkit.
1045 if (this.inputEl().dom.type != 'password') {
1049 var isSelectAll = false;
1051 if(this.inputEl().dom.selectionEnd > 0){
1052 isSelectAll = (this.inputEl().dom.selectionEnd - this.inputEl().dom.selectionStart - this.getValue().length == 0) ? true : false;
1054 if(((event.getKey() == 8 || event.getKey() == 46) && this.getValue().length ==1)){ // backspace and delete key
1055 event.preventDefault();
1060 if(isSelectAll && event.getCharCode() > 31 && !event.ctrlKey) { // not backspace and delete key (or ctrl-v)
1062 event.preventDefault();
1063 // this is very hacky as keydown always get's upper case.
1065 var cc = String.fromCharCode(event.getCharCode());
1066 this.setValue( event.shiftKey ? cc : cc.toLowerCase());
1070 adjustWidth : function(tag, w){
1071 tag = tag.toLowerCase();
1072 if(typeof w == 'number' && Roo.isStrict && !Roo.isSafari){
1073 if(Roo.isIE && (tag == 'input' || tag == 'textarea')){
1077 if(tag == 'textarea'){
1080 }else if(Roo.isOpera){
1084 if(tag == 'textarea'){
1092 setFieldLabel : function(v)
1098 if(this.indicatorEl()){
1099 var ar = this.el.select('label > span',true);
1101 if (ar.elements.length) {
1102 this.el.select('label > span',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
1103 this.fieldLabel = v;
1107 var br = this.el.select('label',true);
1109 if(br.elements.length) {
1110 this.el.select('label',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
1111 this.fieldLabel = v;
1115 Roo.log('Cannot Found any of label > span || label in input');
1119 this.el.select('label',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
1120 this.fieldLabel = v;