Dynamic Loading of Print Stylesheets with YUI

        <p>By and large, people don&#8217;t want to send to the printer the exact same thing as they see on the screen.  The screen display is often full of ads is difficult locations, has sidebar and navigation information that isn&#8217;t relevant to the printed page.  For this reason alone, it makes a lot of sense to offer a print-optimized stylesheet for users who opt to print your content, and luckily this has become pretty easy.  Virtually every browser in use today supports the &#8216;media&#8217; attribute on the link tag (the tag used to include remote stylesheets), which, if set to &#8216;print&#8217; will only be used when the user goes to Print, or Print Preview, one of your pages.</p>

The problem with this is that the CSS still follows the loading rules that all CSS pages are beholden to, meaning that if the print stylesheet is going to override anything it needs to be the last page loaded, even though it won’t be used to display anything. On a site like the new WSU Catalog, where we will display a different CSS based on the options for the page the user is visiting, specifically the Campus of interest, this leads to an interesting problem. Namely, Firefox 2 does not allow you to set the media attribute on a link tag dynamically, once the CSS has loaded.

So, what do I mean by this? Well consider the following code, using the Yahoo User Interface (YUI) Get Utility:

    var urls = [
        "http://designer.wsu.edu/template/css.aspx?key=11f14af0axx081",
        "http://www.wsu.edu/navigatewsu/gen2/print.css"
    ];

    YAHOO.util.Get.css(urls,
    { onSuccess: function(o) {
        for (var i = 0; i < o.nodes.length; i += 1) {
            if (o.nodes[i].href.indexOf('print') > 0) {
                o.nodes[i].setAttribute('media', 'print');
            }
        }
    }
    });

In the above JavaScript, I’m loading a WSU Dynamic CSS Template, as well as the official WSU Print template. Once the CSS is loaded, I find the print CSS, and set it’s media attribute to print. This works on IE 6/7, and Firefox 3. However, on Firefox 2, the Print CSS is displayed, even when out of printing mode. Not willing to stop there, I ran a test, where I build the <link> tag directly and inserted it with the media attribute already set, and sure enough it worked.

This implied to me, that YUI could be extended to support Firefox 2 in this manner. The question was how? I see two answers to that question.

First, would be to allow an optional object associated with each URL, which was an associative array of attributes to apply. The second would be to allow the object that Get.css takes to accept a set of attributes to apply. The first one is potentially confusing, the second requires more code. Ultimately, this probably means the second one is better. Also, both sets of changes requiring hosting the modified Get utility yourself, as you can’t simply replace the necessary code at run-time.

But, for the sake of example, let’s talk about what the first one would require. To the developer, the interface would look like this:

    var urls = [
        "http://designer.wsu.edu/template/css.aspx?key=11f14af0axx081",
        ["http://www.wsu.edu/navigatewsu/gen2/print.css", { "media": "print" }]
    ];

    YAHOO.util.Get.css(urls);

It’s not much of a change, and the code is fully backwards compatible with the old way, but it’s not quite as obvious what the intent is. For the second method the external interface would look like this:

    YAHOO.util.Get.css("http://designer.wsu.edu/template/css.aspx?key=11f14af0axx081");
    YAHOO.util.Get.css("http://www.wsu.edu/navigatewsu/gen2/print.css",
    { "attributes": { "media": "print" }
  });

Method 1: URLs with Attributes

The necessary code changes are visible in the following unified diff):

--- get-debug.js    2008-06-18 08:18:27.939931500 -0700
+++ get-debug-changed.js    2008-08-28 11:34:24.478309800 -0700
@@ -87,15 +87,15 @@
      * @return {HTMLElement} the generated node
      * @private
      */
