PDA

View Full Version : InputTextMask plugin for Textfield



bobbicat71
17 Dec 2007, 7:24 AM
Hi all,
here is a plugin for textfield that adds a mask input to the field.

This is the latest version:


// $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:

var dateTimeField = new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)]});

Enjoy!

krycek
17 Dec 2007, 8:44 AM
I'm going to test it right now =P~

krycek
17 Dec 2007, 9:03 AM
It didn't work for me. I've tried on IE 6 and firefox 2.
Here is how i used:


items: [{
xtype:'textfield',
fieldLabel: 'Name',
name: 'name',
plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)],
anchor:'95%'
}, {...}

dandfra
17 Dec 2007, 9:58 AM
It worked for me (IE6).
Here's the 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>

krycek
17 Dec 2007, 10:27 AM
It doesn't work when keyCode.unicode are between 97 and 120 (numeric keyboard)

krycek
17 Dec 2007, 10:32 AM
I've changed processMaskFormatting to:


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

krycek
17 Dec 2007, 11:18 AM
As always, opera is the only browser that masks don't work :(

devnull
17 Dec 2007, 3:18 PM
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?

bobbicat71
18 Dec 2007, 1:14 AM
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.

bobbicat71
18 Dec 2007, 1:18 AM
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.

JeffHowden
18 Dec 2007, 2:42 AM
Currently this feature is not implemented.
I am looking for a solution to manage the copy paste.

There's onpaste for IE, Safari, and FF3 and oninput for FF2.

krycek
18 Dec 2007, 8:18 AM
Now the keypad works, but the backspace is not working properly.

dandfra
18 Dec 2007, 9:35 AM
Hi,
Roberto posted a new version of the code, with an initial paste (only Ctrl+V) support and the fixes to the various bugs.
Roberto will be away for Christmas, and so I'm posting here.
ISSUES

Pressing the Delete key doesn't work: work in progress here ;)
The menu on right click to paste doesn't work. We tried the onPaste event but it seems that it was never called (on('paste',fn) on the element, right?)
It doesn't work on Safari (on Windows at leat, but we don't have a MAC... The documentation says that a onBeforePaste event exists. On windows this event fires only when you right click on the field (even without pasting...). Is this a bug of Safari on windows or it works that way on MAc too?)


To end this post, I want to recognize http://www.mail-archive.com/commits@wicket.apache.org/msg02553.html since we grabbed the initial code from there...
Ciao

krycek
18 Dec 2007, 10:09 AM
Thank you very much, bobbicat71 and dandfra.

AdmiralKirk
19 Dec 2007, 6:09 AM
A masked input has been sorely missing from Ext 2.0, I'm very glad you saw fit to make one. I'm looking forward to see this completed. Keep up the good work!

dandfra
20 Dec 2007, 8:47 AM
Paste managed on IE, FF e Safari (on Windows, we don't have a MAC), both by keyboard and right clicking in the mouse.
Ctrl+C to copy now works
Ctrl+X is forbidden
Initial support to the Delete key

Here's the new version. When Roberto will be back the first post of the thread will be updated


/**
* 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
* 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.
*/

Ext.namespace('Ext.ux');

Ext.ux.InputTextMask = function(mask, clearWhenInvalid) {

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 = null;
} 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] = mask.charAt(i);
mai++;
}
}

this.specialChars = this.viewMask.replace(/(L|l|9|A|_|X)/g,'');
};

Ext.ux.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);
}
if(Ext.isGecko){
this.field.getEl().on('mousedown',this.setPreviousValue,this);
this.field.getEl().on('input',this.managePaste,this);
}
if(Ext.isSafari){
this.field.getEl().on('cut',this.stopEvent,this);
}
},
stopEvent: function(e){
e.stopEvent();
},

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 {
return (this.maskArray[cursorPosition.start].length > 1)? keyCode.pressedKey.match(new RegExp(maskKey)): null;
}
}
},

removeValueWhenInvalid : function() {
if(this.inputTextElement.value.indexOf('_') > -1){
this.inputTextElement.value = '';
}
},

managePaste : function() {
var valuePasted=this.inputTextElement.value.substring(this.oldCursorPos.start,this.inputTextElement.value.length-(this.oldCursorPos.previousValue.length-this.oldCursorPos.end));
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++){
if(this.oldCursorPos.start>=this.viewMask.length)
break;
keycode.pressedKey=valuePasted.substr(i,1);
keycode.unicode=valuePasted.charCodeAt(i);

if(this.viewMask.charAt(this.oldCursorPos.start)!=='_')
this.oldCursorPos.start=this.oldCursorPos.start+1;

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.moveCursorToPosition(null, this.oldCursorPos);
}
},

processKeyDown : function(e){
this.processMaskFormatting(e,'keydown');
},

processKeyPress : function(e){
this.processMaskFormatting(e,'keypress');
},

startTask: function(){
if(this.task==undefined){
this.task=new Ext.util.DelayedTask(this.managePaste,this);
}
this.oldCursorPos=this.getCursorPosition();
this.task.delay(0);
},

processMaskFormatting : function(e,type) {
var keyCode = this.getKeyCode(e);
var cursorPos = this.getCursorPosition();
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, forbid this one
e.stopEvent();
}
if((keyCode.unicode==86 || keyCode.unicode==118) && e.ctrlKey){//Ctrl+v, manage paste....
this.startTask();
return;
}
if(type=='keydown' && ((keyCode.unicode >= 48 && keyCode.unicode <= 57) ||
(keyCode.unicode >= 65 && keyCode.unicode <= 90) ||
(keyCode.unicode >= 97 && keyCode.unicode <= 122) )){
return true;
}
if(type=='keypress' && !((keyCode.unicode >= 48 && keyCode.unicode <= 57) ||
(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;
}
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;
},

processMaskFocus : function(){
if(this.inputTextElement.value.length == 0){
var cursorPos = this.getCursorPosition();
this.inputTextElement.value = this.viewMask;
this.moveCursorToPosition(null, cursorPos);
}
},

handleEventBubble : function(keyEvent, keyCode) {
try {
if(keyCode && (keyCode.isTab || keyCode.isLeftOrRightArrow)){
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 || keyCode.isDelete)))? 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.unicode==cursorPosition.previousValue.charCodeAt(cursorPosition.start))
return true;
var key;
if(keyCode.isBackspace || keyCode.isDelete){
key='_';
} else {
key=this.getValidatedKey(keyCode, cursorPosition);
}
if(key){
this.inputTextElement.value = cursorPosition.previousValue.substring(0,cursorPosition.start)
+ key +
cursorPosition.previousValue.substring(cursorPosition.start + 1,cursorPosition.previousValue.length);
return true;
}
return false;
},

getKeyCode : function(onKeyDownEvent) {
var keycode = {};
keycode.unicode =onKeyDownEvent.getKey();
keycode.isShiftPressed = onKeyDownEvent.shiftKey;
keycode.isDelete=(onKeyDownEvent.getKey() == Ext.EventObject.DELETE)? true: false;
keycode.isTab = (onKeyDownEvent.getCharCode() == Ext.EventObject.TAB)? true: false;
keycode.isBackspace = (onKeyDownEvent.getCharCode() == Ext.EventObject.BACKSPACE)? true: false;
keycode.isLeftOrRightArrow = (onKeyDownEvent.getCharCode() == Ext.EventObject.LEFT || onKeyDownEvent.getCharCode() == 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.incStart = function(){cursorPosition.start++;};
cursorPosition.decStart = function(){cursorPosition.start--;};
return(cursorPosition);
}

};

// Add escape prototype feature to RegExp object
// text.replace(/[.*+?^${}()|[\]\/\\]/g, '\\$0');
// text.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g, '\\$1');

if(!RegExp.escape) {
RegExp.escape = function(text){
var sp;
if(!arguments.callee.sRE){
sp=['/','.','*','+','?','|','(',')','[',']','{','}','\\'];
arguments.callee.sRE = new RegExp('(\\' + sp.join('|\\') + ')','g');
}
return text.replace(arguments.callee.sRE, '\\$1');
};
};

JeffHowden
20 Dec 2007, 9:20 AM
Once you get delete working when the field value has selection, you can then support CTRL+X as that's the equivalent of "copy to the clipboard" and "clear the contents of the field". In fact, considering the browser is capable of handling all that, I'm surprised CTRL+X can't just work.

galdaka
20 Dec 2007, 9:25 AM
Excellent addon!!

Thanks for share!!

dandfra
21 Dec 2007, 8:39 AM
I don't know bugs in this version (the famous last words ;)).
What changed:

Ctrl+x and cut management (thanks JeffHowden)
Better handling of selection with backspace and delete
Better handling of all the funtions that moves the cursore (home, End etc..)
Fixed a bug with Ctrl+v on FF
Corrected handling of non alpahabetic or numeric allowed character (for example # when the max is X.X)
Managed 2 mask characters one near the other
Tested with FF 2, IE 6 and Safari 3.0.4 (on Windows)

Is there a way to edit Roberto post at the top of the thread? Until I find a way, here's the new version of the code:


/**
* 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.
*/

Ext.namespace('Ext.ux');

Ext.ux.InputTextMask = function(mask, clearWhenInvalid) {

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 = null;
} 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.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){
this.field.getEl().on('mousedown',this.setPreviousValue,this);
this.field.getEl().on('input',this.onInput,this);
}
},
onInput: function(){
this.startTask(false);
},

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.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(cursorPos.previousValue.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.unicode==Ext.EventObject.BACKSPACE ||
keyCode.unicode==Ext.EventObject.DELETE){
return(true);
}
return(false);
},

