Category Archives: JavaScript

Cache Busting Your Web Service Requests

29 Jun 2012

While working with web services you always need to keep in mind that web browsers cache files. if you web service request doesn’t change ever so slightly you will run into the issue of the web browser not pulling the new response but instead pulling the previously cached response. This is easily mitigated by adding a variable that itself is always unique – notice that i didn’t say “random” – random may work, sure, but “random” means that you run the risk of encountering that number again at some point. Yes, feel free to come up with a random 20 digit number and it may be for all intents and purposes appear to be “unique” but the risk of that number re-appearing is still there, however obscure it may be. As most web service calls are GET requests its as simple as adding a new name/value pair with a unique value to every request. The service ignores it and the browser takes note that the request is different than the previous request and so accepts the new response from the server.

Add the following somewhere within your code to get the unique value:

...
   var d = new Date();
   var unique = d.getTime();
...

That gives you a number similar to 1340979406482. That value is the number of milliseconds since January 1, 1970 so naturally it increments every millisecond. Ideally the getTime() method would accept a date as an argument and would then provide the milliseconds between Jan 1, 1970 and the specified date but since we don’t provide that value the current date is assumed. So, that number represents the number of milliseconds from that date in 1970 and the millisecond in time when the code was executed.

I have seen people blog this topic but include dividing the millisecond value by 1000 and rounding the result. Not a great idea. Dividing by 1000 and rounding the result shortens the length of the number by 3 digits. If you shorten it too much it won’t be useful. Already, by dividing by 1000 I’m not getting a number that updates every millisecond but instead a number that updates every second. I suppose it depends on your idea of “unique” and your specific application, but I’d rather have the guarantee of uniqueness than have something that is fuzzy like doing Math on the already guaranteed unique number… makes no sense.

As to applying this, here’s a jQuery example:

...
var d = new Date();
var unique = d.getTime();
var dataStr = 'name_value_pairs_here';
$.ajax({
  url:      	'/some_service_here/',
  data:      	dataStr + '&nocache=' + unique,
  contentType:	'application/x-www-form-urlencoded',
  dataType: 	'json',
  error:function(obj,status,xhr){
    // handle error here
  },
  success:function(obj,status,xhr){
    // handle response here
  }
});
...

And better yet – jQuery has such a thing built into it, simply set the cache attribute as in this example:

...
var dataStr = 'name_value_pairs_here';
$.ajax({
  url:      	'/some_service_here/',
  data:      	dataStr,
  cache:        false, // cache buster
  contentType:	'application/x-www-form-urlencoded',
  dataType: 	'json',
  error:function(obj,status,xhr){
    // handle error here
  },
  success:function(obj,status,xhr){
    // handle response here
  }
});
...

When using the “cache” attribute within the jQuery Ajax config object jQuery will append a new name/value pair to the GET request in the form of:

  1. _=[timestamp]

Where “[timestamp]” is a unique value.

The usefullness of using d.getTime() is still there outside of jQuery. For example any call to a file that you don’t want to be cached, such as web pages, JS files, CSS, etc.

Javascript/Chrome as a Dataset Manipulation Tool

29 May 2012

You might have caught on that I’m a front-end developer – while I *have* done some ASP, the occasional PHP, minor SQL and even the rather obscure MIVAScript (DBF’s with index files) I definitely don’t consider myself a back-end guy. So, recently I was given the task of updating an e-commerce site, a global change across all products where the range of colors for certain items have all changed. As I originally setup the site (its a CoreCommerce site) I am the most familiar within the organization as to how it works and what such a change entails. My process would be to export the Personalization Table (this describes product-specific options), modify as needed and then import/apply the updates. Simple enough, right?

Most people I imagine would import the csv into Access or import into a temporary database on a server, do some SQL, export a new CSV, import into the system and be done with it. Well, as I’ve said, thats not quite my bag.

