Using the jQuery Globalization plugin to toggle between edit and display

August 9, 2010

The web application I am working on involves a lot of data entry.  Since my client is an insurance company, some of the values we are expecting to see inputted can venture into the millions.

The project manager came around the other day and mentioned, “You know … if you are entering large numbers in Excel, it automatically shows the comma separator so you can easily discern the amount you are entering.  It sure would be nice if the application displayed commas in the fields like Excel does.”

So … I put on my jQuery hat and came up with a solution that I think works rather nicely.

I remembered that the Gu recently posted about using the jQuery globalization plugin.  By using this plugin, we can avoid writing custom parsing and formatting functions.  Instead, we can leverage the work that has already been done to do those functions with the added bonus of instant support for over 350 cultures.

Setting it up

First of all, download the plugin and add a reference to it on your View:

<script type="text/javascript" src="/Scripts/jquery.glob.min.js"></script>

And here are the custom jQuery extensions I wrote that can be called on our text boxes.

jQuery.fn.formatForDisplay = function () {
  // call formatValue() passing in a formatting function
  return $(this).formatValue(
    function(val,fmtString) {
      // format the string using the globalization plugin
      // i.e. 1500000 becomes 1,500,000.00
      return jQuery.format(val,fmtString);
    });
}

jQuery.fn.formatForEdit = function () {
  return $(this).formatValue();
}

jQuery.fn.formatValue = function(fnDoFormat) {
  var that = $(this);
  // default to 2 decimal places unless it is marked
  // with a "numbers" class
  var decPlaces = that.hasClass("numbers") ? 0 : 2;
  var fmtString = "n" + decPlaces.toString();
  var thatVal = that.val();
  if (thatVal && thatVal !== '') {
    // parse the value using the globalization plugin
    var parsedVal = jQuery.parseFloat(thatVal, fmtString);
    if (!isNaN(parsedVal)) {
      // call the format function if it was passed in
      var fmtVal = fnDoFormat ? fnDoFormat(parsedVal, fmtString) : parsedVal;
      that.val(fmtVal);
    }
  }
  return that;
}

Here’s how it works.  When the page is rendered, the fields are formatted to “display” mode using the browser’s default locale.  Once a user enters a field, the field enters into “edit” mode and the formatting is removed.  When the user leaves a field (i.e. the blur() event is fired), the field value is formatted once again.

Wiring it up

I am using CSS classes to mark if a textbox should contain decimals or numbers.  I am also using a “noformat” class to make sure we can ignore certain textboxes if we don’t want them formatted.

  $(document).ready(function() {
    var fieldsForFormat = $(".numbers:not(.noformat),.decimal:not(.noformat)");

    fieldsForFormat
      .live('focus', function() { $(this).formatForEdit(); })
      .live('blur', function() { $(this).formatForDisplay(); })
      .each(function () { $(this).formatForDisplay(); });
  });

When the page is rendered, we get the fields we want to toggle for display/edit.  We wire the focus() event so that the value is stripped of location formatting, and wire the blur() event to add the formatting back in.  We also initialize each field for display.

How does it look?

Here are two screenshots, showing how a field will look on focus and on blur:

Focus (Edit mode) Blur (Display mode)
On Focus Display

One more thing…

Keep in mind, the field values are now “strings” and not “ints” or “floats”. If you are expecting these values to be numbers, you will have to do some manipulation, either in javascript before the form is submitted or in your controller.

Using Server-Side Constant values in Javascript

August 1, 2010

One of the challenges with writing rich web UIs is managing business logic across the client-side and the server-side.  Oftentimes, the lines are somewhat blurry as to where this logic should occur and if/when it needs to be repeated.

I recently ran into a bug in my web application that surfaced when the values from a lookup table in the database had changed.  Even though the constants in the server-side C# code were updated to map to the new values, the literal DB values were hard-coded in the javascript code.  So our javascript code no longer worked since the IDs on the client-side no longer matched what was present in the DB.

Unfortunately, even tools like ReSharper are not enough to help us here.  Since the values were hard-coded in the javascript, a “Rename” operation would not catch the change.  Bugs like these can be very hard to  track down.

In order to prevent this from happening in the future, I decided to take a “DRY” approach to keeping the server-side and client-side variables in sync.

If you need to create global or “application” variables in javascript, you need to do so with some caution.  Anything you name these variables can potentially clash with other 3rd party javascript libraries you are using, as well as other variables in your own code.  A good practice to use is to create a single object literal that will hold all the global variables for your application.

  if (!myApp) { var myApp = {}; }

Now we can starting using this variable to embed our application variables.  The global variable effectively “namespaces” all variables you declare within it.

What I did was to create a separate partial view whose sole purpose is to hold client-side application level information.  The nice thing about this approach is: 1) it is modular; 2) the information is stored in one recognizable place; and 3) it can be included as needed in any web page. 

Since the javascript values I declare will be directly mapped to C# constants that will be escaped in the view rendering process, this needs to be performed in a web view rather than in a .js file (where you would normally expect to see it).

So here is my partial view, which I’ve named ~/Views/Shared/JavascriptGlobals.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<script type="text/javascript">
  if (!myApp) { var myApp = {}; }

  if (!myApp.ccType) { myApp.ccType = {}; }
  myApp.ccType = {
    visa: <%= LookupItem.CC_TYPE_VISA %>,
    amex: <%= LookupItem.CC_TYPE_AMEX %>,
    mastercard: <%= LookupItem.CC_TYPE_MASTERCARD %>
  }
</script>

Now, we can embed this partial into any view we want.  Personally, I include it at the bottom of my master page.

<% Html.RenderPartial("JavascriptGlobals"); %> 

Now we can use our application variables in our web pages.

<script type="text/javascript">
  $(document).ready(function() {
    $("#CreditCardType").change(function(evt) {
      var val = $(this).val();
      if (val) {
        var ccType = parseInt(val);
        if ( ccType === myApp.ccType.visa ) {
          // do something
        }
        else if ( ccType === myApp.ccType.amex ) {
          //
        }
        // etc
      }
    }
  }
</script>