Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jamesarosen/302796 to your computer and use it in GitHub Desktop.
Save jamesarosen/302796 to your computer and use it in GitHub Desktop.
README for a proposed Rails navigation helper

Tired of building custom navigation code for each site?

Have you seen Ryan Heath's Navigation Helper? It's very usable, but it only does one nav-bar. I often have sites where there are many, e.g.

+-----------------------------------------------------------+
| MyApp                           | Home | Sign Out | FAQ | | # "site" nav bar
|===========================================================|
| | *Projects* | Friends |                                  | # "area" nav bar
|===========================================================|
| | Project Overview | *Budget* | History |                 | # "tabs" nav bar
|                                                           |
|                                                           |
| Project "Foo" Budget                                      |
| ====================                                      |
| ...                                                       |
+-----------------------------------------------------------+

Each navigation bar has dynamic content. The site one will often have a "Sign In" and "Sign Up" link when no user is signed-in, but will have a "Sign Out" link otherwise. The area bar will often change based on the permissions/roles of the person signed in. The tabs bar will certainly depend on the area and the permissions/roles, and possibly also the features of the item being viewed. (Do all projects have budgets? Also, if the "Budget" link goes to "./budget" then it doesn't have to be generated for each project, but it might not be so easy.)

(Of course, these site, area, and tabs aren't the only possibilities, merely a common pattern.)

Another common pattern is to differentiate the currently-selected navigation item in each applicable bar. (The asterisks in the above display.) In the case above, it probably makes sense to leave the "Projects" area item as a link when browsing a subject to let the user easily return to the projects listing page. On the other hand, the currently active tabs item probably doesn't need to be a link. Clicking that would be the equivalent of a refresh.

Thus, I propose the following plugin. I would greatly appreciate any thoughts -- please fork this Gist or send me a note. Let's thrash early.

Controller class methods

Each of these is really just a shortcut to creating a before_filter. The one significant problem is that path helpers and instance variables aren't available here, so they have to be described as Procs.

The absolute simplest usage assumes that there is only one navigation bar for the site and that each item has some text that is the same as a path helper:

class ApplicationController
  nav_bar do |nb|     # assumes the :default nav bar
    nb.nav_item :home # like link_to('home', :home_path)
    nb.nav_item :faq  # like link_to('faq', :faq_path)
  end
end

Have multiple navigation bars on your site? Use nav_bar(name) {...} or nav_item_for(nav_bar_name, item_name):

class ApplicationController
  # individual style:
  nav_item_for :header, :home
  
  # block style:
  nav_bar(:footer) do |nb|
    nb.nav_item :about_us
    nb.nav_item :faq
    nb.nav_item :feedback
    nb.nav_item :privacy_policy
  end
end

# in app/views/layouts/application.rb:
<%= render_navigation_bar :site -%>
...
<%= render_navigation_bar :area -%>

Have a navigation item that should only appear conditionally? Use :if or :unless:

class ApplicationController
  nav_item_for :site, :sign_in,  :unless => :signed_in?
  nav_item_for :site, :sign_out, :if => :signed_in?
  nav_item_for :area, :admin,
               :if => lambda { |controller| controller.current_user.admin? }
end

Question: you probably want to declare a navigation item like the 'Admin' area in a central place like ApplicationController, but you might want it to be the rightmost item in the area navigation bar. How should we specify position?

Or just for certain actions? Use :only or :except:

class ProjectsController
  nav_bar(:tabs, :only => [:show, :budget, :history, :edit]) do |nb|
    ...
  end
end

Want some prettier text? Pass :text:

class ApplicationController
  nav_item_for :site, :faq,
               :text => "<abbr title='Frequently Asked Questions'>FAQ</abbr>"
end

Have an external link? Pass a String URL:

class ApplicationController
  nav_item_for :site, :blog, :link => 'http://blog.example.org'
end

Want to use a named route other than the one generated from the item_name? Pass a Proc that, when called, generates a path:

class ApplicationController
  nav_item_for :site, :faq,
               :link => lambda { |controller| controller.page_path('faq') }
end

Want to specify which area is active for all actions within a Controller? Use nav_bar.selected:

class ProjectsController
  nav_bar(:area).selected = :projects
end

Controller Instance Methods

All of the class versions above are just before_filters that wrap instance methods, so they're all available within an action.

class ProjectsController
  nav_bar(:area).selected = :projects
  
  def index
    nav_bar(:tabs) do |nb|
      nb.nav_item :by_alpha, :text => 'A-Z',
                             :link => projects_path(:tab => 'by_alpha')
      nb.nav_item :recent, :text => 'Recently Updated',
                           :link => projects_path(:tab => 'recent')
      nb.selected = params[:tab] || :by_alpha
    end
  end

  def show
    @project = Project.find(params[:id])
    prepare_project_tabs :overview
  end

  def budget
    @project = Project.find(params[:id])
    prepare_project_tabs :budget
  end

  def history
    @project = Project.find(params[:id])
    prepare_project_tabs :history
  end

  protected
  def prepare_project_tabs(selected)
     nav_bar(:tabs) do |nb|
      nb.nav_item :overview, :link => project_path(@project)
      nb.nav_item :budget,   :link => budget_project_path(@project)
      nb.nav_item :history,  :link => history_project_path(@project)
      nb.selected = selected
    end
  end

end

View Methods

Generate the :default navigation bar (usually for a single-bar site):

<%= nav_bar -%>

That does (approximately) the same thing as this:

<ul class="default nav">
  <li class="home"><a href="<%= home_path %>">Home</a></li>
  <li class="faq"><a href="<%= faq_path %>">Faq</a></li>
</ul>

Generate a named navigation bar:

<%= nav_bar(:area) -%>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment