Tag Archives: Javascript

USD Currency Formatter to End All USD Currency Formatters

01 May 2012

I have created what I believe to be the world’s premier currency formatting function, yes, the last USD Currency Formatter you will ever need. Period.

Today I needed to format strings like ‘1234567’ and ‘1234567.8’ and even ‘1234567.89’ as USD complete with commas and two-digit cents. A quick googling returned a few stackoverflow posts but people are posting up stuff like .NET and whatever. Fine, I’ll do it myself…. Anyway, if you’re searching for the same thing your search ends now. The seas have parted and all you need to do is skip gayly across the sea floor (don’t trip on any mollusks) towards the nirvana that is presented below.

Did you catch the sarcasm? Good. There are lots of ways to approach this. I think mine is pretty simple and simple more often than not works the best. Here it is:

...
function formatAsDollarAmount(num){
   var str = num.toString();
   var strWithCommas = '';
   var finalString = '';
   var dollarsAndCents = str.indexOf('.') != -1 ? str.split('.') : [str,'00'];
   if (dollarsAndCents[0].length > 3){
      var skip = dollarsAndCents[0].length % 3;
      var strForCommas = dollarsAndCents[0].substr(skip,dollarsAndCents[0].length);
      var groupingRegEx = (/(\d){3}/g);
      var groups = strForCommas.match(groupingRegEx);
      for (var i=0;i 0 ? ',' : '') + strWithCommas + '.' + (dollarsAndCents[1].length != 2 ? dollarsAndCents[1] + '0' : dollarsAndCents[1]);
   return finalString;
}
...

Now the purists among you might proclaim “It doesn’t do any rounding!” To which I reply “Do it yourself you maggot! Or do not for I don’t care! Surely what little grey matter you possess would not be taxed too harshly by such a trivial addition!”

But, yes, it doesn’t. How can I claim then that this thing is so grand? Well, the problem is adding the commas and the cents, isn’t it? This does so in a very neat and tidy manner, does it not? Yes, I thought you would agree. So, go ahead and feed it your rounded numbers and then…. begone with you!

Eh? Are you still about? (Sigh), ok then….. I now present to you with much fanfare the new **improved** USD Currency Formatter To End The Previously Proclaimed USD Currency Formatter-ending Formatter™!

This now contains that which you’ve been groveling for: comma insertion, 2-digit cents and rounding to the nearest hundredth. Take it and prosper.

...
function formatAsDollarAmount(num){
   num = Math.round(num * 100) / 100; // this is the part your brain was having problems with
   var str = num.toString();
   var strWithCommas = '';
   var finalString = '';
   var dollarsAndCents = str.indexOf('.') != -1 ? str.split('.') : [str,'00'];
   if (dollarsAndCents[0].length > 3){
      var skip = dollarsAndCents[0].length % 3;
      var strForCommas = dollarsAndCents[0].substr(skip,dollarsAndCents[0].length);
      var groupingRegEx = (/(\d){3}/g);
      var groups = strForCommas.match(groupingRegEx);
      for (var i=0;i 0 ? ',' : '') + strWithCommas + '.' + (dollarsAndCents[1].length != 2 ? dollarsAndCents[1] + '0' : dollarsAndCents[1]);
   return finalString;
}
...

Meh.

Formatting Credit Card Numbers – Dealing with Webkit Text Input Oddity

27 Apr 2012

Another day, another script added to my library…. I had a credit card text field in one of my Sencha Touch projects that needed to be “dash separated” while the person is typing. Below is my solution to this particular minor challenge. Please remove any other validation in your project as this already features:

  • Removal of all undesired alpha characters and symbols
  • Limited to a maximum of 16 numbers
  • Plays nice when you backspace from the end

Note that you shouldn’t try to correct a number in the middle of the string – if you delete a number the entire string gets re-written for the current string of numbers. Also, the first thing that may come to mind is the HTML 5 “pattern” attribute which accepts a regular expression with which to validate the text field value, however, that would validate as you type, not format the string in the desired manner while the typing is happening. Sencha has its own way of validating text fields which is essentially the same thing but the issue is that it also doesn’t do on-the-fly string formatting.

So here is the function itself, which works perfectly in a normal web-browser (yes, a caveat for android webkit follows):

...
    function do_ccFormat(str){
	var nums = str.substr(0,19).replace(/[^\d]/gi,'');
	var r = nums.match(/(\d){4}/g);
	if (r){	
	    var i=0, nStr = '';
	    for (;i

However, in Android 2.3.6's webkit there is some odd behavior that is a pattern that I'm sure others somewhere have come up against. So the pattern is this: first assume that you are doing some string manipulation on the keyup event, waiting for some criteria to be met at every key stroke before manipulating the string. In the case of my CC formatter its the 5th character that triggers the string re-write. So lets say you type 5 characters such as "12345". When "5" is typed the entire string is replaced with a new one that contains a new character/delimiter - like so: "1234-5". The very next character that you type will not go at the end where the cursor is but before the last character that was entered. So if we enter "6" the string ends up like this: "1234-65". Further, if you hit the backspace key you backup not from the cursor position (which is at the end of the string) but from where the last character was erroneously entered! Weird stuff! This gets worse with every new delimiter that is added to the string.

Upon inspection and some thought, I realize that replacing the value with a formatted value as you type is something that the Android 2.x webkit can't handle when it has to then figure out where the cursor should go. It seems that programatically setting the value does not update the cursor position for the field - it stays where it is. If you pay close attention when all this happens you'll notice that the cursor will quickly jump around.

I then hit upon the idea of blurring the field (thinking to therefore dump whatever erroneous machinations may be at play) and then quickly give focus back to it knowing that the act of giving focus to a field sets the cursor to the end. This works as long as you space out the blur/focus methods with a setTimeout. I spaced them out by 100 milliseconds hoping that the soft keyboard wouldn't flash. This appeared to work well Android 2.3.6 (a Samsung Galaxy S2). The keyboard would just sit there apparently none-the-wiser.

While this did seem to work it turned out to be unpredictable. In Android 2.2 the keyboard would go away and not reappear, sometimes that would happen in 2.3.6, and it does go away in Android 3.

Its unfortunate that this doesn't work in pre-ICS Android - it is what it is. Below is how I implemented it in Sencha Touch 1 - as you can see I apply it on keyup for iOS and ICS for as-you-type formatting. For pre-ICS Android I limit the keyed cc number length to a max of 16 characters and apply CC formater when the CC field is blurred (a length of 19 is needed otherwise since we are adding 3 additional characters in the form of the "-" delimiter).

I should end by saying that this obviously does not include a MOD10 check. I'll toss in my MOD10 checker at a later date as a separate article - it will include the ability to check not only entire cc numbers - which all of the readily available MOD10 checkers do - but it will also allow you to check a CC number for its **type** based on the first four digits. Until then, here's Credit Card String Formatter sample implementation:

...
items:[
  {
    xtype:'textfield',
    name:'cardNumber',
    inputType:'tel',
    maxLength:'19',//allow the 3 dashes needed to format the string to be included
    listeners:{
	keyup:function(o,e){
            if (Ext.is.iOS || (Ext.is.Android && Ext.is.AndroidVersion > 3)){
	        o.setValue(do_ccFormat(o.getValue()));
            } else { // assume old android
                o.setValue(o.setValue().substr(0,16));
            }
	},
        blur:function(o,e){
            if (Ext.is.Android && Ext.is.AndroidVersion < 3){
               o.setValue(o.setValue().substr(0,16));
               o.setValue(do_ccFormat(o.getValue()));
            }
        }
    }
  }
]
...

Ext.Picker Slots with A Single Value: showing a Decimal Point

08 Feb 2012

A case has come up where I need a picker with multiple slots that represent a number to two decimal places. A decimal point will be necessary to delineate between whole numbers and the decimals. To achieve this I will need to have a 5 slot Ext.Picker where the middle slot is a decimal point, as shown in the image below:

Sencha Picker with 5 slots, the center having a single value.

At first glance this might seem to be fairly easy. As slots are simple to define we then assume that the center slot just needs to be a single value, right? Actually, that’s not quite true. A few things make this not so clear-cut, among them is that a slot cannot be disabled. There *is* an option to set “draggable” to false but it doesn’t work (Sencha 1.1.1). As a result the slot will always be subject to being dragged by the user. Also an Ext.Picker with a slot consisting of only a single value will actualy throw errors if the slot is dragged up or down – trial and error reveals that a slot must consist of at least two values.

So then, we will create our center slot with two values. Also, I think its pretty tedious to manualy create all of the values for the other slots, so I wrote a function that builds an array of configuration objects that I’ll use to populate the slot data. Here’s what we have at this point:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	]
});

As you can see the first two slots are a range of numbers, 0 through 9. The center slot is the decimal and an empty string, and then the last two slots like the first pair are also setup to display 0 thru 9.

When the picker is shown via the show() method it appears the way we want - the decimal point is the first selection. Recall though that we have two values for the center slot, and that the user can play with the slot as they wish. If the decimal is dragged down Sencha handily animates it back into position. However if the decimal is dragged up - we get the empty-string as the selection for the slot. Note that we actual don't care what the selection of the center slot is - I don't use it - but visually this looks wrong so it needs to be addressed. If we can't prevent the user from dragging the decimal then we should at least find a way to put it back where we want it.

They key here is that Slots fire "pick" events when they are changed. So that's where we'll start; by adding a listener to the picker. Next, we need to set its value and there is a method that does just that - setSelectedNode(). Lets create the listener:

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			pickerS.setSelectedNode(0,null);
		}
	}
	...

