From 0dea72e516f8c3443b1e321d5504421d43a5c311 Mon Sep 17 00:00:00 2001 From: Greg Burghardt Date: Wed, 9 Oct 2013 11:07:46 -0400 Subject: [PATCH 1/5] Added "autoclear" setting so invalid values persist To increase usability, add a new setting that keeps invalid values in the input field to give the user a chance to correct a mistake. If the user fat fingers the value on accident and doesn't realize it before tabbing away from the form field, the invalid value will remain so the user can correct the value rather than retyping it. - Added new setting called "autoclear", defaulting to true, to maintain existing behavior. - When autoclear=false, leave invalid values visible so the user can correct their mistake. - Added test coverage for when the mask(...) is first invoked (Init.Spec.js) - Added scenarios to Focus.Spec.js to cover new functionality - Repackaged the distribution version to include this new feature --- dist/jquery.maskedinput.js | 33 ++++++++++++++-------- dist/jquery.maskedinput.min.js | 2 +- spec/Focus.Spec.js | 32 +++++++++++++++++++++ spec/Init.Spec.js | 51 ++++++++++++++++++++++++++++++++++ spec/SpecRunner.html | 3 +- src/jquery.maskedinput.js | 27 +++++++++++++----- 6 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 spec/Init.Spec.js diff --git a/dist/jquery.maskedinput.js b/dist/jquery.maskedinput.js index 89ce020..7ecfcc0 100644 --- a/dist/jquery.maskedinput.js +++ b/dist/jquery.maskedinput.js @@ -9,7 +9,7 @@ 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", @@ -25,6 +25,7 @@ $.mask = { 'a': "[A-Za-z]", '*': "[A-Za-z0-9]" }, + autoclear: true, dataName: "rawMaskFn", placeholder: '_' }; @@ -79,6 +80,7 @@ $.fn.extend({ return input.data($.mask.dataName)(); } settings = $.extend({ + autoclear: $.mask.autoclear, placeholder: $.mask.placeholder, // Load default placeholder completed: null }, settings); @@ -251,7 +253,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 +278,14 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - input.val(""); - clearBuffer(0, len); + if (settings.autoclear) { + 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 +308,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 +325,25 @@ $.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 + + 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..558899e 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=/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 o,l,u,s,f,h;return!t&&this.length>0?(o=e(this[0]),o.data(e.mask.dataName)()):(r=e.extend({autoclear:e.mask.autoclear,placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,u=[],s=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,s=e):l[t]?(u.push(new RegExp(l[t])),null===f&&(f=u.length-1)):u.push(null)}),this.trigger("unmask").each(function(){function o(e){for(;++e=0&&!u[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=o(t);h>n;n++)if(u[n]){if(!(h>a&&u[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=o(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(u[t]){if(a=o(t),i=R[t],R[t]=n,!(h>a&&u[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=o(n-1),a=46===r?o(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,s=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==s.end-s.begin&&(k(s.begin,s.end),m(s.begin,s.end-1)),n=o(s.begin-1),h>n&&(a=String.fromCharCode(l),u[n].test(a)&&(p(n),R[n]=a,b(),i=o(n),c?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++)u[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a,i=x.val(),c=-1;for(t=0,a=0;h>t;t++)if(u[t]){for(R[t]=r.placeholder;a++i.length)break}else R[t]===i.charAt(a)&&t!==s&&(a++,c=t);return e?b():s>c+1?r.autoclear?(x.val(""),k(0,h)):b():(b(),x.val(x.val().substring(0,c+1))),s?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 u[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 diff --git a/spec/Focus.Spec.js b/spec/Focus.Spec.js index b7a2143..01ddebd 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(){ diff --git a/spec/Init.Spec.js b/spec/Init.Spec.js new file mode 100644 index 0000000..1b64130 --- /dev/null +++ b/spec/Init.Spec.js @@ -0,0 +1,51 @@ +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 with an invalid value and placeholders remaining"); +}); 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 749da65..6382f70 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", @@ -18,6 +18,7 @@ $.mask = { 'a': "[A-Za-z]", '*': "[A-Za-z0-9]" }, + autoclear: true, dataName: "rawMaskFn", placeholder: '_' }; @@ -72,6 +73,7 @@ $.fn.extend({ return input.data($.mask.dataName)(); } settings = $.extend({ + autoclear: $.mask.autoclear, placeholder: $.mask.placeholder, // Load default placeholder completed: null }, settings); @@ -269,8 +271,16 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - input.val(""); - clearBuffer(0, len); + if (settings.autoclear) { + // 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)); @@ -296,8 +306,9 @@ $.fn.extend({ var pos; focusText = input.val(); + pos = checkVal(); - + caretTimeoutId = setTimeout(function(){ writeBuffer(); if (pos == mask.length) { @@ -309,20 +320,22 @@ $.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 + + checkVal(); //Perform initial check for existing values }); } }); From b9c0097c176d41c9d7e3d3dd6be5fed8115abcda Mon Sep 17 00:00:00 2001 From: Greg Burghardt Date: Thu, 10 Oct 2013 13:17:49 -0400 Subject: [PATCH 2/5] Fixing default mask display with "autoclear" set to false When a mask is initialized, a text field with an empty value should show up as a blank field. When "autoclear" was set to false, it showed the mask even when the user did not have focus on the field. This fixes that issue. --- demo/index.html | 8 +++++--- dist/jquery.maskedinput.js | 5 ++++- dist/jquery.maskedinput.min.js | 2 +- spec/Init.Spec.js | 12 +++++++++++- src/jquery.maskedinput.js | 3 ++- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/demo/index.html b/demo/index.html index 60d7e12..68dfe10 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,7 +2,7 @@ jQuery Mask Test - + @@ -38,6 +39,7 @@ Eye Script~9.99 ~9.99 999 Purchase Orderaaa-999-*** Percent99% + Phone (autoclear=false)99%
diff --git a/dist/jquery.maskedinput.js b/dist/jquery.maskedinput.js index 7ecfcc0..656174f 100644 --- a/dist/jquery.maskedinput.js +++ b/dist/jquery.maskedinput.js @@ -114,6 +114,7 @@ $.fn.extend({ return defs[c] ? settings.placeholder : c; } }), + defaultBuffer = buffer.join(''), focusText = input.val(); function seekNext(pos) { @@ -278,7 +279,9 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - if (settings.autoclear) { + 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 { diff --git a/dist/jquery.maskedinput.min.js b/dist/jquery.maskedinput.min.js index 558899e..9c6056c 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),c=/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 o,l,u,s,f,h;return!t&&this.length>0?(o=e(this[0]),o.data(e.mask.dataName)()):(r=e.extend({autoclear:e.mask.autoclear,placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,u=[],s=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,s=e):l[t]?(u.push(new RegExp(l[t])),null===f&&(f=u.length-1)):u.push(null)}),this.trigger("unmask").each(function(){function o(e){for(;++e=0&&!u[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=o(t);h>n;n++)if(u[n]){if(!(h>a&&u[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=o(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(u[t]){if(a=o(t),i=R[t],R[t]=n,!(h>a&&u[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=o(n-1),a=46===r?o(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,s=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==s.end-s.begin&&(k(s.begin,s.end),m(s.begin,s.end-1)),n=o(s.begin-1),h>n&&(a=String.fromCharCode(l),u[n].test(a)&&(p(n),R[n]=a,b(),i=o(n),c?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++)u[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a,i=x.val(),c=-1;for(t=0,a=0;h>t;t++)if(u[t]){for(R[t]=r.placeholder;a++i.length)break}else R[t]===i.charAt(a)&&t!==s&&(a++,c=t);return e?b():s>c+1?r.autoclear?(x.val(""),k(0,h)):b():(b(),x.val(x.val().substring(0,c+1))),s?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 u[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=/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 o,l,u,s,f,h;return!t&&this.length>0?(o=e(this[0]),o.data(e.mask.dataName)()):(r=e.extend({autoclear:e.mask.autoclear,placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,u=[],s=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,s=e):l[t]?(u.push(new RegExp(l[t])),null===f&&(f=u.length-1)):u.push(null)}),this.trigger("unmask").each(function(){function o(e){for(;++e=0&&!u[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=o(t);h>n;n++)if(u[n]){if(!(h>a&&u[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=o(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(u[t]){if(a=o(t),i=R[t],R[t]=n,!(h>a&&u[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=o(n-1),a=46===r?o(a):a),k(n,a),m(n,a-1),e.preventDefault()):27==r&&(x.val(A),x.caret(0,y()),e.preventDefault())}function v(t){var n,a,i,l=t.which,s=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==s.end-s.begin&&(k(s.begin,s.end),m(s.begin,s.end-1)),n=o(s.begin-1),h>n&&(a=String.fromCharCode(l),u[n].test(a)&&(p(n),R[n]=a,b(),i=o(n),c?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++)u[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a,i=x.val(),c=-1;for(t=0,a=0;h>t;t++)if(u[t]){for(R[t]=r.placeholder;a++i.length)break}else R[t]===i.charAt(a)&&t!==s&&(a++,c=t);return e?b():s>c+1?r.autoclear||R.join("")===S?(x.val(""),k(0,h)):b():(b(),x.val(x.val().substring(0,c+1))),s?t:f}var x=e(this),R=e.map(t.split(""),function(e){return"?"!=e?l[e]?r.placeholder:e:void 0}),S=R.join(""),A=x.val();x.data(e.mask.dataName,function(){return e.map(R,function(e,t){return u[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;A=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()!=A&&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 diff --git a/spec/Init.Spec.js b/spec/Init.Spec.js index 1b64130..0bd7dea 100644 --- a/spec/Init.Spec.js +++ b/spec/Init.Spec.js @@ -47,5 +47,15 @@ feature("Initializing a Mask",function(){ }); }); - scenario("An input with an invalid value and placeholders remaining"); + 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/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 6382f70..060a946 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -107,6 +107,7 @@ $.fn.extend({ return defs[c] ? settings.placeholder : c; } }), + defaultBuffer = buffer.join(''), focusText = input.val(); function seekNext(pos) { @@ -271,7 +272,7 @@ $.fn.extend({ if (allow) { writeBuffer(); } else if (lastMatch + 1 < partialPosition) { - if (settings.autoclear) { + if (settings.autoclear || buffer.join('') === defaultBuffer) { // Invalid value. Remove it and replace it with the // mask, which is the default behavior. input.val(""); From debc0d1878fca126c09acdaccedae20d95a507b6 Mon Sep 17 00:00:00 2001 From: Greg Burghardt Date: Thu, 31 Oct 2013 09:00:07 -0400 Subject: [PATCH 3/5] Adding demo with optional markers and autoclear=false --- demo/index.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/index.html b/demo/index.html index 68dfe10..8efda6a 100644 --- a/demo/index.html +++ b/demo/index.html @@ -16,7 +16,8 @@ $("#eyescript").mask("~9.99 ~9.99 999"); $("#po").mask("PO: aaa-999-***"); $("#pct").mask("99%"); - $("#phoneAutoclearFalse").mask("(999) 999-999-9999", { autoclear: false }); + $("#phoneAutoclearFalse").mask("(999) 999-9999", { autoclear: false }); + $("#phoneExtAutoclearFalse").mask("(999) 999-9999? x99999", { autoclear: false }); $("input").blur(function() { $("#info").html("Unmasked value: " + $(this).mask()); @@ -39,7 +40,8 @@ Eye Script~9.99 ~9.99 999 Purchase Orderaaa-999-*** Percent99% - Phone (autoclear=false)99% + Phone (autoclear=false)(999) 999-9999 + Phone + Ext (autoclear=false)(999) 999-9999? x99999
From 2f518e6f37df7b1bde026b4fa4a284faee686905 Mon Sep 17 00:00:00 2001 From: Greg Burghardt Date: Thu, 31 Oct 2013 09:01:19 -0400 Subject: [PATCH 4/5] Adding test coverage around masks with optional markers and autoclear=false --- spec/Focus.Spec.js | 48 +++++++++++++++++++++++++++ spec/Raw.Spec.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/spec/Focus.Spec.js b/spec/Focus.Spec.js index 01ddebd..f469bf5 100644 --- a/spec/Focus.Spec.js +++ b/spec/Focus.Spec.js @@ -123,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"); @@ -134,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/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 From a4308eac7c3bffaeef6415cefec583b3230f5cae Mon Sep 17 00:00:00 2001 From: Greg Burghardt Date: Thu, 31 Oct 2013 09:47:41 -0400 Subject: [PATCH 5/5] Rebuilding distribution files after merging from upsteam/master. --- dist/jquery.maskedinput.js | 22 ++++++++++++++++++++-- dist/jquery.maskedinput.min.js | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dist/jquery.maskedinput.js b/dist/jquery.maskedinput.js index 656174f..5c84271 100644 --- a/dist/jquery.maskedinput.js +++ b/dist/jquery.maskedinput.js @@ -15,6 +15,7 @@ var pasteEventName = getPasteEvent() + ".mask", ua = navigator.userAgent, iPhone = /iphone/i.test(ua), + chrome = /chrome/i.test(ua), android=/android/i.test(ua), caretTimeoutId; @@ -205,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) { @@ -342,11 +359,12 @@ $.fn.extend({ settings.completed.call(input); }, 0); }); - + if (chrome && android) { + input.bind("keyup.mask", keypressEvent); + } checkVal(); //Perform initial check for existing values }); } }); - })(jQuery); \ No newline at end of file diff --git a/dist/jquery.maskedinput.min.js b/dist/jquery.maskedinput.min.js index 9c6056c..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),c=/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 o,l,u,s,f,h;return!t&&this.length>0?(o=e(this[0]),o.data(e.mask.dataName)()):(r=e.extend({autoclear:e.mask.autoclear,placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,u=[],s=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,s=e):l[t]?(u.push(new RegExp(l[t])),null===f&&(f=u.length-1)):u.push(null)}),this.trigger("unmask").each(function(){function o(e){for(;++e=0&&!u[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=o(t);h>n;n++)if(u[n]){if(!(h>a&&u[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=o(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(u[t]){if(a=o(t),i=R[t],R[t]=n,!(h>a&&u[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=o(n-1),a=46===r?o(a):a),k(n,a),m(n,a-1),e.preventDefault()):27==r&&(x.val(A),x.caret(0,y()),e.preventDefault())}function v(t){var n,a,i,l=t.which,s=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==s.end-s.begin&&(k(s.begin,s.end),m(s.begin,s.end-1)),n=o(s.begin-1),h>n&&(a=String.fromCharCode(l),u[n].test(a)&&(p(n),R[n]=a,b(),i=o(n),c?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++)u[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a,i=x.val(),c=-1;for(t=0,a=0;h>t;t++)if(u[t]){for(R[t]=r.placeholder;a++i.length)break}else R[t]===i.charAt(a)&&t!==s&&(a++,c=t);return e?b():s>c+1?r.autoclear||R.join("")===S?(x.val(""),k(0,h)):b():(b(),x.val(x.val().substring(0,c+1))),s?t:f}var x=e(this),R=e.map(t.split(""),function(e){return"?"!=e?l[e]?r.placeholder:e:void 0}),S=R.join(""),A=x.val();x.data(e.mask.dataName,function(){return e.map(R,function(e,t){return u[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;A=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()!=A&&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