PDA

View Full Version : input mask plugin



fother
6 Apr 2009, 5:37 AM
now its possible add mask in TextField type of String :D

how add mask in for your fields
create one class called TextFieldMask and put into the client package in your project paste the source code.. for more details see the example..

Example


public void onModuleLoad() {

FormPanel form = new FormPanel();
form.setLabelWidth(250);

TextFieldMask field = new TextFieldMask("99/99/9999");
field.setFieldLabel("99/99/9999");
form.add(field);

field = new TextFieldMask("(999) 999-9999");
field.setFieldLabel("(999) 999-9999");
form.add(field);

field = new TextFieldMask("(999) 999-9999& x99999");
field.setFieldLabel("(999) 999-9999& x99999");
form.add(field);

field = new TextFieldMask("99-9999999");
field.setFieldLabel("99-9999999");
form.add(field);

field = new TextFieldMask("999-99-9999");
field.setFieldLabel("999-99-9999");
form.add(field);

field = new TextFieldMask("a*-999-a999");
field.setFieldLabel("a*-999-a999");
form.add(field);

RootPanel.get().add(form);

}


Source Code


import java.util.HashMap;
import java.util.Map;

import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.KeyboardListener;

public class TextFieldMask extends TextField<String> {

public class Settings {

private String placeHolder;

public Settings() {

}

public Settings(String placeHolder) {
this.placeHolder = placeHolder;
}

public String getPlaceHolder() {
return placeHolder;
}

public void setPlaceHolder(String placeHolder) {
this.placeHolder = placeHolder;
}

}

private final String mask;

private int len;

private Settings settings;
private int partialPosition;
private String[] buffer;
private boolean ignore;
private String focusText;
private String[] tests;

private Integer firstNonMaskPos;
private int cursorBegin = -1;

private int cursorEnd = -1;

private static final Map<String, String> defs;

static {

defs = new HashMap<String, String>();
defs.put("9", "[0-9]");
defs.put("a", "[A-Za-z]");
defs.put("*", "[A-Za-z0-9]");

}

private static boolean cotainDef(String key) {

if (defs.get(key) != null) {
return true;
} else {
return false;
}
}

private static String getDef(String key) {
return defs.get(key);
}

public TextFieldMask(String mask) {
this.mask = mask;
}

private void buffer() {

String[] aux = split(mask);

buffer = new String[aux.length];

for (int i = 0; i < aux.length; i++) {

if (cotainDef(aux[i])) {
buffer[i] = settings.getPlaceHolder();
} else {
buffer[i] = aux[i];
}

}

}

private int checkVal(boolean allow) {

String test = "";

if (getValue() != null) {
test = getValue();
}

int lastMatch = -1;

int a = 0;

for (int i = 0, pos = 0; i < len; i++) {

if (tests[i] != null) {

buffer[i] = settings.getPlaceHolder();

while (pos++ < test.length()) {

String c = String.valueOf(test.charAt(pos - 1));

if (c.matches(tests[i])) {

buffer[i] = String.valueOf(c);
lastMatch = i;
break;
}
}
if (pos > test.length()) {
break;
}
} else if (i != partialPosition) {

try {

char d = test.charAt(pos);

if (buffer[i].equals(String.valueOf(d))) {

pos++;
lastMatch = i;
}

} catch (Exception e) {
continue;
}
}

a = i;
}

if (!allow && lastMatch + 1 < partialPosition) {

setValue("");
clearBuffer(0, len);

} else if (allow || lastMatch + 1 >= partialPosition) {

writeBuffer();

if (!allow) {

if (getValue() != null) {
setValue(getValue().substring(0, lastMatch + 1));
}
}
}

return a;

}

private void clearBuffer(int start, int end) {

for (int i = start; i < end && i < len; i++) {
if (tests[i] != null) {
buffer[i] = settings.getPlaceHolder();
}
}

}

private void each() {

for (int i = 0; i < tests.length; i++) {

String c = tests[i];

if (c == "?") {

len--;
partialPosition = i;

} else if (cotainDef(c)) {

tests[i] = getDef(c);

if (firstNonMaskPos == null) {
firstNonMaskPos = tests.length - 1;
}

} else {
tests[i] = null;
}

}

}

private void maskField() {

settings = new Settings("_");

tests = new String[] {};
partialPosition = mask.length();
firstNonMaskPos = null;
len = mask.length();

tests = split(mask);

each();
buffer();

ignore = false;

focusText = "";

if (getValue() != null) {
focusText = getValue();
}

if (!isReadOnly()) {

checkVal(false);
}

}

@Override
protected void onBlur(ComponentEvent be) {

super.onBlur(be);

checkVal(false);
}

@Override
protected void onFocus(ComponentEvent be) {

super.onFocus(be);

focusText = "";

if (getValue() != null) {
focusText = getValue();
}

int pos = checkVal(false);
writeBuffer();

if (pos == mask.length()) {
cursorBegin = 0;
cursorEnd = pos;

select(0, pos);
} else {

cursorBegin = pos;
cursorEnd = pos;

setCursorPos(pos);
}
}

@Override
protected void onKeyDown(FieldEvent fe) {

super.onKeyDown(fe);

int k = fe.getKeyCode();

ignore = k < 16 || k > 16 && k < 32 || k > 32 && k < 41;

// delete selection before proceeding
if (cursorBegin - cursorEnd != 0 && (!ignore || k == KeyboardListener.KEY_BACKSPACE || k == KeyboardListener.KEY_DELETE)) {
clearBuffer(cursorBegin, cursorEnd);
}

// backspace, delete, and escape get special treatment
if (k == KeyboardListener.KEY_BACKSPACE || k == KeyboardListener.KEY_DELETE) {

shiftL(getCursorPos() + (k == KeyboardListener.KEY_DELETE ? 0 : -1));
fe.stopEvent();
} else if (k == KeyboardListener.KEY_ESCAPE) {// escape
setValue(focusText);
fe.stopEvent();
}
}

@Override
protected void onKeyPress(FieldEvent fe) {

super.onKeyPress(fe);

int k = fe.getKeyCode();

if (ignore) {
// Fixes Mac FF bug on backspace

if (k == KeyboardListener.KEY_BACKSPACE) {
fe.stopEvent();
}

return;
}

if (fe.isControlKey() || fe.isAltKey()) {// Ignore

fe.stopEvent();

} else if (k >= 32 && k <= 125 || k > 186) {// typeable characters

int p = seekNext(getCursorPos() - 1);

if (p < len) {

String c = String.valueOf((char) fe.getKeyCode());

if (c.matches(tests[p])) {

shiftR(p);
buffer[p] = c;
writeBuffer();
int next = seekNext(p);

setCursorPos(next);

cursorBegin = next;
cursorEnd = next;
}
}
}

fe.stopEvent();
}

@Override
protected void onRender(Element target, int index) {

super.onRender(target, index);

maskField();
}

private int seekNext(int index) {

while (++index <= len) {

try {
if (tests[index] != null) {
break;
}
} catch (Exception e) {
break;
}
}

return index;

}

private void shiftL(int index) {

for (int i = index; i >= 0; i--) {

if (tests[i] != null) {

index = i;
break;
}
}

for (int i = index; i < len; i++) {

if (tests[i] != null) {

buffer[i] = settings.getPlaceHolder();

int j = seekNext(i);

if (j < len && buffer[j].matches(tests[i])) {
buffer[i] = buffer[j];
} else {
break;
}
}
}

writeBuffer();

setCursorPos(index);

}

private void shiftR(int index) {

String c = settings.getPlaceHolder();

for (int i = index; i < len; i++) {

if (tests[i] != null) {

int j = seekNext(i);
String t = buffer[i];
buffer[i] = c;
if (j < len && t.matches(tests[j])) {
c = t;
} else {
break;
}
}
}

}

private String[] split(String text) {

int length = text.length();

String[] array = new String[length];

for (int i = 0; i < length; i++) {
array[i] = String.valueOf(text.charAt(i));
}

return array;
}

private void writeBuffer() {

String valueAux = "";

for (String element2 : buffer) {

valueAux += element2;
}

setValue(valueAux);

}

}



