/** * autonumeric.js * @author: bob knothe * @author: sokolov yura * @version: 1.9.35 - 2015-04-16 gmt 10:30 am * * created by robert j. knothe on 2010-10-25. please report any bugs to https://github.com/bobknothe/autonumeric * created by sokolov yura on 2010-11-07 * * copyright (c) 2011 robert j. knothe http://www.decorplanit.com/plugin/ * * the mit license (http://www.opensource.org/licenses/mit-license.php) * * permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "software"), to deal in the software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the software, and to permit persons to whom the * software is furnished to do so, subject to the following * conditions: * * the above copyright notice and this permission notice shall be * included in all copies or substantial portions of the software. * * the software is provided "as is", without warranty of any kind, * express or implied, including but not limited to the warranties * of merchantability, fitness for a particular purpose and * noninfringement. in no event shall the authors or copyright * holders be liable for any claim, damages or other liability, * whether in an action of contract, tort or otherwise, arising * from, out of or in connection with the software or the use or * other dealings in the software. */ (function ($) { "use strict"; /*jslint browser: true*/ /*global jquery: false*/ /*cross browser routine for getting selected range/cursor position */ /** * cross browser routine for getting selected range/cursor position */ function getelementselection(that) { var position = {}; if (that.selectionstart === undefined) { that.focus(); var select = document.selection.createrange(); position.length = select.text.length; select.movestart('character', -that.value.length); position.end = select.text.length; position.start = position.end - position.length; } else { position.start = that.selectionstart; position.end = that.selectionend; position.length = position.end - position.start; } return position; } /** * cross browser routine for setting selected range/cursor position */ function setelementselection(that, start, end) { if (that.selectionstart === undefined) { that.focus(); var r = that.createtextrange(); r.collapse(true); r.moveend('character', end); r.movestart('character', start); r.select(); } else { that.selectionstart = start; that.selectionend = end; } } /** * run callbacks in parameters if any * any parameter could be a callback: * - a function, which invoked with jquery element, parameters and this parameter name and returns parameter value * - a name of function, attached to $(selector).autonumeric.functionname(){} - which was called previously */ function runcallbacks($this, settings) { /** * loops through the settings object (option array) to find the following * k = option name example k=anum * val = option value example val=0123456789 */ $.each(settings, function (k, val) { if (typeof val === 'function') { settings[k] = val($this, settings, k); } else if (typeof $this.autonumeric[val] === 'function') { /** * calls the attached function from the html5 data example: data-a-sign="functionname" */ settings[k] = $this.autonumeric[val]($this, settings, k); } }); } /** * converts the vmin, vmax & mdec string to numeric value */ function convertkeytonumber(settings, key) { if (typeof (settings[key]) === 'string') { settings[key] *= 1; } } /** * preparing user defined options for further usage * merge them with defaults appropriately */ function autocode($this, settings) { runcallbacks($this, settings); settings.taglist = ['b', 'caption', 'cite', 'code', 'dd', 'del', 'div', 'dfn', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ins', 'kdb', 'label', 'li', 'output', 'p', 'q', 's', 'sample', 'span', 'strong', 'td', 'th', 'u', 'var']; var vmax = settings.vmax.tostring().split('.'), vmin = (!settings.vmin && settings.vmin !== 0) ? [] : settings.vmin.tostring().split('.'); convertkeytonumber(settings, 'vmax'); convertkeytonumber(settings, 'vmin'); convertkeytonumber(settings, 'mdec'); /** set mdec if not defined by user */ settings.mdec = (settings.mround === 'chf') ? '2' : settings.mdec; settings.allowleading = true; settings.aneg = settings.vmin < 0 ? '-' : ''; vmax[0] = vmax[0].replace('-', ''); vmin[0] = vmin[0].replace('-', ''); settings.mint = math.max(vmax[0].length, vmin[0].length, 1); if (settings.mdec === null) { var vmaxlength = 0, vminlength = 0; if (vmax[1]) { vmaxlength = vmax[1].length; } if (vmin[1]) { vminlength = vmin[1].length; } settings.mdec = math.max(vmaxlength, vminlength); } /** set alternative decimal separator key */ if (settings.altdec === null && settings.mdec > 0) { if (settings.adec === '.' && settings.asep !== ',') { settings.altdec = ','; } else if (settings.adec === ',' && settings.asep !== '.') { settings.altdec = '.'; } } /** cache regexps for autostrip */ var anegreg = settings.aneg ? '([-\\' + settings.aneg + ']?)' : '(-?)'; settings.anegregautostrip = anegreg; settings.skipfirstautostrip = new regexp(anegreg + '[^-' + (settings.aneg ? '\\' + settings.aneg : '') + '\\' + settings.adec + '\\d]' + '.*?(\\d|\\' + settings.adec + '\\d)'); settings.skiplastautostrip = new regexp('(\\d\\' + settings.adec + '?)[^\\' + settings.adec + '\\d]\\d*$'); var allowed = '-' + settings.anum + '\\' + settings.adec; settings.allowedautostrip = new regexp('[^' + allowed + ']', 'gi'); settings.numregautostrip = new regexp(anegreg + '(?:\\' + settings.adec + '?(\\d+\\' + settings.adec + '\\d+)|(\\d*(?:\\' + settings.adec + '\\d*)?))'); return settings; } /** * strips all unwanted characters and leave only a number alert */ function autostrip(s, settings, strip_zero) { if (settings.asign) { /** remove currency sign */ while (s.indexof(settings.asign) > -1) { s = s.replace(settings.asign, ''); } } s = s.replace(settings.skipfirstautostrip, '$1$2'); /** first replace anything before digits */ s = s.replace(settings.skiplastautostrip, '$1'); /** then replace anything after digits */ s = s.replace(settings.allowedautostrip, ''); /** then remove any uninterested characters */ if (settings.altdec) { s = s.replace(settings.altdec, settings.adec); } /** get only number string */ var m = s.match(settings.numregautostrip); s = m ? [m[1], m[2], m[3]].join('') : ''; if ((settings.lzero === 'allow' || settings.lzero === 'keep') && strip_zero !== 'strip') { var parts = [], nsign = ''; parts = s.split(settings.adec); if (parts[0].indexof('-') !== -1) { nsign = '-'; parts[0] = parts[0].replace('-', ''); } if (parts[0].length > settings.mint && parts[0].charat(0) === '0') { /** strip leading zero if need */ parts[0] = parts[0].slice(1); } s = nsign + parts.join(settings.adec); } if ((strip_zero && settings.lzero === 'deny') || (strip_zero && settings.lzero === 'allow' && settings.allowleading === false)) { var strip_reg = '^' + settings.anegregautostrip + '0*(\\d' + (strip_zero === 'leading' ? ')' : '|$)'); strip_reg = new regexp(strip_reg); s = s.replace(strip_reg, '$1$2'); } return s; } /** * places or removes brackets on negative values * works only when with psign: 'p' */ function negativebracket(s, settings) { if (settings.psign === 'p') { var brackets = settings.nbracket.split(','); if (!settings.hasfocus && !settings.removebrackets) { s = s.replace(settings.aneg, ''); s = brackets[0] + s + brackets[1]; } else if ((settings.hasfocus && s.charat(0) === brackets[0]) || (settings.removebrackets && s.charat(0) === brackets[0])) { s = s.replace(brackets[0], settings.aneg); s = s.replace(brackets[1], ''); } } return s; } /** * function to handle numbers less than 0 that are stored in exponential notation ex: .0000001 stored as 1e-7 */ function checkvalue(value, settings) { if (value) { var checksmall = +value; if (checksmall < 0.000001 && checksmall > -1) { value = +value; if (value < 0.000001 && value > 0) { value = (value + 10).tostring(); value = value.substring(1); } if (value < 0 && value > -1) { value = (value - 10).tostring(); value = '-' + value.substring(2); } value = value.tostring(); } else { var parts = value.split('.'); if (parts[1] !== undefined) { if (+parts[1] === 0) { value = parts[0]; } else { parts[1] = parts[1].replace(/0*$/, ''); value = parts.join('.'); } } } } return (settings.lzero === 'keep') ? value : value.replace(/^0*(\d)/, '$1'); } /** * prepare number string to be converted to real number */ function fixnumber(s, adec, aneg) { if (adec && adec !== '.') { s = s.replace(adec, '.'); } if (aneg && aneg !== '-') { s = s.replace(aneg, '-'); } if (!s.match(/\d/)) { s += '0'; } return s; } /** * prepare real number to be converted to our format */ function presentnumber(s, adec, aneg) { if (aneg && aneg !== '-') { s = s.replace('-', aneg); } if (adec && adec !== '.') { s = s.replace('.', adec); } return s; } /** * private function to check for empty value */ function checkempty(iv, settings, signonempty) { if (iv === '' || iv === settings.aneg) { if (settings.wempty === 'zero') { return iv + '0'; } if (settings.wempty === 'sign' || signonempty) { return iv + settings.asign; } return iv; } return null; } /** * private function that formats our number */ function autogroup(iv, settings) { iv = autostrip(iv, settings); var testneg = iv.replace(',', '.'), empty = checkempty(iv, settings, true); if (empty !== null) { return empty; } var digitalgroup = ''; if (settings.dgroup === 2) { digitalgroup = /(\d)((\d)(\d{2}?)+)$/; } else if (settings.dgroup === 4) { digitalgroup = /(\d)((\d{4}?)+)$/; } else { digitalgroup = /(\d)((\d{3}?)+)$/; } /** splits the string at the decimal string */ var ivsplit = iv.split(settings.adec); if (settings.altdec && ivsplit.length === 1) { ivsplit = iv.split(settings.altdec); } /** assigns the whole number to the a variable (s) */ var s = ivsplit[0]; if (settings.asep) { while (digitalgroup.test(s)) { /** re-inserts the thousand separator via a regular expression */ s = s.replace(digitalgroup, '$1' + settings.asep + '$2'); } } if (settings.mdec !== 0 && ivsplit.length > 1) { if (ivsplit[1].length > settings.mdec) { ivsplit[1] = ivsplit[1].substring(0, settings.mdec); } /** joins the whole number with the decimal value */ iv = s + settings.adec + ivsplit[1]; } else { /** if whole numbers only */ iv = s; } if (settings.asign) { var has_aneg = iv.indexof(settings.aneg) !== -1; iv = iv.replace(settings.aneg, ''); iv = settings.psign === 'p' ? settings.asign + iv : iv + settings.asign; if (has_aneg) { iv = settings.aneg + iv; } } if (testneg < 0 && settings.nbracket !== null) { /** removes the negative sign and places brackets */ iv = negativebracket(iv, settings); } return iv; } /** * round number after setting by pasting or $().autonumericset() * private function for round the number * please note this handled as text - javascript math function can return inaccurate values * also this offers multiple rounding methods that are not easily accomplished in javascript */ function autoround(iv, settings) { /** value to string */ iv = (iv === '') ? '0' : iv.tostring(); convertkeytonumber(settings, 'mdec'); /** set mdec to number needed when mdec set by 'update method */ if (settings.mround === 'chf') { iv = (math.round(iv * 20) / 20).tostring(); } var ivrounded = '', i = 0, nsign = '', rdec = (typeof (settings.apad) === 'boolean' || settings.apad === null) ? (settings.apad ? settings.mdec : 0) : +settings.apad; var truncatezeros = function (ivrounded) { /** truncate not needed zeros */ var regex = (rdec === 0) ? (/(\.(?:\d*[1-9])?)0*$/) : rdec === 1 ? (/(\.\d(?:\d*[1-9])?)0*$/) : new regexp('(\\.\\d{' + rdec + '}(?:\\d*[1-9])?)0*$'); ivrounded = ivrounded.replace(regex, '$1'); /** if there are no decimal places, we don't need a decimal point at the end */ if (rdec === 0) { ivrounded = ivrounded.replace(/\.$/, ''); } return ivrounded; }; if (iv.charat(0) === '-') { /** checks if the iv (input value)is a negative value */ nsign = '-'; iv = iv.replace('-', ''); /** removes the negative sign will be added back later if required */ } if (!iv.match(/^\d/)) { /** append a zero if first character is not a digit (then it is likely to be a dot)*/ iv = '0' + iv; } if (nsign === '-' && +iv === 0) { /** determines if the value is zero - if zero no negative sign */ nsign = ''; } if ((+iv > 0 && settings.lzero !== 'keep') || (iv.length > 0 && settings.lzero === 'allow')) { /** trims leading zero's if needed */ iv = iv.replace(/^0*(\d)/, '$1'); } var dpos = iv.lastindexof('.'), /** virtual decimal position */ vdpos = (dpos === -1) ? iv.length - 1 : dpos, /** checks decimal places to determine if rounding is required */ cdec = (iv.length - 1) - vdpos; /** check if no rounding is required */ if (cdec <= settings.mdec) { ivrounded = iv; /** check if we need to pad with zeros */ if (cdec < rdec) { if (dpos === -1) { ivrounded += '.'; } var zeros = '000000'; while (cdec < rdec) { zeros = zeros.substring(0, rdec - cdec); ivrounded += zeros; cdec += zeros.length; } } else if (cdec > rdec) { ivrounded = truncatezeros(ivrounded); } else if (cdec === 0 && rdec === 0) { ivrounded = ivrounded.replace(/\.$/, ''); } if (settings.mround !== 'chf') { return (+ivrounded === 0) ? ivrounded : nsign + ivrounded; } if (settings.mround === 'chf') { dpos = ivrounded.lastindexof('.'); iv = ivrounded; } } /** rounded length of the string after rounding */ var rlength = dpos + settings.mdec, tround = +iv.charat(rlength + 1), ivarray = iv.substring(0, rlength + 1).split(''), odd = (iv.charat(rlength) === '.') ? (iv.charat(rlength - 1) % 2) : (iv.charat(rlength) % 2), onepass = true; if (odd !== 1) { odd = (odd === 0 && (iv.substring(rlength + 2, iv.length) > 0)) ? 1 : 0; } /*jslint white: true*/ if ((tround > 4 && settings.mround === 's') || /** round half up symmetric */ (tround > 4 && settings.mround === 'a' && nsign === '') || /** round half up asymmetric positive values */ (tround > 5 && settings.mround === 'a' && nsign === '-') || /** round half up asymmetric negative values */ (tround > 5 && settings.mround === 's') || /** round half down symmetric */ (tround > 5 && settings.mround === 'a' && nsign === '') || /** round half down asymmetric positive values */ (tround > 4 && settings.mround === 'a' && nsign === '-') || /** round half down asymmetric negative values */ (tround > 5 && settings.mround === 'b') || /** round half even "banker's rounding" */ (tround === 5 && settings.mround === 'b' && odd === 1) || /** round half even "banker's rounding" */ (tround > 0 && settings.mround === 'c' && nsign === '') || /** round to ceiling toward positive infinite */ (tround > 0 && settings.mround === 'f' && nsign === '-') || /** round to floor toward negative infinite */ (tround > 0 && settings.mround === 'u') || /** round up away from zero */ (settings.mround === 'chf')) { /** round swiss franc */ /*jslint white: false*/ for (i = (ivarray.length - 1); i >= 0; i -= 1) { /** round up the last digit if required, and continue until no more 9's are found */ if (ivarray[i] !== '.') { if (settings.mround === 'chf' && ivarray[i] <= 2 && onepass) { ivarray[i] = 0; onepass = false; break; } if (settings.mround === 'chf' && ivarray[i] <= 7 && onepass) { ivarray[i] = 5; onepass = false; break; } if (settings.mround === 'chf' && onepass) { ivarray[i] = 10; onepass = false; } else { ivarray[i] = +ivarray[i] + 1; } if (ivarray[i] < 10) { break; } if (i > 0) { ivarray[i] = '0'; } } } } ivarray = ivarray.slice(0, rlength + 1); /** reconstruct the string, converting any 10's to 0's */ ivrounded = truncatezeros(ivarray.join('')); /** return rounded value */ return (+ivrounded === 0) ? ivrounded : nsign + ivrounded; } /** * truncate decimal part of a number */ function truncatedecimal(s, settings, paste) { var adec = settings.adec, mdec = settings.mdec; s = (paste === 'paste') ? autoround(s, settings) : s; if (adec && mdec) { var parts = s.split(adec); /** truncate decimal part to satisfying length * cause we would round it anyway */ if (parts[1] && parts[1].length > mdec) { if (mdec > 0) { parts[1] = parts[1].substring(0, mdec); s = parts.join(adec); } else { s = parts[0]; } } } return s; } /** * checking that number satisfy format conditions * and lays between settings.vmin and settings.vmax * and the string length does not exceed the digits in settings.vmin and settings.vmax */ function autocheck(s, settings) { s = autostrip(s, settings); s = truncatedecimal(s, settings); s = fixnumber(s, settings.adec, settings.aneg); var value = +s; return value >= settings.vmin && value <= settings.vmax; } /** * holder object for field properties */ function autonumericholder(that, settings) { this.settings = settings; this.that = that; this.$that = $(that); this.formatted = false; this.settingsclone = autocode(this.$that, this.settings); this.value = that.value; } autonumericholder.prototype = { init: function (e) { this.value = this.that.value; this.settingsclone = autocode(this.$that, this.settings); this.ctrlkey = e.ctrlkey; this.cmdkey = e.metakey; this.shiftkey = e.shiftkey; this.selection = getelementselection(this.that); /** keypress event overwrites meaningful value of e.keycode */ if (e.type === 'keydown' || e.type === 'keyup') { this.kdcode = e.keycode; } this.which = e.which; this.processed = false; this.formatted = false; }, setselection: function (start, end, setreal) { start = math.max(start, 0); end = math.min(end, this.that.value.length); this.selection = { start: start, end: end, length: end - start }; if (setreal === undefined || setreal) { setelementselection(this.that, start, end); } }, setposition: function (pos, setreal) { this.setselection(pos, pos, setreal); }, getbeforeafter: function () { var value = this.value, left = value.substring(0, this.selection.start), right = value.substring(this.selection.end, value.length); return [left, right]; }, getbeforeafterstriped: function () { var parts = this.getbeforeafter(); parts[0] = autostrip(parts[0], this.settingsclone); parts[1] = autostrip(parts[1], this.settingsclone); return parts; }, /** * strip parts from excess characters and leading zeroes */ normalizeparts: function (left, right) { var settingsclone = this.settingsclone; right = autostrip(right, settingsclone); /** if right is not empty and first character is not adec, */ /** we could strip all zeros, otherwise only leading */ var strip = right.match(/^\d/) ? true : 'leading'; left = autostrip(left, settingsclone, strip); /** prevents multiple leading zeros from being entered */ if ((left === '' || left === settingsclone.aneg) && settingsclone.lzero === 'deny') { if (right > '') { right = right.replace(/^0*(\d)/, '$1'); } } var new_value = left + right; /** insert zero if has leading dot */ if (settingsclone.adec) { var m = new_value.match(new regexp('^' + settingsclone.anegregautostrip + '\\' + settingsclone.adec)); if (m) { left = left.replace(m[1], m[1] + '0'); new_value = left + right; } } /** insert zero if number is empty and io.wempty == 'zero' */ if (settingsclone.wempty === 'zero' && (new_value === settingsclone.aneg || new_value === '')) { left += '0'; } return [left, right]; }, /** * set part of number to value keeping position of cursor */ setvalueparts: function (left, right, paste) { var settingsclone = this.settingsclone, parts = this.normalizeparts(left, right), new_value = parts.join(''), position = parts[0].length; if (autocheck(new_value, settingsclone)) { new_value = truncatedecimal(new_value, settingsclone, paste); if (position > new_value.length) { position = new_value.length; } this.value = new_value; this.setposition(position, false); return true; } return false; }, /** * helper function for expandselectiononsign * returns sign position of a formatted value */ signposition: function () { var settingsclone = this.settingsclone, asign = settingsclone.asign, that = this.that; if (asign) { var asignlen = asign.length; if (settingsclone.psign === 'p') { var hasneg = settingsclone.aneg && that.value && that.value.charat(0) === settingsclone.aneg; return hasneg ? [1, asignlen + 1] : [0, asignlen]; } var valuelen = that.value.length; return [valuelen - asignlen, valuelen]; } return [1000, -1]; }, /** * expands selection to cover whole sign * prevents partial deletion/copying/overwriting of a sign */ expandselectiononsign: function (setreal) { var sign_position = this.signposition(), selection = this.selection; if (selection.start < sign_position[1] && selection.end > sign_position[0]) { /** if selection catches something except sign and catches only space from sign */ if ((selection.start < sign_position[0] || selection.end > sign_position[1]) && this.value.substring(math.max(selection.start, sign_position[0]), math.min(selection.end, sign_position[1])).match(/^\s*$/)) { /** then select without empty space */ if (selection.start < sign_position[0]) { this.setselection(selection.start, sign_position[0], setreal); } else { this.setselection(sign_position[1], selection.end, setreal); } } else { /** else select with whole sign */ this.setselection(math.min(selection.start, sign_position[0]), math.max(selection.end, sign_position[1]), setreal); } } }, /** * try to strip pasted value to digits */ checkpaste: function () { if (this.valuepartsbeforepaste !== undefined) { var parts = this.getbeforeafter(), oldparts = this.valuepartsbeforepaste; delete this.valuepartsbeforepaste; /** try to strip pasted value first */ parts[0] = parts[0].substr(0, oldparts[0].length) + autostrip(parts[0].substr(oldparts[0].length), this.settingsclone); if (!this.setvalueparts(parts[0], parts[1], 'paste')) { this.value = oldparts.join(''); this.setposition(oldparts[0].length, false); } } }, /** * process pasting, cursor moving and skipping of not interesting keys * if returns true, further processing is not performed */ skipallways: function (e) { var kdcode = this.kdcode, which = this.which, ctrlkey = this.ctrlkey, cmdkey = this.cmdkey, shiftkey = this.shiftkey; /** catch the ctrl up on ctrl-v */ if (((ctrlkey || cmdkey) && e.type === 'keyup' && this.valuepartsbeforepaste !== undefined) || (shiftkey && kdcode === 45)) { this.checkpaste(); return false; } /** codes are taken from http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/javascript-char-codes-key-codes.aspx * skip fx keys, windows keys, other special keys */ if ((kdcode >= 112 && kdcode <= 123) || (kdcode >= 91 && kdcode <= 93) || (kdcode >= 9 && kdcode <= 31) || (kdcode < 8 && (which === 0 || which === kdcode)) || kdcode === 144 || kdcode === 145 || kdcode === 45) { return true; } if ((ctrlkey || cmdkey) && kdcode === 65) { /** if select all (a=65)*/ return true; } if ((ctrlkey || cmdkey) && (kdcode === 67 || kdcode === 86 || kdcode === 88)) { /** if copy (c=67) paste (v=86) or cut (x=88) */ if (e.type === 'keydown') { this.expandselectiononsign(); } if (kdcode === 86 || kdcode === 45) { /** try to prevent wrong paste */ if (e.type === 'keydown' || e.type === 'keypress') { if (this.valuepartsbeforepaste === undefined) { this.valuepartsbeforepaste = this.getbeforeafter(); } } else { this.checkpaste(); } } return e.type === 'keydown' || e.type === 'keypress' || kdcode === 67; } if (ctrlkey || cmdkey) { return true; } if (kdcode === 37 || kdcode === 39) { /** jump over thousand separator */ var asep = this.settingsclone.asep, start = this.selection.start, value = this.that.value; if (e.type === 'keydown' && asep && !this.shiftkey) { if (kdcode === 37 && value.charat(start - 2) === asep) { this.setposition(start - 1); } else if (kdcode === 39 && value.charat(start + 1) === asep) { this.setposition(start + 1); } } return true; } if (kdcode >= 34 && kdcode <= 40) { return true; } return false; }, /** * process deletion of characters * returns true if processing performed */ processallways: function () { var parts; /** process backspace or delete */ if (this.kdcode === 8 || this.kdcode === 46) { if (!this.selection.length) { parts = this.getbeforeafterstriped(); if (this.kdcode === 8) { parts[0] = parts[0].substring(0, parts[0].length - 1); } else { parts[1] = parts[1].substring(1, parts[1].length); } this.setvalueparts(parts[0], parts[1]); } else { this.expandselectiononsign(false); parts = this.getbeforeafterstriped(); this.setvalueparts(parts[0], parts[1]); } return true; } return false; }, /** * process insertion of characters * returns true if processing performed */ processkeypress: function () { var settingsclone = this.settingsclone, ccode = string.fromcharcode(this.which), parts = this.getbeforeafterstriped(), left = parts[0], right = parts[1]; /** start rules when the decimal character key is pressed */ /** always use numeric pad dot to insert decimal separator */ if (ccode === settingsclone.adec || (settingsclone.altdec && ccode === settingsclone.altdec) || ((ccode === '.' || ccode === ',') && this.kdcode === 110)) { /** do not allow decimal character if no decimal part allowed */ if (!settingsclone.mdec || !settingsclone.adec) { return true; } /** do not allow decimal character before aneg character */ if (settingsclone.aneg && right.indexof(settingsclone.aneg) > -1) { return true; } /** do not allow decimal character if other decimal character present */ if (left.indexof(settingsclone.adec) > -1) { return true; } if (right.indexof(settingsclone.adec) > 0) { return true; } if (right.indexof(settingsclone.adec) === 0) { right = right.substr(1); } this.setvalueparts(left + settingsclone.adec, right); return true; } /** * start rule on negative sign & prevent minus if not allowed */ if (ccode === '-' || ccode === '+') { if (!settingsclone.aneg) { return true; } /** caret is always after minus */ if (left === '' && right.indexof(settingsclone.aneg) > -1) { left = settingsclone.aneg; right = right.substring(1, right.length); } /** change sign of number, remove part if should */ if (left.charat(0) === settingsclone.aneg) { left = left.substring(1, left.length); } else { left = (ccode === '-') ? settingsclone.aneg + left : left; } this.setvalueparts(left, right); return true; } /** digits */ if (ccode >= '0' && ccode <= '9') { /** if try to insert digit before minus */ if (settingsclone.aneg && left === '' && right.indexof(settingsclone.aneg) > -1) { left = settingsclone.aneg; right = right.substring(1, right.length); } if (settingsclone.vmax <= 0 && settingsclone.vmin < settingsclone.vmax && this.value.indexof(settingsclone.aneg) === -1 && ccode !== '0') { left = settingsclone.aneg + left; } this.setvalueparts(left + ccode, right); return true; } /** prevent any other character */ return true; }, /** * formatting of just processed value with keeping of cursor position */ formatquick: function () { var settingsclone = this.settingsclone, parts = this.getbeforeafterstriped(), leftlength = this.value; if ((settingsclone.asep === '' || (settingsclone.asep !== '' && leftlength.indexof(settingsclone.asep) === -1)) && (settingsclone.asign === '' || (settingsclone.asign !== '' && leftlength.indexof(settingsclone.asign) === -1))) { var subparts = [], nsign = ''; subparts = leftlength.split(settingsclone.adec); if (subparts[0].indexof('-') > -1) { nsign = '-'; subparts[0] = subparts[0].replace('-', ''); parts[0] = parts[0].replace('-', ''); } if (subparts[0].length > settingsclone.mint && parts[0].charat(0) === '0') { /** strip leading zero if need */ parts[0] = parts[0].slice(1); } parts[0] = nsign + parts[0]; } var value = autogroup(this.value, this.settingsclone), position = value.length; if (value) { /** prepare regexp which searches for cursor position from unformatted left part */ var left_ar = parts[0].split(''), i = 0; for (i; i < left_ar.length; i += 1) { /** thanks peter kovari */ if (!left_ar[i].match('\\d')) { left_ar[i] = '\\' + left_ar[i]; } } var leftreg = new regexp('^.*?' + left_ar.join('.*?')); /** search cursor position in formatted value */ var newleft = value.match(leftreg); if (newleft) { position = newleft[0].length; /** if we are just before sign which is in prefix position */ if (((position === 0 && value.charat(0) !== settingsclone.aneg) || (position === 1 && value.charat(0) === settingsclone.aneg)) && settingsclone.asign && settingsclone.psign === 'p') { /** place caret after prefix sign */ position = this.settingsclone.asign.length + (value.charat(0) === '-' ? 1 : 0); } } else if (settingsclone.asign && settingsclone.psign === 's') { /** if we could not find a place for cursor and have a sign as a suffix */ /** place carret before suffix currency sign */ position -= settingsclone.asign.length; } } this.that.value = value; this.setposition(position); this.formatted = true; } }; /** * thanks to anthony & evan c */ function autoget(obj) { if (typeof obj === 'string') { obj = obj.replace(/\[/g, "\\[").replace(/\]/g, "\\]"); obj = '#' + obj.replace(/(:|\.)/g, '\\$1'); /** obj = '#' + obj.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); */ /** possible modification to replace the above 2 lines */ } return $(obj); } /** * function to attach data to the element * and imitate the holder */ function getholder($that, settings, update) { var data = $that.data('autonumeric'); if (!data) { data = {}; $that.data('autonumeric', data); } var holder = data.holder; if ((holder === undefined && settings) || update) { holder = new autonumericholder($that.get(0), settings); data.holder = holder; } return holder; } var methods = { /** * method to initiate autonumeric and attached the settings (default and options passed as a parameter * $(someselector).autonumeric('init'); // initiate autonumeric with defaults * $(someselector).autonumeric('init', {option}); // initiate autonumeric with options * $(someselector).autonumeric(); // initiate autonumeric with defaults * $(someselector).autonumeric({option}); // initiate autonumeric with options * options passes as a parameter example '{asep: '.', adec: ',', asign: '€ '} */ init: function (options) { return this.each(function () { var $this = $(this), settings = $this.data('autonumeric'), /** attempt to grab 'autonumeric' settings, if they don't exist returns "undefined". */ tagdata = $this.data(), /** attempt to grab html5 data, if they don't exist we'll get "undefined".*/ $input = $this.is('input[type=text], input[type=hidden], input[type=tel], input:not([type])'); if (typeof settings !== 'object') { /** if we couldn't grab settings, create them from defaults and passed options. */ settings = $.extend({}, $.fn.autonumeric.defaults, tagdata, options, { anum: '0123456789', hasfocus: false, removebrackets: false, runonce: false, taglist: ['b', 'caption', 'cite', 'code', 'dd', 'del', 'div', 'dfn', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ins', 'kdb', 'label', 'li', 'output', 'p', 'q', 's', 'sample', 'span', 'strong', 'td', 'th', 'u', 'var'] }); /** merge defaults, tagdata and options */ if (settings.adec === settings.asep) { $.error("autonumeric will not function properly when the decimal character adec: '" + settings.adec + "' and thousand separator asep: '" + settings.asep + "' are the same character"); } $this.data('autonumeric', settings); /** save our new settings */ } else { return this; } var holder = getholder($this, settings); if (!$input && $this.prop('tagname').tolowercase() === 'input') { /** checks for non-supported input types */ $.error('the input type "' + $this.prop('type') + '" is not supported by autonumeric()'); } if ($.inarray($this.prop('tagname').tolowercase(), settings.taglist) === -1 && $this.prop('tagname').tolowercase() !== 'input') { $.error("the <" + $this.prop('tagname').tolowercase() + "> is not supported by autonumeric()"); } if (settings.runonce === false && settings.aform) { /** routine to format default value on page load */ if ($input) { var setvalue = true; if ($this[0].value === '' && settings.wempty === 'empty') { $this[0].value = ''; setvalue = false; } if ($this[0].value === '' && settings.wempty === 'sign') { $this[0].value = settings.asign; setvalue = false; } if (setvalue && $this[0].value === $this.prop('defaultvalue')) { $this.autonumeric('set', $this.val()); } } if ($.inarray($this.prop('tagname').tolowercase(), settings.taglist) !== -1 && $this.text() !== '') { $this.autonumeric('set', $this.text()); } } settings.runonce = true; if ($this.is('input[type=text], input[type=hidden], input[type=tel], input:not([type])')) { /**added hidden type */ $this.on('keydown.autonumeric', function (e) { holder = getholder($this); if (holder.settings.adec === holder.settings.asep) { $.error("autonumeric will not function properly when the decimal character adec: '" + holder.settings.adec + "' and thousand separator asep: '" + holder.settings.asep + "' are the same character"); } if (holder.that.readonly) { holder.processed = true; return true; } /** the below streamed code / comment allows the "enter" keydown to throw a change() event */ /** if (e.keycode === 13 && holder.inval !== $this.val()){ $this.change(); holder.inval = $this.val(); }*/ holder.init(e); if (holder.skipallways(e)) { holder.processed = true; return true; } if (holder.processallways()) { holder.processed = true; holder.formatquick(); e.preventdefault(); return false; } holder.formatted = false; return true; }); $this.on('keypress.autonumeric', function (e) { holder = getholder($this); var processed = holder.processed; holder.init(e); if (holder.skipallways(e)) { return true; } if (processed) { e.preventdefault(); return false; } if (holder.processallways() || holder.processkeypress()) { holder.formatquick(); e.preventdefault(); return false; } holder.formatted = false; }); $this.on('keyup.autonumeric', function (e) { holder = getholder($this); holder.init(e); var skip = holder.skipallways(e); holder.kdcode = 0; delete holder.valuepartsbeforepaste; if ($this[0].value === holder.settings.asign) { /** added to properly place the caret when only the currency is present */ if (holder.settings.psign === 's') { setelementselection(this, 0, 0); } else { setelementselection(this, holder.settings.asign.length, holder.settings.asign.length); } } if (skip) { return true; } if (this.value === '') { return true; } if (!holder.formatted) { holder.formatquick(); } }); $this.on('focusin.autonumeric', function () { holder = getholder($this); var $settings = holder.settingsclone; $settings.hasfocus = true; if ($settings.nbracket !== null) { var checkval = $this.val(); $this.val(negativebracket(checkval, $settings)); } holder.inval = $this.val(); var onempty = checkempty(holder.inval, $settings, true); if (onempty !== null && onempty !== '') { $this.val(onempty); } }); $this.on('focusout.autonumeric', function () { holder = getholder($this); var $settings = holder.settingsclone, value = $this.val(), origvalue = value; $settings.hasfocus = false; var strip_zero = ''; /** added to control leading zero */ if ($settings.lzero === 'allow') { /** added to control leading zero */ $settings.allowleading = false; strip_zero = 'leading'; } if (value !== '') { value = autostrip(value, $settings, strip_zero); if (checkempty(value, $settings) === null && autocheck(value, $settings, $this[0])) { value = fixnumber(value, $settings.adec, $settings.aneg); value = autoround(value, $settings); value = presentnumber(value, $settings.adec, $settings.aneg); } else { value = ''; } } var groupedvalue = checkempty(value, $settings, false); if (groupedvalue === null) { groupedvalue = autogroup(value, $settings); } if (groupedvalue !== holder.inval || groupedvalue !== origvalue) { $this.change(); $this.val(groupedvalue); delete holder.inval; } }); } }); }, /** * method to remove settings and stop autonumeric() - does not remove the formatting * $(someselector).autonumeric('destroy'); // destroy autonumeric * no parameters accepted */ destroy: function () { return $(this).each(function () { var $this = $(this); $this.off('.autonumeric'); $this.removedata('autonumeric'); }); }, /** * method to update settings - can be call as many times * $(someselector).autonumeric('update', {options}); // updates the settings * options passes as a parameter example '{asep: '.', adec: ',', asign: '€ '} */ update: function (options) { return $(this).each(function () { var $this = autoget($(this)), settings = $this.data('autonumeric'); if (typeof settings !== 'object') { $.error("you must initialize autonumeric('init', {options}) prior to calling the 'update' method"); } var strip = $this.autonumeric('get'); settings = $.extend(settings, options); getholder($this, settings, true); if (settings.adec === settings.asep) { $.error("autonumeric will not function properly when the decimal character adec: '" + settings.adec + "' and thousand separator asep: '" + settings.asep + "' are the same character"); } $this.data('autonumeric', settings); if ($this.val() !== '' || $this.text() !== '') { return $this.autonumeric('set', strip); } return; }); }, /** * method to format value sent as a parameter "" * $(someselector).autonumeric('set', 'value'}); // formats the value being passed * value passed as a string - can be a integer '1234' or double '1234.56789' * must contain only numbers and one decimal (period) character */ set: function (valuein) { if (valuein === null) { return; } return $(this).each(function () { var $this = autoget($(this)), settings = $this.data('autonumeric'), value = valuein.tostring(), testvalue = valuein.tostring(), $input = $this.is('input[type=text], input[type=hidden], input[type=tel], input:not([type])'); if (typeof settings !== 'object') { $.error("you must initialize autonumeric('init', {options}) prior to calling the 'set' method"); } /** routine to handle page re-load from back button */ if (testvalue !== $this.attr('value') && $this.prop('tagname').tolowercase() === 'input' && settings.runonce === false) { value = (settings.nbracket !== null) ? negativebracket($this.val(), settings) : value; value = autostrip(value, settings); } /** allows locale decimal separator to be a comma */ if ((testvalue === $this.attr('value') || testvalue === $this.text()) && settings.runonce === false) { value = value.replace(',', '.'); } if (!$.isnumeric(+value)) { $.error("the value (" + value + ") being 'set' is not numeric and has caused a error to be thrown"); } value = checkvalue(value, settings); settings.setevent = true; value.tostring(); if (value !== '') { value = autoround(value, settings); } value = presentnumber(value, settings.adec, settings.aneg); if (!autocheck(value, settings)) { value = autoround('', settings); } value = autogroup(value, settings); if ($input) { return $this.val(value); } if ($.inarray($this.prop('tagname').tolowercase(), settings.taglist) !== -1) { return $this.text(value); } return false; }); }, /** * method to get the unformatted that accepts up to one parameter * $(someselector).autonumeric('get'); no parameters accepted * values returned as iso numeric string "1234.56" where the decimal character is a period * only the first element in the selector is returned */ get: function () { var $this = autoget($(this)), settings = $this.data('autonumeric'); if (typeof settings !== 'object') { $.error("you must initialize autonumeric('init', {options}) prior to calling the 'get' method"); } var getvalue = ''; /** determine the element type then use .eq(0) selector to grab the value of the first element in selector */ if ($this.is('input[type=text], input[type=hidden], input[type=tel], input:not([type])')) { /**added hidden type */ getvalue = $this.eq(0).val(); } else if ($.inarray($this.prop('tagname').tolowercase(), settings.taglist) !== -1) { getvalue = $this.eq(0).text(); } else { $.error("the <" + $this.prop('tagname').tolowercase() + "> is not supported by autonumeric()"); } if ((getvalue === '' && settings.wempty === 'empty') || (getvalue === settings.asign && (settings.wempty === 'sign' || settings.wempty === 'empty'))) { return ''; } if (getvalue !== '' && settings.nbracket !== null) { settings.removebrackets = true; getvalue = negativebracket(getvalue, settings); settings.removebrackets = false; } if (settings.runonce || settings.aform === false) { getvalue = autostrip(getvalue, settings); } getvalue = fixnumber(getvalue, settings.adec, settings.aneg); if (+getvalue === 0 && settings.lzero !== 'keep') { getvalue = '0'; } if (settings.lzero === 'keep') { return getvalue; } getvalue = checkvalue(getvalue, settings); return getvalue; /** returned numeric string */ }, /** * the 'getstring' method used jquerys .serialize() method that creates a text string in standard url-encoded notation * it then loops through the string and un-formats the inputs with autonumeric * $(someselector).autonumeric('getstring'); no parameter accepted * values returned as iso numeric string "1234.56" where the decimal character is a period */ getstring: function () { var isautonumeric = false, $this = autoget($(this)), str = $this.serialize(), parts = str.split('&'), formindex = $('form').index($this), inputindex = []; /*jslint unparam: true*/ $.each(parts, function (i, miniparts) { miniparts = parts[i].split('='); var $field = $('form:eq(' + formindex + ') input:eq(' + i + ')'), settings = $field.data('autonumeric'); if ($field.length > 1) { $field = $field.eq(inputindex[i]); } if (typeof settings === 'object' && settings !== '') { if (miniparts[1] !== null) { miniparts[1] = $field.autonumeric('get'); parts[i] = miniparts.join('='); isautonumeric = true; } } }); /*jslint unparam: false*/ if (!isautonumeric) { $.error("you must initialize autonumeric('init', {options}) prior to calling the 'getstring' method"); } return parts.join('&'); }, /** * the 'getstring' method used jquerys .serializearray() method that creates array or objects that can be encoded as a json string * it then loops through the string and un-formats the inputs with autonumeric * $(someselector).autonumeric('getarray'); no parameter accepted * values returned as iso numeric string "1234.56" where the decimal character is a period */ getarray: function () { var isautonumeric = false, $this = autoget($(this)), formfields = $this.serializearray(), formindex = $('form').index($this), inputindex = []; /*jslint unparam: true*/ $.each(formfields, function (i, field) { var $field = $('form:eq(' + formindex + ') input:eq(' + i + ')'), settings = $field.data('autonumeric'); if ($field.length > 1) { $field = $field.eq(inputindex[i]); } if (typeof settings === 'object') { if (field.value !== '') { field.value = $field.autonumeric('get').tostring(); } isautonumeric = true; } }); /*jslint unparam: false*/ if (!isautonumeric) { $.error("you must initialize autonumeric('init', {options}) prior to calling the 'getarray' method"); } return formfields; }, /** * the 'getsteetings returns the object with autonumeric settings for those who need to look under the hood * $(someselector).autonumeric('getsettings'); // no parameters accepted * $(someselector).autonumeric('getsettings').adec; // return the adec setting as a string - ant valid setting can be used */ getsettings: function () { var $this = autoget($(this)); return $this.eq(0).data('autonumeric'); } }; /** * autonumeric function */ $.fn.autonumeric = function (method) { if (methods[method]) { return methods[method].apply(this, array.prototype.slice.call(arguments, 1)); } if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } $.error('method "' + method + '" is not supported by autonumeric()'); }; /** * defaults are public - these can be overridden by the following: * html5 data attributes * options passed by the 'init' or 'update' methods * use jquery's $.extend method - great way to pass asp.net current culture settings */ $.fn.autonumeric.defaults = { /** allowed thousand separator characters * comma = ',' * period "full stop" = '.' * apostrophe is escaped = '\'' * space = ' ' * none = '' * note: do not use numeric characters */ asep: ',', /** digital grouping for the thousand separator used in format * dgroup: '2', results in 99,99,99,999 common in india for values less than 1 billion and greater than -1 billion * dgroup: '3', results in 999,999,999 default * dgroup: '4', results in 9999,9999,9999 used in some asian countries */ dgroup: '3', /** allowed decimal separator characters * period "full stop" = '.' * comma = ',' */ adec: '.', /** allow to declare alternative decimal separator which is automatically replaced by adec * developed for countries the use a comma ',' as the decimal character * and have keyboards\numeric pads that have a period 'full stop' as the decimal characters (spain is an example) */ altdec: null, /** allowed currency symbol * must be in quotes asign: '$', a space is allowed asign: '$ ' */ asign: '', /** placement of currency sign * for prefix psign: 'p', * for suffix psign: 's', */ psign: 'p', /** maximum possible value * value must be enclosed in quotes and use the period for the decimal point * value must be larger than vmin */ vmax: '9999999999999.99', /** minimum possible value * value must be enclosed in quotes and use the period for the decimal point * value must be smaller than vmax */ vmin: '-9999999999999.99', /** max number of decimal places = used to override decimal places set by the vmin & vmax values * value must be enclosed in quotes example mdec: '3', * this can also set the value via a call back function mdec: 'css:# */ mdec: null, /** method used for rounding * mround: 's', round-half-up symmetric (default) * mround: 'a', round-half-up asymmetric * mround: 's', round-half-down symmetric (lower case s) * mround: 'a', round-half-down asymmetric (lower case a) * mround: 'b', round-half-even "bankers rounding" * mround: 'u', round up "round-away-from-zero" * mround: 'd', round down "round-toward-zero" - same as truncate * mround: 'c', round to ceiling "toward positive infinity" * mround: 'f', round to floor "toward negative infinity" */ mround: 's', /** controls decimal padding * apad: true - always pad decimals with zeros * apad: false - does not pad with zeros. * apad: `some number` - pad decimals with zero to number different from mdec * thanks to jonas johansson for the suggestion */ apad: true, /** places brackets on negative value -$ 999.99 to (999.99) * visible only when the field does not have focus the left and right symbols should be enclosed in quotes and seperated by a comma * nbracket: null, nbracket: '(,)', nbracket: '[,]', nbracket: '<,>' or nbracket: '{,}' */ nbracket: null, /** displayed on empty string * wempty: 'empty', - input can be blank * wempty: 'zero', - displays zero * wempty: 'sign', - displays the currency sign */ wempty: 'empty', /** controls leading zero behavior * lzero: 'allow', - allows leading zeros to be entered. zeros will be truncated when entering additional digits. on focusout zeros will be deleted. * lzero: 'deny', - allows only one leading zero on values less than one * lzero: 'keep', - allows leading zeros to be entered. on fousout zeros will be retained. */ lzero: 'allow', /** determine if the default value will be formatted on page ready. * true = automatically formats the default value on page ready * false = will not format the default value */ aform: true }; }(jquery));