Ajax Bestiary: A Javascript Field Guide
 
Ajax Bestiary: A Javascript Field Guide
 
 

Entries Tagged as 'Article'

CSS and mobile device orientation

Posted by Dave Mahon

As frustrating as dealing with mobile device orientation can be for event handling, it’s very easy with CSS.

For example, it is entirely feasible to rotating the screen to display alternate content within your mobile web app.

Let’s create a small sample document.

<body>
  <div class="landscape">
    Visible Only in Landscape
  </div>
  <div class="portrait">
    Visible Only in Portrait
  </div>
</body>

Now let’s add the CSS rules:

@media only screen and (orientation: landscape) {
  .landscape {
    display: block;
  }

  .portrait {
    display: none;
  }
}
@media only screen and (orientation: portrait) {
  .landscape {
    display: none;
  }

  .portrait {
    display: block;
  }
}

It’s incredibly simple and you’ve now offloaded this handling to the browser. These selectors are currently supported on iOS and Android, so you can trust that it will work reliably.

How to: Detect display density

Posted by Dave Mahon

In some of my past projects, I’ve had a strong desire to know the pixel density of the display device. In traditional desktop environments, we could reasonably trust that an assumption of 72dpi would work well, but on mobile devices this is not the case. The iPhone 4 packs twice as many pixels in an inch as the 3GS and Android devices vary wildly, while it sometimes feels like you can visually distinguish the individual pixels on older Blackberry devices.

Meanwhile, you still need everything to fit as close to pixel-perfect as possible. On the server-side, this requires a massive database, like Tera-WURFL, but on the client-side, we can use a fast and simple script.

For the purposes of this post, I will use jQuery, but you can use the framework of your choice to achieve the same end.

var test = $('</div>');
 test.height("1 inch").width("1 inch");
 var yDensity = Math.round(test.height());
 var xDensity = Math.round(test.width());
 test.remove();

First, I create a DIV with some minimal level of content, since mobile browsers like to collapse (or even ignore!) empty DOM nodes to improve performance.

Then I set its dimensions. I tried setting the dimensions as part of the style, but curiously, Firefox chose to ignore it so I didn’t even bother testing this behavior in Safari.

Now I can use the height() and width() methods in jQuery, which return an integer representing the number of pixels. Note that I have to round this figure, because those listed pixel densities are sometimes approximate. You’ll definitely get a float if you use metric units, of course.

Finally, I remove the node, as we no longer require it. Note that we never attached it to the DOM, so we never influenced rendering of the actual page.

Event-driven development with Backbone

Posted by Dave Mahon

Do you really care if the user clicks an option inside of a SELECT? In most cases, it’s only a means to the end of getting the new value. So why is your change event handler sending data to the server and modifying the DOM? This leads to spaghetti code, violation of DRY and ever more expensive maintenance costs as your app’s interface evolves.

Thus was born Backbone, an extension of underscore, with default support for jQuery, and with a bit of editing, support for MooTools.

So let’s say you’re Amazon and you’re trying to find a way to comply with the legal requirements in some states that say affiliate sold items are subject to sales tax. This is complex and you probably only want to get the most relevant data as late in the process as possible. Backbone makes it surprisingly easy.

Bind to a change event on our data object:

var destination = new Backbone.Model();

_.extend(destination, {state: null});

var order = {
  subtotal: 100,
  tax: 0,
  total: 100
};

//underscore makes it easy to pseudo-subclass
_.extend(order, new Backbone.View());

destination.bind( "change", function() {
  //Pass null in the first parameter to save all model properties
  destination.save(null, {
    success: function(model, reps, xhr){
      //Apply the tax rate
      order.total = order.subtotal + order.subtotal * xhr.taxrate;

      //Update display. Note that this is a no-op by default and
      //you should define it as needed elsewhere.
      order.render();
    }
  } );
} );

Now for our event handler on the SELECT. Note that I’m using jQuery, but you could use any library (or none at all).

$("#state").change( function() {
  //Do whatever pre-processing is required
  var v = $(this).val();

  //This could be generalized very easily
  destination.set({
    state: v
  });
} );

Thoughts on Google’s Native Client platform?

Posted by Dave Mahon

Steven Shankland, over at CNET, has written an interesting piece about Google’s NaCl (yes, chemistry geeks, that is the formula for table salt). Overall, it’s a fairly balanced review.

On the one side, we get all of the benefits of WebKit, but with the performance of a compiled native application. In theory, that then allows us to write a knock-off of Photoshop and make it cross-platform, with easy electronic distribution.

On the other side, it’s not so cross-platform that it works on mobile devices and it does splinter development efforts. It only works on x86 CPU’s to date and it requires a browser plug-in API, which already dates it, since IE10′s Metro version will be plug-in-free.

Finally, Google and Mozilla both offer competing engines. Google’s Dart is intended to supplant JavaScript while Mozilla’s IonMonkey will further improve compiler performance.

