Wednesday, May 02, 2007

CSS: CSS Browser Selector


Each browser has its quirks. Having a clear way of organizing the work arounds
for those quirks is a challenge. Using CSS selectors is not a new idea, but I thought it might be helpful to others to give an example of the technique and how we've been able to successfully deploy it for revolutionhealth.com.



First, for IE (the browser that usually requires a hack), we can rely on conditional comments. This is good because it means we don't need to depend on Javascript. For other browsers we'll have to rely on a document.write solution. For Safari, Opera, and Firefox, we rely on the script from http://rafael.adm.br/css_browser_selector/ and for IE, conditional comments.


Here's what we include at the top of our document. (The browser_detect_start partial.)



<!--[if lt IE 7.]>
<div class='ie ie6'>
<![endif]-->
<!--[if IE 7]>
<div class='ie ie7'>
<![endif]-->
<script type="text/javascript">//<![CDATA[
var d = browserCSSDetection();
if( d.browser != "ie" ){ document.write( "<div class='" + d.browser + " " + d.os + "'>" ); }
//]]></script>

And here's what we do for the end of the document. (The browser_detect_end partial.)

<!--[if IE ]>
</div>
<![endif]-->
<script type="text/javascript">//<![CDATA[
var d = browserCSSDetection();
if( d.browser != "ie" ){ document.write( "</div>" ); }
//]]></script>


The browser detection in Javascript. This could be enhanced further, but for us this allowed us to get the site working relatively easy in Konqueror. As well it enabled us to fix our menu's so that they float over flash in Linux using this techinque.



function browserCSSDetection()
{
// see: http://rafael.adm.br/css_browser_selector/
var ua = navigator.userAgent.toLowerCase();
var is = function(t){ return ua.indexOf(t) != -1; };
var b = (!(/opera|webtv/i.test(ua))&&/msie (\d)/.test(ua)) ?
('ie ie'+RegExp.$1) :
is('gecko/') ? 'gecko' :
is('opera/9') ? 'opera opera9' :
/opera (\d)/.test(ua) ? 'opera opera'+RegExp.$1 :
is('konqueror')?'konqueror' :
is('applewebkit/') ? 'webkit safari':
is('mozilla/')?'gecko':'';
// see: http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html
var os = (is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';
var css = {browser:b,os:os};
return css;
}


Finally, to make all this fit nicely into a layout:

<head>
<%= javascript_include_tag 'browser_detect' %>
</head>
<body>
<%= render :partial => "browser_detect_start" %>
<%= @content_for_layout %>
<%= render :partial => "browser_detect_end" %>
</body>


Update:
Here's a simple example of what this enables. The advantage of this over a conditionally included file is it keeps everything about the login box isolated to one place. You don't need to worry about openning up a separate file to make your IE fixes. We do use iefix specific CSS for our site, but only for very large features like menus



#login {
padding:0px;
}
.ie6 #login {
padding: 2px;
}

Plugems - Why views in plugems?


When we started to develop Plugems we had multiple applications that all shared a similar look and feel. One concept that emerged recently for me was that layouts can act like objects. In our suite of applications, we have a base layout that each application can derive from to satisfy it's unique set of presentation requirements. For example our UI plugem has the following:



ui/views/layouts/default.rhtml

Using a little magic from fora.pragprog we can extend our default layout writing the following:

<% inside_layout 'default' do -%>
<%= @content_for_layout %>
<% end -%>



As well as with layouts, there are also smaller blocks of UI that make up a page that can be shared. I've always thought of these as widgets, which in many cases can be defined as a set of partials and helpers. An application may place a widget on a view, feeding it the data needed to bring it to life. This is different from components because there is no controller logic. Really the presentation side of a widget can be expressed by a set of partials, Javascript, CSS and a healthy dose of ruby [e.g. helpers]. In this way partials are the basic building block on which many UI components can be built. By themselves they are incomplete, but within the context of an application they are alive...




Plugems enables you to share common UI widgets across multiple applications. Common assets (css, js, images) can live in a plugem, but be shared across applications at deployment time. I hope to post more in depth on this topic in near future.