TO DO LIST

Paste into field - broken
create the config - to show or not the replace holder and change it
turn on create your regex expression - for example pass "H" and your regex, when you create the mask and set "H" validate the regex passed for parameter
allow pass various mask

cstdenis
7 Apr 2009, 8:40 AM
This is very useful.

Some suggestions for improvement:
* Only return the value that is entered, not the underscores.
* Add support for optional characters. In your example you have "(999) 999-9999& x99999" but that is a problem for any phone number that does not have an ext since you can't leave it blank (can't put in a country code either).
* This would make a nice addition to the DateField

fother
7 Apr 2009, 10:01 AM
* Only return the value that is entered, not the underscores.


of course.. I can create the config.. to show underscore or not.. and set other character that can be different the underscore :)



* Add support for optional characters. In your example you have "(999) 999-9999& x99999" but that is a problem for any phone number that does not have an ext since you can't leave it blank (can't put in a country code either).


sorry... I dont understand



* This would make a nice addition to the DateField


the problem to do this.. is that DateField extends Field.. and I needed create all Fields again (MyTextFieldMask, MyDateFieldMask and extends to MyField - isnt difficult.. but for this time.. I need first test if what I do its ok.. after I can implements this) good idea too..


thanks to reply.. other problem occurs when you paste anything.. I could not find a solution yet

