Hi,
Yes this is the old thread, but my finally code is this one:
JS Plugin code
// $Id: InputTextMask.js 100 2008-02-26 09:38:02Z bobbicat71 $
Ext.namespace('Ext.ux.netbox');
Ext.ux.netbox.InputTextMask = function (config) {
var mask = config.mask;
var clearWhenInvalid = config.clearWhenInvalid;
var relinkEvents = config.relinkEvents;
if (clearWhenInvalid === undefined) {
this.clearWhenInvalid = true;
} else {
this.clearWhenInvalid = clearWhenInvalid;
}
//Añadido para controlar la vinculación de eventos en el cambio de mascara
if (relinkEvents === undefined) {
this.relinkEvents = true;
} else {
this.relinkEvents = relinkEvents;
}
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) == 'L' || mask.charAt(i) == 'l' || mask.charAt(i) == 'A' || mask.charAt(i) == '9' || mask.charAt(i) == '?') {
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;
if (this.relinkEvents == true) {
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(/[\-\+\ ]/);
} else if (maskKey) {
return (keycode.pressedKey.match(new RegExp(maskKey)));
}
return (null);
},
removeValueWhenInvalid: function () {
//Rellenado de los espacios vacios, con los valores por defecto (en números)
for (var i = 0; i < this.inputTextElement.value.length; i++) {
if (this.rawMask[i] == '9' && this.inputTextElement.value[i] == '_') {
this.inputTextElement.value = this.inputTextElement.value.substring(0, i) + '0' + this.inputTextElement.value.substring(i + 1);
}
if (this.rawMask[i] == '?' && this.inputTextElement.value[i] == '_') {
this.inputTextElement.value = this.inputTextElement.value.substring(0, i) + ' ' + this.inputTextElement.value.substring(i + 1);
}
}
this.field.validate();
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);
this.inputTextElement.value += 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);
},
changeMask: function (newMask, newClearWhenInvalid, relinkEvents) {
var maskPlugin = this;
var field = maskPlugin.field;
field.un('render', maskPlugin.assignEl, maskPlugin);
field.un('blur', maskPlugin.removeValueWhenInvalid, maskPlugin);
field.un('focus', maskPlugin.processMaskFocus, maskPlugin);
field.getEl().un('keypress', maskPlugin.processKeyPress, maskPlugin);
field.getEl().un('keydown', maskPlugin.processKeyDown, maskPlugin);
if (Ext.isSafari || Ext.isIE) {
field.getEl().un('paste', maskPlugin.startTask, maskPlugin);
field.getEl().un('cut', maskPlugin.startTask, maskPlugin);
}
if (Ext.isGecko || Ext.isOpera) {
field.getEl().un('mousedown', maskPlugin.setPreviousValue, maskPlugin);
}
if (Ext.isGecko) {
field.getEl().un('input', maskPlugin.onInput, maskPlugin);
}
if (Ext.isOpera) {
field.getEl().un('input', maskPlugin.onInputOpera, maskPlugin);
}
if (Ext.isArray(field.plugins)) {
field.plugins.pop(maskPlugin);
field.plugins.pop(maskPlugin);
}
else {
field.plugins = null;
}
maskPlugin = new Ext.ux.netbox.InputTextMask({ mask: newMask, clearWhenInvalid: newClearWhenInvalid, relinkEvents: relinkEvents });
this.viewMask = maskPlugin.viewMask;
this.rawMask = maskPlugin.rawMask;
this.maskArray = maskPlugin.maskArray;
if (Ext.isArray(field.plugins)) {
field.plugins.push(maskPlugin);
}
else {
field.plugins = maskPlugin;
}
maskPlugin.init(field);
}
};
Ext.applyIf(RegExp, {
escape: function (str) {
return str.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}
});
Ext.ux.InputTextMask = Ext.ux.netbox.InputTextMask;
The parameter relinkEvents is used for linkEvents of the plugin only the first time, because in next mask change the event fires more than one time (number of mask changes). If you don't understand it use, set this parameter to true all the time in changeMask function and type it the textfield.
Solving this...; when you have a mask with length of 10 characters and change it to a mask of 5 characters for example, anything inside the plugin has wrong data or something because you could tip 10 characters. In otherwise if the first mask has 5 characters and the second one has 7 charactrs you only can type 5 characters.
The first mask length persists or something similar.
I've a sample project if you request it.
But with this piece of code and the plugin you can reproduce it fine. Only change the reference to the plugin
ASP code
<%@ Page Language="C#" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Register Assembly="Softmachine.Millenium.Presentation.Web.MVC.Client" Namespace="Ext.Net"
TagPrefix="Custom" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Ext.Net Example</title>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" />
<ext:Viewport ID="ViewPort1" runat="server" Layout="fit">
<Items>
<ext:Panel ID="ToolbarsPanel" runat="server" Border="false" Layout="Fit">
<TopBar>
<ext:Toolbar ID="Toolbar1" runat="server">
<Items>
<ext:Button runat="server" Text="First Action">
<Listeners>
<Click Handler="mask.changeMask('99-99-99',false,true); textBox.setValue('33-12-45');" />
</Listeners>
</ext:Button>
<ext:Button runat="server" Text="Rest of Actions">
<Listeners>
<Click Handler="mask.changeMask('99,999',false,false); textBox.setValue('12,456');" />
</Listeners>
</ext:Button>
</Items>
</ext:Toolbar>
</TopBar>
<Items>
<ext:Panel runat="server" AutoHeight="true">
<Items>
<ext:TextField ID="textBox" runat="server">
<Plugins>
<Custom:InputTextMask ID="mask" runat="server" Mask="999:999:999" ClearWhenInvalid="false" />
</Plugins>
</ext:TextField>
</Items>
</ext:Panel>
</Items>
</ext:Panel>
</Items>
</ext:Viewport>
</form>
</body>
</html>
.NET plugin code
using System.ComponentModel;
using System.Collections.Generic;
using System.Web.UI;
using System.Xml.Serialization;
using Newtonsoft.Json;
[assembly: WebResource("Softmachine.Millenium.Presentation.Web.MVC.Client.Plugins.InputTextMask.js", "text/javascript")]
namespace Ext.Net
{
[ToolboxItem(true)]
[ToolboxData("<{0}:InputTextMask runat=\"server\" />")]
[Description("InputTextMask plugin used for mask/regexp operations")]
public class InputTextMask : Plugin
{
/// <summary>
///
/// </summary>
[Description("")]
protected override List<ResourceItem> Resources
{
get
{
List<ResourceItem> baseList = base.Resources;
baseList.Capacity += 1;
ClientScriptItem _csi = new ClientScriptItem(typeof(InputTextMask), "Softmachine.Millenium.Presentation.Web.MVC.Client.Plugins.InputTextMask.js", "/ux/plugins/inputtextmask/inputtextmask.js");
baseList.Add(_csi);
return baseList;
}
}
/// <summary>
///
/// </summary>
[Category("0. About")]
[Description("")]
public override string InstanceOf
{
get
{
return "Ext.ux.netbox.InputTextMask";
}
}
[ConfigOption]
[DefaultValue("")]
[NotifyParentProperty(true)]
[Description("The InputTextMask")]
public string Mask
{
get
{
object obj = this.ViewState["Mask"];
return obj == null ? "" : (string)obj;
}
set
{
this.ViewState["Mask"] = value;
}
}
[ConfigOption]
[DefaultValue(true)]
[NotifyParentProperty(true)]
[Description("True to clear the mask when the field blurs and the text is invalid. Optional, default is true")]
public bool ClearWhenInvalid
{
get
{
object obj = this.ViewState["ClearWhenInvalid"];
return obj == null ? true : (bool)obj;
}
set
{
this.ViewState["ClearWhenInvalid"] = value;
}
}
/// <summary>
///
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[XmlIgnore]
[JsonIgnore]
public override ConfigOptionsCollection ConfigOptions
{
get
{
ConfigOptionsCollection list = base.ConfigOptions;
list.Add("mask", new ConfigOption("mask", null, string.Empty, this.Mask));
list.Add("clearWhenInvalid", new ConfigOption("clearWhenInvalid", null, true, this.ClearWhenInvalid));
return list;
}
}
}
}