Skip navigation

Tag Archives: YUI

We covered a lot of ground on the MyFrontSteps project this week. We managed to migrate our Photo Management / Uploading solution on Homebook from SWFUpload and a lot of hacked together jQuery to a more elegant solution (albeit one that could still use some refactoring – but what code can’t use refactoring really). We decided to use the “still-in-beta” YUI Uploader coupled with some custom jQuery. The implementation seems much more cohesive than our previous solution but there were a few gotchas we rant into when trying to get things working properly in IE7.

YUI Uploader

I know this component is still in beta support from Yahoo, but I had given it a very brief try in our supported browser baseline (currently FF2+, Safari3+, IE7+) and it seemed to work really well. All of the YUI stuff we’re using works really well and the graded browser support and testing the YUI team puts behind their work makes implementation that much more reliable. That being said, no piece of software is perfect and we ran into a pretty frustrating bug implementing the YUI Uploader in a specific area of the application.

A flyover context menu with an embedded YUI Uploader.

A flyover context menu with an embedded YUI Uploader.

We have a number of context menus in Homebook that appear to the user on mouseover. This particular thumbnail represents the default image for a users Home, and clicking it prompts the user with a file dialog to select a new photo which is then uploaded via the YUI Uploader. When instantiated, the uploader inserts a transparent Flash object using the SWFObject plugin into the markup in the container specified. In our case we had the following markup:

    ul class='mfs-context-menu invisible' id='default-image-menu'
        li
           a
             span class='uploader' id='upload-profile-picture' /span
             'Change Profile Picture'
          /a
       /li
    /ul

We use a class of invisible to set the css property ‘display:none;’ on the ‘ul’ so it’s hidden by default when the page loads. When the page loads and the script triggers, the uploader sits nicely in the span tag directly above the link in the li tag. The user then clicks on the transparent flash object and the upload can start. Now, this approach works fine in every browser EXCEPT IE7. For some reason when the uploader is instantiated in IE7 if it becomes hidden and then shown again at any point the YUI Uploader loses all binding to the functions that trigger submission to the server side url. I tried doing some searching to see if this was documented anywhere and came up empty everywhere I looked.

My best guess is that the IE7 security model doesn’t like the fact that an ’embed’ tag with the Flash object is being manipulated from JavaScript and decided to turn off something that disables the uploader from completing an upload. The file dialog will still popup, and the embedded Flash remains in the DOM but the uploader doesn’t trigger sending of the files to the server. It took us a while to figure out what was going on, and the only way we tracked it down was by a process of elimination commenting out lines of code in the uploader instantiation script until we discovered it was our context menu show/hide trigger that made the uploader behave this way.

So, the solution in our case was to move the uploader ‘span’ tag outside of the ‘li’ but still inside the thumbnail container, give it a fixed width and height directly above the image (like a transparent rectangle) and bind mouseover/mouseout events to it that triggered show/hide of the ‘li’ element with the ‘a’ inside:

    ul class='mfs-context-menu invisible' id='default-image-menu'
        span class='uploader' id='upload-profile-picture' /span
        li a 'Change Profile Picture' /a /li
    /ul

This way the flash element never gets hidden, and IE7 doesn’t do anything funky to it so the uploader remains operational.

Advertisements

Brett and I recently began refactoring a significant amount of the JavaScript that is currently in MyFrontSteps and Homebook. One of the areas we identified as needing improvement was controlling when scripts get loaded in the page; it’s a challenging subject especially when utilizing Django templates which can extend and include bits of HTML that are both static and dynamic. We’re not finished the refactoring quite yet but I thought it would be valuable to blog about the lessons we’ve learned early on about how to manage JavaScript loading without having script tags all over the place.

Manual Dependency Management is Hard

Not too long ago, Yahoo put out a list of guidelines that web developers can use to enhance the performance of their websites. I won’t get into it in this post but there is a Firefox plugin called YSlow available that can automate some of the performance checking. The recommendation I’m going to focus on here is the one regarding moving scripts as close to the bottom of the page as possible. I’ll just quote the Yahoo doc as they explain it very clearly:

The problem caused by scripts is that they block parallel downloads. The HTTP/1.1 specification suggests that browsers download no more than two components in parallel per hostname. If you serve your images from multiple hostnames, you can get more than two downloads to occur in parallel. While a script is downloading, however, the browser won’t start any other downloads, even on different hostnames.

In some situations it’s not easy to move scripts to the bottom. If, for example, the script uses document.writeto insert part of the page’s content, it can’t be moved lower in the page. There might also be scoping issues. In many cases, there are ways to workaround these situations.

