5.3. View
Templates
Photo Share is supposed to be a web application
for storing photos, but so far the scaffolding shows only boring
filenames. To make that change, we'll work with view
templates and controllers. Edit the
file app/views/photos/show.rhtml,
which is the view template created by the scaffold generator. If
you have used template languages like ASP or JSP before, you will recognize
the syntax for embedding executable code within the HTML template.
In this case, Rails is using the ERb (Embedded Ruby) template system for
embedding Ruby code within an HTML template. As you recall, text
between <% and %> is Ruby code that is
executed, text between <%= and %> is a Ruby
expression, and the results from executing that code is inserted
into the HTML when ERb evaluates the template.
Insert this line at the beginning of
app/views/photos/show.rhtml:
<%= image_tag 'photos/' + @photo.filename %>
This line calls the Rails helper function
image_tag, which generates an HTML <img>
tag for the photo's filename. By default, images are expected to be
in the public/images directory of
our Rails app, but the photos are in public/images/photos, so prefix the filename
with photos/.@photo
contains the database record for the photos that we want to display
and that was set by the photos controller:
def show
@photo = Photo.find(params[:id])
end
Let's see how this looks. Make sure that the web
server is started, browse to http://127.0.0.1:3000/photos/list,
and click on the Show link for any of the pictures. Now that
(Figure 5-2)
is much niceran actual picture!
Now that you can see the images, it's time to go
back and beautify the photo/list
page. Do this by including the thumbnail image in place of the
filename, and make it clickable, as a link to the show page. This strategy lets you eliminate
almost everything else about the photo and enables the user go to
the show page to see the details. Edit app/views/photos/list.rhtml to look like
this:
<h1>Listing photos</h1>
<table>
<% for photo in @photos %>
<tr>
<td>
<%= link_to(image_tag("photos/#{photo.thumbnail}",
:size => '75x56',
:border => 1),
url_for(:action => 'show', :id => photo)
)
%>
</td>
<td>
<%=h photo.filename %>
<br/>
<%= link_to 'delete me', { :action => 'destroy', :id => photo },
:confirm => 'Are you sure?' %>
</td>
</tr>
<% end %>
</table>
<%= link_to 'Previous page', { :page => @photo_pages.current.previous }
if @photo_pages.current.previous %>
<%= link_to 'Next page', { :page => @photo_pages.current.next } if
@photo_pages.current.next %>
<br />
<%= link_to 'New photo', :action => 'new' %>
There is a lot going on in this code, so we will
go through it in considerable detail, but first, let's just see how
it looks. Browse to http://127.0.0.1:3000/photos/list;
you should see something like Figure 5-3. This is starting to look
halfway decent.
Let's examine that code in detail:
<% for photo in @photos
%>
-
Rails executes the code between <%
and %>, looping through each database row contained in
@photos, which contains a list of Photo objects
set by the controller. Each Photo, in turn, is assigned to
photo.
"photos/#{photo.thumbnail}"
-
Ruby allows single quotes and double quotes to
delimit strings. Ruby evaluates the contents of strings with double
quotes, but not single quotes. That evaluation pass will process
substitutions. In this example, Ruby substitutes the result of
photo.thumbnail, at execution time, for
#{photo.thumbnail}, so this expression is exactly the same
as 'photos/' + photo.thumbnail.
<%= link_to(image_tag(...), url_for(...))
%>
-
The link_to helper function creates a
hyperlink. The first parameter is link text or the image to
display, and the second parameter is the target URL for the
link.
image_tag("photos/#{photo.thumbnail}",
-
:size => '75x56',
:border => 1)>
The image_tag helper function creates
an image tag. The first parameter is the path to the thumbnail, and
those remaining specify attributes for the image tag.
url_for(:action => 'show', :id =>
photo)
-
The url_for helper creates a URL that
targets a given controller and action. Omit the controller, so that
Rails defaults to the controller invoking the view. You need the ID
of the photo to show, so use the photo object. Rails will
substitute the ID of that object.
<%=h photo.filename %>
-
The h method creates properly escaped
HTML text, so characters like < become
<. This line displays the photo's filename, making
sure that any special characters are properly escaped. We could
have used <%= h(photo.filename) %>, but this style
is more common because it makes the h call look more like
its part of the tag.
-
<%= link_to 'delete me', { :action => 'destroy', :id => photo },
:confirm => 'Are you sure?' %>
-
Here the link_to method is used again.
This time it creates a link to the destroy method of the
current controller, but with a twist. We use the :confirm
option, which creates a JavaScript pop-up dialog in the browser
asking "Are you sure?" If the user answers "OK," the link
is taken, and the photo entry is destroyed. If the user cancels,
then nothing further happens.
<%= link_to 'Previous page', { :page =>
@photo_pages.current.previous }
-
Rails supplies pagination helpers to break long
lists into multiple pages with Next and Previous buttons.
def list
-
@photo_pages, @photos = paginate :photos,
:per_page => 10
end
-
This controller code, not shown in the example,
calls the paginate method with two parameters. The first
(:photos) says to read rows from the photos database
table, and the second (:per_page => 10) says to read
these rows in groups of 10.
The paginate method returns two values:
@photo_pages, which captures the current page while
implementing next and previous, and @photos, which is the
list of database rows (photos) for the page currently in view.
You'll get a previous page if one exists.
if @photo_pages.current.previous
%>
-
Our original code creates a link to the previous
page of photos (using the @photo_pages object), but only
if there actually is a previous page. Ruby will conditionally
executes a line if you append an if expression at the end
of the line.
<%= link_to 'New photo', :action => 'new'
%>
-
You should be able to figure this one out by
now. This creates a link to the new action in the current
controller.
5.3.1.
Layouts
You may have noticed that the HTML pages that we
created are incomplete. Rails uses a feature called layouts to let you specify a common set of display
elements for every page rendered by a controller. This feature is
typically useful for common headers, footers, and sidebars. By
default, Rails looks in its app/view/layouts directory for an RHTML file
whose name matches the controller's name.
Take a look at app/views/layouts/photos.rhtml; you should see
something like this:
<html>
<head>
<title>Photos: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= @content_for_layout %>
</body>
</html>
This is the layout template for photos
controller. The HTML output created by any action in the photos
controller is inserted into the layout where you see the line:
<%= @content_for_layout %>
and sent back to the browser for display. The
end result is a valid HTML page.
Let's modify this layout to add some common
links that will show at the bottom of every page. Edit app/views/layouts/photos.rhtml, and insert the
following code just before the </body> tag:
<div style="background-color:LightBlue">
<p>
<%= link_to 'Photos', :controller => 'photos', :action => 'list' %>
<%= link_to 'Categories', :controller => 'categories', :action => 'list' %>
<%= link_to 'Slideshows', :controller => 'slideshows', :action => 'list' %>
</p>
</div>
This layout displays a simple navigation
bar with
links to the pages that list the photos, categories, and
slideshows. This navigation bar appears at the bottom of every page
displayed by the photos controller.
Browse to http://127.0.0.1:3000/photos/list;
you should see a web page like the one in Figure 5-4. Try clicking the new photo
link or any of the thumbnails, and notice that the navigation
remains at the bottom of the page.
This navigation bar is good, but it still has a
few problems. First, it appears only when you are in the photos
controller. If a user clicks on the Categories or Slideshows links, the navigation will be gone.
You really want the same layout to appear throughout. Second, you
should move the navigation bar to the top of the page so that it
doesn't seem to jump around as users move from page to page.
By default, Rails looks for a layout file with
the same name as the controller, which is why we added our
navigation bar in the app/views/layouts/photos.rhtml file. You can
also tell Rails what layout file to use. We'll do that, not
directly in the various controller classes, but in the common
parent class for all of the controllers.
The common superclass for all of the controllers
is defined in app/controllers/application.rb. Edit this file
and add layout 'standard' to the class body so that it
looks like this:
class ApplicationController < ActionController::Base
layout 'standard'
end
This tells Rails to use a layout named
"standard" instead of the default name. And putting this in the
superclass is the same as putting it in each controller
individually. This approach is better, of course, because you don't
have to duplicate the code, and if you add a new controller in the
future, it will automatically use the same layout.
Now let's create the layout template. Create
app/views/layouts/standard.rhtml
with the following content:
<html>
<head>
<title>Photo Share</title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
<div style="background-color:LightBlue">
<p>
<%= link_to 'Photos', :controller => 'photos', :action => 'list' %>
<%= link_to 'Categories', :controller => 'categories', :action => 'list' %>
<%= link_to 'Slideshows', :controller => 'slideshows', :action => 'list' %>
</p>
</div>
<p style="color: green"><%= flash[:notice] %></p>
<%= @content_for_layout %>
</body>
</html>
You probably recognize this text as pure HTML,
with a few simple Ruby expressions to link to the list
actions for photos, categories, and slideshows. This layout is the
same as photos.rhtml, except that
the navigation bar has been moved to
the top of the page. Now you can click on any link, and every page
in this Photo Share application will have this navigation bar at
the top.
We no longer need the other layout files in
app/views/layouts, so delete all
of them, except for standard.rhtml.
 |