Excel has some macro functionality and I’m sure that given enough time I might be able to pick up enough VB to do what I want, but, I really don’t care to learn any VB. After spending an hour or two in Excell messing around with Macros I decided to apply what I already know and work with/in everyday: Javascript.

First things first – I need to get the csv into the browser in such a way as to make it usable. That means turning it into a Javascript-friendly string. This would involve surrounding all of it in single quotes, adding a comma to the end of every line and removing all the newlines/carriage returns so as to have a single string instead of 11,408 individual rows (yes, that many rows, with 41 columns).

Easiest way to accomplish the above is in Notepad++. In a few minutes I had what I wanted. I did a search and replace on the single quote character, replacing it with the escaped version (\’) – I probably should have just replaced all single quotes with the entity equivalent but, oh well, either way works. Next, I did a search/replace on the newline character utilizing the “Extended” feature of Notepadd++’s Replace dialogue (a regular expression would have worked here as well) and replaced it with a comma so as to have each line ending with a comma. Finally, I removed all carriage returns with another Extended Search operation – the search looked for \r and replaced with nothing resulted with one single line. All that’s needed then is to assign the result to variable by placing “var data = ” before the string, surrounding the string with single quotes and tacking a semi-colon on the end. The finished string had a length of 1,939,099 characters.

Ok, now we’re ready to write some Javascript!

Continuing in Notepad++ I created a simple HTML document and “imported” the data string via a Script tag, which looked like this:


  



Lets load the page in Chrome and consult the console (F12):

Note the message – the file has been loaded. No need to worry about the mime type. Lets verify that its there – lets check for the length of the string. Recall that I declared the variable as “data”, so lets do the following: data.length:

As you can see the data variable is present by virtue of the fact that we could discover its length. Note that its a bit shorter than the length we found inside NotePad++ – this shouldn’t be a surprise as I escaped all single quotes and the escape character doesn’t apply towards the character account. In addition, I added a tiny bit of Javascript to the text which doesn’t count either. Lets prove this – back in NotePad++ I did a search for the backslash (\) character. The result was 6,046 hits. 1,933,039 from Chrome’s reporting of the string’s length + the 6,046 backslashes = 1,939,085. Still a bit short, lets add the number of characters caused by the presence of the bit of Javascript and we get the total number of characters as reported by Notepad++. So, Chrome isn’t under reporting the string length but rather is reporting the exact length of the actual string excluding the above factors.

Ok then, lets move on. Lets convert this thing to an array. We know that the comma is the value delimiter so lets use the split array method using the comma as the required argument:


  
  



As you can see my function is called “rip” so I’ll reload the page and fire it manually by typing “rip()”:

A few milliseconds later its done. Note the “undefined” – there’s nothing wrong here – the rip() function doesn’t return anything so the “undefined” as the result of my function call is Chrome’s way of telling me as much. Its not that *nothing* has happened, its just that nothing was returned. If you like you could add a return statement to the end of the function but its totaly unecesary as we know why “undefined” appears.

So anyway now that we’ve fired rip() lets see if it worked. Lets check the length of the new array that was assigned to the “dataStepOne” variable:

You can see that to check the array’s length I typed the following into the console: “dataStepOne.length” – the length of the array is 467,728.

At this point I know that the data had 41 columns. What I want to do now is to recreate each row in some way – I decide to create a multidimensional array where each index of the 1st dimension represents a row and where the array at that dimension reflects the values for the row. This next bit accomplishes that task:


  
  



Again, within Chrome's console I execute the rip() function. This time I check the length of the dataStepTwo array by typing "dataStepTwo.length" and then hitting the enter key - the length is 11,408 - which is the number of rows in the original spreadsheet. To verify that the resulting data construct is good I decide to take a look at a "random" row by typing dataStepTwo[200]. Chrome displays the array at index 200 for me. See the following image:

I cross reference the above array values against the 200th row in the spreadsheet and the values are identical. For kicks I also check the first and second rows of the array - the first row should contain all the column names, which it does, and the second row shouldn't have any columns names in it - a sanity to check to ensure that the column count is correct and that no row data is being erroneously placed in other rows. Yes, given the first test I already knew this to be the case but I wanted to look anyway. Everything is correct! Moving on....