cstdenis
7 Apr 2009, 10:07 AM
sorry... I dont understand



If the phone number mask is "(999) 999-9999 x999" but the phone number you need to enter is 555-555-1234 then what? That mask will not accept (555) 555-1234 because it's missing characters.

But if I were to use a mask of "(999) 999-9999", then there is no way to input something like (555) 555-4321 x123. A phone number field generally needs to accept a few variations of numbers.

Perhaps this could be best solved by accepting an array of Strings in the constructor and require that any 1 input mask match.

fother
7 Apr 2009, 10:23 AM
9 = [0-9]
A = [A-Za-z]
* = [A-Za-z0-9]


if you create one mask using "9" can be replaced by (0,1,2,3,4,5,6,7,8,9) - only numbers
if you create one mask using "A" can be replaced by (A,b,c...X,Y,z) - uppercase and lowercase letters
if you create one mask using "*" cab be replaced by (0,1,g,E,u,P) - can be replaced by numbers and letters


you are suggesting create one mask, for example "h" that can be replaced by your regex?

fother
7 Apr 2009, 10:29 AM
completing my reply..

if you dont set [9,A,*] the mask assume that you need this character in this position..

example: (XJUA) 9987456
can return: (XJUK) 1187456
can return: (XJUC) 2487456

only when have A and 9 you can replace by the regex

cstdenis
7 Apr 2009, 10:37 AM
I'm thinking more like modifiers.

9+ = /[0-9]+/
9* = /[0-9]*/
9? = /[0-9]?/
a+ = /[A-Za-z]+/
a* = /[A-Za-z]*/
a? = /[A-Za-z]?/
h = /[A-Za-z0-9]/
h+ = /[A-Za-z0-9]+/
h* = /[A-Za-z0-9]*/
h? = /[A-Za-z0-9]?/

Or maybe just accept arbitrary regex as the mask.

What I want is a mask for phone number along the lines of

/([0-9]+ )?+\([0-9]{3}\)\ [0-9]{3}\-[0-9]{4}( x[0-9]+)?/

Basically (555) 555-1234 with optional country code (followed by space) and optional extension at the end (prefixed by a space and the character x).


The other way I see of making this kind of thing work is

field = new TextFieldMask({"(999) 999-9999", "9 (999) 999-9999", "(999) 999-9999 x9999"});

fother
7 Apr 2009, 11:00 AM
hmm.. you suggest that I can use different mask for one field?

cstdenis
7 Apr 2009, 11:02 AM
Yes.

I don't know how hard it would be to make it work, but it should be possible in theory.

fother
7 Apr 2009, 11:06 AM
hmm... I can try :D

