Ajax
filed in Uncategorized on Apr.30, 2007
Ajax 大開眼界
Ajax 主要的特色就是在網頁上改完資料後新的資料會馬上顯現,不需要跳到別頁再顯現。 這樣的話網站看起來反應會很快,不像以前每一個動作後要等整個網頁更新後才好。 Ajax 這個技術在很久以前就有了,但直到 Gmail 跟 Google Maps 開始大量使用時大家才發覺它的好處。 但是,看別的網站用的好跟在自己的網站上套用是兩回事。 Ajax 本身並不是很好用,當初少人用就是這個問題。 直到許多高手寫好的 library 出來時才越來越多人使用。
還記得 scaffold 嗎? 就是 Rails 自動產生的 CRUD (create, read, update, delete) 介面。 我們之前架的 scaffod 雖然可用,長相並不是怎麼好看,而且沒有 Ajax 功能。 不用擔心,網路上有一位叫 Richard White 的先生把 Ajax 功能加到 scaffold 裡面去了。 要了解他的作品可到 http://www.ajaxscaffold.com/ 參考一下,或是在這裡寫個簡單的程式來體驗比較快。 這程式裡會有基本的 CRUD 功能,也會有 先進的 Ajax 使用者介面。 我們把這個程式叫 flowers。 寫這程式的步驟跟在 Localization 文章裡的 plants 差不多,但為了簡單化,flowers 將不會有中文的功能。 要中文的話請到 Localization 看要怎麼加。
動工了! 首先,在 MySQL 裡建一個叫 flowers_development 的資料庫。 然後建一個新的 rails 程式:
rails flowers cd flowers
在這新的 rails 程式裡安裝 Richard White 的 scaffold 程式碼產生器:
gem install ajax_scaffold_generator
產生新的 model 叫做 “Flower”。
ruby script/generate model Flower
來複習一下以前學過的概念。 在寫新的程式時,我們都會先產生 model,然後再修飾自動產生的 migration 檔案,然後再利用那個檔案產生真正的 table。 這些步驟如果不熟悉的話請看 “Migration 版本管理” 章節。
剛剛 model 有產生了,所以現在要修飾自動產生的 migration 檔。 把 db/migrate/001_create_flowers.rb 打開,在 def self.up 裡放入下面這幾行:
create_table (:flowers) do |t|
t.column "name", :string
t.column "planted_at", :datetime
t.column "seed", :boolean
end
檔案儲存後下此指令:
rake migrate
這時 table 已建好了,我們就可以啟動剛剛安裝的 scaffold 程式碼產生器:
ruby script/generate ajax_scaffold Flower Admin
這個指令會產生一個控制 Flower 的 controller。 Controller 的名字叫作 Admin。 這指令也會產生一些以前 rails 已自動產生過的檔案,新的檔案就有 ajax 的功能。 它會問你確定要覆蓋舊的檔案嗎?
overwrite app/models/flower.rb? [Ynaq]
按一下 “a” 來全部覆蓋舊的檔案。
來看看新的檔案長的什麼樣子。 打開 app/controllers/admin_controller.rb 哇!多出了好多 method。 原本的 scaffold 只有七個 method (index, list, show, new, create, edit, update, destroy) 現在多了五個 method (update_params_filter, return_to_main, component_update, component, cancel) 而且原本的 method 裡也新增了很多程式碼。 這麼多的訊息真是消化不良。 再來看看 app/views/admin 裡面,也多了好幾個檔案,這麼多東西,是要解釋到那時候? 還是先不管,來看看多出那麼多的程式碼是增加了那些功能。 啟動伺服器:
ruby script/server
然後到 http://localhost:3000/admin/ 看看。