This is great except for one thing - all of the slots are being animated back to their initial values. We need a way of being more selectful in our approach as this code doesn't discriminate and attacks all of the slots. The answer here is to look at the value of the slot responsible for the event call and do a comparison against it to see if its the slot we're looking for.

The slot object is being passed as "pickerS", so lets grab that and compare the name given to the slot against what we know is the name of the decimal slot (refer to the picker code above - all of the slots have a "name"):

	...
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				pickerS.setSelectedNode(0,null);
			}
		}
	}
	...

On line 4 above you can see that I have an if statement comparing the passed slot's name to "dot" which is the name I gave to the decimal-having slot. This ensures that we're working with the correct slot when the pick event is fired.

Sweet, that should do it - however, of course there is one last detail... there always is! The above behaves unpredictably. Sometimes the decimal slides back to being selected, sometimes not. In these cases I always wonder if too much is happening at the same time. A simple setTimeout should make all the difference and it does.

        setTimeout(function(){pickerS.setSelectedNode(0,null);},20);

As you can see I've delayed the setSelectedNode() call by 20 milliseconds. Thats all that was needed - if the user drags the center slot to the second "empty" slot we return it to the desired decimal-having slot 20 ms later. Its the best that we can do. Here's the complete source:

function returnNumbers(){
	var num = [];
	for (i=0;i<10;i++){
		num[num.length] = {text:i,value:i};
	}
	return num;
}