cri1258
31 Aug 2009, 8:55 AM
Just used your code. Seems to work very well. Thanks. Wonder if there is an updated version. Is there? The only thing so far that I think I would change is to have the input cursor placed at the first type in char. For example, if the mask is "(999) 999-9999", I would have the input cursor positioned at the first "9", so the user can immediately start typing. This would be great functionality to have added to GXT. Thanks

fother
1 Sep 2009, 4:02 AM
I have not changed over the code that did not use it more. More if you have any idea or any contribution is welcome .. :)

Arno.Nyhm
1 Sep 2009, 4:09 AM
nice Widget :-)

i have this questions:

1) is this also working in GXT 2.x?
2) is it not better to create it as a component plugin or not (so it can work with other types than String)

fother
1 Sep 2009, 4:52 AM
I dont know too.. because I dont use more.. you can test? ;)

cri1258
1 Sep 2009, 4:57 AM
Hi, Just wondering why you don't use the mask code anymore. Did you find what seems a better alternative? Thanks

titobundy
15 Feb 2010, 12:40 PM
Hi,

How I can refresh the mask of the TextFieldMask Object ? I tried using layout method in the contentpane, but not working...

titobundy
15 Feb 2010, 1:11 PM
Hi,

How I can refresh the mask of the TextFieldMask Object ? I tried using layout method in the contentpane, but not working...

I solved this problem implementing setMask method

public void setMask(String mask) {
this.mask = mask;

maskField();
}

jonas.esser
13 Sep 2010, 2:15 AM
Hello,

the checkVal() Method returns the wrong position. I added

a++;

before "return a;" to correct the position.

GXT Version: 2.2.0

Best Regards,
Jonas

Joshua093
10 Aug 2011, 6:45 AM
Hello,

the checkVal() Method returns the wrong position. I added

a++;

before "return a;" to correct the position.

GXT Version: 2.2.0

Best Regards,
Jonas

For anyone stumbling across this - This solution will only work when the first editable character is in the second position. I have implemented a small fix that will handle the cases where the first editable character is in any position. Basically, the original code is always skipping over the a = i; because of the use of the break statements. I am using GXT Version 2.3.0



private int checkVal(boolean allow) {

String test = "";


if (getValue() != null) {
test = getValue();
}


int lastMatch = -1;


int a = 0;


for (int i = 0, pos = 0; i < len; i++) {
if (tests[i] != null) {
buffer[i] = settings.getPlaceHolder();


while (pos++ < test.length()) {
String c = String.valueOf(test.charAt(pos - 1));


if (c.matches(tests[i])) {


buffer[i] = String.valueOf(c);
lastMatch = i;
a = i;
break;
}
}
if (pos > test.length()) {
a = i;
break;
}
} else if (i != partialPosition) {
try {
char d = test.charAt(pos);


if (buffer[i].equals(String.valueOf(d))) {
pos++;
lastMatch = i;
}
} catch (Exception e) {
continue;
}
}
}


if (!allow && lastMatch + 1 < partialPosition) {
setValue("");
clearBuffer(0, len);


} else if (allow || lastMatch + 1 >= partialPosition) {
writeBuffer();


if (!allow) {


if (getValue() != null) {
setValue(getValue().substring(0, lastMatch + 1));
}
}
}

return a;
}

paritha.j
11 Oct 2011, 10:52 PM
This is a wonderful plugin! Noticed that it is compatible with Chrome and IE, however doesn't seem to work for mozilla (version 3.0.19). Will be greatful if you could help me out by providing the necessary code.
Awaiting your response....

Thanking you in advance,
Pavitra

mariusz.pala
30 Mar 2012, 3:34 AM
Any chance for some fix for Firefox? It doesn't work at least in version 10 and 11.

Thanks,
Mariusz

