2
* jQuery number plug-in 2.1.0
3
* Copyright 2012, Digital Fusion
4
* Licensed under the MIT license.
5
* http://opensource.teamdf.com/license/
7
* A jQuery plugin which implements a permutation of phpjs.org's number_format to provide
8
* simple number formatting, insertion, and as-you-type masking of a number.
11
* @docs http://www.teamdf.com/web/jquery-number-format-redux/196/
16
* Method for selecting a range of characters in an input/textarea.
18
* @param int rangeStart : Where we want the selection to start.
19
* @param int rangeEnd : Where we want the selection to end.
23
function setSelectionRange( rangeStart, rangeEnd )
25
// Check which way we need to define the text range.
26
if( this.createTextRange )
28
var range = this.createTextRange();
29
range.collapse( true );
30
range.moveStart( 'character', rangeStart );
31
range.moveEnd( 'character', rangeEnd-rangeStart );
35
// Alternate setSelectionRange method for supporting browsers.
36
else if( this.setSelectionRange )
39
this.setSelectionRange( rangeStart, rangeEnd );
44
* Get the selection position for the given part.
46
* @param string part : Options, 'Start' or 'End'. The selection position to get.
48
* @return int : The index position of the selection part.
50
function getSelection( part )
52
var pos = this.value.length;
54
// Work out the selection part.
55
part = ( part.toLowerCase() == 'start' ? 'Start' : 'End' );
57
if( document.selection ){
58
// The current selection
59
var range = document.selection.createRange(), stored_range, selectionStart, selectionEnd;
60
// We'll use this as a 'dummy'
61
stored_range = range.duplicate();
63
//stored_range.moveToElementText( this );
64
stored_range.expand('textedit');
65
// Now move 'dummy' end point to end point of original range
66
stored_range.setEndPoint( 'EndToEnd', range );
67
// Now we can calculate start and end points
68
selectionStart = stored_range.text.length - range.text.length;
69
selectionEnd = selectionStart + range.text.length;
70
return part == 'Start' ? selectionStart : selectionEnd;
73
else if(typeof(this['selection'+part])!="undefined")
75
pos = this['selection'+part];
81
* Substitutions for keydown keycodes.
82
* Allows conversion from e.which to ascii characters.
96
187 : 61, //IE Key codes
97
186 : 59, //IE Key codes
98
189 : 45, //IE Key codes
99
110 : 46 //IE Key codes
127
* jQuery number formatter plugin. This will allow you to format numbers on an element.
129
* @params proxied for format_number method.
131
* @return : The jQuery collection the method was called with.
133
$.fn.number = function( number, decimals, dec_point, thousands_sep ){
135
// Enter the default thousands separator, and the decimal placeholder.
136
thousands_sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
137
dec_point = (typeof dec_point === 'undefined') ? '.' : dec_point;
138
decimals = (typeof decimals === 'undefined' ) ? 0 : decimals;
140
// Work out the unicode character for the decimal placeholder.
141
var u_dec = ('\\u'+('0000'+(dec_point.charCodeAt(0).toString(16))).slice(-4)),
142
regex_dec_num = new RegExp('[^'+u_dec+'0-9]','g'),
143
regex_dec = new RegExp(u_dec,'g');
145
// If we've specified to take the number from the target element,
146
// we loop over the collection, and get the number.
147
if( number === true )
149
// If this element is a number, then we add a keyup
150
if( this.is('input:text') )
152
// Return the jquery collection.
156
* Handles keyup events, re-formatting numbers.
158
* @param object e : the keyup event object.s
162
'keydown.format' : function(e){
164
// Define variables used in the code below.
166
data = $this.data('numFormat'),
167
code = (e.keyCode ? e.keyCode : e.which),
168
chara = '', //unescape(e.originalEvent.keyIdentifier.replace('U+','%u')),
169
start = getSelection.apply(this,['start']),
170
end = getSelection.apply(this,['end']),
174
// Webkit (Chrome & Safari) on windows screws up the keyIdentifier detection
175
// for numpad characters. I've disabled this for now, because while keyCode munging
176
// below is hackish and ugly, it actually works cross browser & platform.
178
// if( typeof e.originalEvent.keyIdentifier !== 'undefined' )
180
// chara = unescape(e.originalEvent.keyIdentifier.replace('U+','%u'));
184
if (_keydown.codes.hasOwnProperty(code)) {
185
code = _keydown.codes[code];
187
if (!e.shiftKey && (code >= 65 && code <= 90)){
189
} else if (!e.shiftKey && (code >= 69 && code <= 105)){
191
} else if (e.shiftKey && _keydown.shifts.hasOwnProperty(code)){
192
//get shifted keyCode value
193
chara = _keydown.shifts[code];
196
if( chara == '' ) chara = String.fromCharCode(code);
199
// Stop executing if the user didn't type a number key, a decimal character, or backspace.
200
if( code !== 8 && chara != dec_point && !chara.match(/[0-9]/) )
202
// We need the original keycode now...
203
var key = (e.keyCode ? e.keyCode : e.which);
204
if( // Allow control keys to go through... (delete, etc)
205
key == 46 || key == 8 || key == 9 || key == 27 || key == 13 ||
206
// Allow: Ctrl+A, Ctrl+R
207
( (key == 65 || key == 82 ) && ( e.ctrlKey || e.metaKey ) === true ) ||
208
// Allow: home, end, left, right
209
( (key >= 35 && key <= 39) )
213
// But prevent all other keys.
218
//console.log('Continuing on: ', code, chara);
220
// The whole lot has been selected, or if the field is empty, and the character
221
if( ( start == 0 && end == this.value.length || $this.val() == 0 ) && !e.metaKey && !e.ctrlKey && !e.altKey && chara.length === 1 && chara != 0 )
223
// Blank out the field, but only if the data object has already been instanciated.
227
// Reset the cursor position.
228
data.init = (decimals>0?-1:0);
229
data.c = (decimals>0?-(decimals+1):0);
230
setSelectionRange.apply(this, [0,0]);
233
// Otherwise, we need to reset the caret position
234
// based on the users selection.
237
data.c = end-this.value.length;
240
// If the start position is before the decimal point,
241
// and the user has typed a decimal point, we need to move the caret
242
// past the decimal place.
243
if( decimals > 0 && chara == dec_point && start == this.value.length-decimals-1 )
246
data.init = Math.max(0,data.init);
249
// Set the selection position.
250
setPos = this.value.length+data.c;
253
// If the user is just typing the decimal place,
254
// we simply ignore it.
255
else if( chara == dec_point )
257
data.init = Math.max(0,data.init);
261
// If hitting the delete key, and the cursor is behind a decimal place,
262
// we simply move the cursor to the other side of the decimal place.
263
else if( decimals > 0 && code == 8 && start == this.value.length-decimals )
268
// Set the selection position.
269
setPos = this.value.length+data.c;
272
// If hitting the delete key, and the cursor is to the right of the decimal
273
// (but not directly to the right) we replace the character preceeding the
275
else if( decimals > 0 && code == 8 && start > this.value.length-decimals )
277
if( this.value === '' ) return;
279
// If the character preceeding is not already a 0,
280
// replace it with one.
281
if( this.value.slice(start-1, start) != '0' )
283
val = this.value.slice(0, start-1) + '0' + this.value.slice(start);
284
$this.val(val.replace(regex_dec_num,'').replace(regex_dec,dec_point));
290
// Set the selection position.
291
setPos = this.value.length+data.c;
294
// If the delete key was pressed, and the character immediately
295
// before the caret is a thousands_separator character, simply
297
else if( code == 8 && this.value.slice(start-1, start) == thousands_sep )
302
// Set the selection position.
303
setPos = this.value.length+data.c;
306
// If the caret is to the right of the decimal place, and the user is entering a
307
// number, remove the following character before putting in the new one.
311
this.value.length > decimals+1 &&
312
start > this.value.length-decimals-1 && isFinite(+chara) &&
313
!e.metaKey && !e.ctrlKey && !e.altKey && chara.length === 1
316
// If the character preceeding is not already a 0,
317
// replace it with one.
318
if( end === this.value.length )
320
val = this.value.slice(0, start-1);
324
val = this.value.slice(0, start)+this.value.slice(start+1);
327
// Reset the position.
332
// If we need to re-position the characters.
333
if( setPos !== false )
335
//console.log('Setpos keydown: ', setPos );
336
setSelectionRange.apply(this, [setPos, setPos]);
339
// Store the data on the element.
340
$this.data('numFormat', data);
345
* Handles keyup events, re-formatting numbers.
347
* @param object e : the keyup event object.s
351
'keyup.format' : function(e){
353
// Store these variables for use below.
355
data = $this.data('numFormat'),
356
code = (e.keyCode ? e.keyCode : e.which),
357
start = getSelection.apply(this,['start']),
360
// Stop executing if the user didn't type a number key, a decimal, or a comma.
361
if( this.value === '' || (code < 48 || code > 57) && (code < 96 || code > 105 ) && code !== 8 ) return;
363
// Re-format the textarea.
364
$this.val($this.val());
368
// If we haven't marked this item as 'initialised'
369
// then do so now. It means we should place the caret just
370
// before the decimal. This will never be un-initialised before
371
// the decimal character itself is entered.
374
start = this.value.length-decimals-( data.init < 0 ? 1 : 0 );
375
data.c = start-this.value.length;
378
$this.data('numFormat', data);
381
// Increase the cursor position if the caret is to the right
382
// of the decimal place, and the character pressed isn't the delete key.
383
else if( start > this.value.length-decimals && code != 8 )
387
// Store the data, now that it's changed.
388
$this.data('numFormat', data);
392
//console.log( 'Setting pos: ', start, decimals, this.value.length + data.c, this.value.length, data.c );
394
// Set the selection position.
395
setPos = this.value.length+data.c;
396
setSelectionRange.apply(this, [setPos, setPos]);
400
* Reformat when pasting into the field.
402
* @param object e : jQuery event object.
404
* @return false : prevent default action.
406
'paste.format' : function(e){
408
// Defint $this. It's used twice!.
410
original = e.originalEvent,
413
// Get the text content stream.
414
if (window.clipboardData && window.clipboardData.getData) { // IE
415
val = window.clipboardData.getData('Text');
416
} else if (original.clipboardData && original.clipboardData.getData) {
417
val = original.clipboardData.getData('text/plain');
420
// Do the reformat operation.
423
// Stop the actual content from being pasted.
430
// Loop each element (which isn't blank) and do the format.
433
var $this = $(this).data('numFormat',{
436
thousands_sep : thousands_sep,
437
dec_point : dec_point,
438
regex_dec_num : regex_dec_num,
439
regex_dec : regex_dec,
443
// Return if the element is empty.
444
if( this.value === '' ) return;
446
// Otherwise... format!!
447
$this.val($this.val());
452
// return the collection.
453
return this.each(function(){
454
var $this = $(this), num = +$this.text().replace(regex_dec_num,'').replace(regex_dec,'.');
455
$this.number( !isFinite(num) ? 0 : +num, decimals, dec_point, thousands_sep );
460
// Add this number to the element as text.
461
return this.text( $.number.apply(window,arguments) );
465
// Create .val() hooks to get and set formatted numbers in inputs.
468
// We check if any hooks already exist, and cache
469
// them in case we need to re-use them later on.
470
var origHookGet = null, origHookSet = null;
472
// Check if a text valHook already exists.
473
if( $.valHooks.text )
475
// Preserve the original valhook function
476
// we'll call this for values we're not
477
// explicitly handling.
478
origHookGet = $.valHooks.text.get;
479
origHookSet = $.valHooks.text.set;
483
// Define an object for the new valhook.
484
$.valHooks.text = {};
488
* Define the valHook to return normalised field data against an input
489
* which has been tagged by the number formatter.
491
* @param object el : The raw DOM element that we're getting the value from.
493
* @return mixed : Returns the value that was written to the element as a
494
* javascript number, or undefined to let jQuery handle it normally.
496
$.valHooks.text.get = function( el ){
498
// Get the element, and its data.
499
var $this = $(el), num,
500
data = $this.data('numFormat');
502
// Does this element have our data field?
505
// Check if the valhook function already existed
506
if( $.isFunction( origHookGet ) )
508
// There was, so go ahead and call it
509
return origHookGet(el);
513
// No previous function, return undefined to have jQuery
514
// take care of retrieving the value
520
// Remove formatting, and return as number.
521
if( el.value === '' ) return '';
523
// Convert to a number.
525
.replace( data.regex_dec_num, '' )
526
.replace( data.regex_dec, '.' ));
528
// If we've got a finite number, return it.
529
// Otherwise, simply return 0.
530
// Return as a string... thats what we're
531
// used to with .val()
532
return ''+( isFinite( num ) ? num : 0 );
537
* A valhook which formats a number when run against an input
538
* which has been tagged by the number formatter.
540
* @param object el : The raw DOM element (input element).
541
* @param float : The number to set into the value field.
543
* @return mixed : Returns the value that was written to the element,
544
* or undefined to let jQuery handle it normally.
546
$.valHooks.text.set = function( el, val )
548
// Get the element, and its data.
550
data = $this.data('numFormat');
552
// Does this element have our data field?
555
// Check if the valhook function already existed
556
if( $.isFunction( origHookSet ) )
558
// There was, so go ahead and call it
559
return origHookSet(el,val);
563
// No previous function, return undefined to have jQuery
564
// take care of retrieving the value
570
return el.value = $.number( val, data.decimals, data.dec_point, data.thousands_sep )
575
* The (modified) excellent number formatting method from PHPJS.org.
576
* http://phpjs.org/functions/number_format/
578
* @modified by Sam Sehnert (teamdf.com)
579
* - don't redefine dec_point, thousands_sep... just overwrite with defaults.
580
* - don't redefine decimals, just overwrite as numeric.
581
* - Generate regex for normalizing pre-formatted numbers.
583
* @param float number : The number you wish to format, or TRUE to use the text contents
584
* of the element as the number. Please note that this won't work for
585
* elements which have child nodes with text content.
586
* @param int decimals : The number of decimal places that should be displayed. Defaults to 0.
587
* @param string dec_point : The character to use as a decimal point. Defaults to '.'.
588
* @param string thousands_sep : The character to use as a thousands separator. Defaults to ','.
590
* @return string : The formatted number as a string.
592
$.number = function( number, decimals, dec_point, thousands_sep ){
594
// Set the default values here, instead so we can use them in the replace below.
595
thousands_sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
596
dec_point = (typeof dec_point === 'undefined') ? '.' : dec_point;
597
decimals = !isFinite(+decimals) ? 0 : Math.abs(decimals);
599
// Work out the unicode representation for the decimal place.
600
var u_dec = ('\\u'+('0000'+(dec_point.charCodeAt(0).toString(16))).slice(-4));
602
// Fix the number, so that it's an actual number.
603
number = (number + '')
604
.replace(new RegExp(u_dec,'g'),'.')
605
.replace(new RegExp('[^0-9+\-Ee.]','g'),'');
607
var n = !isFinite(+number) ? 0 : +number,
609
toFixedFix = function (n, decimals) {
610
var k = Math.pow(10, decimals);
611
return '' + Math.round(n * k) / k;
614
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
615
s = (decimals ? toFixedFix(n, decimals) : '' + Math.round(n)).split('.');
616
if (s[0].length > 3) {
617
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, thousands_sep);
619
if ((s[1] || '').length < decimals) {
621
s[1] += new Array(decimals - s[1].length + 1).join('0');
623
return s.join(dec_point);