555 lines
14 KiB
JavaScript
555 lines
14 KiB
JavaScript
(function($){
|
|
var $chk = function(obj){
|
|
return !!(obj || obj === 0);
|
|
};
|
|
|
|
/**
|
|
* Masks an input with a pattern
|
|
* @class
|
|
* @requires jQuery
|
|
* @name jQuery.iMask
|
|
* @param {Object} options
|
|
* @param {String} options.type (number|fixed)
|
|
* @param {String} options.mask Mask using 9,a,x notation
|
|
* @param {String} [options.maskEmptyChr=' ']
|
|
* @param {String} [options.validNumbers='1234567890']
|
|
* @param {String} [options.validAlphas='abcdefghijklmnopqrstuvwxyz']
|
|
* @param {String} [options.validAlphaNums='abcdefghijklmnopqrstuvwxyz1234567890']
|
|
* @param {Number} [options.groupDigits=3]
|
|
* @param {Number} [options.decDigits=2]
|
|
* @param {String} [options.currencySymbol]
|
|
* @param {String} [options.groupSymbol=',']
|
|
* @param {String} [options.decSymbol='.']
|
|
* @param {Boolean} [options.showMask=true]
|
|
* @param {Boolean} [options.stripMask=false]
|
|
* @param {Function} [options.sanity]
|
|
* @param {Object} [options.number] Override options for when validating numbers only
|
|
* @param {Boolean} [options.number.stripMask=false]
|
|
* @param {Boolean} [options.number.showMask=false]
|
|
*/
|
|
var iMask = function(){
|
|
this.initialize.apply(this, arguments);
|
|
};
|
|
|
|
iMask.prototype = {
|
|
std_options: {
|
|
maskEmptyChr : ' ',
|
|
|
|
validNumbers : "1234567890",
|
|
validAlphas : "abcdefghijklmnopqrstuvwxyz",
|
|
validAlphaNums : "abcdefghijklmnopqrstuvwxyz1234567890",
|
|
|
|
groupDigits : 3,
|
|
decDigits : 2,
|
|
currencySymbol : '',
|
|
groupSymbol : ',',
|
|
decSymbol : '.',
|
|
showMask : true,
|
|
stripMask : false,
|
|
|
|
lastFocus : 0,
|
|
|
|
number : {
|
|
stripMask : false,
|
|
showMask : false
|
|
}
|
|
},
|
|
|
|
initialize: function(node, options) {
|
|
this.node = node;
|
|
this.domNode = node[0];
|
|
this.setOptions(options);
|
|
var self = this;
|
|
|
|
if(options.type == "number") this.node.css("text-align", "right");
|
|
|
|
this.node
|
|
.bind( "mousedown click", function(ev){
|
|
ev.stopPropagation(); ev.preventDefault(); } )
|
|
|
|
.bind( "mouseup", function(){ self.onMouseUp .apply(self, arguments); } )
|
|
.bind( "keydown", function(){ self.onKeyDown .apply(self, arguments); } )
|
|
.bind( "keypress", function(){ self.onKeyPress.apply(self, arguments); } )
|
|
.bind( "focus", function(){ self.onFocus .apply(self, arguments); } )
|
|
//.bind( "blur", function(){ self.onBlur .apply(self, arguments); } );
|
|
},
|
|
|
|
setOptions: function(options) {
|
|
this.options = $.extend({},
|
|
this.std_options, this.std_options[options.type] || {}, options);
|
|
},
|
|
|
|
isFixed : function(){ return this.options.type == 'fixed'; },
|
|
isNumber : function(){ return this.options.type == 'number'; },
|
|
|
|
onMouseUp: function( ev ) {
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
|
|
if( this.isFixed() ) {
|
|
var p = this.getSelectionStart();
|
|
this.setSelection(p, (p + 1));
|
|
} else if(this.isNumber() ) {
|
|
this.setEnd();
|
|
}
|
|
},
|
|
|
|
onKeyDown: function(ev) {
|
|
if(ev.ctrlKey || ev.altKey || ev.metaKey) {
|
|
return;
|
|
|
|
} else if(ev.which == 13) { // enter
|
|
this.node.blur();
|
|
|
|
this.submitForm(this.node);
|
|
|
|
} else if(!(ev.which == 9)) { // tab
|
|
if(this.options.type == "fixed") {
|
|
ev.preventDefault();
|
|
|
|
var p = this.getSelectionStart();
|
|
switch(ev.which) {
|
|
case 8: // Backspace
|
|
this.updateSelection( this.options.maskEmptyChr );
|
|
this.selectPrevious();
|
|
break;
|
|
case 36: // Home
|
|
this.selectFirst();
|
|
break;
|
|
case 35: // End
|
|
this.selectLast();
|
|
break;
|
|
case 37: // Left
|
|
case 38: // Up
|
|
this.selectPrevious();
|
|
break;
|
|
case 39: // Right
|
|
case 40: // Down
|
|
this.selectNext();
|
|
break;
|
|
case 46: // Delete
|
|
this.updateSelection( this.options.maskEmptyChr );
|
|
this.selectNext();
|
|
break;
|
|
default:
|
|
var chr = this.chrFromEv(ev);
|
|
if( this.isViableInput( p, chr ) ) {
|
|
this.updateSelection( ev.shiftKey ? chr.toUpperCase() : chr );
|
|
this.node.trigger("valid", ev, this.node);
|
|
this.selectNext();
|
|
} else {
|
|
this.node.trigger("invalid", ev, this.node);
|
|
}
|
|
break;
|
|
}
|
|
} else if(this.options.type == "number") {
|
|
switch(ev.which) {
|
|
case 35: // END
|
|
case 36: // HOME
|
|
case 37: // LEFT
|
|
case 38: // UP
|
|
case 39: // RIGHT
|
|
case 40: // DOWN
|
|
break;
|
|
case 8: // backspace
|
|
case 46: // delete
|
|
var self = this;
|
|
setTimeout(function(){
|
|
self.formatNumber();
|
|
}, 1);
|
|
break;
|
|
|
|
default:
|
|
ev.preventDefault();
|
|
|
|
var chr = this.chrFromEv( ev );
|
|
if( this.isViableInput( p, chr ) ) {
|
|
var range = new Range( this )
|
|
, val = this.sanityTest( range.replaceWith( chr ) );
|
|
|
|
if(val !== false){
|
|
this.updateSelection( chr );
|
|
this.formatNumber();
|
|
}
|
|
this.node.trigger( "valid", ev, this.node );
|
|
} else {
|
|
this.node.trigger( "invalid", ev, this.node );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
allowKeys : {
|
|
8 : 1 // backspace
|
|
, 9 : 1 // tab
|
|
, 13 : 1 // enter
|
|
, 35 : 1 // end
|
|
, 36 : 1 // home
|
|
, 37 : 1 // left
|
|
, 38 : 1 // up
|
|
, 39 : 1 // right
|
|
, 40 : 1 // down
|
|
, 46 : 1 // delete
|
|
},
|
|
|
|
onKeyPress: function(ev) {
|
|
var key = ev.which || ev.keyCode;
|
|
|
|
if(
|
|
!( this.allowKeys[ key ] )
|
|
&& !(ev.ctrlKey || ev.altKey || ev.metaKey)
|
|
) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
},
|
|
|
|
onFocus: function(ev) {
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
|
|
this.options.showMask && (this.domNode.value = this.wearMask(this.domNode.value));
|
|
this.sanityTest( this.domNode.value );
|
|
|
|
var self = this;
|
|
|
|
setTimeout( function(){
|
|
self[ self.options.type === "fixed" ? 'selectFirst' : 'setEnd' ]();
|
|
}, 1 );
|
|
},
|
|
|
|
onBlur: function(ev) {
|
|
// ev.stopPropagation();
|
|
// ev.preventDefault();
|
|
//
|
|
// if(this.options.stripMask)
|
|
// this.domNode.value = this.stripMask();
|
|
},
|
|
|
|
selectAll: function() {
|
|
this.setSelection(0, this.domNode.value.length);
|
|
},
|
|
|
|
selectFirst: function() {
|
|
for(var i = 0, len = this.options.mask.length; i < len; i++) {
|
|
if(this.isInputPosition(i)) {
|
|
this.setSelection(i, (i + 1));
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
selectLast: function() {
|
|
for(var i = (this.options.mask.length - 1); i >= 0; i--) {
|
|
if(this.isInputPosition(i)) {
|
|
this.setSelection(i, (i + 1));
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
selectPrevious: function(p) {
|
|
if( !$chk(p) ){ p = this.getSelectionStart(); }
|
|
|
|
if(p <= 0) {
|
|
this.selectFirst();
|
|
} else {
|
|
if(this.isInputPosition(p - 1)) {
|
|
this.setSelection(p - 1, p);
|
|
} else {
|
|
this.selectPrevious(p - 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
selectNext: function(p) {
|
|
if( !$chk(p) ){ p = this.getSelectionEnd(); }
|
|
|
|
if( this.isNumber() ){
|
|
this.setSelection( p+1, p+1 );
|
|
return;
|
|
}
|
|
|
|
if( p >= this.options.mask.length) {
|
|
this.selectLast();
|
|
} else {
|
|
if(this.isInputPosition(p)) {
|
|
this.setSelection(p, (p + 1));
|
|
} else {
|
|
this.selectNext(p + 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
setSelection: function( a, b ) {
|
|
a = a.valueOf();
|
|
if( !b && a.splice ){
|
|
b = a[1];
|
|
a = a[0];
|
|
}
|
|
|
|
if(this.domNode.setSelectionRange) {
|
|
this.domNode.focus();
|
|
this.domNode.setSelectionRange(a, b);
|
|
} else if(this.domNode.createTextRange) {
|
|
var r = this.domNode.createTextRange();
|
|
r.collapse();
|
|
r.moveStart("character", a);
|
|
r.moveEnd("character", (b - a));
|
|
r.select();
|
|
}
|
|
},
|
|
|
|
updateSelection: function( chr ) {
|
|
var value = this.domNode.value
|
|
, range = new Range( this )
|
|
, output = range.replaceWith( chr );
|
|
|
|
this.domNode.value = output;
|
|
if( range[0] === range[1] ){
|
|
this.setSelection( range[0] + 1, range[0] + 1 );
|
|
}else{
|
|
this.setSelection( range );
|
|
}
|
|
},
|
|
|
|
setEnd: function() {
|
|
var len = this.domNode.value.length;
|
|
this.setSelection(len, len);
|
|
},
|
|
|
|
getSelectionRange : function(){
|
|
return [ this.getSelectionStart(), this.getSelectionEnd() ];
|
|
},
|
|
|
|
getSelectionStart: function() {
|
|
var p = 0,
|
|
n = this.domNode.selectionStart;
|
|
|
|
if( n ) {
|
|
if( typeof( n ) == "number" ){
|
|
p = n;
|
|
}
|
|
} else if( document.selection ){
|
|
var r = document.selection.createRange().duplicate();
|
|
r.moveEnd( "character", this.domNode.value.length );
|
|
p = this.domNode.value.lastIndexOf( r.text );
|
|
if( r.text == "" ){
|
|
p = this.domNode.value.length;
|
|
}
|
|
}
|
|
return p;
|
|
},
|
|
|
|
getSelectionEnd: function() {
|
|
var p = 0,
|
|
n = this.domNode.selectionEnd;
|
|
|
|
if( n ) {
|
|
if( typeof( n ) == "number"){
|
|
p = n;
|
|
}
|
|
} else if( document.selection ){
|
|
var r = document.selection.createRange().duplicate();
|
|
r.moveStart( "character", -this.domNode.value.length );
|
|
p = r.text.length;
|
|
}
|
|
return p;
|
|
},
|
|
|
|
isInputPosition: function(p) {
|
|
var mask = this.options.mask.toLowerCase();
|
|
var chr = mask.charAt(p);
|
|
return !!~"9ax".indexOf(chr);
|
|
},
|
|
|
|
sanityTest: function( str, p ){
|
|
var sanity = this.options.sanity;
|
|
|
|
if(sanity instanceof RegExp){
|
|
return sanity.test(str);
|
|
}else if($.isFunction(sanity)){
|
|
var ret = sanity(str, p);
|
|
if(typeof(ret) == 'boolean'){
|
|
return ret;
|
|
}else if(typeof(ret) != 'undefined'){
|
|
if( this.isFixed() ){
|
|
var p = this.getSelectionStart();
|
|
this.domNode.value = this.wearMask( ret );
|
|
this.setSelection( p, p+1 );
|
|
this.selectNext();
|
|
}else if( this.isNumber() ){
|
|
var range = new Range( this );
|
|
this.domNode.value = ret;
|
|
this.setSelection( range );
|
|
this.formatNumber();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
|
|
isViableInput: function() {
|
|
return this[ this.isFixed() ? 'isViableFixedInput' : 'isViableNumericInput' ].apply( this, arguments );
|
|
},
|
|
|
|
isViableFixedInput : function( p, chr ){
|
|
var mask = this.options.mask.toLowerCase();
|
|
var chMask = mask.charAt(p);
|
|
|
|
var val = this.domNode.value.split('');
|
|
val.splice( p, 1, chr );
|
|
val = val.join('');
|
|
|
|
var ret = this.sanityTest( val, p );
|
|
if(typeof(ret) == 'boolean'){ return ret; }
|
|
|
|
if(({
|
|
'9' : this.options.validNumbers,
|
|
'a' : this.options.validAlphas,
|
|
'x' : this.options.validAlphaNums
|
|
}[chMask] || '').indexOf(chr) >= 0){
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
isViableNumericInput : function( p, chr ){
|
|
return !!~this.options.validNumbers.indexOf( chr );
|
|
},
|
|
|
|
wearMask: function(str) {
|
|
var mask = this.options.mask.toLowerCase()
|
|
, output = ""
|
|
, chrSets = {
|
|
'9' : 'validNumbers'
|
|
, 'a' : 'validAlphas'
|
|
, 'x' : 'validAlphaNums'
|
|
};
|
|
|
|
for(var i = 0, u = 0, len = mask.length; i < len; i++) {
|
|
switch(mask.charAt(i)) {
|
|
case '9':
|
|
case 'a':
|
|
case 'x':
|
|
output +=
|
|
((this.options[ chrSets[ mask.charAt(i) ] ].indexOf( str.charAt(u).toLowerCase() ) >= 0) && ( str.charAt(u) != ""))
|
|
? str.charAt( u++ )
|
|
: this.options.maskEmptyChr;
|
|
break;
|
|
|
|
default:
|
|
output += mask.charAt(i);
|
|
if( str.charAt(u) == mask.charAt(i) ){
|
|
u++;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
return output;
|
|
},
|
|
|
|
stripMask: function() {
|
|
var value = this.domNode.value;
|
|
if("" == value) return "";
|
|
var output = "";
|
|
|
|
if( this.isFixed() ) {
|
|
for(var i = 0, len = value.length; i < len; i++) {
|
|
if((value.charAt(i) != this.options.maskEmptyChr) && (this.isInputPosition(i)))
|
|
{output += value.charAt(i);}
|
|
}
|
|
} else if( this.isNumber() ) {
|
|
for(var i = 0, len = value.length; i < len; i++) {
|
|
if(this.options.validNumbers.indexOf(value.charAt(i)) >= 0)
|
|
{output += value.charAt(i);}
|
|
}
|
|
}
|
|
|
|
return output;
|
|
},
|
|
|
|
chrFromEv: function(ev) {
|
|
var chr = '', key = ev.which;
|
|
|
|
if(key >= 96 && key <= 105){ key -= 48; } // shift number-pad numbers to corresponding character codes
|
|
chr = String.fromCharCode(key).toLowerCase(); // key pressed as a lowercase string
|
|
return chr;
|
|
},
|
|
|
|
formatNumber: function() {
|
|
// stripLeadingZeros
|
|
var olen = this.domNode.value.length
|
|
, str2 = this.stripMask()
|
|
, str1 = str2.replace( /^0+/, '' )
|
|
, range = new Range(this);
|
|
|
|
// wearLeadingZeros
|
|
|
|
str2 = str1;
|
|
str1 = "";
|
|
for(var len = str2.length, i = this.options.decDigits; len <= i; len++) {
|
|
str1 += "0";
|
|
}
|
|
str1 += str2;
|
|
|
|
// decimalSymbol
|
|
str2 = str1.substr(str1.length - this.options.decDigits)
|
|
str1 = str1.substring(0, (str1.length - this.options.decDigits))
|
|
|
|
// groupSymbols
|
|
var re = new RegExp("(\\d+)(\\d{"+ this.options.groupDigits +"})");
|
|
while(re.test(str1)) {
|
|
str1 = str1.replace(re, "$1"+ this.options.groupSymbol +"$2");
|
|
}
|
|
|
|
this.domNode.value = this.options.currencySymbol + str1 + this.options.decSymbol + str2;
|
|
this.setSelection( range );
|
|
},
|
|
|
|
getObjForm: function() {
|
|
return this.node.getClosest('form');
|
|
},
|
|
|
|
submitForm: function() {
|
|
//var form = this.getObjForm();
|
|
//form.trigger('submit');
|
|
}
|
|
};
|
|
|
|
function Range( obj ){
|
|
this.range = obj.getSelectionRange();
|
|
this.len = obj.domNode.value.length
|
|
this.obj = obj;
|
|
|
|
this['0'] = this.range[0];
|
|
this['1'] = this.range[1];
|
|
}
|
|
Range.prototype = {
|
|
valueOf : function(){
|
|
var len = this.len - this.obj.domNode.value.length;
|
|
return [ this.range[0] - len, this.range[1] - len ];
|
|
},
|
|
replaceWith : function( str ){
|
|
var val = this.obj.domNode.value
|
|
, range = this.valueOf();
|
|
|
|
return val.substr( 0, range[0] ) + str + val.substr( range[1] );
|
|
}
|
|
};
|
|
|
|
$.fn.iMask = function(options){
|
|
this.each(function(){
|
|
if(this._imask) {
|
|
// don't re-initialize, just reset the options dynamically
|
|
this._imask.setOptions(options);
|
|
} else {
|
|
this._imask = new iMask($(this), options);
|
|
}
|
|
});
|
|
};
|
|
})(jQuery);
|