PDA

View Full Version : Mask for TextField



FrankXP
6 Jun 2011, 10:19 AM
I couldn't find a plugin to mask a TextField the way I need it. The only one that did what I was looking for was this one http://digitalbush.com/projects/masked-input-plugin/ but it was for jQuery. So I decided to "port it" over to ExtJS.
First I tried to make it a plugin but I decided to overwrite the TextField class so subclasses will inherit it too.

This is how to use it:



{
xtype: 'textfield',
mask: '(999) 999-9999'
}


or



{
xtype: 'textfield',
mask: { text: '(999) 999-9999', placeholder:'#', includeInValue: false }
}


includeInValue will include/exclude the mask when getValue() is called. (default: true)

Here is the full source code:



Ext.form.MaskDefinitions = {
'9': '[0-9]',
'a': '[A-Za-z]',
'*': '[A-Za-z0-9]'
};
Ext.applyIf(Array.prototype, {
map: function (fn, scope) {
var r = [],
i = 0,
len = this.length,
v;
for (; i < len; i++) {
v = fn.call(scope, this[i], i, this);
if (v != null)
r.push(v);
}
return r;
}
});
(function () {
var _initComponent = Ext.form.TextField.prototype.initComponent,
_initEvents = Ext.form.TextField.prototype.initEvents,
_getValue = Ext.form.TextField.prototype.getValue,
_setValue = Ext.form.TextField.prototype.setValue,
_preFocus = Ext.form.TextField.prototype.preFocus;

Ext.override(Ext.form.TextField, {
initComponent: function () {
if (this.initMask()) {
Ext.apply(this, {
disableKeyFilter: true,
maskRe: null
});
this.addEvents('maskComplete');
}
_initComponent.apply(this);
},
initEvents: function () {
_initEvents.apply(this);
if (this.mask) {
if (!this.enableKeyEvents && this.mask) {
this.mon(this.el, {
scope: this,
keydown: this.onKeyDown,
keypress: this.onKeyPress
});
}
this.mon(this.el, 'paste', this.onPaste, this);
}
},
initMask: function () {
if (!Ext.isDefined(this.mask)) {
return false;
}
this.mask = Ext.isString(this.mask) ? { text: this.mask} : this.mask;
Ext.applyIf(this.mask, { placeholder: '_', includeInValue: true });
if (!this.mask.text) {
return false;
}

Ext.apply(this.mask, {
regexp: [],
partialPosition: this.mask.text.length,
firstNonMaskPos: null,
length: this.mask.text.length,
buffer: this.mask.text.split('').map(function (c) {
return (c === '?') ? null : Ext.form.MaskDefinitions[c] ? this.mask.placeholder : c;
}, this),
seekNext: function (pos) {
while (++pos <= this.length && !this.regexp[pos]);
return pos;
},
seekPrev: function (pos) {
while (--pos >= 0 && !this.regexp[pos]);
return pos;
},
shiftL: function (start, end) {
if (start < 0) {
return;
}
for (var i = start, j = this.seekNext(end); i < this.length; i++) {
if (this.regexp[i]) {
if (j < this.length && this.regexp[i].test(this.buffer[j])) {
this.buffer[i] = this.buffer[j];
this.buffer[j] = this.placeholder;
} else {
break;
}
j = this.seekNext(j);
}
}
},
shiftR: function (pos) {
for (var i = pos, c = this.placeholder; i < this.length; i++) {
if (this.regexp[i]) {
var j = this.seekNext(i);
var t = this.buffer[i];
this.buffer[i] = c;
if (j < this.length && this.regexp[j].test(t)) {
c = t;
}
else {
break;
}
}
}
},
clearBuffer: function (start, end) {
for (var i = start; i < end && i < this.length; i++) {
if (this.regexp[i]) {
this.buffer[i] = this.placeholder;
}
}
},
getBufferValue: function (removeMask) {
return removeMask ?
this.buffer.map(function (c, i) {
return (this.regexp[i] && c != this.placeholder) ? c : null;
}, this).join('') :
this.buffer.join('');
}
});

Ext.each(this.mask.text.split(''), function (c, i) {
if (c === '?') {
this.mask.length--;
this.mask.partialPosition = i;
} else if (Ext.form.MaskDefinitions[c]) {
this.mask.regexp.push(new RegExp(Ext.form.MaskDefinitions[c]));
if (this.mask.firstNonMaskPos == null) {
this.mask.firstNonMaskPos = this.mask.regexp.length - 1;
}
} else {
this.mask.regexp.push(null);
}
}, this);

return true;
},

//private
_cursor: function (start, end) {
var s, e, r, d = this.el.dom;
if (typeof start == 'number') {
end = (typeof end == 'number') ? end : start;
if (d.setSelectionRange) {
d.setSelectionRange(start, end);
} else if (d.createTextRange) {
r = d.createTextRange();
r.collapse(true);
r.moveEnd('character', end);
r.moveStart('character', start);
r.select();
}
}
else {
if (d.setSelectionRange) {
s = d.selectionStart;
e = d.selectionEnd;
}
else if (document.selection && document.selection.createRange) {
r = document.selection.createRange();
s = 0 - r.duplicate().moveStart('character', -d.value.length);
e = s + r.text.length;
}
return { start: isNaN(s) ? 0 : s, end: isNaN(e) ? 0 : e };
}
},
_checkValueMask: function (allow) {
var test = this.getRawValue();
var lastMatch = -1;
for (var i = 0, pos = 0; i < this.mask.length; i++) {
if (this.mask.regexp[i]) {
this.mask.buffer[i] = this.mask.placeholder;
while (pos++ < test.length) {
var c = test.charAt(pos - 1);
if (this.mask.regexp[i].test(c)) {
this.mask.buffer[i] = c;
lastMatch = i;
break;
}
}
if (pos > test.length)
break;
} else if (this.mask.buffer[i] == test.charAt(pos) && i != this.mask.partialPosition) {
pos++;
lastMatch = i;
}
}
if (!allow && lastMatch + 1 < this.mask.partialPosition) {
this.setRawValue('');
this.mask.clearBuffer(0, this.mask.length);
} else if (allow || lastMatch + 1 >= this.mask.partialPosition) {
this._writeMaskBuffer();
if (!allow) this.setRawValue(this.getRawValue().substring(0, lastMatch + 1));
}
return (this.mask.partialPosition ? i : this.mask.firstNonMaskPos);
},
_writeMaskBuffer: function () {
this.setRawValue(this.mask.getBufferValue());
},

//public
getValue: function () {
var v = _getValue.apply(this);
if (this.mask && !Ext.isEmpty(v)) {
v = this.mask.includeInValue ? v.replace(this.mask.placeholder,'') : this.mask.getBufferValue(true);
}
return v;
},
setValue: function (v) {
_setValue.call(this, v);
if (this.mask && !Ext.isEmpty(v)) {
this._checkValueMask();
}
},

//protected
preFocus: function () {
_preFocus.apply(this);
if (this.mask && !this.hasFocus) {
this.startValue = this.getValue();
var pos = this._checkValueMask();
this._writeMaskBuffer();
(function () {
if (pos == this.mask.length) {
this._cursor(0, pos);
}
else {
this._cursor(pos);
}
}).defer(0, this);
}
},
beforeBlur: function () {
if (this.mask && !this.readOnly) {
this._checkValueMask();
}
},
onKeyDown: function (e) {
this.fireEvent('keydown', this, e);
if (this.mask && !this.readOnly) {
var k = e.getKey();
if (k == e.BACKSPACE || k == e.DELETE) {
var pos = this._cursor(),
start = pos.start,
end = pos.end;
if (end - start == 0) {
start = k != e.DELETE ? this.mask.seekPrev(start) : (end = this.mask.seekNext(start - 1));
end = k == e.DELETE ? this.mask.seekNext(end) : end;
}
this.mask.clearBuffer(start, end);
this.mask.shiftL(start, end - 1);
this._writeMaskBuffer();
this._cursor(Math.max(this.mask.firstNonMaskPos, start));
e.stopEvent();
} else if (k == e.ESC) {
this.setValue(this.startValue);
this._cursor(0, this._checkValueMask());
e.stopEvent();
}
}
},
onKeyPress: function (e) {
this.fireEvent('keypress', this, e);
if (this.mask && !this.readOnly) {
var k = e.getKey(),
pos = this._cursor(),
start = pos.start,
end = pos.end;
if (!e.ctrlKey && !e.altKey && !e.isNavKeyPress() && k != e.CONTEXT_MENU && k >= 32) {
if (end - start != 0) {
this.mask.clearBuffer(start, end);
this.mask.shiftL(start, end - 1);
this._writeMaskBuffer();
this._cursor(Math.max(this.mask.firstNonMaskPos, start));
}
var p = this.mask.seekNext(start - 1);
if (p < this.mask.length) {
var c = String.fromCharCode(e.getCharCode());
if (this.mask.regexp[p].test(c)) {
this.mask.shiftR(p);
this.mask.buffer[p] = c;
this._writeMaskBuffer();
var next = this.mask.seekNext(p);
this._cursor(next);
if (next >= this.mask.length) {
this.fireEvent('maskComplete', this, this.mask);
}
}
}
e.stopEvent();
}
}
},
onPaste: function (e) {
if (this.mask && !this.readOnly) {
(function () { this._cursor(this._checkValueMask(true)); }).defer(10, this);
}
}
});
})();


