-
Notifications
You must be signed in to change notification settings - Fork 0
20 Browsing Categories
It sure would be swell not to have to know the ID of a category in order to visit its show
page. How about a nice menu of categories across the top of the page?
A <nav>
seems appropriate for our purposes. Let's add it just before the header in our layout.
app/views/layouts/application.html.erb
<div class="col-xs-12">
<nav id="categories">
</nav>
<header><%= link_to 'Redly', posts_path %></header>
</div>
Inside the <nav>
element, we'll create a <ul>
and loop through the categories, displaying a link for each.
app/views/layouts/application.html.erb
<nav id="categories">
<ul class="category-links">
<% Category.find_each do |c| -%>
<li>
<%= link_to c.name, category_path(c) %>
</li>
<% end -%>
</ul>
</nav>
Let's add some CSS before we even bother to look at it in the browser.
app/assets/stylesheets/style.scss
nav#categories {
background-color: #f0f0f0;
white-space: nowrap;
text-transform: uppercase;
border-bottom: 1px solid gray;
font-size: 10px;
height: 18px;
line-height: 18px;
margin-bottom: 0;
ul.category-links {
position: absolute;
display: inline;
right: 0;
left: 95px;
overflow: hidden;
display: inline;
list-style: none;
padding: 0;
margin: 0;
li {
display: inline;
}
}
}
Now check it out. Not too bad. I've left some space on the far left to be used in the future. Still, there's not much space between each category. Let's add a separator. We'll include the separator before each category, except the first one.
So we can keep track of our place in the array of categories, let's change from Category.find_each
to Category.all.each_with_index
.
app/views/layouts/application.html.erb
<% Category.all.each_with_index do |c, i| -%>
<li>
<%= link_to c.name, category_path(c) %>
</li>
<% end -%>
Now to add the separator.
app/views/layouts/application.html.erb
<ul class="category-links">
<% Category.all.each_with_index do |c, i| -%>
<li>
<% if i > 0 -%>
<span class="separator">-</span>
<% end -%>
<%= link_to c.name, category_path(c) %>
</li>
<% end -%>
</ul>
We'll add a little bit off CSS just before the end of the nav#categories
rule.
.separator {
color: gray;
margin: 0px .7ex 0px .7ex;
}
What happens if we have a whole bunch of categories? If you have more than will fit across the nav bar at the top of the page, it will simply show as many as will fit (thanks to our stylesheet). That's nice for presentation purposes, but we need a way to select from among the other categories.
Let's add a dropdown menu. Bootstrap makes that pretty easy. We haven't used any of Bootstrap's JavaScript features until now, so we haven't bothered to include its .js files in our asset pipeline. It's time to include them.
I'm going to require bootstrap-sprockets
just after jquery_ujs
in application.js
.
app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require turbolinks
//= require_tree .
Now let's edit the layout, using the required markup for Bootstrap dropdowns. Place the following just inside <nav id="categories">
, before we spit out the horizontal menu:
app/views/layouts/application.html.erb
<div class="dropdown">
<button id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Categories
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<% Category.all.find_each do |c| -%>
<li role="presentation"><%= link_to c.name, category_path(c), role: 'menuitem', tabindex: '-1' %></li>
<% end -%>
</ul>
</div>
<span class="separator">|</span>
The <button>
includes a data-toggle="dropdown"
attribute that Bootstrap uses to add the dropdown behavior.
Inside the button
element, we have our text ("Categories") and an empty <span class="caret">
that Bootstrap will style so that it looks like a downward-facing arrow.
The dropdown menu itself it a <ul>
of a certain class and role. Each menu item is an <li>
and, again, we assign a certain class and role.
The various aria-
attributes are there for accessibility purposes. The Accessible Rich Internet Applications (WAI-ARIA) standard includes recommendations for marking up behavior-rich widgets (dropdowns, modals, etc.) to work with assistive technologies for those with disabilities.
Let's add some CSS, nested inside the nav#categories
rule, just after the .separator
rule.
app/assets/stylesheets/style.scss
.dropdown {
float: left;
padding-left: 5px;
button {
text-transform: uppercase;
border: 0;
background-color: inherit;
padding: 0;
}
.dropdown-menu {
font-size: 10px;
}
}
Try it out in your browser. Not bad, eh? If it's working, let's commit.
$ git add .
$ git commit -m "Add categories dropdown to layout."
You may have noticed we're calling Category.all
twice in that layout. Before we move on, let's DRY that up.
We can't access an instance variable from the layout. What we can do is define a categories
method in ApplicationController
and declare it a helper method to make the categories available on every page.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :categories
def categories
@categories = Category.all if !defined? @categories
@categories
end
end
Within the categories
method, we use an instance variable to ensure that a second call to categories
will not load them from the DB again.
Then we just replace Category.all
with categories
in our layout.
app/views/layouts/application.html.erb
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<% categories.each do |c| -%>
# ...
<span class="separator">|</span>
<ul class="category-links">
<% categories.each_with_index do |c, i| -%>
Be sure everything still works. If you watch your server output now, you may notice that the categories are only loaded from the database once per page now.
Shall we commit?
$ git add .
$ git commit -m "Use a helper method to load categories for navigation."