1. #1
    Ext User bobbicat71's Avatar
    Join Date
    Dec 2007
    Location
    Italy
    Posts
    20
    Vote Rating
    1
    bobbicat71 is on a distinguished road

      1  

    Default InputTextMask plugin for Textfield

    InputTextMask plugin for Textfield


    Hi all,
    here is a plugin for textfield that adds a mask input to the field.

    This is the latest version:
    Code:
    // $Id: InputTextMask.js 293638 2008-02-04 14:33:36Z UE014015 $
    
    Ext.namespace('Ext.ux.netbox');
    
    /**
     * InputTextMask script used for mask/regexp operations.
     * Mask Individual Character Usage:
     * 9 - designates only numeric values
     * L - designates only uppercase letter values
     * l - designates only lowercase letter values
     * A - designates only alphanumeric values
     * X - denotes that a custom client script regular expression is specified</li>
     * All other characters are assumed to be "special" characters used to mask the input component.
     * Example 1:
     * (999)999-9999 only numeric values can be entered where the the character
     * position value is 9. Parenthesis and dash are non-editable/mask characters.
     * Example 2:
     * 99L-ll-X[^A-C]X only numeric values for the first two characters,
     * uppercase values for the third character, lowercase letters for the
     * fifth/sixth characters, and the last character X[^A-C]X together counts
     * as the eighth character regular expression that would allow all characters
     * but "A", "B", and "C". Dashes outside the regular expression are non-editable/mask characters.
     * @constructor
     * @param (String) mask The InputTextMask
     * @param (boolean) clearWhenInvalid True to clear the mask when the field blurs and the text is invalid. Optional, default is true.
     */
    Ext.ux.netbox.InputTextMask = function(mask,clearWhenInvalid) {
    
        if(clearWhenInvalid === undefined)
    		this.clearWhenInvalid = true;
    	else
    		this.clearWhenInvalid = clearWhenInvalid;
        this.rawMask = mask;
        this.viewMask = '';
        this.maskArray = new Array();
        var mai = 0;
        var regexp = '';
        for(var i=0; i<mask.length; i++){
            if(regexp){
                if(regexp == 'X'){
                    regexp = '';
                }
                if(mask.charAt(i) == 'X'){
                    this.maskArray[mai] = regexp;
                    mai++;
                    regexp = '';
                } else {
                    regexp += mask.charAt(i);
                }
            } else if(mask.charAt(i) == 'X'){
                regexp += 'X';
                this.viewMask += '_';
            } else if(mask.charAt(i) == '9' || mask.charAt(i) == 'L' || mask.charAt(i) == 'l' || mask.charAt(i) == 'A') {
                this.viewMask += '_';
                this.maskArray[mai] = mask.charAt(i);
                mai++;
            } else {
                this.viewMask += mask.charAt(i);
                this.maskArray[mai] = RegExp.escape(mask.charAt(i));
                mai++;
            }
        }
    
        this.specialChars = this.viewMask.replace(/(L|l|9|A|_|X)/g,'');
    };
    
    Ext.ux.netbox.InputTextMask.prototype = {
    
        init : function(field) {
            this.field = field;
    
            if (field.rendered){
                this.assignEl();
            } else {
                field.on('render', this.assignEl, this);
            }
    
            field.on('blur',this.removeValueWhenInvalid, this);
            field.on('focus',this.processMaskFocus, this);
        },
    
        assignEl : function() {
            this.inputTextElement = this.field.getEl().dom;
            this.field.getEl().on('keypress', this.processKeyPress, this);
            this.field.getEl().on('keydown', this.processKeyDown, this);
            if(Ext.isSafari || Ext.isIE){
                this.field.getEl().on('paste',this.startTask,this);
                this.field.getEl().on('cut',this.startTask,this);
            }
            if(Ext.isGecko || Ext.isOpera){
                this.field.getEl().on('mousedown',this.setPreviousValue,this);
            }
            if(Ext.isGecko){
              this.field.getEl().on('input',this.onInput,this);
            }
            if(Ext.isOpera){
              this.field.getEl().on('input',this.onInputOpera,this);
            }
        },
        onInput : function(){
            this.startTask(false);
        },
        onInputOpera : function(){
          if(!this.prevValueOpera){
            this.startTask(false);
          }else{
            this.manageBackspaceAndDeleteOpera();
          }
        },
        
        manageBackspaceAndDeleteOpera: function(){
          this.inputTextElement.value=this.prevValueOpera.cursorPos.previousValue;
          this.manageTheText(this.prevValueOpera.keycode,this.prevValueOpera.cursorPos);
          this.prevValueOpera=null;
        },
    
        setPreviousValue : function(event){
            this.oldCursorPos=this.getCursorPosition();
        },
    
        getValidatedKey : function(keycode, cursorPosition) {
            var maskKey = this.maskArray[cursorPosition.start];
            if(maskKey == '9'){
                return keycode.pressedKey.match(/[0-9]/);
            } else if(maskKey == 'L'){
                return (keycode.pressedKey.match(/[A-Za-z]/))? keycode.pressedKey.toUpperCase(): null;
            } else if(maskKey == 'l'){
                return (keycode.pressedKey.match(/[A-Za-z]/))? keycode.pressedKey.toLowerCase(): null;
            } else if(maskKey == 'A'){
                return keycode.pressedKey.match(/[A-Za-z0-9]/);
            } else if(maskKey){
                return (keycode.pressedKey.match(new RegExp(maskKey)));
            }
            return(null);
        },
    
        removeValueWhenInvalid : function() {
            if(this.clearWhenInvalid && this.inputTextElement.value.indexOf('_') > -1){
                this.inputTextElement.value = '';
            }
        },
    
        managePaste : function() {
            if(this.oldCursorPos==null){
              return;
            }
            var valuePasted=this.inputTextElement.value.substring(this.oldCursorPos.start,this.inputTextElement.value.length-(this.oldCursorPos.previousValue.length-this.oldCursorPos.end));
            if(this.oldCursorPos.start<this.oldCursorPos.end){//there is selection...
              this.oldCursorPos.previousValue=
                this.oldCursorPos.previousValue.substring(0,this.oldCursorPos.start)+
                this.viewMask.substring(this.oldCursorPos.start,this.oldCursorPos.end)+
                this.oldCursorPos.previousValue.substring(this.oldCursorPos.end,this.oldCursorPos.previousValue.length);
              valuePasted=valuePasted.substr(0,this.oldCursorPos.end-this.oldCursorPos.start);
            }
            this.inputTextElement.value=this.oldCursorPos.previousValue;
            keycode={unicode :'',
            isShiftPressed: false,
            isTab: false,
            isBackspace: false,
            isLeftOrRightArrow: false,
            isDelete: false,
            pressedKey : ''
            }
            var charOk=false;
            for(var i=0;i<valuePasted.length;i++){
                keycode.pressedKey=valuePasted.substr(i,1);
                keycode.unicode=valuePasted.charCodeAt(i);
                this.oldCursorPos=this.skipMaskCharacters(keycode,this.oldCursorPos);
                if(this.oldCursorPos===false){
                    break;
                }
                if(this.injectValue(keycode,this.oldCursorPos)){
                    charOk=true;
                    this.moveCursorToPosition(keycode, this.oldCursorPos);
                    this.oldCursorPos.previousValue=this.inputTextElement.value;
                    this.oldCursorPos.start=this.oldCursorPos.start+1;
                }
            }
            if(!charOk && this.oldCursorPos!==false){
                this.moveCursorToPosition(null, this.oldCursorPos);
            }
            this.oldCursorPos=null;
        },
    
        processKeyDown : function(e){
            this.processMaskFormatting(e,'keydown');
        },
    
        processKeyPress : function(e){
            this.processMaskFormatting(e,'keypress');
        },
    
        startTask : function(setOldCursor){
            if(this.task==undefined){
                this.task=new Ext.util.DelayedTask(this.managePaste,this);
          }
            if(setOldCursor!== false){
                this.oldCursorPos=this.getCursorPosition();
          }
          this.task.delay(0);
        },
    
        skipMaskCharacters : function(keycode, cursorPos){
            if(cursorPos.start!=cursorPos.end && (keycode.isDelete || keycode.isBackspace))
                return(cursorPos);
            while(this.specialChars.match(RegExp.escape(this.viewMask.charAt(((keycode.isBackspace)? cursorPos.start-1: cursorPos.start))))){
                if(keycode.isBackspace) {
                    cursorPos.dec();
                } else {
                    cursorPos.inc();
                }
                if(cursorPos.start >= cursorPos.previousValue.length || cursorPos.start < 0){
                    return false;
                }
            }
            return(cursorPos);
        },
    
        isManagedByKeyDown : function(keycode){
            if(keycode.isDelete || keycode.isBackspace){
                return(true);
            }
            return(false);
        },
    
        processMaskFormatting : function(e, type) {
            this.oldCursorPos=null;
            var cursorPos = this.getCursorPosition();
            var keycode = this.getKeyCode(e, type);
            if(keycode.unicode==0){//?? sometimes on Safari
                return;
            }
            if((keycode.unicode==67 || keycode.unicode==99) && e.ctrlKey){//Ctrl+c, let's the browser manage it!
                return;
            }
            if((keycode.unicode==88 || keycode.unicode==120) && e.ctrlKey){//Ctrl+x, manage paste
                this.startTask();
                return;
            }
            if((keycode.unicode==86 || keycode.unicode==118) && e.ctrlKey){//Ctrl+v, manage paste....
                this.startTask();
                return;
            }
            if((keycode.isBackspace || keycode.isDelete) && Ext.isOpera){
              this.prevValueOpera={cursorPos: cursorPos, keycode: keycode};
              return;
            }
            if(type=='keydown' && !this.isManagedByKeyDown(keycode)){
                return true;
            }
            if(type=='keypress' && this.isManagedByKeyDown(keycode)){
                return true;
            }
            if(this.handleEventBubble(e, keycode, type)){
                return true;
            }
            return(this.manageTheText(keycode, cursorPos));
        },
        
        manageTheText: function(keycode, cursorPos){
          if(this.inputTextElement.value.length === 0){
              this.inputTextElement.value = this.viewMask;
          }
          cursorPos=this.skipMaskCharacters(keycode, cursorPos);
          if(cursorPos===false){
              return false;
          }
          if(this.injectValue(keycode, cursorPos)){
              this.moveCursorToPosition(keycode, cursorPos);
          }
          return(false);
        },
    
        processMaskFocus : function(){
            if(this.inputTextElement.value.length == 0){
                var cursorPos = this.getCursorPosition();
                this.inputTextElement.value = this.viewMask;
                this.moveCursorToPosition(null, cursorPos);
            }
        },
    
        isManagedByBrowser : function(keyEvent, keycode, type){
            if(((type=='keypress' && keyEvent.charCode===0) ||
                type=='keydown') && (keycode.unicode==Ext.EventObject.TAB ||
                keycode.unicode==Ext.EventObject.RETURN ||
                keycode.unicode==Ext.EventObject.ENTER ||
                keycode.unicode==Ext.EventObject.SHIFT ||
                keycode.unicode==Ext.EventObject.CONTROL ||
                keycode.unicode==Ext.EventObject.ESC ||
                keycode.unicode==Ext.EventObject.PAGEUP ||
                keycode.unicode==Ext.EventObject.PAGEDOWN ||
                keycode.unicode==Ext.EventObject.END ||
                keycode.unicode==Ext.EventObject.HOME ||
                keycode.unicode==Ext.EventObject.LEFT ||
                keycode.unicode==Ext.EventObject.UP ||
                keycode.unicode==Ext.EventObject.RIGHT ||
                keycode.unicode==Ext.EventObject.DOWN)){
                    return(true);
            }
            return(false);
        },
    
        handleEventBubble : function(keyEvent, keycode, type) {
            try {
                if(keycode && this.isManagedByBrowser(keyEvent, keycode, type)){
                    return true;
                }
                keyEvent.stopEvent();
                return false;
            } catch(e) {
                alert(e.message);
            }
        },
    
        getCursorPosition : function() {
            var s, e, r;
            if(this.inputTextElement.createTextRange){
                r = document.selection.createRange().duplicate();
                r.moveEnd('character', this.inputTextElement.value.length);
                if(r.text === ''){
                    s = this.inputTextElement.value.length;
                } else {
                    s = this.inputTextElement.value.lastIndexOf(r.text);
                }
                r = document.selection.createRange().duplicate();
                r.moveStart('character', -this.inputTextElement.value.length);
                e = r.text.length;
            } else {
                s = this.inputTextElement.selectionStart;
                e = this.inputTextElement.selectionEnd;
            }
            return this.CursorPosition(s, e, r, this.inputTextElement.value);
        },
    
        moveCursorToPosition : function(keycode, cursorPosition) {
            var p = (!keycode || (keycode && keycode.isBackspace ))? cursorPosition.start: cursorPosition.start + 1;
            if(this.inputTextElement.createTextRange){
                cursorPosition.range.move('character', p);
                cursorPosition.range.select();
            } else {
                this.inputTextElement.selectionStart = p;
                this.inputTextElement.selectionEnd = p;
            }
        },
    
        injectValue : function(keycode, cursorPosition) {
            if (!keycode.isDelete && keycode.unicode == cursorPosition.previousValue.charCodeAt(cursorPosition.start))
                return true;
            var key;
            if(!keycode.isDelete && !keycode.isBackspace){
                key=this.getValidatedKey(keycode, cursorPosition);
            } else {
                if(cursorPosition.start == cursorPosition.end){
                    key='_';
                    if(keycode.isBackspace){
                        cursorPosition.dec();
                    }
                } else {
                    key=this.viewMask.substring(cursorPosition.start,cursorPosition.end);
                }
            }
            if(key){
                this.inputTextElement.value = cursorPosition.previousValue.substring(0,cursorPosition.start)
                    + key +
                    cursorPosition.previousValue.substring(cursorPosition.start + key.length,cursorPosition.previousValue.length);
                return true;
            }
            return false;
        },
    
        getKeyCode : function(onKeyDownEvent, type) {
            var keycode = {};
            keycode.unicode = onKeyDownEvent.getKey();
            keycode.isShiftPressed = onKeyDownEvent.shiftKey;
            
            keycode.isDelete = ((onKeyDownEvent.getKey() == Ext.EventObject.DELETE && type=='keydown') || ( type=='keypress' && onKeyDownEvent.charCode===0 && onKeyDownEvent.keyCode == Ext.EventObject.DELETE))? true: false;
            keycode.isTab = (onKeyDownEvent.getKey() == Ext.EventObject.TAB)? true: false;
            keycode.isBackspace = (onKeyDownEvent.getKey() == Ext.EventObject.BACKSPACE)? true: false;
            keycode.isLeftOrRightArrow = (onKeyDownEvent.getKey() == Ext.EventObject.LEFT || onKeyDownEvent.getKey() == Ext.EventObject.RIGHT)? true: false;
            keycode.pressedKey = String.fromCharCode(keycode.unicode);
            return(keycode);
        },
    
        CursorPosition : function(start, end, range, previousValue) {
            var cursorPosition = {};
            cursorPosition.start = isNaN(start)? 0: start;
            cursorPosition.end = isNaN(end)? 0: end;
            cursorPosition.range = range;
            cursorPosition.previousValue = previousValue;
            cursorPosition.inc = function(){cursorPosition.start++;cursorPosition.end++;};
            cursorPosition.dec = function(){cursorPosition.start--;cursorPosition.end--;};
            return(cursorPosition);
        }
    };
    
    Ext.applyIf(RegExp, {
      escape : function(str) {
        return new String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
      }
    });
    
    Ext.ux.InputTextMask=Ext.ux.netbox.InputTextMask;
    And here is an example of the usage:
    Code:
    var dateTimeField = new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)]});
    Enjoy!
    Last edited by bobbicat71; 4 Feb 2008 at 6:43 AM. Reason: latest version, opera support

  2. #2
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    I'm going to test it right now

  3. #3
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    It didn't work for me. I've tried on IE 6 and firefox 2.
    Here is how i used:
    Code:
     items: [{
                        xtype:'textfield',
                        fieldLabel: 'Name',
                        name: 'name',
    		    plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)],
                        anchor:'95%'
                    }, {...}
    Attached Images

  4. #4
    Ext User dandfra's Avatar
    Join Date
    Jun 2007
    Location
    Trento, Italy
    Posts
    122
    Vote Rating
    0
    dandfra is on a distinguished road

      0  

    Default


    It worked for me (IE6).
    Here's the code:
    PHP Code:
    <html>
    <
    head>
        <
    meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
        <
    title>Demo</title>
        <
    link rel="stylesheet" type="text/css" href="../../ext-2.0/resources/css/ext-all.css" />
        <
    script type="text/javascript" src="../../ext-2.0/adapter/ext/ext-base.js"></script>
        <script type="text/javascript" src="../../ext-2.0/ext-all-debug.js"></script>
        <script type="text/javascript" src="../src/InputTextMask.js"></script>
        <script>
    Ext.onReady(function(){
      var simple = new Ext.FormPanel({
            labelWidth: 75, // label settings here cascade unless overridden
            url:'save-form.php',
            frame:true,
            title: 'Simple Form',
            bodyStyle:'padding:5px 5px 0',
            width: 350,
            defaults: {width: 230},
            items: [{
                    fieldLabel: 'Date',
                    name: 'first',
                    xtype:'textfield',
                    plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)]
                }]
        });

        simple.render(document.body);
    });
    </script>
    </head>
    <body>
    </body>
    </html> 

  5. #5
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    It doesn't work when keyCode.unicode are between 97 and 120 (numeric keyboard)

  6. #6
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    I've changed processMaskFormatting to:
    Code:
     processMaskFormatting : function(e,type) {
            var keyCode = this.getKeyCode(e);
            if((type=='keydown' || type=='keypress') && ((keyCode.unicode >= 65 && keyCode.unicode <= 90) || (keyCode.unicode >= 97 && keyCode.unicode <= 122))){
                return true;
            }
    
            if(this.handleEventBubble(e, keyCode)){
                return true;
            }
            if(this.inputTextElement.value.length === 0){
                this.inputTextElement.value = this.viewMask;
            }
            var cursorPos = this.getCursorPosition();
            if(cursorPos.end == cursorPos.previousValue.length && !keyCode.isBackspace){
                return false;
            }
    
            while(this.specialChars.match(RegExp.escape(cursorPos.previousValue.charAt(((keyCode.isBackspace)? cursorPos.start-1: cursorPos.start))))){
                if(keyCode.isBackspace) {
                    cursorPos.decStart();
                } else {
                    cursorPos.incStart();
                }
                if(cursorPos.start >= cursorPos.previousValue.length || cursorPos.start < 0){
                    return false;
                }
            }
            if(keyCode.isBackspace){
                cursorPos.decStart();
            }
            if(this.injectValue(keyCode, cursorPos)){
                this.moveCursorToPosition(keyCode, cursorPos);
            }
            return false;
        },
    and It worked

  7. #7
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    As always, opera is the only browser that masks don't work

  8. #8
    Ext User
    Join Date
    Jul 2007
    Posts
    3,128
    Vote Rating
    1
    devnull is an unknown quantity at this point

      0  

    Default


    I had been working on this kind of functionality for Ext 1, but eventually gave up when I realized that it didnt allow for pasting into the field (and just about got sacked by my users because of it). I cant tell after taking a quick browse through your code, but does it handle pasting?

  9. #9
    Ext User bobbicat71's Avatar
    Join Date
    Dec 2007
    Location
    Italy
    Posts
    20
    Vote Rating
    1
    bobbicat71 is on a distinguished road

      0  

    Default


    Quote Originally Posted by krycek View Post
    It doesn't work when keyCode.unicode are between 97 and 120 (numeric keyboard)
    Thanks for the feedback. I have fixed my code to manage the numeric keypad.

  10. #10
    Ext User bobbicat71's Avatar
    Join Date
    Dec 2007
    Location
    Italy
    Posts
    20
    Vote Rating
    1
    bobbicat71 is on a distinguished road

      0  

    Default


    Quote Originally Posted by devnull View Post
    I had been working on this kind of functionality for Ext 1, but eventually gave up when I realized that it didnt allow for pasting into the field (and just about got sacked by my users because of it). I cant tell after taking a quick browse through your code, but does it handle pasting?

    Currently this feature is not implemented.
    I am looking for a solution to manage the copy paste.