1/* 2 * jQuery Calculation Plug-in 3 * 4 * Copyright (c) 2007 Dan G. Switzer, II 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * Revision: 11 11 * Version: 0.4.07 12 * 13 * Revision History 14 * v0.4.07 15 * - Added trim to parseNumber to fix issue with whitespace in elements 16 * 17 * v0.4.06 18 * - Added support for calc() "format" callback so that if return value 19 * is null, then value is not updated 20 * - Added jQuery.isFunction() check for calc() callbacks 21 * 22 * v0.4.05 23 * - Added support to the sum() & calc() method for automatically fixing precision 24 * issues (will detect the max decimal spot in the number and fix to that 25 * depth) 26 * 27 * v0.4.04 28 * - Fixed bug #5420 by adding the defaults.cleanseNumber handler; you can 29 * override this function to handle stripping number of extra digits 30 * 31 * v0.4.02 32 * - Fixed bug where bind parameter was not being detecting if you specified 33 * a string in method like sum(), avg(), etc. 34 * 35 * v0.4a 36 * - Fixed bug in aggregate functions so that a string is passed to jQuery's 37 * text() method (since numeric zero is interpetted as false) 38 * 39 * v0.4
40 * - Added support for -$.99 values 41 * - Fixed regex so that decimal values without leading zeros are correctly 42 * parsed 43 * - Removed defaults.comma setting 44 * - Changed secondary regex that cleans additional formatting from parsed 45 * number 46 * 47 * v0.3 48 * - Refactored the aggregate methods (since they all use the same core logic) 49 * to use the $.extend() method 50 * - Added support for negative numbers in the regex) 51 * - Added min/max aggregate methods 52 * - Added defaults.onParseError and defaults.onParseClear methods to add logic for 53 * parsing errors 54 * 55 * v0.2 56 * - Fixed bug in sMethod in calc() (was using getValue, should have been setValue) 57 * - Added arguments for sum() to allow auto-binding with callbacks 58 * - Added arguments for avg() to allow auto-binding with callbacks 59 * 60 * v0.1a 61 * - Added semi-colons after object declaration (for min protection) 62 * 63 * v0.1 64 * - First public release 65 * 66*/ 67(function($){ 68 69 // set the defaults 70 var defaults = { 71 // regular expression used to detect numbers, if you want to force the field to contain 72 // numbers, you can add a ^ to the beginning or $ to the end of the regex to force the 73 // the regex to match the entire string: /^(-|-\$)?(\d+(,\d{3})*(\.\d{1,})?|\.\d{1,})$/g 74 reNumbers: /(-|-\$)?(\d+(,\d{3})*(\.\d{1,})?|\.\d{1,})/g 75 // this function is used in the parseNumber() to cleanse up any found numbers 76 // the function is intended to remove extra information found in a number such 77 // as extra commas and dollar signs. override this function to strip European values 78 , cleanseNumber: function (v){ 79 // cleanse the number one more time to remove extra data (like commas and dollar signs) 80 // use this for European numbers: v.replace(/[^0-9,\-]/g, "").replace(/,/g, ".") 81 return v.replace(/[^0-9.\-]/g, ""); 82 } 83 // should the Field plug-in be used for getting values of :input elements? 84 , useFieldPlugin: (!!$.fn.getValue) 85 // a callback function to run when an parsing error occurs 86 , onParseError: null 87 // a callback function to run once a parsing error has cleared 88 , onParseClear: null 89 }; 90 91 // set default options 92 $.Calculation = { 93 version: "0.4.07", 94 setDefaults: function(options){ 95 $.extend(defaults, options); 96 } 97 }; 98 99 100 /* 101 * jQuery.fn.parseNumber() 102 * 103 * returns Array - detects the DOM element and returns it's value. input 104 * elements return the field value, other DOM objects 105 * return their text node 106 * 107 * NOTE: Breaks the jQuery chain, since it returns a Number. 108 * 109 * Examples: 110 * $("input[name^='price']").parseNumber(); 111 * > This would return an array of potential number for every match in the selector 112 * 113 */ 114 // the parseNumber() method -- break the chain 115 $.fn.parseNumber = function(options){ 116 var aValues = []; 117 options = $.extend(options, defaults); 118 119 this.each( 120 function (){ 121 var 122 // get a pointer to the current element 123 $el = $(this), 124 // determine what method to get it's value 125 sMethod = ($el.is(":input") ? (defaults.useFieldPlugin ? "getValue" : "val") : "text"), 126 // parse the string and get the first number we find 127 v = $.trim($el[sMethod]()).match(defaults.reNumbers, ""); 128 129 // if the value is null, use 0 130 if( v == null ){ 131 v = 0; // update value 132 // if there's a error callback, execute it 133 if( jQuery.isFunction(options.onParseError) ) options.onParseError.apply($el, [sMethod]); 134 $.data($el[0], "calcParseError", true); 135 // otherwise we take the number we found and remove any commas 136 } else { 137 // clense the number one more time to remove extra data (like commas and dollar signs) 138 v = options.cleanseNumber.apply(this, [v[0]]); 139 // if there's a clear callback, execute it 140 if( $.data($el[0], "calcParseError") && jQuery.isFunction(options.onParseClear) ){ 141 options.onParseClear.apply($el, [sMethod]); 142 // clear the error flag 143 $.data($el[0], "calcParseError", false); 144 } 145 } 146 aValues.push(parseFloat(v, 10)); 147 } 148 ); 149 150 // return an array of values 151 return aValues; 152 }; 153 154 /* 155 * jQuery.fn.calc() 156 * 157 * returns Number - performance a calculation and updates the field 158 * 159 * Examples: 160 * $("input[name='price']").calc(); 161 * > This would return the sum of all the fields named price 162 * 163 */ 164 // the calc() method 165 $.fn.calc = function(expr, vars, cbFormat, cbDone){ 166 var 167 // create a pointer to the jQuery object 168 $this = this 169 // the value determine from the expression 170 , exprValue = "" 171 // track the precision to use 172 , precision = 0 173 // a pointer to the current jQuery element 174 , $el 175 // store an altered copy of the vars 176 , parsedVars = {} 177 // temp variable 178 , tmp 179 // the current method to use for updating the value 180 , sMethod 181 // a hash to store the local variables 182 , _ 183 // track whether an error occured in the calculation 184 , bIsError = false; 185 186 // look for any jQuery objects and parse the results into numbers 187 for( var k in vars ){ 188 // replace the keys in the expression 189 expr = expr.replace( (new RegExp("(" + k + ")", "g")), "_.$1"); 190 if( !!vars[k] && !!vars[k].jquery ){ 191 parsedVars[k] = vars[k].parseNumber(); 192 } else { 193 parsedVars[k] = vars[k]; 194 } 195 } 196 197 this.each( 198 function (i, el){ 199 var p, len; 200 // get a pointer to the current element 201 $el = $(this); 202 // determine what method to get it's value 203 sMethod = ($el.is(":input") ? (defaults.useFieldPlugin ? "setValue" : "val") : "text"); 204 205 // initialize the hash vars 206 _ = {}; 207 for( var k in parsedVars ){ 208 if( typeof parsedVars[k] == "number" ){ 209 _[k] = parsedVars[k]; 210 } else if( typeof parsedVars[k] == "string" ){ 211 _[k] = parseFloat(parsedVars[k], 10); 212 } else if( !!parsedVars[k] && (parsedVars[k] instanceof Array) ) { 213 // if the length of the array is the same as number of objects in the jQuery 214 // object we're attaching to, use the matching array value, otherwise use the 215 // value from the first array item 216 tmp = (parsedVars[k].length == $this.length) ? i : 0; 217 _[k] = parsedVars[k][tmp]; 218 } 219 220 // if we're not a number, make it 0 221 if( isNaN(_[k]) ) _[k] = 0; 222 223 // check for decimals and check the precision 224 p = _[k].toString().match(/\.\d+$/gi); 225 len = (p) ? p[0].length-1 : 0; 226 227 // track the highest level of precision 228 if( len > precision ) precision = len; 229 } 230 231 232 // try the calculation
233 try { 234 exprValue = eval( expr ); 235 236 // fix any the precision errors 237 if( precision ) exprValue = Number(exprValue.toFixed(Math.max(precision, 4))); 238 239 // if there's a format callback, call it now 240 if( jQuery.isFunction(cbFormat) ){ 241 // get return value 242 var tmp = cbFormat.apply(this, [exprValue]) 243 // if we have a returned value (it's null null) use it 244 if( !!tmp ) exprValue = tmp; 245 } 246 247 // if there's an error, capture the error output 248 } catch(e){ 249 exprValue = e; 250 bIsError = true; 251 } 252 253 // update the value 254 $el[sMethod](exprValue.toString()); 255 } 256 ); 257 258 // if there's a format callback, call it now 259 if( jQuery.isFunction(cbDone) ) cbDone.apply(this, [this]); 260 261 return this; 262 }; 263 264 /* 265 * Define all the core aggregate functions. All of the following methods 266 * have the same functionality, but they perform different aggregate 267 * functions. 268 * 269 * If this methods are called without any arguments, they will simple 270 * perform the specified aggregate function and return the value. This 271 * will break the jQuery chain. 272 * 273 * However, if you invoke the method with any arguments then a jQuery 274 * object is returned, which leaves the chain intact. 275 * 276 * 277 * jQuery.fn.sum() 278 * returns Number - the sum of all fields 279 * 280 * jQuery.fn.avg() 281 * returns Number - the avg of all fields 282 * 283 * jQuery.fn.min() 284 * returns Number - the minimum value in the field 285 * 286 * jQuery.fn.max() 287 * returns Number - the maximum value in the field 288 * 289 * Examples: 290 * $("input[name='price']").sum(); 291 * > This would return the sum of all the fields named price 292 * 293 * $("input[name='price1'], input[name='price2'], input[name='price3']").sum(); 294 * > This would return the sum of all the fields named price1, price2 or price3 295 * 296 * $("input[name^=sum]").sum("keyup", "#totalSum"); 297 * > This would update the element with the id "totalSum" with the sum of all the 298 * > fields whose name started with "sum" anytime the keyup event is triggered on 299 * > those field. 300 * 301 * NOTE: The syntax above is valid for any of the aggregate functions 302 * 303 */ 304 $.each(["sum", "avg", "min", "max"], function (i, method){ 305 $.fn[method] = function (bind, selector){ 306 // if no arguments, then return the result of the aggregate function 307 if( arguments.length == 0 ) 308 return math[method](this.parseNumber()); 309 310 // if the selector is an options object, get the options 311 var bSelOpt = selector && (selector.constructor == Object) && !(selector instanceof jQuery); 312 313 // configure the options for this method 314 var opt = bind && bind.constructor == Object ? bind : { 315 bind: bind || "keyup" 316 , selector: (!bSelOpt) ? selector : null 317 , oncalc: null 318 }; 319 320 // if the selector is an options object, extend the options 321 if( bSelOpt ) opt = jQuery.extend(opt, selector); 322 323 // if the selector exists, make sure it's a jQuery object 324 if( !!opt.selector ) opt.selector = $(opt.selector); 325 326 var self = this 327 , sMethod 328 , doCalc = function (){ 329 // preform the aggregate function 330 var value = math[method](self.parseNumber(opt)); 331 // check to make sure we have a selector 332 if( !!opt.selector ){ 333 // determine how to set the value for the selector 334 sMethod = (opt.selector.is(":input") ? (defaults.useFieldPlugin ? "setValue" : "val") : "text"); 335 // update the value 336 opt.selector[sMethod](value.toString()); 337 } 338 // if there's a callback, run it now 339 if( jQuery.isFunction(opt.oncalc) ) opt.oncalc.apply(self, [value, opt]); 340 }; 341 342 // perform the aggregate function now, to ensure init values are updated 343 doCalc(); 344 345 // bind the doCalc function to run each time a key is pressed 346 return self.bind(opt.bind, doCalc); 347 } 348 }); 349 350 /* 351 * Mathmatical functions 352 */ 353 var math = { 354 // sum an array 355 sum: function (a){ 356 var total = 0, precision = 0; 357 358 // loop through the value and total them 359 $.each(a, function (i, v){ 360 // check for decimals and check the precision 361 var p = v.toString().match(/\.\d+$/gi), len = (p) ? p[0].length-1 : 0; 362 // track the highest level of precision 363 if( len > precision ) precision = len; 364 // we add 0 to the value to ensure we get a numberic value 365 total += v; 366 }); 367 368 // fix any the precision errors 369 if( precision ) total = Number(total.toFixed(precision)); 370 371 // return the values as a comma-delimited string 372 return total; 373 }, 374 // average an array 375 avg: function (a){ 376 // return the values as a comma-delimited string 377 return math.sum(a)/a.length; 378 }, 379 // lowest number in array 380 min: function (a){ 381 return Math.min.apply(Math, a); 382 }, 383 // highest number in array 384 max: function (a){ 385 return Math.max.apply(Math, a); 386 } 387 }; 388 389 390})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.