processMaskFormatting : function(e,type) {
this.oldCursorPos=null;
var cursorPos = this.getCursorPosition();
var keyCode = this.getKeyCode(e,cursorPos);
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(type=='keydown' && !this.isManagedByKeyDown(keyCode)){
return true;
}
if(type=='keypress' && this.isManagedByKeyDown(keyCode)){
return true;
}
if(this.handleEventBubble(e, keyCode)){
return true;
}
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(keyCode){
if(!keyCode.isShiftPressed && (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 ||
keyCode.unicode==Ext.EventObject.F5)){
return(true);
}
return(false);
},

handleEventBubble : function(keyEvent, keyCode) {
try {
if(keyCode && this.isManagedByBrowser(keyCode)){
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.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) {
var keycode = {};
keycode.unicode =onKeyDownEvent.getKey();
keycode.isShiftPressed = onKeyDownEvent.shiftKey;
keycode.isDelete=(onKeyDownEvent.getKey() == Ext.EventObject.DELETE)? true: false;
keycode.isTab = (onKeyDownEvent.getCharCode() == Ext.EventObject.TAB)? true: false;
keycode.isBackspace = (onKeyDownEvent.getCharCode() == Ext.EventObject.BACKSPACE)? true: false;
keycode.isLeftOrRightArrow = (onKeyDownEvent.getCharCode() == Ext.EventObject.LEFT || onKeyDownEvent.getCharCode() == 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);
}

};

// Add escape prototype feature to RegExp object
// text.replace(/[.*+?^${}()|[\]\/\\]/g, '\\$0');
// text.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g, '\\$1');

if(!RegExp.escape) {
RegExp.escape = function(text){
var sp;
if(!arguments.callee.sRE){
sp=['/','.','*','+','?','|','(',')','[',']','{','}','\\'];
arguments.callee.sRE = new RegExp('(\\' + sp.join('|\\') + ')','g');
}
return text.replace(arguments.callee.sRE, '\\$1');
};
};

DigitalSkyline
3 Jan 2008, 7:20 PM
I know this is probably a dumb question, but how do I simply limit the number of characters in a field?

bobbicat71
4 Jan 2008, 8:15 AM
I know this is probably a dumb question, but how do I simply limit the number of characters in a field?

Thanks for the question that has revealed a bug in the code. Take the latest version in the first post and for a control over the number of characters you can do this:

Example 1 - 3 numeric values:
new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('999', true)]});

Example 2 - 4 alphanumeric values:
new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('AAAA', true)]});

Example 3 - 2 character (all kinds):
new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('X.XX.X', true)]});

DigitalSkyline
4 Jan 2008, 12:00 PM
Ok, but what if I want to limit to say 20/40/100 characters ... maybe there could be a config option for this. It would probably be easy to add so I may be posting again with an update.

other things -
In your (new) code, I think this is not used:
Ext.namespace('Ext.ux.netbox');


In my code, I'm using Ext's way to prototype (just thought I'd share I know there's no real difference):

// Add escape prototype feature to RegExp object
Ext.applyIf(RegExp, {
/**
* extend RegExp class with a escape method
* @return {String} An escaped string.
*/
escape : function(str) {
return new String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}
});

dandfra
7 Jan 2008, 6:18 AM
Ok, but what if I want to limit to say 20/40/100 characters ... maybe there could be a config option for this. It would probably be easy to add so I may be posting again with an update.
I'm not sure I'm following you....
Is this a problem of expressing a mask with less characters?
For example for a number of exactly 10 digits you now write:

new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('9999999999', true)]});

What do you want to write instead? I'm open for suggestions :-/

DigitalSkyline
7 Jan 2008, 2:09 PM
I decided this wasn't the best plug-in for that purpose ;)

The problem is that maxlength is not being set in the html by Ext, even thought there is a maxLength in Ext. Ext allows any number of characters regardless of this setting, the only thing Ext does is mark the field as invalid if the characters exceed the threshold.

The preferred behavior would be for no input beyond the maxLength.

Maybe a separate plug-in would be better, or some Ext override. Haven't had much time to explore this.

PS ... my preferred syntax would be :


new Ext.ux.InputTextMask({
mask:'9999999999',
clearInvalid:true
})

DigitalSkyline
7 Jan 2008, 3:11 PM
JIC: This config fixes my issue:



autoCreate : {tag: "input", type: "text", size: "20", maxlength:20, autocomplete: "off"}

wemakeitwork
17 Jan 2008, 3:01 AM
I encountered a bug with this mask : X[0|1]X9:99 X[a|p]Xm

I fixed this by resetting regexp to '', not null :



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 = ''; // <= RIGHT HERE !
} else {
regexp += mask.charAt(i);
}
}
...snip...
}

bobbicat71
18 Jan 2008, 1:39 AM
I encountered a bug with this mask : X[0|1]X9:99 X[a|p]Xm

I fixed this by resetting regexp to '', not null :



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 = ''; // <= RIGHT HERE !
} else {
regexp += mask.charAt(i);
}
}
...snip...
}



Thank you for reporting but the bug that you found is no more present in the code since 7 January 2008. Maybe your code was not updated to the latest version that you can find in the first post.

fother
21 Jan 2008, 4:59 PM
in opera 9.25 for windows xp if you use the "backspace" or "delete" for clear your input.. the mask fail..

fother
22 Jan 2008, 4:27 AM
works fine..

http://extjs.com/forum/showthread.php?t=23815

dandfra
22 Jan 2008, 5:46 AM
Yes, I know...
The problem is that we don't support Opera at the moment (we don't need it, we are a bit short with our roadmaps and with Opera some events (such the Backspace and the Delete) are not easyly stoppable...).
We will support Opera when we will find the time to look at the problem...
All the patches are welcome...

fother
22 Jan 2008, 7:31 AM
Yesterday evening I tried to tamper with the script .. Most received no positive result

dandfra
22 Jan 2008, 8:45 AM
Did you try the following strategy?

Listen on keypress. If the pressed key is delete or backspace simply store the actual text and the actual key pressed and the actual cursor position (I don't know if there is a way to distinguish from the '.' and the Delete key....)
listen on input. When this happens the text is already changed. Now you can guess what happened from the stored values. It Is similar to the way the paste is managed in FF.


PS: I tried the other script you posted but it doesn't manage paste (Ctrl+V doesn't work and if you right click on the field and select paste on the menu it pastes the text even if wrong)

Ciao

fother
23 Jan 2008, 4:21 AM
if(this.inputTextElement.value.length === 0){
this.inputTextElement.value = this.viewMask;
}

condition "===" represent???

i see this in the first post that have a maskinput

dandfra
23 Jan 2008, 4:27 AM
http://www.w3schools.com/js/js_comparisons.asp

fother
23 Jan 2008, 4:30 AM
I did not know that heheheh

=== - is exactly equal to (value and type)

x==5 is true
x==="5" is false

Spirit
30 Jan 2008, 4:32 AM
...but:

If i understood this plugin right, it is only possible to mask a field for a known length. So if I want to mask it for eMail or a telephone number which length isnt known exactly, it wont work. Right ?

My telephone number may look like:
(+49) 40684
(+49) 805-6568452

So i would be in need of wildcards, something like this:
(+99) 9*
(+99) 9*-9*
or a combination so both a accepted at the same time :)

It would also be nice if i could give a regular expression for the whole field and not only for a digit, so it would be possible to test for a valid email adress with a regexp like this:

^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$

Ok, as i m typing and thinking about it, for eMail Adresses masking input didnt seem to be the right way to do it. Maybe its easier to verify the input after its typed. But would be nice if user couldn

dandfra
30 Jan 2008, 9:04 AM
Is the following one the wanted behaviour?
Suppose I have a mask to enter an phone number
Now my phone number can be 0123/123456 or 01/12345678
So you write this mask (or something like this):
99X[0-9]*X/999999X[0-9]*X

In the field you initially see:
__/______

Now, if you want to enter 0123/123456, you type the 0 and 1 and you have:
01/______

Now, if you type 2 the mask become
012/______
Now, if you type 3 the mask become
0123/______
Now, if you type 0 the mask become
01230/______

To move after the "/" you have to move using the arrows key, or you have to type /
Correct?

The feature is not implemented, but I don't think it's too difficult to do (look at the injectValue method, and getValidatedKey method... ). I don't have time in this moment, but maybe I will look into this (I have the problem of phone numbers too, but not now...) If you need it now, I accept a patch :D.
PS: before doing something i will wait for feedback on the wanted behaviour.....

darkbob
30 Jan 2008, 4:23 PM
Nice plugin.
However I have found a bug on firefox with linux (works in windows) . If you delete characters in a textfield too fast (When you let your finger on the button, it works otherwise) the mask is also deleted which cause unwanted behavior.
You then need to blur the textfield and refocus so that the mask comes back.

I'll take a look at what could be the cause of it when I'll have some time.

Anybody else have this bug...? :-?

Spirit
31 Jan 2008, 3:15 AM
To move after the "/" you have to move using the arrows key, or you have to type /
Correct?
Sounds good ;) Sry that i have no patch for that. By now i m not so good in javascript :s

I tried to use your plugin with a combobox as "search field". It works well... but if i leave the combobox with a mouse click in another field the typed text vanishes ... Do i have to change something for Combo fields ? :D

I ll watch this thread maybe i can switch from my vtypes to this plugin at some point...
Nice work!
~o)~o)

bobbicat71
4 Feb 2008, 6:50 AM
add support to opera (tested with latest version)
fix clearWhenInvalid behaviour: now when the second parameter of constructor is false when the field loses focus and the text is invalid the field is not cleared.