嗯!使用者介面顏色配的不錯。 加幾筆假資料吧。 按 “Create New” 之後,在同一頁裡會出現新增資料的表格。 新增之後的紀錄也顯示在同一頁中,網頁都不用更新。 用這網站感覺就是操作流程快了許多。 螢幕沒常在更新的話使用者也覺得安全一點,東西不會怕說消失後跑不出來。 現在按一下左邊 “name” 這個字,整個資料就自動幫你照那個欄位排了! 真好。
從頭來看 - 自己做 ajax
這些這麼酷的功能是怎麼做的呢? 壞消息是,ajax scaffold 做的太複雜,沒辦法在一篇文章內解釋完。 好消息是,我們會再做一個比較簡單的 ajax 範例叫 horses,從那個範例裡你就可以了解 ajax 的概念,這樣再回來看 flowers 裡的 ajax scaffold 程式碼就看的懂了。
現在我們要建立一個新的 Rails 程式叫 horses。 在這程式裡,我們不會用 ajax scaffold,而是手動增加 ajax 功能。
在這本書裡我們常常建立新的 Rails 程式,現在程序應該背熟了吧? 首先建一個叫 horses_development 的資料庫。 建好了之後建一個新的 rails 程式:
rails horses cd horses ruby script/generate model Horse
這時候 Migration 會產生一個檔案叫 db/migrate/001_create_horses.rb。 把這個檔案打開,在 def self.up 裡放入:
create_table (:flowers) do |t|
t.column "name", :string
t.column "height", :integer
t.column "weight", :integer
t.column "born_on", :date
t.column "died_on", :date
end
檔案儲存後下此指令:
rake migrate ruby script/generate scaffold Horse Admin ruby script/server
去看看 http://localhost:3000/admin/ 哇!回到以前的 scaffold 了,真懷念。 剛剛這些步驟都是家常便飯,跟 ajax 沒關係,現在就要開始加 ajax 功能了!
步驟一: 包含控制 JavaScript 的 class
Rails 的 ajax 功能是包含在一個叫做 JavascriptHelper 的 class 裡頭。 JavascriptHelper 讓你用 ruby 來控制 JavaScript。 要用 JavascriptHelper 前 要把這小段程式碼放在
app/views/layouts/admin.rhtml 裡的 <head></head> 中間:
<%= JavaScript_include_tag :defaults %>
這一小段碼會把所需要用到的 ruby class 包裝進來。
=== 小提示 ===
你可能會想說直接把這段程式碼放在 app/views/layouts/application.rhtml 檔案裡頭,不就不用在每個 controller 的 layout 檔放了嗎? 這個想法是錯的喔。 一個 controller 如果沒在 app/views/layouts/ 裡設定 layout 檔的話,application.rhtml 才會被用到。 如今我們的 admin controller 已在 app/views/layouts/ 裡有自己的 admin.rhtml, application.rhtml 檔案裡的東西是不會被用到的。
===
第一步驟其實這樣就好了,但為了要更清楚的展示如何寫 ajax 程式碼, 我們要把 app/views/admin/list.rhtml 簡單化。 把此檔案打開,並把裡面有關 table 的 html code 移除。 產生欄位標題的 code 也移除,還有 <%= link_to 'Show'... %> 也移除。 剩下來的程式碼如下:
<h1>Listing horses </h1>
<% for horse in @horses %>
<% for column in Horse.content_columns %>
<%=h horse.send(column.name) %>
<% end %>
<%= link_to 'Edit', :action => 'edit', :id => horse %>
<%= link_to 'Destroy', { :action => 'destroy', :id => horse }, :confirm => 'Are you sure?', :post => true %>
<% end %>
<%= link_to 'Previous page', { :page => @horse_pages.current.previous } if @horse_pages.current.previous %>
<%= link_to 'Next page', { :page => @horse_pages.current.next } if @horse_pages.current.next %>
<br />
<%= link_to 'New horse', :action => 'new' %>
啟動伺服器後加一些假資料進去。 現在所有的資料顯示時都擠在一起:

沒關係,我們繼續修改,利用顏色區分不同的資料。
把 <%=h horse.send(column.name) %> 改成
<font color="blue"><%= column.human_name %>: </font><%=h horse.send(column.name) %><br />
然後在 <%= link_to 'Destroy'....%> 下面加個 <p> 這樣每一匹馬的資料會一塊一塊的區分開來:

步驟二: 使用 <div><div>
第一步驟所做的動作只有把操控 JavaScript 的程式碼匯入我們的程式裡。 第二步驟要做的就是告知 ajax 資料處理後網頁的那個部份需要更新。 當我們在一個網頁上修改資料時,
Ajax 會利用 JavaScript 來送一小段修改資料的指令給伺服器,伺服器處理那段指令後會傳回來一段回覆碼, JavaScript 再利用那段回覆碼把網頁中的一小段內容改掉。 傳送這一小段的指令跟回覆碼總比把整個網頁的內容再傳送一次還快。
雖然 Asynchronous Javascript and XML (Ajax) 裡的 x 是代表 XML, 但伺服器傳回的可以是 html,或 JavaScript, 或純文字等等。 當伺服器傳回 html 回覆碼時, JavaScript 怎麼知道這段 html 要放那邊呢? 放的地方就是要用 <div><div> 來指定了。
我們把每一匹馬的紀錄當做一個區塊,要改變時只要那個區塊有變動,其他同一網頁的東西不必要動。 所以在 <% for horse in @horses %> 之後要放 <div> ,而 div 的結束點 <div> 要放在 <% end %> 前面。 <div><div> 的用途就是把每匹碼的資料區分開來。 但雖然分成了不同的區塊,我們不同的區塊要編不同的識別碼,這樣伺服器回傳 html 碼時才知道是要代替那個區塊的內容。 區塊怎麼添加識別碼呢?就是把 <div> 裡加一個 id:
把 <div> 改成 <div id=”myDiv<%= horse.id %>“>
這裡的識別碼我們用了 horse 的 id, 因為 horse 的 id 一定不會重複的。
myDiv 是我自己加的,這只是一個卡位的名字,可以隨便取。 這時你如果把網頁 reload 然後去看它的網頁編碼 (page source),你就可以看到每匹馬的紀錄會有 <div id=”myDiv1″> 或

