Action View
在這一章中我們將進入MVC架構中的View,也就是提供介面給用戶操作,與我們的應用程式做互動。
ActionView是Rails中處理View的元件名稱,而提供給用戶的文件,我們會用Template樣板來呈現。本章假設讀者們都對HTML有基本的認識。
Template樣板
什麼是Template樣板呢? 我們知道伺服器最終提供給瀏覽器的格式是HTML文件,而Template樣板就是動態產生HTML的方式。
Rails預設用來產生Template的方式是Embedded Ruby(ERb),如果你曾經使用過PHP、JSP或ASP,那麼你會非常熟悉這種內嵌程式碼的風格,這是一種最為直覺且容易學習的方法。例如以下是一小段嵌入目前時間的ERb,中間<%= %>
的部份便是Ruby程式:
<h1><%= Time.now.to_s %></h1>
Rails的Template檔案位置和名稱也是有玄機的,例如app/views/welcome/index.html.erb來說,welcome目錄是它的Controller名稱,檔案第一段index是它的Action名稱,附檔名則是用來指定要用什麼方式來產生什麼格式的文件:index.html.erb表示用ERb產生HTML格式的文件。會有這樣慣例的原因,你可能已經猜到,那就是使用ERb不代表一定就是用來產生HTML。用什麼Template引擎(在Rails中又叫作Template Handler)產生文件,和文件的Format格式是兩回事情。所以ERb其實可以用來產生任何文字檔格式,例如CSV、XML、JavaScript等等。
雖然可以,但ERb並不是產生XML的最好方式,通常在我們會用Builder來產生XML,例如一個叫做show.xml.builder的檔案:
people do |p|
p.person "test"
end
就會產生以下的XML:
<people>
<person>test<person>
</people>
以下是常見的樣板引擎與格式組合:
格式 | 引擎 |
---|---|
html、xhtml | erb |
js | erb |
xml、rss、atom | builder |
擴充Template Handler
Rails預設只有內建ERb和Builder這兩套樣板引擎,但要擴充非常容易。例如在Rails社群中,也很流行用HAML這套樣板引擎來取代ERb。HAML是一套利用縮排技術簡化HTML撰寫的格式,例如:
#content
.box
test
就會產生以下的HTML:
<div id="content">
<div class="box">
test
</div>
</div>
要安裝使用,只需要在Gemfile檔案中加上gem "haml-rails"
然後bundle install
即可。不過相較於ERb,使用HAML雖然可以更為有效率地撰寫HTML樣板,但是會需要考量團隊中的網頁設計師是否能夠配合使用。
使用Renderer在Controller中直接回傳結果
有一些格式的本質其實不需要Template引擎,只需要再Controller中直接render其結果即可,例如JSON和CSV或是XML。Rails對ActiveRecord model提供了to_xml
和to_json
方法。而CSV則可以使用FasterCSV函式庫。範例如下:
require 'csv'
class PeopleController < ApplicationController
def index
@people = Person.all
respond_to do |format|
format.html
format.json{ render :json => @person.to_json }
format.xml { render :xml => @person.to_xml }
format.csv do
csv_string = CSV.generate do |csv|
csv << ["Name", "Created At"]
@people.each do |person|
csv << [person.name, person.created_at]
end
end
render :text => csv_string
end
end
end
ERb標籤
除了上述介紹的ERb標籤<%= %>
會輸出中間的Ruby
程式執行結果,還有一些其他用法:
<% %>
這樣就不會輸出任何結果,通常用在if
或迴圈條件中,例如:
<% @people.each do |person| %>
<% if person.name.present? %>
<p><%= person.name %></p>
<% end %>
<% end %>
上述的<% %>
標籤雖然不會輸出HTML內容,但是還是在HTML原始碼中換行了,為了避免輸出時多餘的換行,可以改用<%- -%>
。不過實際上並沒有很多人在乎就是了,畢竟這不影響用戶的頁面。
<%# blah blah %>
這是註解,不會輸出任何內容。不過如果需要整段多行註解,會常這樣寫:
<%- output = false -%>
<% if output %>
<%= foo %>
<hr>
<%= bar %>
<% end %>
這時候只要控制output
的值就是輸出的開關,稍後我們可以將output
變成一個Helper方法,這樣就可以動態決定內容的輸出與否了。
Layout版型
Layout可以用來包裹Template樣板,讓不同View可以共用Layout作為文件的頭尾。因此我們可以為全站的頁面建立共用的版型。這個檔案預設是app/views/layouts/application.html.erb。如果在app/views/layouts目錄下有跟某Controller同名的Layout檔案,那這個Controller下的所有Views就會使用這個同名的Layout。
預設的Layout長得如下:
<!DOCTYPE html>
<html>
<head>
<title>YourApplicationName</title>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
其中的<%= yield %>
會被替換成個別Action
的樣板。
如果需要指定Controller的Layout,可以這麼做:
class EventsController < ApplicationController
layout "special"
end
這樣就會指定Events Controller下的Views都使用app/views/layouts/special.html.erb這個Layout,你可以加上參數:only
或:except
表示只有特定的Action:
class EventsController < ApplicationController
layout "special", :only => :index
end
或是
class EventsController < ApplicationController
layout "special", :except => [:show, :edit, :new]
end
請注意到使用字串和Symbol是不同的。使用Symbol的話,它會透過一個同名的方法來動態決定,例如以下的Layout是透過determine_layout
這個方法來決定:
class EventsController < ApplicationController
layout :determine_layout
private
def determine_layout
( rand(100)%2 == 0 )? "event_open" : "event_closed"
end
end
除了在Controller層級設定Layout,我們也可以設定個別的Action使用不同的Layout,例如:
def show
@event = Event.find(params[:id])
render :layout => "foobar"
end
這樣show Action的樣板就會套用foobar Layout。更常見的情形是關掉Layout,這時候我們可以寫render :layout => false
。
自定Layout內容
除了<%= yield %>
會載入Template內容之外,我們也可以預先自定一些其他的區塊讓Template可以置入內容。例如,要在Layout中放一個側欄用的區塊,取名叫做:sidebar
:
<div id="sidebar">
<%= yield :sidebar %>
</div>
<div id="content">
<%= yield %>
</div>
那麼在Template樣板中,任意地方放:
<%= content_for :sidebar do %>
<ul>
<li>foo</li>
<li>bar</li>
</ul>
<% end %>
那麼這段內容就會被置入到Layout的<%= yield :sidebar %>
之中。
Layout版型的繼承(進階)
- http://edgerails.info/articles/what-s-new-in-edge-rails/2011/01/12/template-inheritance/index.html
- http://asciicasts.com/episodes/269-template-inheritance
局部樣板Partials
局部樣板可以將Template中重複的程式碼抽出來,例如我們在Part1中示範過的新增和編輯的表單。Partial Template的命名慣例是底線開頭,但是呼叫時不需加上底線,例如:
<%= render :partial => "common/nav" %>
這樣便會使用app/views/common/_nav.html.erb這個樣板。如果使用Partial的樣板和Partial所在的目錄相同,可以省略第一段的common路徑。
在Partial樣板中是可以直接使用實例變數的(也就是@
開頭的變數)。不過好的實務作法是透過:locals
明確傳遞區域變數,這樣程式會比較清楚,Partial樣板也比較容易被重複使用:
<%= render :partial => "common/nav", :locals => { :a => 1, :b => 2 } %>
這樣在partial樣板中,就可以存取到區域變數a
和b
。
如果是陣列的資料,一般直覺會這樣寫:
@items.each do |item|
render :partial => "item", :locals => { :item => item }
end
但是,partial可以支援處理陣列形式:
render :partial => "item", :collection => @items, :as => :item
這樣的好處不只是少打字而已,還有效能上的好處,Rails會針對這種形式做最佳化。
集合型Collection
像是tr
或li
這類會一直重複的Template元素,我們可以使用collection
參數來處理,Rails有針對集合型的Partial做效率最佳化,例如像以下的程式:
<ul>
<% @people.each do |person| %>
<%= render :partial => "person", :locals => { :person => person } %>
<% end %>
<ul>
可以改使用Collection方式,
<ul>
<%= render :partial => "person", :collection => @people %>
<ul>
如此既可以少掉兩行each
程式碼,執行的效能又更好。