dandfra
6 Feb 2008, 2:17 AM
I don't understand exactly what you are trying to do...
Can you please add a snippet of code?
Thanks

philrl9
26 Feb 2008, 7:01 AM
Has anyone implemented this code into an editable grid? It's working fine when editing a blank field. But, when I attempt to edit a field with data it's not behaving as expected (at least as I expect it to!).

For example in one field I have an existing phone number: 999-999-9999.

Here's my code for the field:


{
header: "Phone",
sortable: true,
dataIndex: 'PHONE',
editor: new Ext.form.TextField({
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('(999)999-9999', true)]
})
},


When I edit the field and try to change anything it's behaves inconsistently. If I erase all data in the field using the backspace key I can't type anything in the first position of the field. If I use the arrow key to get to a specific location and then delete and replace a character it works some of the time. It never adds the missing characters as defined in the mask ()-.

bobbicat71
3 Mar 2008, 12:12 AM
Has anyone implemented this code into an editable grid? It's working fine when editing a blank field. But, when I attempt to edit a field with data it's not behaving as expected (at least as I expect it to!).

When I edit the field and try to change anything it's behaves inconsistently. If I erase all data in the field using the backspace key I can't type anything in the first position of the field. If I use the arrow key to get to a specific location and then delete and replace a character it works some of the time. It never adds the missing characters as defined in the mask ()-.

Hello, could you tell me what browser are you using?

luv2hike
3 Mar 2008, 8:33 PM
Nice work, but I found a small bug. Using this:


new Ext.form.TextField({
fieldLabel:"SSN",
xtype:"textfield",
allowBlank:false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
}),

works fine except it allows me to enter decimal points (. character). I need it to only accept the digits 0-9.

philrl9
4 Mar 2008, 7:20 AM
I'm using FF 2 (.0.0.12)
Thanks.

dandfra
4 Mar 2008, 8:35 AM
...but maybe not...
I reproduced your problem (maybe). If I'm not wrong your mask is (999)999-9999 but your phone numbers have this format: 123-123-1234. So there is a mismatch between the mask and the format and all goes wrong. The right mask is probably 999-999-9999.
If this is not your problem, can you send me a whole example? I tried to reproduce your problem but it works for me (TM)

luv2hike
4 Mar 2008, 7:09 PM
I have not gotten this to work in the case where it is in a form that is lazily rendered. If I try and use it like:


{
fieldLabel: 'SSN #',
name: ssn,
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
}

I get this error upon loading the page:
this.field.getEl() has no properties on line 79 of InputTextMask.js.

If I remove the plugins option, the page loads and runs fine, but obviously without the mask for social security numbers. I use this same form in another app where the form is rendered and displayed upon the initial layout, and it works fine. It seems it is only when the form rendering is deferred as it is in a collapsed panel.

dandfra
5 Mar 2008, 12:24 AM
The line with the error should be 83 in the last version.
This is a bit strange since we are using it in lazy initialization... Look for example at http://cherryonext.googlecode.com/svn/demos/demo/inputTextMask.htm

Can you please verify if yours is the last version, and evantually send some more detail (ideally a whole example )

luv2hike
5 Mar 2008, 7:23 AM
The line with the error should be 83 in the last version.
This is a bit strange since we are using it in lazy initialization... Look for example at http://cherryonext.googlecode.com/svn/demos/demo/inputTextMask.htm

Can you please verify if yours is the last version, and evantually send some more detail (ideally a whole example )

Ok, here is the deal. First, it is line 79 in my version which I only downloaded yesterday. So I'm not sure how else to get the newest version. A version number in the comments at the top might be helpful. Line 79 reads:


this.inputTextElement = this.field.getEl().dom;

I am not able to post my entire source for the app as there are confidential portions, however I was making a set of static pages that duplicated the layout to be able to post and show the issue. But, the static test pages work. :s

So now I either go line by line between the test version and the real app to see what is different, or I continue to work on the test version until I get the problem.

Any suggestions or ideas as to what types of things can cause the error on this line so I know where to begin looking? Also, where is the official source for me to get the most current version? Thanks!

luv2hike
5 Mar 2008, 7:34 AM
UPDATE: I broke the static test page with the same error! :))
This is good because now I can post the code. It's also good because I know exactly what breaks it, but have no idea how to fix it.

The issue is trying to use the same form config in more than one place. I create 2 form objects with one config, and those objects appear in 2 different containers. Without the InputTextMask plugin all is well, the forms work in both places. With the plugin, if I uncomment the 2nd container I get the error. If I comment it out so only one instance of the form is in the layout, it works. See my comments in the code starting with the word NOTE for the exact spot.

The main page:


<html>
<head>
<meta HTTP-EQUIV="Pragma" CONTENT="no-cache"/>
<meta HTTP-EQUIV="Expires" CONTENT="-1"/>

<title>Testing ExtJS</title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"/>

<!-- LIBS -->
<script type="text/javascript" src="adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-all.js"></script>
<script type="text/javascript" src="TooltipFix.js"></script>
<script type="text/javascript" src="InputTextMask.js"></script>
<script type="text/javascript" src="complexform.js"></script>
<!-- ENDLIBS -->

<script type="text/javascript">
var eastPanel;
var eastCards;

Ext.onReady(function()
{
var workData = [
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
];
var workReader = new Ext.data.ArrayReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]);

var workgrid = new Ext.grid.GridPanel({
store: new Ext.data.Store({
data: workData,
reader: workReader
}),
columns: [
{header: 'Company', width: 120, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 90, sortable: true, dataIndex: 'price'},
{header: 'Change', width: 90, sortable: true, dataIndex: 'change'},
{header: '% Change', width: 90, sortable: true, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 120, sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'}
],
viewConfig: {
forceFit: false
},
renderTo: Ext.getBody(),
title: 'Tab Grid One',
tabTip: 'This is the first grid',
stripeRows: false,
frame: true
});
workgrid.getSelectionModel().selectFirstRow();

var myData = [
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
];
var myReader = new Ext.data.ArrayReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]);

var grid = new Ext.grid.GridPanel({
store: new Ext.data.Store({
data: myData,
reader: myReader
}),
columns: [
{header: 'Company', width: 120, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 90, sortable: true, dataIndex: 'price'},
{header: 'Change', width: 90, sortable: true, dataIndex: 'change'},
{header: '% Change', width: 90, sortable: true, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 120, sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'}
],
viewConfig: {
forceFit: false
},
renderTo: Ext.getBody(),
title: 'Tab Grid Two',
tabTip: 'This is the second grid',
stripeRows: false,
frame: true
});
grid.getSelectionModel().selectFirstRow();

var viewport = new Ext.Viewport(
{
layout:"border",
renderTo: Ext.getBody(),
items:[
{
id: 'north',
region:"north",
title:"Testing Form Layouts",
bodyStyle:"background-color:#bacfff",
height:50,
buttons: [
{
handler: function() {
eastPanel.expand();
eastCards.getLayout().setActiveItem("newform");
},
text: "Show Form",
tooltip: "Click to show form"
}
]
},
new Ext.Panel({
region:'center',
layout:'border',
items:[
new Ext.Panel({
region:"south",
bodyStyle:"background-color:#bacfff",
title:"Details",
height:80,
autoScroll:true,
collapsible:true,
split:true
}),
new Ext.TabPanel({
region:'center',
deferredRender:false,
activeTab:0,
items:[
workgrid,
grid
// NOTE: When I uncomment the following lines, I get the error <-----
/*,{
xtype: 'panel',
layout: 'fit',
title: "Form",
items: [
testForm
]
}*/
]
})
]
}),

// right-side card layout panel
eastPanel = new Ext.Panel(
{
region: "east",
layout: "border",
width: 400,
minWidth: 262,
split: true,
animCollapse: false,
collapsible: true,
collapseMode: 'mini',
collapsed: true,
hideCollapseTool: true,
items: [
eastCards = new Ext.Panel(
{
region: "center",
layout: "card",
activeItem: 'qresults',
width:400,
items: [
{
xtype: 'panel',
id: 'newform',
layout: 'fit',
title: "New",
items: [
testForm2
]
},
new Ext.Panel(
{
id: 'qresults',
title: "Query Results",
autoScroll: true,
items: [ Ext.get("queryDiv") ]
})
]
}),
{
region: "south",
xtype: "panel",
layout: "fit",
split: true,
animCollapse: false,
collapsible: true,
collapseMode: 'mini',
collapsed: true,
hideCollapseTool: true,
height: 200,
items: [ Ext.get("dummy") ]
}
]
}) // end main layout east pane
]/*,
// See comment below for initialFormFix
listeners: {
afterlayout: {
fn: initialFormFix,
single: true
}
}*/
});

// Init the singleton. Any tag-based quick tips will start working.
Ext.QuickTips.init();

// Apply a set of config properties to the singleton
Ext.apply(Ext.QuickTips.getQuickTip(), {
showDelay: 50,
dismissDelay: 0,
animCollapse: false,
trackMouse: true,
mouseOffset: [10, 10]
});
});//end onReady function

// Call fixFormSize for each form that uses it upon initial layout
// NOT USED in this version as it causes an error (since Form is not initially visible?)
function initialFormFix()
{
fixFormSize(testform);
// others would follow here if there is more than one form
}

// This makes the form buttons appear the way it is wanted.
function fixFormSize(theform)
{
theform.suspendEvents();

var formHeight = theform.getSize().height;
var ctHeight = theform.ownerCt.getInnerHeight();

if(formHeight >= ctHeight)
{
theform.autoHeight = false;
theform.setHeight(ctHeight);
}
else
{
theform.syncSize();
}

theform.autoHeight = true;
theform.resumeEvents();
}
</script>
</head>

