Configuration

Nicolas Connault edited this page Nov 10, 2017 · 7 revisions

Basic configuration

By default your navigation is configured in the file config/navigation.rb. To generate a template-file with comments in it (to have an easier start), call the following generator:

# Rails 3 and 4
$ rails generate navigation_config

# Rails 2
$ script/generate navigation_config

If you want to use simple-navigation inside a plugin you have to add the plugin/config path to the SimpleNavigation.config_file_paths.

# pluginname/lib/pluginname/engine.rb
require 'simple-navigation'
SimpleNavigation.config_file_paths << File.expand_path('../../config', __FILE__)

So, the best way to name your config-file is creating a config/pluginname_navigation.rb and in the view write as bellow:

<%= render_navigation(context: :pluginname) %>
<%# Example: admin plugin %>
<%#= render_navigation(context: :admin) %>

In the config/navigation.rb file you define your navigation items as illustrated in the following example:

SimpleNavigation::Configuration.run do |navigation|  
  navigation.items do |primary|
    primary.item :books, 'Books', books_path
    primary.item :music, 'Music', musics_path
    primary.item :dvds, 'Dvds', dvds_path
  end
end

This creates a primary navigation with three items (for an online store which sells books, music and dvds). For each item, you define:

  • a key - used to identify the active navigation item from controllers
  • a name - a string (you can call your I18n framework here)
  • a url (optional) - you can use url_for, restful route helpers, named route helpers or any string
  • options (optional) - a hash that can be used to specify attributes that will be included in the rendered navigation item (e.g. id, class, etc.).

Note: as of v3.6.0 url is optional, items which do not have a URL are not rendered with a link and may be used for headings, separators, extra information, etc.

In addition to these HTML attributes, you can set the following special options:

  • :if - Specifies a proc to call to determine if the item should be rendered (e.g. if: Proc.new { current_user.admin? }). The proc should evaluate to a true or false value and is evaluated in the context of the view. See Conditional Navigation Items below for examples
  • :unless - You get the point...
  • :method - Specifies the HTTP-method for the generated link - default is :get
  • :highlights_on - if auto-highlighting is turned off and/or you want to explicitly specify when the item should be highlighted, you can set a regexp which is matched against the current URI or supply your own conditions as a proc. See Highlighting the active Navigation Item below for details.

If you want to define a sub navigation for a primary item you can specify a block for that item:

primary.item :books, 'Books', books_path do |books|
  books.item :fiction, 'Fiction', fiction_books_path
  books.item :history, 'History', history_books_path
  books.item :sports, 'Sports', sports_books_path
end

which defines three sub navigation items for 'Books'.

You can nest as many sub navigations as you like, i.e. if you would like to add a sub navigation for 'History', simply add another block to that item:

...
books.item :history, 'History', history_books_path do |history|
  history.item :ancient, 'Ancient', ancient_books_path
  history.item :modern, 'Modern', modern_books_path
end
...

Availability of helpers inside the config-file

The config-file is evaluated in the context of the view that renders the navigation. This means you can access all the view-helpers inside the config-file.

Conditional navigation items

If you want a navigation item to appear only if certain conditions apply (e.g. only showing an item linking to the admin-zone if the user is admin), you can use :if or :unless options specifying a proc (or lambda) that contains your condition:

primary.item :admin, 'Admin', admin_path, if: proc { current_user.admin? }

or if you want to show an item only if a user is logged in:

primary.item :account, 'Account', account_path(@user), unless: proc { logged_in? }

The procs you specify are - as stated above - evaluated in the context of the views, thus you can use all the methods and vars in your proc that are available in the views.

Restful delete links

If you need a navigation item that performs a destroy action (restfully spoken), you can specify method: :delete as an option for your navigation item:

primary.item :logout, 'logout', session_path, method: :delete, if: proc { logged_in? }

# link with confirmation and custom class.
primary.item :project_delete, 'Delete', project_path(@project), {
  highlights_on: proc { false },
  link_html: {
    :'data-confirm' => "Are you sure you wish to delete '#{@project.name}'?",
    :'data-method' => 'delete',
    :class => 'btn btn-danger'
  }
}

For further information on the config/navigation.rb file (more options etc...) check the comments in the generated file itself.

Providing the navigation items dynamically

In case your navigation is based on dynamic content (e.g. a user specific navigation stored in the database) you can provide the navigation items dynamically. Please see Dynamic Navigation Items for details.

Highlighting the active navigation item

