/**
* @version 1.0.4 $Revision: 1.1 $
* The MIT License
* Copyright (c) 2008 Fabio M. Costa http://www.meiocodigo.com
*/
/**
* jquery.meiomask.js
* $URL: http://svn.assembla.com/svn/meiomask/jquery.meiomask.js $
* @author: $Author: kighie $
* @version 1.0.4 $Revision: 1.1 $
* @lastchange: $Date: 2009/05/26 12:47:56 $
*
* Created by Fabio M. Costa on 2008-09-16. Please report any bug at http://www.meiocodigo.com
*
* Copyright (c) 2008 Fabio M. Costa http://www.meiocodigo.com
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
(function($){
var isIphone = (window.orientation!=undefined);
$.extend({
mask : {
// the mask rules. You may add yours!
// number rules will be overwritten
rules : {
'z': /[a-z]/,
'Z': /[A-Z]/,
'a': /[a-zA-Z]/,
'*': /[0-9a-zA-Z\.\-\_]/,
'@': /[0-9a-zA-Z\@\.]/
},
// fixed chars to be used on the masks. You may change it for your needs!
fixedChars : '[(),:/ -]',
// these keys will be ignored by the mask.
// all these numbers where obtained on the keydown event
keyRepresentation : {
8 : 'backspace',
9 : 'tab',
13 : 'enter',
16 : 'shift',
17 : 'control',
18 : 'alt',
27 : 'esc',
33 : 'page up',
34 : 'page down',
35 : 'end',
36 : 'home',
37 : 'left',
38 : 'up',
39 : 'right',
40 : 'down',
45 : 'insert',
46 : 'delete',
116 : 'f5',
224 : 'command'
},
iphoneKeyRepresentation : {
10 : 'go',
127 : 'delete'
},
signals : {
'+' : '',
'-' : '-'
},
// default settings for the plugin
options : {
attr: 'alt', // an attr to look for the mask name or the mask itself
mask : null, // the mask to be used on the input
type : 'fixed', // the mask of this mask
defaultValue : '', // the default value for this input
signal : false, // this should not be set, to use signal at masks put the signal you want ('-' or '+') at the default value of this mask.
// See the defined masks for a better understanding.
onInvalid : function(){},
onValid : function(){},
onOverflow : function(){}
},
// masks. You may add yours!
// Ex: $.fn.setMask.masks.msk = {mask: '999'}
// and then if the 'attr' options value is 'alt', your input should look like:
//
masks : {
'phone' : { mask : '(99) 9999-9999' },
'phone-us' : { mask : '(999) 9999-9999' },
'cpf' : { mask : '999.999.999-99' }, // cadastro nacional de pessoa fisica
'cnpj' : { mask : '99.999.999/9999-99' },
'date' : { mask : '39/19/9999' }, //uk date
'date-us' : { mask : '19/39/9999' },
'cep' : { mask : '99999-999' },
'time' : { mask : '29:59' },
'cc' : { mask : '9999 9999 9999 9999' }, //credit card mask
'integer' : { mask : '999.999.999.999', type : 'reverse' },
'decimal' : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '000' },
'decimal-us' : { mask : '99.999,999,999,999', type : 'reverse', defaultValue : '000' },
'signed-decimal' : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '+000' },
'signed-decimal-us' : { mask : '99,999.999.999.999', type : 'reverse', defaultValue : '+000' },
'decimal-kr' : { mask : '999,999,999,999,999', type : 'reverse'}
},
init : function(){
// if has not inited...
if( !this.hasInit ){
var self = this, i,
keyRep = ( isIphone ) ? this.iphoneKeyRepresentation : this.keyRepresentation;
this.ignore = false;
this.fixedCharsReg = new RegExp(this.fixedChars);
this.fixedCharsRegG = new RegExp(this.fixedChars,'g');
// constructs number rules
for(i=0; i<=9; i++) this.rules[i] = new RegExp('[0-'+i+']');
this.keyRep = keyRep;
// ignore keys array creation for iphone or the normal ones
this.ignoreKeys = [];
$.each(keyRep,function(key){
self.ignoreKeys.push( parseInt(key) );
});
this.hasInit = true;
}
},
set: function(el,options){
var maskObj = this,
$el = $(el),
mlStr = 'maxLength';
this.init();
return $el.each(function(){
var $this = $(this),
o = $.extend({},maskObj.options),
attrValue = $this.attr(o.attr),
tmpMask = '',
// 'input' event fires on every keyboard event on the input
pasteEvent = maskObj.__getPasteEvent();
// then we look for the 'attr' option
tmpMask = ( typeof options == 'string' )?options:( attrValue != '' )?attrValue:null;
if(tmpMask) o.mask = tmpMask;
// then we see if it's a defined mask
if(maskObj.masks[tmpMask]) o = $.extend(o,maskObj.masks[tmpMask]);
// then it looks if the options is an object, if it is we will overwrite the actual options
if( typeof options == 'object' ) o = $.extend(o,options);
//then we look for some metadata on the input
if($.metadata) o = $.extend(o,$this.metadata());
if( o.mask != null ){
if($this.data('mask')) maskObj.unset($this);
var defaultValue = o.defaultValue,
mlValue = $this.attr(mlStr),
reverse = (o.type=='reverse');
o = $.extend({},o,{
maxlength: mlValue,
maskArray : o.mask.split(''),
maskNonFixedCharsArray : o.mask.replace(maskObj.fixedCharsRegG,'').split('')
});
//sets text-align right for reverse masks
if(reverse) $this.css('text-align','right');
// apply mask to the current value of the input
if($this.val()!='') $this.val( maskObj.string($this.val(),o) );
// apply the default value of the mask to the input
else if(defaultValue!='') $this.val( maskObj.string(defaultValue,o) );
$this.data('mask',o);
// removes the maxlength attribute (it will be set again if you use the unset method)
$this.removeAttr(mlStr);
// setting the input events
$this.bind('keydown',{func:maskObj._keyDown,thisObj:maskObj},maskObj._onMask)
.bind('keyup',{func:maskObj._keyUp,thisObj:maskObj},maskObj._onMask)
.bind('keypress',{func:maskObj._keyPress,thisObj:maskObj},maskObj._onMask)
.bind(pasteEvent,{func:maskObj._paste,thisObj:maskObj},maskObj._delayedOnMask);
}
});
},
//unsets the mask from el
unset : function(el){
var $el = $(el),
_this = this;
return $el.each(function(){
var $this = $(this);
if( $this.data('mask') ){
var maxLength = $this.data('mask').maxlength,
pasteEvent = _this.__getPasteEvent();
if(maxLength != -1) $this.attr('maxLength',maxLength);
$this.unbind('keydown',_this._onMask)
.unbind('keypress',_this._onMask)
.unbind('keyup',_this._onMask)
.unbind(pasteEvent,_this._delayedOnMask)
.removeData('mask');
}
});
},
//masks a string
string : function(str,options){
this.init();
var o={};
if(typeof str != 'string') str = String(str);
switch(typeof options){
case 'string':
// then we see if it's a defined mask
if(this.masks[options]) o = $.extend(o,this.masks[options]);
else o.mask = options;
break;
case 'object':
o = options;
}
// insert signal if any
if( (o.type=='reverse') && o.defaultValue ){
if( typeof this.signals[o.defaultValue.charAt(0)] != 'undefined' ){
var maybeASignal = str.charAt(0);
o.signal = (typeof this.signals[maybeASignal] != 'undefined') ? this.signals[maybeASignal] : this.signals[o.defaultValue.charAt(0)];
o.defaultValue = o.defaultValue.substring(1);
}
}
return this.__maskArray(str.split(''),
o.mask.replace(this.fixedCharsRegG,'').split(''),
o.mask.split(''),
o.type,
o.defaultValue,
o.signal);
},
unmaskedVal : function(el){
return $(el).val().replace($.mask.fixedCharsRegG,'');
},
_onMask : function(e){
var thisObj = e.data.thisObj,
o = {};
o._this = e.target;
o.$this = $(o._this);
// if the input is readonly it does nothing
if( o.$this.attr('readonly') ) return true;
o.value = o.$this.val();
o.nKey = thisObj.__getKeyNumber(e);
o.range = thisObj.__getRange(o._this);
o.valueArray = o.value.split('');
o.data = o.$this.data('mask');
o[o.data.type] = true;
return e.data.func.call(thisObj,e,o);
},
// the timeout is set because on ie we can't get the value from the input without it
_delayedOnMask : function(e){
e.type='paste';
setTimeout(function(){ e.data.thisObj._onMask(e); },1);
},
_keyDown : function(e,o){
// lets say keypress at desktop == keydown at iphone (theres no keypress at iphone)
this.ignore = ( $.inArray(o.nKey,this.ignoreKeys) > -1 );
if( this.ignore ){
var rep = this.keyRep[o.nKey];
o.data.onValid.call(o._this,rep?rep:'',o.nKey);
}
return isIphone ? this._keyPress(e,o) : true;
},
_keyUp : function(e,o){
//9=TAB_KEY
//this is a little bug, when you go to an input with tab key
//it would remove the range selected by default, and that's not a desired behavior
if(o.nKey==9 && ($.browser.safari || $.browser.msie)) return true;
return (!o.infinite)?this._paste(e,o):true;
},
_paste : function(e,o){
// changes the signal at the data obj from the input
if(o.reverse) this.__changeSignal(e.type,o);
var $thisVal = this.__maskArray(
o.valueArray,
o.data.maskNonFixedCharsArray,
o.data.maskArray,
o.data.type,
o.data.defaultValue,
o.data.signal
);
o.$this.val( $thisVal );
// this makes the caret stay at first position when
// the user removes all values in an input and the plugin adds the default value to it (if it haves one).
if( !o.reverse && o.data.defaultValue.length && (o.range.start==o.range.end) )
this.__setRange(o._this,o.range.start,o.range.end);
//fix so ie's and safari's caret won't go to the end of the input value.
if( ($.browser.msie || $.browser.safari) && !o.reverse) this.__setRange(o._this,o.range.start,o.range.end);
return true;
},
_keyPress: function(e,o){
if( this.ignore || e.ctrlKey || e.metaKey || e.altKey ) return true;
// changes the signal at the data obj from the input
if(o.reverse) this.__changeSignal(e.type,o);
var c = String.fromCharCode(o.nKey),
rangeStart = o.range.start,
rawValue = o.value,
maskArray = o.data.maskArray;
if(o.reverse){
// the input value from the range start to the value start
var valueStart = rawValue.substr(0,rangeStart),
// the input value from the range end to the value end
valueEnd = rawValue.substr(o.range.end,rawValue.length);
rawValue = (valueStart+c+valueEnd);
//necessary, if not decremented you will be able to input just the mask.length-1 if signal!=''
//ex: mask:99,999.999.999 you will be able to input 99,999.999.99
if( o.data.signal && (rangeStart-o.data.signal.length > 0 ) ) rangeStart-=o.data.signal.length;
}
var valueArray = rawValue.replace(this.fixedCharsRegG,'').split(''),
// searches for fixed chars begining from the range start position, till it finds a non fixed
extraPos = this.__extraPositionsTill(rangeStart,maskArray);
o.rsEp = rangeStart+extraPos;
if( o.infinite ) o.rsEp = 0;
// if the rule for this character doesnt exist (value.length is bigger than mask.length)
if( !this.rules[maskArray[o.rsEp]] ){
o.data.onOverflow.call(o._this,c,o.nKey);
return false;
}
// if the new character is not obeying the law... :P
else if( !this.rules[maskArray[o.rsEp]].test( c ) ){
o.data.onInvalid.call(o._this,c,o.nKey);
return false;
}
else o.data.onValid.call(o._this,c,o.nKey);
var $thisVal = this.__maskArray(
valueArray,
o.data.maskNonFixedCharsArray,
maskArray,
o.data.type,
o.data.defaultValue,
o.data.signal,
extraPos
);
o.$this.val( $thisVal );
return (o.reverse)?this._keyPressReverse(e,o):(o.fixed)?this._keyPressFixed(e,o):true;
},
_keyPressFixed : function(e,o){
if(o.range.start==o.range.end){
// the 0 thing is cause theres a particular behavior i wasnt liking when you put a default
// value on a fixed mask and you select the value from the input the range would go to the
// end of the string when you enter a char. with this it will overwrite the first char wich is a better behavior.
// opera fix, cant have range value bigger than value length, i think it loops thought the input value...
if( (o.rsEp==0 && o.value.length==0) || o.rsEp < o.value.length )
this.__setRange(o._this,o.rsEp,o.rsEp+1);
}
else
this.__setRange(o._this,o.range.start,o.range.end);
return true;
},
_keyPressReverse : function(e,o){
//fix for ie
//this bug was pointed by Pedro Martins
//it fixes a strange behavior that ie was having after a char was inputted in a text input that
//had its content selected by any range
if($.browser.msie && ( (o.rangeStart==0 && o.range.end==0) || o.rangeStart != o.range.end ) )
this.__setRange(o._this,o.value.length);
return false;
},
// changes the signal at the data obj from the input
__changeSignal : function(eventType,o){
if(o.data.signal!==false){
var inputChar = (eventType=='paste')?o.value.charAt(0):String.fromCharCode(o.nKey);
if( this.signals && (typeof this.signals[inputChar] != 'undefined') ){
o.data.signal = this.signals[inputChar];
}
}
},
// browsers like firefox2 and before and opera doenst have the onPaste event, but the paste feature can be done with the onInput event.
__getPasteEvent : function(){
return ( $.browser.opera || ( $.browser.mozilla && parseFloat($.browser.version.substr(0,3)) < 1.9 ))?'input':'paste';
},
__getKeyNumber : function(e){
return (e.charCode||e.keyCode||e.which);
},
// this function is totaly specific to be used with this plugin, youll never need it
// it gets the array representing an unmasked string and masks it depending on the type of the mask
__maskArray : function(valueArray,maskNonFixedCharsArray,maskArray,type,defaultValue,signal,extraPos){
if(type == 'reverse') valueArray.reverse();
valueArray = this.__removeInvalidChars(valueArray,maskNonFixedCharsArray);
if(defaultValue) valueArray = this.__applyDefaultValue.call(valueArray,defaultValue);
valueArray = this.__applyMask(valueArray,maskArray,extraPos);
switch(type){
case 'reverse':
valueArray.reverse();
return (signal || '')+valueArray.join('').substring(valueArray.length-maskArray.length);
case 'infinite':
return valueArray.join('');
default:
return valueArray.join('').substring(0,maskArray.length);
}
return '';
},
// applyes the default value to the result string
__applyDefaultValue : function(defaultValue){
var defLen = defaultValue.length,thisLen = this.length,i;
//removes the leading chars
for(i=thisLen-1;i>=0;i--){
if(this[i]==defaultValue.charAt(0)) this.pop();
else break;
}
// apply the default value
for(i=0;i