]> git.proxmox.com Git - sencha-touch.git/blob - src/src/field/Input.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / field / Input.js
1 /**
2 * @private
3 */
4 Ext.define('Ext.field.Input', {
5 extend: 'Ext.Component',
6 xtype : 'input',
7
8 /**
9 * @event clearicontap
10 * Fires whenever the clear icon is tapped.
11 * @param {Ext.field.Input} this
12 * @param {Ext.EventObject} e The event object
13 */
14
15 /**
16 * @event masktap
17 * @preventable doMaskTap
18 * Fires whenever a mask is tapped.
19 * @param {Ext.field.Input} this
20 * @param {Ext.EventObject} e The event object.
21 */
22
23 /**
24 * @event focus
25 * @preventable doFocus
26 * Fires whenever the input get focus.
27 * @param {Ext.EventObject} e The event object.
28 */
29
30 /**
31 * @event blur
32 * @preventable doBlur
33 * Fires whenever the input loses focus.
34 * @param {Ext.EventObject} e The event object.
35 */
36
37 /**
38 * @event click
39 * Fires whenever the input is clicked.
40 * @param {Ext.EventObject} e The event object.
41 */
42
43 /**
44 * @event keyup
45 * Fires whenever keyup is detected.
46 * @param {Ext.EventObject} e The event object.
47 */
48
49 /**
50 * @event paste
51 * Fires whenever paste is detected.
52 * @param {Ext.EventObject} e The event object.
53 */
54
55 /**
56 * @event mousedown
57 * Fires whenever the input has a mousedown occur.
58 * @param {Ext.EventObject} e The event object.
59 */
60
61 /**
62 * @property {String} tag The el tag.
63 * @private
64 */
65 tag: 'input',
66
67 cachedConfig: {
68 /**
69 * @cfg {String} cls The `className` to be applied to this input.
70 * @accessor
71 */
72 cls: Ext.baseCSSPrefix + 'form-field',
73
74 /**
75 * @cfg {String} focusCls The CSS class to use when the field receives focus.
76 * @accessor
77 */
78 focusCls: Ext.baseCSSPrefix + 'field-focus',
79
80 // @private
81 maskCls: Ext.baseCSSPrefix + 'field-mask',
82
83 /**
84 * @cfg {String/Boolean} useMask
85 * `true` to use a mask on this field, or `auto` to automatically select when you should use it.
86 * @private
87 * @accessor
88 */
89 useMask: 'auto',
90
91 /**
92 * @cfg {String} type The type attribute for input fields -- e.g. radio, text, password.
93 *
94 * If you want to use a `file` input, please use the {@link Ext.field.File} component instead.
95 * @accessor
96 */
97 type: 'text',
98
99 /**
100 * @cfg {Boolean} checked `true` if the checkbox should render initially checked.
101 * @accessor
102 */
103 checked: false
104 },
105
106 config: {
107 /**
108 * @cfg
109 * @inheritdoc
110 */
111 baseCls: Ext.baseCSSPrefix + 'field-input',
112
113 /**
114 * @cfg {String} name The field's HTML name attribute.
115 * __Note:__ This property must be set if this field is to be automatically included with
116 * {@link Ext.form.Panel#method-submit form submit()}.
117 * @accessor
118 */
119 name: null,
120
121 /**
122 * @cfg {Mixed} value A value to initialize this field with.
123 * @accessor
124 */
125 value: null,
126
127 /**
128 * @property {Boolean} `true` if the field currently has focus.
129 * @accessor
130 */
131 isFocused: false,
132
133 /**
134 * @cfg {Number} tabIndex The `tabIndex` for this field.
135 *
136 * __Note:__ This only applies to fields that are rendered, not those which are built via `applyTo`.
137 * @accessor
138 */
139 tabIndex: null,
140
141 /**
142 * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty.
143 * @accessor
144 */
145 placeHolder: null,
146
147 /**
148 * @cfg {Number} [minValue=undefined] The minimum value that this Number field can accept (defaults to `undefined`, e.g. no minimum).
149 * @accessor
150 */
151 minValue: null,
152
153 /**
154 * @cfg {Number} [maxValue=undefined] The maximum value that this Number field can accept (defaults to `undefined`, e.g. no maximum).
155 * @accessor
156 */
157 maxValue: null,
158
159 /**
160 * @cfg {Number} [stepValue=undefined] The amount by which the field is incremented or decremented each time the spinner is tapped.
161 * Defaults to `undefined`, which means that the field goes up or down by 1 each time the spinner is tapped.
162 * @accessor
163 */
164 stepValue: null,
165
166 /**
167 * @cfg {Number} [maxLength=0] The maximum number of permitted input characters.
168 * @accessor
169 */
170 maxLength: null,
171
172 /**
173 * @cfg {Boolean} [autoComplete=undefined]
174 * `true` to set the field's DOM element `autocomplete` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
175 * @accessor
176 */
177 autoComplete: null,
178
179 /**
180 * @cfg {Boolean} [autoCapitalize=undefined]
181 * `true` to set the field's DOM element `autocapitalize` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset
182 * @accessor
183 */
184 autoCapitalize: null,
185
186 /**
187 * `true` to set the field DOM element `autocorrect` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
188 * @cfg {Boolean} autoCorrect
189 * @accessor
190 */
191 autoCorrect: null,
192
193 /**
194 * @cfg {Boolean} [readOnly=undefined]
195 * `true` to set the field DOM element `readonly` attribute to `"true"`. Defaults to `undefined`, leaving the attribute unset.
196 * @accessor
197 */
198 readOnly: null,
199
200 /**
201 * @cfg {Number} [maxRows=undefined]
202 * Sets the field DOM element `maxRows` attribute. Defaults to `undefined`, leaving the attribute unset.
203 * @accessor
204 */
205 maxRows: null,
206
207 /**
208 * @cfg {String} pattern The value for the HTML5 `pattern` attribute.
209 * You can use this to change which keyboard layout will be used.
210 *
211 * Ext.define('Ux.field.Pattern', {
212 * extend : 'Ext.field.Text',
213 * xtype : 'patternfield',
214 *
215 * config : {
216 * component : {
217 * pattern : '[0-9]*'
218 * }
219 * }
220 * });
221 *
222 * Even though it extends {@link Ext.field.Text}, it will display the number keyboard.
223 *
224 * @accessor
225 */
226 pattern: null,
227
228 /**
229 * @cfg {Boolean} [disabled=false] `true` to disable the field.
230 *
231 * Be aware that conformant with the [HTML specification](http://www.w3.org/TR/html401/interact/forms.html),
232 * disabled Fields will not be {@link Ext.form.Panel#method-submit submitted}.
233 * @accessor
234 */
235
236 /**
237 * @cfg {Mixed} startValue
238 * The value that the Field had at the time it was last focused. This is the value that is passed
239 * to the {@link Ext.field.Text#change} event which is fired if the value has been changed when the Field is blurred.
240 *
241 * __This will be `undefined` until the Field has been visited.__ Compare {@link #originalValue}.
242 * @accessor
243 */
244 startValue: false,
245
246 /**
247 * @cfg {Boolean} fastFocus
248 *
249 * Enable Fast Input Focusing on iOS, using this workaround will stop some touchstart events in order to prevent
250 * delayed focus issues.
251 *
252 * @accessor
253 */
254 fastFocus: true
255 },
256
257 /**
258 * @cfg {String/Number} originalValue The original value when the input is rendered.
259 * @private
260 */
261
262 // @private
263 getTemplate: function() {
264 var items = [
265 {
266 reference: 'input',
267 tag: this.tag
268 },
269 {
270 reference: 'mask',
271 classList: [this.config.maskCls]
272 },
273 {
274 reference: 'clearIcon',
275 cls: 'x-clear-icon'
276 }
277 ];
278
279 return items;
280 },
281
282 initElement: function() {
283 var me = this;
284
285 me.callParent();
286
287 me.input.on({
288 scope: me,
289
290 keyup: 'onKeyUp',
291 keydown: 'onKeyDown',
292 focus: 'onFocus',
293 blur: 'onBlur',
294 input: 'onInput',
295 paste: 'onPaste',
296 tap: 'onInputTap'
297 });
298
299
300 // Stock android has a delayed mousedown event that is dispatched
301 // this prevents the mousedown from focus's an input when not intended (click a message box button or picker button that lays over an input)
302 // we then force focus on touchend.
303 if(Ext.browser.is.AndroidStock) {
304 me.input.dom.addEventListener("mousedown", function(e) {
305 if(document.activeElement != e.target) {
306 e.preventDefault();
307 }
308 } );
309 me.input.dom.addEventListener("touchend", function() { me.focus(); });
310 }
311
312 me.mask.on({
313 scope: me,
314 tap: 'onMaskTap'
315 });
316
317 if (me.clearIcon) {
318 me.clearIcon.on({
319 tap: 'onClearIconTap',
320 touchstart: 'onClearIconPress',
321 touchend: 'onClearIconRelease',
322 scope: me
323 });
324 }
325
326 // Hack for IE10. Seems like keyup event is not fired for 'enter' keyboard button, so we use keypress event instead to handle enter.
327 if(Ext.browser.is.ie && Ext.browser.version.major >=10){
328 me.input.on({
329 scope: me,
330 keypress: 'onKeyPress'
331 });
332 }
333 },
334
335 updateFastFocus: function(newValue) {
336 // This is used to prevent 300ms delayed focus bug on iOS
337 if (newValue) {
338 if (this.getFastFocus() && Ext.os.is.iOS) {
339 this.input.on({
340 scope: this,
341 touchstart: "onTouchStart"
342 });
343 }
344 } else {
345 this.input.un({
346 scope: this,
347 touchstart: "onTouchStart"
348 });
349 }
350 },
351
352 /**
353 * Manual Max Length processing is required for the stock "Browser" on Android
354 * @private
355 * @return {Boolean} 'true' if non-chrome browser is detected on Android
356 */
357 useManualMaxLength: function() {
358 return Boolean((Ext.os.is.Android && !Ext.browser.is.Chrome));
359 },
360
361 applyUseMask: function(useMask) {
362 if (useMask === 'auto') {
363 useMask = Ext.os.is.iOS && Ext.os.version.lt('5');
364 }
365
366 return Boolean(useMask);
367 },
368
369 /**
370 * Updates the useMask configuration
371 */
372 updateUseMask: function(newUseMask) {
373 this.mask[newUseMask ? 'show' : 'hide']();
374 },
375
376 updatePattern : function (pattern) {
377 this.updateFieldAttribute('pattern', pattern);
378 },
379
380 /**
381 * Helper method to update a specified attribute on the `fieldEl`, or remove the attribute all together.
382 * @private
383 */
384 updateFieldAttribute: function(attribute, newValue) {
385 var input = this.input;
386
387 if (!Ext.isEmpty(newValue, true)) {
388 input.dom.setAttribute(attribute, newValue);
389 } else {
390 input.dom.removeAttribute(attribute);
391 }
392 },
393
394 /**
395 * Updates the {@link #cls} configuration.
396 */
397 updateCls: function(newCls, oldCls) {
398 this.input.addCls(Ext.baseCSSPrefix + 'input-el');
399 this.input.replaceCls(oldCls, newCls);
400 },
401
402 /**
403 * Updates the type attribute with the {@link #type} configuration.
404 * @private
405 */
406 updateType: function(newType, oldType) {
407 var prefix = Ext.baseCSSPrefix + 'input-';
408
409 this.input.replaceCls(prefix + oldType, prefix + newType);
410 this.updateFieldAttribute('type', newType);
411 },
412
413 /**
414 * Updates the name attribute with the {@link #name} configuration.
415 * @private
416 */
417 updateName: function(newName) {
418 this.updateFieldAttribute('name', newName);
419 },
420
421 /**
422 * Returns the field data value.
423 * @return {Mixed} value The field value.
424 */
425 getValue: function() {
426 var input = this.input;
427
428 if (input) {
429 this._value = input.dom.value;
430 }
431
432 return this._value;
433 },
434
435 // @private
436 applyValue: function(value) {
437 return (Ext.isEmpty(value)) ? '' : value;
438 },
439
440 /**
441 * Updates the {@link #value} configuration.
442 * @private
443 */
444 updateValue: function(newValue) {
445 var input = this.input;
446
447 if (input) {
448 input.dom.value = newValue;
449 }
450 },
451
452 setValue: function(newValue) {
453 var oldValue = this._value;
454
455 this.updateValue(this.applyValue(newValue));
456
457 newValue = this.getValue();
458
459 if (String(newValue) != String(oldValue) && this.initialized) {
460 this.onChange(this, newValue, oldValue);
461 }
462
463 return this;
464 },
465
466 //<debug>
467 // @private
468 applyTabIndex: function(tabIndex) {
469 if (tabIndex !== null && typeof tabIndex != 'number') {
470 throw new Error("Ext.field.Field: [applyTabIndex] trying to pass a value which is not a number");
471 }
472 return tabIndex;
473 },
474 //</debug>
475
476 /**
477 * Updates the tabIndex attribute with the {@link #tabIndex} configuration
478 * @private
479 */
480 updateTabIndex: function(newTabIndex) {
481 this.updateFieldAttribute('tabIndex', newTabIndex);
482 },
483
484 // @private
485 testAutoFn: function(value) {
486 return [true, 'on'].indexOf(value) !== -1;
487 },
488
489 //<debug>
490 applyMaxLength: function(maxLength) {
491 if (maxLength !== null && typeof maxLength != 'number') {
492 throw new Error("Ext.field.Text: [applyMaxLength] trying to pass a value which is not a number");
493 }
494 return maxLength;
495 },
496 //</debug>
497
498 /**
499 * Updates the `maxlength` attribute with the {@link #maxLength} configuration.
500 * @private
501 */
502 updateMaxLength: function(newMaxLength) {
503 if (!this.useManualMaxLength()) {
504 this.updateFieldAttribute('maxlength', newMaxLength);
505 }
506 },
507
508 /**
509 * Updates the `placeholder` attribute with the {@link #placeHolder} configuration.
510 * @private
511 */
512 updatePlaceHolder: function(newPlaceHolder) {
513 this.updateFieldAttribute('placeholder', newPlaceHolder);
514 },
515
516 // @private
517 applyAutoComplete: function(autoComplete) {
518 return this.testAutoFn(autoComplete);
519 },
520
521 /**
522 * Updates the `autocomplete` attribute with the {@link #autoComplete} configuration.
523 * @private
524 */
525 updateAutoComplete: function(newAutoComplete) {
526 var value = newAutoComplete ? 'on' : 'off';
527 this.updateFieldAttribute('autocomplete', value);
528 },
529
530 // @private
531 applyAutoCapitalize: function(autoCapitalize) {
532 return this.testAutoFn(autoCapitalize);
533 },
534
535 /**
536 * Updates the `autocapitalize` attribute with the {@link #autoCapitalize} configuration.
537 * @private
538 */
539 updateAutoCapitalize: function(newAutoCapitalize) {
540 var value = newAutoCapitalize ? 'on' : 'off';
541 this.updateFieldAttribute('autocapitalize', value);
542 },
543
544 // @private
545 applyAutoCorrect: function(autoCorrect) {
546 return this.testAutoFn(autoCorrect);
547 },
548
549 /**
550 * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration.
551 * @private
552 */
553 updateAutoCorrect: function(newAutoCorrect) {
554 var value = newAutoCorrect ? 'on' : 'off';
555 this.updateFieldAttribute('autocorrect', value);
556 },
557
558 /**
559 * Updates the `min` attribute with the {@link #minValue} configuration.
560 * @private
561 */
562 updateMinValue: function(newMinValue) {
563 this.updateFieldAttribute('min', newMinValue);
564 },
565
566 /**
567 * Updates the `max` attribute with the {@link #maxValue} configuration.
568 * @private
569 */
570 updateMaxValue: function(newMaxValue) {
571 this.updateFieldAttribute('max', newMaxValue);
572 },
573
574 /**
575 * Updates the `step` attribute with the {@link #stepValue} configuration
576 * @private
577 */
578 updateStepValue: function(newStepValue) {
579 this.updateFieldAttribute('step', newStepValue);
580 },
581
582 // @private
583 checkedRe: /^(true|1|on)/i,
584
585 /**
586 * Returns the `checked` value of this field
587 * @return {Mixed} value The field value
588 */
589 getChecked: function() {
590 var el = this.input,
591 checked;
592
593 if (el) {
594 checked = el.dom.checked;
595 this._checked = checked;
596 }
597
598 return checked;
599 },
600
601 // @private
602 applyChecked: function(checked) {
603 return !!this.checkedRe.test(String(checked));
604 },
605
606 setChecked: function(newChecked) {
607 this.updateChecked(this.applyChecked(newChecked));
608 this._checked = newChecked;
609 },
610
611 /**
612 * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration
613 * @private
614 */
615 updateChecked: function(newChecked) {
616 this.input.dom.checked = newChecked;
617 },
618
619 /**
620 * Updates the `readonly` attribute with the {@link #readOnly} configuration
621 * @private
622 */
623 updateReadOnly: function(readOnly) {
624 this.updateFieldAttribute('readonly', readOnly ? true : null);
625 },
626
627 //<debug>
628 // @private
629 applyMaxRows: function(maxRows) {
630 if (maxRows !== null && typeof maxRows !== 'number') {
631 throw new Error("Ext.field.Input: [applyMaxRows] trying to pass a value which is not a number");
632 }
633
634 return maxRows;
635 },
636 //</debug>
637
638 updateMaxRows: function(newRows) {
639 this.updateFieldAttribute('rows', newRows);
640 },
641
642 doSetDisabled: function(disabled) {
643 this.callParent(arguments);
644
645 if (Ext.browser.is.Safari && !Ext.os.is.BlackBerry) {
646 this.input.dom.tabIndex = (disabled) ? -1 : 0;
647 }
648
649 this.input.dom.disabled = (Ext.browser.is.Safari && !Ext.os.is.BlackBerry) ? false : disabled;
650
651 if (!disabled) {
652 this.blur();
653 }
654 },
655
656 /**
657 * Returns `true` if the value of this Field has been changed from its original value.
658 * Will return `false` if the field is disabled or has not been rendered yet.
659 * @return {Boolean}
660 */
661 isDirty: function() {
662 if (this.getDisabled()) {
663 return false;
664 }
665
666 return String(this.getValue()) !== String(this.originalValue);
667 },
668
669 /**
670 * Resets the current field value to the original value.
671 */
672 reset: function() {
673 this.setValue(this.originalValue);
674 },
675
676 // @private
677 onInputTap: function(e) {
678 this.fireAction('inputtap', [this, e], 'doInputTap');
679 },
680
681 // @private
682 doInputTap: function(me, e) {
683 if (me.getDisabled()) {
684 return false;
685 }
686
687 // Fast focus switching
688 if (this.getFastFocus() && Ext.os.is.iOS) {
689 me.focus();
690 }
691 },
692
693 // @private
694 onMaskTap: function(e) {
695 this.fireAction('masktap', [this, e], 'doMaskTap');
696 },
697
698 // @private
699 doMaskTap: function(me, e) {
700 if (me.getDisabled()) {
701 return false;
702 }
703
704 me.focus();
705 },
706
707 // @private
708 showMask: function() {
709 if (this.getUseMask()) {
710 this.mask.setStyle('display', 'block');
711 }
712 },
713
714 // @private
715 hideMask: function() {
716 if (this.getUseMask()) {
717 this.mask.setStyle('display', 'none');
718 }
719 },
720
721 /**
722 * Attempts to set the field as the active input focus.
723 * @return {Ext.field.Input} this
724 */
725 focus: function() {
726 var me = this,
727 el = me.input;
728
729 if (el && el.dom.focus) {
730 el.dom.focus();
731 }
732 return me;
733 },
734
735 /**
736 * Attempts to forcefully blur input focus for the field.
737 * @return {Ext.field.Input} this
738 * @chainable
739 */
740 blur: function() {
741 var me = this,
742 el = this.input;
743
744 if (el && el.dom.blur) {
745 el.dom.blur();
746 }
747 return me;
748 },
749
750 /**
751 * Attempts to forcefully select all the contents of the input field.
752 * @return {Ext.field.Input} this
753 * @chainable
754 */
755 select: function() {
756 var me = this,
757 el = me.input;
758
759 if (el && el.dom.setSelectionRange) {
760 el.dom.setSelectionRange(0, 9999);
761 }
762 return me;
763 },
764
765 onFocus: function(e) {
766 this.fireAction('focus', [e], 'doFocus');
767 },
768
769 // @private
770 doFocus: function(e) {
771 var me = this;
772
773 me.hideMask();
774
775 if (!me.getIsFocused()) {
776 me.setStartValue(me.getValue());
777 }
778 me.setIsFocused(true);
779 },
780
781 onTouchStart: function(e) {
782 // This will prevent 300ms delayed focus from occurring on iOS
783 if(document.activeElement != e.target) {
784 e.preventDefault();
785 }
786 },
787
788 onBlur: function(e) {
789 this.fireAction('blur', [e], 'doBlur');
790 },
791
792 // @private
793 doBlur: function(e) {
794 var me = this,
795 value = me.getValue(),
796 startValue = me.getStartValue();
797
798 me.showMask();
799
800 me.setIsFocused(false);
801
802 if (String(value) != String(startValue)) {
803 me.onChange(me, value, startValue);
804 }
805
806 },
807
808 // @private
809 onClearIconTap: function(e) {
810 this.fireEvent('clearicontap', this, e);
811
812 //focus the field after cleartap happens, but only on android.
813 //this is to stop the keyboard from hiding. TOUCH-2064
814 if (Ext.os.is.Android) {
815 this.focus();
816 }
817 },
818
819 onClearIconPress: function() {
820 this.clearIcon.addCls(Ext.baseCSSPrefix + 'pressing');
821 },
822
823 onClearIconRelease: function() {
824 this.clearIcon.removeCls(Ext.baseCSSPrefix + 'pressing');
825 },
826
827 onClick: function(e) {
828 this.fireEvent('click', e);
829 },
830
831 onChange: function(me, value, startValue) {
832 if (this.useManualMaxLength()) {
833 this.trimValueToMaxLength();
834 }
835 this.fireEvent('change', me, value, startValue);
836 },
837
838 onPaste: function(e) {
839 if (this.useManualMaxLength()) {
840 this.trimValueToMaxLength();
841 }
842 this.fireEvent('paste', e);
843 },
844
845 onKeyUp: function(e) {
846 if (this.useManualMaxLength()) {
847 this.trimValueToMaxLength();
848 }
849 this.fireEvent('keyup', e);
850 },
851
852 onKeyDown: function() {
853 // tell the class to ignore the input event. this happens when we want to listen to the field change
854 // when the input autocompletes
855 this.ignoreInput = true;
856 },
857
858 onInput: function(e) {
859 var me = this;
860
861 // if we should ignore input, stop now.
862 if (me.ignoreInput) {
863 me.ignoreInput = false;
864 return;
865 }
866
867 // set a timeout for 10ms to check if we want to stop the input event.
868 // if not, then continue with the event (keyup)
869 setTimeout(function() {
870 if (!me.ignoreInput) {
871 me.fireEvent('keyup', e);
872 me.ignoreInput = false;
873 }
874 }, 10);
875 },
876
877 // Hack for IE10 mobile. Handle pressing 'enter' button and fire keyup event in this case.
878 onKeyPress: function(e) {
879 if(e.browserEvent.keyCode == 13){
880 this.fireEvent('keyup', e);
881 }
882 },
883
884 onMouseDown: function(e) {
885 this.fireEvent('mousedown', e);
886 },
887
888 trimValueToMaxLength: function() {
889 var maxLength = this.getMaxLength();
890 if (maxLength) {
891 var value = this.getValue();
892 if (value.length > this.getMaxLength()) {
893 this.setValue(value.slice(0, maxLength));
894 }
895 }
896 }
897 });