Wednesday, September 21, 2011

Jquery plugin IBM

Introduction
In a previous article in this series, Working with jQuery, Part 2: Intermediate JQuery: The UI project, I discussed using plug-ins in your own jQuery code to increase the effectiveness of your Web applications. However, these plug-ins don't write themselves. They are written and tested by developers like you and me, donating their free time to the jQuery community to make it better. And we do this all for free, living only for the love of our own code. This article focuses on how you can give something back to this great community, by writing your own plug-in and checking it into the plug-in pages hosted by jQuery. This lets everyone use the plug-in you create, bettering the entire jQuery development community. Consider this my charity work for the year.
While writing the plug-in for this article, I found that the plug-in creation, and the framework for creating it, is extremely straightforward and easy. The hard work is actually thinking of something that hasn't already been done by someone else, and writing the "grunt work" JavaScript code that actually does things. Because of the straightforward plug-in structure, which is easy for beginners and flexible for advanced coders, the number of plug-ins has grown rapidly.
Of course, in doing the research for this article, I also found that every author has their own style for writing a plug-in and that jQuery allows several different styles for writing them. I will concentrate on the easiest one here, and the one recommended by jQuery itself, but will point out any differences or options as they pop up.
Describing your plug-in
Step 1 in creating a plug-in is, of course, thinking of a good idea. Chances are, like most good ideas, someone else has already beaten you to it. For the plug-in I'll be developing here, it's not a novel concept, but at the time of this article I couldn't find it anywhere in the jQuery plug-in community. I know I would personally get a lot of use out of it.
My plug-in is a NumberFormatter plug-in. Number formatting is likely familiar to anyone who's worked with server-side code like Java™ or PHP and internationalization. It's common knowledge that not everyone in the world formats their numbers in the same way. For example, not everyone uses miles for distance. A number that we would write in the U.S. as "1,250,500.75" (I just grabbed this number from my tax form), would be written differently in different countries: "1.250.500,75" in Germany, "1 250 500,75" in France, "1'250'500.75" in Switzerland, and "125,0500.75" in Japan. The number is exactly the same, but it's just written using a different format when presented to users of the Web application.
So, the question emerges, when you're writing an international application, how would you present these numbers to people in different countries? The solution, of course, is to use server-side formatting, which is pretty common. Java has a robust formatting library to make formatting numbers easy. When the page is set up on the server with numbers, the server can take care of formatting these numbers. However, many times you might not be on the server, so you need a way to format numbers on the client, without ever talking to the server.
Here's a typical use case for what I'm describing. You have an input field in your Web application that asks a person for their salary. In the U.S., the user can type in varied forms of input - "$65000", "65,000", "65000", "65,000.00". All these numbers are exactly the same, but you want to control how these numbers look on the screen, in order to create a better user experience. You could call the server after the number is entered, but this could get annoying if you have a lot of number fields with different formats. Plus, if you can take care of this problem on the client, and offer instantaneous feedback to the user, why wouldn't you do that?
So, I've established the gap in JavaScript/jQuery functionality I am trying to fill. My plug-in is going to offer number formatting on the client, giving others a way to offer internationalization for their Web applications without having to talk to the server. As an added bonus, my plug-in will also do the opposite function; the plug-in will allow developers to parse numbers, getting a number out of a formatted text string. This can be used for mathematical operations on the client. Additionally, I will mimic the functionality in the Java DecimalFormatter class, to maintain a commonality between the client code and the standard server-side method of doing number formatting.
Step 1 result: I've established a need for a plug-in, and have defined the specs that I will use in filling the need.
Plug-in rules
The jQuery team has established a number of general rules that they want plug-in authors to follow to create a common and expected environment for the plug-in users. Because I consider the jQuery team to be much smarter than me, who am I to disagree with any of the rules, right? For that reason, I'll outline the rules here, and will also strive to follow them in every step of my own plug-in.

  • Name your file "jquery.<your plug-in name>.js"
    Well, this makes sense, because you want someone to look at the file and know right away that it's a jQuery plug-in, and which plug-in it is. Check. I will call my plug-in "jquery.numberformatter.js."
  • All new methods are attached to the jQuery.fn object, all new functions to the jQuery object
    This may be confusing at this stage, and I will discuss this more in the next section because this is the most important rule for actual coding. Check. I will only attach methods/functions to those two objects.
  • "this" is a reference to the jQuery object
    This is to facilitate plug-in writing by letting all plug-in authors know what object they will receive from jQuery when they reference "this." Check. I will use "this" only as a reference to the jQuery object.
  • All methods/functions defined in the plug-in must have a ";" (semicolon) at the end or code minimizers will break.
    Because it is a best practice to minimize JavaScript files, breaking the minimizer would be bad, and chances are your plug-in would get dumped quickly Check. All of the methods/functions will have a ";" at the end.
  • All methods must return the jQuery object, unless otherwise noted
    The daisy-chaining of jQuery methods is quite popular, and if you write a plug-in that breaks this chain, it literally "breaks the chain" Check. My format() method will return the jQuery object, and even though the parse() method will not return the jQuery object, I have documented it in many places that this function breaks the chain. (After all, it would be difficult/impossible to return a Number object and not break the chain.)
  • You should always use this.each() to iterate over the matched elements, as this is a reliable and efficient way to loop through objects
    For performance and stability reasons, they recommend all methods use this means to loop through matched elements. Check. My methods will only loop through matched elements in this manner.
  • Always use "jQuery" instead of "$" in your plug-in code
    This is important because it allows users who have a conflict with "$" (those who may be using another JavaScript library) the ability to change their pseudonym for jQuery in one place using the "var JQ = jQuery.noConflict();" function. However, as I have looked through many plug-ins, I have found that this rule is broken quite often, which is unfortunate. If the developer ever needs to change the jQuery pseudonym, it likely means that the broken plug-ins will get dumped. Check. In my plug-in, I will only use the jQuery and not its "$" pseudonym.
