Current Pages

Jul 18
comments

Hi everybody. Sorry for the inactivity recently - I haven’t had chance to use Sinatra much at all, but that should change over the summer, happy days!

Here’s a quick piece of code that can often prove to be useful. When you’re producing websites or apps, you often need to know if a link is pointing to the current page or not. The main use for this is in a navigation menu - the current page is often styled differently. You can see a nice example of this on Google’s new homepage - the black nav bar at the top has some links, but the current page has an orange border on top.

I’m going to demonstrate how easy this is to do in Sinatra by making a simple copy of Google’s new page. It won’t do any of the search stuff, but will have 2 lists of links that behave differently if that link is the current page.

You can see an example of my page here.

To start with we need to build the pages and links. Here is the first bit of code, it’s all in the same file, I called it main.rb:

require 'sinatra'
require 'slim'
require 'sass'

set :pages, %w[images videos maps news shopping]

get('/styles.css'){ content_type 'text/css', :charset => 'utf-8' ; scss :styles }

get '/' do
  @title='Web'
  slim :web
end

settings.pages.each do |page|
  get '/'+page do
    @title = page.capitalize
    slim :page
  end
end

We start off by requiring the sinatra gem as well as slim and sass (for html and css respectively). Then I set an array of all the page names (I’ll use this later to make the pages quickly). After this is a standard get handler for stylesheets if you are using Sass. Then there is a get handler for the homepage (called ‘web’ in the navigation bar). Then I have cheated a little bit to create some pages quickly, but it’s a nice technique. I iterate through each page listed in the pages array and build a get handler for each of these. Each page has a url of ‘/page-name’ and a title of the page name capitalised. They all use the same template (called ‘page’)

Now we need to write some templates:

__END__
@@layout
doctype html
html
  head
    meta charset="utf-8"
    title= @title || 'Give me a title!'
    link rel="shortcut icon" href="/fav.ico"
    link rel="stylesheet" media="screen, projection" href="/styles.css"
    /[if lt IE 9]
      script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"
  body
    == slim :nav
    img#logo src="http://www.seomofo.com/downloads/new-google-logo-official.png"
    == yield
    == slim :nav
    
@@nav
nav role="navigation"
  ul#google
    li
      a href='/' Home
    - settings.pages.each do |link|
      li
        a href='/#{link.downcase}'=link.capitalize

@@web
form
  input size=41

@@page
#page
  h1= @title
  p This is the #{@title} page.

This is a fairly standard html5 layout. I’ve used a partial calld ‘nav’ to insert the a list of links based on the array of pages. The ‘web’ layout contains an imput box for searching and all the other pages just contain a heading and simple paragraph (basically just holding pages for now).

This should work, but all the links are styled in the same way. We want to change the styling if it is a link to the page we are currently on. There are two ways to do this - 1) add a class of ‘current’ if the page is a current page and 2) Don’t make it a link if it is the current page - just output plain text. This is why I have used two nav menus (one at the top and one at the bottom), so that I can demonstrate both techniques.

I’m going to use helpers for this. The helpers for both techniques are relatively simple. They both use a Sinatra method called request.path_info, which is basically a wrapper for the Rack request class.This will return the relative url of the page that you are on (excluding any parameters). You can see the other options available to you on the Sinatra FAQ.

Here is the code for the helpers - it just needs inserting after the requires at the top of the file:

helpers do
  def current?(path='')
    request.path_info=='/'+path ? 'current':  nil
  end

  def link_to_unless_current(url,text=url)
    link=request.path_info==url ? "" : "<a href=\"#{url}\">"
    link << text
    link << "</a>" unless request.path_info==url
    link
  end
end

The first helper method returns ‘current’ if the page is indeed the current page and nil otherwise. This is useful for adding a class of ‘current’.

The second helper builds up some html that will be a link if the page is NOT current and just plain text if it is the current page (sometimes there’s no point linking to the page you are on).

To use these helpers, I’m going to have to add change some of the templates. I’m going to use a new partial for the bottom navigation that I will call footer, so the layout changes to this:

  body
    == slim :nav
    img#logo src="http://www.seomofo.com/downloads/new-google-logo-official.png"
    == yield
    == slim :footer

Now I need to rewrite the two templates used for navigation. The first is called ‘nav’:

@@nav
nav role="navigation"
  ul#google
    li
      a class=current? href='/' Home
    - settings.pages.each do |link|
      li
        a href='/#{link.downcase}' class=current?(link.downcase) 
          =link.capitalize

This uses the current? helper to add a class of ‘current’ to the link tag if it is the current page. This can then be used for styling purposes. The second template is called ‘footer’:

@@footer
nav role="navigation"
  ul#footer
    li
      ==link_to_unless_current('/','Home')
    - settings.pages.each do |link|
      li        ==link_to_unless_current('/'+link,link.capitalize)

This uses the link_to_unless_current helper to produce a list of links apart from the current page, which will just be returned as an ordinary list item.

All that is needed now is a few styles to Googlify it. Because I’m using Sass, these go at the bottom of the page as a template called ‘styles’:

@@styles
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote, pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt, dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article, aside, canvas, details,figcaption,figure,footer,header,hgroup,menu,nav,section, summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;line-height:1;}
body{font: 13px/27px Arial,sans-serif;}

#logo{width:280px;display:block;margin:10px auto;}

h1{font-size:28px;color:blue;line-height:2;}

form{width:280px;margin:0 auto;}
#page{width:480px;margin:0 auto;}

#google{background:#2d2d2d;overflow:hidden;
li{float:left;display:inline;list-style-type:none;margin:0;line-height:27px;}
li a,li a:link{padding:0 5px;text-decoration:none;display:block;
color:white;border-top:2px solid transparent;}
li a:hover{background:#4d4d4d;}
li a.current{font-weight:bold;border-top-color:#DD4B39;}}

#footer{
overflow:hidden;;padding:10px;width:300px;margin:100px auto 0;
li{float:left;display:inline;list-style-type:none;margin:0;font-weight:bold;color:#DD4B39;}
li a,li a:link{padding:0 5px;text-decoration:none;display:block;
color:blue;font-weight:normal;}
li a:hover{text-decoration:underline;}}

There’s a lot there, but it should make sense (just ask in the comments if you want anything clarifying).

Here’s the code in full, it weighs in at exactly 100 lines!

require 'sinatra'
require 'slim'
require 'sass'

set :pages, %w[images videos maps news shopping]

helpers do
  def current?(path='') ; request.path_info=='/'+path ? 'current':  nil ; end
  def link_to_unless_current(url,text=url)
    link=request.path_info==url ? "" : "<a href=\"#{url}\">"
    link << text
    link << "</a>" unless request.path_info==url
    link
  end
end

get('/styles.css'){ content_type 'text/css', :charset => 'utf-8' ; scss :styles }

get '/' do
  @title='Web'
  slim :web
end

settings.pages.each do |page|
  get '/'+page do
    @title = page.capitalize
    slim :page
  end
end


__END__
@@layout
doctype html
html
  head
    meta charset="utf-8"
    title= @title || 'Give me a title!'
    link rel="shortcut icon" href="/fav.ico"
    link rel="stylesheet" media="screen, projection" href="/styles.css"
    /[if lt IE 9]
      script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"
  body
    == slim :nav
    img#logo src="http://www.seomofo.com/downloads/new-google-logo-official.png"
    == yield
    == slim :footer
    
@@nav
nav role="navigation"
  ul#google
    li
      a class=current? href='/' Home
    - settings.pages.each do |link|
      li
        a href='/#{link.downcase}' class=current?(link.downcase) 
          =link.capitalize
    
@@footer
nav role="navigation"
  ul#footer
    li
      ==link_to_unless_current('/','Home')
    - settings.pages.each do |link|
      li
        ==link_to_unless_current('/'+link,link.capitalize)       

@@web
form
  input size=41

@@page
#page
  h1= @title
  p This is the #{@title} page.

@@styles
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote, pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt, dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article, aside, canvas, details,figcaption,figure,footer,header,hgroup,menu,nav,section, summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;line-height:1;}
body{font: 13px/27px Arial,sans-serif;}

#logo{width:280px;display:block;margin:10px auto;}

h1{font-size:28px;color:blue;line-height:2;}

form{width:280px;margin:0 auto;}
#page{width:480px;margin:0 auto;}

#google{background:#2d2d2d;overflow:hidden;
li{float:left;display:inline;list-style-type:none;margin:0;line-height:27px;}
li a,li a:link{padding:0 5px;text-decoration:none;display:block;
color:white;border-top:2px solid transparent;}
li a:hover{background:#4d4d4d;}
li a.current{font-weight:bold;border-top-color:#DD4B39;}}

#footer{
overflow:hidden;;padding:10px;width:300px;margin:100px auto 0;
li{float:left;display:inline;list-style-type:none;margin:0;font-weight:bold;color:#DD4B39;}
li a,li a:link{padding:0 5px;text-decoration:none;display:block;
color:blue;font-weight:normal;}
li a:hover{text-decoration:underline;}}

Come to think of it, this really shows the power of Sinatra. We’ve managed to create what is basically the start of a basic brochure site and the whole thing (including styles) is 1 file and 100 lines!

I’d love to hear any feedback in the comments.

blog comments powered by Disqus