Why you need it
Like many other websites out there, Obsidian Portal arranges its content into multiple columns. We have the main content in the center column, and ancillary content in the right sidebar. It’s a fairly standard organization scheme and it works well for us.
Normally, this sort of organization is handled in Rails via the use of layouts. One layout may contain the sidebars for your homepage, another one holds the sidebars for the user’s profile page, and so on. In the beginning, this works.
However, once your application reaches a certain level of complexity, this approach begins to show its true inflexibility. In many cases, you will have some sidebars that are common to every page, such as a login or search bar. You can split these into a partial, but you still have to include that partial in every layout. Not a big deal, but frustrating. Soon after this, you’ll run into cases where you need different sidebars based on the particular action for a controller. Perhaps there are some standard info sidebars for showing and listing, but for editing and creating you want a sidebar that has some editing tips. Now, if you’ve been using the convention for layouts, you probably have a layout for each controller that is named the same as the controller. That way, Rails will automatically select that layout. To support the different sidebars for different actions, you’ll have to clutter that layout up complicated if/then conditionals. Very quickly the template contains more Ruby than HTML.
Trust me, we went this route initially, and it became a nightmare. Here is one of our layouts, prior to SimpleSidebar:
< %= render :partial => “layouts/header” %>
< % if controller.action_name == 'show' %>
GM: < %= link_to @campaign.game_master.login.capitalize, :controller => “account”, :action => “profile”, :login => @campaign.game_master.login %>
-
< % players_with_pcs = [] %>
-
< %=h pc.author.login %> (
< %= link_to h(pc.name), :controller => ‘game_contents’, :action => ‘show’, :id => pc._id %>
)
< % players_with_pcs << pc.author %> - < %=h player.login %>
< % for pc in @campaign.player_characters %>
< % end %>
< % for player in @campaign.players %>
< % if !players_with_pcs.include?(player) %>
< % end %>
< % end %>
< % end %>
< % if (controller.action_name == 'list') or (controller.action_name == 'search') %>
< %= render(:partial => ‘search/search_bar’, :locals => {:search_type => ‘Campaigns’, :search_controller => ‘campaigns’, :search_action => ‘search’}) %>
< % end %>
< % end %>
< % if flash[:warning] %>
< % end %>
< % if flash[:notice] %>
< % end %>
< %= yield %>
< %= render :partial => “layouts/footer” %>
Ignoring the fact that I’m terrible at writing templates, you can see there’s a fair amount of if/then conditional code sprinkled in. The Login sidebar is supposed to be there always, the Description and Party sidebars are only supposed to appear in the show action, and the Search bar is only supposed to be there in the list or search action. Ugh! And, this is only one of several layouts for several controllers. Each one had this sort of conditional garbage all over the place.
With SimpleSidebar, much of this conditional code moves into your controllers. Much like before and after filters, you define sidebars for a controller (or a particular action) and can give conditions on when they appear.
For example, using the previous example, with SimpleSidebar, I added the following to my controller:
sidebar :login, :unless => :logged_in?
sidebar :welcome, :if => :logged_in?
sidebar :campaign_description,
nly => :show
sidebar :campaign_party_info,
nly => :show
sidebar :campaign_search,
nly => [:list, :search]
This allowed me to reduce my layout to the following:
< %= render :partial => “layouts/header” %>
< % end %>
< % if flash[:warning] %>
< % end %>
< % if flash[:notice] %>
< % end %>
< %= yield %>
< %= render :partial => “layouts/footer” %>
A little nicer, huh? If you look closely, there is nothing in there specific to any particular controller or action. Once I converted all my controllers over to SimpleSidebar, I noticed that all my layouts were identical, meaning I was able to delete most of them and just keep a single standard layout.
How to use it
SimpleSidebar is incredibly easy. I was up and running in about ten minutes (including the vendor drop). I was finished refactoring all my ugly templates in about 1.5 hours. Not bad for a major refactor.
Note: These instructions are for revision 13 of the plugin. Beware if you’re using a different version (although it probably won’t change that much).
1. Get the plugin
As always, it’s very easy to install plugins. Just fire up your plugin installer
script/plugin install svn://rubyforge.org/var/svn/simplesidebar
Likewise, as always, we here at AisleTen suggest you skip the plugin installer and do it manually using vendor drops. Just be sure to execute the install.rb when you’re done.
2. Update your controllers
This is where most of the magic of SimpleSidebar occurs. Rather than dealing with sidebars and their conditional display in the view layer, SimpleSidebar moves it to the controller layer. For example, your controller might look like this:
def MyController < ApplicationController
sidebar :login, :unless => :logged_in?
sidebar :welcome, :if => :logged_in?
sidebar :search
sidebar :edit_help,
nly => [:edit, :new]
…
end
If you’re familiar with filters, then this probably looks very familiar. When an action is executed in this controller, the following will be run:
- If the
logged_in?method returnsfalse, then theloginsidebar is appended to the list of sidebars to display. - If the
logged_in?method returnstrue, then thewelcomesidebar is appended to the list of sidebars to display. - The
searchsidebar is appended to the list of sidebars to display. - If the current action is
editornew, then theedit_helpsidebar is appended to the list of sidebars to display.
3. Add the partials.
If you’ve been good, you already have all your sidebars as partials. If not, it’s time to refactor as such. The partials need to be named the same as the sidebars listed in your controllers. Using our previous example, we will need four partials:
_login.rhtml_welcome.rhtml_search.rhtml_edit_help.rhtml
These partials must be placed in the app/views/sidebars directory.
4. Update your layout.
Inside your layout, the code becomes very simple. All you have to do is add <%= render_sidebars %> wherever you want the sidebars displayed.
I highly recommend extracting this to your layouts. That way, rather than calling render_sidebars in each template you render, you just put it into the layout and know it gets called every time the layout is rendered around your template.
5. Delete all the layouts no longer needed.
Assuming you’re like me, you’ll quickly find that many of your layouts are no longer needed. Feel free to consolidate them as appropriate.
Advanced Features
SimpleSidebar also contains some advanced features that I have yet to experiment with, but I’ll still give a quick overview.
Components
If I understand it correctly, components are sidebars that also have an associated controller. This is a very exciting feature, as it could allow you to execute multiple controllers for a single request.
A good example of where this is appropriate is for dynamic sidebars, such as the “Recently Updated Campaigns” sidebar you see on the Obsidian Portal homepage. I need to execute a database query (find) in order to get this. Currently, I’m just doing it inside the partial, which is not really what you’re supposed to do. When I get a chance, I’ll see if I can refactor this and use a component to move the database querying into a controller and make the partial a dumb template.
Sidebar ordering and sorting
In the previous examples, sidebars were ordered simply by their place in the controller. However, if you need more advanced functionality, SimpleSidebar supports ordering and sorting.
Moving sidebars is accomplished like this:
sidebar_move :login, :top
sidebar_move :search, :bottom
sidebar_move :search, :up
sidebar_move :login, :down
Easy enough…
Sorting looks a little more complicated. If I figure it out (or ever have a need for it), then I’ll update how it’s done. Until then, just look in the plugin source for sidebar_sort.
Issues
The only issue I can raise against SimpleSidebar (and it’s a long shot) is that by design it mixes controller and view functionality. I think sidebars are an aspect of the view, but with SimpleSidebar you deal with them in the controller.
In all seriousness, though, this is like whining over the color of the inside of the glove box in a luxury car. In no way does it take away from the sheer awesomeness of the item in question.
Final thoughts
After my brief introduction, I am completely in love with SimpleSidebar. It has quickly made its way into my bag of permanent plugins for all Rails projects. In any future project, it will be the one I install right after acts_as_authenticated. Yes, it’s that awesome.
June 6th, 2007 at 12:50 pm
Great work DRYing up code that desperatly needed it! It looks like you’ve found a pretty elegent solution, but part of me sees this as a little to much overlap between the controller and the view layer. I haven’t run into this problem thus I don’t really know of a way to better seperate concerns. Adding another layer between the controller and the view might be the answer. It seems like you want to display certain snippets according to some state. Maybe a StatePattern between the controller and view, Oh well, its not that big of a deal anyways. Sweet plugin man!
June 6th, 2007 at 12:57 pm
@Eric,
Just to be 100% above-board, I should say that I didn’t write the plugin. That honor goes to someone else. I’m not sure, but I think his name is Mathew Abonyi.
Also, I agree that it seems like too much overlap between the view and controller layer, but once I used it and saw how much code I was able to cut out, I gave up caring.
June 13th, 2007 at 6:22 am
I dont have such a situation as of now. But anyways the plugin is nice.
July 2nd, 2007 at 6:49 am
Great right up I actually found the plugin about five minutes before I found this post and thanks cause it answered some of the questions I had in order to get the plugin functioning correctly on my app. Any way thank for the great articles and tutorials they have helped me a lot enough to bookmark the site as a reference.
July 2nd, 2007 at 6:56 am
Thanks for the comment and bookmark, Mitchell. Don’t forget to add us to your feeds. That’s the best way to know when we put up new articles.
July 12th, 2007 at 7:28 am
I have a couple of questions with this plugin:
1)how to tune the look of sidebar links?
2)how to make nested sidebars? (close or expand maybe)
Thank you very much for any help!
November 30th, 2007 at 10:46 am
[...] http://blog.aisleten.com/2007/06/03/simplesidebar-if-you-have-sidebars-you-need-this-plugin/ [...]
January 26th, 2008 at 12:32 am
[...] about layouts in Ruby on Rails. As suggested in the Midnight Oil blog (Beds are burning?) article SimpleSidebar – If you have sidebars, you need this plugin I now use SimpleSidebar in The Project. At first it didn’t work at all, but updating [...]
January 30th, 2008 at 11:31 am
Nice write up. Thanks.
One note, I would argue that the job of the controller is to return the proper view. In the case of this plugin and non standard layouts, the controller determines which to use based on the request criteria. Other frameworks handle the overlap between the View and the Controller with a Presenter (and you can emulate that in rails), but within Rails’s paradigm, this does conform to MVC.
March 7th, 2008 at 2:02 am
Has anyone tried this plugin with the new file extensions in Rails 2.0? Partials appended with .html.erb don’t seem to work for me – I had to revert to rhtml.
March 10th, 2008 at 5:45 am
Neil,
We’re using the plugin with Rails 2.0, but we haven’t renamed all the partials. That’s because we wrote the majority of the code for 1.2.3 and just recently updated.
March 13th, 2008 at 9:08 am
Neil,
We used this blog post to create a rake task to both check for any deprecations and also to rename all views from .rhtml to .html.erb.
http://www.slashdotdash.net/articles/2007/12/03/rails-2-upgrade-notes
Also, I’ve submitted a patch to simple sidebar for adding Rails 2.0 support:
http://code.google.com/p/mabs29/issues/detail?id=4