This is the part, where you actually get to see some HTML, something visible. Do I hear sounds of relief? We're even close to the end to this tutorial.
Now, have you looked into the app/skin.rb like I told you before?
require 'nitro/element' class Page < Nitro::Element def doctype %~ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ~ end def render %~ #{doctype} <html> <head> <base href="\#{request.host_url}" /> <link href="style.css" media="screen" rel="Stylesheet" type="text/css" /> </head> <body> #{content} </body> </html> ~ end end
As said before, that content variable will be filled with your own stuff.
What stuff? Nitro .xhtml Templates.
The Page class is used as an HTML tag in your template, it is like a skin
you wrap around your own HTML to make every page look just like the others. It
greatly reduces clutter and repetition in your View templates.
All your templates go into the templates/<controller mount point> folder.
This maps to:
MainController (mount point: '/')
def index => 'templates/index.xhtml'MailController (mount point: '/mail')
def index => 'templates/mail/index.xhtml'def view => 'templates/mail/view.xhtml'def search => 'templates/mail/search.xhtml'As you can see, every function we defined in our controller can have a template. But, we're lazy, so we only create templates for the MailController and not for the MainController, as it only has a index which has a link to the MailController in it. It actually serves no other purpose than to forward people to the real deal.
So, to clarify, every function can have a template, but it doesn't have to.
Mh, you say, tell me already what a template looks like, you procrastinator ;/
templates/mail/view.xhtml<Page> <?r if @mail ?> <div class="email"> <h1>#{@mail.subject}</h1> <div class="from">From: #{@mail.email_from}</div> <code><pre> #{CGI.escapeHTML(@mail.header)} <br /><br /> #{CGI.escapeHTML(@mail.body)} </pre></code > </div> <!-- .email --> <?r end ?> </Page>
The <Page></Page> hides details from the one who is making the template. It
also is nice to look at, ain't it?
Everything enclosed in <?r ?> is pure Ruby, you can use it like any
other Ruby code, except in a few corner cases.
#{stuff} interpolates, just like the equivalent in Ruby strings. It is
just to embed the output of some Ruby code into the environment. In here it
does the same. <?r ?> shouldn't be used to write into the template, always
use #{} for that. As well as with <?r ?> there are a few corner cases
where it can't be used. Most notably:
<html #{'color=white' if @color}>This will for example throw an error, since REXML is used to parse the template so that it is correct.
Anyway, I digress.
So, all this page does, is to present a single email. Remember the @mail we
created in the controller?
templates/mail/index.xhtml1 <Page> 2 <?r if @entries ?> 3 <?r @entries.each do |e| ?> 4 5 <div class="short_mail"> 6 <div class="from">EMail from #{e.email_from}:</div> 7 <h1><a href="/mail/view/#{e.oid}">#{e.subject}</a></h1> 8 <p><small>#{e.header}</small></p> 9 <p>#{e.description}</p> 10 </div> <!-- .short_mail --> 11 12 <?r end ?> 13 14 <div class="pager"> 15 #{@pager.navigation} 16 </div> 17 18 <?r end ?> 19 </Page>
Pretty much the same as before. We use the @entries created in the index
method to iterate over them and present single emails.
See how we create the links in line 7? We just put the oid from the
IndexedMail object in there and a hyperlink to it will be generated. Most easy
cut 'n paste :D.
The next interesting thing is the #{@pager.navigation}. You will see that
it creates an ugly list with <ul><li>'s which you really want to transform
using CSS, hence the <div class="pager"> around it.
templates/mail/search.xhtmlLast part, the search page, which holds the search form.
1 <Page> 2 <h1>Search</h1> 3 4 <form action="/mail/search" method="get"> 5 <div class="search"> 6 <input name="q" type="text" value="#{@query}" /> 7 <input type="submit" name="search_button" value="find" /></div> 8 </form><!-- search form --> 9 10 <?r if @results && !@results.empty? ?> <!-- if something found --> 11 <h1>Mails</h1> 12 13 <div class="results"> 14 15 <?r @results.each do |r, score| ?> 16 17 <h2><a href="/mail/view/#{r.oid}">#{r.subject}</a></h2> 18 <h3>#{r.email_from}</h3> 19 <p>#{r.description}</p> 20 21 <?r end ?> 22 23 <div class="pager"> 24 #{@pager.navigation if @pager} 25 </div> 26 </div> <!-- .results --> 27 28 <?r else ?> <!-- if nothing found --> 29 30 <div class="results_none"> 31 <h2>no results found</h2> 32 </div> 33 34 <?r end ?> 35 </Page>
Right on top, the mandatory search form, that's what we wanted anyway. See
how it sends information to /mail/search? Just for the heck of it, I use
the @query to show the last query made again.
After that, there is a quick check if any results are found (this code starts to look like PHP to me....) and shows all found emails.
Now... uhm... was that everything?
Yep, it was.
Right now you should be screaming in anger! If you aren't, you are a lazy
reader! Why you should be angry you ask? You just recently (check Part 6)
went through exensive trouble to create a new type in PostgreSQL, which you
maybe did never before! Does a bell ring? Yes, the rank! We wanted to
preserve the rank to do something with it. We didn't.
But, you have that power at your fingertips now, you could for example show
the rank at the search page (<p>#{r.find_rank}</p>) or use it to check the
correct ordering or even use them to check for prime numbers :D. Yes, glad
you asked, I'm crazy alright ^_^.
If you really made it through this whole tutorial, being a newbie on any of the tools and techniques I used here, I have to congratulate you.
And I have to tell you, that you made your first step to mastering Nitro. Now you have a codebase. As this was your first Nitro project, it might seem a little hard, but there is great potential in reuse. Don't listen to DHH that reuse is overrated! Reuse any tool, any library, any template.xhtml, any SQL statement which might be useful in your next project, and you'll see that you can crank out new Nitro apps like weeds grow in your front yard.
Have a good evening.