diff --git a/demo/index.html b/demo/index.html index 60d7e12..8efda6a 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,7 +2,7 @@ jQuery Mask Test - + @@ -38,6 +40,8 @@ Eye Script~9.99 ~9.99 999 Purchase Orderaaa-999-*** Percent99% + Phone (autoclear=false)(999) 999-9999 + Phone + Ext (autoclear=false)(999) 999-9999? x99999
diff --git a/dist/jquery.maskedinput.js b/dist/jquery.maskedinput.js index 89ce020..5c84271 100644 --- a/dist/jquery.maskedinput.js +++ b/dist/jquery.maskedinput.js @@ -9,12 +9,13 @@ var el = document.createElement('input'), name = 'onpaste'; el.setAttribute(name, ''); - return (typeof el[name] === 'function')?'paste':'input'; + return (typeof el[name] === 'function')?'paste':'input'; } var pasteEventName = getPasteEvent() + ".mask", ua = navigator.userAgent, iPhone = /iphone/i.test(ua), + chrome = /chrome/i.test(ua), android=/android/i.test(ua), caretTimeoutId; @@ -25,6 +26,7 @@ $.mask = { 'a': "[A-Za-z]", '*': "[A-Za-z0-9]" }, + autoclear: true, dataName: "rawMaskFn", placeholder: '_' }; @@ -79,6 +81,7 @@ $.fn.extend({ return input.data($.mask.dataName)(); } settings = $.extend({ + autoclear: $.mask.autoclear, placeholder: $.mask.placeholder, // Load default placeholder completed: null }, settings); @@ -112,6 +115,7 @@ $.fn.extend({ return defs[c] ? settings.placeholder : c; } }), + defaultBuffer = buffer.join(''), focusText = input.val(); function seekNext(pos) { @@ -202,6 +206,22 @@ $.fn.extend({ c, next; + if (k == 0) { + // unable to detect key pressed. Grab it from pos and adjust + // this is a failsafe for mobile chrome + // which can't detect keypress events + // reliably + if (pos.begin >= len) { + input.val(input.val().substr(0, len)); + e.preventDefault(); + return false; + } + if (pos.begin == pos.end) { + k = input.val().charCodeAt(pos.begin - 1); + pos.begin--; + pos.end--; + } + } if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore return; } else if (k) { @@ -251,7 +271,8 @@ $.fn.extend({ var test = input.val(), lastMatch = -1, i, - c; + c, + pos; for (i = 0, pos = 0; i < len; i++) { if (tests[i]) { @@ -275,8 +296,16 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - input.val(""); - clearBuffer(0, len); + if (settings.autoclear || buffer.join('') === defaultBuffer) { + // Invalid value. Remove it and replace it with the + // mask, which is the default behavior. + input.val(""); + clearBuffer(0, len); + } else { + // Invalid value, but we opt to show the value to the + // user and allow them to correct their mistake. + writeBuffer(); + } } else { writeBuffer(); input.val(input.val().substring(0, lastMatch + 1)); @@ -299,12 +328,12 @@ $.fn.extend({ }) .bind("focus.mask", function() { clearTimeout(caretTimeoutId); - var pos, - moveCaret; + var pos; focusText = input.val(); + pos = checkVal(); - + caretTimeoutId = setTimeout(function(){ writeBuffer(); if (pos == mask.length) { @@ -316,23 +345,26 @@ $.fn.extend({ }) .bind("blur.mask", function() { checkVal(); + if (input.val() != focusText) input.change(); }) .bind("keydown.mask", keydownEvent) .bind("keypress.mask", keypressEvent) .bind(pasteEventName, function() { - setTimeout(function() { + setTimeout(function() { var pos=checkVal(true); - input.caret(pos); + input.caret(pos); if (settings.completed && pos == input.val().length) settings.completed.call(input); }, 0); }); - checkVal(); //Perform initial check for existing values + if (chrome && android) { + input.bind("keyup.mask", keypressEvent); + } + checkVal(); //Perform initial check for existing values }); } }); - -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/dist/jquery.maskedinput.min.js b/dist/jquery.maskedinput.min.js index 0d9ce6e..818d1c8 100644 --- a/dist/jquery.maskedinput.min.js +++ b/dist/jquery.maskedinput.min.js @@ -4,4 +4,4 @@ Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) Version: 1.3.1 */ -(function(e){function t(){var e=document.createElement("input"),t="onpaste";return e.setAttribute(t,""),"function"==typeof e[t]?"paste":"input"}var n,a=t()+".mask",r=navigator.userAgent,i=/iphone/i.test(r),o=/android/i.test(r);e.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},dataName:"rawMaskFn",placeholder:"_"},e.fn.extend({caret:function(e,t){var n;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof e?(t="number"==typeof t?t:e,this.each(function(){this.setSelectionRange?this.setSelectionRange(e,t):this.createTextRange&&(n=this.createTextRange(),n.collapse(!0),n.moveEnd("character",t),n.moveStart("character",e),n.select())})):(this[0].setSelectionRange?(e=this[0].selectionStart,t=this[0].selectionEnd):document.selection&&document.selection.createRange&&(n=document.selection.createRange(),e=0-n.duplicate().moveStart("character",-1e5),t=e+n.text.length),{begin:e,end:t})},unmask:function(){return this.trigger("unmask")},mask:function(t,r){var c,l,s,u,f,h;return!t&&this.length>0?(c=e(this[0]),c.data(e.mask.dataName)()):(r=e.extend({placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,s=[],u=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,u=e):l[t]?(s.push(RegExp(l[t])),null===f&&(f=s.length-1)):s.push(null)}),this.trigger("unmask").each(function(){function c(e){for(;h>++e&&!s[e];);return e}function d(e){for(;--e>=0&&!s[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=c(t);h>n;n++)if(s[n]){if(!(h>a&&s[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=c(a)}b(),x.caret(Math.max(f,e))}}function p(e){var t,n,a,i;for(t=e,n=r.placeholder;h>t;t++)if(s[t]){if(a=c(t),i=R[t],R[t]=n,!(h>a&&s[a].test(i)))break;n=i}}function g(e){var t,n,a,r=e.which;8===r||46===r||i&&127===r?(t=x.caret(),n=t.begin,a=t.end,0===a-n&&(n=46!==r?d(n):a=c(n-1),a=46===r?c(a):a),k(n,a),m(n,a-1),e.preventDefault()):27==r&&(x.val(S),x.caret(0,y()),e.preventDefault())}function v(t){var n,a,i,l=t.which,u=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==u.end-u.begin&&(k(u.begin,u.end),m(u.begin,u.end-1)),n=c(u.begin-1),h>n&&(a=String.fromCharCode(l),s[n].test(a)&&(p(n),R[n]=a,b(),i=c(n),o?setTimeout(e.proxy(e.fn.caret,x,i),0):x.caret(i),r.completed&&i>=h&&r.completed.call(x))),t.preventDefault())}function k(e,t){var n;for(n=e;t>n&&h>n;n++)s[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a=x.val(),i=-1;for(t=0,pos=0;h>t;t++)if(s[t]){for(R[t]=r.placeholder;pos++a.length)break}else R[t]===a.charAt(pos)&&t!==u&&(pos++,i=t);return e?b():u>i+1?(x.val(""),k(0,h)):(b(),x.val(x.val().substring(0,i+1))),u?t:f}var x=e(this),R=e.map(t.split(""),function(e){return"?"!=e?l[e]?r.placeholder:e:void 0}),S=x.val();x.data(e.mask.dataName,function(){return e.map(R,function(e,t){return s[t]&&e!=r.placeholder?e:null}).join("")}),x.attr("readonly")||x.one("unmask",function(){x.unbind(".mask").removeData(e.mask.dataName)}).bind("focus.mask",function(){clearTimeout(n);var e;S=x.val(),e=y(),n=setTimeout(function(){b(),e==t.length?x.caret(0,e):x.caret(e)},10)}).bind("blur.mask",function(){y(),x.val()!=S&&x.change()}).bind("keydown.mask",g).bind("keypress.mask",v).bind(a,function(){setTimeout(function(){var e=y(!0);x.caret(e),r.completed&&e==x.val().length&&r.completed.call(x)},0)}),y()}))}})})(jQuery); \ No newline at end of file +!function(e){function t(){var e=document.createElement("input"),t="onpaste";return e.setAttribute(t,""),"function"==typeof e[t]?"paste":"input"}var n,a=t()+".mask",r=navigator.userAgent,i=/iphone/i.test(r),c=/chrome/i.test(r),o=/android/i.test(r);e.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},autoclear:!0,dataName:"rawMaskFn",placeholder:"_"},e.fn.extend({caret:function(e,t){var n;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof e?(t="number"==typeof t?t:e,this.each(function(){this.setSelectionRange?this.setSelectionRange(e,t):this.createTextRange&&(n=this.createTextRange(),n.collapse(!0),n.moveEnd("character",t),n.moveStart("character",e),n.select())})):(this[0].setSelectionRange?(e=this[0].selectionStart,t=this[0].selectionEnd):document.selection&&document.selection.createRange&&(n=document.selection.createRange(),e=0-n.duplicate().moveStart("character",-1e5),t=e+n.text.length),{begin:e,end:t})},unmask:function(){return this.trigger("unmask")},mask:function(t,r){var l,u,s,f,d,h;return!t&&this.length>0?(l=e(this[0]),l.data(e.mask.dataName)()):(r=e.extend({autoclear:e.mask.autoclear,placeholder:e.mask.placeholder,completed:null},r),u=e.mask.definitions,s=[],f=h=t.length,d=null,e.each(t.split(""),function(e,t){"?"==t?(h--,f=e):u[t]?(s.push(new RegExp(u[t])),null===d&&(d=s.length-1)):s.push(null)}),this.trigger("unmask").each(function(){function l(e){for(;++e=0&&!s[e];);return e}function p(e,t){var n,a;if(!(0>e)){for(n=e,a=l(t);h>n;n++)if(s[n]){if(!(h>a&&s[n].test(A[a])))break;A[n]=A[a],A[a]=r.placeholder,a=l(a)}y(),R.caret(Math.max(d,e))}}function g(e){var t,n,a,i;for(t=e,n=r.placeholder;h>t;t++)if(s[t]){if(a=l(t),i=A[t],A[t]=n,!(h>a&&s[a].test(i)))break;n=i}}function v(e){var t,n,a,r=e.which;8===r||46===r||i&&127===r?(t=R.caret(),n=t.begin,a=t.end,0===a-n&&(n=46!==r?m(n):a=l(n-1),a=46===r?l(a):a),k(n,a),p(n,a-1),e.preventDefault()):27==r&&(R.val(T),R.caret(0,x()),e.preventDefault())}function b(t){var n,a,i,c=t.which,u=R.caret();if(0==c){if(u.begin>=h)return R.val(R.val().substr(0,h)),t.preventDefault(),!1;u.begin==u.end&&(c=R.val().charCodeAt(u.begin-1),u.begin--,u.end--)}t.ctrlKey||t.altKey||t.metaKey||32>c||c&&(0!==u.end-u.begin&&(k(u.begin,u.end),p(u.begin,u.end-1)),n=l(u.begin-1),h>n&&(a=String.fromCharCode(c),s[n].test(a)&&(g(n),A[n]=a,y(),i=l(n),o?setTimeout(e.proxy(e.fn.caret,R,i),0):R.caret(i),r.completed&&i>=h&&r.completed.call(R))),t.preventDefault())}function k(e,t){var n;for(n=e;t>n&&h>n;n++)s[n]&&(A[n]=r.placeholder)}function y(){R.val(A.join(""))}function x(e){var t,n,a,i=R.val(),c=-1;for(t=0,a=0;h>t;t++)if(s[t]){for(A[t]=r.placeholder;a++i.length)break}else A[t]===i.charAt(a)&&t!==f&&(a++,c=t);return e?y():f>c+1?r.autoclear||A.join("")===S?(R.val(""),k(0,h)):y():(y(),R.val(R.val().substring(0,c+1))),f?t:d}var R=e(this),A=e.map(t.split(""),function(e){return"?"!=e?u[e]?r.placeholder:e:void 0}),S=A.join(""),T=R.val();R.data(e.mask.dataName,function(){return e.map(A,function(e,t){return s[t]&&e!=r.placeholder?e:null}).join("")}),R.attr("readonly")||R.one("unmask",function(){R.unbind(".mask").removeData(e.mask.dataName)}).bind("focus.mask",function(){clearTimeout(n);var e;T=R.val(),e=x(),n=setTimeout(function(){y(),e==t.length?R.caret(0,e):R.caret(e)},10)}).bind("blur.mask",function(){x(),R.val()!=T&&R.change()}).bind("keydown.mask",v).bind("keypress.mask",b).bind(a,function(){setTimeout(function(){var e=x(!0);R.caret(e),r.completed&&e==R.val().length&&r.completed.call(R)},0)}),c&&o&&R.bind("keyup.mask",b),x()}))}})}(jQuery); \ No newline at end of file diff --git a/spec/Focus.Spec.js b/spec/Focus.Spec.js index b7a2143..f469bf5 100644 --- a/spec/Focus.Spec.js +++ b/spec/Focus.Spec.js @@ -50,6 +50,24 @@ feature("Focusing A Masked Input",function(){ expect(error).toBeUndefined(); }) }); + + scenario("Mask contains a partial value with autoclear set to false",function(){ + given("the input has a partial value",function(){ + input.val("1"); + }); + given("a mask with two placeholders and autoclear=false",function(){ + input.mask("99", { autoclear: false }); + }); + when("focusing on the input",function(){ + input.focus(); + }); + then("the value should be partially filled out",function(){ + expect(input).toHaveValue("1_"); + }); + then("the input partial value should remain",function(){ + expect(input).toHaveValue("1_"); + }); + }); }); feature("Leaving A Masked Input",function(){ @@ -76,6 +94,20 @@ feature("Leaving A Masked Input",function(){ expect(input).toHaveValue(""); }); }); + + scenario("Empty placeholders remaining with autoclear set to false",function(){ + given("a mask with two placeholders",function(){ + input.mask("99", { autoclear: false }); + }); + when("typing one character and blurring",function(){ + input.caret(0); + input.mashKeys("1") + input.blur(); + }); + then("value should remain visible with placeholders",function(){ + expect(input).toHaveValue("1_"); + }); + }); }); feature("Optional marker",function(){ @@ -91,6 +123,18 @@ feature("Optional marker",function(){ }); }); + scenario("Placeholders not filled to marker and autoclear = false", function() { + given("a mask with an optional marker",function(){ + input.mask("99?99", { autoclear: false }); + }); + when("typing one character and leaving",function(){ + input.mashKeys("1").blur(); + }); + then("value should be empty",function(){ + expect(input).toHaveValue("1___"); + }); + }); + scenario("Placeholders filled to marker",function(){ given("a mask with an optional marker",function(){ input.mask("99?99"); @@ -102,4 +146,40 @@ feature("Optional marker",function(){ expect(input).toHaveValue("12"); }); }); + + scenario("Placeholders filled to marker and autoclear = false", function() { + given("a mask with an optional marker",function(){ + input.mask("99?99", { autoclear: false }); + }); + when("typing two characters and leaving",function(){ + input.mashKeys("12").blur(); + }); + then("value should remain",function(){ + expect(input).toHaveValue("12"); + }); + }); + + scenario("Placeholders filled, one marker filled, and autoclear = false", function() { + given("a mask with an optional marker",function(){ + input.mask("99?99", { autoclear: false }); + }); + when("typing three characters and leaving",function(){ + input.mashKeys("123").blur(); + }); + then("value should remain",function(){ + expect(input).toHaveValue("123"); + }); + }); + + scenario("Placeholders and markers filled, and autoclear = false", function() { + given("a mask with an optional marker",function(){ + input.mask("99?99", { autoclear: false }); + }); + when("typing four characters and leaving",function(){ + input.mashKeys("1234").blur(); + }); + then("value should remain",function(){ + expect(input).toHaveValue("1234"); + }); + }); }); diff --git a/spec/Init.Spec.js b/spec/Init.Spec.js new file mode 100644 index 0000000..0bd7dea --- /dev/null +++ b/spec/Init.Spec.js @@ -0,0 +1,61 @@ +feature("Initializing a Mask",function(){ + scenario("An input with no value",function(){ + given("an input with no value",function(){ + input.val(""); + }); + when("setting a mask with two placeholders",function(){ + input.mask("99"); + }); + then("the value should be an empty string",function(){ + expect(input).toHaveValue(""); + }); + }); + + scenario("An input with a valid value and no placeholders remaining",function(){ + given("an input with a valid value",function(){ + input.val("5555555555"); + }); + when("setting a mask",function(){ + input.mask("(999) 999-9999"); + }); + then("the value should be intact",function(){ + expect(input).toHaveValue("(555) 555-5555"); + }); + }); + + scenario("An input with an invalid value and placeholders remaining",function(){ + given("an invalid input value",function(){ + input.val("55555555"); + }); + when("setting a mask",function(){ + input.mask("(999) 999-9999"); + }); + then("the value should be empty",function(){ + expect(input).toHaveValue(""); + }); + }); + + scenario("An input with an invalid value, placeholders remaining and autoclear set to false",function(){ + given("an invalid input value",function(){ + input.val("55555555"); + }); + when("setting a mask with autoclear set to false",function(){ + input.mask("(999) 999-9999", { autoclear: false }); + }); + then("the value be intact with placeholders visible",function(){ + expect(input).toHaveValue("(555) 555-55__"); + }); + }); + + scenario("An input no value and autoclear set to false", function() { + given("an input with no value",function(){ + input.val(""); + }); + when("setting a mask with autoclear set to false",function(){ + input.mask("(999) 999-9999", { autoclear: false }); + }); + then("the value should be empty",function(){ + expect(input).toHaveValue(""); + }); + }); +}); diff --git a/spec/Raw.Spec.js b/spec/Raw.Spec.js index 0516971..97be1f8 100644 --- a/spec/Raw.Spec.js +++ b/spec/Raw.Spec.js @@ -54,4 +54,86 @@ feature("Getting raw value",function(){ expect(input.mask()).toEqual("12"); }); }); +}); + +feature("Getting raw value with autoclear set to false", function() { + scenario("After typing",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("9/9", { autoclear: false }); + }); + + when("typing all numbers",function(){ + input.mashKeys("12"); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual("12"); + }); + }); + + scenario("While typing",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("9/9", { autoclear: false }); + }); + + when("typing a number",function(){ + input.mashKeys("1"); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual("1"); + }); + }); + + scenario("Before typing",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("9/9", { autoclear: false }); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual(""); + }); + }); + + scenario("After typing partial input past an optional marker",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("9?99", { autoclear: false }); + }); + + when("typing a partial input",function(){ + input.mashKeys("12"); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual("12"); + }); + }); + + scenario("After typing partial input",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("99?99", { autoclear: false }); + }); + + when("typing a partial input",function(){ + input.mashKeys("1"); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual("1"); + }); + }); + + scenario("After typing partial input up to an optional marker",function(){ + given("an input with a mask containing a literal", function(){ + input.mask("9?99", { autoclear: false }); + }); + + when("typing a partial input",function(){ + input.mashKeys("1"); + }); + + then("raw value should be correct",function(){ + expect(input.mask()).toEqual("1"); + }); + }); }); \ No newline at end of file diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html index 0339b85..6747474 100644 --- a/spec/SpecRunner.html +++ b/spec/SpecRunner.html @@ -40,7 +40,8 @@ - + + diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 9c0d392..a9a2ae5 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -2,7 +2,7 @@ function getPasteEvent() { var el = document.createElement('input'), name = 'onpaste'; el.setAttribute(name, ''); - return (typeof el[name] === 'function')?'paste':'input'; + return (typeof el[name] === 'function')?'paste':'input'; } var pasteEventName = getPasteEvent() + ".mask", @@ -19,6 +19,7 @@ $.mask = { 'a': "[A-Za-z]", '*': "[A-Za-z0-9]" }, + autoclear: true, dataName: "rawMaskFn", placeholder: '_' }; @@ -73,6 +74,7 @@ $.fn.extend({ return input.data($.mask.dataName)(); } settings = $.extend({ + autoclear: $.mask.autoclear, placeholder: $.mask.placeholder, // Load default placeholder completed: null }, settings); @@ -106,6 +108,7 @@ $.fn.extend({ return defs[c] ? settings.placeholder : c; } }), + defaultBuffer = buffer.join(''), focusText = input.val(); function seekNext(pos) { @@ -286,8 +289,16 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - input.val(""); - clearBuffer(0, len); + if (settings.autoclear || buffer.join('') === defaultBuffer) { + // Invalid value. Remove it and replace it with the + // mask, which is the default behavior. + input.val(""); + clearBuffer(0, len); + } else { + // Invalid value, but we opt to show the value to the + // user and allow them to correct their mistake. + writeBuffer(); + } } else { writeBuffer(); input.val(input.val().substring(0, lastMatch + 1)); @@ -313,8 +324,9 @@ $.fn.extend({ var pos; focusText = input.val(); + pos = checkVal(); - + caretTimeoutId = setTimeout(function(){ writeBuffer(); if (pos == mask.length) { @@ -326,15 +338,16 @@ $.fn.extend({ }) .bind("blur.mask", function() { checkVal(); + if (input.val() != focusText) input.change(); }) .bind("keydown.mask", keydownEvent) .bind("keypress.mask", keypressEvent) .bind(pasteEventName, function() { - setTimeout(function() { + setTimeout(function() { var pos=checkVal(true); - input.caret(pos); + input.caret(pos); if (settings.completed && pos == input.val().length) settings.completed.call(input); }, 0); @@ -342,7 +355,7 @@ $.fn.extend({ if (chrome && android) { input.bind("keyup.mask", keypressEvent); } - checkVal(); //Perform initial check for existing values + checkVal(); //Perform initial check for existing values }); } });