java局部刷新session过期_Ajax局部页面刷新和History API结合的陷阱
ajax在現(xiàn)代網(wǎng)站已經(jīng)得到非常普遍地應(yīng)用,主要的好處大家都知道(異步加載數(shù)據(jù),不用刷新整個(gè)瀏覽器,更小的數(shù)據(jù)傳輸尺寸)。對于那些老網(wǎng)站或者老項(xiàng)目來說全盤改造成ajax并不現(xiàn)實(shí),于是就有了“局部頁面刷新”這個(gè)解決方案。如果不知道“局部頁面刷新”是何物請看這里,這里和這里。
在我們的項(xiàng)目里,將原來的iframe或者frame統(tǒng)統(tǒng)替換成了時(shí)髦的div,然后修改了頁面上所有發(fā)起請求的地方,把響應(yīng)內(nèi)容jQuery.load到div里。
于是乎原來老舊的網(wǎng)站變成了一個(gè)時(shí)髦的基于ajax的網(wǎng)站,每個(gè)頁面?zhèn)鬏數(shù)臄?shù)據(jù)量變小了,再也不用解決令人頭疼的:
如何訪問parent window變量的問題(還有如何訪問parent的parent的parent... window變量的問題)
如何訪問child iframe里的變量[]的問題了(還有如何訪問child的child的child... iframe里的變量的問題)
因?yàn)榇蠹矣肋h(yuǎn)都在同一個(gè)window里,而且div本身就會(huì)根據(jù)內(nèi)容自動(dòng)撐大。但是等等!瀏覽器怎么不能后退了?
我們的那個(gè)項(xiàng)目是一個(gè)滿大街可見的XX管理信息系統(tǒng),這種系統(tǒng)最常見的布局就是左側(cè)一個(gè)樹形菜單區(qū)域,右側(cè)是一個(gè)功能區(qū)域,功能區(qū)域里有一個(gè)查詢條件區(qū)域(里面有個(gè)查詢按鈕),還有一個(gè)空白的區(qū)域用來顯示查詢結(jié)果,同時(shí)是用戶操作數(shù)據(jù)的地方(比如form表單)。
在iframe時(shí)代,上面講到的4個(gè)區(qū)域都是一個(gè)iframe,這也就意味著我們可以有很變態(tài)的后退能力。
當(dāng)然了一般來說用戶最常用的就是對操作區(qū)域做后退動(dòng)作,比如查詢一下,選擇一條記錄點(diǎn)擊修改,看到form表單,修改一下,在點(diǎn)擊保存前后悔了,點(diǎn)擊瀏覽器的后退,回到查詢結(jié)果頁面。
但是在引入了ajax后無法后退了,因?yàn)閍jax請求不會(huì)記錄到瀏覽器歷史里,歷史都沒有了自然就無法后退了。
好在Html5的History API能夠幫助我們解決問題。我們可以人為的使用history.pushState來人造歷史信息,并且通過監(jiān)聽popstate事件來知道用戶點(diǎn)擊了瀏覽器后退或前進(jìn)按鈕,然后將頁面元素還原到歷史上的某個(gè)狀態(tài)。關(guān)于Html5 History API的相關(guān)信息可以看這里。
但是事情遠(yuǎn)不止這么簡單,下面是我們遇到的一些坑:
陷阱1:重復(fù)執(zhí)行js腳本
// 點(diǎn)擊查詢按鈕的時(shí)候人為構(gòu)造一個(gè)瀏覽器歷史
$('#some-button').click(function() {
$(targetSelector).load(url);
history.pushState({
container : targetSelector,
content : $(targetSelector).html()
}, null, url);
});
// 當(dāng)瀏覽器后退后者前進(jìn)的時(shí)候,我們把當(dāng)時(shí)的結(jié)果重新加載到container里來
window.addEventListener('popstate', function() {
var state = history.state
$(state.container).html(state.content);
})
一切看上去都OK,直到...我們發(fā)現(xiàn)局部頁面刷新所獲得的結(jié)果里包含了操作dom元素的js。
當(dāng)遇到這種情況時(shí)會(huì)發(fā)生很奇妙的現(xiàn)象,history state.content是已經(jīng)加載完畢+js執(zhí)行后的結(jié)果,當(dāng)我們重新還原的時(shí)候,我們會(huì)把這個(gè)結(jié)果加載出來,并且又會(huì)執(zhí)行一遍js。如果這個(gè)js是一個(gè)添加dom的動(dòng)作那么在后退的時(shí)候你會(huì)看到這個(gè)重復(fù)的dom元素。
我們想過跟蹤哪些dom元素是被js修改過的來避免這個(gè)問題,但是...這是不現(xiàn)實(shí)的。
陷阱2:無法還原到最初狀態(tài)
前面的方案因?yàn)閘oad的內(nèi)容里可能有js腳本所以有嚴(yán)重缺陷,于是我們換了個(gè)思路,history里保存responseText,而不是已經(jīng)load好后的東西。
// 點(diǎn)擊查詢按鈕的時(shí)候人為構(gòu)造一個(gè)瀏覽器歷史
$('#some-button').click(function() {
$(targetSelector).load(url, function(responseText) {
history.pushState({
container : targetSelector,
content : responseText
}, null, url);
});
});
// popstate事件的處理方式一樣
但是仍然遇到了這么一個(gè)問題,如果container(刷新目標(biāo)區(qū)域,某個(gè)div)原來是有內(nèi)容的,而這個(gè)內(nèi)容不是通過ajax局部頁面刷新而來,而是用戶一進(jìn)入這個(gè)頁面就已經(jīng)有的,比如使用服務(wù)器端的模板引擎生成的頁面,那么在它加載完html片段后就無法回退了。因?yàn)樗膬?nèi)容一開始就不在history里(事實(shí)上瀏覽器自己產(chǎn)生的history是沒有state的),這樣就形成了退無可退的局面。
如果你想,我們只要保存這個(gè)container原來的內(nèi)容不就行了,當(dāng)后退的時(shí)候我們直接恢復(fù)它原來的內(nèi)容,但是請看陷阱1
不過當(dāng)發(fā)生退無可退的情況時(shí),我們認(rèn)為已經(jīng)退回到了第一次進(jìn)入頁面的狀態(tài),這個(gè)時(shí)候我們刷新整個(gè)頁面就行了。
陷阱3:多個(gè)并列的container
陷阱2的解決方案實(shí)際上是基于container之間是屬于嵌套關(guān)系或者就一個(gè)container的情況的。如果是這種情況就不行了:
有A和B兩個(gè)container,點(diǎn)擊某個(gè)按鈕刷新了A的內(nèi)容(產(chǎn)生歷史),然后在點(diǎn)擊某個(gè)按鈕刷新的B的按鈕(產(chǎn)生歷史),按照用戶的預(yù)想情況,第一次后退還原B原來的內(nèi)容,第二次后退還原A原來的內(nèi)容。但實(shí)際上,第一次后退無法還原B的內(nèi)容(陷阱2),第二次后退頁面刷新了(一切恢復(fù)最初的樣子)。
如果B是嵌套在A里的就無所謂了,第一次后退的時(shí)候獲得的是A的state,根據(jù)A的state還原A的內(nèi)容的時(shí)候順便把B也還原了,第二次后退頁面刷新,把A也還原了。
而且根據(jù)陷阱1所講,我們也不能在history里存儲(chǔ)A或者B里原來的內(nèi)容。
解決辦法:對于這種操作就不要記錄歷史了。
陷阱4:看到過時(shí)頁面
我們在History state里存的是當(dāng)時(shí)load時(shí)的responseText,當(dāng)我們后退的時(shí)候看到的是過時(shí)的頁面,比如我們原先查詢結(jié)果里看到有A記錄,然后我們跳轉(zhuǎn)到其他頁面里,然后再后退到查詢結(jié)果頁面看到A記錄還在,但是這個(gè)A記錄很可能只是一個(gè)幽靈,在數(shù)據(jù)庫里早就已經(jīng)不存在了。如果我們這個(gè)時(shí)候再對A記錄操作就有出現(xiàn)錯(cuò)誤。
解決辦法是我們在history state里保存url已經(jīng)相關(guān)的參數(shù),當(dāng)popstate的時(shí)候重新發(fā)起請求就行了,這樣一來的話也減少了history存儲(chǔ)state所需要的空間。
// 這里只給get請求的例子,post的原理也差不多
$('#some-button').click(function() {
$(targetSelector).load(url, function(responseText) {
history.pushState({
container : targetSelector,
url : url
}, null, url);
});
});
window.addEventListener('popstate', function() {
var state = history.state;
$(state.container).load(state.url);
});
陷阱5:redirect
即使我們在history state保存了url你就以為沒事了?too simple, too naive!如果我們對這個(gè)url發(fā)起的請求被服務(wù)器redirect到另一個(gè)url,那么在history state里保存這個(gè)url就不對了。
如果我們這個(gè)url是用來刪除某條記錄的,服務(wù)器收到請求在數(shù)據(jù)庫里刪除了這條記錄,然后redirect到了首頁url,那么這個(gè)時(shí)候你在history里應(yīng)該存那個(gè)url呢?顯然是首頁的url,因?yàn)槿绻愦媪藙h除url,那么在后退的時(shí)候,我們會(huì)重新發(fā)起這個(gè)url,想想這多嚇人。
解決辦法其實(shí)不太簡單,因?yàn)閍jax是否被redirect你是不知道的,用jQuery封裝的jqXHR對象也沒法知道這個(gè)。
我的做法在服務(wù)器sendRedirect之前在requestUrl的queryString里添加一個(gè)flag,用一個(gè)專門的servlet filter判斷過來的請求是否有這個(gè)flag,如果有那么就將本次請求的url(也就是redirect到的url)放到response的一個(gè)特定的header里。然后就可以用jqXHR.getResponseHeader('some-header')來獲得這個(gè)url,把這個(gè)url放到history state里。
陷阱6:無法精確還原dom對象的狀態(tài)
不論是保存responseText還是保存url請求參數(shù),都無法在瀏覽器后退的時(shí)候精確還原dom對象的狀態(tài),比如我在IE6里有個(gè)這樣的特性,你在某個(gè)頁面勾選了某個(gè)checkbox,然后跳轉(zhuǎn)到一個(gè)新的頁面然后再后退,那個(gè)checkbox還是處于勾選狀態(tài),這個(gè)在利用ajax局部頁面刷新里是完全做不到的,想到用戶和我說以前后退的時(shí)候那個(gè)勾還在現(xiàn)在勾沒有了,不解決這個(gè)BUG就不驗(yàn)收的事情時(shí)才想到iframe的好啊。
所以如果要精確還原dom對象的狀態(tài),得在history.pushState的時(shí)候自行把相關(guān)信息保存下來,在popstate的時(shí)候用到這些信息并還原dom。
事實(shí)上即使用了iframe也并不是所有的瀏覽器會(huì)還原dom對象狀態(tài),看這篇文章。
總結(jié)
不要輕易從iframe切換到ajax局部頁面刷新
要自己控制那些ajax局部頁面刷新紀(jì)錄歷史,哪些不記錄,有些時(shí)候可能還需要replaceState,不要想當(dāng)然的把所有請求都記錄歷史
把代碼改造成ajax局部頁面刷新只是第一步,還需要對整個(gè)網(wǎng)站、應(yīng)用的UI做規(guī)劃和設(shè)計(jì),關(guān)于這個(gè)問題不存在通用的解決方案
參考資料
總結(jié)
以上是生活随笔為你收集整理的java局部刷新session过期_Ajax局部页面刷新和History API结合的陷阱的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android选择头像弹窗,Androi
- 下一篇: 易语言取CPU序列号特征字