So now that I have a representation of the data its time to start manipulating it. The way CoreCommerce's table is setup is a bit odd - you can see that one of the values has a pipe in it - this is a delimiter as there are actually two values in that field. I've no idea why they designed it this way - whatever, it is what it is. The thing to note here is that this reflects only a single color for the given product. There are 21 colors, so there are 21 rows representing the color options per drop-down selection list per product with the only difference being the name of the color in each row. See, the left value before the pipe represents the name of the drop-down list and the right side of the pipe represents the value that goes in that list. Yeah, I know, kinda crazy but that's what it is.

So knowing how things are structured I need to loop through the dataStepTwo array and remove/modify as needed.

First thing though is to create the new data that is to replace the old data. Here is the new data to replace the old:


  
  



Note that newData[x][0] is just the value that I'm concerned with - in the actual table its the pipe delimited value that I mentioned previously. As you continue reading you will see in my code that I am splitting the originally pipe-delimited value, replacing the [1] with a new value then joining and inserting the updated string.

After putting the above together and thinking about what I needed to do I realized that the only things that need to change are the values in index 4 and index 15 of the arrays representing each individual table row. So, most of the newData array as seen above is not necessary as I will only be using newData[x][0] and newData[x][9]. At this point I let it be as I wasn't sure if I would need it for some reason yet to be discovered. Anyway, no harm done, lets continue...

So, now about the logic - there are 14 new colors. Any color name that contains the words "Powder Coat" are ones that need to have their values changed. Caveat is that some existing products have more colors than what we will end up with. So I will need to edit the first 14 colors to reflect the new values and then delete any left over colors. Here's what I came up with:


  
  



To test this I capped the for loop at 100 and then as before took a look at specific dataStepTwo indexes via the console. I discovered that the script was working fine, modifying/deleting rows as needed. Next I wanted to do the full monty - rip through everything, rebuild the CSV and output to the screen via a textarea tag. Here then is the entire script:


  
  


  


And a pic of the final product - 10,752 rows, which is quite a few less than the original but that's expected as I have deleted unnecessary rows. All that is left is to copy and paste into a text file:

So.... JavaScript does all this work quite easily. The join, split and splice array methods proved to be quite handy. Also, Chrome's performance was near instantaneous. The only things that took 2-3 seconds to do were to determine the length of the strings/arrays when I wanted them output to the console for testing. Otherwise, without doing those things it takes no time at all for Chrome to crunch all the strings/loop through the arrays.

The best part is how little code was necessary to do this and there was no need to setup a temporary database and mess with SQL, it was more straightforward than what I thought it would be.

Cross-Domain Ajax and XML with jQuery

07 May 2012

Did a fun little thing today – built a jQuery/PHP-based Cost of Living (COL) calculator for a city’s website (sorry, have to be vague on the client’s name) utilizing the data from www.coli.org.

Coli.org provides a service that allows interested parties to compare the cost of living across a range of criteria between two different locations within the Untied States.

Typically as in this case the destination location is the city that is providing the calculator. The intent is to allow potential new residents to compare their current cost of living to the destination city.

To start with you will need to query two of the services on page load – one to get the list of origin locations and another to get the list of destination locations (typically one or two locations – and if you want only one destination you could easily hard code the value of the city via a hidden form field or similar). Once you receive this data you will need to populate two select lists to allow the user to make their selections. With that data you will then query another web service to get the results. Further manipulation of the returned data on the client side with some math will get you percentages and adjusted income.

Approach

There are a couple of ways to approach this – server-side or client-side. As I’m a front-end guy my approach then was to do Ajax knowing that jQuery makes it trivial to do AJAX and parse XML. However, since this was going to be a cross-domain call a 100% client-side solution wouldn’t be possible. Instead, I would have to use something that wouldn’t be subject to a browser’s cross-domain policy – a proxy to act as the go-between for client and the web service. The server the site is hosted on supports PHP and so that dictated the tech to use. A little research revealed PHP’s cURL and a few lines of code later I had myself the proxy that I needed to accomplish the cross-domain service calls.