步驟三: link_to_remote
從步驟一到現在我們已把 JavaScript 的控制檔匯到程式裡,也有用 <div> 指定了可變動的區塊,剩下來的就是指定那個連結會變動那個區塊。
把
<%= link_to 'Edit', :action => 'edit', :id => horse %>
改成
<%= link_to_remote image_tag("/images/edit.png", {:alt => 'edit'}),
:update => "myDiv#{horse.id}",
:url => { :action => 'edit', :id => horse.id }
%>
原始的 link_to 會產生一個連結,連結按了後整個頁面會跳到 http://localhost:3000/admin/edit/ 那裡。 現在把 link_to 改成 link_to_remote 後, link_to_remote 就會在幕後用 JavaScript 跟伺服器溝通。 link_to_remote 是 Rails 用來包裝 ajax 指令的 method。 我們要給它一些變數它才知道有那些東西要處理。
image_tag 裡指定這個連結的圖案檔放那裡。 記得要放一張 edit.png 在 public/images/ 裡頭。 別的圖片格式也可以,不用一定是 png 檔。 圖片名字也可以改,只要檔案名稱跟 image_tag 裡一樣就好。 :alt 是圖片檔如果找不到時要顯示的文字。 如果不要用圖片來當連結, image_tag(”/images/edit.png”, {:alt => ‘edit’}) 這段字換成 “Edit” 它就會顯示文字了:
<%= link_to_remote (“Edit”,
:update => "myDiv#{horse.id}",
:url => { :action => 'edit', :id => horse.id })
%>
:update 這變數包含著資料傳回來時要取代區塊的辨識碼。
:url 指定要用那個 method 來處理傳給伺服器的碼。 上面指定的是 Admin controller 裡的 method, 叫 edit。 edit 這 method 在跑時也需要知道處理那匹馬,所以我們也有傳入馬的 id。
回到 http://localhost:3000/admin/ 看看。 按一下任何資料的 “edit”, 螢幕上會出現一些可以改的欄位,但也會出現 “Show” 跟 “Back”:

因為我們整個修改的流程都會在同一頁上,這兩個連結就不需要了。 把 app/views/admin/edit.rhtml 檔案打開,移除
<%= link_to 'Show', :action => 'show', :id => @horse %> | <%= link_to 'Back', :action => 'list' %>
<h1> Editing horse </h1>
也移掉。

剩下來還有一個問題。 當我們在網頁上改完一筆資料後,
它會跳到 http://localhost:3000/admin/show/ 去。 我們不希望它自動跳去 show 那裡,所以把 app/controllers/admin_controller.rb 開啟,把 “update” method 裡的
redirect_to :action => 'show', :id => @horse
改成
redirect_to :action => 'list'
現在回網頁再試試看,改完後沒跳到別的網頁了!
增加 ajax 的方法就只需這三步驟, 以後你會接觸到各式各樣的 ajax 功能,但應用的概念還是這三個基本的步驟喔。
Ajax 特效
Ajax 的另一個好處是它可加特效。 不用特別下載軟體。 使用者如果按一份資料的 destroy 連結時,我們要讓那份資料像”煙”一樣消失掉。 把 app/views/admin/list.rhtml 裡的
<%= link_to 'Destroy', { :action => 'destroy', :id => horse }, :confirm => 'Are you sure?', :post => true %>
換成
<%= link_to_remote image_tag("/images/delete.png", {:alt => 'delete'}),
:url => { :action => 'destroy', :id => horse.id },
:complete => visual_effect(:puff, "myDiv#{horse.id}"),
:confirm => 'Are you sure you want to delete?', :post => true %>
圖片要記得放喔。 這個 destroy 連結的 link_to_remote 跟 edit 連結的 link_to_remote 不同的地方是它多個 :complete 跟 :confirm。
:confirm 是再確認的視窗。 按了 “delete” 之後,視窗會問說 “Are you sure you want to delete?”:

