web development

Different content in Rails based on UserAgent

I was recently working on a website built using Rails that needed to render different content for certain user agents. Specifically, we needed simpler versions of certain pages for BlackBerry devices. Here's how I accomplished.

First, I added a new mime-type for BlackBerry by adding the following line to config/initializers/mime_types.rb:

Mime::Type.register_alias "text/html", :blackberry

Next, I added two utility methods to app/controllers/application.rb:

# Checks UserAgent
def is_blackberry?
  ua = request.user_agent
  return false if ua.nil?
  return false if ! ua.downcase.index('blackberry')
 
  # Don't call the BlackBerry 9800 a BlackBerry, since it has a modern browser
  # based on WebKit:
  # Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, Like Gecko) Version/6.0.0.141 Mobile Safari/534.1+
  return false if ua.downcase.index('webkit')
 
  # Must be a BlackBerry!
  true
end
 
# Sets the respond_to format to blackberry if blackberry
def set_blackberry_format
  if !request.xhr? && is_blackberry?
    request.format = :blackberry
  end
end

With that in hand, it's easy to render BlackBerry specific content on specific pages:

set_blackberry_format
respond_to do |format|
  format.blackberry
  format.html
  format.js { render :layout => false }
end

FlashVars broken in IE8

Unsurprisingly, Internet Explorer 8 broke yet another feature of the web. This time, the folks at Redmond broke how Internet Explorer passes the flashvars parameter into Flash. Typically, when placing a Flash object on an HTML page, you use the following syntax:

<object id="flv" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="692" height="516" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" align="middle">
 <param name="allowScriptAccess" value="sameDomain" />
 <param name="allowFullScreen" value="true" />
 <param name="quality" value="high" />
 <param name="bgcolor" value="#424242" />
 <param name="FlashVars" value="name=value" />
 <param name="wmode" value="transparent" />
 <embed type="application/x-shockwave-flash" width="692" height="516" src="http://www.example.com/mysample.swf" name="flv" allowfullscreen="true" allowscriptaccess="sameDomain" wmode="transparent" bgcolor="#424242" quality="high" flashvars="name=value" pluginspage="http://www.macromedia.com/go/getflashplayer" align="middle"></embed>
 <param name="movie" value="http://www.example.com/mysample.swf" />
</object>

The OBJECT tag is used by Internet Explorer and the EMBED tag is used by everyone else (Firefox, Safari, etc.). Although the flashvars paramter is not formally described in the HTML 4.0.1 spec1, this code worked fine with IE 6 and IE 7. Unfortunately, IE 8 does not pass flashvars into the Flash Player. The only work around is to pass the flashvars parameter as part of the movie name parameter, as shown below:

<object id="flv" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="692" height="516" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" align="middle">
 <param name="allowScriptAccess" value="sameDomain" />
 <param name="allowFullScreen" value="true" />
 <param name="quality" value="high" />
 <param name="bgcolor" value="#424242" />
 <param name="FlashVars" value="name=value" />
 <param name="wmode" value="transparent" />
 <embed type="application/x-shockwave-flash" width="692" height="516" src="http://www.example.com/mysample.swf" name="flv" allowfullscreen="true" allowscriptaccess="sameDomain" wmode="transparent" bgcolor="#424242" quality="high" flashvars="name=value" pluginspage="http://www.macromedia.com/go/getflashplayer" align="middle"></embed>
 <param name="movie" value="http://www.example.com/mysample.swf?name=value" />
</object>

 

Notes:

1 http://www.w3.org/TR/html4/struct/objects.html#h-13.3.2

Embedding HTML Document in an IFRAME with GWT

While working on a recent project in GWT, I needed to embed a full HTML document inside an IFRAME. And I didn't want to specify a remote URL for the IFRAME - I actually wanted to shove the HTML content directly into the IFRAME.

Initially I thought it was trivial: create an IFrameElement and call setInnerHTML.

IFrameElement iframe = Document.get().createIFrameElement();
iframe.setInnerHTML(htmlContent);

Unfortunately, that doesn't work. It doesn't cause any errors, but it doesn't actually fill in the iframe. Instead, you have to use native javascript to write into the document object for the iframe:

private final native void fillIframe(IFrameElement iframe, String content) /*-{
  var doc = iframe.document;
 
  if(iframe.contentDocument)
    doc = iframe.contentDocument; // For NS6
  else if(iframe.contentWindow)
    doc = iframe.contentWindow.document; // For IE5.5 and IE6
 
   // Put the content in the iframe
  doc.open();
  doc.writeln(content);
  doc.close();
}-*/;

Voila! However, one of the side effects is that the iframe doesn't include any CSS unless your embedded HTML references a stylesheet. If you want to manually add a reference to a specific stylesheet, you can do that through native javascript as well:

private final native void addHeadElement(IFrameElement iframe, String cssUrl) /*-{
  setTimeout(function() {
 
    var body;
    if ( iframe.contentDocument ) { 
      // FF
      iframe.contentDocument.designMode= "On";
      iframe.contentDocument.execCommand('styleWithCSS',false,'false');
      body= iframe.contentDocument.body;
    }
    else if ( iframe.contentWindow ) {
      // IE
      body = iframe.contentWindow.document.body;  
    }
 
    if (body == null) {
      return;
    }
 
    body.className = "custom-body-classname";
    var head = body.previousSibling;
    if(head == null) {
      head = iframe.contentWindow.document.createElement("head");
 
      iframe.contentWindow.document.childNodes[0].insertBefore(head, body);
    }
    var fileref = iframe.contentWindow.document.createElement("link");
    fileref.setAttribute("rel", "stylesheet");
    fileref.setAttribute("type", "text/css");
    fileref.setAttribute("href", cssUrl); 
    head.appendChild(fileref);
  }, 50);
}-*/;}

There's still one more problem. You can't use either of native methods until the IFRAME element has been attached to the DOM. The easiest way around this is to add the IFRAME element to a panel and over the onLoad() method for the panel:

final IFrameElement iframe = Document.get().createIFrameElement();
FlowPanel innerBox = new FlowPanel() {
    @Override
    protected void onLoad() {
        super.onLoad();
 
        // Fill the IFrame with the content html
        fillIframe(iframe, contentHtml);
 
        // Add a HEAD element to the IFrame with the appropriate CSS
        addHeadElement(iframe, cssUrl);
    }
};
innerBox.getElement().appendChild(iframe);

I got the idea and the important code from an article titled Inject HTML into an IFrame from Software As She's Developed.

Struts Redirect w/Parameters Part 2

In an earlier post, I described a class to handle redirects in Struts and passing parameters along. That technique is not necessary; as of Struts 1.2.7, you can use the ActionRedirect class instead.

Here's a basic example from inside the execute method in an Action.

Exception Handling Framework for J2EE Applications

OnJava has an interesting article on generic approach to handling exceptions within a J2EE framework. The basic idea involves creating a generic base class for exceptions and a basic handler for all requests that includes dispatcher to pass the request along to the appropriate method.

Syndicate content