-    var _linkNode = function(url, win, charset) {
-        var c = charset || "utf-8";
-        return _node("link", {
+    var _linkNode = function(url, win, custom_attrs) {
+           var attrs = custom_attrs || { };
+           attrs.charset = attrs.charset || "utf-8";
+        return _node("link", lang.merge({
                 "id":      "yui__dyn_" + (nidx++),
                 "type":    "text/css",
-                "charset": c,
                 "rel":     "stylesheet",
                 "href":    url
-            }, win);
+            }, attrs), win);
     };

     /**
@@ -106,14 +106,14 @@
      * @return {HTMLElement} the generated node
      * @private
      */
-    var _scriptNode = function(url, win, charset) {
-        var c = charset || "utf-8";
-        return _node("script", {
+    var _scriptNode = function(url, win, custom_attrs) {
+           var attrs = custom_attrs || { };
+           attrs.charset = attrs.charset || "utf-8";
+        return _node("script", lang.merge({
                 "id":      "yui__dyn_" + (nidx++),
                 "type":    "text/javascript",
-                "charset": c,
                 "src":     url
-            }, win);
+            }, attrs), win);
     };

     /**
@@ -245,12 +245,18 @@


         var url = q.url[0];
+        var custom_attrs = {};
+        if (lang.isArray(url)) {
+           custom_attrs = url[1];
+           url = url[0];
+        }
+        custom_attrs.charset = q.charset;
         YAHOO.log("attempting to load " + url, "info", "Get");

         if (q.type === "script") {
-            n = _scriptNode(url, w, q.charset);
+            n = _scriptNode(url, w, custom_attrs);
         } else {
-            n = _linkNode(url, w, q.charset);
+            n = _linkNode(url, w, custom_attrs);
         }

         // track this node's load progress

It’s a fairly small number of code changes, but it does change the internal structure fairly significantly. Some of these changes are unavoidable in trying to solve this problem, other may be.

Method 2: Attributes for All

The diff in question:

--- get-debug.js    2008-06-18 08:18:27.939931500 -0700
+++ get-debug-otherchanges.js   2008-08-28 12:41:30.457239000 -0700
@@ -87,15 +87,16 @@
      * @return {HTMLElement} the generated node
      * @private
      */
-    var _linkNode = function(url, win, charset) {
+    var _linkNode = function(url, win, charset, attrs) {
         var c = charset || "utf-8";
-        return _node("link", {
+        var a = attrs || { };
+        return _node("link", lang.merge({
                 "id":      "yui__dyn_" + (nidx++),
                 "type":    "text/css",
                 "charset": c,
                 "rel":     "stylesheet",
                 "href":    url
-            }, win);
+            }, a), win);
     };

     /**
@@ -106,14 +107,15 @@
      * @return {HTMLElement} the generated node
      * @private
      */
-    var _scriptNode = function(url, win, charset) {
+    var _scriptNode = function(url, win, charset, attrs) {
         var c = charset || "utf-8";
-        return _node("script", {
+        var a = attrs || { };
+        return _node("script", lang.merge({
                 "id":      "yui__dyn_" + (nidx++),
                 "type":    "text/javascript",
                 "charset": c,
                 "src":     url
-            }, win);
+            }, a), win);
     };

     /**
@@ -248,9 +250,9 @@
         YAHOO.log("attempting to load " + url, "info", "Get");

         if (q.type === "script") {
-            n = _scriptNode(url, w, q.charset);
+            n = _scriptNode(url, w, q.charset, q.attributes);
         } else {
-            n = _linkNode(url, w, q.charset);
+            n = _linkNode(url, w, q.charset, q.attributes);
         }

         // track this node's load progress

This diff is shorter, and the API is more consistent with the Get utilities old behavior, as well as the rest of YUI. This is probably the better answer to this problem. In fact the only downside is that a second call to Get.css is required, and is that really that negative?

Method 3: YUI ain’t Broken

Okay, there is actually a means to accomplish this without changing YUI, and I would be remiss to not mention it. It looks something like this.

In the Header:

    <link id="print_css" media="print" type="text/css" charset="utf-8" 
        rel="stylesheet" href="http://www.wsu.edu/navigatewsu/gen2/print.css" />

In the Script:

    YAHOO.util.Get.css("http://designer.wsu.edu/template/css.aspx?key=11f14af0axx081", { "insertBefore": "print_css");

This works fine. I only have two problems with it.

  1. I can not dynamically choose my print css with this method
  2. It does not future proof the Get utility for future optional attributes

Ultimately, I like Method 2. I think it’s the most flexible, while also the most compliant with current YUI standards. I’ve submitted these patches to Yahoo!, so we’ll see if they go anywhere, and I’m going to look into YUI 3 to see about extending this functionality there.