/*

 * Copyright (c) 2007 Josh Bush (digitalbush.com)

 * 

 * 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. 

 */

 

/*

 * Version: 1.1

 * Release: 2007-09-08

 */ 

(function($) {

    //Helper Functions for Caret positioning

    function getCaretPosition(ctl){

        var res = {begin: 0, end: 0 };

        if (ctl.setSelectionRange){

            res.begin = ctl.selectionStart;

            res.end = ctl.selectionEnd;

        }else if (document.selection && document.selection.createRange){

            var range = document.selection.createRange();           

            res.begin = 0 - range.duplicate().moveStart('character', -100000);

            res.end = res.begin + range.text.length;

        }

        return res;

    };



    function setCaretPosition(ctl, pos){        

        if(ctl.setSelectionRange){

            ctl.focus();

            ctl.setSelectionRange(pos,pos);

        }else if (ctl.createTextRange){

            var range = ctl.createTextRange();

            range.collapse(true);

            range.moveEnd('character', pos);

            range.moveStart('character', pos);

            range.select();

        }

    };

    

    //Predefined character definitions

    var charMap={

        '9':"[0-9]",

        'a':"[A-Za-z]",

        '*':"[A-Za-z0-9]"

    };

    

    //Helper method to inject character definitions

    $.mask={

        addPlaceholder : function(c,r){

            charMap[c]=r;

        }

    };

    

    $.fn.unmask=function(){

        return this.trigger("unmask");

    };

    

    //Main Method

    $.fn.mask = function(mask,settings) {   

        settings = $.extend({

            placeholder: "_",

            completed: null

        }, settings);

            

        //Build Regex for format validation

        var reString="^";   

        for(var i=0;i<mask.length;i++)

            reString+=(charMap[mask.charAt(i)] || ("\\"+mask.charAt(i)));                   

        reString+="$";

        var re = new RegExp(reString);



        return this.each(function(){        

            var input=$(this);

            var buffer=new Array(mask.length);

            var locked=new Array(mask.length);      



            //Build buffer layout from mask

            for(var i=0;i<mask.length;i++){

                locked[i]=charMap[mask.charAt(i)]==null;

                buffer[i]=locked[i]?mask.charAt(i):settings.placeholder;                    

            }

            

            /*Event Bindings*/

            function focusEvent(){                  

                checkVal();

                writeBuffer();

                setTimeout(function(){

                    setCaretPosition(input[0],0);

                },0);

            };          

            input.bind("focus",focusEvent);



            input.bind("blur",checkVal);

            

            //Paste events for IE and Mozilla thanks to Kristinn Sigmundsson

            if ($.browser.msie) 

                this.onpaste= function(){setTimeout(checkVal,0);};                     

            else if ($.browser.mozilla)

                this.addEventListener('input',checkVal,false);

            

            var ignore=false;  //Variable for ignoring control keys

            

            function keydownEvent(e){

                var pos=getCaretPosition(this);                                                 

                var k = e.keyCode;

                ignore=(k < 16 || (k > 16 && k < 32 ) || (k > 32 && k < 41));

                

                //delete selection before proceeding

                if((pos.begin-pos.end)!=0 && (!ignore || k==8 || k==46)){

                    clearBuffer(pos.begin,pos.end);

                }   

                //backspace and delete get special treatment

                if(k==8){//backspace                    

                    while(pos.begin-->=0){

                        if(!locked[pos.begin]){                             

                            buffer[pos.begin]=settings.placeholder;

                            if($.browser.opera){

                                //Opera won't let you cancel the backspace, so we'll let it backspace over a dummy character.                               

                                writeBuffer(pos.begin);

                                setCaretPosition(this,pos.begin+1);

                            }else{

                                writeBuffer();

                                setCaretPosition(this,pos.begin);

                            }                                   

                            return false;                               

                        }

                    }                       

                }else if(k==46){//delete

                    clearBuffer(pos.begin,pos.begin+1);

                    writeBuffer();

                    setCaretPosition(this,pos.begin);

                    return false;

                }else if (k==27){

                    clearBuffer(0,mask.length);

                    writeBuffer();

                    setCaretPosition(this,0);

                    return false;

                }

                                    

            };

            input.bind("keydown",keydownEvent);



            function keypressEvent(e){                  

                if(ignore){

                    ignore=false;

                    return;

                }

                e=e||window.event;

                var k=e.charCode||e.keyCode||e.which;



                var pos=getCaretPosition(this);                 

                var caretPos=pos.begin; 

                

                if(e.ctrlKey || e.altKey){//Ignore

                    return true;

                }else if ((k>=41 && k<=122) ||k==32 || k>186){//typeable characters

                    while(pos.begin<mask.length){   

                        var reString=charMap[mask.charAt(pos.begin)];

                        var match;

                        if(reString){

                            var reChar=new RegExp(reString);

                            match=String.fromCharCode(k).match(reChar);

                        }else{//we're on a mask char, go forward and try again

                            pos.begin+=1;

                            pos.end=pos.begin;

                            caretPos+=1;

                            continue;

                        }



                        if(match)

                            buffer[pos.begin]=String.fromCharCode(k);

                        else

                            return false;//reject char



                        while(++caretPos<mask.length){//seek forward to next typable position

                            if(!locked[caretPos])                           

                                break;                          

                        }

                        break;

                    }

                }else

                    return false;                               



                writeBuffer();

                if(settings.completed && caretPos>=buffer.length)

                    settings.completed.call(input);

                else

                    setCaretPosition(this,caretPos);

                

                return false;               

            };

            input.bind("keypress",keypressEvent);



            /*Helper Methods*/

            function clearBuffer(start,end){

                for(var i=start;i<end;i++){

                    if(!locked[i])

                        buffer[i]=settings.placeholder;

                }               

            };

            

            function writeBuffer(pos){

                var s="";

                for(var i=0;i<mask.length;i++){

                    s+=buffer[i];

                    if(i==pos)

                        s+=settings.placeholder;

                }

                input.val(s);

                return s;

            };

            

            function checkVal(){    

                //try to place charcters where they belong

                var test=input.val();

                var pos=0;

                for(var i=0;i<mask.length;i++){

                    if(!locked[i]){

                        while(pos++<test.length){

                            //Regex Test each char here.

                            var reChar=new RegExp(charMap[mask.charAt(i)]);

                            if(test.charAt(pos-1).match(reChar)){

                                buffer[i]=test.charAt(pos-1);

                                break;

                            }                                   

                        }

                    }

                }

                var s=writeBuffer();

                if(!s.match(re)){                           

                    input.val("");  

                    clearBuffer(0,mask.length);

                }                   

            };

            

            input.one("unmask",function(){

                input.unbind("focus",focusEvent);

                input.unbind("blur",checkVal);

                input.unbind("keydown",keydownEvent);

                input.unbind("keypress",keypressEvent);

                if ($.browser.msie) 

                    this.onpaste= null;                     

                else if ($.browser.mozilla)

                    this.removeEventListener('input',checkVal,false);

            });

                            

        });

    };

})(jQuery);
