Announcing 'Browserized Styles'

Browser compatibility, the web designer’s nightmare, has always seemed more difficult than it has to be. Why hasn’t there been an industry-standard, simple way to target CSS to specific browsers, allowing one to style the page properly without worrying about hacks and other difficult ways of pulling all the information together? I thought that something should be done about it, so taking Richard Livsey’s ‘browser_detect’ plugin as a starting point, I developed an automatic solution for including browser-specific stylesheets.

Browserized Styles provides a dead simple way to create browser-specific CSS code for use in a Rails application. All you need to do is create a .css file targeted to a browser by appending an underscore and identifier to the end.

Installation


script/plugin install http://svn.intridea.com/svn/public/browserized_styles

Example

Let’s say I have some complex CSS code that looks bad in some browsers, but works in others. Let’s also say that i’ve put it into a stylesheet in stylesheets/complex.css.

My stylesheet link tag looks something like this:


<%= stylesheet_link_tag 'complex' %>

Now all I have to do to target a browser is create a new CSS file with the browser’s identifier appended to it with an underscore (e.g. “complex_ie6.css”). That’s it! The same exact stylesheet link tag will automatically check the current user agent and load a browser-specific CSS file if it exists. Ta-da! One-step browser styles!

More information is available in the readme, but the end result is browser-targeting bliss.

The plugin is brand new and will probably see some modifications in the future. If you run into any problems or come up with a patch, feel free to submit it to the Intridea Public Trac .

Share:

Comment on this post (0 comments)


DRYing markup using block-accepting helpers

Posted by on October 19th, 2007.

As much as we all strive to write perfect, semantic XHTML that can be styled entirely independently, the reality of browser compatibility and even our own creative choices often leads to design refactoring. Obviously in Rails one big help for this is the use of partials, making sure that you can change your markup in one place and it will be reflected for often-repeated content. But what about for lower-level, underlying layout structure? Nested partials quickly become a mess to deal with, and traditional helpers don't always offer the flexibility and intelligence that is needed to get the job done.

Enter block-accepting helpers. These allow you to wrap markup around content without the ugliness of a my_thing_start and my_thing_end. With a little more work, they can also provide an intelligent layout structure that requires minimal (and beautiful) code in the view. Let's start with a simple example. Lets say that most, but not all, of the pages in my app have a similar container and title. Creating multiple layouts using partials for everything just to add or not add a couple of lines of code is way too heavy. On the other hand, my designers want the flexibility to change how the container is represented in markup, so I can't just throw the markup into each action. Here's the markup I want (for now):

<div class='main_container'>
    <h3><!-- Some Title --></h3>
    <!-- Some Contents-->
</div>

This is obviously pretty basic, and wouldn't be a big deal to change if the design changed, but this is just an example. If you're dealing with some kind of nested div rounded corner solution, or any number of other necessary CSS/markup hacks to get the look you desire, it could become quite a chore. So how do we create our DRY helper? Primarily through the use of the concat method in the ActionView::Helpers::TextHelper module. Here's my helper (just put this in either the relevant helper or ApplicationHelper:

def main_container(options = {}, &block)
  concat("<div class='main_container'>",block.binding)
  concat("<h3>#{options[:title]}</h3>",block.binding) unless options[:title].blank?
  yield
  concat("</div>",block.binding)
end

So that wasn't so bad, was it? Now in our views all we have to do to get our main_container is:

<% main_container :title => 'My Page Title' do %>
    <p>We can put whatever we want here, and it will appear inside the container.</p>
<% end %>

Cool, huh? There are other resources that dig much deeper into blocks, procs, and the ruby wonderfulness going on behind the scenes, but this is purely a functional demonstration. Suffice to say that placing a &block (or &proc or whatever you want to call it) as the last parameter of a method allows that method to accept a block and later yield to it. The yield statement works much the way it does in layouts, passing code execution for a time and then getting it back when the yielded code is finished.

Do make note that, like form_for, you do not use the <%= but just <% when declaring your block. This is because you aren't returning anything, but rather directly concatenating code onto the binding passed when you make the block call.

Now that was certainly useful, but doesn't really have that 'wow' factor that really makes you appreciate a new way of doing things. Let's take a stab at another common problem in app design: menu systems. Using block-accepting helpers and introducing the concept of a builder class, we can build menus intelligently and easily.

The implementation you are probably already familiar with of this type of class is the FormBuilder used in form_for. We will take that same concept and apply it to menus on a web application, taking care of a number of common headaches associated with menus. Here's the whole enchilada, and we'll go through it piece by piece. This code should be dropped into a helper:

def menu(options = {}, &block)
  id = options[:id] || 'menu'
  concat("<div id='#{id}'><ul>",block.binding)
  # we yield a MenuBuilder so that specialized methods 
  # may be called inside the block
  yield MenuBuilder.new(self)
  concat("</ul></div>",block.binding)
end

class MenuBuilder
  def initialize(template)
    # by passing in the ActionView instance, we gain 
    # access to all of its helpers through @template
    @template = template
    @action_count = 0
  end

  def action(name, options = {}, html_options = {})
    # create a local array for the classes we will be tacking on to the list item
    classes = Array.new
    # add the 'current' class if the current page matches the link options
    classes << 'current' if @template.current_page?(options)
    # add the 'first' class if this is the first time we've added an action
    classes << 'first' if (@action_count == 0)
    @action_count += 1
    "<li class='#{classes.join(' ')}'>#{@template.link_to(name, options, html_options)}</li>"
  end
end

So that should look at least somewhat familiar, right? Here's a sample implementation so that we can talk about what's going on:

<% menu do |m| %>
  <%= m.action 'Home', home_path %>
  <%= m.action 'Blog', blog_path %>
  <%= m.action 'Account', edit_user_path(current_user) %>
<% end %>

So in just a few readable lines of code, we've done all we need for a pretty complex menu. However, because of our helper, all of the complexities are going on behind the scenes! So let's talk about our helpers. The actual helper that we call is very similar to the one we made earlier with one difference: rather than yielding with no parameters, we yielded a MenuBuilder object, which was then called in the yielded code to create menu actions. A builder can be used when there needs to be state information or more complex logic than simple helpers can provide.

In this case, we wanted to make sure that an action was marked as the first action if it was indeed the first to be called. By implementing an instance variable to count the number of actions that had been called, we are able to do this with ease. Additionally, if we ever wanted to change how the menu items were rendered, all of the changes are completely contained in the helper and builder, leaving the views unchanged.

I've found it a useful practice to stub out block-accepting helpers for any markup that I think I'll be using frequently at the start of an application. This gives the developers a chance to keep going while the design can evolve on a completely separate track. Additionally, it creates some fantastically readable code in the views.

Share:

Comment on this post (1 comment)