var taxPicker = new Ext.Picker({
	slots: [
		{
			name : 'whole1',
			title: 'col 1',
			data : returnNumbers()
		},
		{
			name : 'whole2',
			title: 'col 2',
			data : returnNumbers()
		},
		{
			name : 'dot',
			title: 'decimal',
			draggable:false, // doesn't work!!
			data : [{text:'.',value:'.'},{text: '', value: ''}]
		},
		{
			name : 'decimal1',
			title: 'col 3',
			data :  returnNumbers()
		},
		{
			name : 'decimal2',
			title: 'col 4',
			data :  returnNumbers()
		}
	],
	listeners:{
		pick:function(pickerO,o,pickerS){
			if (pickerS.name == 'dot'){ // send back to the decimal...
				setTimeout(function(){pickerS.setSelectedNode(0,null);},20);
			}
		}
	}
});

Reset Ext.Panel Y Coord to 0

08 Feb 2012

Doubtless there is a way to do this with Ext, but this is my own method of returning a scrolling panel within Sencha Touch to the “top” of the view. I did spend some time reviewing the Sencha Touch docs but the only thing that came close to the desired result was the setPosition() method which only works on floating panels. I did spend some time playing with setting the floating property of the panel and then applying setPosition() but quickly ran into undesired results.

So, as always, time is short and a solution needs to be had quickly. A few minutes later I came up with this:

// setPositionToTop(cmp):void
// This function takes the scrolling area of a panel and sets its y position to 0;
// arguments: cmp:Object, an Ext.Panel
// returns: nothing
function setPositionToTop(cmp){
	var cmpId = cmp.getId();
	var sStr = document.getElementById(cmpId).firstChild.firstChild.getAttribute('style');
	if (sStr && sStr.indexOf('translate3d') != -1){
		var temp = sStr.split(',');
		temp[1] = ' 0px';
		var newStr = temp.join(',');
		//console.log('before: ' + sStr + ', after: ' + newStr);
	document.getElementById(cmpId).firstChild.firstChild.setAttribute('style',newStr)
	cmp.doComponentLayout();
	}
}

The above is based on the following source that Sencha Touch generates:

...

It should be plain that the top-most div is the component, and that I’m traversing the DOM to the third DIV and manipulating the style attribute. In this example the webkit-transform is responsible for the position of the content div and so I’m setting the y value back to 0px thus placing it where I want – back to the top.

The interesting thing here is that without calling doComponentLayout() on the component the space that the scrolling content area occupied before changing its position is still present resulting in a scrolling area larger than desired. In a worse-case scenario you can actually scroll the content area right off the screen – never to get it back. doComponentLayout() fixes this. Simply comment that line out to see this for yourself – scroll your panel to its most extreme y position, nav away, nav back, scroll your panel up as far as you can go…. gone.

To use this function I add it to a beforecardswitch listener for the desired panel and I add an if statement to check if the panel has been rendered yet, which it won’t unless you’ve visited it previously:

beforecardswitch:function(e,o){
	if (transactionsCardDeck.getActiveItem()){
		setPositionToTop(transactionsCardDeck.getActiveItem());
        }
}

The result is that the panel’s y coordinate is reset before you return to it.

Logo

All content © 2012-2017.