Overall, if you’re willing to venture into relatively uncharted territory, have significant say in your deployment environment, and need as much performance as possible, this is an intriguing initiative. I just wonder how many of us developers fall into that bucket.

GPG4Browsers: Site functionality via browser extensions

Posted by Dave Mahon

I’m not sure how I feel about having to install browser extensions to unlock the full potential of a site (It reminds me of Pepsi’s site, circa 1997), but some functionality simply isn’t practical without it.

GPG4Browsers, for example, performs browser-based PGP Gmail message encryption, storing the encryption keys as a browser setting, which from a usability perspective, makes a lot of sense, because it means that the entire experience can be nearly transparent to the user, or require minimal retraining.

For now, this only works in Chrome, but Firefox support is under development (and presumably similar behavior will be possible in IE 10). Naturally, it also has security implications stemming from Javascript’s lazy garbage collection and wide open variable scope, in addition to the usual concerns about browser security.

While GPG4Browsers is clearly a case of progressive enhancement, I can’t help but wonder if JS-based extensions could actually lead to browser lock-in.

Accounting.js A handy formatting library for numbers and finance

Posted by Don Albrecht

Today I stumbled across a handy little javascript framework called accounting.js.  Like the handy date.js library.  It focuses ongoing one thing extremely well. Convert numbers between simple strings of digits into nicely formatted easily readable strings.

it has just a few functions.

formatMoney()

by default, format the string to USD with 2 decimal places, comma separation at the thousands and a $ sign.  Optional params of [currencySymbol], [precision], [thousands separator], [decimal separator].

formatColumn()

just like formatMoney, but it takes an array of values and white space pads their conversions to the same length.

unformat(string)

parse a given number formatted string and return its numerical value.

Joss Crowcroft’s Original Blog Announcment is here:

http://www.josscrowcroft.com/2011/code/my-weekend-project-accounting-js/

And the GitHub site is here:

https://github.com/josscrowcroft/accounting.js

Using functions to further improve mustache

Posted by Dave Mahon

When we last discussed mustache, we saw data that looked like this:

{
  "title": "Our Web Store",
  "items": [
    {"name": "Widget",
     "unitprice": "1.45",
     "quantity": "2",
     "totalprice": "2.90"
    },
    {"name": "Whosit & Whatsit",
     "unitprice": "0.45",
     "quantity": "1",
     "totalprice": "0.90"
    }
  ]
}

There are obvious issues though. We have leading and trailing zeros and we have totals that are obviously calculated. These are all display issues, so why do we tolerate having display data in what functions as our model?

Never fear, for you can use functions in the data, as you would expect from any JSON source. Let’s look at that in action.

First, we’ll restructure our data a bit:

