讓AJAX動態內容支援瀏覽器回上頁功能
4 |
隨著AJAX動態更新技術的普及,手邊專案有愈來愈多網頁開始實現"無PostBack"的設計風格,透過jQuery $.post(), $.get()與ASP.NET程式溝通,執行查詢、更新作業並取得結果,再動態改變HTML DOM回應使用者。(註: 對ASP.NET開發者來說,UpdatePanel是另一個無痛實現AJAX化的選項,但有些副作用)
由使用者的回饋來看,減少網頁PostBack與網頁重新導向次數,確實大幅提高操作回應速度,提供更好的操作體驗,不過倒有一個常被垢病之處 --- 使用者明明覺得點選操作後瀏覽器切換到下一個畫面,為什麼按覽器的回上頁卻無法回到上個畫面?
細究這個問題的根源,在於程式是透過AJAX方式取得下一畫面內容更新在網頁上,或是動態顯示/隱藏不同的DOM元素,給予使用者"由操作網頁A切換到操作網頁B"的認知,但以瀏覽器的角度,卻始終是同一個網頁;由於使用者覺得網頁已被切換過,當需要回前一個介面進行資料修改或重新選擇時,直覺上便會去點選"回上頁",結果跳過了AJAX切換介面的過程,直接進入這個網頁前的瀏覽網頁,造成認知與執行結果的落差,形成抱怨。
當然,在吃過幾次悶虧(例如: 回上頁造成結果沒儲存之類的,這個倒有解藥),碰了一鼻子灰之後,使用者就會學乖,自動避開"在某某網頁不能按【回上頁】,很可怕,不要問"的地雷。或者,我們也可以恐嚇提示或教育使用者避免在這類介面按回上頁,或是在網頁加上自己寫的"AJAX版回上頁"按鈕實現切換回上一個UI效果做為替代,不過,這些治標做法終究無法完全避免使用者誤觸回上頁所造成的困擾。
用一個實例來展示這個問題:
<!DOCTYPE html>
<html>
<head>
<title>AJAX GoBack</title>
<script src="Scripts/jquery-1.6.3.js" type="text/javascript"> </script>
<script>
$(function () {
$("#s1").show();
$("input.nav-btn").click(function () {
var $btn = $(this);
//隱藏目前按鈕所在的div
$btn.parent().hide();
//由按鈕的data-nav屬性決定要顯示div的id
$("#" + $btn.data("nav")).show();
});
});
</script>
<style>
#main div { width: 300px; height: 200px; display: none; padding: 10px; }
#s1 { background-color: #ff7777; }
#s2 { background-color: #77ff77; }
#s3 { background-color: #7777ff; }
</style>
</head>
<body>
<div id="main">
<div id="s1">
STEP1
<input type="button" class="nav-btn" value="Next" data-nav="s2" />
</div>
<div id="s2">
STEP2
<input type="button" class="nav-btn" value="Next" data-nav="s3" />
</div>
<div id="s3">
FINAL
<input type="button" value="Submit" />
</div>
</div>
</body>
</html>
以上的網頁包含三個<div>,假裝是輸入流程中的三個步驟,STEP1按Next可以跳到STEP2,STEP2按Next可以跳到STEP3。在操作上,讓使用者感覺在不同的網頁介面間切換,但對瀏覽器來說,自始至終都在同一網頁,於是,如圖所以,操作時連回上頁鈕都沒得按。
搞HTML的老骨頭可能記得#書籤的用法(參考[請看"書籤"相關說明]),在網頁MyPage.htm中放入<a name="myBookmark"></a>,當URL指定MyPage.htm#myBookmark,瀏覽器就會開啟該網頁並捲動到<a name="myBookmark"></a>的所在位置。有趣的是,MyPage.htm#a跟MyPage.htm#b可被視為不同網址(這表示在瀏覽歷史中會出現一筆),卻又不會觸發網頁重新載入,這個特性讓AJAX回上頁問題出現一線曙光。
在URI規範中,#符號後方所帶的資訊被定義成Fragment Identifier(#是Hash symbol,#後方的內容也被稱為hash),不只是作為書籤定址,還可實現AJAX的歷史巡覽功能(from Wiki: With the rise of AJAX, some websites use fragment identifiers to emulate the back button behavior of browsers for page changes that do not require a reload, or to emulate subpages.),硬綁綁的規範讓人頭好痛,這裡只研究怎麼應用就好。
以下是一個簡單的hash應用示範(請使用IE8/9/10的標準模式、Firefox、Chrome或Safari執行)
<!DOCTYPE html>
<html>
<head>
<title>Hash Demo</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.3.js">
</script>
<script>
$(function () {
function setHash(x) {
var n = x;
//在第n秒將loation.href改為後方加#sn
//注意: 只變動#以後的參數不會導致網頁重新載入
setTimeout(function () {
location.hash = "#s" + n;
}, n * 1000);
}
//在第0,1,...,7秒為location.url加上#s0-#s7
for (var i = 0; i < 8; i++) setHash(i);
$("#btnShowHash").click(function () {
$("#ulDisp").append("<li>hash=" + location.hash + "</li>");
});
});
</script>
</head>
<body>
<input type="button" id="btnShowHash" value="Show location.hash" />
<ul id="ulDisp"></ul>
</body>
</html>
網頁裡有一小段程式,會在第0-7秒時,在URL的最後方加上#s0-#s7,執行完畢後,按瀏覽器回上頁及回下頁可在不同的#sn間切換,按下"Show location.href"則會顯示目前的location.hash,當來回切換上一頁下一頁,下方hash=#sn的<li>顯示並不會被清除,驗證了網頁並未被重新載入,Javascript及DOM的狀態在切換過程中不會遺失。
但很不幸地,IE7(或IE8/9開相容模式)對location.hash特性支援不夠完整,hash改變時並不會在瀏覽歷史中產生記錄,因此需要透過加入隱藏式iframe等特殊手法來克服,自己處理跨瀏覽器議題太辛苦,有個很好用的jQuery外掛BBQ(不是中秋烤肉巴比Q,是Back Button & Query)可以讓我們輕鬆寫出跨瀏覽器版的AJAX回上頁功能。
小小改寫前述STEP1-STEP2-FINAL網頁,主要是在切換網頁時透過$.bbq.pushState()將目前的階段設成location.hash(#step=sn),另外在window.onhashchage事件則加入一小段程式,讀取hash值決定現在是STEP1,2還是FINAL,以及該顯示哪一個<div>。如此,一旦使用者按下回上頁/回下頁,URL中的hash不同就會觸發hashchange事件,程式就能依指定的階段切換不同的<div>。
<!DOCTYPE html>
<html>
<head>
<title>AJAX GoBack</title>
<script src="Scripts/jquery-1.6.3.js" type="text/javascript"> </script>
<script src="Scripts/jquery.ba-bbq.js" type="text/javascript"> </script>
<script>
$(function () {
$("#s1").show();
$("input.nav-btn").click(function () {
var $btn = $(this);
$btn.parent().hide();
var nav = $btn.data("nav");
$("#" + nav).show();
//將目前的步驟加註在location.hash
$.bbq.pushState({ step: nav });
});
//hash變化時觸發hashchange事件
$(window).bind('hashchange', function (e) {
//由hash取出step參數,決定要顯示哪一個div
var s = e.getState("step") || "s1";
if (!$("#" + s + ":visible").length) {
$("#main > div").hide();
$("#" + s).show();
}
});
});
</script>
<style>
#main div { width: 300px; height: 200px; display: none; padding: 10px; }
#s1 { background-color: #ff7777; }
#s2 { background-color: #77ff77; }
#s3 { background-color: #7777ff; }
</style>
</head>
<body>
<div id="main">
<div id="s1">
STEP1
<input type="button" class="nav-btn" value="Next" data-nav="s2" />
</div>
<div id="s2">
STEP2
<input type="button" class="nav-btn" value="Next" data-nav="s3" />
</div>
<div id="s3">
FINAL
<input type="button" value="Submit" />
</div>
</div>
</body>
</html>
經過這番加工,網頁就能跨瀏覽器支援回上頁功能,我們又向高級AJAX網頁開發再前進一步囉!
【2011-09-23更新】另外補充HTML5支援AJAX動態內容瀏覽歷程的新做法。
Comments
# by Ammon
用 Hash 已經過時了,看看新的 History API ( http://goo.gl/47dk3 ) 透過 pushState popState replaceState 改變網址並且不會 reload 網頁,Facebook 和 github 都有用這項技術。
# by Jeffrey
to Ammon, 感謝提供HTML5武器新知,稍後會再整理補充。
# by 星寂
我記得ASP.NET Ajax 3.5 SP 1已經有這個問題的解決方式(http://www.dotblogs.com.tw/alonstar/archive/2009/11/19/ajax-history-support.aspx),也是用HASH去控制。JQuery也有UI可以控制,單純的用HASH如果是複雜的網址會不好處理。
# by Jeffrey
to 星寂, 謝謝補充,這再度證明了UpdatePanel真是很優秀的傻瓜式AJAX實作選擇。