I'm new to Ext and the forums so please any like/dislikes will be appreciate it
Thanks

jmariani
10 Jun 2011, 6:37 AM
I get this errors:

this.mask.getBufferValue is not a function
on this.setRawValue(this.mask.getBufferValue());

Cya

FrankXP
10 Jun 2011, 6:47 AM
Sorry about that, I've just updated the code. If you still get errors let me know how you are using it.

jmariani
10 Jun 2011, 6:52 AM
I'm using it like this:


Ext.apply(this.itemglcode, {
vtype: 'glcode',
helpText: "Formato: 9999.9999.9999.99.999",
iconCls: "ico-help-req",
mask: { text: '9999.9999.9999.99.999', placeholder:'#', includeInValue: true}
});

FrankXP
10 Jun 2011, 7:24 AM
I assume 'glcode' extend from 'textfield' so I've just try this:


{
xtype:'textfield',
helpText: "Formato: 9999.9999.9999.99.999",
iconCls: "ico-help-req",
mask: { text: '9999.9999.9999.99.999', placeholder: '#', includeInValue: true },
width: 185
}


And this is how it looks like:

FrankXP
10 Jun 2011, 7:30 AM
One thing to notice is that if your component is already initialized you need to call initMask() to initialize the mask. Also make sure you set it up with enableKeyEvents so keydown and keypress get register. I'm working on a way to allow the mask to be changed after the initialization and also remove the mask all-together.

jmariani
10 Jun 2011, 8:36 AM
Hi!
initMask saved the day.

I'll keep testing and I'll tell you.

Cya!