<body>
<div id="queryDiv">Query results</div>
<div id="dummy">Hi.</div>
</body>
<meta HTTP-EQUIV="Pragma" CONTENT="no-cache"/>
<meta HTTP-EQUIV="Expires" CONTENT="-1"/>
</html>

complexform.js:


Ext.ux.DateSearch = Ext.extend(Ext.form.DateField, {
initComponent: Ext.form.TwinTriggerField.prototype.initComponent,
getTrigger: Ext.form.TwinTriggerField.prototype.getTrigger,
initTrigger: Ext.form.TwinTriggerField.prototype.initTrigger,
onTrigger1Click: Ext.form.DateField.prototype.onTriggerClick,
trigger1Class: Ext.form.DateField.prototype.triggerClass,
trigger2Class: 'x-form-search-trigger'
});

Ext.reg('datesearch', Ext.ux.DateSearch);

var formConfig = {
autoHeight:true,
autoScroll: true,
url: 'save.jsp',
method: 'POST',
onSubmit: Ext.emptyFn,
labelAlign: "right",
labelWidth: 120,
//defaults: { labelStyle: "font-weight:bold" },
defaultType: "textfield",
monitorValid: true,
items: [
{
fieldLabel: 'Last Name',
name: 'lastName',
allowBlank: false
},
{
fieldLabel: 'First Name',
name: 'firstName',
allowBlank: false
},
{
fieldLabel: 'Middle Name',
name: 'middleName',
allowBlank: true
},
{
fieldLabel: 'SSN #',
xtype: 'textfield',
name: 'ssn',
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
},
{
fieldLabel: 'Date of Birth',
xtype:'datesearch',
anchor:'70%',
name: 'dob',
allowBlank: false,
onTrigger2Click: function()
{
southPanel.expand();
southPanel.getLayout().setActiveItem("history");
historyGrid.setTitle("Date of Birth");
historyGrid.store.load(
{
params: { table: "ChangeLog", filter: "like 'dob%'" }
});
}
},
{
xtype: 'panel',
layout: 'column',
items: [
{
layout:'form',
items: [
{
fieldLabel: 'Gender',
name: 'gender',
xtype: 'radio',
boxLabel: 'Female',
inputValue: 'F',
allowBlank: false,
columnWidth: 0.5
}
]
},
{
layout:'form',
items: [
{
hideLabel:true,
labelSeparator: '',
name: 'gender',
xtype: 'radio',
boxLabel: 'Male',
inputValue: 'M',
allowBlank: false,
columnWidth: 0.5
}
]
}
]
},
{
xtype: 'panel',
layout: 'form',
autoHeight: true,
bodyStyle: "background:#bdc0d9; border:thin solid #AABBCC; margin-left:70px; margin-bottom:8px; width:185px",
labelWidth: 48,
items: [
{
fieldLabel: 'Race',
name: 'race',
xtype: 'radio',
boxLabel: 'White',
inputValue: 'W',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Black',
inputValue: 'B',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Hispanic',
inputValue: 'H',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Asian',
inputValue: 'A',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Native American',
inputValue: 'N',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Other',
inputValue: 'O',
allowBlank: true
}
]
},
{
fieldLabel: 'Address 1',
name: 'addr1',
allowBlank: true
},
{
fieldLabel: 'Address 2',
name: 'addr2',
allowBlank: true
},
{
fieldLabel: 'Country',
name: 'country',
allowBlank: true
},
{
fieldLabel: 'City',
name: 'city',
allowBlank: true
},
{
fieldLabel: 'State',
name: 'state',
allowBlank: true
},
{
fieldLabel: 'Zip Code',
name: 'zipcode',
allowBlank: true
},
{
fieldLabel: 'Primary Phone #',
name: 'phone1',
allowBlank: true
},
{
fieldLabel: 'Secondary Phone #',
name: 'phone2',
allowBlank: true
},
{
fieldLabel: 'E-mail',
name: 'email',
vtype: 'email',
allowBlank: true
}
],
buttons: [
{
text: 'Save',
tooltip: 'Click to save',
formBind: true,
handler: function()
{
this.getForm().submit(
{
success: function(f, action)
{
}
});
}
},
{
text: 'Cancel',
tooltip: 'Click to cancel',
handler: function()
{
}
}
]
};

// Create form object and resize handler
testForm = new Ext.form.FormPanel(formConfig);
testForm.on('resize', function()
{
fixFormSize(this);
});

// Create form object and resize handler
testForm2 = new Ext.form.FormPanel(formConfig);
testForm2.on('resize', function()
{
fixFormSize(this);
});

luv2hike
5 Mar 2008, 7:44 AM
I updated the code in my previous post to be a more exact match of the app layout giving me trouble. Basically the change was the 2nd instance of the form is a tab, not another card in the east panel. The first version I posted actually worked once I fixed the id in the card layout. So the updated version above now behaves exactly like the app. Comment out the form tab panel, it works. Uncomment it, I get the line 79 error.

luv2hike
5 Mar 2008, 8:00 AM
NEW INFO: I got around the line 79 error but not without bad side effects. It has something to do with the deferredRender so I get rid of the error and both forms "work" by commenting out the deferredRender option for the tab panel:


new Ext.TabPanel({
region:'center',
//deferredRender:false, // This fixes the line 79 error but has nasty sideeffects.
hideMode:'offsets',
activeTab:0,
items:[
workgrid,
grid
// NOTE: When I uncomment the following lines, I get the error <-----
,{
xtype: 'panel',
layout: 'fit',
title: "Form",
items: [
testForm
]
}
]
})

I've attached 4 screenshots to show the effects. Here is what to see in each:
1. Upon initial rendering, no error messages, but you can "see" the grid from the 2nd tab in the page toolbar area.
2. After clicking on the 2nd tab, the toolbar area is cleaned up and the grid displays fine.
3. The other bad side effect - the 2nd instance of the form that resides in the tab panel is missing the 2 nested panels containing the gender and race radio buttons.
4. Here is the 1st instance of the form in the east card panel showing how it should look as it displays correctly.

dandfra
5 Mar 2008, 8:50 AM
The problem is that an inputMask can be the plugin of only a textfield. It listens to some events of the textfield and manage the content of the textfield. You are using the same InputTextMask with 2 different text fields, and this is not supported...
Now, I have a suggestion on how to solve your problem, and it's the following:

define a function that create the config:
call the function to obtain the config object