Once you have defined your navigation you may ask yourself how to mark a specific navigation item as selected (i.e. highlighted). There are basically three circumstances which can mark a navigation item as selected:

  • Automatic highlighting - Since v2.0.0 there is a feature called 'auto highlighting' which is turned on by default. With auto highlighting turned on the URL that has been defined for the navigation item (in the config-file) is matched against the current request's URL. If there is a match, the item is marked as selected.
  • Explicit highlighting of an item using regular expressions - In the config-file you can add a regexp to an item that is matched against the current_url to see if the item should be highlighted.
  • Explicit highlighting of an item using a proc - In the config-file you can supply your own proc to determine whether an item should be highlighted.
  • Sub navigation is selected - If you have more than one level of navigation and a sub navigation of an item is selected, that item (i.e. the parent) also gets marked as selected.

Automatic highlighting

As described above with automatic highlighting enabled a navigation item is marked as selected if its URL matches the current request's URL. This probably works for about 80% of the use cases. That's why it's turned on by default. However, depending on your application and navigation setup you might encounter two categories of problems:

An item that you think should be auto highlighted is not

This situation might occur for

  • URLs with dynamic params, e.g. if you are displaying a paged list with the current offset as param and the highlighted navigation item should be the same for the whole paging process
  • incomplete navigations, e.g. you have defined a top level navigation (e.g. Account) that actually has sub pages (e.g. User Data, Settings) but you don't have a sub navigation that stands for these sub pages. In that case you probably want the top level navigation to be highlighted for all the sub pages, thus auto highlighting cannot help here.

The solution for those kinds of problems is to explicitly highlight an item using the :highlights_on option for an item in the config-file (see below).

An item should not be auto highlighted but nonetheless is

This situation should rarely occur. If it does you can always disable the automatic highlighting feature either globally or for a specific navigation level.

To disable it globally for the whole plugin, set

SimpleNavigation::Configuration.run do |navigation|  
  navigation.auto_highlight = false
  navigation.items do |primary|
    # ...
  end
end

To turn it off for specific level (e.g. the primary navigation), set

SimpleNavigation::Configuration.run do |navigation|  
  navigation.items do |primary|
    primary.auto_highlight = false
    # ...
  end
end

There might be other cases where auto highlighting doesn't work. Please drop me a line if you experience such problems.

Explicitly highlighting an item using regular expressions (as of v2.7.0)

If you're having troubles with auto highlighting or you just want to have a more granular control of when to highlight an item you can define a regular expression for an item using the :highlights_on option:

primary.item :books, 'Books', books_path, highlights_on: %r(/books) do |books|
  # ...
end

The specified regexp is matched against the current URI (which is /books for the example's books_path). Please note that this feature is very powerful, but you have to define your regexp quite carefully. The regexp of the example above - %r(/books) - matches /books, /books/fiction, /books/drama as well as /store/books/fiction. If you only want a match for /books, you would have to change the regexp to %r(^/books$).

This feature is also useful to highlight URLs with params, e.g. /book/5 or /books?show_page=3 etc.

Explicitly highlighting an item using procs (as of v3.3.4)

For even more control than using a regular expression, you can supply a proc as for the :highlights_on option:

primary.item :books, 'Books', books_path, highlights_on: lambda { @my_instance_variable.my_method? } do |books|
  # ...
end

This can be used to cope with any case where you want full control over the highlighting. If the specified lambda or proc returns a truthy value, the item is highlighted; if the return value is falsey, the item will not be highlighted.

Under Rails, a possible pattern is to include navigation helper methods in your application_helper.rb (or perhaps a module in /lib) to isolate the generation of complex lambda or proc objects from the navigation.rb file itself.

For example, in application_helper.rb:

CREATE_ACTIONS = ['new', 'create']

def create_action_matcher(controller, &additional_condition)
  action_matcher(controller, CREATE_ACTIONS, &additional_condition)
end

def action_matcher(controller,action, &additional_condition)
  controllers = Array(controller)
  actions = Array(action)
  lambda do 
    controllers.include?(controller_name) && 
    actions.include?(action_name) && 
    (additional_condition.nil? ? true : additional_condition.call)
  end
end

Then, in your navigation.rb you can use syntax like:

primary.item :new_book, 'Create new book', new_book_path, highlights_on: create_action_matcher('books')

Automatically highlighting an item when using RESTful subpaths (as of v3.4.0)

If you supply the symbol :subpath for the :highlights_on option, all subpaths under the path specified for the item will cause the item to be highlighted.

For example: use this option if you want the "users" menu item highlighted when using routes like users/1, users/new etc.

Use subpath globally (as of v4.0.0)

Instead of supplying highlights_on: :subpath to every item of your navigation, you can simply set highlight_on_subpath = true option in the navigation's configuration.