{
  "title": "Our Web Store",
  "items": [
    {"name": "Whosit & Whatsit",
     "prices": {
       "unit": "0.45",
       "total": "0.9"
     }
  ]
}

Now we’ll introduce the functions, taking advantage of Accounting.js as well.

{
  "title": "Our Web Store",
  "items": [
    {"name": "Whosit & Whatsit",
     "prices": {
       "unit": "0.45",
       "total": "0.9"
     },
     "quantity": "2",
     "unitprice": function() {
       return accounting.formatMoney(parseInt(this.prices.unit, 10));
     },
     "totalprice": function() {
       return accounting.formatMoney(parseInt(this.prices.unit, 10) *
              parseInt(this.quantity, 10));
     }
  ]
}

We’ll get almost the same output as before (with an extra dollar sign which we can easily remove from the template) but now we’ve moved all of the display processing into our display layer, which is pretty nice.

Why you should care about MooTools

Posted by Dave Mahon

Let me start by saying that I love jQuery. Most of the time, it makes it very easy to write sane code. Sometimes, however, it just feels weird.

For example, let’s say we need to insert a link at the beginning of a div. You might write something like:

$('#myDiv').prepend('<a href="http://my.host/path" title="This is a cool link!">Click here</a>');

-or-

$('<a href="http://my.host/path" title="This is a cool link!">Click here</a>').prependTo('#myDiv');

It seems fairly straightforward, but it’s a maintenance nightmare, because you now have raw HTML buried inside of your Javascript. If it’s complicated HTML that is being dynamically constructed, it’s all too easy to introduce hard to find browser parser errors.

MooTools, on the other hand, gives you a powerful alternative with its Element class.

To create a new DOM element you literally call a constructor and pass the tag name and attributes to it. So, for example, that same link would be built with:

var myLink = new Element('a', {
 href: "http://my.host/path",
 title: "This is a cool link!",
 text: "Click here"
});

At the price of a handful of few extra bytes, you get clear, legible code. The equivalent of .prepend() is thus:

$('#myDiv').grab(myLink, 'top');

While this might at first seem less intuitive, you can use the same method call to insert that link before or after the selected element. Sure, you could do that with .before() or .after() in jQuery, but imagine if you needed to make the insertion point variable!

There are also a slew of additional benefits, like ECMAScript 5 array methods, support for multiple inheritance, and event handlers that make it easy to detect if the user is pressing Alt while right clicking.

Give MooTools a try, but before you do, I strongly urge you to read Sean McArthur’s excellent post, A Better Way to use Elements, on the difference between $() and $$(). If you come from a background of using Prototype, it will seem perfectly natural, but jQuery users can be caught by surprise.

Getting Started With Chrome Frame

Posted by Don Albrecht

As a follow up to my last post, I thought I’d put out an instruction on actually creating a Google Chrome retreat.

Step 1: Add a Chrome Frame header to your site.

Add this to the head of the Chrome Frame dependent pages:

<meta http-equiv="X-UA-Compatible" content="chrome=1">.

If this isn’t practical, You can also configure your server to send an HTTP header of

X-UA-Compatible: chrome=1

I advise against this approach, however because server configs won’t be immediately available to other users on a site.  By its nature, a Chrome Frame retreat should be considered a reasonably unique event.  It’s good to clearly document this dependency for anyone else who may need to debug things later.  Once you’ve started to retreat, make sure you’re applying the header to every page of the site.  The two engines render things differently and you may create an inconsistent experience for your users if they are transitioning between Chrome Frame and Trident environments frequently.  In my experience, border-radius and drop shadow toggling on and off is a pretty strong “It’s Broken” signal for end users.

Step 2: Identify your Dependency Wall.

  1. Identify the range of pages / screens in your web app that you can’t support in IE.
  2. Identify the navigation routes to those pages.
  3. Leverage the routes to find natural locations for gatekeepers.
      1. Login Screens (the perfect location)
      2. Tutorial Screens (A natural context fit for users)
      3. An Advanced Mode (Similar to the “enriched mode” Microsoft uses for ActiveX requiring cloud apps).
    • If you’re lucky, this will be a screen somewhere beyond your conversion pipeline.
  4. Create an interception event for IE users.  You can bake this into your app, but the easy way is with conditional comments.
    1. Identify the Dom Nodes in your layout that must be presented to the user for them to progress.
    2. <div id=’loginbox’>…</div>
    3. Create a conditional comment to hide the nodes and prompt the user to install Chrome Frame.
    1. Be careful of z-index.  The chrome install iframe doesn’t work well if IE is floating some other content node on top of the install button.
    2. UN-INSTALL chrome frame once you’ve tested your retreat perimeter.  This is a big deal for ongoing development as you won’t be testing in IE otherwise.
    3. Becareful about your conversion pipeline.  Your key selling points may be behind the perimeter, but the act of demanding Chrome Frame is a pretty severe barrier to forward movement.
  5. <html>
    <body> <div id='prompt'></div>
    <!--[if IE]>
    <script type="text/javascript"
    src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
    <style>
    #loginBox{
    display:none;
    }
    </style>

    <script>

    window.attachEvent("onload", function() {
    CFInstall.check({
    mode: "inline",
    }); });
    </script> <![endif]-->
    </body>
    </html>

    Other Lessons Learned

    Here’s a few lessons learned from deploying this.

     

Strategically Retreating to Chrome Frame

Posted by Don Albrecht

About a month ago, I found myself In a terrible debugging situation. A web app that I had carefully QA’d in both an IE 8 and IE9 environment was receiving a slew of bug requests from users. Some of these we’d expected. The use of several minor CSS3 tweaks meant that the visual experience was intentionally degraded in IE, but many of these we hadn’t.

Script timeout errors popped up randomly throughout the enterprise.
IE 666 users complained and I wasn’t allowed to take appropriate action.
Layout quirks surfaced.

Even more troubling was the discovery of radically different layout interpretations between builds of IE 8. My testing had proved inadequate because minor build number differences were interpreting the css in radically different ways. Since the app had managed to make it into production with these issues, and fully repairing all of them looked to be a slog I didn’t have the temporal luxury of pursuing; I punted. I threw up conditional comments to repair the issues that were preventing casual browsing. Then I hid the login box for IE users; replacing it instead with a Chrome Frame install prompt.

The results were immediate. My complaint volume dropped astoundingly. More importantly, the number of unresolved Scorchers went to zero. At the same time, no one complained about needing to install Chrome Frame. I had a few issues with insufficient permissions, and a few lingering CSS tweaks that needed rolled out, but the ease of the fix really was astounding.

I’ve since retrofitted most of my production apps and web servers to take advantage of my users newly capable Microsoft Browsers and plan on making the strategic retreat a deployment strategy for many new projects. While I don’t plan on abandoning support for IE, I look forward to minimizing its impact on project risk.

Note:  This post was updated on 2011.6.16 due to a mistake in my account.