In this way you generate 2 (equal but not same instance) config and all should work
Example (I didn't test this code, only a little example I built inspiring to your code):


var getFormConfig=function(){
var formConfig = {
autoHeight:true,
autoScroll: true,
url: 'save.jsp',
method: 'POST',
onSubmit: Ext.emptyFn,
labelAlign: "right",
labelWidth: 120,
//defaults: { labelStyle: "font-weight:bold" },
defaultType: "textfield",
monitorValid: true,
items: [
{
fieldLabel: 'Last Name',
name: 'lastName',
allowBlank: false
},
{
fieldLabel: 'First Name',
name: 'firstName',
allowBlank: false
},
{
fieldLabel: 'Middle Name',
name: 'middleName',
allowBlank: true
},
{
fieldLabel: 'SSN #',
xtype: 'textfield',
name: 'ssn',
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
},
{
fieldLabel: 'Date of Birth',
xtype:'datesearch',
anchor:'70%',
name: 'dob',
allowBlank: false,
onTrigger2Click: function()
{
southPanel.expand();
southPanel.getLayout().setActiveItem("history");
historyGrid.setTitle("Date of Birth");
historyGrid.store.load(
{
params: { table: "ChangeLog", filter: "like 'dob%'" }
});
}
},
{
xtype: 'panel',
layout: 'column',
items: [
{
layout:'form',
items: [
{
fieldLabel: 'Gender',
name: 'gender',
xtype: 'radio',
boxLabel: 'Female',
inputValue: 'F',
allowBlank: false,
columnWidth: 0.5
}
]
},
{
layout:'form',
items: [
{
hideLabel:true,
labelSeparator: '',
name: 'gender',
xtype: 'radio',
boxLabel: 'Male',
inputValue: 'M',
allowBlank: false,
columnWidth: 0.5
}
]
}
]
},
{
xtype: 'panel',
layout: 'form',
autoHeight: true,
bodyStyle: "background:#bdc0d9; border:thin solid #AABBCC; margin-left:70px; margin-bottom:8px; width:185px",
labelWidth: 48,
items: [
{
fieldLabel: 'Race',
name: 'race',
xtype: 'radio',
boxLabel: 'White',
inputValue: 'W',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Black',
inputValue: 'B',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Hispanic',
inputValue: 'H',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Asian',
inputValue: 'A',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Native American',
inputValue: 'N',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Other',
inputValue: 'O',
allowBlank: true
}
]
},
{
fieldLabel: 'Address 1',
name: 'addr1',
allowBlank: true
},
{
fieldLabel: 'Address 2',
name: 'addr2',
allowBlank: true
},
{
fieldLabel: 'Country',
name: 'country',
allowBlank: true
},
{
fieldLabel: 'City',
name: 'city',
allowBlank: true
},
{
fieldLabel: 'State',
name: 'state',
allowBlank: true
},
{
fieldLabel: 'Zip Code',
name: 'zipcode',
allowBlank: true
},
{
fieldLabel: 'Primary Phone #',
name: 'phone1',
allowBlank: true
},
{
fieldLabel: 'Secondary Phone #',
name: 'phone2',
allowBlank: true
},
{
fieldLabel: 'E-mail',
name: 'email',
vtype: 'email',
allowBlank: true
}
],
buttons: [
{
text: 'Save',
tooltip: 'Click to save',
formBind: true,
handler: function()
{
this.getForm().submit(
{
success: function(f, action)
{
}
});
}
},
{
text: 'Cancel',
tooltip: 'Click to cancel',
handler: function()
{
}
}
]
}
};
// Create form object and resize handler
testForm = new Ext.form.FormPanel(getFormConfig());
testForm.on('resize', function()
{
fixFormSize(this);
});

// Create form object and resize handler
testForm2 = new Ext.form.FormPanel(getFormConfig());
testForm2.on('resize', function()
{
fixFormSize(this);
});

luv2hike
5 Mar 2008, 9:08 AM
I did try this but it did not work. A different error and at least the grid tabs were fine, but the forms report errors. The thumbnail shows Firebug's console. The first error happened immediately upon rendering, the 2nd when I clicked the Show Form button, the 3rd when I clicked the Form tab. This was done with deferredRender:false back in the tab panel config options.

luv2hike
5 Mar 2008, 9:10 AM
Also, what is the difference between getting the config object from a function versus defining the config object, then creating 2 separate form objects with it like I was doing/still do? I thought this would already have created 2 separate text field objects since the config is just a definition and not the actual form yet. Am I mistaken?

dandfra
5 Mar 2008, 9:16 AM
With your code it creates 2 TextField, but there is only one InputTextMask to manage both.
As you see in my example, when the function is called it creates a new object
(var fromConfig={....} at the top of the function) and so you have 2 InputTextMask and 2 TextField, and so all should work.
Disabling lazyRendering but not duplicationg the InputTextMask will give you other problems (try to use the fields to see what I mean)
Ciao

luv2hike
5 Mar 2008, 9:39 AM
Ok, I think I have it working. I did not understand your last statement:

Disabling lazyRendering but not duplicationg the InputTextMask will give you other problems (try to use the fields to see what I mean)
but did find what was wrong with the sample you posted. The function getFormConfig needed to return the formConfig value. Once I added a return statement at its end, the errors went away. It appears now that both forms look fine (including the radio button panels) and are both usable. Plus, with deferredRender:false reinstated for the tab panel, the visual miscues are gone as well. See the screen shot for the results.

Here is the latest working code in case anyone else is following this that has similar issues:


<html>
<head>
<meta HTTP-EQUIV="Pragma" CONTENT="no-cache"/>
<meta HTTP-EQUIV="Expires" CONTENT="-1"/>

<title>Testing ExtJS</title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"/>

<!-- LIBS -->
<script type="text/javascript" src="adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-all.js"></script>
<script type="text/javascript" src="InputTextMask.js"></script>
<!-- ENDLIBS -->

<script type="text/javascript">
var eastPanel;
var eastCards;

Ext.onReady(function()
{
var workData = [
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
];
var workReader = new Ext.data.ArrayReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]);

var workgrid = new Ext.grid.GridPanel({
store: new Ext.data.Store({
data: workData,
reader: workReader
}),
columns: [
{header: 'Company', width: 120, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 90, sortable: true, dataIndex: 'price'},
{header: 'Change', width: 90, sortable: true, dataIndex: 'change'},
{header: '% Change', width: 90, sortable: true, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 120, sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'}
],
viewConfig: {
forceFit: false
},
renderTo: Ext.getBody(),
title: 'Tab Grid One',
tabTip: 'This is the first grid',
stripeRows: false,
frame: true
});
workgrid.getSelectionModel().selectFirstRow();

var myData = [
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
];
var myReader = new Ext.data.ArrayReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]);

var grid = new Ext.grid.GridPanel({
store: new Ext.data.Store({
data: myData,
reader: myReader
}),
columns: [
{header: 'Company', width: 120, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 90, sortable: true, dataIndex: 'price'},
{header: 'Change', width: 90, sortable: true, dataIndex: 'change'},
{header: '% Change', width: 90, sortable: true, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 120, sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'}
],
viewConfig: {
forceFit: false
},
renderTo: Ext.getBody(),
title: 'Tab Grid Two',
tabTip: 'This is the second grid',
stripeRows: false,
frame: true
});
grid.getSelectionModel().selectFirstRow();

// Begin form definition
Ext.ux.DateSearch = Ext.extend(Ext.form.DateField, {
initComponent: Ext.form.TwinTriggerField.prototype.initComponent,
getTrigger: Ext.form.TwinTriggerField.prototype.getTrigger,
initTrigger: Ext.form.TwinTriggerField.prototype.initTrigger,
onTrigger1Click: Ext.form.DateField.prototype.onTriggerClick,
trigger1Class: Ext.form.DateField.prototype.triggerClass,
trigger2Class: 'x-form-search-trigger'
});

Ext.reg('datesearch', Ext.ux.DateSearch);

var getFormConfig=function(){
var formConfig = {
autoHeight:true,
autoScroll: true,
url: 'save.jsp',
method: 'POST',
onSubmit: Ext.emptyFn,
labelAlign: "right",
labelWidth: 120,
defaultType: "textfield",
monitorValid: true,
items: [
{
fieldLabel: 'Last Name',
name: 'lastName',
allowBlank: false
},
{
fieldLabel: 'First Name',
name: 'firstName',
allowBlank: false
},
{
fieldLabel: 'Middle Name',
name: 'middleName',
allowBlank: true
},
{
fieldLabel: 'SSN #',
xtype: 'textfield',
name: 'ssn',
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
},
{
fieldLabel: 'Date of Birth',
xtype:'datesearch',
anchor:'70%',
name: 'dob',
allowBlank: false,
onTrigger2Click: function()
{
Ext.Msg.alert('Details','You clicked the 2nd trigger.');
}
},
{
xtype: 'panel',
layout: 'column',
items: [
{
layout:'form',
items: [
{
fieldLabel: 'Gender',
name: 'gender',
xtype: 'radio',
boxLabel: 'Female',
inputValue: 'F',
allowBlank: false,
columnWidth: 0.5
}
]
},
{
layout:'form',
items: [
{
hideLabel:true,
labelSeparator: '',
name: 'gender',
xtype: 'radio',
boxLabel: 'Male',
inputValue: 'M',
allowBlank: false,
columnWidth: 0.5
}
]
}
]
},
{
xtype: 'panel',
layout: 'form',
autoHeight: true,
bodyStyle: "background:#bdc0d9; border:thin solid #AABBCC; margin-left:70px; margin-bottom:8px; width:185px",
labelWidth: 48,
items: [
{
fieldLabel: 'Race',
name: 'race',
xtype: 'radio',
boxLabel: 'White',
inputValue: 'W',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Black',
inputValue: 'B',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Hispanic',
inputValue: 'H',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Asian',
inputValue: 'A',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Native American',
inputValue: 'N',
allowBlank: true
},
{
labelSeparator: '',
name: 'race',
xtype: 'radio',
boxLabel: 'Other',
inputValue: 'O',
allowBlank: true
}
]
},
{
fieldLabel: 'Address 1',
name: 'addr1',
allowBlank: true
},
{
fieldLabel: 'Address 2',
name: 'addr2',
allowBlank: true
},
{
fieldLabel: 'Country',
name: 'country',
allowBlank: true
},
{
fieldLabel: 'City',
name: 'city',
allowBlank: true
},
{
fieldLabel: 'State',
name: 'state',
allowBlank: true
},
{
fieldLabel: 'Zip Code',
name: 'zipcode',
allowBlank: true
},
{
fieldLabel: 'Primary Phone #',
name: 'phone1',
allowBlank: true
},
{
fieldLabel: 'Secondary Phone #',
name: 'phone2',
allowBlank: true
},
{
fieldLabel: 'E-mail',
name: 'email',
vtype: 'email',
allowBlank: true
}
],
buttons: [
{
text: 'Save',
tooltip: 'Click to save',
formBind: true,
handler: function()
{
this.getForm().submit(
{
success: function(f, action)
{
}
});
}
},
{
text: 'Cancel',
tooltip: 'Click to cancel',
handler: function()
{
}
}
]
}
return formConfig; // added this as it caused errors when missing
};
// end form definition

// Create form object and resize handler
testForm = new Ext.form.FormPanel(getFormConfig());
testForm.on('resize', function()
{
fixFormSize(this);
});

// Create form object and resize handler
testForm2 = new Ext.form.FormPanel(getFormConfig());
testForm2.on('resize', function()
{
fixFormSize(this);
});

var viewport = new Ext.Viewport(
{
layout:"border",
renderTo: Ext.getBody(),
items:[
{
id: 'north',
region:"north",
title:"Testing Form Layouts",
bodyStyle:"background-color:#bacfff",
height:50,
buttons: [
{
handler: function() {
eastPanel.expand();
eastCards.getLayout().setActiveItem("newform");
},
text: "Show Form",
tooltip: "Click to show form"
}
]
},
new Ext.Panel({
region:'center',
layout:'border',
items:[
new Ext.Panel({
region:"south",
bodyStyle:"background-color:#bacfff",
title:"Details",
height:80,
autoScroll:true,
collapsible:true,
split:true
}),
new Ext.TabPanel({
region:'center',
deferredRender: false,
activeTab:0,
items:[
workgrid,
grid,
{
xtype: 'panel',
layout: 'fit',
title: "Form",
items: [
testForm
]
}
]
})
]
}),

// right-side card layout panel
eastPanel = new Ext.Panel(
{
region: "east",
layout: "border",
width: 400,
minWidth: 262,
split: true,
animCollapse: false,
collapsible: true,
collapseMode: 'mini',
collapsed: true,
hideCollapseTool: true,
items: [
eastCards = new Ext.Panel(
{
region: "center",
layout: "card",
activeItem: 'qresults',
width:400,
items: [
{
xtype: 'panel',
id: 'newform',
layout: 'fit',
title: "New",
items: [
testForm2
]
},
new Ext.Panel(
{
id: 'qresults',
title: "Query Results",
autoScroll: true,
items: [ Ext.get("queryDiv") ]
})
]
}),
{
region: "south",
xtype: "panel",
layout: "fit",
split: true,
animCollapse: false,
collapsible: true,
collapseMode: 'mini',
collapsed: true,
hideCollapseTool: true,
height: 200,
items: [ Ext.get("dummy") ]
}
]
}) // end main layout east pane
]
});

// Init the singleton. Any tag-based quick tips will start working.
Ext.QuickTips.init();

// Apply a set of config properties to the singleton
Ext.apply(Ext.QuickTips.getQuickTip(), {
showDelay: 50,
dismissDelay: 0,
animCollapse: false,
trackMouse: true,
mouseOffset: [10, 10]
});
});//end onReady function