Proxy

One thing I noticed in the live examples that Coli.org provided is that the GUID was exposed which seemed unnecessary to me. So, instead of following their implementation examples verbatim I placed the GUID into the PHP proxy thereby keeping it secure. Of course, Coli.org might be mitigating GUID abuse by restricting calls to registered domains – I’ve no idea, but in any event, it seemed to be a better approach to not expose it if possible.

If you’re looking into using the Coli.org data by mirroring my approach you’ll be interested in the proxy, which I’ve provided below:

...

define ('HOSTNAME', 'http://www.coli.org/_clicktocomparews/service.asmx/');
$path = $_GET['ws_path'];
$url = HOSTNAME.$path;
$session = curl_init($url);
if ($_GET['ws_path']) {
     $getvars = '';
     while ($element = current($_GET)) {
          $getvars .= key($_GET).'='.$element.'&';
          next($_GET);
     }
     $getvars .= '&licenseeGUID={_YOUR_GUID_GOES_HERE_}';
     curl_setopt ($session, CURLOPT_POST, true);
     curl_setopt ($session, CURLOPT_POSTFIELDS, $getvars);
}

curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
$xml = curl_exec($session);
header("Content-Type: text/xml");
echo $xml;
curl_close($session)

...

All you need to do is to replace the string “_YOUR_GUID_GOES_HERE_” with the GUID that Coli.org gives you and as long as your server supports cURL your proxy is ready to be used. Note that the curly braces surrounding the numeric GUID is necessary.

AJAX & jQuery

Next is the AJAX. jQuery really makes it easy to do AJAX, here’s a stripped down version of my AJAX call to the Proxy:

...
function doAjax(wsPath,args){
   var dataStr = args ? 'ws_path=' + wsPath + '&' + args : 'ws_path=' + wsPath;
   $.ajax({
      url:      '/col_proxy.php',
      data:      dataStr + '&action=compute',
      dataType: 'xml',
      error:     function(xhr,status,err){
         //console.log(status + '\n' + err);
      },
      success:function(responseData,status,xhr){
         // console.log('success')
      });
}
...

So lets break this down, doAjax() expects two arguments to be passed to it. “wsPath” is the web service path, “args” are the arguments to be sent to the webservice. You would use the success callback to process the returned XML.

The web service paths for Coli.org’s web services are the following (as can be publicly seen here):

  • GetComparisonDetail
  • GetComparisonSummary
  • GetFromCommonPlaceTotal
  • GetFromLocations
  • GetPeriodInfo
  • GetSampleLocations
  • GetToCommonPlaceTotal
  • GetToLocations

Since I need the list of origin and destination locations first before any other web service can be used I call doAjax() via $(document).ready with the following placed at the end of my script:

...
$(document).ready(function(){
... 
... 
  // bottom of script
  (function init(){
    doAjax('GetFromLocations',null);
    doAjax('GetToLocations',null);
  })();
}); 

The “init” function is self-invoking and as you might have guessed, “GetFromLocations” is the web service for getting the list of origin locations, and “GetToLocations” provides the list of possible destination locations. These two services don’t require any extra attributes so the second argument to doAjax() is simply passed null.

The next task would be to call another webservice once the origin and destination cities have been selected by passing the data that repesents the city selections and the desired web service path. I didn’t use all of the webservices that Coli.org has availiable as they weren’t needed for my application. For my needs only two more calls were needed: “GetComparisonDetail” and “GetComparisonSummary”. In both of these cases the required name-value pairs to send along are:

  • fromCity
  • toCity

So then, knowing this and we would would pass the web service name as the first argument for doAjax() and construct the name/value pair string to be the value of the second argument. Something that in your own application may look similar to this:

...
// put together the "from" and to" name value pairs
// where "fromVal" is the origin city code and
// "toVal" is the destination city code
var args = 'fromCity=' + fromVal + '&toCity=' + toVal;

// specify the web service to use
var wServiceName = 'GetComparisonSummary';

// query the webservice
doAjax(wServiceName,args);
...

Processing XML

The result from a successful web service call will be XML. Coli.org unfortunately doesn’t provide XML samples so you’ll have to discover this on your own. What I did was to construct the entire web service call **sans proxy** directly in my browser’s address bar. The entire call, from www.colie.org… all the way to the GUID and any other necessary arguments – and then hit enter on my keyboard. If succesfull the XML response will appear in-browser.

Here’s an example of discovering the XML structure via a direct link in a browser:

http://www.coli.org/_clicktocomparews/service.asmx/GetFromLocations?licenseeGUID={_YOUR_GUID_HERE}

Here is a portion of the returned XML:

...

  
  T-Bone SteakFairbanksAK110.439.2655586592178789Q12011
T-Bone SteakWarsawIN19.79.2655586592178789Q12011
Ground BeefFairbanksAK13.83.0240502793296091Q12011
...

Once you know the XML structure the next few steps couldn’t be easier – in this case, each location is described as an XML node/tag called “Table”. Within this node are other nodes that themselves contain the specific values that you are looking for, such as:

  • Category_Name
  • Place_Name
  • State
  • Cost

In jQuery to find any node/tag within an XML doc simply use the “find()” method and you will get an array of all matching nodes. looking back at the doAjax() sample code we can see that the success callback assigns the returned XML to “responseData”. So then we have all the piecess to this rather small puzzle. To find all of the “Table” nodes within “responseData” our jQuery code would be:

...
var tableArray = $(responseData).find('Table');
...

This gives us all the “Table” nodes – but… we of course have to loop through each one looking for the specific values that we need and while a for loop would work here to iterate through each “Table” we should instead utilize chaining and jQuery’s built-in iterator, the “each()” method, and its callback:

...
$(responseData).find('Table').each(function(){
  // additional processing instructions here;
});
...

Pretty cool how chaining allows us to do multiple things simultaneously, right? This is a rather small chaining example too, but anyway, the next step is to find the desired values within each individual “Table” node. This is quite simple as well, see the next example:

...
$(responseData).find('Table').each(function(){
  var cname = $(this).find('Category_Name').text();
  var pname = $(this).find('Place_Name').text();
});
...

The $(this) statement in this context simply means “the current Table node” – then we look for the specific child node inside the current Table by again using the find() method. And on the end, we use the “text()” method to pull the text that is within the desired nodes. Here we are pulling the text for the “Category_Name” and “Place_Name” nodes which again are child nodes of “Table”.

THis is great, but this code doesn’t do us much good at this point as all we end up with are the Category and Place values of the very last Table in two variables. Instead we would actualy use this to populate the page with HTML for each individual Table. We can quickly print out the results to the page by appending html to a pre-existing div. As seen in the following example our existing div is called “tableResults”:

...
$(responseData).find('Table').each(function(){
  var currentRow =  '

Category: ' + $(this).find('Category_Name').text(); currentRow += ', Location: ' +$(this).find('Place_Name').text() + '

'; $('#tableResults').append(currentRow) }); ...

This will print out the Category and Location of each Table node within the mentioned pre-existing div called “tableResults”. Note that I’ve wrapped each set of Table data in a P tag. This gives us some automatic padding between lines which makes things a little more visually appealing. This sample is pretty basic, you’ll of course want to do more in your own application.

And… (drum roll)… thats about it. This was just a brief overview of the technical aspects of integrating into Coli.org’s web services via client-side technologies, not a complete functional example though with the code samples above – and complete working PHP-based proxy – you should be able to integrate with their web services quite easily or at the very least get a jump start on a similar project of your own.

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()));
            }
        }
    }
  }
]
...