非同步 JavaScript 和 XML(Asynchronous JavaScript and XML,Ajax)無疑是最流行的新 Web 技術。本文中我們將完全使用 PHP 和 Simple Ajax Toolkit (Sajax) 建立一個簡單的相簿作為線上 Web 應用程式。我們首先用標準的 PHP 開發方法編寫簡單的相簿,然後再用 Sajax 將其變成活動的 Web 應用程式。
建立一個簡單的相簿
本文將使用兩種方法建立一個簡單的相簿:傳統的 Web 應用程式和基於 Sajax 的應用程式。我們將用 PHP 編寫一個相簿,讀取某一目錄中的內容,顯示縮圖組成的表格。如果使用者單擊一個縮圖,就會完全展開該映像。因為編寫的是傳統應用程式,所以每次單擊都會是一個新的 HTTP 要求,而參數則作為 URL 的一部分傳遞。
您將學習如何將 Sajax 庫應用於相簿,瞭解為何使用 Sajax 可以加快應用程式的開發。
添加一個分頁器表
訪問相簿的使用者需要某種快速查看照片的方法。因為很多大照片不容易在一頁上顯示,所以需要建立一個分頁器 —— 每次顯示少量縮圖的簡單表格。還要編寫導航,協助使用者在映像列表中來回移動。
圖 1. 分頁器提供了顯示使用者照片的一種方式
什麼是 OpenAjax Alliance?
2006 年 5 月 JavaOne Conference 開始前,29 家公司的代表在 Adobe Systems 的會議室裡碰頭,準備大體上確定 Ajax 的未來,這個小組就稱為 OpenAjax Alliance。
小組做了幾項決定,其中最顯著的就是給自己取了個名字:OpenAjax Alliance。它還決定不把自己組織成一個正式的標準團體,或者 Eclipse Foundation 那樣的開放源碼主導的組織,因此小組自身的形式暫時也是非正式的。小組同意大約每周召開一次電話會議。
OpenAjax Alliance 主要關注三個方面:通過提供互通性降低採用 Ajax 的風險,保證 Ajax 解決方案堅持走開放標準路線和使用開放源碼技術,保持 Web 的開放性。小組的第一項任務就是尋求建立和保持 Ajax 工具間互通性的方法。
OpenAjax Alliance 包括 31 家技術公司,其中有 IBM?、Adobe Systems、Eclipse Foundation、Google、Laszlo Systems Inc.、Oracle、Red Hat Inc. 和 Zend Technologies Ltd.。
首先要收集至少 20 幅 .jpg 圖片,並將它們放到一個檔案夾中。每個圖片還要有一個儲存在單獨縮圖檔案夾中的縮圖。雖然可使用 GD 軟體包產生縮圖(請參閱 參考資料),但本文假設已經準備好了縮圖。也可使用本文提供的照片和縮圖(請參閱 下載)。
為了完成本文的剩餘部分,後面假設照片儲存在 /images 子目錄中,縮圖則放在 /images/thumbnails 中。可以在代碼中做適當的修改。此外,我們還假定縮圖和對應的映像使用相同的名稱。
分頁器應該傳遞兩個參數:start 是按照字母順序顯示的第一幅照片的索引號,step 是顯示的照片數。
清單 1. 相簿查看器
/*
* Find a list of images in /images and provide thumbnails
*/
function get_table ( $limit_start = 0, $limit_step = 5 ) {
$images = get_image_list('images');
// Generate navigation for Previous and Next buttons
// Code given below
$output .= '<table class="image_table">';
$columns = 5;
foreach ($images as $index => $image) {
// Begin directory listing at item number $limit_start
if ( $index < $limit_start ) continue;
// End directory listing at item number $limit_end
if ( $index >= $limit_start + $limit_step ) continue;
// Begin column
if ( $index - $limit_start % $columns == 0 ) {
$output .= '<tr>';
}
// Generate link to blown up image (see below)
$thumbnail = '<img src="thumbnails/' . $image . '" />';
$output .= '<td>' . get_image_link($thumbnail, $index) . '</td>';
// Close column
if ( $index - $limit_start % $columns == $columns - 1 ) {
$output .= '</tr>';
}
}
$output .= '</table>';
return $nav . $output;
}
這個表很簡單,它從索引號 $limit_start 開始遍曆圖片列表。然後放上每個圖片的縮圖,每五張圖片作為一行。達到 $limit_start + $limit_step 的時候迴圈結束。
該表是目錄列表的可視化表示,因此需要一個函數列出目錄中的所有映像。清單 1 中的 get_file_list() 函數用索引數組返回 /images 目錄中的所有圖片列表。下面是一個樣本實現。
清單 2. get_file_list 實現
function get_image_list ( $image_dir ) {
$d = dir($image_dir);
$files = array();
if ( !$d ) return null;
while (false !== ($file = $d->read())) {
// getimagesize returns true only on valid images
if ( @getimagesize( $image_dir . '/' . $file ) ) {
$files[] = $file;
}
}
$d->close();
return $files;
}
注意:本文後面還要使用 get_file_list() 函數。有一點很重要,無論何時調用該函數,返回的數組都是不變的。因為提供的實現要進行目錄搜尋,必須保證目錄中的指定檔案不會改變,每次都要按字母順序排序。
導航的實現
雖然表格列出了目錄中的一些映像,但使用者還需要一種查看錶格中未出現的圖片的方法。要真正實現分頁器的導行,則需要一套標準的連結:首頁、上一頁、下一頁和尾頁。
清單 3. 分頁器導航
// Append navigation
$output = '<h4>Showing items ' . $limit_start . '-' .
min($limit_start + $limit_step - 1, count($images)) .
' of ' . count($images) . '<br />';
$prev_start = max(0, $limit_start - $limit_step);
if ( $limit_start > 0 ) {
$output .= get_table_link('<<', 0, $limit_step);
$output .= ' | ' . get_table_link('Prev',
$prev_start, $limit_step);
} else {
$output .= '<< | Prev';
}
// Append next button
$next_start = min($limit_start + $limit_step, count($images));
if ( $limit_start + $limit_step < count($images) ) {
$output .= ' | ' . get_table_link('Next',$next_start, $limit_step);
$output .= ' | ' . get_table_link('>>',(count($images) - $limit_step), $limit_step);
} else {
$output .= ' | Next | >>';
}
$output .= '</h4>';
最後還要編寫 get_image_link() 和 get_table_link() 函數,讓使用者將縮圖展開成完整的映像(參見清單 4)。注意,指令碼 index.php(以及後面要建立的 expand.php)只在這兩個函數中調用。這樣就很容易改變連結的功能。事實上在下面與 Sajax 進行整合時,只有這兩個函數需要修改。
清單 4. get_image_link、get_table_link 實現
function get_table_link ( $title, $start, $step ) {
$link = "index.php?start=$start&step=$step";
return '<a href="' . $link . '">' . $title .'</a>';
}
function get_image_link ( $title, $index ) {
$link = "expand.php?index=$index";
return '<a href="' . $link . '">' . $title . '</a>';
}
放大圖片
現在有了一個可用的分頁器為使用者提供一些縮圖。相簿的第二項功能是允許使用者單擊縮圖來查看全圖。get_image_link() 函數調用了 expand.php 指令碼,我們現在就來編寫它。該指令碼傳遞使用者希望展開的檔案的索引,因此必須在此列出目錄並獲得適當的檔案名稱。隨後的操作就很簡單了,只需建立病輸出 image 標記即可。
清單 5. get_image 函數
function get_image ( $index ) {
$images = get_image_list ( 'images' );
// Generate navigation
$output .= '<img src="images/' . $images[$index] . '" />';
return $output;
}
接下來還要提供與分頁器類似的導航機制。“上一張” 導航到編號為 $index-1 的映像,“下一張” 導航到編號為 $index+1 的映像,“返回” 則回到分頁器。
清單 6. get_image 導航
$output .= '<h4>Viewing image ' . $index .' of ' . count($images) . '<br />';
if ( $index > 0 ) {
$output .= get_image_link('<<', 0);
$output .= ' | ' . get_image_link('Prev', $index-1);
} else {
$output .= '<< | Prev';
}
$output .= ' | ' . get_table_link('Up', $index, 5);
if ( $index < count($images) ) {
$output .= ' | ' . get_image_link('Next', $index+1);
$output .= ' | ' . get_image_link('>>', count($images));
} else {
$output .= ' | Next | >>';
}
$output .= '</h4>';
最後建立一個簡單的 HTML 容器,將其命名為 expand.php。
清單 7. get_image 導航
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Creating a simple picture album viewer</title>
<style type="text/css">
body { text-align: center }
table.image_table { margin: 0 auto 0 auto; width: 700px;
padding:10px; border: 1px solid #ccc; background: #eee; }
table.image_table td { padding: 5px }
table.image_table a { display: block; }
table.image_table img { display: block; width: 120px;
padding: 2px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h1>Creating a simple picture album viewer</h1>
<?php
$index = isset($_REQUEST['index']) ? $_REQUEST['index'] : 0;
echo get_image($index);
?>
</body>
</html>
這樣我們就完成了相簿。使用者可以看到所有的圖片,而且很容易導航。自然,使用者可以來回切換,甚至能通過書籤功能返回喜歡的圖片。
圖 2. 完成的相簿
添加 Sajax
現在相簿提供了基本的導航功能,目錄中的映像添加了索引。下面您將看到添加 Sajax 能夠改進編程和使用者體驗。
這裡假設您對 Ajax 有基本的瞭解,最好還熟悉 Sajax 的基礎知識(請參閱 參考資料 中的教程)。
Sajax、Ajax 與傳統 Web 應用程式
現在我們已經使用標準的 Web 開發模型開發了應用程式。兩項主要功能是分頁器和映像查看器,它們分別對應不同的 PHP 檔案。參數作為 HTTP GET 請求傳遞給指令碼,指令碼直接向 Web 客戶機返回頁面。
圖 3. 在傳統的 Web 應用程式中,三個不同的請求調用了兩個頁面
Web 開發社區的人都知道,Ajax 允許向伺服器發出非同步輔助請求,並直接在網頁中顯示結果( 4 所示)。不幸的是,即便最簡單的 Ajax 應用程式實現起來也是一項大任務。因為 Ajax 不是標準化的技術,Internet Explorer 和其他瀏覽器(如 Firefox、Safari)的實現是不同的。此外,程式員至少要編寫三個函數才能實現一個功能,這三個函數是:發送 HTTP 要求的初始 JavaScript,返迴響應的 PHP 指令碼,以及另一個處理這些響應的 JavaScript 函數。
圖 4. Ajax 應用程式負責所有的 HTTP 要求
建立在 Ajax 庫之上的 Sajax 通過運用簡單的啟發學習法方法大大簡化了這一過程:Web 客戶機需要訪問的每個 PHP 函數都由 Sajax “匯出”。如果有一個名為 foo_bar() 的 PHP 函數,那麼 Sajax 會把該函數匯出為 JavaScript 函數 x_foo_bar()。客戶機對 x_foo_bar() 的任何調用都會自動轉寄給伺服器上的 foo_bar(),輸出則傳遞給另一個 JavaScript 函數。清單 8 中的簡短頁面示範了這種功能。運行這個例子需要下載 Sajax 庫(請參閱 參考資料)。
清單 8. Sajax 的應用
<?php
require("Sajax.php");
function foo_bar ( $param ) {
return "You typed: $param";
}
$sajax_request_type = "GET"; // Set HTTP request type to GET
sajax_init(); // Prepare Sajax
sajax_export("foo_bar"); // foo_bar can now be called by client
sajax_handle_client_request(); // Discussed below
?>
<html>
<head>
<script language="javascript">
<? sajax_show_javascript(); ?>
</script>
</head>
<body>
<form onSubmit="x_foo_bar(this.input.value, alert);return false;">
<input type="text" name="input" />
</form>
</body>
</html>
如果開啟清單 8 中的頁面,在輸入框中輸入一些內容然後單擊 Enter,那麼輸入內容就會在一個警告框中顯示出來。但在這個看似簡單的網頁背後,x_foo_bar() JavaScript 函數將遠程調用 foo_bar() 函數,並把響應傳遞給 JavaScript 內建函數 alert()。每個 Sajax 匯出函數的最後一個參數都是一個響應處理常式,負責處理 foo_bar() 的輸出。
這個例子還說明了 Sajax 快速開發的另一個重要特性:不需要每個函數都有一個單獨的檔案,頁面實際上調用的是其自身,因此更便於跟蹤函數的調用( 5 所示)。x_foo_bar() 函數直接向頁面發回 Ajax 請求,在請求中包含函數名和參數。關鍵是 sajax_handle_client_request() 函數,它截獲所有的 Sajax 調用並自動對它們進行處理。
圖 5. 使用 Sajax 客戶機可通過一個頁面訪問伺服器端的多個函數
將 Sajax 串連到相簿
利用剛剛建立的代碼,我們將用 Sajax 迅速把相簿從多頁面應用程式轉化成活動的 Ajax 應用程式。
因為相簿主要有兩個函數,get_table() 和 get_image(),這也是需要用 Sajax 匯出的全部函數。事實上,為了通過 Sajax 調用這些函數,這些函數本身基本上不需要修改,很快我們就會看到,我們只需要修改產生的連結即可。
清單 9. Sajax 相簿的頭部
<?php
require("Sajax.php");
function get_image () { } // Defined later
function get_thumbs_table () { } // Defined later
// Standard Sajax stuff. Use Get, and export two
// main functions to javascript
$sajax_request_type = "GET";
sajax_init();
sajax_export("get_thumbs_table", "get_image");
sajax_handle_client_request();
?>
對於本文而言,文檔主體部分很簡單。我們將使用 div 和 window 的 id 來顯示伺服器的輸出。
清單 10. 顯示伺服器輸出的 div 和 window id
<body>
<h1>Sajax photo album</h1>
<div id="window"></div>
</body>
最後還要編寫 JavaScript 回呼函數。該例中,因為所有的伺服器輸出都直接輸出到 window div 標記,所以可以重複使用簡單的回呼函數。將回呼函數添加到 Sajax 函數調用中,就可以得到頭(head)。
清單 11. 簡單的頭
<head>
<title>Creating a Sajax photo album</title>
<style type="text/css">
body { text-align: center }
div#window { margin: 0 auto 0 auto; width: 700px;
padding: 10px; border: 1px solid #ccc; background: #eee; }
table.image_table { margin: 0 auto 0 auto; }
table.image_table td { padding: 5px }
table.image_table a { display: block; }
table.image_table img { display: block; width: 120px
padding: 2px; border: 1px solid #ccc; }
img.full { display: block; margin: 0 auto 0 auto;
width: 300px; border: 1px solid #000 }
</style>
<script language="javascript">
<? sajax_show_javascript(); ?>
// Outputs directly to the "window" div
function to_window(output) {
document.getElementById("window").innerHTML = output;
}
window.onload = function() {
x get table to window);
};
</script>
</head>
最後一步是保證應用程式中的所有連結都是自訂的 Sajax 調用。只需要取上一節中的代碼並作如下替換:href="index.php?start=0&step=5" 變為 onclick="x_get_table(0, 5, to_window)",href="expand.php?index=0" 變為 onclick="x_get_image(0, to_window)"。
並在相應的函數中做同樣修改:get_image_link() 和 get_table_link()。這樣向 Sajax 的轉化就完成了( 6 所示)。所有連結都變成了與遠程 PHP 調用對應的 JavaScript 調用,PHP 使用 JavaScript 響應處理常式 to_window() 直接輸出到頁面。
整個應用程式都包含在一個頁面中,還可以把其餘功能(get_table()、get_image() 等)放在不能從 Web 存取的單獨的庫檔案中。在大多數 Ajax 應用程式中,每個發往伺服器的請求都需要由單獨的指令碼處理,或至少需要編寫一個非常龐大的處理常式指令碼來重新導向請求。將所有這些檔案都集中到一起可能非常麻煩。使用 Sajax 永遠只需要一個檔案,在該檔案中只需定義我們使用的函數即可。Sajax 代替了處理常式指令碼。
圖 6. 完成的基於 Sajax 的相簿(縮圖)
可以看到 URL 仍然保持不變,並帶來了更多愉快的使用者體驗。window div 顯示在一個灰色的框中,通過 Sajax 產生的內容非常清楚。指令碼不一定要知道自身或者它在伺服器上的位置,因為所有的連結最終都成為直接對頁面自身的 JavaScript 調用。因此我們的代碼能夠很好的模組化。我們只需要保持 JavaScript 和 PHP 函數在同一個頁面上即可,即使頁面位置發生了變化也沒有關係。
擴充相簿
使用 Sajax 把我們的相簿變成活動的 Web 應用程式如此輕而易舉,我們要再花點時間添加一些功能,進一步說明 Sajax 如何使從伺服器檢索資料變得完全透明。我們將為相簿添加中繼資料功能,這樣使用者就能為他們的圖片添加說明。
中繼資料
沒有上下文說明的相簿是不完整的,比如照片的來源、作者等。為此我們要將映像集中起來建立一個簡單的 XML 檔案。根節點是 gallery,它包含任意多個 photo 節點。每個 photo 節點都通過其 file 屬性來編號。在 photo 節點中可以使用任意多個標記來描述照片,但本例中只使用了 date、locale 和 comment。
清單 12. 包含中繼資料的 XML 檔案
<?xml version="1.0"?>
<gallery>
<photo file="image01.jpg">
<date>August 6, 2006</date>
<locale>Los Angeles, CA</locale>
<comment>Here's a photo of my favorite celebrity</comment>
</photo>
<photo file="image02.jpg">
<date>August 7, 2006</date>
<locale>San Francisco, CA</locale>
<comment>In SF, we got to ride the street cars</comment>
</photo>
<photo file="image03.jpg">
<date>August 8, 2006</date>
<locale>Portland, OR</locale>
<comment>Time to end our road trip!</comment>
</photo>
</gallery>
檔案的解析不在本文討論範圍之列。我們假設您能夠熟練使用 PHP 中眾多 XML 解析方法中的一種。如果不熟悉的話,建議閱讀 參考資料 中的文章。我們不再浪費時間解釋如何將該檔案轉化成 HTML,作為一個練習,讀者可以自己瞭解下面的代碼如何使用 XML 檔案並產生 HTML。清單 13 中的代碼使用了 PHP V5 中內建的 SimpleXML 包。
清單 13. 中繼資料函數
function get_meta_data ( $file ) {
// Using getimagesize, the server calculates the dimensions
list($width, $height) = @getimagesize("images/$file");
$output = "<p>Width: {$width}px, Height: {$height}px</p>";
// Use SimpleXML package in PHP_v5:
// http://us3.php.net/manual/en/ref.simplexml.php
$xml = simplexml_load_file("gallery.xml");
foreach ( $xml as $photo ) {
if ($photo['id'] == $file) {
$output .= !empty($photo->date) ? "<p>Date taken:{$photo->date}</p>" : '';
$output .= !empty($photo->locale) ? "<p>Location:{$photo->locale}>/p>" : '';
$output .= !empty($photo->comment) ? "<p>Comment:{$photo->comment}</p>" : '';
}
}
return $output;
要注意的是,get_meta_data() 函數中還使用 getimagesize()(一個核心 PHP 函數,不需要 GD)計算映像的大小。
再回到 get_image() 函數,它包含由 get_image_list() 產生的檔案名稱的列表。尋找中繼資料只需要將檔案名稱傳遞給該函數即可。
清單 14. 添加中繼資料
function get_image ( $index ) {
$images = get_image_list ( 'images' );
// ...
$output .= '<img src="images/' . $images[$index] . '" />';
$output .= '<div id="meta_data">' .
get_meta_data( $images[$index] ) . '</div>';
return $output;
}
重新開啟頁面將看到伺服器請求的結果。圖 7 顯示了帶有中繼資料的放大的映像。
圖 7. 使用中繼資料的相簿
結束語
我們看到,使用 Sajax 可以消除客戶機和伺服器之間的障礙,程式員能夠進行無縫遠程函數調用而不用擔心傳輸層、HTTP GET 和 POST 請求。我們可以花更多時間編寫提供資料的 PHP 指令碼以及展示層和控制層的 JavaScript。在這個相簿例子中,我們讓客戶機直接連接到映像資料庫。通過添加簡單的中繼資料,我們看到讓使用者直接存取伺服器上的資訊是多麼簡單,無需擔心協議的問題。
與所有的 Ajax 應用程式一樣,我們的相簿也有一個致命的弱點:沒有使用瀏覽器的 “訪問曆史”,因為破壞了後退按鈕的功能。在 “利用 PHP 開發 Ajax 應用程式” 系列的第 2 部分中,我們將通過實現記錄緩衝和狀態跟蹤機制來解決這個問題。