// This makes the form buttons appear the way it is wanted.
function fixFormSize(theform)
{
theform.suspendEvents();

var formHeight = theform.getSize().height;
var ctHeight = theform.ownerCt.getInnerHeight();

if(formHeight >= ctHeight)
{
theform.autoHeight = false;
theform.setHeight(ctHeight);
}
else
{
theform.syncSize();
}

theform.autoHeight = true;
theform.resumeEvents();
}
</script>
</head>

<body>
<div id="queryDiv">Query results</div>
<div id="dummy">Hi.</div>
</body>
<meta HTTP-EQUIV="Pragma" CONTENT="no-cache"/>
<meta HTTP-EQUIV="Expires" CONTENT="-1"/>
</html>

Thank you for your help!

luv2hike
5 Mar 2008, 7:34 PM
Nice work, but I found a small bug. Using this:


{
fieldLabel:"SSN",
xtype:"textfield",
allowBlank:false,
plugins: [new Ext.ux.InputTextMask('999-99-9999', false)]
},

works fine except it allows me to enter decimal points (. character). I need it to only accept the digits 0-9.

Any ideas on this? The mask is great but it accepts the period character. Am I using the wrong mask for only allowing the digits 0 thru 9?

Edit: I just ran some more tests after looking at the code in InputTextMask.js and discovered the above mask accepts the following characters to be typed:
0 1 2 3 4 5 6 7 8 9 _ . ' t
So for some reason which I do not understand, it allows the underscore, a period, the lowercase letter t, and an apostrophe to be entered in addition to the digits. The match function seems to work in a little test script I wrote, but it is never getting there in the plugin code.

dandfra
6 Mar 2008, 12:52 AM
We tested it on IE, FF, Opera and Safari (3) on Windows XP, and here it works. The problem is that we don't have any other platform
I see in your screenshot that the scrollbars are different from the standard Windows ones.
Which platform are you using?

luv2hike
6 Mar 2008, 6:10 AM
I'm sorry. I should've mentioned that in my original post. I run OS/X and Linux. I use Firefox 2.0.11 in both and see the same issue in both.

zaunaf
9 Mar 2008, 11:06 AM
Amazing plugin ! It would be great if this also supports currency. I've tried with mask like 9,999,999 gives strange results such as 2,050,0__. I've searched the net, and found this : http://www.fci.com.br/maskedit/MaskEdit/MaskEdit.aspx , it's good idea that the field inserts the value starting from right for currency/money type of field. I'd be glad to change the code myself but if anyone could point me where to begin with.. Thanks !!

dandfra
10 Mar 2008, 12:41 AM
Here I write a version of the function with comment:


injectValue : function(keycode, cursorPosition) {
//if it's not delete and the value typed is the same that there was before
if (!keycode.isDelete && keycode.unicode == cursorPosition.previousValue.charCodeAt(cursorPosition.start))
return true;
var key;
if(!keycode.isDelete && !keycode.isBackspace){
//if it's not delete or backspace, get the validated key. null means that it's not valid.
key=this.getValidatedKey(keycode, cursorPosition);
} else {
//backspace and delete management
if(cursorPosition.start == cursorPosition.end){
key='_';
if(keycode.isBackspace){
cursorPosition.dec();
}
} else {
key=this.viewMask.substring(cursorPosition.start,cursorPosition.end);
}
}
if(key){//if the typed value is valid, change the value in the input text. cursorPosition.start is the actual position of the cursor. Here you should probably do your work...
this.inputTextElement.value = cursorPosition.previousValue.substring(0,cursorPosition.start)
+ key +
cursorPosition.previousValue.substring(cursorPosition.start + key.length,cursorPosition.previousValue.length);
return true;
}
return false;
}

zaunaf
10 Mar 2008, 1:01 PM
Great !!
I'll have a look and hopefully soon post something useful here ;)..

Thanks!!

zaunaf
10 Mar 2008, 5:16 PM
This is the best I can come up so far (i simply replaced the code to ease trial & error):


...
if(key){
//zaunaf adds
var resultText = cursorPosition.previousValue.substring(0, cursorPosition.previousValue.length - cursorPosition.start - key.length) +
cursorPosition.previousValue.substring(cursorPosition.previousValue.length - cursorPosition.start, cursorPosition.previousValue.length) +
+ key;

var j=0;

for (i=0; i < cursorPosition.previousValue.length-1; i++) {
if (this.viewMask[i] == this.specialChars[j]) {
if (this.viewMask[i] != resultText[i]) {
resultArray = resultText.split("");
resultArray[i-1] = resultArray[i];
resultArray[i] = this.viewMask[i];
resultText = resultArray.join("");
}
j++;
}
}
this.inputTextElement.value = resultText;

//this.inputTextElement.value = cursorPosition.previousValue.substring(0,cursorPosition.start)
// + key +
// cursorPosition.previousValue.substring(cursorPosition.start + key.length,cursorPosition.previousValue.length);

return true;
}
return false;
...
I used this to call :


...
plugins: [new Ext.ux.InputTextMask('999.999.999.999',false)],
...
I preserve the code logic having the cursor position begins to input from left, but it seems that after the cursor "collide" with the number, the field refuses to accept further entry (I tried 1.200.000.000, but it stuck in ___.__1.200.000) . Backspace is also screwed up.

Is it possible to have right-to-left logic on this plugin? I've read trough the code, I'm afraid there'll be too much thing to adjust. IMHO the plugin is too left-to-right oriented.

dandfra
11 Mar 2008, 1:49 AM
Yes, the logic to move the cursor is a bit scattered.