In the case of the code we’re using on MyFrontSteps we had all kinds of Django templates with includes and templatetags that include other little bits of dynamic JavaScript that was adding markup to the DOM and manipulating DOM content. Some of this code would appear prior to certain dependent scripts being loaded which created a real nightmare for us as we had to manually track down the position of the dependent scripts in the page that was fed to the browser after a variable number of layers of templates and includes. We decided to research a better way to manage all these scripts and since we had been using the YUI library for many other parts of the website we settled on the YUI loader.

YUI Loader Makes Dependency Management Easy

The YUI Loader Utility is a client-side JavaScript component that allows you to load specific YUI components and their dependencies into your page via script. YUI Loader can operate as a holistic solution by loading all of your necessary YUI components, or it can be used to add one or more components to a page on which some YUI content already exists.

The great thing about the YUI Loader is that you can use it to manage dependency sorting for all of the YUI components you use on a page but the killer feature imho is the fact that it allows you to create custom modules to load your own JS and CSS libraries. The basic pattern we’re using in our root level Django template is as follows:

  1. Load all CSS Files at the top of the template
  2. Place all YUI Loader and other statically included scripts at the bottom of the page prior to the closing BODY tag
  3. Define the YUI Loader instance
  4. Define custom modules for the YUI loader
  5. Provide a Django template block to allow manipulation of the onSuccess callback
  6. Call the YUI Loaders .insert() method to trigger insertion of all dependency sorted scripts into the DOM

The advantage to using the loader in conjunction with our root Django template is clear; all of our scripts are loaded at the bottom of the page and CSS is loaded at the top. When a user requests the pages they will start receiving the content from the server immediately and not have to wait for scripts to process as the loader dynamically inserts them into the HEAD element after the page content has been rendered. Another trick we’re using to control when scripts get executed is by manipulating how the onSuccess callback of the loader works. Here’s the code:

// Instantiate and configure YUI Loader:
MFSLoader = new YAHOO.util.YUILoader({
    base: "",
    require: ["base","reset-fonts-grids","MFS"],
    loadOptional: false,
    combine: true,
    filter: "MIN",
    allowRollup: true,
    onSuccess: function() {
        while( MFSLoader.onReady.length )  {
            MFSLoader.onReady.shift()();
        }
    }
});

// Define a list of executables that we
// can add to prior to page load completion
MFSLoader.onReady = [];

// Custom Modules for Loader
MFSLoader.addModule({
    name: 'MFS', type: 'js', varName: 'MFS',
    path: '{% vurl "/static/script/MFS.js" %}',
    requires: ['jQuery', 'jQueryUI']
});

{# Override this block if you need script at the global level. #}
{% block global.script %}{% endblock %}
// Trigger insertion of all dependency sorted scripts into the DOM
MFSLoader.insert();

As you can see we have a few of the custom modules defined here, which include a ‘requires’ attribute in the configuration object. This lets the YUI Loader know that these files require the other modules to be loaded. The Loader then determines sort order for inclusion based on all required modules and goes to work inserting the scripts for you.

Tying it all Together

The key to making this work in the varying levels of Django templates is to manipulate the onSuccess callback. When the YUI Loader finishes loading all the dependency sorted scripts it will execute this callback and allow you to then execute any additional code that depends on the dynamically inserted scripts. We have a number of namespaced JS objects for MyFrontSteps (MFS.js, MFS.DataGrid.js etc..) and when we need to call functions defined in these objects we do so by extending the global.script block in our child template and pushing a new anonymous function onto the MFSLoader.onReady list we defined in the root template. Here’s a simple example:

{# A CHILD TEMPLATE #}
{% block global.script %}
    #include the parent templates global.script contents
    {{ block.super }} 

    // Load our required features
    MFSLoader.require( 'MFS.DataGrid', 'uploader', 'MFS.Uploader' );
    // Push an anonymous function onto the list
    // to get executed when all required scripts have been loaded
    MFSLoader.onReady.push( function()  {
        MFS.DataGrid.initPhotosGrid();
    });
{% endblock %}

Once the page is rendered and the YUI Loader has finished parsing the loaded scripts the code we have in the onSuccess callback pops all the anonymous functions off the list which causes them to be executed.

{# THE ONSUCCESS CALLBACK #}
onSuccess: function() {
    while( MFSLoader.onReady.length )  {
        MFSLoader.onReady.shift()();
    }
}

We still have a number of files to refactor, but the performance benefits we’ve seen so far using this approach have really made us confident that this is the way to go. Using a dependency manager that works for custom JS / CSS libraries is just so much more liberating than having to manually keep track of where scripts are getting executed. Because we’re also migrating all of our code to external library files it makes things that much easier to debug in Firebug as well. 🙂