Navigasmic: Semantic navigation for Rails using simple view level or configuration definitions.
Semantic navigation; a semantic way to build beautifully simple navigation structures in Rails views or configuration.
Most of the navigation styles I’ve done over the years pretty much boil down to this idea: Use simple markup, and do the
rest with CSS (and Javascript if you need it). Because of that the default markup is beautifully simple.
Ok, so navigation is easy right? Until you start managing active, disabled, and hidden states, and more if you may need
them. This can quickly become a mess, and all too often it just stays in the views with whatever logic tossed on top of
it as people go. I’ve seen it too many times, and I wanted to do something about it.
These where the core goals:
And working with gvarela at the time, we wrote a DSL that met those requirements:
semantic_navigation :primary do |n|
n.group "Blog" do
n.item "Articles", "/blog/posts", highlights_on: "/my_awesome_blog"
n.item "Links", "/blog/links", disabled_if: proc { !logged_in? }
n.item "My Portfolio" # auto links to the my_portfolio_path if it exists
end
end
Since we wanted something that allowed for customization we ended up using the Builder Pattern – the way Rails uses
form builders basically. There are some builders that are provided, and these builders can be configured, extended or
replaced if you need more custom markup – there’s more on how to do that stuff below.
Include the gem in your Gemfile and bundle to install the gem.
gem "navigasmic"
You can also get the initializer by running the install generator.
rails generate navigasmic:install
Navigasmic allows you to define navigation in two ways. The first is directly in your views (in a partial, or layout for
instance), and the second is via a global configuration similar to how
simple-navigation works.
config.semantic_navigation :primary do |n|
n.group "Blog", class: "blog" do
n.item "Articles", controller: "/blog/posts"
n.item "Links", controller: "/blog/links"
end
n.group "Info" do
n.item "Me", "/about", title: "The Awesomeness That Is"
n.item "My Portfolio"
end
end
The semantic_navigation
method in Navigasmic provides a single name for defining and rendering navigation. You can use
this method in your layouts, views, and partials to render navigation structures that you’ve defined in the initializer.
<%= semantic_navigation :primary, class: 'my-navigation' %>
While it’s nice to be able to define navigation in the initializer, it’s also nice to be able to prototype out some
navigation in the views directly. You can do this by simply passing a block to the semantic_navigation
helper. By
passing it a block you’re able to define the navigation structure easily and quickly.
ERB
<%= semantic_navigation :primary, builder: Navigasmic::Builder::ListBuilder, class: "my-navigation" do |n| %>
<% n.group "Blog", class: "blog" do %>
<li>Custom Node</li>
<% n.item "Articles", controller: "/blog/posts" %>
<% n.item "Links", controller: "/blog/links" %>
<% end %>
<% n.group "Info" do
n.item "Me", "/about", title: "The Awesomeness That Is"
n.item "My Portfolio"
end %>
<% end %>
HAML
= semantic_navigation :primary, config: :bootstrap, class: "my-navigation" do |n|
- n.group "Blog", class: "blog" do
%li Custom Node
- n.item "Articles", controller: "/blog/posts"
- n.item "Links", controller: "/blog/links"
- n.group "Info" do
- n.item "Me", "/about", title: "The Awesomeness That Is"
- n.item "My Portfolio"
If you ran the install generator you should have a navigasmic.rb
file in your initializers. This file has several
examples and some more documentation. It can be used to define navigation structures as well as create named
configurations for each builder.
When you invoke the semantic_navigation
method you can provide which builder you want to use, and the named
configuration for that builder. By defining these builder specific configurations you’ll be able to render navigation
differently in different parts of your site using the same builder. This allows for the greatest flexibility.
Twitter Bootstrap is pretty awesome, so it’s worth
supporting. There’s a configuration that’s provided in the initializer that allows for nice bootstrap support. It
handles nav-pills, nav-tabs, and the navbar structure.
There’s several options that you can pass to the item
method that dictate the state of a given navigation item. You
can tell it what to highlight on, if it’s disabled and when, and if it should be hidden entirely.
All of the options allow for passing a proc. In the examples below procs are used on anything that needs to happen
within the view scope. This is especially useful when you define the navigation structure in an initializer. If you
want to check if a user is logged in, those things need to happen within the view scope, so if you’re defining your
navigation in a view scope you don’t need to use a proc, but if you’re using the initializer you will.
You can pass links to the item
method in a few ways. You can just provide a controller (and/or action) in the options,
you can pass a first argument, or you can explicitly call out what the link options are. Here are some examples:
n.item "Articles", controller: "/blog/posts", class: "featured"
n.item "Articles", controller: "/blog/posts", action: "index", class: "featured"
n.item "Articles", "/blog/posts", class: "featured"
n.item "Articles", { controller: "/blog/posts" }, class: "featured"
n.item "Articles", class: "featured", link: { controller: "/blog/posts" }
You can take this much further by matching specific url options. Here’s some examples that would match to specific blog
posts (they will also only highlight on the given record):
n.item "Article", controller: "/blog/posts", action: "show", id: "42"
n.item "Article", class: "featured", link: { controller: "/blog/posts", action: "show", id: "42" }
Note that we’re passing a string for the posts id. That’s because when the param comes in and is compared against the
link options you provided, the types need to match.
If you don’t provide a link, Navigasmic attempts to find a path helper from the label. In the following example we only
provide the label, but if I’ve defined a route (E.g. match "/portfolio" => "portfolio#index", as: "my_portfolio"
) it
will automatically use the my_porfolio_path
path helper.
n.item "My Portfolio" # Yeah auto link!
Highlight rules allows for passing an array containing any of/or a Boolean, String, Regexp, Hash or Proc. The following
examples will highlight:
n.item "On the /my_thoughts path, and on Mondays", "/blog/posts", highlights_on: ["/my_thoughts", proc { Time.now.wday == 1 }]
n.item "On any action in BlogController", highlights_on: [{ controller: "blog" }]
n.item "On any path beginning with 'my_'", highlights_on: /^\/my_/
n.item "Only on '/my_thoughts'", highlights_on: "/my_thoughts"
n.item "When the highlight param is set", highlights_on: proc { params[:highlight].present? }
Disable rules allow for you to pass a Boolean or Proc. The following examples will be disabled:
n.item "On Tuesdays, and when not logged in", disabled_if: proc { Time.now.wday == 2 || !logged_in? }
n.item "Never", disabled_if: false
n.item "Always", disabled_if: true
Hide rules allow for you to pass a Boolean or Proc. The following examples will be hidden:
n.group "On Tuesdays, and when not logged in", hidden_unless: proc { Time.now.wday != 2 && logged_in? } do
n.item "When not logged in", hidden_unless: proc { logged_in? }
n.item "Never", hidden_unless: false
n.item "Always", hidden_unless: true
end
Navigasmic comes with a few builders by default. Here’s a breakdown of what’s available, and what each one does.
The ListBuilder is the default builder (unless otherwise specified in the initializer). It builds a UL/LI structure
that’s pretty easy to style and work with.
excluded_keys
-[:map]
wrapper_tag
-:ul
group_tag
-:ul
item_tag
-:li
wrapper_class
-"semantic-navigation"
item_class
-nil
has_nested_class
-"with-group"
is_nested_class
-"with-group"
disabled_class
-"disabled"
highlighted_class
-"active"
label_generator
-proc { |label, options, has_link, has_nested| "<span>#{label}</span>" }
link_generator
-proc { |label, link, link_options, is_nested| link_to(label, link, options.delete(:link_html)) }
The MapBuilder is used for generate XML sitemaps that follow the protocol laid out by the Sitemaps XML format.
excluded_keys
-[]
option_namespace
-:map
wrapper_tag
-:urlset
item_tag
-:url
A simple example of using the MapBuilder – create a [action].xml.builder
view, and add the following:
xml.instruct!
xml << semantic_navigation(:primary, builder: Navigasmic::Builder::MapBuilder)
Licensed under the MIT License
Copyright 2019 jejacks0n