So, those are the only rules/recommendations that are placed on your plug-in code. The truth of the matter is, they are de facto enforced, because if you start breaking the rules of the plug-ins, your plug-in won't get used very often, and it will get bad reviews from your peers. What will result is a plug-in that gets rarely used, and one you probably wasted your time on. Therefore, it's important to follow the rules. This not only helps your peers out, and makes their coding consistent, it also gives your plug-in a greater chance of success.
Step 2 result: I will follow all the rules of creating a jQuery plug-in
Writing the plug-in
Now it's time to start writing the code! The first step in starting your plug-in is to decide how you want to structure your plug-in. There are two choices to start off with: do you want it to be a method or a function? "What's the difference?" you might ask.
A method, as I said above, needs to be attached to the jQuery.fn object, and a function needs to be attached to the jQuery object. Oh, that clears everything up, right? Well, probably not if you're relatively new to jQuery. Instead, think of it this way. A method gives your code the ability to loop through all the selected elements that are passed into the plug-in. As a result, your plug-in can accept any type of HTML element, and it's up to your plug-in to define how to work with each element. Thus, your plug-in method can accept any jQuery selector, anything from "p" to "#mySpecificPageElement". This would be desirable if you want to have some flexibility in your plug-in, allowing users to pass in any type of page element. The burden is on you, the plug-in developer, to deal with everything properly. Contrast that with a function, which doesn't take any selected elements as arguments. It's simply a function that will be applied to the entire page. The burden shifts to the plug-in developer, who has to define which page elements they want the plug-in to interact with, and ignore the others. Let's take a look at the difference in some code.

Listing 1. jQuery plug-in method/function
		   
// This is a method because you can pass any type of selector to the method and it 
// will take some action on the results of the selector.  In this case, it will
// doSomething() on the page element with an ID of myExample
$("#myExample").doSomething();
   
// This is also a method, even though you are passing the entire page body to
// the method, because you are still passing a selector
$("body").doSomethingElse();

// This is a function, because you are NOT passing any selector to the function
// The plug-in developer must determine what page elements they want to take action on.
// This is usually accomplished by the plug-in developer requiring the page elements
// to contain a certain class name.

<div class="anotherThing">

// This hypothetical plug-in developer would document that his plug-in only works
// on elements with the class "anotherThing"
$.anotherThing();

So, judging from these descriptions, it looks like your plug-in should use a method, because you want to allow its users to tell you what page elements they want formatted. Listing 2 shows how your plug-in code looks now.

Listing 2. Your method definition
		   
jQuery.fn.format = function();

// You would call your plug-in like this (at this point)
$("#myText").format();