:complete 是 action 處理後要做的動作,像增加特別效果。 這些特效是由 http://script.aculo.us/ 網站上的 Thomas Fuchs 寫的。 script.aculo.us 本身是架在 prototype 上。 Prototype 是一個 JavaScript library,把一些 Ajax 的功能寫好包裝成 class 來讓大家用。 Script.aculo.us 在這 library 上多建了動畫功能,drag and drop,同欄位修改,測試功能等等。 這個 library 不只是 RoR 可用,其他語言像 pearl, php, java, .net 等等都可用。 因為 Rails 本身就有把 script.aculo.us 內建,要使用時需要做剛剛所學過的 “步驟一: 包含控制 JavaScript 的 class”就好,不要費時間去他的網站查看在 RoR 怎麼套用喔。 還沒讀到這裡之前,你應該已迫不及待的去看看 “特效” 是什麼了吧? 沒的話先去看看。 嗯,資料像煙一樣消失的確是很酷!

除了 “煙” 之外,其他效果多得很呢:
這網頁裡 所有特效都一目了然,讓你很快的決定你要的特效是什麼。
* 消失/出現 的特效
appear / fade 出現或消失。 div 要先沒看到,這個效果才會有效
blindup / blinddown 像窗簾一樣拉上或拉下
slideup / slidedown 整個 div 滑上或滑下
grow / shrink 由中間往外膨脹或由外往中間縮小
* 消失 的特效
dropout 往下掉然後消失
fold 由下縮到上,然後由右縮到左
squish 同時由下縮到上,由右縮到左
puff 煙化掉 (讓一切煩惱隨著風吹而散)
fade 淡化掉
* “注意!” 的特效
highlight 銀光筆突顯。 如果資料有 update 的話,用這個功能來突顯會很好
pulsate 消失,出現,消失,出現。。。像心跳一樣
我們也可以進一步的控制這些特效,像顏色,特效長度,等等。 以剛剛 destroy 的範例來說,來做一個客製化的特效吧。
:complete => visual_effect(:highlight, "myDiv#{horse.id}",
:delay => 5,
:duration => 6,
:from => 0.3,
:to => 0.7,
:queue => 'front')
:highlight 我們要客製化的特效名稱,這個特效是”銀光筆突顯”
:delay 等 x 秒後再開始特效
:duration 特效重頭到尾要跑 x 秒
:from 從特效的那階段開始,可選由 0.0 到 1.0. 預設是 0.0
:to 到特效的那階段結束,可選由 0.0 到 1.0. 預設是 1.0
:queue 特效跟別的特效同時跑的排程。 有三種選擇,front (跑在別的特效前), parallel (同時跑), back (跑在別的特效後)
從剛剛的範例來看,這個”銀光筆突顯”特效會在資料刪除後五秒開始,從頭到尾會跑六秒,如同時有別的特效的話,會在那些特效後跑,跑的時候會在特效平常的 30% 效果開始跑,跑到 70% 的效果後停止。

這些可客製化的變數叫做 “options”。 :delay, :duration, :from, :to, :queue 等都是 options。 其他的 options 還有很兩三個,像 :fps, :transition, :sync, :direction 等等。 這些不常用到的東西如果要了解的話到此網站看看:
自己探索
script.aculo.us 網站上還有欄位原地修改的功能 (in-place editing),不用按 “submit” 就可以改資料了。 還有,<div> 裡包含的東西可以一整塊拖放,也就是drag and drop。 很有用的。 可惜是這些功能就要自己上它的網站去研究了! 要寫這些先進的功能之前,還是先回到 ajaxscaffold.com,看看它的 ajax 的 scaffold 是怎麼寫的吧。
想參考看看別人 ajax 功夫練到了什麼程度嗎? 雖然以下這些網站沒有開放原始碼,但 blog.wired.com/monkeybites/ 網站上的讀者覺得他們的 Ajax 功能設計的不錯。 來參考看看,以後自己的程式也用的到:
del.icio.us , Local.Live.com , GMail , Google Calendar , Google Spreadsheets , Digg , YouTube , Yelp , Blinklist , Bloglines , Basecamp , Writely , Dimewise , Kayak , Spurl , LibraryThing , Last.fm , NetVibes, Meebo, Mint, Shopify, Traineo。
資源
script.aculo.us Javascript 特效
Ajax library
Thomas Fuchs 的 blog ,他是 script.aculo.us 的作者
December 15th, 2007 on 1:31 am
ruby script/generate scaffold Horse Admin
這段要用scaffold好像又出問題了!
請問scaffold目前到底要如何使用呢?
謝謝!