some bits are in managePaste (for copy & paster management, here it moves to the left, look at the line this.oldCursorPos.start=this.oldCursorPos.start+1; I don't know if the logic should be reversed here
a little bit in injectValue to move the cursor back after a backspace,

if(keycode.isBackspace){
cursorPosition.dec();
}
something in skipMaskCharacters (to skip the mask character)

You have to change in this places to manage your case remembering that when you type the cursor normally moves to the right, and so you have probably to move it back to the left

zaunaf
11 Mar 2008, 8:20 AM
To avoid conflict with existing logic, I prefer to only re arrange output just before it was spit out, and then correct the output should any character collide with the skipCharacters.



some bits are in managePaste (for copy & paster management, here it moves to the left, look at the line this.oldCursorPos.start=this.oldCursorPos.start+1; I don't know if the logic should be reversed here

I don't believe this is a problem, because my code keeps inputting behaviour left to right. I've tried pasting, work nicely - only constrained by the collision :D



a little bit in injectValue to move the cursor back after a backspace,

if(keycode.isBackspace){
cursorPosition.dec();
}

Ok. I'll have a look to fix the backspace mess



something in skipMaskCharacters (to skip the mask character)You have to change in this places to manage your case remembering that when you type the cursor normally moves to the right, and so you have probably to move it back to the left


Yes, I've already seen this method. I don't alter anything here, because it isn't necessary -- yet. :D As long as the format is reversably equal (i.e 999.999, not 9.999.999). But if we also want to do this, we have to change something here, i.e read the mask backward.. Consequently we have to alter the whole logic :(..

The problem so far is the "collision". I suspect that the plugin reads the previous number, not the mask. So when it "collides" (i.e the cursor went to the position where the number already filled, no longer a mask "_"). I wonder where could this logic be placed..

Thanks for helping !

Regards, Z

gsans
17 Mar 2008, 7:22 AM
Hi there,
just to make the following issues public.

Paste feature. You can paste data into the masked field after selecting the whole empty mask. In this case mask is not being applied.

Special characters after selecting. When selecting the whole empty mask and typing any of the following characters, those are not filtered by the defined mask: ' (single quote), # (hash), t (letter t).

Cheers,
Gerard.

philrl9
25 Mar 2008, 7:43 AM
Can you please tell me where I can get the lastest version of the plugin?

Thanks!

carpmike
4 Apr 2008, 9:31 AM
I have data that is valid but doesn't use the entire mask. As an example, I set a mask of '(999) 999-9999 x99999' for a US phone number. It is valid for there to be no extension or a partial extension (less than 5 digits). When I call setValue on the TextField the entire mask is not applied to the field unless the data uses the entire mask. E.g.

(123) 456-7890 x12

shows as

(123) 456-7890 x12

but I expected

(123) 456-7890 x12___

In fact, I can't enter more text on the end of the string because the full mask isn't there.

I have hacked a solution for this (making use of the bulk of the managePaste method) but I'm curious whether the authors think this should be supported and if so what the cleanest solution would be.

Thanks,

mike

dandfra
6 Apr 2008, 11:20 PM
I see on the forum that there are a lot of different neds regarding a plugin that masks the input (right to left input for numbers, mask with variable length and so on). I think that the better solution is to separate the logic to manage the input, tfrom he logic to process the input, to let the developer to easylly add his implementation...
The problem is that I', very very busy ATM, and so I don't know when I will have time

Ciao

mrtwism
29 Apr 2008, 5:02 PM
Just curious if there is any way to have this work without the '_' values showing up in the textbox. I want to just limit the field to 6 characters and have it look and feel as if nothing was different. I'm currently using it with this config option:


plugins: [new Ext.ux.InputTextMask('999999', false)]

poetawd
9 Jun 2008, 4:01 PM
This script is awesome !!! I dont know why it isnt in the core ! Thanks !

wm003
9 Jun 2008, 10:40 PM
very nice. i wonder how we can simply create new vtypes out of this? (does it even make sense for vtypes anymore..perhaps a dump question ...;))

ray007
20 Jun 2008, 7:16 AM
Is it possible to use this to create a "quantity" input field with this?
By quantity input field I mean:
enter any number, be able to only enter one decimal point, and get it all nicely formatted with a thousand separator.

regards,
Ray

dandfra
20 Jun 2008, 7:55 AM
No, actually no.
I think that a good work could be to extract all the input management logic, insert int into some classes, and let the text management to the application....
But I don't have time ATM :(

geoffrey.mcgill
4 Sep 2008, 1:51 PM
Hi bobbicat71,

Brilliant work! One question: What license are you releasing this extension under? GPL 3.0 or one of the FLOSS compatible open-source options?

I searched through this thread and the source, but could not find any reference to a license. I apologize in advance if it's noted somewhere and I just missed.

dandfra
4 Sep 2008, 11:17 PM
It's released as part of Cherry On Ext (http://code.google.com/p/cherryonext/), and so is LGPL v3 or later.
Ciao

vanderbill
12 Sep 2008, 5:48 AM
when i hold delete or backspace the mask disconfigure...i need making any stuff???
the key '~' is not validate i think
the key '

vanderbill
12 Sep 2008, 8:52 AM
hello this key dont work too

tidalbobo
13 Sep 2008, 6:21 PM
Excellent work.
How ever javascript (which Ext is based on) already has some good regEx support.
See http://www.zytrax.com/tech/web/regex.htm for the syntaxes.

Rather than build a syntax of your own, why not build upon the existing, so that
1. It will be easy for those who have already know the javascript syntax and use of regEx.
2. You will be adding some EXTRA stuff.. Not figuring out tht the wheel has to be round!
3. It will be more comprehensive, as the javascript functions are mature by now, bug fixed ( i hope)

What i would suggest is to extend your grate work to provide with some plugin (possible) at the component level, tht can be connected to ANY UI widget. So even a password,textbox,textarea can use this facility.

Also some prebuilt testers for email ( xxx@xx.xx), address, po. box, phone number, country, timezone, age etc might be nice.

vanderbill
17 Sep 2008, 3:19 AM
hello guys.....the problem happen only in linux....in Windows its works :D:D:D:D

ty so much for the great work....cya!!!!

pokerking400
19 Sep 2008, 9:36 AM
Great plugin. It works fine but i have one problem.

Why i am getting _ in my text?. I thought cursor position is part of the mask. How do i remove

that?.

Thanks.

pokerking400
19 Sep 2008, 10:22 AM
How do i make this Mask to represent Email address?.

Can we have Wildcare between 0-n@0-n.AAA

Can someone help?.

thanks

dandfra
21 Sep 2008, 11:30 PM
It doesn't support variable-length mask, I'm sorry :(

charleshimmer
9 Oct 2008, 10:48 AM
Great plugin!!! Thanks a ton for sharing!

donowp
28 Oct 2008, 9:44 PM
its works.... thanks :)

dolzenko
17 Dec 2008, 10:39 AM
Hi!
I had troubles with cursor not moving and all Tab keystrokes being eaten under latest Opera. Not sure if this won't break things in other browsers but I figured I'd better share it anyway.

--- InputTextMask_bobbicat71.js Wed Dec 17 21:24:49 2008
+++ InputTextMask_bobbicat71_patched.js Wed Dec 17 20:23:02 2008
@@ -283 +283 @@
- if(((type=='keypress' && keyEvent.charCode===0) ||
+ if(((type=='keypress' && (keyEvent.charCode===0 || (Ext.isOpera && keyEvent.charCode==null)) ) ||
@@ -337 +337 @@
- if(this.inputTextElement.createTextRange){
+ if(this.inputTextElement.createTextRange && !Ext.isOpera){

bobbicat71
19 Dec 2008, 1:36 AM
Hi!
I had troubles with cursor not moving and all Tab keystrokes being eaten under latest Opera. Not sure if this won't break things in other browsers but I figured I'd better share it anyway.


Hi,
thanks for your contribution. I inserted the code in our repository.


Merry Christmas to all.

g13013
31 Dec 2008, 5:11 PM
I taken the liberty of changing the code to add support when the function setValue is called, I think that the bug that make the mask disappear will be fixed with that, because logically, if the events of keydown, setValue and paste (which are the only direct way to change the value) are well managed then there is no error possible with the validity of data.
i hope that will be usefull

//

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.override(Ext.form.TextField, {//override the TextField
setValue : function(v){
//do the same things that the original function but fire setval event
// very usefull to manage the value changes by setValue way
this.value = v;
this.fireEvent("setval", this, v);//fires the event
if(this.rendered){
this.el.dom.value = (v === null || v === undefined ? '' : v);
this.validate();
}
},
})


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);
//fired with the override of TextField with the setval event when SetValue() is called
field.on('setval',this.startSet, 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 = '';
}
},

startSet: function(){//function called when setValue to set "SetNew" to true before call startTask function
this.setNew=true;
this.startTask();
},

startTask : function(setOldCursor){//function called when Paste
if(this.task==undefined){
this.task=new Ext.util.DelayedTask(this.manageChange,this);
}
if(setOldCursor!== false){
this.oldCursorPos=this.getCursorPosition();
}
this.task.delay(0);
},

manageChange : function() {//manage change that occures with paste and setval events (old name managePaste)
if(this.oldCursorPos==null){
return;
}
if (this.setNew==true) {//if setValue
var newValue=this.inputTextElement.value; //the new value is set from the fild value
this.oldCursorPos.previousValue=this.viewMask;
} else {//if paste
var newValue=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);
newValue=newValue.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<newValue.length;i++){
keycode.pressedKey=newValue.substr(i,1);
keycode.unicode=newValue.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;
this.setNew=false;

},

processKeyDown : function(e){
this.processMaskFormatting(e,'keydown');
},

processKeyPress : function(e){
this.processMaskFormatting(e,'keypress');
},

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;

Tests :
Firefox 3 : Well:D
Google Chrome : Well:D
Internet Explorer 8 & 7 : Well:D
Opera 9.6 : not working !!!:((

reinaldoaf
27 Jan 2009, 7:41 AM
2 thinks...
in the code my GUI .NET VWD C# don´t know this "}," the , after } is wrong in my browser... whats hell my GUI don´t work?? someone know??

and

I try use this with the coolite code, but I don´t know the problem, every receive de messagem object requerid.

my code

first


<ext:ScriptContainer runat="server" / >
<script src="js/InputTextMask.js" type="text/javascript"></script>


in page i put this code


<ext:TextField ID="m_maxima" runat="server" FieldLabel="Metragem Máxima" Width="250" >
<Plugins>
<ux:InputTextMask Mask="9.999,99" />
</Plugins>
</ext:TextField>

don´t work,

i try this



<ext:TextField ID="m_maxima" runat="server" FieldLabel="Metragem Máxima" Width="250" >
<Plugins>

<ext:GenericPlugin Path="~/js/InputTextMask.js"
InstanceOf="Ext.ux.InputTextMask({mask:'999.999.999-99', clearInvalid:true})" />
</Plugins>
</ext:TextField>


but don´t work nothing :(

the Ext.ux.InputTextMask is correct name, I have the code of this page posted by g13013.
and this code http://www.desenvolvedores.net/reinaldo/files/InputTextMask.rar

tks to help and sorry my english :(

g13013
27 Jan 2009, 12:45 PM
seems to be the same code :-?, i used the code posted in this thread in my application and add the management of setValue() method, because i used this fonctionality to retrieve data from server and popolate fields with mask enabled

I'am not developper on C# but try to execute the code in the client Side
runat="server" because the mask needs to get the value that is written in the client side

reinaldoaf
27 Jan 2009, 2:48 PM
hehe, I make to work, but don´t is very simple, I need build de componente coolite.ext.ux, with the code of the source
http://code.google.com/p/coolite-ux-toolkit/source/browse/#svn/trunk/Coolite.Ext.UX/Plugins/InputTextMask

the coolite is very nice ext js for the .net

i now learning this componente

my post in cooliteforum

http://www.coolite.com/forums/Topic7650-4-1.aspx

now is solved my problem and create more others...

I need to format to de rigth to left...

but I have more big problems here, my boss don´t be happy...
auhahua th project is very very late.

thanks for attention

g13013
27 Jan 2009, 3:05 PM
:))

Rafael
11 Mar 2009, 12:03 PM
i have a doubt.
I need to change mask when onblur, how to make ?

Arno.Nyhm
3 Apr 2009, 4:53 AM
there are new contributions to this plugin? like the refactoring as dandfra suggested

how about this features:
- enable multi length
- right to left typing (for money fields)
- regex support

Arno.Nyhm
3 Apr 2009, 4:56 AM
I don't believe this is a problem, because my code keeps inputting behaviour left to right. I've tried pasting, work nicely - only constrained by the collision :D


can you post your full code of your solution (not just particular codesnippets)? thx

georgiosleon
21 Jun 2009, 4:18 AM
do you support Orera yet
Very nice plug in by the way

moegal
2 Jul 2009, 10:50 AM
Hi,

Nice extension. I am having a problem with allowBlank: false

The field shows as required from the start and then if I click in it and then move focus to another field it no longer shows the field as required.

Thanks, Marty

orionx7
1 Oct 2009, 8:58 PM
Grazie amico!

tBSTAR
23 Nov 2009, 11:05 AM
How do I use your ux to have an input box for price ie. $ 1,900.00?
Thanks

g13013
25 Nov 2009, 5:20 PM
i think that my extention is better solution for you
http://www.extjs.com/forum/showthread.php?t=63051

cpvaishya
27 Apr 2010, 8:44 PM
Hi,

I need to change the mask of an existing field based on value from database.
So, basically the same textfield can have different mask based on database.

Is this possible with this plugin?
I was able to apply a static mask to the textfield, but am not able to change it dynamically.

Regards,
Chintan

dandfra
27 Apr 2010, 11:00 PM
Not in the current state....

But I think it should be quite easy.... I don't know the result (all untested) but:
1) try copying this piece of code from the constructor to a method (setInputMask for example) with a parameter, mask:


this.rawMask = mask;
this.viewMask = '';
this.maskArray = [];
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,'');
2) in the constructor replace the code with a call to the setInputMask method
3) when you have to change the mask call the setInputMask method

I don't hnow what happen if the element has focus when you change the mask (if this can happen in your case and it doesn't work report here what happens)

Ciao

Lmouse
28 Apr 2010, 1:31 AM
I am going to test right now!
Thank you guy!

dfm_77
15 Jun 2010, 8:00 PM
anyone publish the code and sample for download with last version, thanks. and how to install it.

mario.fts
3 Aug 2010, 8:27 PM
When you load a form with data without the mask, the field mask breaks.

for example:

mask: 999.999.999-99

when the data come from server with the mask (306.928.258-00, for example) the mask works great. But when the data come without the mask (for ex: 30692825800) the mask breaks when you try to edit.

I am attaching some images to show what's going on.

as you can see, where there should be a dot,-after the 6- there is the field content -caracter 9 -.

I think that's because of the load does not fire any event to apply the mask.

Can anyone tell me where can I start to fix this behaviour? what events are fired when the form loads content on the field?

Regards

PS: sorry for my bad english. :D

dandfra
17 Aug 2010, 10:41 PM
Sorry for the long wait, I was on holiday.
When you load a form the setValue of the field is called. The plugin doesn't override this method, since it only manages user input. To gix your problem you have to override the setValue method of your filed to add the formatting before calling the setValue method of the parent class.
NB: all this is highly speculative and untested

trase
29 Oct 2010, 8:40 AM
Hello.

I have this problem with InputTextMask.

I have this code:

{header: 'Value:', sortable: false, dataIndex: 'value', width: 60,renderer: rendererValue,
editor: new fm.TextField({
allowBlank: true,
plugins: [new Ext.ux.InputTextMask({
mask:'99/99'
})]
})
},

When I write in the cell and press ENTER the value disappear :s, but in the store the value has change correctly.
For more information, in renderer function 'rendererValue' I do this. this function only change the colour of the cell.

var rendererValue = function (data, cellmd, record, rowIndex,colIndex, store) {

if (record.data.code != 'N'){
cellmd.css = 'editCell';
}
}

Anybody can help me?
Sorry for my English.
thanks.

htammen
14 Dec 2010, 4:36 AM
Is the following one the wanted behaviour?
Suppose I have a mask to enter an phone number
Now my phone number can be 0123/123456 or 01/12345678
So you write this mask (or something like this):
99X[0-9]*X/999999X[0-9]*X

In the field you initially see:
__/______

Now, if you want to enter 0123/123456, you type the 0 and 1 and you have:
01/______

Now, if you type 2 the mask become
012/______
Now, if you type 3 the mask become
0123/______
Now, if you type 0 the mask become
01230/______

To move after the "/" you have to move using the arrows key, or you have to type /
Correct?

The feature is not implemented, but I don't think it's too difficult to do (look at the injectValue method, and getValidatedKey method... ). I don't have time in this moment, but maybe I will look into this (I have the problem of phone numbers too, but not now...) If you need it now, I accept a patch :D.
PS: before doing something i will wait for feedback on the wanted behaviour.....

this does not work for me. I´m using the version from 2008-02-26. Is there a newer one? I haven't found.
Regular expressions work for single characters but not for repeating values. Means: X[0-9]X works buts X[0-9]*X or X[0-9]{3} don't not work. In the latter cases only one digit is accepted.

Regards Helmut

logicspeak
8 Aug 2011, 11:12 AM
Thanks so much for writing this! It will be incredibly useful to us. Quick question though...how do I create a mask that will accept either a 5 or a 5-4 digit zip code format?

As in:
30019 or 30019-1708? Our application has to accept both (or either) as valid formats and I can't figure out how to use the custom regex mask to make it work (if it is even possible).

Thanks!
Jason


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

This is the latest version:


// $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:

var dateTimeField = new Ext.form.TextField({plugins: [new Ext.ux.InputTextMask('99/99/9999 99:99', true)]});

Enjoy!

amackay11
21 Sep 2011, 4:48 AM
FYI to all.... I found a 4.0 version at : http://www.extjs.com.br/forum/index.php?PHPSESSID=3a88765ec1b311af7dc68c51e627a6a5&action=printpage;topic=4808.0

It works great!

jhuaraya
14 Dec 2011, 6:23 AM
Hi:
im using this plugin to mask an ip address like that:


var comp = new Ext.form.TextField({
name:'ip-address',
fieldLabel:'ip address',
plugins: [new Ext.ux.InputTextMask('999.999.999.999', true)]
});

but when i want to write an ip addres like 192.168.1.2 doesn't work, i must put 192.168.001.002
is there any way to solve this problem

giovannicandido
28 Dec 2011, 11:27 AM
I create a project to build ExtJS extensions. And put this InputTextMask in them.
The version works with Extjs 4.

The project is in: https://bitbucket.org/giovanni/extjs/

lamynes
24 Feb 2012, 4:23 PM
Awesome extension first off, is there an example for email input?
Is it possible?

nil5286
31 Jul 2012, 3:37 AM
I have used the masking plugin - it works but i have a specific requirement.

1. When the page is loaded it is not showing the masking for the field, Even though i have set. Only when i click inside the field that it shows the masking.

2. When i do
Ext.getCmp('phone').getValue() i should just get the field value without masking, ie i should get "1111111111" instead of "(111)111-1111" .

example of code that i tried:



{
xtype: 'textfield',
fieldLabel: 'Phone',
id: 'phone',
allowBlank: false,
plugins: [new Ext.ux.InputTextMask('(999)999-9999', false)]
},



Any help is appreciated.

brentdooley999
6 Aug 2012, 2:16 PM
In my application I have defined a form to edit employees. This form is instantiated on the main employee edit page. I have another instance of this for that exists in a hidden window. For some reason having 2 of the same objects with input masks as children really messes things up. I have been able to throw together a simple test case that breaks in the same way as my actual application.




Ext.define('form', {
extend: 'Ext.form.Panel',
items:[{
xtype:'textfield',
fieldLabel: 'test',
plugins: [new Ext.ux.InputTextMask('99999-9999', false)]
}]
});

var win1 = Ext.create('Ext.Window', {
height: 300,
width: 300,
items: Ext.create('form')
});

var win2 = Ext.create('Ext.Window', {
height: 300,
width: 300,
items: Ext.create('form')
});

win1.show();

win2.show();



When I run this I get the following error


TypeError: this.field.inputEl is undefined

this.inputTextElement = this.field.inputEl.dom


Can someone else verify this behavior?

amackay11
16 Oct 2012, 7:54 AM
Yes brentdooley999, I am experiencing the same problem. I have a 'master' form with a plugin field on it. When the form is extended to two children, I get lock-up on the field. Anyone solved this?

giovannicandido
16 Oct 2012, 11:11 AM
Awesome extension first off, is there an example for email input?
Is it possible?

The way mask does, it is not possible make an email mask. Because the mask have to be the number of input characters, for example, a mask of 9999 have to be 4 digits. Not possible do the same as regular expressions can do, but if your specific mask is some like user@domain.com and '@domain.com' is fixed and user have exact 4 digits, them you could.

JacobGu
7 Mar 2014, 10:34 AM
This plugin is broken on IE 11 and any other browser which adheres to the new standards used for text selection. Does anyone have a fixed version?

DerekBrennan20
16 Jul 2014, 12:32 AM
Hi, as per previous post I have found that the plugin is broken in later browsers. There have been changes to how selections and ranges are worked with that have broken the functions getCursorPosition and moveCursorToPosition. Has anyone had a go at fixing this? I've had a look but can't figure it out.

I'm on ExtJS 4.2.

Assuming this did work, would this plugin also work on Touch?

Thanks,

Derek Bennnan.

meher13
15 Dec 2014, 1:23 PM
Any updates?
Thank You