/* * * Copyright (c) 2006-2014 Sam Collett (http://www.texotela.co.uk) * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. * * Version 1.4.1 * Demo: http://www.texotela.co.uk/code/jquery/numeric/ * */ (function(factory){ if(typeof define === 'function' && define.amd){ define(['jquery'], factory); }else{ factory(window.jQuery); } }(function($) { /* * Allows only valid characters to be entered into input boxes. * Note: fixes value when pasting via Ctrl+V, but not when using the mouse to paste * side-effect: Ctrl+A does not work, though you can still use the mouse to select (or double-click to select all) * * @name numeric * @param config { decimal : "." , negative : true } * @param callback A function that runs if the number is not valid (fires onblur) * @author Sam Collett (http://www.texotela.co.uk) * @example $(".numeric").numeric(); * @example $(".numeric").numeric(","); // use , as separator * @example $(".numeric").numeric({ decimal : "," }); // use , as separator * @example $(".numeric").numeric({ altDecimal : "," }); // accept , as alternative separator, but use . as separator in output * @example $(".numeric").numeric({ negative : false }); // do not allow negative values * @example $(".numeric").numeric({ decimalPlaces : 2 }); // only allow 2 decimal places * @example $(".numeric").numeric(null, callback); // use default values, pass on the 'callback' function * */ $.fn.numeric = function(config, callback) { if(typeof config === 'boolean') { config = { decimal: config, negative: true, decimalPlaces: -1 }; } config = config || {}; // if config.negative undefined, set to true (default is to allow negative numbers) if(typeof config.negative == "undefined") { config.negative = true; } // set decimal point var decimal = (config.decimal === false) ? "" : config.decimal || "."; // set alternative key as decimal point var altDecimal = (config.altDecimal === false) ? "" : config.altDecimal || decimal; // allow negatives var negative = (config.negative === true) ? true : false; // set decimal places var decimalPlaces = (typeof config.decimalPlaces == "undefined") ? -1 : config.decimalPlaces; // callback function callback = (typeof(callback) == "function" ? callback : function() {}); // set data and methods return this.data("numeric.decimal", decimal).data("numeric.altDecimal", altDecimal).data("numeric.negative", negative).data("numeric.callback", callback).data("numeric.decimalPlaces", decimalPlaces).keypress($.fn.numeric.keypress).keyup($.fn.numeric.keyup).blur($.fn.numeric.blur); }; $.fn.numeric.keypress = function(e) { // get decimal character and determine if negatives are allowed var decimal = $.data(this, "numeric.decimal"); var negative = $.data(this, "numeric.negative"); var decimalPlaces = $.data(this, "numeric.decimalPlaces"); // get an alternative decimal separator var altDecimal = $.data(this, "numeric.altDecimal"); // get the key that was pressed var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0; // allow enter/return key (only when in an input box) if(key == 13 && this.nodeName.toLowerCase() == "input") { return true; } else if(key == 13) { return false; } //dont allow #, $, % else if(key == 35 || key == 36 || key == 37){ return false; } var allow = false; // allow Ctrl+A if((e.ctrlKey && key == 97 /* firefox */) || (e.ctrlKey && key == 65) /* opera */) { return true; } // allow Ctrl+X (cut) if((e.ctrlKey && key == 120 /* firefox */) || (e.ctrlKey && key == 88) /* opera */) { return true; } // allow Ctrl+C (copy) if((e.ctrlKey && key == 99 /* firefox */) || (e.ctrlKey && key == 67) /* opera */) { return true; } // allow Ctrl+Z (undo) if((e.ctrlKey && key == 122 /* firefox */) || (e.ctrlKey && key == 90) /* opera */) { return true; } // allow or deny Ctrl+V (paste), Shift+Ins if((e.ctrlKey && key == 118 /* firefox */) || (e.ctrlKey && key == 86) /* opera */ || (e.shiftKey && key == 45)) { return true; } // if a number was not pressed if(key < 48 || key > 57) { var value = $(this).val(); /* '-' only allowed at start and if negative numbers allowed */ if($.inArray('-', value.split('')) !== 0 && negative && key == 45 && (value.length === 0 || parseInt($.fn.getSelectionStart(this), 10) === 0)) { return true; } /* only one decimal separator allowed */ if(decimal && key == decimal.charCodeAt(0) && $.inArray(decimal, value.split('')) != -1) { allow = false; } // check for other keys that have special purposes if( key != 8 /* backspace */ && key != 9 /* tab */ && key != 13 /* enter */ && key != 35 /* end */ && key != 36 /* home */ && key != 37 /* left */ && key != 39 /* right */ && key != 46 /* del */ ) { allow = false; } else { // for detecting special keys (listed above) // IE does not support 'charCode' and ignores them in keypress anyway if(typeof e.charCode != "undefined") { // special keys have 'keyCode' and 'which' the same (e.g. backspace) if(e.keyCode == e.which && e.which !== 0) { allow = true; // . and delete share the same code, don't allow . (will be set to true later if it is the decimal point) if(e.which == 46) { allow = false; } } // or keyCode != 0 and 'charCode'/'which' = 0 else if(e.keyCode !== 0 && e.charCode === 0 && e.which === 0) { allow = true; } } } // if key pressed is the decimal or altDecimal and decimal is not already in the field if(decimal && key == decimal.charCodeAt(0) || altDecimal && key == altDecimal.charCodeAt(0)) { if($.inArray(decimal, value.split('')) == -1) { allow = true; } else { allow = false; } } } else { allow = true; // remove extra decimal places if(decimal && decimalPlaces > 0) { var selectionStart = $.fn.getSelectionStart(this); var selectionEnd = $.fn.getSelectionEnd(this); var dot = $.inArray(decimal, $(this).val().split('')); if (selectionStart === selectionEnd && dot >= 0 && selectionStart > dot && $(this).val().length > dot + decimalPlaces) { allow = false; } } } return allow; }; $.fn.numeric.keyup = function(e) { var val = $(this).val(); if(val && val.length > 0) { // get carat (cursor) position var carat = $.fn.getSelectionStart(this); var selectionEnd = $.fn.getSelectionEnd(this); // get decimal character and determine if negatives are allowed var decimal = $.data(this, "numeric.decimal"); var negative = $.data(this, "numeric.negative"); var decimalPlaces = $.data(this, "numeric.decimalPlaces"); // get an alternative decimal separator var altDecimal = $.data(this, "numeric.altDecimal"); // prepend a 0 if necessary if(decimal !== "" && decimal !== null) { // find decimal point var dot = $.inArray(decimal, val.split('')); // if dot at start, add 0 before if(dot === 0) { this.value = "0" + val; carat++; selectionEnd++; } // if dot at position 1, check if there is a - symbol before it if(dot == 1 && val.charAt(0) == "-") { this.value = "-0" + val.substring(1); carat++; selectionEnd++; } val = this.value; } // if pasted in, only allow the following characters var validChars = [0,1,2,3,4,5,6,7,8,9,'-',decimal]; // get length of the value (to loop through) var length = val.length; // loop backwards (to prevent going out of bounds) for(var i = length - 1; i >= 0; i--) { var ch = val.charAt(i); // remove '-' if it is in the wrong place if(i !== 0 && ch == "-") { val = val.substring(0, i) + val.substring(i + 1); } // remove character if it is at the start, a '-' and negatives aren't allowed else if(i === 0 && !negative && ch == "-") { val = val.substring(1); } var validChar = false; // loop through validChars for(var j = 0; j < validChars.length; j++) { // if it is valid, break out the loop if(ch == validChars[j]) { validChar = true; break; } } // if not a valid character and character is altDecimal, replace if(!validChar && ch == altDecimal) { val = val.substring(0, i) + decimal + val.substring(i + 1); validChar = true; } // if not a valid character, or a space, remove if(!validChar || ch == " ") { val = val.substring(0, i) + val.substring(i + 1); } } // remove extra decimal characters var firstDecimal = $.inArray(decimal, val.split('')); if(firstDecimal > 0) { for(var k = length - 1; k > firstDecimal; k--) { var chch = val.charAt(k); // remove decimal character if(chch == decimal) { val = val.substring(0, k) + val.substring(k + 1); } } } // remove extra decimal places if(decimal && decimalPlaces > 0) { var dot = $.inArray(decimal, val.split('')); if (dot >= 0) { val = val.substring(0, dot + decimalPlaces + 1); selectionEnd = Math.min(val.length, selectionEnd); } } // set the value and prevent the cursor moving to the end this.value = val; $.fn.setSelection(this, [carat, selectionEnd]); } }; $.fn.numeric.blur = function() { var decimal = $.data(this, "numeric.decimal"); var callback = $.data(this, "numeric.callback"); var negative = $.data(this, "numeric.negative"); var val = this.value; if(val !== "") { var re = new RegExp("^" + (negative?"-?":"") + "\\d+$|^" + (negative?"-?":"") + "\\d*" + decimal + "\\d+$"); if(!re.exec(val)) { callback.apply(this); } } }; $.fn.removeNumeric = function() { return this.data("numeric.decimal", null).data("numeric.altDecimal", null).data("numeric.negative", null).data("numeric.callback", null).data("numeric.decimalPlaces", null).unbind("keypress", $.fn.numeric.keypress).unbind("keyup", $.fn.numeric.keyup).unbind("blur", $.fn.numeric.blur); }; // Based on code from http://javascript.nwbox.com/cursor_position/ (Diego Perini ) $.fn.getSelectionStart = function(o) { if(o.type === "number"){ return undefined; } else if (o.createTextRange && document.selection) { var r = document.selection.createRange().duplicate(); r.moveEnd('character', o.value.length); if (r.text == '') return o.value.length; return Math.max(0, o.value.lastIndexOf(r.text)); } else { try { return o.selectionStart; } catch(e) { return 0; } } }; // Based on code from http://javascript.nwbox.com/cursor_position/ (Diego Perini ) $.fn.getSelectionEnd = function(o) { if(o.type === "number"){ return undefined; } else if (o.createTextRange && document.selection) { var r = document.selection.createRange().duplicate() r.moveStart('character', -o.value.length) return r.text.length } else return o.selectionEnd } // set the selection, o is the object (input), p is the position ([start, end] or just start) $.fn.setSelection = function(o, p) { // if p is number, start and end are the same if(typeof p == "number") { p = [p, p]; } // only set if p is an array of length 2 if(p && p.constructor == Array && p.length == 2) { if(o.type === "number") { o.focus(); } else if (o.createTextRange) { var r = o.createTextRange(); r.collapse(true); r.moveStart('character', p[0]); r.moveEnd('character', p[1] - p[0]); r.select(); } else { o.focus(); try{ if(o.setSelectionRange) { o.setSelectionRange(p[0], p[1]); } } catch(e) { } } } }; }));