Of course, your function can't simply be a one-size-fits-all plug-in, because you're dealing with internationalization and you can't automatically figure out what country you want the text formatted for or the format you want. So, you must modify your plug-in slightly to accept some options. You will need two options in your formatting method: the format the number should use (for example, #,### vs. #,###.00) and the locale (the locale being a simple 2-letter country code to determine which international number formatting to use).
You also want to make your plug-in as easy to use as possible, because you want to increase your plug-in's chances of success. This means you should also go ahead and define some default options so the user doesn't have to pass in the options if they don't want to. Because I'm writing this from the U.S., and that also happens to be the most common number formatting in the world, I'll default to the "us" locale, and I'll default to the "#,###.00" format, for no other reason other that it can be used for currency with this default.

Listing 3. Allowing options in your plug-in
		   
jQuery.fn.format = function(options) {

    // the jQuery.extend function takes an unlimited number of arguments, and each
    // successive argument can overwrite the values of the previous ones.
    // This setup is beneficial for defining default values, because you define
    // them first, and then use the options passed into the method as the
    // second argument.  This allows the user to override any default values with their
    // own in an easy-to-use setup.
    var options = jQuery.extend( {

      format: "#,###.00",
      locale: "us"

}, options);

The final step to creating your plug-in framework is to work properly with the selected elements that were passed into the method. If you'll recall from the examples above, the selected elements could be 1 page element, or it could be many page elements. You have to deal with them all equally well. Also, recall from the jQuery plug-in commandments, the "this" object is a reference to the jQuery object. So, you have a reference to the jQuery-selected elements that were passed into the method, and now you just need to iterate through them. Also, looking back at the Commandments, each plug-in method should return the jQuery object. You know, of course, the jQuery object is "this", so you can simply return this in your method and be all good. Let's take a look at how you can accomplish both in a code snippet, iterating through each selected element and return the jQuery object.

Listing 4. Working with the jQuery object
		    
jQuery.fn.format = function(options) {

    var options = jQuery.extend( {

      format: "#,###.00",
      locale: "us"

    }, options);

// this code snippet will loop through the selected elements and return the jQuery object 
// when complete
return this.each(function(){
  // inside each iteration, you can reference the current element by using the standard
  // jQuery(this) notation
  // the rest of the plug-in code goes here
  });

Because the actual plug-in code itself isn't the point of this article, I won't spend any time on it, but you can see it all in the plug-in code attached to this article (see Download). I'd also like to show you an example of how you'd set up your plug-in architecture if you decided to write a function, as opposed to a method.

Listing 5. Example plug-in using a function
		   
jQuery.exampleFunction = function(options) {

   var options = jQuery.extend( {

     // your defaults

    }, options);
    
    jQuery(".exampleSelector").each(function(){
    
    });

});

Fine tuning the plug-in
Most beginner plug-in articles you find around the Web will stop there, letting you take the basic plug-in format and run with it. However, this basic framework is just that, basic. There are other important things that you must consider when writing your own plug-in, things that will give your plug-in the polish it needs to be more than a beginner plug-in. With these two added steps, you can turn your beginner plug-in into an intermediate-level plug-in.
Fine tune #1 - Make internal methods private
In any object-oriented programming language, you will find it handy to create external functions that can run repetitious code. In the NumberFormatter plug-in that I've created, there is an example of this code — the code that decides which locale got passed into the function and which characters to use as the decimal points and the group separator. This code is needed in the both the format() method and the parse() method, and any beginning programmer can tell you this belongs in its own method. However, one problem arises from this decision because you are working with a jQuery plug-in: if you simply make it its own function, defined in JavaScript, then the method can be called by anyone who uses the script, for any purpose. This isn't what the function is intended for, and I would like to prevent outsiders from calling it, because it is intended only for my internal work. So, let's see how you can make this function a private one.
The solution for this private method problem is something called a Closure, and it essentially closes the entire plug-in code from outside calls, except those you attach to the jQuery object (which is public). Using this design, you can put any code you want within your plug-in and not worry about it being called by outside scripts. By attaching your plug-in methods to the jQuery object, you are essentially making them public methods, with all the rest of the functions/classes becoming private. Listing 6 gives you a look at the code required to do this.

Listing 6. Make a function private
		   
// this code creates the Closure structure
(function(jQuery) {

   // this function is "private"
   function formatCodes(locale) {
      // plug-in specific code here
   };  // don't forget the semi-colon

   // this method is "public" because it's attached to the jQuery object
    jQuery.fn.format = function(options) {

     var options = jQuery.extend( {

          format: "#,###.00",
          locale: "us"

     },options);

     return this.each(function(){
         var text = new String(jQuery(this).text());
         if (jQuery(this).is(":input"))
             text = new String(jQuery(this).val());

         // you can call the private function like any other function
         var formatData = formatCodes(options.locale.toLowerCase());

         // plug-in-specific code  here
      });
     }; // don't forget the semi-colon to close the method

// this code ends the Closure structure
})(jQuery);

Fine tune #2 - Make your plug-in's defaults overridable
The last step to fine-tuning this plug-in is to give it the ability to have its defaults overridden. After all, if a developer in Germany downloaded this plug-in, and knew that all of his Web application's users would want to see the German locale, you should give him the ability to change the default locale in one line of code, rather than requiring him to write it in every method call. This is particularly handy in your plug-in because it is highly unlikely that a Web application will present numbers in different international formats to its users. Chances are, if you're on a Web page, all the numbers are going to be formatted using the same locale.
This step will require you to modify your code somewhat, so what you've seen so far has been updated to reflect this latest polishing step.

Listing 7. Overridable defaults
		   

jQuery.fn.format = function(options) {
// Change how you load your options in to take advantage of your overridable defaults
// You change how your extend() function works, because the defaults
// are globally defined, rather than within the method.  If you didn't use the
// {} as the first argument, you'd copy the options passed in over the defaults, which is
// undesirable.  This {} creates a new temporary object to store the options
// You can simply call the defaults as an object within your plug-in
var options = jQuery.extend({},jQuery.fn.format.defaults, options);

   return this.each(function(){
   
   // rest of the plug-in code here

// define the defaults here as an object in the plug-in
 jQuery.fn.format.defaults = {
      format: "#,###.00",
      locale: "us"
      }; // don't forget the semi-colon

That is the last step to creating your plug-in! What you should have now at this point is a polished plug-in that's ready for the final steps of testing. Listing 8 shows the completed plug-in that you've put together in this article so you can see how all the pieces fit together. Also, included is the parse() function, which I haven't talked about up until this point, but which is included in the plug-in. (I've excluded the grunt work of the plug-in, the parts that deal with formatting, because they are outside the scope of the article. They are included in the examples, and are of course in the plug-in itself).

Listing 8. The NumberFormatter plug-in
		   
(function(jQuery) {

    function FormatData(valid, dec, group, neg) {
       this.valid = valid;
       this.dec = dec;
       this.group = group;
       this.neg = neg;
    };

    function formatCodes(locale) {
      // format logic goes here
      return new FormatData(valid, dec, group, neg);
    };

 jQuery.fn.parse = function(options) {

    var options = jQuery.extend({},jQuery.fn.parse.defaults, options);

    var formatData = formatCodes(options.locale.toLowerCase());

    var valid = formatData.valid;
    var dec = formatData.dec;
    var group = formatData.group;
    var neg = formatData.neg;

    var array = [];
    this.each(function(){

       var text = new String(jQuery(this).text());
       if (jQuery(this).is(":input"))
           text = new String(jQuery(this).val());


       // now we need to convert it into a number
       var number = new Number(text.replace(group,'').replace(dec,".").replace(neg,"-"));
       array.push(number);
     });

     return array;
 };

 jQuery.fn.format = function(options) {

    var options = jQuery.extend({},jQuery.fn.format.defaults, options);  

    var formatData = formatCodes(options.locale.toLowerCase());

    var valid = formatData.valid;
    var dec = formatData.dec;
    var group = formatData.group;
    var neg = formatData.neg;

    return this.each(function(){
       var text = new String(jQuery(this).text());
       if (jQuery(this).is(":input"))
           text = new String(jQuery(this).val());

       // formatting logic goes here

       if (jQuery(this).is(":input"))
           jQuery(this).val(returnString);
       else
           jQuery(this).text(returnString);
    });
 };
 
 jQuery.fn.parse.defaults = {
    locale: "us"
 };

 jQuery.fn.format.defaults = {
    format: "#,###.00",
    locale: "us"
 };
 
 })(jQuery);

Testing the plug-in
The final step in creating my plug-in is to test it—thoroughly. Nothing angers a plug-in user more than seeing a bug in your plug-in. Rather than fix it, they will quickly dump your plug-in and move on. Getting a few of these types of users as well as some bad reviews can quickly sink your plug-in. Besides, it's a good reciprocal behavior—you want the plug-ins you use in your code to be well tested, so you should return the favor in your own plug-in code.
I created a quick test structure to test my plug-in (no need for a unit testing library), which creates dozens of spans with various numbers and the correct format right after the number. The JavaScript test calls format on the numbers and then compares the formatted number with the desired result, showing them as red if it fails. With this quick test, I can set up dozens of different test cases, testing every possible format, which I've done. I've attached my test page to the examples download so you can see one possible solution to testing your plug-in, utilizing jQuery to do the testing for you.
Looking at the finished plug-in
Let's take a look at the new NumberFormatter in action. I've created a simple Web application that you can look at to see how the NumberFormatter plug-in can fit in your applications.

Figure 1. NumberFormatter in action
A screen shot shows a series of labels with numeric fields, each displaying numbers correctly formatted in various styles.
The Web application is simple and straightforward enough. When the user leaves the textfield, after typing in their salary, house, and child information, the NumberFormatter plug-in will format their answers appropriately. The plug-in allows the Web app to consistently format numbers to the user. Also, note that this Web application is formatted for a user in Germany, as the decimal and group characters are different than it would be for a user in the U.S. (this lets me show how to change defaults).

Listing 9. NumberFormatter in action
		   
$(document).ready(function() {

   // use the AlphaNumeric plug-in to limit the input
   $(".decimal").decimal();
   $(".numeric").numeric();

   // you want to change the defaults to use the German locale
   // this will change the default for every method call on the
   // entire page, so you won't have to pass in the "locale"
   // argument to any function
   $.fn.format.defaults.locale = "de";
   $.fn.parse.defaults.locale = "de";

   // when the salary field loses focus, format it properly
   $("#salary").blur(function(){
      $(this).format({format:"#,###.00"});
   });

   // when the house field loses focus, format it properly
   $("#houseWorth").blur(function(){
      $(this).format({format:"#,###"});
   });
   
   // when the kids field loses focus, format it properly
   $("#kids").blur(function(){
      $(this).format({format:"#"});
   });
   
   // calculate the tax
   $("#calculate").click(function(){
      // parse all the numbers from the fields
      var salary = $("#salary").parse();
      var house = $("#houseWorth").parse();
      var kids = $("#kids").parse();
      // make some imaginary tax formula
      var tax = Math.max(0,(0.22*salary) + (0.03*house) - (4000*kids));
      // place the result in the tax field, and then format the resulting number
      // you need one intermediate step though, and that's the "formatNumber" function
      // because all numbers in JavaScript use a US locale when made into a String
      // you need to convert this Number into a German locale String before
      // calling format on it.
      // So, the steps are:
      //   1) the tax is a Number that looks like 9200.54 (US locale)
      //   2) formatNumber converts this to a String of 9200,54 (German locale)
      //   3) put this String in the #tax field
      //   4) Call format() on this field
      $("#tax").text($.formatNumber(tax)).format({format:"#,###"});
   });

});

Some remaining things to point out about the NumberFormatter plug-in before I close things up. First of all, this is the first 1.0.0 release of the plug-in, so I'm hoping to expand it in the future to include more formatting features that are included in the Java DecimalFormatter. This includes support for currency, scientific notation, and percentages. It will also include separate formatting rules for positive and negative numbers, beyond the simple "-" for a negative (for example, using (5,000) for negative numbers, which is done in accounting). Finally, a good formatter will allow any character in the format, it just ignores any that aren't part of the reserved characters. These are all features I hope to add in the near term to make this a robust plug-in.
Getting the locale of a user
One last question that's not specific to jQuery plug-ins, but one that might arise from using this plug-in—how do you get the user's locale? This is a good question, because there's currently no way to get this information using JavaScript. You'll need to create a JavaScript Bridge to do it. What's a JavaScript Bridge? I just mean you can set up a simple design pattern to get values into the JavaScript code from the server-side code. Listing 10 shows you could use Java; how you could do this on a JSP page.

Listing 10. Getting a user's locale
		   
<%

    // the request object is built into JSPs
    // unfortunately, it's not any easier
    // tested on FF, IE, Safari, Chrome
    String locale = "us"; // or your default locale
    String accLang = request.getHeader("Accept-Language");
    if (accLang.length() > 5)
    {
       accLang = accLang.substring(0,5);
       locale = accLang.substring(accLang.indexOf("-")+1);
    }

%>

$(document).ready(function() {

   // take advantage of the ability to override defaults by using the JavaScript
   // Bridge here.  Then your page can use the format() and parse() functions
   // elsewhere in the page without modifying them for a user's locale.
   $.fn.format.defaults.locale = "<%=locale%>";
   $.fn.parse.defaults.locale = "<%=locale%>";
   
   });

0 comments:

Post a Comment

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Affiliate Network Reviews