-
Mask for TextField
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:
Code:
{
xtype: 'textfield',
mask: '(999) 999-9999'
}
or
Code:
{
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:
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
-
Does not work.
I get this errors:
this.mask.getBufferValue is not a function
on this.setRawValue(this.mask.getBufferValue());
Cya
-
Sorry about that, I've just updated the code. If you still get errors let me know how you are using it.
-
The same
I'm using it like this:
PHP Code:
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}
});
-
1 Attachment(s)
I assume 'glcode' extend from 'textfield' so I've just try this:
Code:
{
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:
-
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.
-
initMask
Hi!
initMask saved the day.
I'll keep testing and I'll tell you.
Cya!