Joshua093
30 Mar 2012, 6:53 AM
Can you tell me what exactly is not working? I am currently using this with my project (although I've modified it) and it is working fine in Firefox 10.0.3 and 11.0.

I am guessing that it is an issue in the onKeyPress method as I have had some issues with FF picking up correct key presses. I will include all of my code, which has been tested in IE (7,8 and 9), FF (8-11) and Chrome (17.0.963.83 m).

In my code I extend a MaxLengthTextField which is my own version of a TextField that correctly handles the max length attribute.




import java.util.HashMap;
import java.util.Map;

import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;

public class TextFieldMask extends MaxLengthTextField<String> {

public class Settings {

private String placeHolder;

public Settings() {

}

public Settings(String placeHolder) {
this.placeHolder = placeHolder;
}

public String getPlaceHolder() {
return placeHolder;
}

public void setPlaceHolder(String placeHolder) {
this.placeHolder = placeHolder;
}

}

private Settings settings;
private final String mask;
private int len;
private int partialPosition;
private int cursorBegin = -1;
private int cursorEnd = -1;
private Integer firstNonMaskPos;
private boolean ignore;
private String[] buffer;
private String[] tests;
private String focusText;
private int keyDownValue;

private static final Map<String, String> defs;

static {
defs = new HashMap<String, String>();
defs.put("9", "[0-9]");
defs.put("a", "[A-Za-z]");
defs.put("*", "[A-Za-z0-9]");
}

private static boolean cotainDef(String key) {

if (defs.get(key) != null) {
return true;
} else {
return false;
}
}

private static String getDef(String key) {
return defs.get(key);
}

public TextFieldMask(String mask) {
super();
this.mask = mask;
}

private void buffer() {

String[] aux = split(mask);

buffer = new String[aux.length];

for (int i = 0; i < aux.length; i++) {
if (cotainDef(aux[i])) {
buffer[i] = settings.getPlaceHolder();
} else {
buffer[i] = aux[i];
}
}
}

private int checkVal(boolean allow) {

String test = "";

if (getValue() != null) {
test = getValue();
}

int lastMatch = -1;

int a = 0;

for (int i = 0, pos = 0; i < len; i++) {
if (tests[i] != null) {
buffer[i] = settings.getPlaceHolder();

while (pos++ < test.length()) {
String c = String.valueOf(test.charAt(pos - 1));

if (c.matches(tests[i])) {

buffer[i] = String.valueOf(c);
lastMatch = i;
a = i;
break;
}
}
if (pos > test.length()) {
a = i;
break;
}
} else if (i != partialPosition) {
try {
char d = test.charAt(pos);

if (buffer[i].equals(String.valueOf(d))) {
pos++;
lastMatch = i;
}
} catch (Exception e) {
continue;
}
}
}

if (allow || lastMatch + 1 >= partialPosition) {
writeBuffer();

if (!allow) {

if (getValue() != null) {
setValue(getValue().substring(0, lastMatch + 1));
}
}
a = partialPosition;
}

return a;
}

private void clearBuffer(int start, int end) {

for (int i = start; i < end && i < len; i++) {
if (tests[i] != null) {
buffer[i] = settings.getPlaceHolder();
}
}
}

private void each() {

for (int i = 0; i < tests.length; i++) {
String c = tests[i];

if (c == "?") {
len--;
partialPosition = i;
} else if (cotainDef(c)) {
tests[i] = getDef(c);

if (firstNonMaskPos == null) {
firstNonMaskPos = tests.length - 1;
}
} else {
tests[i] = null;
}
}
}

public void maskField() {

settings = new Settings("_");

tests = new String[] {};
partialPosition = mask.length();
firstNonMaskPos = null;
len = mask.length();

tests = split(mask);

each();
buffer();

ignore = false;

focusText = "";

if (getValue() != null) {
focusText = getValue();
}

if (!isReadOnly()) {
checkVal(false);
}
}

@Override
protected void onBlur(ComponentEvent be) {

super.onBlur(be);
checkVal(true);
}

@Override
protected void onFocus(ComponentEvent be) {

super.onFocus(be);

focusText = "";

if (getValue() != null) {
focusText = getValue();
}

int pos = checkVal(false);
writeBuffer();

if (pos == mask.length()) {
cursorBegin = 0;
cursorEnd = pos;
select(0, pos);
} else {
cursorBegin = pos;
cursorEnd = pos;
setCursorPos(pos);
}
}

@Override
protected void onKeyDown(FieldEvent fe) {

super.onKeyDown(fe);

int k = fe.getKeyCode();
keyDownValue = k;

ignore = k < 16 || k > 16 && k < 32 || k > 32 && k < 41;

// delete selection before proceeding
if (cursorBegin - cursorEnd != 0
&& (!ignore || k == KeyCodes.KEY_BACKSPACE || k == KeyCodes.KEY_DELETE)) {
clearBuffer(cursorBegin, cursorEnd);
}

// backspace, delete, and escape get special treatment
if (k == KeyCodes.KEY_BACKSPACE || k == KeyCodes.KEY_DELETE) {
shiftL(getCursorPos() + (k == KeyCodes.KEY_DELETE ? 0 : -1));
fe.stopEvent();
} else if (k == KeyCodes.KEY_ESCAPE) {// escape
setValue(focusText);
fe.stopEvent();
}
}

@Override
protected void onKeyPress(FieldEvent fe) {

super.onKeyPress(fe);

int k = (fe.getKeyCode() == 0) ? keyDownValue : fe.getKeyCode();

//Used to convert codes for fire fox
if(fe.getKeyCode() == 0 && k != 0){
switch(k){
case 96: k = 48; break;
case 97: k = 49; break;
case 98: k = 50; break;
case 99: k = 51; break;
case 100: k = 52; break;
case 101: k = 53; break;
case 102: k = 54; break;
case 103: k = 55; break;
case 104: k = 56; break;
case 105: k = 57; break;
}
};
if (ignore) {
// Fixes Mac FF bug on backspace
if (k == KeyCodes.KEY_BACKSPACE) {
fe.stopEvent();
}
return;
}

if (fe.isControlKey() || fe.isAltKey()) {// Ignore
//fe.stopEvent();
} else if (k >= 32 && k <= 125 || k > 186) {// typeable characters
int p = seekNext(getCursorPos() - 1);

if (p < len) {
String c = String.valueOf((char) k);
if (c.matches(tests[p])) {
shiftR(p);
buffer[p] = c;
writeBuffer();
int next = seekNext(p);
setCursorPos(next);
cursorBegin = next;
cursorEnd = next;
}
}
}
fe.stopEvent();
}

@Override
protected void onRender(Element target, int index) {
super.onRender(target, index);
maskField();
}

private int seekNext(int index) {

while (++index <= len) {
try {
if (tests[index] != null) {
break;
}
} catch (Exception e) {
break;
}
}
return index;
}

private void shiftL(int index) {
for (int i = index; i >= 0; i--) {
if (i < tests.length && tests[i] != null) {
index = i;
break;
}
}

for (int i = index; i < len; i++) {
if (i >= 0 && tests[i] != null) {
buffer[i] = settings.getPlaceHolder();

int j = seekNext(i);

if (j < len && buffer[j].matches(tests[i])) {
buffer[i] = buffer[j];
} else {
break;
}
}
}
writeBuffer();
setCursorPos(index);
}

private void shiftR(int index) {

String c = settings.getPlaceHolder();

for (int i = index; i < len; i++) {
if (tests[i] != null) {
int j = seekNext(i);
String t = buffer[i];
buffer[i] = c;
if (j < len && t.matches(tests[j])) {
c = t;
} else {
break;
}
}
}
}

private String[] split(String text) {
int length = text.length();
String[] array = new String[length];

for (int i = 0; i < length; i++) {
array[i] = String.valueOf(text.charAt(i));
}
return array;
}

private void writeBuffer() {

String valueAux = "";

for (String element2 : buffer) {
valueAux += element2;
}
setValue(valueAux);
}

/*
* This is used as a hack for deleting items in the array. If highlighing something
* use the 'Delete' key for more accurate results. Backspace will take one extra
* character to the left. This can be dealt with later.
*/
@Override
public void onComponentEvent(ComponentEvent ce){
super.onComponentEvent(ce);
switch (ce.getEventTypeInt()) {
case Event.ONMOUSEUP: cursorBegin = getCursorPos(); break;
case Event.ONCLICK: cursorEnd = getSelectionLength() + cursorBegin; break;
}
}


All that I ask from you is to post any enhancements that you come up with so that we can all share in the glory brought to us by 'fother'.