/** * jquery.mask.js * * @version: v1.14.16 * @author: Igor Escobar * * Created by Igor Escobar on 2012-03-10. Please report any bug at github.com/igorescobar/jQuery-Mask-Plugin * * Copyright (c) 2012 Igor Escobar http://igorescobar.com * * The MIT License (http://www.opensource.org/licenses/mit-license.php) * * 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. */ /* jshint laxbreak: true */ /* jshint maxcomplexity:17 */ /* global define */ // UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere. // https://github.com/umdjs/umd/blob/master/templates/jqueryPlugin.js // reformatted code by themeComplete ( function( factory, jQuery, Zepto ) { 'use strict'; if ( typeof define === 'function' && define.amd ) { define( [ 'jquery' ], factory ); } else if ( typeof exports === 'object' && typeof Meteor === 'undefined' ) { window.module.exports = factory( window.require( 'jquery' ) ); } else { factory( jQuery || Zepto ); } }( function( $ ) { 'use strict'; var JSON = window.JSON; var globals; var Mask = function( el, mask, options ) { var jMask = this; var regexMask; var oldValue; var p = { invalid: [], getCaret: function() { var sel, pos = 0, ctrl, dSel, cSelStart; try { ctrl = el.get( 0 ); dSel = document.selection; cSelStart = ctrl.selectionStart; // IE Support if ( dSel && navigator.appVersion.indexOf( 'MSIE 10' ) === -1 ) { sel = dSel.createRange(); sel.moveStart( 'character', -p.val().length ); pos = sel.text.length; } else if ( cSelStart || cSelStart === '0' ) { // Firefox support pos = cSelStart; } return pos; } catch ( err ) { window.console.log( err ); } }, setCaret: function( pos ) { var range; var ctrl; try { if ( el.is( ':focus' ) ) { ctrl = el.get( 0 ); // Firefox, WebKit, etc.. if ( ctrl.setSelectionRange ) { ctrl.setSelectionRange( pos, pos ); } else { // IE range = ctrl.createTextRange(); range.collapse( true ); range.moveEnd( 'character', pos ); range.moveStart( 'character', pos ); range.select(); } } } catch ( err ) { window.console.log( err ); } }, events: function() { el.on( 'keydown.mask', function( e ) { el.data( 'mask-keycode', e.keyCode || e.which ); el.data( 'mask-previus-value', el.val() ); el.data( 'mask-previus-caret-pos', p.getCaret() ); p.maskDigitPosMapOld = p.maskDigitPosMap; } ) .on( $.jMaskGlobals.useInput ? 'input.mask' : 'keyup.mask', p.behaviour ) .on( 'paste.mask drop.mask', function() { setTimeout( function() { el.keydown().keyup(); }, 100 ); } ) .on( 'change.mask', function() { el.data( 'changed', true ); } ) .on( 'blur.mask', function() { if ( oldValue !== p.val() && ! el.data( 'changed' ) ) { el.trigger( 'change' ); } el.data( 'changed', false ); } ) // it's very important that this callback remains in this position // otherwhise oldValue it's going to work buggy .on( 'blur.mask', function() { oldValue = p.val(); } ) // select all text on focus .on( 'focus.mask', function( e ) { if ( options.selectOnFocus === true ) { $( e.target ).select(); } } ) // clear the value if it not complete the mask .on( 'focusout.mask', function() { if ( options.clearIfNotMatch && ! regexMask.test( p.val() ) ) { p.val( '' ); } } ); }, getRegexMask: function() { var maskChunks = [], translation, pattern, optional, recursive, oRecursive, r; var i; for ( i = 0; i < mask.length; i += 1 ) { translation = jMask.translation[ mask.charAt( i ) ]; if ( translation ) { pattern = translation.pattern.toString().replace( /.{1}$|^.{1}/g, '' ); optional = translation.optional; recursive = translation.recursive; if ( recursive ) { maskChunks.push( mask.charAt( i ) ); oRecursive = { digit: mask.charAt( i ), pattern: pattern }; } else { maskChunks.push( ! optional && ! recursive ? pattern : pattern + '?' ); } } else { // eslint-disable-next-line no-useless-escape maskChunks.push( mask.charAt( i ).replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ) ); } } r = maskChunks.join( '' ); if ( oRecursive ) { r = r.replace( new RegExp( '(' + oRecursive.digit + '(.*' + oRecursive.digit + ')?)' ), '($1)?' ).replace( new RegExp( oRecursive.digit, 'g' ), oRecursive.pattern ); } return new RegExp( r ); }, destroyEvents: function() { el.off( [ 'input', 'keydown', 'keyup', 'paste', 'drop', 'blur', 'focusout', '' ].join( '.mask ' ) ); }, val: function( v ) { var isInput = el.is( 'input' ), method = isInput ? 'val' : 'text', r; if ( arguments.length > 0 ) { if ( el[ method ]() !== v ) { el[ method ]( v ); } r = el; } else { r = el[ method ](); } return r; }, calculateCaretPosition: function( oldVal ) { var newVal = p.getMasked(), caretPosNew = p.getCaret(); var caretPosOld, newValL, oldValL, maskDigitsBeforeCaret = 0, maskDigitsAfterCaret = 0, maskDigitsBeforeCaretAll = 0, maskDigitsBeforeCaretAllOld = 0, i = 0; var caretPos; if ( oldVal !== newVal ) { caretPosOld = el.data( 'mask-previus-caret-pos' ) || 0; newValL = newVal.length; oldValL = oldVal.length; for ( i = caretPosNew; i < newValL; i += 1 ) { if ( ! p.maskDigitPosMap[ i ] ) { break; } maskDigitsAfterCaret = maskDigitsAfterCaret + 1; } for ( i = caretPosNew - 1; i >= 0; i -= 1 ) { if ( ! p.maskDigitPosMap[ i ] ) { break; } maskDigitsBeforeCaret = maskDigitsBeforeCaret + 1; } for ( i = caretPosNew - 1; i >= 0; i -= 1 ) { if ( p.maskDigitPosMap[ i ] ) { maskDigitsBeforeCaretAll = maskDigitsBeforeCaretAll + 1; } } for ( i = caretPosOld - 1; i >= 0; i -= 1 ) { if ( p.maskDigitPosMapOld[ i ] ) { maskDigitsBeforeCaretAllOld = maskDigitsBeforeCaretAllOld + 1; } } // if the cursor is at the end keep it there if ( caretPosNew > oldValL ) { caretPosNew = newValL * 10; } else if ( caretPosOld >= caretPosNew && caretPosOld !== oldValL ) { if ( ! p.maskDigitPosMapOld[ caretPosNew ] ) { caretPos = caretPosNew; caretPosNew -= maskDigitsBeforeCaretAllOld - maskDigitsBeforeCaretAll; caretPosNew -= maskDigitsBeforeCaret; if ( p.maskDigitPosMap[ caretPosNew ] ) { caretPosNew = caretPos; } } } else if ( caretPosNew > caretPosOld ) { caretPosNew += maskDigitsBeforeCaretAll - maskDigitsBeforeCaretAllOld; caretPosNew += maskDigitsAfterCaret; } } return caretPosNew; }, behaviour: function( e ) { var keyCode = el.data( 'mask-keycode' ); var newVal; var oldVal; var caretPos; e = e || window.event; p.invalid = []; if ( $.inArray( keyCode, jMask.byPassKeys ) === -1 ) { newVal = p.getMasked(); caretPos = p.getCaret(); oldVal = el.data( 'mask-previus-value' ) || ''; // this is a compensation to devices/browsers that don't compensate // caret positioning the right way setTimeout( function() { p.setCaret( p.calculateCaretPosition( oldVal ) ); }, $.jMaskGlobals.keyStrokeCompensation ); p.val( newVal ); p.setCaret( caretPos ); return p.callbacks( e ); } }, getMasked: function( skipMaskChars, val ) { var buf = [], value = val === undefined ? p.val() : val + '', m = 0, maskLen = mask.length, v = 0, valLen = value.length, offset = 1, addMethod = 'push', resetPos = -1, maskDigitCount = 0, maskDigitPosArr = [], lastMaskChar, check; var lastUntranslatedMaskChar; var maskDigit; var valDigit; var translation; var lastMaskCharDigit; var newVal; if ( options.reverse ) { addMethod = 'unshift'; offset = -1; lastMaskChar = 0; m = maskLen - 1; v = valLen - 1; check = function() { return m > -1 && v > -1; }; } else { lastMaskChar = maskLen - 1; check = function() { return m < maskLen && v < valLen; }; } while ( check() ) { maskDigit = mask.charAt( m ); valDigit = value.charAt( v ); translation = jMask.translation[ maskDigit ]; if ( translation ) { if ( valDigit.match( translation.pattern ) ) { buf[ addMethod ]( valDigit ); if ( translation.recursive ) { if ( resetPos === -1 ) { resetPos = m; } else if ( m === lastMaskChar && m !== resetPos ) { m = resetPos - offset; } if ( lastMaskChar === resetPos ) { m -= offset; } } m += offset; } else if ( valDigit === lastUntranslatedMaskChar ) { // matched the last untranslated (raw) mask character that we encountered // likely an insert offset the mask character from the last entry; fall // through and only increment v maskDigitCount = maskDigitCount - 1; lastUntranslatedMaskChar = undefined; } else if ( translation.optional ) { m += offset; v -= offset; } else if ( translation.fallback ) { buf[ addMethod ]( translation.fallback ); m += offset; v -= offset; } else { p.invalid.push( { p: v, v: valDigit, e: translation.pattern } ); } v += offset; } else { if ( ! skipMaskChars ) { buf[ addMethod ]( maskDigit ); } if ( valDigit === maskDigit ) { maskDigitPosArr.push( v ); v += offset; } else { lastUntranslatedMaskChar = maskDigit; maskDigitPosArr.push( v + maskDigitCount ); maskDigitCount = maskDigitCount + 1; } m += offset; } } lastMaskCharDigit = mask.charAt( lastMaskChar ); if ( maskLen === valLen + 1 && ! jMask.translation[ lastMaskCharDigit ] ) { buf.push( lastMaskCharDigit ); } newVal = buf.join( '' ); p.mapMaskdigitPositions( newVal, maskDigitPosArr, valLen ); return newVal; }, mapMaskdigitPositions: function( newVal, maskDigitPosArr, valLen ) { var maskDiff = options.reverse ? newVal.length - valLen : 0; var i; p.maskDigitPosMap = {}; for ( i = 0; i < maskDigitPosArr.length; i += 1 ) { p.maskDigitPosMap[ maskDigitPosArr[ i ] + maskDiff ] = 1; } }, callbacks: function( e ) { var val = p.val(), changed = val !== oldValue, defaultArgs = [ val, e, el, options ], callback = function( name, criteria, args ) { if ( typeof options[ name ] === 'function' && criteria ) { options[ name ].apply( this, args ); } }; callback( 'onChange', changed === true, defaultArgs ); callback( 'onKeyPress', changed === true, defaultArgs ); callback( 'onComplete', val.length === mask.length, defaultArgs ); callback( 'onInvalid', p.invalid.length > 0, [ val, e, el, p.invalid, options ] ); } }; el = $( el ); oldValue = p.val(); mask = typeof mask === 'function' ? mask( p.val(), undefined, el, options ) : mask; // public methods jMask.mask = mask; jMask.options = options; jMask.remove = function() { var caret = p.getCaret(); if ( jMask.options.placeholder ) { el.removeAttr( 'placeholder' ); } if ( el.data( 'mask-maxlength' ) ) { el.removeAttr( 'maxlength' ); } p.destroyEvents(); p.val( jMask.getCleanVal() ); p.setCaret( caret ); return el; }; // get value without mask jMask.getCleanVal = function() { return p.getMasked( true ); }; // get masked value without the value being in the input or element jMask.getMaskedVal = function( val ) { return p.getMasked( false, val ); }; jMask.init = function( onlyMask ) { var i; var translation; var caret; var maxlength; onlyMask = onlyMask || false; options = options || {}; jMask.clearIfNotMatch = $.jMaskGlobals.clearIfNotMatch; jMask.byPassKeys = $.jMaskGlobals.byPassKeys; jMask.translation = $.extend( {}, $.jMaskGlobals.translation, options.translation ); jMask = $.extend( true, {}, jMask, options ); regexMask = p.getRegexMask(); if ( onlyMask ) { p.events(); p.val( p.getMasked() ); } else { if ( options.placeholder ) { el.attr( 'placeholder', options.placeholder ); } // this is necessary, otherwise if the user submit the form // and then press the "back" button, the autocomplete will erase // the data. Works fine on IE9+, FF, Opera, Safari. if ( el.data( 'mask' ) ) { el.attr( 'autocomplete', 'off' ); } // detect if is necessary let the user type freely. // for is a lot faster than forEach. for ( i = 0, maxlength = true; i < mask.length; i += 1 ) { translation = jMask.translation[ mask.charAt( i ) ]; if ( translation && translation.recursive ) { maxlength = false; break; } } if ( maxlength ) { el.attr( 'maxlength', mask.length ).data( 'mask-maxlength', true ); } p.destroyEvents(); p.events(); caret = p.getCaret(); p.val( p.getMasked() ); p.setCaret( caret ); } }; jMask.init( ! el.is( 'input' ) ); }; var notSameMaskObject = function( field, mask, options ) { var maskObject = $( field ).data( 'mask' ), stringify = JSON.stringify, value = $( field ).val() || $( field ).text(); options = options || {}; try { if ( typeof mask === 'function' ) { mask = mask( value ); } return typeof maskObject !== 'object' || stringify( maskObject.options ) !== stringify( options ) || maskObject.mask !== mask; } catch ( err ) { window.console.log( err ); } }, HTMLAttributes = function() { var input = $( this ), options = {}, prefix = 'data-mask-', mask = input.attr( 'data-mask' ); if ( input.attr( prefix + 'reverse' ) ) { options.reverse = true; } if ( input.attr( prefix + 'clearifnotmatch' ) ) { options.clearIfNotMatch = true; } if ( input.attr( prefix + 'selectonfocus' ) === 'true' ) { options.selectOnFocus = true; } if ( notSameMaskObject( input, mask, options ) ) { return input.data( 'mask', new Mask( this, mask, options ) ); } }, eventSupported = function( eventName ) { var el = document.createElement( 'div' ), isSupported; eventName = 'on' + eventName; isSupported = eventName in el; if ( ! isSupported ) { el.setAttribute( eventName, 'return;' ); isSupported = typeof el[ eventName ] === 'function'; } el = null; return isSupported; }; $.maskWatchers = {}; $.fn.mask = function( mask, options ) { var selector = this.selector, maskGlobals = $.jMaskGlobals, interval = maskGlobals.watchInterval, watchInputs, maskFunction; options = options || {}; watchInputs = options.watchInputs || maskGlobals.watchInputs; maskFunction = function() { if ( notSameMaskObject( this, mask, options ) ) { return $( this ).data( 'mask', new Mask( this, mask, options ) ); } }; $( this ).each( maskFunction ); if ( selector && selector !== '' && watchInputs ) { clearInterval( $.maskWatchers[ selector ] ); $.maskWatchers[ selector ] = setInterval( function() { $( document ).find( selector ).each( maskFunction ); }, interval ); } return this; }; $.fn.masked = function( val ) { return this.data( 'mask' ).getMaskedVal( val ); }; $.fn.unmask = function() { clearInterval( $.maskWatchers[ this.selector ] ); delete $.maskWatchers[ this.selector ]; return this.each( function() { var dataMask = $( this ).data( 'mask' ); if ( dataMask ) { dataMask.remove().removeData( 'mask' ); } } ); }; $.fn.cleanVal = function() { return this.data( 'mask' ).getCleanVal(); }; $.applyDataMask = function( selector ) { var $selector; selector = selector || $.jMaskGlobals.maskElements; $selector = selector instanceof $ ? selector : $( selector ); $selector.filter( $.jMaskGlobals.dataMaskAttr ).each( HTMLAttributes ); }; globals = { maskElements: 'input,td,span,div', dataMaskAttr: '*[data-mask]', dataMask: true, watchInterval: 300, watchInputs: true, keyStrokeCompensation: 10, // old versions of chrome dont work great with input event useInput: ! /Chrome\/[2-4][0-9]|SamsungBrowser/.test( window.navigator.userAgent ) && eventSupported( 'input' ), watchDataMask: false, byPassKeys: [ 9, 16, 17, 18, 36, 37, 38, 39, 40, 91 ], translation: { 0: { pattern: /\d/ }, 9: { pattern: /\d/, optional: true }, '#': { pattern: /\d/, recursive: true }, A: { pattern: /[a-zA-Z0-9]/ }, S: { pattern: /[a-zA-Z]/ } } }; $.jMaskGlobals = $.jMaskGlobals || {}; globals = $.jMaskGlobals = $.extend( true, {}, globals, $.jMaskGlobals ); // looking for inputs with data-mask attribute if ( globals.dataMask ) { $.applyDataMask(); } setInterval( function() { if ( $.jMaskGlobals.watchDataMask ) { $.applyDataMask(); } }, globals.watchInterval ); }, window.jQuery, window.Zepto ) );