Dynamic Navigation Items

Simon Courtois edited this page May 2, 2014 · 8 revisions

There might be several occasions where the content of the navigation is not static but should be dynamically loaded.

In some cases you just want to add dynamic names and URLs to menu items configured in the navigation.rb file (this is useful for breadcrumb trails), and in other cases you might want to render a completely ad-hoc menu structure (examples for this would be a CMS which has dynamic pages or a user who can specify it's own navigation etc). Simple-navigation handles both cases.

Dynamic items

The navigation.rb file is evaluated for each call to render(), so you can use code in the items as long as you take care to make that code work in all cases. For example, say we have a Rails application that manages people, who have names:

primary.item :home, 'Home', root_path do |sub_nav|
  sub_nav.item(:people, 'People', people_path, highlights_on: /people/) do |person|
    person.item :person, @person.try(:name), url_for(@person), highlights_on: /people\/[0-9]+/
    person.item :new_person, 'New Person', new_person_path, highlights_on: /people\/new$/
  end
end

This might not be too useful in a menu, but it could be helpful in a breadcrumb trail:

render_navigation level: 1..2 # will render the "home" and "people" link for menus
render_navigation(renderer: :breadcrumbs, join_with: ' / ') # will render a list of breadcrumbs

Note the calls to person.try(:name) and url_for(@person), which will render an item with the name and url of the currently-selected person. Be careful, though, because this code will be called for other controllers besides the person controller so the code can't crash if @person is nil.

Ad-hoc menus

Simple-navigation allows you to provide the navigation items dynamically in the render_navigation call, i.e. in your view call. There are two ways to do this:

Passing a block to render_navigation (cleaner and preferred way) (as of v3.9.0)

# in your view
render_navigation do |primary|
  primary.item ...
  primary.item ... do |subnav|
    subnav.item ...
  end
end

The object that is yielded to the block is the same as in the config file, i.e. the syntax is the same as in the config file. Using this approach it is quite easy to dynamically generate your navigations, e.g. loading pages out of the database and generating the menu dynamically for those pages.

If you don't want the code to create your items in the view, you could also create a helper method that returns a block:

# in navigation_helper.rb
def my_menu_items
  proc do |primary|
    ...
  end
end

# in your view
render_navigation &my_menu_items

Passing the items as an array of hashes

Note: This option might be deprecated in the future, so please pass a block to render_navigation if possible, as described above.

render_navigation items: @my_items

@my_items is an array of the navigation items to be displayed. An item can either be an object or a hash. An item has to respond to the following methods (or needs the following keys if it's a hash) - similar to the stuff you would provide for static items in the navigation config-file:

  • key - the item's key
  • name - the item's name (that gets rendered)
  • url - the target url of the item

and optionally

  • options - all the options for the item
  • items - the item's subnavigation items (again, an array of either objects or hashes)

Example using an array of hashes

@my_items = [
  { key: :main, name: 'Main', url: '/main', options: {your options here}, items: [
    { key: :sub1, name: 'Submenu 1', url: '/sub1' },
    { key: :sub2, name: 'Submenu 2', url: '/sub2' }
  ]}, {...next primary item...}
]

How you generate that hash is completely up to you. The most common way will be that you have models that you can extract that information from. As stated above, if you have objects/models that already respond to those methods, you don't have to convert them into a hash.

Setting the container's dom_class and dom_id

When providing the items statically in the config file, you can set the dom_id and dom_class of the item's container like this:

...
primary.dom_id = 'my-id'
primary.dom_class = 'my-class'
primary.item ...
...

However, when providing the items dynamically, you have to set the options :container_id and container_class on the item's itself, i.e.

@my_items = [
  { key: :main, name: 'Main', url: '/main', options: { container_id: 'my-id', container_class: 'my_class'}, items: [
    { key: :sub1, name: 'Submenu 1', url: '/sub1' },
    { key: :sub2, name: 'Submenu 2', url: '/sub2' }
  ]}, {...next primary item...}
]

This would set the class and id of the container of the primary navigation items.

Showing all items

When making a drop down menu, and you want to render all these items all the time, pass expand_all: true to the render_navigation method.

Mixing static and dynamic content

If you would like to mix static and dynamic menu content, you have to provide ALL the items dynamically and mix the static content into the items yourself.