From 4fdeff9ab874e148f0e74dc5dc0c6318ed3feda1 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Tue, 18 Mar 2014 22:48:43 -0500 Subject: [PATCH 01/18] Removing caret from jQuery namespace. This could be more concise later, just wanted it out now. --- gruntfile.js | 2 +- spec/lib/setup.js | 33 ++++++++++++++++ src/jquery.maskedinput.js | 82 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 5896f86..b54a2b3 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -35,10 +35,10 @@ module.exports = function( grunt ) { options: { specs: "spec/*[S|s]pec.js", vendor: [ + "lib/jquery-1.9.0.min.js", "spec/lib/matchers.js", "spec/lib/jasmine-species/jasmine-grammar.js", "spec/lib/setup.js", - "lib/jquery-1.9.0.min.js", "spec/lib/jquery.keymasher.js" ] } diff --git a/spec/lib/setup.js b/spec/lib/setup.js index 9539215..14d79fb 100644 --- a/spec/lib/setup.js +++ b/spec/lib/setup.js @@ -9,6 +9,39 @@ function importGrammar(g){ importGrammar(jasmine.grammar.FeatureStory); importGrammar(jasmine.grammar.GWT); + +$.fn.caret= function(begin, end) { + var range; + + if (this.length === 0 || this.is(":hidden")) { + return; + } + + if (typeof begin == 'number') { + end = (typeof end === 'number') ? end : begin; + return this.each(function() { + if (this.setSelectionRange) { + this.setSelectionRange(begin, end); + } else if (this.createTextRange) { + range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', begin); + range.select(); + } + }); + } else { + if (this[0].setSelectionRange) { + begin = this[0].selectionStart; + end = this[0].selectionEnd; + } else if (document.selection && document.selection.createRange) { + range = document.selection.createRange(); + begin = 0 - range.duplicate().moveStart('character', -100000); + end = begin + range.text.length; + } + return { begin: begin, end: end }; + } + }; var input; beforeEach(function(){ input = $("").appendTo("body").focus(); }); afterEach(function(){ input.remove();}); diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index cd226c8..c6b3a7a 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -1,5 +1,32 @@ (function($) { + function setCaret(elm,begin,end){ + end = (typeof end === 'number') ? end : begin; + + if (elm.setSelectionRange) { + elm.setSelectionRange(begin, end); + } else if (elm.createTextRange) { + var range = elm.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', begin); + range.select(); + } + } + + function getCaret(elm){ + var begin,end; + if (elm.setSelectionRange) { + begin = elm.selectionStart; + end = elm.selectionEnd; + } else if (document.selection && document.selection.createRange) { + var range = document.selection.createRange(); + begin = 0 - range.duplicate().moveStart('character', -100000); + end = begin + range.text.length; + } + return { begin: begin, end: end }; + } + function getPasteEvent() { var el = document.createElement('input'), name = 'onpaste'; @@ -27,39 +54,6 @@ $.mask = { }; $.fn.extend({ - //Helper Function for Caret positioning - caret: function(begin, end) { - var range; - - if (this.length === 0 || this.is(":hidden")) { - return; - } - - if (typeof begin == 'number') { - end = (typeof end === 'number') ? end : begin; - return this.each(function() { - if (this.setSelectionRange) { - this.setSelectionRange(begin, end); - } else if (this.createTextRange) { - range = this.createTextRange(); - range.collapse(true); - range.moveEnd('character', end); - range.moveStart('character', begin); - range.select(); - } - }); - } else { - if (this[0].setSelectionRange) { - begin = this[0].selectionStart; - end = this[0].selectionEnd; - } else if (document.selection && document.selection.createRange) { - range = document.selection.createRange(); - begin = 0 - range.duplicate().moveStart('character', -100000); - end = begin + range.text.length; - } - return { begin: begin, end: end }; - } - }, unmask: function() { return this.trigger("unmask"); }, @@ -102,7 +96,8 @@ $.fn.extend({ }); return this.trigger("unmask").each(function() { - var input = $(this), + var elm = this, + input = $(this), buffer = $.map( mask.split(""), function(c, i) { @@ -144,7 +139,7 @@ $.fn.extend({ } } writeBuffer(); - input.caret(Math.max(firstNonMaskPos, begin)); + setCaret(elm,Math.max(firstNonMaskPos, begin)) } function shiftR(pos) { @@ -182,7 +177,7 @@ $.fn.extend({ //backspace, delete, and escape get special treatment if (k === 8 || k === 46 || (iPhone && k === 127)) { - pos = input.caret(); + pos = getCaret(elm); begin = pos.begin; end = pos.end; @@ -198,14 +193,14 @@ $.fn.extend({ blurEvent.call(this, e); } else if (k === 27) { // escape input.val(focusText); - input.caret(0, checkVal()); + setCaret(elm,0,checkVal()); e.preventDefault(); } } function keypressEvent(e) { var k = e.which, - pos = input.caret(), + pos = getCaret(elm), p, c, next; @@ -249,12 +244,13 @@ $.fn.extend({ //Path for CSP Violation on FireFox OS 1.1 var proxy = function() { - $.proxy($.fn.caret,input,next); + + $.proxy(setCaret,elm,next); } setTimeout(proxy,0); }else{ - input.caret(next); + setCaret(elm,next); } if (settings.completed && next >= len) { @@ -348,9 +344,9 @@ $.fn.extend({ caretTimeoutId = setTimeout(function(){ writeBuffer(); if (pos == mask.replace("?","").length) { - input.caret(0, pos); + setCaret(elm,0,pos); } else { - input.caret(pos); + setCaret(elm,pos); } }, 10); }) @@ -360,7 +356,7 @@ $.fn.extend({ .on(pasteEventName, function() { setTimeout(function() { var pos=checkVal(true); - input.caret(pos); + setCaret(elm,pos); if (settings.completed && pos == input.val().length) settings.completed.call(input); }, 0); From 990de2eecd5a38788333aecc8b65efc70773088c Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Thu, 20 Mar 2014 22:41:07 -0500 Subject: [PATCH 02/18] Prying apart masking logic from DOM manipulation. Still rough with 18 test failures. --- src/jquery.maskedinput.js | 622 ++++++++++++++++++-------------------- 1 file changed, 288 insertions(+), 334 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index c6b3a7a..74d3736 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -27,345 +27,299 @@ return { begin: begin, end: end }; } -function getPasteEvent() { - var el = document.createElement('input'), + $.mask = { + masks:[], + //Predefined character definitions + //TODO: Move these to the mask def itself. + definitions: { + '9': "[0-9]", + 'a': "[A-Za-z]", + '*': "[A-Za-z0-9]" + }, + autoclear: true, + dataName: "maskedinput", + placeholder: '_' + }; + + function FixedWidthMask (mask, settings){ + var self=this; + //Build up structures necessary to quickly apply masking + this.settings = settings; + + this.tests = []; + this.partialPosition = mask.length; + this.length = mask.length; + + var firstNonMaskPos = null; + + $.each(mask.split(""), function(i, c) { + if (c == '?') { + self.length--; + self.partialPosition = i; + } else if (settings.definitions[c]) { + self.tests.push(new RegExp(settings.definitions[c])); + if (firstNonMaskPos === null) { + firstNonMaskPos = self.tests.length - 1; + } + } else { + self.tests.push(c); + } + }); + } + + FixedWidthMask.prototype.applyBackspace = function(input, pos){ + var i, buffer = input.split(''); + for(i = pos - 1; i >= 0; i--){ + if(this.tests[i].test) + break + } + buffer.splice(i, 1); + return this.apply(buffer.join(''), i); + } + + FixedWidthMask.prototype.applyDelete = function(input, pos){ + var i, buffer = input.split(''); + for(i = pos; i < buffer.length; i++){ + if(this.tests[i].test) + break + } + buffer.splice(i, 1); + var result=this.apply(buffer.join(''), i); + result.pos=i; + return result; + } + + FixedWidthMask.prototype.apply = function(input, caretPosition){ + if(caretPosition == null) + caretPosition = 0; + var buffer=[], + raw=[], + lastMatch = -1, + i, + action, + pos; + + for (i = 0, pos = 0; i < this.length; i++) { + var action=this.tests[i]; + + if (action.test) { + buffer.push(this.settings.placeholder); + + while (pos++ < input.length) { + c = input.charAt(pos - 1); + if (action.test(c)) { + buffer[i] = c; + raw.push(c) + lastMatch = i; + break; + } + } + } else { + buffer.push(action); + if(action === input.charAt(pos) && i !== this.partialPosition) { + pos++; + lastMatch=i; + } + } + } + + //Find the next spot waiting for input + var maxCaret=Math.min(caretPosition,this.length); + for(i=Math.max(lastMatch+1,maxCaret);i= this.partialPosition + }; + console.log(result); + return result; + } + + function getPasteEvent() { + var el = document.createElement('input'), name = 'onpaste'; - el.setAttribute(name, ''); - 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; - -$.mask = { - //Predefined character definitions - definitions: { - '9': "[0-9]", - 'a': "[A-Za-z]", - '*': "[A-Za-z0-9]" - }, - autoclear: true, - dataName: "rawMaskFn", - placeholder: '_' -}; - -$.fn.extend({ - unmask: function() { - return this.trigger("unmask"); - }, - mask: function(mask, settings) { - var input, - defs, - tests, - partialPosition, - firstNonMaskPos, - len; - - if (!mask && this.length > 0) { - input = $(this[0]); - return input.data($.mask.dataName)(); - } - settings = $.extend({ - autoclear: $.mask.autoclear, - placeholder: $.mask.placeholder, // Load default placeholder - completed: null - }, settings); - - - defs = $.mask.definitions; - tests = []; - partialPosition = len = mask.length; - firstNonMaskPos = null; - - $.each(mask.split(""), function(i, c) { - if (c == '?') { - len--; - partialPosition = i; - } else if (defs[c]) { - tests.push(new RegExp(defs[c])); - if (firstNonMaskPos === null) { - firstNonMaskPos = tests.length - 1; - } - } else { - tests.push(null); - } - }); - - return this.trigger("unmask").each(function() { - var elm = this, + el.setAttribute(name, ''); + 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; + + + + $.fn.extend({ + //TODO: Be a good citizen and get this down to only .mask() + unmask: function() { + return this.trigger("unmask"); + }, + //TODO: we need a conflict thing here, maybe only use maskedinput(or alias?) + mask: function(mask, settings) { + var input, + defs, + tests, + partialPosition, + firstNonMaskPos, + len; + + //TODO: make these more in line with newer plugin interaction guidelines. + if (!mask && this.length > 0) { + input = $(this[0]); + return input.data($.mask.dataName).apply(input.val()).raw; + } + + settings = $.extend({ + definitions: $.mask.definitions, + autoclear: $.mask.autoclear, + placeholder: $.mask.placeholder, + completed: null + }, settings); + + var mask=new FixedWidthMask(mask,settings); + + + return this.trigger("unmask").each(function() { + var elm = this, input = $(this), - buffer = $.map( - mask.split(""), - function(c, i) { - if (c != '?') { - return defs[c] ? settings.placeholder : c; - } - }), - defaultBuffer = buffer.join(''), - focusText = input.val(); - - function seekNext(pos) { - while (++pos < len && !tests[pos]); - return pos; - } - - function seekPrev(pos) { - while (--pos >= 0 && !tests[pos]); - return pos; - } - - function shiftL(begin,end) { - var i, - j; - - if (begin<0) { - return; - } - - for (i = begin, j = seekNext(end); i < len; i++) { - if (tests[i]) { - if (j < len && tests[i].test(buffer[j])) { - buffer[i] = buffer[j]; - buffer[j] = settings.placeholder; - } else { - break; - } - - j = seekNext(j); - } - } - writeBuffer(); - setCaret(elm,Math.max(firstNonMaskPos, begin)) - } - - function shiftR(pos) { - var i, - c, - j, - t; - - for (i = pos, c = settings.placeholder; i < len; i++) { - if (tests[i]) { - j = seekNext(i); - t = buffer[i]; - buffer[i] = c; - if (j < len && tests[j].test(t)) { - c = t; - } else { - break; - } - } - } - } - - function blurEvent(e) { - checkVal(); - - if (input.val() != focusText) - input.change(); - } - - function keydownEvent(e) { - var k = e.which, - pos, - begin, - end; - - //backspace, delete, and escape get special treatment - if (k === 8 || k === 46 || (iPhone && k === 127)) { - pos = getCaret(elm); - begin = pos.begin; - end = pos.end; - - if (end - begin === 0) { - begin=k!==46?seekPrev(begin):(end=seekNext(begin-1)); - end=k===46?seekNext(end):end; - } - clearBuffer(begin, end); - shiftL(begin, end - 1); - - e.preventDefault(); - } else if( k === 13 ) { // enter - blurEvent.call(this, e); - } else if (k === 27) { // escape - input.val(focusText); - setCaret(elm,0,checkVal()); - e.preventDefault(); - } - } - - function keypressEvent(e) { - var k = e.which, - pos = getCaret(elm), - p, - 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; + focusText = elm.value; + + function blurEvent(e) { + if(settings.autoclear){ + var result = mask.apply(elm.value, 0); + if(!result.isComplete){ + elm.value = ""; } - if (pos.begin == pos.end) { - k = input.val().charCodeAt(pos.begin - 1); - pos.begin--; - pos.end--; + } + + if (elm.value != focusText){ + input.change(); + } + } + + function keydownEvent(e) { + var k = e.which; + + //backspace, delete, enter, and escape get special treatment + if (k === 8 || k === 46) { + var pos = getCaret(elm); + var result; + + if(pos.begin != pos.end){ + var buffer = elm.value.split(''); + buffer.splice(pos.begin,pos.end - pos.begin); + result = mask.apply(buffer.join(''), pos.begin+1); + result.pos = pos.begin; + }else{ + if(k==8){ + result = mask.applyBackspace(elm.value, pos.begin); + }else{ + result = mask.applyDelete(elm.value, pos.begin); + } } + + elm.value = result.value; + setCaret(elm, result.pos); + e.preventDefault(); + } else if(k === 13) { // enter + blurEvent.call(this, e); + } else if (k === 27) { // escape + elm.value = focusText; + setCaret(elm, 0, focusText.length); + e.preventDefault(); } + } - if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore - return; - } else if ( k && k !== 13 ) { - if (pos.end - pos.begin !== 0){ - clearBuffer(pos.begin, pos.end); - shiftL(pos.begin, pos.end-1); - } - - p = seekNext(pos.begin - 1); - if (p < len) { - c = String.fromCharCode(k); - if (tests[p].test(c)) { - shiftR(p); - - buffer[p] = c; - writeBuffer(); - next = seekNext(p); - - if(android){ - //Path for CSP Violation on FireFox OS 1.1 - var proxy = function() - { - - $.proxy(setCaret,elm,next); - } - - setTimeout(proxy,0); - }else{ - setCaret(elm,next); - } - - if (settings.completed && next >= len) { - settings.completed.call(input); - } - } - } - e.preventDefault(); - } - } - - function clearBuffer(start, end) { - var i; - for (i = start; i < end && i < len; i++) { - if (tests[i]) { - buffer[i] = settings.placeholder; - } - } - } - - function writeBuffer() { input.val(buffer.join('')); } - - function checkVal(allow) { - //try to place characters where they belong - var test = input.val(), - lastMatch = -1, - i, - c, - pos; - - for (i = 0, pos = 0; i < len; i++) { - if (tests[i]) { - buffer[i] = settings.placeholder; - while (pos++ < test.length) { - c = test.charAt(pos - 1); - if (tests[i].test(c)) { - buffer[i] = c; - lastMatch = i; - break; - } - } - if (pos > test.length) { - break; - } - } else if (buffer[i] === test.charAt(pos) && i !== partialPosition) { - pos++; - lastMatch = i; - } - } - if (allow) { - writeBuffer(); - } else if (lastMatch + 1 < partialPosition) { - 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)); - } - return (partialPosition ? i : firstNonMaskPos); - } - - input.data($.mask.dataName,function(){ - return $.map(buffer, function(c, i) { - return tests[i]&&c!=settings.placeholder ? c : null; - }).join(''); - }); - - if (!input.attr("readonly")) - input - .one("unmask", function() { - input - .off(".mask") - .removeData($.mask.dataName); - }) - .on("focus.mask", function() { - clearTimeout(caretTimeoutId); - var pos; - - focusText = input.val(); - - pos = checkVal(); - - caretTimeoutId = setTimeout(function(){ - writeBuffer(); - if (pos == mask.replace("?","").length) { - setCaret(elm,0,pos); - } else { - setCaret(elm,pos); - } - }, 10); - }) - .on("blur.mask", blurEvent) - .on("keydown.mask", keydownEvent) - .on("keypress.mask", keypressEvent) - .on(pasteEventName, function() { - setTimeout(function() { - var pos=checkVal(true); - setCaret(elm,pos); - if (settings.completed && pos == input.val().length) - settings.completed.call(input); - }, 0); - }); - if (chrome && android) { - input.on("keyup.mask", keypressEvent); + function keypressEvent(e) { + var k = e.which, + pos = getCaret(elm); + + if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore + return; + } else if (k && k !== 13) { + var buffer = elm.value.split(''); + buffer.splice(pos.begin, pos.end - pos.begin, String.fromCharCode(k)); + var result = mask.apply(buffer.join(''), pos.begin+1); + + elm.value = result.value; + setCaret(elm, result.pos); + if(result.isComplete && settings.completed) + settings.completed.call(input); //TODO: Raise event instead. + e.preventDefault(); + + // if(android){ + // //Path for CSP Violation on FireFox OS 1.1 + // var proxy = function() + // { + // + // $.proxy(setCaret,elm,next); + // } + // + // setTimeout(proxy,0); + // }else{ + // setCaret(elm,next); + // } + } + } + + var caretTimeoutId; + function focusEvent(e){ + clearTimeout(caretTimeoutId); + var result = mask.apply(elm.value, 0); + focusText = elm.value; + + caretTimeoutId = setTimeout(function(){ + elm.value = result.value; + if (result.isComplete) { + setCaret(elm, 0, result.pos); + } else { + setCaret(elm, result.pos); + } + }, 10); } - checkVal(); //Perform initial check for existing values - }); - } -}); + + function pasteEvent(e){ + setTimeout(function() { + var pos = getCaret(elm); + var result = mask.apply(elm.value, pos.end); + elm.value = result.value; + setCaret(elm, result.pos); + if(result.isComplete && settings.completed) + settings.completed.call(input); //TODO: Raise event instead. + }, 0); + } + input.data($.mask.dataName,mask); + + if (!input.attr("readonly")) + input + .one("unmask", function() { + input + .off(".mask") + .removeData($.mask.dataName); + }) + .on("focus.mask",focusEvent) + .on("blur.mask", blurEvent) + .on("keydown.mask", keydownEvent) + .on("keypress.mask", keypressEvent) + .on(pasteEventName, pasteEvent); + + // if (chrome && android) { + // input.on("keyup.mask", keypressEvent); + // } + + //Apply initital mask + if(elm.value.length){ + var result=mask.apply(elm.value, 0); + elm.value = result.value; + } + }); + } + }); })(jQuery); From 34d579fadc9da7978dabc49165d4c3526237009d Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Thu, 20 Mar 2014 23:12:53 -0500 Subject: [PATCH 03/18] Fixing issue with blur leaving placeholders for optional values. --- src/jquery.maskedinput.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 74d3736..a318c1d 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -118,7 +118,6 @@ buffer.push(action); if(action === input.charAt(pos) && i !== this.partialPosition) { pos++; - lastMatch=i; } } } @@ -130,9 +129,16 @@ break } + var trimmed=buffer; + if(this.partialPosition < this.length){ + trimmed=buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)) + } + + //TODO: better names for these props var result={ - value: buffer.join(''), - raw: raw.join(''), //TODO: separate unmask call? + value: buffer.join(''), //Prompt Value + trimmed: trimmed.join(''), //Display Value + raw: raw.join(''), //Raw Value, TODO: separate unmask call? pos: i , //(partialPosition ? i : firstNonMaskPos) isComplete: (lastMatch + 1) >= this.partialPosition }; @@ -192,11 +198,10 @@ focusText = elm.value; function blurEvent(e) { - if(settings.autoclear){ - var result = mask.apply(elm.value, 0); - if(!result.isComplete){ - elm.value = ""; - } + var result = mask.apply(elm.value, 0); + elm.value = result.trimmed; + if(settings.autoclear && !result.isComplete){ + elm.value = ""; } if (elm.value != focusText){ From 2fae445e0af3ab225a14522ef560f6aa3d26f670 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Thu, 20 Mar 2014 23:43:27 -0500 Subject: [PATCH 04/18] Fix backspace caret position. --- src/jquery.maskedinput.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index a318c1d..e45a080 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -74,7 +74,9 @@ break } buffer.splice(i, 1); - return this.apply(buffer.join(''), i); + var result= this.apply(buffer.join(''), i); + result.pos=i; + return result; } FixedWidthMask.prototype.applyDelete = function(input, pos){ From 36243cb2661d4d0cdfc261474dd71618dd6ac218 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Fri, 21 Mar 2014 21:41:59 -0500 Subject: [PATCH 05/18] Fixing caret position when typing in the middle of the string. --- src/jquery.maskedinput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index e45a080..7db0db5 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -126,7 +126,7 @@ //Find the next spot waiting for input var maxCaret=Math.min(caretPosition,this.length); - for(i=Math.max(lastMatch+1,maxCaret);i Date: Fri, 21 Mar 2014 21:52:33 -0500 Subject: [PATCH 06/18] Fixing caret position on focus. --- src/jquery.maskedinput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 7db0db5..f29abdd 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -93,7 +93,7 @@ FixedWidthMask.prototype.apply = function(input, caretPosition){ if(caretPosition == null) - caretPosition = 0; + caretPosition = this.length; var buffer=[], raw=[], lastMatch = -1, @@ -200,7 +200,7 @@ focusText = elm.value; function blurEvent(e) { - var result = mask.apply(elm.value, 0); + var result = mask.apply(elm.value); elm.value = result.trimmed; if(settings.autoclear && !result.isComplete){ elm.value = ""; @@ -279,7 +279,7 @@ var caretTimeoutId; function focusEvent(e){ clearTimeout(caretTimeoutId); - var result = mask.apply(elm.value, 0); + var result = mask.apply(elm.value); focusText = elm.value; caretTimeoutId = setTimeout(function(){ @@ -323,7 +323,7 @@ //Apply initital mask if(elm.value.length){ - var result=mask.apply(elm.value, 0); + var result=mask.apply(elm.value); elm.value = result.value; } }); From 26da479cbea786e43704248b10d5354aa87ac4d6 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Fri, 21 Mar 2014 22:10:06 -0500 Subject: [PATCH 07/18] Fixed bug and adjusted specs for around autoclear = false. --- spec/Enter.Spec.js | 4 ++-- spec/Focus.Spec.js | 4 ++-- src/jquery.maskedinput.js | 12 ++++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spec/Enter.Spec.js b/spec/Enter.Spec.js index 025e21e..636aa09 100644 --- a/spec/Enter.Spec.js +++ b/spec/Enter.Spec.js @@ -37,7 +37,7 @@ feature("Enter Key", function() { input.trigger(enterKeyEvent); }); then("value should remain visible with placeholders",function(){ - expect(input).toHaveValue("1_"); + expect(input).toHaveValue("1"); }); }); }); @@ -63,7 +63,7 @@ feature("Enter Key", function() { input.mashKeys("1").trigger(enterKeyEvent); }); then("value should be empty",function(){ - expect(input).toHaveValue("1___"); + expect(input).toHaveValue("1"); }); }); diff --git a/spec/Focus.Spec.js b/spec/Focus.Spec.js index 9964893..91c6c25 100644 --- a/spec/Focus.Spec.js +++ b/spec/Focus.Spec.js @@ -123,7 +123,7 @@ feature("Leaving A Masked Input",function(){ input.blur(); }); then("value should remain visible with placeholders",function(){ - expect(input).toHaveValue("1_"); + expect(input).toHaveValue("1"); }); }); }); @@ -149,7 +149,7 @@ feature("Optional marker",function(){ input.mashKeys("1").blur(); }); then("value should be empty",function(){ - expect(input).toHaveValue("1___"); + expect(input).toHaveValue("1"); }); }); diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index f29abdd..4da121f 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -133,7 +133,11 @@ var trimmed=buffer; if(this.partialPosition < this.length){ - trimmed=buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)) + trimmed = buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)) + } + + if(!this.settings.autoclear){ + trimmed = buffer.slice(0,i); } //TODO: better names for these props @@ -324,7 +328,11 @@ //Apply initital mask if(elm.value.length){ var result=mask.apply(elm.value); - elm.value = result.value; + if(!settings.autoclear || result.isComplete){ + elm.value = result.value; + }else{ + elm.value=""; + } } }); } From 86cff48f29d4575b51c4f1375d0b96d35e67f64f Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Sun, 23 Mar 2014 23:36:05 -0500 Subject: [PATCH 08/18] Fixed last of failing tests around shifting with some awful code. --- src/jquery.maskedinput.js | 49 +++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 4da121f..3a12f2c 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -74,7 +74,7 @@ break } buffer.splice(i, 1); - var result= this.apply(buffer.join(''), i); + var result= this.apply(buffer.join(''), i, true); result.pos=i; return result; } @@ -86,15 +86,17 @@ break } buffer.splice(i, 1); - var result=this.apply(buffer.join(''), i); + var result=this.apply(buffer.join(''), i, true); result.pos=i; return result; } - FixedWidthMask.prototype.apply = function(input, caretPosition){ + FixedWidthMask.prototype.apply = function(inputString, caretPosition, doShift){ if(caretPosition == null) caretPosition = this.length; - var buffer=[], + + var input=inputString.split(''), + buffer=[], raw=[], lastMatch = -1, i, @@ -108,17 +110,50 @@ buffer.push(this.settings.placeholder); while (pos++ < input.length) { - c = input.charAt(pos - 1); + c = input[pos - 1]; if (action.test(c)) { buffer[i] = c; - raw.push(c) + raw.push(c); lastMatch = i; break; + }else if(doShift){ + //TODO: The following is awful and needs to be refactored. + var tests=$.map(this.tests.slice(i + 1),function(test, offset){ + var index = pos - 1 + offset; + if(test.test && input[index] != null){ + return {regex:test,char:input[index]}; + } + }); + + if(tests.length){ + var newInput = [],canShift = true; + tests.unshift({regex: action}); + for(var j = 1; j < tests.length; j++){ + if(!tests[j-1].regex.test(tests[j].char)){ + canShift = false; + break; + } + newInput.push(tests[j].char); + } + } + + if(canShift){ + //Everything to the right can shift left and still match. + input = newInput; + buffer[i] = input[0]; + pos = 1; + }else{ + //Retry current char at next position leaving a blank. + pos--; + } + //Only allow shift attempt to happen once. + doShift = false; + break; } } } else { buffer.push(action); - if(action === input.charAt(pos) && i !== this.partialPosition) { + if(action === input[pos] && i !== this.partialPosition) { pos++; } } From c1b07803b5eb5e8cc3b1f68c33c6467a5ff70075 Mon Sep 17 00:00:00 2001 From: Jared Barboza Date: Mon, 24 Mar 2014 00:53:58 -0400 Subject: [PATCH 09/18] fix global var leak in FixedWidthMask#apply --- src/jquery.maskedinput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 3a12f2c..011b4d6 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -110,7 +110,7 @@ buffer.push(this.settings.placeholder); while (pos++ < input.length) { - c = input[pos - 1]; + var c = input[pos - 1]; if (action.test(c)) { buffer[i] = c; raw.push(c); From 6f552731fdce574b81d600d70a94a86cb639c35e Mon Sep 17 00:00:00 2001 From: Jared Barboza Date: Mon, 24 Mar 2014 00:54:54 -0400 Subject: [PATCH 10/18] change applyDelete/Backspace nested if/else to else if/else --- src/jquery.maskedinput.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 011b4d6..d0d51f8 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -258,17 +258,15 @@ var pos = getCaret(elm); var result; - if(pos.begin != pos.end){ + if (pos.begin != pos.end) { var buffer = elm.value.split(''); buffer.splice(pos.begin,pos.end - pos.begin); result = mask.apply(buffer.join(''), pos.begin+1); result.pos = pos.begin; - }else{ - if(k==8){ - result = mask.applyBackspace(elm.value, pos.begin); - }else{ - result = mask.applyDelete(elm.value, pos.begin); - } + } else if( k == 8 ) { + result = mask.applyBackspace(elm.value, pos.begin); + } else { + result = mask.applyDelete(elm.value, pos.begin); } elm.value = result.value; From 70ccc51f62f32295a9365f980834b932f727a57e Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 21:27:59 -0500 Subject: [PATCH 11/18] Adding concat and watch grunt tasks. --- gruntfile.js | 88 +++++++++++++++++++++++++++++++--------------------- package.json | 11 ++++--- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index b54a2b3..997d1fc 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,54 +1,70 @@ - "use strict"; +module.exports = function(grunt) { -module.exports = function( grunt ) { grunt.initConfig({ - // TODO: change to read component.json - pkg: require('./package.json'), - - uglify: { - options: { - banner: '/*\n <%= pkg.description %>\n Copyright (c) 2007 - <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)\n Version: <%= pkg.version %>\n*/\n' - }, - - dev: { + pkg: grunt.file.readJSON('package.json'), + banner: '/*\n'+ + ' <%= pkg.description %>\n'+ + ' Copyright (c) 2007 - <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n'+ + ' Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)\n'+ + ' Version: <%= pkg.version %>\n'+ + '*/\n\n', + concat: { options: { - beautify: true, - mangle: false + banner: '<%= banner %>', + stripBanners: false }, - - files: { - 'dist/jquery.maskedinput.js': ['src/jquery.maskedinput.js'] + dist: { + src: ['src/jquery.maskedinput.js'], + dest: 'dist/<%= pkg.name %>.js' } - }, - - min: { + }, + uglify: { + options: { + banner: '<%= banner %>' + }, + dist: { files: { - 'dist/jquery.maskedinput.min.js': ['src/jquery.maskedinput.js'] + 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] } } }, - jasmine: { - full: { - src: "src/**/*.js", - options: { - specs: "spec/*[S|s]pec.js", - vendor: [ - "lib/jquery-1.9.0.min.js", - "spec/lib/matchers.js", - "spec/lib/jasmine-species/jasmine-grammar.js", - "spec/lib/setup.js", - "spec/lib/jquery.keymasher.js" - ] + full: { + src: '<%= concat.dist.src %>', + options: { + specs: 'spec/*[S|s]pec.js', + vendor: [ + 'lib/jquery-1.9.0.min.js', + 'spec/lib/matchers.js', + 'spec/lib/jasmine-species/jasmine-grammar.js', + 'spec/lib/setup.js', + 'spec/lib/jquery.keymasher.js' + ] + } + } + }, + jshint: { + files: ['gruntfile.js', '<%= concat.dist.src %>', '<%= jasmine.full.options.specs %>'], + options: { + // options here to override JSHint defaults + globals: { + jQuery: true } } + }, + watch: { + files: ['<%= jshint.files %>'], + tasks: [/*'jshint',*/ 'jasmine'] } }); - grunt.loadNpmTasks("grunt-contrib-jasmine"); - grunt.loadNpmTasks("grunt-contrib-uglify"); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-jasmine'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.registerTask('test', ['jasmine']); - grunt.registerTask('default', ['test', 'uglify']); + grunt.registerTask('test', [/*'jshint',*/ 'jasmine']); + grunt.registerTask('default', [/*'jshint',*/ 'jasmine', 'concat', 'uglify']); }; diff --git a/package.json b/package.json index 2f8f240..561b68a 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "version": "1.3.1", "author": "Josh Bush (digitalbush.com)", "description": "jQuery Masked Input Plugin", - "devDependencies": { - "grunt": "0.4.x", - "grunt-contrib-jasmine": "0.5.x", - "grunt-contrib-watch": "0.5.x", - "grunt-contrib-uglify": "0.2.x" + "grunt": "~0.4.4", + "grunt-contrib-jasmine": "0.5.x", + "grunt-contrib-watch": "~0.6.1", + "grunt-contrib-uglify": "0.2.x", + "grunt-contrib-jshint": "~0.9.2", + "grunt-contrib-concat": "~0.3.0" }, "scripts": { "test": "grunt test" From 4b0c1c50d4937b4be5f7bddc58c39313041eccac Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 22:04:58 -0500 Subject: [PATCH 12/18] Adding jshint --- gruntfile.js | 119 ++++++++++++++++++++++++-------------------------- package.json | 2 +- src/.jshintrc | 5 +++ 3 files changed, 64 insertions(+), 62 deletions(-) create mode 100644 src/.jshintrc diff --git a/gruntfile.js b/gruntfile.js index 997d1fc..c8701fb 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,70 +1,67 @@ -"use strict"; -module.exports = function(grunt) { - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - banner: '/*\n'+ - ' <%= pkg.description %>\n'+ - ' Copyright (c) 2007 - <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n'+ - ' Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)\n'+ - ' Version: <%= pkg.version %>\n'+ - '*/\n\n', - concat: { - options: { - banner: '<%= banner %>', - stripBanners: false +module.exports = function(grunt) { + "use strict"; + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + banner: '/*\n'+ + ' <%= pkg.description %>\n'+ + ' Copyright (c) 2007 - <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n'+ + ' Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)\n'+ + ' Version: <%= pkg.version %>\n'+ + '*/\n\n', + concat: { + options: { + banner: '<%= banner %>', + stripBanners: false + }, + dist: { + src: ['src/jquery.maskedinput.js'], + dest: 'dist/<%= pkg.name %>.js' + } }, - dist: { - src: ['src/jquery.maskedinput.js'], - dest: 'dist/<%= pkg.name %>.js' - } - }, - uglify: { - options: { - banner: '<%= banner %>' + uglify: { + options: { + banner: '<%= banner %>' + }, + dist: { + files: { + 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] + } + } }, - dist: { - files: { - 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] - } - } - }, - jasmine: { - full: { - src: '<%= concat.dist.src %>', + jasmine: { + full: { + src: '<%= concat.dist.src %>', + options: { + specs: 'spec/*[S|s]pec.js', + vendor: [ + 'lib/jquery-1.9.0.min.js', + 'spec/lib/matchers.js', + 'spec/lib/jasmine-species/jasmine-grammar.js', + 'spec/lib/setup.js', + 'spec/lib/jquery.keymasher.js' + ] + } + } + }, + jshint: { + files: ['gruntfile.js', '<%= concat.dist.src %>', '<%= jasmine.full.options.specs %>'], options: { - specs: 'spec/*[S|s]pec.js', - vendor: [ - 'lib/jquery-1.9.0.min.js', - 'spec/lib/matchers.js', - 'spec/lib/jasmine-species/jasmine-grammar.js', - 'spec/lib/setup.js', - 'spec/lib/jquery.keymasher.js' - ] + jshintrc: 'src/.jshintrc' } + }, + watch: { + files: ['<%= jshint.files %>'], + tasks: ['jshint', 'jasmine'] } - }, - jshint: { - files: ['gruntfile.js', '<%= concat.dist.src %>', '<%= jasmine.full.options.specs %>'], - options: { - // options here to override JSHint defaults - globals: { - jQuery: true - } - } - }, - watch: { - files: ['<%= jshint.files %>'], - tasks: [/*'jshint',*/ 'jasmine'] - } - }); + }); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-jasmine'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-jasmine'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.registerTask('test', [/*'jshint',*/ 'jasmine']); - grunt.registerTask('default', [/*'jshint',*/ 'jasmine', 'concat', 'uglify']); + grunt.registerTask('test', ['jshint', 'jasmine']); + grunt.registerTask('default', ['jshint', 'jasmine', 'concat', 'uglify']); }; diff --git a/package.json b/package.json index 561b68a..7d80b3f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "jQuery Masked Input Plugin", "devDependencies": { "grunt": "~0.4.4", - "grunt-contrib-jasmine": "0.5.x", + "grunt-contrib-jasmine": "0.5.x", "grunt-contrib-watch": "~0.6.1", "grunt-contrib-uglify": "0.2.x", "grunt-contrib-jshint": "~0.9.2", diff --git a/src/.jshintrc b/src/.jshintrc new file mode 100644 index 0000000..b4f03e6 --- /dev/null +++ b/src/.jshintrc @@ -0,0 +1,5 @@ +{ + "asi" : true, + "jquery" : true, + "eqnull" : true +} From 9099e814be9273d1aa443a39b9f643a979a83a78 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 22:50:33 -0500 Subject: [PATCH 13/18] Making jshint happy with current source. --- gruntfile.js | 22 ++++++---- spec/.jshintrc | 3 ++ src/.jshintrc | 10 +++-- src/jquery.maskedinput.js | 85 ++++++++++++++++++--------------------- 4 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 spec/.jshintrc diff --git a/gruntfile.js b/gruntfile.js index c8701fb..a507209 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -23,11 +23,11 @@ module.exports = function(grunt) { options: { banner: '<%= banner %>' }, - dist: { - files: { - 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] + dist: { + files: { + 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] + } } - } }, jasmine: { full: { @@ -45,9 +45,17 @@ module.exports = function(grunt) { } }, jshint: { - files: ['gruntfile.js', '<%= concat.dist.src %>', '<%= jasmine.full.options.specs %>'], - options: { - jshintrc: 'src/.jshintrc' + src: { + options: { + jshintrc: 'src/.jshintrc' + }, + src: '<%= concat.dist.src %>' + }, + specs: { + options: { + jshintrc: 'spec/.jshintrc' + }, + src: ['<%= jasmine.full.options.specs %>'] } }, watch: { diff --git a/spec/.jshintrc b/spec/.jshintrc new file mode 100644 index 0000000..a6649b9 --- /dev/null +++ b/spec/.jshintrc @@ -0,0 +1,3 @@ +{ + "asi" : true +} diff --git a/src/.jshintrc b/src/.jshintrc index b4f03e6..97efc48 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -1,5 +1,9 @@ { - "asi" : true, - "jquery" : true, - "eqnull" : true + "browser" : true, + "jquery" : true, + "eqnull" : true, + "newcap" : true, + "indent" : 4, + "undef" : true, + "unused" : true } diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index d0d51f8..0af047e 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -70,26 +70,26 @@ FixedWidthMask.prototype.applyBackspace = function(input, pos){ var i, buffer = input.split(''); for(i = pos - 1; i >= 0; i--){ - if(this.tests[i].test) - break + if(this.tests[i].test) + break; } buffer.splice(i, 1); var result= this.apply(buffer.join(''), i, true); result.pos=i; return result; - } + }; FixedWidthMask.prototype.applyDelete = function(input, pos){ var i, buffer = input.split(''); for(i = pos; i < buffer.length; i++){ - if(this.tests[i].test) - break + if(this.tests[i].test) + break; } buffer.splice(i, 1); var result=this.apply(buffer.join(''), i, true); result.pos=i; return result; - } + }; FixedWidthMask.prototype.apply = function(inputString, caretPosition, doShift){ if(caretPosition == null) @@ -104,7 +104,7 @@ pos; for (i = 0, pos = 0; i < this.length; i++) { - var action=this.tests[i]; + action=this.tests[i]; if (action.test) { buffer.push(this.settings.placeholder); @@ -118,15 +118,20 @@ break; }else if(doShift){ //TODO: The following is awful and needs to be refactored. - var tests=$.map(this.tests.slice(i + 1),function(test, offset){ + var tests; + /* jshint ignore:start */ + tests=$.map(this.tests.slice(i + 1), function(test, offset){ var index = pos - 1 + offset; if(test.test && input[index] != null){ return {regex:test,char:input[index]}; } }); + /* jshint ignore:end */ + + var newInput = []; + var canShift = tests.length > 0; if(tests.length){ - var newInput = [],canShift = true; tests.unshift({regex: action}); for(var j = 1; j < tests.length; j++){ if(!tests[j-1].regex.test(tests[j].char)){ @@ -163,12 +168,12 @@ var maxCaret=Math.min(caretPosition,this.length); for(i=Math.min(lastMatch+1,maxCaret);i< this.length){ - trimmed = buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)) + trimmed = buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)); } if(!this.settings.autoclear){ @@ -183,9 +188,8 @@ pos: i , //(partialPosition ? i : firstNonMaskPos) isComplete: (lastMatch + 1) >= this.partialPosition }; - console.log(result); return result; - } + }; function getPasteEvent() { var el = document.createElement('input'), @@ -194,14 +198,7 @@ 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; - - + var pasteEventName = getPasteEvent() + ".mask"; $.fn.extend({ //TODO: Be a good citizen and get this down to only .mask() @@ -209,18 +206,13 @@ return this.trigger("unmask"); }, //TODO: we need a conflict thing here, maybe only use maskedinput(or alias?) - mask: function(mask, settings) { - var input, - defs, - tests, - partialPosition, - firstNonMaskPos, - len; + mask: function(format, settings) { + var input; //TODO: make these more in line with newer plugin interaction guidelines. - if (!mask && this.length > 0) { - input = $(this[0]); - return input.data($.mask.dataName).apply(input.val()).raw; + if (!format && this.length > 0) { + input = $(this[0]); + return input.data($.mask.dataName).apply(input.val()).raw; } settings = $.extend({ @@ -230,7 +222,7 @@ completed: null }, settings); - var mask=new FixedWidthMask(mask,settings); + var mask=new FixedWidthMask(format,settings); return this.trigger("unmask").each(function() { @@ -238,7 +230,7 @@ input = $(this), focusText = elm.value; - function blurEvent(e) { + function blurEvent() { var result = mask.apply(elm.value); elm.value = result.trimmed; if(settings.autoclear && !result.isComplete){ @@ -314,7 +306,7 @@ } var caretTimeoutId; - function focusEvent(e){ + function focusEvent(){ clearTimeout(caretTimeoutId); var result = mask.apply(elm.value); focusText = elm.value; @@ -329,7 +321,7 @@ }, 10); } - function pasteEvent(e){ + function pasteEvent(){ setTimeout(function() { var pos = getCaret(elm); var result = mask.apply(elm.value, pos.end); @@ -341,18 +333,19 @@ } input.data($.mask.dataName,mask); - if (!input.attr("readonly")) - input - .one("unmask", function() { + if (!input.attr("readonly")){ input - .off(".mask") - .removeData($.mask.dataName); - }) - .on("focus.mask",focusEvent) - .on("blur.mask", blurEvent) - .on("keydown.mask", keydownEvent) - .on("keypress.mask", keypressEvent) - .on(pasteEventName, pasteEvent); + .one("unmask", function() { + input + .off(".mask") + .removeData($.mask.dataName); + }) + .on("focus.mask",focusEvent) + .on("blur.mask", blurEvent) + .on("keydown.mask", keydownEvent) + .on("keypress.mask", keypressEvent) + .on(pasteEventName, pasteEvent); + } // if (chrome && android) { // input.on("keyup.mask", keypressEvent); From 7fe007591cb49ff9a06bf8ee26d481528e7ec072 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 23:00:21 -0500 Subject: [PATCH 14/18] Breaking out mask implementation into separate file. --- gruntfile.js | 2 +- src/jquery.maskedinput.js | 157 +----------------------------------- src/masks/FixedWidthMask.js | 152 ++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 154 deletions(-) create mode 100644 src/masks/FixedWidthMask.js diff --git a/gruntfile.js b/gruntfile.js index a507209..aa76f29 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -15,7 +15,7 @@ module.exports = function(grunt) { stripBanners: false }, dist: { - src: ['src/jquery.maskedinput.js'], + src: ['src/jquery.maskedinput.js','src/masks/*.js'], dest: 'dist/<%= pkg.name %>.js' } }, diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 0af047e..44cfdea 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -27,8 +27,9 @@ return { begin: begin, end: end }; } + //TODO: alias as maskedinput? $.mask = { - masks:[], + masks:{}, //Predefined character definitions //TODO: Move these to the mask def itself. definitions: { @@ -41,156 +42,6 @@ placeholder: '_' }; - function FixedWidthMask (mask, settings){ - var self=this; - //Build up structures necessary to quickly apply masking - this.settings = settings; - - this.tests = []; - this.partialPosition = mask.length; - this.length = mask.length; - - var firstNonMaskPos = null; - - $.each(mask.split(""), function(i, c) { - if (c == '?') { - self.length--; - self.partialPosition = i; - } else if (settings.definitions[c]) { - self.tests.push(new RegExp(settings.definitions[c])); - if (firstNonMaskPos === null) { - firstNonMaskPos = self.tests.length - 1; - } - } else { - self.tests.push(c); - } - }); - } - - FixedWidthMask.prototype.applyBackspace = function(input, pos){ - var i, buffer = input.split(''); - for(i = pos - 1; i >= 0; i--){ - if(this.tests[i].test) - break; - } - buffer.splice(i, 1); - var result= this.apply(buffer.join(''), i, true); - result.pos=i; - return result; - }; - - FixedWidthMask.prototype.applyDelete = function(input, pos){ - var i, buffer = input.split(''); - for(i = pos; i < buffer.length; i++){ - if(this.tests[i].test) - break; - } - buffer.splice(i, 1); - var result=this.apply(buffer.join(''), i, true); - result.pos=i; - return result; - }; - - FixedWidthMask.prototype.apply = function(inputString, caretPosition, doShift){ - if(caretPosition == null) - caretPosition = this.length; - - var input=inputString.split(''), - buffer=[], - raw=[], - lastMatch = -1, - i, - action, - pos; - - for (i = 0, pos = 0; i < this.length; i++) { - action=this.tests[i]; - - if (action.test) { - buffer.push(this.settings.placeholder); - - while (pos++ < input.length) { - var c = input[pos - 1]; - if (action.test(c)) { - buffer[i] = c; - raw.push(c); - lastMatch = i; - break; - }else if(doShift){ - //TODO: The following is awful and needs to be refactored. - var tests; - /* jshint ignore:start */ - tests=$.map(this.tests.slice(i + 1), function(test, offset){ - var index = pos - 1 + offset; - if(test.test && input[index] != null){ - return {regex:test,char:input[index]}; - } - }); - /* jshint ignore:end */ - - var newInput = []; - var canShift = tests.length > 0; - - if(tests.length){ - tests.unshift({regex: action}); - for(var j = 1; j < tests.length; j++){ - if(!tests[j-1].regex.test(tests[j].char)){ - canShift = false; - break; - } - newInput.push(tests[j].char); - } - } - - if(canShift){ - //Everything to the right can shift left and still match. - input = newInput; - buffer[i] = input[0]; - pos = 1; - }else{ - //Retry current char at next position leaving a blank. - pos--; - } - //Only allow shift attempt to happen once. - doShift = false; - break; - } - } - } else { - buffer.push(action); - if(action === input[pos] && i !== this.partialPosition) { - pos++; - } - } - } - - //Find the next spot waiting for input - var maxCaret=Math.min(caretPosition,this.length); - for(i=Math.min(lastMatch+1,maxCaret);i< this.length){ - trimmed = buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)); - } - - if(!this.settings.autoclear){ - trimmed = buffer.slice(0,i); - } - - //TODO: better names for these props - var result={ - value: buffer.join(''), //Prompt Value - trimmed: trimmed.join(''), //Display Value - raw: raw.join(''), //Raw Value, TODO: separate unmask call? - pos: i , //(partialPosition ? i : firstNonMaskPos) - isComplete: (lastMatch + 1) >= this.partialPosition - }; - return result; - }; - function getPasteEvent() { var el = document.createElement('input'), name = 'onpaste'; @@ -222,8 +73,8 @@ completed: null }, settings); - var mask=new FixedWidthMask(format,settings); - + //Hardcoded as fixed for now. + var mask=new $.mask.masks.fixed(format, settings); return this.trigger("unmask").each(function() { var elm = this, diff --git a/src/masks/FixedWidthMask.js b/src/masks/FixedWidthMask.js new file mode 100644 index 0000000..7e21f09 --- /dev/null +++ b/src/masks/FixedWidthMask.js @@ -0,0 +1,152 @@ +(function(){ + function FixedWidthMask (mask, settings){ + var self=this; + //Build up structures necessary to quickly apply masking + this.settings = settings; + + this.tests = []; + this.partialPosition = mask.length; + this.length = mask.length; + + var firstNonMaskPos = null; + + $.each(mask.split(""), function(i, c) { + if (c == '?') { + self.length--; + self.partialPosition = i; + } else if (settings.definitions[c]) { + self.tests.push(new RegExp(settings.definitions[c])); + if (firstNonMaskPos === null) { + firstNonMaskPos = self.tests.length - 1; + } + } else { + self.tests.push(c); + } + }); + } + + FixedWidthMask.prototype.applyBackspace = function(input, pos){ + var i, buffer = input.split(''); + for(i = pos - 1; i >= 0; i--){ + if(this.tests[i].test) + break; + } + buffer.splice(i, 1); + var result= this.apply(buffer.join(''), i, true); + result.pos=i; + return result; + }; + + FixedWidthMask.prototype.applyDelete = function(input, pos){ + var i, buffer = input.split(''); + for(i = pos; i < buffer.length; i++){ + if(this.tests[i].test) + break; + } + buffer.splice(i, 1); + var result=this.apply(buffer.join(''), i, true); + result.pos=i; + return result; + }; + + FixedWidthMask.prototype.apply = function(inputString, caretPosition, doShift){ + if(caretPosition == null) + caretPosition = this.length; + + var input=inputString.split(''), + buffer=[], + raw=[], + lastMatch = -1, + i, + action, + pos; + + for (i = 0, pos = 0; i < this.length; i++) { + action=this.tests[i]; + + if (action.test) { + buffer.push(this.settings.placeholder); + + while (pos++ < input.length) { + var c = input[pos - 1]; + if (action.test(c)) { + buffer[i] = c; + raw.push(c); + lastMatch = i; + break; + }else if(doShift){ + //TODO: The following is awful and needs to be refactored. + var tests; + /* jshint ignore:start */ + tests=$.map(this.tests.slice(i + 1), function(test, offset){ + var index = pos - 1 + offset; + if(test.test && input[index] != null){ + return {regex:test,char:input[index]}; + } + }); + /* jshint ignore:end */ + + var newInput = []; + var canShift = tests.length > 0; + + if(tests.length){ + tests.unshift({regex: action}); + for(var j = 1; j < tests.length; j++){ + if(!tests[j-1].regex.test(tests[j].char)){ + canShift = false; + break; + } + newInput.push(tests[j].char); + } + } + + if(canShift){ + //Everything to the right can shift left and still match. + input = newInput; + buffer[i] = input[0]; + pos = 1; + }else{ + //Retry current char at next position leaving a blank. + pos--; + } + //Only allow shift attempt to happen once. + doShift = false; + break; + } + } + } else { + buffer.push(action); + if(action === input[pos] && i !== this.partialPosition) { + pos++; + } + } + } + + //Find the next spot waiting for input + var maxCaret=Math.min(caretPosition,this.length); + for(i=Math.min(lastMatch+1,maxCaret);i< this.length){ + trimmed = buffer.slice(0, Math.max(this.partialPosition,lastMatch+1)); + } + + if(!this.settings.autoclear){ + trimmed = buffer.slice(0,i); + } + + //TODO: better names for these props + var result={ + value: buffer.join(''), //Prompt Value + trimmed: trimmed.join(''), //Display Value + raw: raw.join(''), //Raw Value, TODO: separate unmask call? + pos: i , //(partialPosition ? i : firstNonMaskPos) + isComplete: (lastMatch + 1) >= this.partialPosition + }; + return result; + }; + $.mask.masks.fixed = FixedWidthMask; +})(jQuery); From df370e44e311f7842411b3d8c9832a91fdfe2239 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 23:15:57 -0500 Subject: [PATCH 15/18] Breaking: Replacing completed callback with proper event. --- demo/index.html | 5 ++++- spec/Paste.Spec.js | 6 +++--- src/jquery.maskedinput.js | 11 ++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/demo/index.html b/demo/index.html index d297b1e..c57914e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -6,7 +6,10 @@ $(function() { $.mask.definitions['~'] = "[+-]"; - $("#date").mask("99/99/9999",{completed:function(){alert("completed!");}}); + $("#date").mask("99/99/9999") + .on("completed.mask",function(){ + alert("completed with value: "+this.value); + }); $("#phone").mask("(999) 999-9999"); $("#phoneExt").mask("(999) 999-9999? x99999"); $("#iphone").mask("+33 999 999 999"); diff --git a/spec/Paste.Spec.js b/spec/Paste.Spec.js index a9ae291..0997f33 100644 --- a/spec/Paste.Spec.js +++ b/spec/Paste.Spec.js @@ -1,8 +1,8 @@ -feature("Pasting", function() { +feature("Pasting", function() { scenario('When pasting a value',function(){ var completed=false; given("an input with a completed callback", function(){ - input.mask("99",{completed:function(){completed=true;}}); + input.mask("99").on("completed.mask",function(){completed=true;}); }); when("pasting",function(){ @@ -13,4 +13,4 @@ feature("Pasting", function() { expect(completed).toBeTruthy(); }); }); -}); \ No newline at end of file +}); diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 44cfdea..a8c3df7 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -73,7 +73,7 @@ completed: null }, settings); - //Hardcoded as fixed for now. + //Hardcoded as fixed for now. var mask=new $.mask.masks.fixed(format, settings); return this.trigger("unmask").each(function() { @@ -137,8 +137,9 @@ elm.value = result.value; setCaret(elm, result.pos); - if(result.isComplete && settings.completed) - settings.completed.call(input); //TODO: Raise event instead. + if(result.isComplete) + input.trigger("completed.mask"); + e.preventDefault(); // if(android){ @@ -178,8 +179,8 @@ var result = mask.apply(elm.value, pos.end); elm.value = result.value; setCaret(elm, result.pos); - if(result.isComplete && settings.completed) - settings.completed.call(input); //TODO: Raise event instead. + if(result.isComplete) + input.trigger("completed.mask"); }, 0); } input.data($.mask.dataName,mask); From e46b100430f777495c8dfed0c1c736f52855f1c6 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 23:25:37 -0500 Subject: [PATCH 16/18] Force curly braces for single line blocks. --- src/.jshintrc | 4 +++- src/jquery.maskedinput.js | 6 ++++-- src/masks/FixedWidthMask.js | 12 ++++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/.jshintrc b/src/.jshintrc index 97efc48..c07d7d2 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -5,5 +5,7 @@ "newcap" : true, "indent" : 4, "undef" : true, - "unused" : true + "unused" : true, + "trailing" : true, + "curly" : true } diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index a8c3df7..cd4b983 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -137,8 +137,9 @@ elm.value = result.value; setCaret(elm, result.pos); - if(result.isComplete) + if(result.isComplete){ input.trigger("completed.mask"); + } e.preventDefault(); @@ -179,8 +180,9 @@ var result = mask.apply(elm.value, pos.end); elm.value = result.value; setCaret(elm, result.pos); - if(result.isComplete) + if(result.isComplete){ input.trigger("completed.mask"); + } }, 0); } input.data($.mask.dataName,mask); diff --git a/src/masks/FixedWidthMask.js b/src/masks/FixedWidthMask.js index 7e21f09..d61192d 100644 --- a/src/masks/FixedWidthMask.js +++ b/src/masks/FixedWidthMask.js @@ -28,8 +28,9 @@ FixedWidthMask.prototype.applyBackspace = function(input, pos){ var i, buffer = input.split(''); for(i = pos - 1; i >= 0; i--){ - if(this.tests[i].test) + if(this.tests[i].test){ break; + } } buffer.splice(i, 1); var result= this.apply(buffer.join(''), i, true); @@ -40,8 +41,9 @@ FixedWidthMask.prototype.applyDelete = function(input, pos){ var i, buffer = input.split(''); for(i = pos; i < buffer.length; i++){ - if(this.tests[i].test) + if(this.tests[i].test){ break; + } } buffer.splice(i, 1); var result=this.apply(buffer.join(''), i, true); @@ -50,8 +52,9 @@ }; FixedWidthMask.prototype.apply = function(inputString, caretPosition, doShift){ - if(caretPosition == null) + if(caretPosition == null){ caretPosition = this.length; + } var input=inputString.split(''), buffer=[], @@ -125,8 +128,9 @@ //Find the next spot waiting for input var maxCaret=Math.min(caretPosition,this.length); for(i=Math.min(lastMatch+1,maxCaret);i Date: Mon, 24 Mar 2014 23:31:23 -0500 Subject: [PATCH 17/18] Enforcing strict mode. --- src/.jshintrc | 3 ++- src/jquery.maskedinput.js | 4 ++-- src/masks/FixedWidthMask.js | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/.jshintrc b/src/.jshintrc index c07d7d2..a26251b 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -7,5 +7,6 @@ "undef" : true, "unused" : true, "trailing" : true, - "curly" : true + "curly" : true, + "strict" : true } diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index cd4b983..d291665 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -1,5 +1,5 @@ (function($) { - + "use strict"; function setCaret(elm,begin,end){ end = (typeof end === 'number') ? end : begin; @@ -116,7 +116,7 @@ setCaret(elm, result.pos); e.preventDefault(); } else if(k === 13) { // enter - blurEvent.call(this, e); + blurEvent.call(elm, e); } else if (k === 27) { // escape elm.value = focusText; setCaret(elm, 0, focusText.length); diff --git a/src/masks/FixedWidthMask.js b/src/masks/FixedWidthMask.js index d61192d..e7ad0a1 100644 --- a/src/masks/FixedWidthMask.js +++ b/src/masks/FixedWidthMask.js @@ -1,4 +1,5 @@ (function(){ + "use strict"; function FixedWidthMask (mask, settings){ var self=this; //Build up structures necessary to quickly apply masking From 4bde7fe2bb4d2f3e47fa6e4d2e2d1a286f1d53d3 Mon Sep 17 00:00:00 2001 From: Josh Bush Date: Mon, 24 Mar 2014 23:43:30 -0500 Subject: [PATCH 18/18] plugin.json is an artifact from an old build system. --- plugin.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 plugin.json diff --git a/plugin.json b/plugin.json deleted file mode 100644 index e2aa63d..0000000 --- a/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name" : "jquery.maskedinput", - "author" : "Josh Bush (digitalbush.com)", - "version" : "1.3.1" -}