From 0983617a3ab4f934c42f398ceb5f70b3e93f4d7f Mon Sep 17 00:00:00 2001 From: scottrippey Date: Tue, 18 Jan 2011 14:37:00 -0800 Subject: [PATCH 1/5] Added code for custom patterns and custom placeholders. --- src/jquery.maskedinput.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 54cf2e4..753bd65 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -1,4 +1,4 @@ -/* +/* Masked Input plugin for jQuery Copyright (c) 2007-2010 Josh Bush (digitalbush.com) Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) @@ -72,7 +72,7 @@ len--; partialPosition = i; } else if (defs[c]) { - tests.push(new RegExp(defs[c])); + tests.push((defs[c].test) ? defs[c] : new RegExp(defs[c])); // (See if the def already has a test function defined) if(firstNonMaskPos==null) firstNonMaskPos = tests.length - 1; } else { @@ -82,7 +82,7 @@ return this.each(function() { var input = $(this); - var buffer = $.map(mask.split(""), function(c, i) { if (c != '?') return defs[c] ? settings.placeholder : c }); + var buffer = $.map(mask.split(""), function(c, i) { if (c != '?') return (defs[c] ? (defs[c].placeholder || settings.placeholder) : c); }); var ignore = false; //Variable for ignoring control keys var focusText = input.val(); @@ -97,7 +97,7 @@ while (!tests[pos] && --pos >= 0); for (var i = pos; i < len; i++) { if (tests[i]) { - buffer[i] = settings.placeholder; + buffer[i] = tests[i].placeholder || settings.placeholder; var j = seekNext(i); if (j < len && tests[i].test(buffer[j])) { buffer[i] = buffer[j]; @@ -110,11 +110,11 @@ }; function shiftR(pos) { - for (var i = pos, c = settings.placeholder; i < len; i++) { + for (var i = pos, c = null; i < len; i++) { if (tests[i]) { var j = seekNext(i); var t = buffer[i]; - buffer[i] = c; + buffer[i] = c || tests[i].placeholder || settings.placeholder; if (j < len && tests[j].test(t)) c = t; else @@ -176,7 +176,7 @@ function clearBuffer(start, end) { for (var i = start; i < end && i < len; i++) { if (tests[i]) - buffer[i] = settings.placeholder; + buffer[i] = tests[i].placeholder || settings.placeholder; } }; @@ -188,7 +188,7 @@ var lastMatch = -1; for (var i = 0, pos = 0; i < len; i++) { if (tests[i]) { - buffer[i] = settings.placeholder; + buffer[i] = tests[i].placeholder || settings.placeholder; while (pos++ < test.length) { var c = test.charAt(pos - 1); if (tests[i].test(c)) { From 80e122cc182025bf7772a3b12bc09ef7e18fd2a3 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Wed, 19 Jan 2011 23:50:07 -0800 Subject: [PATCH 2/5] Manually merged "focus fix" from trunk --- src/jquery.maskedinput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index 753bd65..ab22f97 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -25,7 +25,6 @@ end = (typeof end == 'number') ? end : begin; return this.each(function() { if (this.setSelectionRange) { - this.focus(); this.setSelectionRange(begin, end); } else if (this.createTextRange) { var range = this.createTextRange(); @@ -226,12 +225,13 @@ focusText = input.val(); var pos = checkVal(); writeBuffer(); - setTimeout(function() { + var moveCaret = function() { if (pos == mask.length) input.caret(0, pos); else input.caret(pos); - }, 0); + }; + $.browser.msie ? moveCaret() : setTimeout(moveCaret,0); }) .bind("blur.mask", function() { checkVal(); From fa908d1e79a2733bb4e113bb3256dfa1c13fa66a Mon Sep 17 00:00:00 2001 From: scottrippey Date: Wed, 19 Jan 2011 23:51:19 -0800 Subject: [PATCH 3/5] Added working "dollar field" code with a customized, heavily refactored "maskedinput" --- src/jquery.maskedinput-custom.js | 325 +++++++++++++++++++++++++++++++ src/toDollarField.js | 141 ++++++++++++++ 2 files changed, 466 insertions(+) create mode 100644 src/jquery.maskedinput-custom.js create mode 100644 src/toDollarField.js diff --git a/src/jquery.maskedinput-custom.js b/src/jquery.maskedinput-custom.js new file mode 100644 index 0000000..7b9f5c1 --- /dev/null +++ b/src/jquery.maskedinput-custom.js @@ -0,0 +1,325 @@ +/* + Masked Input plugin for jQuery + Copyright (c) 2007-2010 Josh Bush (digitalbush.com) + Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) + Version: 1.2.3 +*/ +(function ($) { + var pasteEventName = ($.browser.msie ? 'paste' : 'input') + ".mask"; + var iPhone = (window.orientation != undefined); + + $.mask = { + //Predefined character definitions + definitions: { + '9': "[0-9]", + 'a': "[A-Za-z]", + '*': "[A-Za-z0-9]" + }, + createBuffer: function (input, mask, settings) { + /// + /// This method creates a "buffer" object that handles + /// all masking logic. + /// The "buffer" object has 4 public methods: + /// getText() // Returns the un-masked text + /// insert(c) // Inserts a character + /// remove(d) // Removes a character in the specified direction + /// checkVal() // Checks and formats the current value + /// + + // Parse the mask, and create the tests: + var defs = $.mask.definitions; + var tests = []; + var partialPosition = null; + var firstNonMaskPos = null; + var lastNonMaskPos = null; + + $.each(mask.split(""), function (i, c) { + if (c == '?') { + partialPosition = i; + } else if (defs[c]) { + tests.push(new RegExp(defs[c])); + if (firstNonMaskPos == null) + firstNonMaskPos = tests.length - 1; + lastNonMaskPos = tests.length - 1; + } else { + tests.push(null); + } + }); + if (partialPosition == null) { + partialPosition = lastNonMaskPos; + } + + var buffer = $.map(mask.split(""), function (c, i) { if (c != '?') return (defs[c] ? (defs[c].placeholder || settings.placeholder) : c); }); + + + // Private functions: + function seekNext(pos) { + while (++pos <= tests.length && !tests[pos]); + return pos; + }; + + function shiftL(pos) { + for (var i = pos; i < tests.length; i++) { + if (tests[i]) { + var j = seekNext(i); + buffer[i] = tests[i].placeholder || settings.placeholder; + if (j < tests.length && tests[i].test(buffer[j])) { + buffer[i] = buffer[j]; + } else + break; + } + } + }; + + function shiftR(pos) { + for (var i = pos, c = null; i < tests.length; i++) { + if (tests[i]) { + var j = seekNext(i); + var t = buffer[i]; + buffer[i] = c || tests[i].placeholder || settings.placeholder; + if (j < tests.length && tests[j].test(t)) + c = t; + else + break; + } + } + }; + + + function clearBuffer(start, end) { + for (var i = start; i < end && i < tests.length; i++) { + if (tests[i]) + buffer[i] = tests[i].placeholder || settings.placeholder; + } + }; + + function writeBuffer() { input.val(buffer.join('')); }; + + buffer.getText = function () { + return $.map(buffer, function (c, i) { + return tests[i] ? c : null; + }).join(''); + }; + buffer.insert = function (c) { + var pos = input.caret(); + //delete selection before proceeding + if (pos.begin != pos.end) { + clearBuffer(pos.begin, pos.end); + } + var p = seekNext(pos.begin - 1); + if (p < tests.length) { + if (tests[p].test(c)) { + shiftR(p); + buffer[p] = c; + var next = seekNext(p); + writeBuffer(); + input.caret(next); + + if (settings.completed && next >= tests.length) + settings.completed.call(input); + } + } + }; + buffer.remove = function (d) { + // From KeyDown: + var pos = input.caret(); + var npos; + //delete selection before proceeding + if (pos.begin != pos.end) { + clearBuffer(pos.begin, pos.end); + npos = seekNext(pos.begin - 1); + } + else { // Delete the current character: + var p = pos.begin; + if (d == -1) p--; // "backspace" + // Find the closest character to delete: + while (!tests[p] && 0 <= p && p < tests.length) p += d; + if (tests[p]) { + shiftL(p); + npos = p; + } else { + npos = pos.begin; + } + } + writeBuffer(); + input.caret(npos); + }; + buffer.checkVal = function (allowIncomplete) { + //try to place characters where they belong + var pos = input.caret(); + var test = input.val(); + var lastMatch = -1; + for (var i = 0, p = 0; i < tests.length; i++) { + if (tests[i]) { + buffer[i] = tests[i].placeholder || settings.placeholder; + while (p++ < test.length) { + var c = test.charAt(p - 1); + if (tests[i].test(c)) { + buffer[i] = c; + lastMatch = i; + break; + } + } + if (p > test.length) + break; + } else if (buffer[i] == test.charAt(p) && i != partialPosition) { + p++; + lastMatch = i; + } + } + if (!allowIncomplete && lastMatch + 1 < partialPosition) { + input.val(""); + clearBuffer(0, buffer.length); + } else if (allowIncomplete || lastMatch + 1 >= partialPosition) { + writeBuffer(); + if (!allowIncomplete) input.val(input.val().substring(0, lastMatch + 1)); + } + + return (partialPosition ? i : firstNonMaskPos); + }; + + + + return buffer; + } // end createBuffer + }; + + $.fn.extend({ + // Helper Function for Caret positioning + caret: function (begin, end) { + if (this.length == 0) 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) { + var 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) { + var 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"); }, + mask: function (mask, settings) { + // If "mask(false)" or no parameters were supplied, return the value without the mask: + if (!mask && this.length > 0) { + var input = $(this[0]); + return input.data("buffer").getText(true); + } + // If "mask(true)" was called, let's update the text: + if (mask === true) { + var input = $(this[0]); + return input.data("buffer").checkVal(); + } + + // Default settings: + settings = $.extend({ + placeholder: "_", + completed: null + }, settings); + + return this.each(function () { + var input = $(this); + // Store the initial value of the text (for when the user presses Escape, and to fire Change events): + var focusText = input.val(); + + // Create the buffer: + var createBuffer = (settings.createBuffer || $.mask.createBuffer); // Gets the function that will create our buffer (unless it was overridden) + var buffer = createBuffer(input, mask, settings); + // Store the buffer: + input.data("buffer", buffer); + + // Perform initial check for existing values: + buffer.checkVal(); + + + + + // Variable for ignoring control keys: + var ignore = false; + + // Bind the events: + if (input.attr("readonly")) return; + + input + .one("unmask", function () { + input + .unbind(".mask") + .removeData("buffer") + .removeData("tests"); + }) + .bind("focus.mask", function () { + focusText = input.val(); + var pos = buffer.checkVal(true); + var moveCaret = function(){ + if (pos == buffer.length) + input.caret(0,pos); + else + input.caret(pos); + }; + $.browser.msie ? moveCaret() : setTimeout(moveCaret,0); + }) + .bind("blur.mask", function () { + buffer.checkVal(false); + if (input.val() != focusText) + input.change(); + }) + .bind("keydown.mask", function (e) { + var k = e.keyCode; + ignore = (k < 16 || (k > 16 && k < 32) || (k > 32 && k < 41)); + + // backspace, delete, and escape get special treatment + if (k == 8 || k == 46 || (iPhone && k == 127)) { // backspace/delete + buffer.remove((k == 46 ? 1 : -1)); + return false; + } else if (k == 27) {// escape + input.val(focusText); + var pos = buffer.checkVal(); + if (pos == buffer.length) + input.caret(0, pos); + else + input.caret(pos); + return false; + } + }) + .bind("keypress.mask", function (e) { + if (ignore) { + ignore = false; + // Fixes Mac FF bug on backspace + return (e.keyCode == 8) ? false : null; + } + e = e || window.event; // (unnecessary? jQuery does this automatically?) + var k = e.charCode || e.keyCode || e.which; + if (e.ctrlKey || e.altKey || e.metaKey || e.charCode === 0) { // Ignore + return true; + } else if ((k >= 32 && k <= 125) || k > 186) { // typeable characters + var c = String.fromCharCode(k); + buffer.insert(c); + } + return false; + }) + .bind(pasteEventName, function () { + setTimeout(function () { + var pos = buffer.checkVal(true); + input.caret(pos); + }, 0); + }); + + }); + } + }); +})(jQuery); \ No newline at end of file diff --git a/src/toDollarField.js b/src/toDollarField.js new file mode 100644 index 0000000..5556209 --- /dev/null +++ b/src/toDollarField.js @@ -0,0 +1,141 @@ +(function($) { + + $.fn.extend({ + toDollarField: function(decimals, min, max) { + // ToDo: implement min, max + return this.mask("$#", {decimals:decimals, createBuffer: createNumberBuffer, fillDecimals: true}); + }, + toPercentField: function(decimals, min, max) { + // ToDo: implement min, max + return this.mask("#%", {decimals:decimals, createBuffer: createNumberBuffer, fillDecimals: true}); + } + }); + + + function createNumberBuffer(input, mask, settings) { + + // This is a special "buffer" that can be passed to the maskedinput plugin + // in order to handle "currency formatting" in fields. + + // The mask should contain ONLY 1 "#", along with any other "decorations". + mask = mask.split("#"); + if (mask.length != 2) mask = ["",""]; + + var buffer = input.val().split(""); + + var digit = /[0-9]/; + + + // Private functions: + + function formatBuffer(cpos) { + var coff = 0; // caret offset + var maxDecim = settings.decimals == undefined ? 0 : settings.decimals; + var decim = null; // Decimal place + // Extract all valid characters: + var digits = []; + for (var i = 0; i < buffer.length; i++) { + if (digit.test(buffer[i]) && (decim == null || (digits.length - decim <= maxDecim))) { + digits.push(buffer[i]); + } else if (decim == null && buffer[i] == ".") { + decim = digits.length; + if (settings.fillDecimals && (maxDecim > 0)) + digits.push("."); + } + else if (cpos > i) + coff--; // track the caret as we remove characters. + } + cpos += coff; // Offset the caret + if (decim == null) { // No decimal was found + decim = digits.length; + if (settings.fillDecimals && (maxDecim > 0)) + digits.push("."); + } + + // Add decimal places if necessary: + while (settings.fillDecimals && (digits.length - decim <= maxDecim)) { + digits.push("0"); + } + + // Insert commas: + for (var i = decim-3; i > 0; i-=3) { + digits.splice(i,0,","); + if (cpos >= i) cpos++; // track the caret as we add characters. + } + + // Add the mask "decorations": + cpos += mask[0].length; + Array.prototype.unshift.apply(digits, mask[0].split("")); // before + Array.prototype.push.apply(digits, mask[1].split("")); // and after + + // Replace the entire buffer with the new digits: + digits.unshift(0,buffer.length); + Array.prototype.splice.apply(buffer,digits); + + return cpos; + } + function writeBuffer() { + input.val(buffer.join('')); + }; + + // "Public" functions, used by maskedinput + buffer.getText = function() { + return buffer.join(""); + }; + buffer.insert = function(c) { + var pos = input.caret(); + // Delete the selection + if (pos.begin != pos.end) { + buffer.splice(pos.begin, pos.end-pos.begin); + } + // Insert the character: + buffer.splice(pos.begin,0,c); + // Update the output: + var cpos = formatBuffer(pos.begin+1); + writeBuffer(); + input.caret(cpos); + }; + buffer.remove = function(dir) { + var pos = input.caret(); + var cpos; + // Delete the selection + if (pos.begin != pos.end) { + buffer.splice(pos.begin,pos.end-pos.begin); + cpos = formatBuffer(pos.begin); + } + else { // Delete the current character: + var p = pos.begin; + if (dir == -1) p--; // "backspace" + // Find the closest digit to delete: + while (!digit.test(buffer[p]) && 0 <= p && p < buffer.length) p += dir; + if (p < 0 || buffer.length <= p) { // Nothing to delete + return; + } + buffer.splice(p,1); + cpos = formatBuffer(p); + } + // Update the output: + writeBuffer(); + input.caret(cpos); + }; + buffer.checkVal = function(allowIncomplete) { + //try to place characters where they belong + var pos = input.caret(); + var cpos; // caret position + var text = input.val().split(""); + + // Replace the entire buffer with the current value: + text.unshift(0,buffer.length); + Array.prototype.splice.apply(buffer, text); + + // Update the output: + cpos = formatBuffer(pos.begin); + writeBuffer(); + + return cpos; // Return the new caret position + }; + + return buffer; + }; + +})(jQuery); \ No newline at end of file From c3b1503ed06fd02f62d8c1de98df89ba8aad4017 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 20 Jan 2011 00:22:01 -0800 Subject: [PATCH 4/5] Added a demo and renamed toDollarField. Also fixed a small "fillDecimals" issue. --- demo/numberFieldDemo.html | 36 +++++++++++++++++++ src/jquery.maskedinput-custom.js | 6 ++-- ...DollarField.js => jquery.toNumberField.js} | 19 +++++----- 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 demo/numberFieldDemo.html rename src/{toDollarField.js => jquery.toNumberField.js} (87%) diff --git a/demo/numberFieldDemo.html b/demo/numberFieldDemo.html new file mode 100644 index 0000000..f616ea1 --- /dev/null +++ b/demo/numberFieldDemo.html @@ -0,0 +1,36 @@ + + + jQuery NumberField Demo + + + + + + + + + + + + +
Number (no decimal place)99,999
Number (4 decimal places)99,999.9999
Number (4 filled decimal places)99,999.0000
Currency$99,999.99
Percent99.9%
+
+ + diff --git a/src/jquery.maskedinput-custom.js b/src/jquery.maskedinput-custom.js index 7b9f5c1..31503b6 100644 --- a/src/jquery.maskedinput-custom.js +++ b/src/jquery.maskedinput-custom.js @@ -217,13 +217,11 @@ mask: function (mask, settings) { // If "mask(false)" or no parameters were supplied, return the value without the mask: if (!mask && this.length > 0) { - var input = $(this[0]); - return input.data("buffer").getText(true); + return $(this[0]).data("buffer").getText(true); } // If "mask(true)" was called, let's update the text: if (mask === true) { - var input = $(this[0]); - return input.data("buffer").checkVal(); + return $(this[0]).data("buffer").checkVal(); } // Default settings: diff --git a/src/toDollarField.js b/src/jquery.toNumberField.js similarity index 87% rename from src/toDollarField.js rename to src/jquery.toNumberField.js index 5556209..9f48b40 100644 --- a/src/toDollarField.js +++ b/src/jquery.toNumberField.js @@ -1,13 +1,16 @@ -(function($) { +/// +(function($) { $.fn.extend({ - toDollarField: function(decimals, min, max) { + toNumberField: function(mask, decimals, fillDecimals, min, max) { // ToDo: implement min, max - return this.mask("$#", {decimals:decimals, createBuffer: createNumberBuffer, fillDecimals: true}); + return this.mask(mask || "#", {decimals:decimals, createBuffer: createNumberBuffer, fillDecimals: fillDecimals}); }, - toPercentField: function(decimals, min, max) { - // ToDo: implement min, max - return this.mask("#%", {decimals:decimals, createBuffer: createNumberBuffer, fillDecimals: true}); + toDollarField: function(decimals, fillDecimals, min, max) { + return this.toNumberField("$#", decimals, fillDecimals, min, max); + }, + toPercentField: function(decimals, fillDecimals, min, max) { + return this.toNumberField("#%", decimals, fillDecimals, min, max); } }); @@ -30,7 +33,7 @@ function formatBuffer(cpos) { var coff = 0; // caret offset - var maxDecim = settings.decimals == undefined ? 0 : settings.decimals; + var maxDecim = settings.decimals || 0; var decim = null; // Decimal place // Extract all valid characters: var digits = []; @@ -39,7 +42,7 @@ digits.push(buffer[i]); } else if (decim == null && buffer[i] == ".") { decim = digits.length; - if (settings.fillDecimals && (maxDecim > 0)) + if (maxDecim > 0) digits.push("."); } else if (cpos > i) From 89e2e9fd2cf9dd5a443186acfca29f2b462ae07c Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 20 Jan 2011 00:35:27 -0800 Subject: [PATCH 5/5] Fixed charCode issue and added "$.mask.definitions.add(...)" method. --- src/jquery.maskedinput.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jquery.maskedinput.js b/src/jquery.maskedinput.js index ab22f97..7366023 100644 --- a/src/jquery.maskedinput.js +++ b/src/jquery.maskedinput.js @@ -11,9 +11,14 @@ $.mask = { //Predefined character definitions definitions: { - '9': "[0-9]", - 'a': "[A-Za-z]", - '*': "[A-Za-z0-9]" + '9': /[0-9]/, + 'a': /[A-Za-z]/, + '*': /[A-Za-z0-9]/, + add: function(key, pattern, placeholder) { + if (typeof pattern === 'string') pattern = new RegExp(pattern); + pattern.placeholder = placeholder; + this[key] = pattern; + } } }; @@ -152,7 +157,7 @@ var k = e.charCode || e.keyCode || e.which; var pos = $(this).caret(); - if (e.ctrlKey || e.altKey || e.metaKey) {//Ignore + if (e.ctrlKey || e.altKey || e.metaKey || e.charCode === 0) {//Ignore return true; } else if ((k >= 32 && k <= 125) || k > 186) {//typeable characters var p = seekNext(pos.begin - 1);