天天看點

使用 jQuery Mobile 與 HTML5 開發 Web App —— jQuery Mobile 頁面事件與 deferred

在系列的上一篇文章《使用 jQuery Mobile 與 HTML5 開發 Web App —— jQuery Mobile 事件詳解》中,Kayo 介紹了除頁面事件外的其他 jQuery Mobile 事件,而頁面事件由于事件數較多,并且涉及 jQuery 中一個比較複雜的對象 deferred ,是以在本文中單獨說明。jQuery Mobile 頁面事件使用分為頁面加載事件 (Page load events),頁面跳轉事件 (Page change events),頁面顯示/隐藏事件 (Page show/hide events),頁面初始化事件 (Page initialization events),頁面移除事件 (Page remove events) 五種。本文除了會對以上五種事件作出詳細說明外,還會對 jQuery Mobile 的頁面加載流程作出詳細講解。

一.頁面加載事件 (Page load events)

當一個外部頁面加載到 DOM 時,會觸發兩個事件 —— 第一個事件是 pagebeforeload,第二個是 pageload 或 pageloadfailed。jQuery Mobile 提供了這些 API ,可以使開發者可以友善地在頁面加載前後對頁面資料進行處理。

注意這裡是外部頁面加載到 DOM 的過程 ,即加載的頁面不在目前頁面的文檔中,而一個文檔中的多個 "page" 是本來就存在于 DOM 中,是以在同一文檔中的不同 "page" 的跳轉不會觸發 pagebeforeload 事件。關于 jQuery Mobile 中“page”的了解,可以閱讀《使用 jQuery Mobile 與 HTML5 開發 Web App —— jQuery Mobile 頁面與對話框》

pagebeforeload

pagebeforeload 事件會在頁面加載前被觸發,這個事件的最常用執行個體是 —— 為綁定該事件的回調函數調用 preventDefault() ,阻止這個事件的預設行為,這樣表明由事件的回調函數進行自定義處理頁面,這時開發者必須使用 deferred 對象調用 resolve() 或者 reject() 在自定義處理結束後恢複頁面請求。

在說明為什麼需要用 resove() 或 reject() 恢複頁面請求前,首先要詳細介紹 deferred 對象。

deferred 對象從 jQuery 1.5.0 引入,jQuery Mobile 是基于 jQuery 庫的,當然也可以使用 deferred 對象。在舊版本的 jQuery 中,回調函數功能很單一,就事件機制為例,通常隻能是事件觸發後指定調用一個函數(即為事件綁定的回調函數),然後執行該函數。而引入 deferred 後,jQuery 的回調函數功能則強大很多了,deferred 從字面上來說是“延時”的意思,但 deferred 對象的功能不止如此,它除了延時操作外(解決耗時操作問題),還進行了統一封裝,為回調函數的相關處理提供統一程式設計接口。

deferred 對象是在 $.ajax() 内部實作的,是以可以在調用 ajax 時建立 deferred 對象并調用。jQuery Mobile 是基于 ajax 的,包括它的頁面加載,并且頁面加載的過程在内部實作時已經建立了相應的 deferred 對象,是以在調用頁面事件的回調函數時可以調用 deferred 對象。

是以,我們隻需指定 ajax 請求成功和失敗時的回調函數清單即可友善的進行回調,這兩種回調可以分别寫在 done() 和 fail() 方法中,而上面介紹的 resolve() 或 rejected() 方法,則分别可以手動執行 done() 和 fail() 方法。這時候 deferred 的作用大緻已經說明了。

這裡再引入一個概念,deferred 狀态。deferred 有三種狀态:初始化(unresolved),成功(resolved),失敗(rejected)。正如上面所說, deferred 執行哪些回調函數是依賴于狀态。

簡而言之,deferred 的用途是根據不同的請求狀态,調用相應的回調隊列,使到開發者可以友善地建立一系列的回調,甚至是鍊式調用,而不需要像傳統方法那樣隻能為一個動作綁定一 個回調函數,另外它還有 resolve() 和 rejected() 等方法,使到這個回調處理可以更加靈活。

接下來再回到 pagebeforeload 事件上,上面隻是說明了 deferred 的主要作用,但并沒有說明 deferred 在頁面加載流程中的具體功能。在這之前,我們應該了解新頁面載入的流程是怎樣的?

當我們觸發了一個跳轉到新頁面的連結後,jQuery Mobile 會調用 $.mobile.changePage() 方法,這是 jQuery Mobile 用于加載新頁面的方法,日後會另作介紹,調用這個方法後,加載頁面的流程正式開始,為了進一步說明這個流程,下面 Kayo 節選 $.mobile.changePage() 的源碼并注釋。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

// 若 toPage 參數為字元串,即第一個參數格式正确,則在 $.mobile.changePage() 内調用 $.mobile.loadPage 方法

if

(

typeof

toPage ===

"string"

) {

$.mobile.loadPage( toPage, settings )

// 指定 done() 回調操作隊列

.done(

function

( url, options, newPage, dupCachedPage ) {

isPageTransitioning =

false

;

options.duplicateCachedPage = dupCachedPage;

$.mobile.changePage( newPage, options );

})

// 指定 fail() 回調操作隊列,并鍊式調用

.fail(

function

( url, options ) {

isPageTransitioning =

false

;

// 清除使用者點選的按鈕的激活狀态

removeActiveLinkClass(

true

);

// 釋放 transition

releasePageTransitionLock();

settings.pageContainer.trigger(

"pagechangefailed"

, triggerData );

});

return

;

}

從上面的源碼中可以看出,實際上加載頁面内容是由另一個方法 $.mobile.loadPage() 負責,$.mobile.changePage() 的責任是指定 $.mobile.loadPage() 請求成功和請求失敗時的回調隊列,即請求成功或失敗後分别需要做些什麼。是以不難想象,實際利用 deferred 對象的也是 $.mobile.loadPage() 。

這裡 Kayo 需要指出兩點:一是這裡 done() 和 fail() 是鍊式寫法,即調用 done() 後會繼續調用 fail() ,這是因為無論頁面請求成功與否,有一些操作(像清除使用者點選的按鈕的激活狀态)都是必須的,是以 jQuery Mobile 采用鍊式寫法,把這部分操作寫在 fail() 中,若頁面請求失敗,則直接調用 fail() ,若請求成功則調用 done() 後鍊式調用 fail() 。二是以上兩個方法的實際作用,由于篇幅有限,這裡沒有列出完整源碼,無法看出以上兩個方法更深入的作用,實際上 $.mobile.loadPage() 的作用是把外部頁面的元素插入到目前 DOM 中,而 $.mobile.changePage() 隻是把新頁面顯示(激活一個頁面),是以外部頁面才需要首先調用 $.mobile.loadPage() 把元素插入目前 DOM 中。

下面再對 $.mobile.loadPage() 的源碼進行分析,這裡會為整個 $.mobile.loadPage() 方法的源碼進行注釋,但為了友善閱讀,不會列出全部的源碼實體,以源碼注釋代替完整源碼。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

$.mobile.loadPage =

function

( url, options ) {

// 建立一個 deferred 對象,用于告知調用者 $.mobile.changePage() 頁面請求成功或是出現錯誤

// 也就是讓 $.mobile.changePage() 知道需要調用 done() 或是 fail() 回調隊列

var

deferred = $.Deferred(),

// … 根據參數處理頁面資料 …

// 觸發一個 pagebeforeload 事件

var

mpc = settings.pageContainer,

pblEvent =

new

$.Event(

"pagebeforeload"

),

// 儲存頁面選項在 data 參數中

triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };

// 讓監聽器知道正準備加載一個新頁面

mpc.trigger( pblEvent, triggerData );

// 如果開發者阻止了預設行為,本函數馬上結束,并傳回 deferred 對象的 promise() 方法

if

( pblEvent.isDefaultPrevented() ) {

return

deferred.promise();

}

// 提示正在加載頁面

// 使用 ajax 把頁面插入 DOM ,然後根據 ajax 請求成功還是失敗作出相應的處理

// 請求成功

// 調用 resolve() 方法使 deferred 狀态為成功

deferred.resolve( absUrl, options, page, dupCachedPage );

// 請求失敗

// 調用 rejected() 方法使 deferred 狀态為失敗

deferred.reject( absUrl, options );

// 傳回一個 deferred 對象的 promise() 方法

return

deferred.promise();

};

看了上面的代碼和注釋後,相信大家對頁面加載的流程以及頁面事件的觸發時間已經有了完整的了解,deferred 對象起到了控制使用哪種回調的作用,這些回調的具體内容是在 $.mobile.changePage() 中分别以 done() 和 fail() 指定的。

若使用者沒有阻止事件的預設行為,則根據請求成功或失敗,會調用 resolve() 或 rejected() 方法改變 deferred 的狀态,然後根據狀态判斷是調用 done() ,或是直接調用 fail() 。

若使用者阻止事件的預設行為,jQuery Mobile 會馬上終止加載頁面,并傳回一個 deferred 的 promise() 方法,promise() 方法傳回的是 deferred 的隻讀版本,即傳回一個可讀不可寫的 deferred 對象,除此之外沒有任何處理了。但是上面的源碼注釋中已經說明,$.mobile.loadPage 會把一系列的屬性儲存在 data 參數中,其中包括了 deferred 對象,我們知道,開發者可以為事件綁定一個回調函數,這個 data 對象會被配置設定到 pagebeforepage 事件的回調函數的第二個參數中(第一個參數為事件本身),是以我們可以調用這個 data.deferred 對象的 resolve() 或 rejected() 方法恢複請求,實際上這裡的恢複請求是告知調用者 $.mobile.changePage() 這次 ajax 請求是成功還是失敗,使到 $.mobile.changePage() 方法知道需要調用 done() 回調隊列還是直接調用 fail() 回調隊列。

resolve() 或 rejected() 中填寫的參數實際上也就是 done() 或 fail() 中相應的參數。

當然,若開發者沒有阻止預設行為,這個 $.mobile.loadPage() 在最後也會觸發 resolve() 或 rejected() 方法,是以實際上有沒有利用阻止預設行為進行自定義處理的差別是 —— 是否進行頁面處理和觸發 loadpage 或 loadpagefailed 事件,即自定義處理不會有上面的源碼注釋中使用 注釋的部分,或者說,自定義處理需要做的,就是自行設計一些處理,來代替 注釋中的預設處理。

這就意味着,若有需要的話,阻止預設行為并進行自定義處理頁面(調整 jqm header, jqm title 移除 loading)後還必須手動觸發 loadpage 事件或 loadpagefailed 事件。

在說明整個加載頁面流程後,這裡再介紹一下上面所說的傳遞給 pagebeforeload 事件的回調函數的第二個參數 —— data 的各項屬性。

  • url (string) 通過回調函數傳遞絕對或者相對的 url 到 $.mobile.loadPage()
  • absUrl (string) 絕對 url
  • dataUrl (string) 當識别頁面或者更新浏覽器位址的時候使用過濾過的絕對 url
  • deferred (object) 回調函數中調用 preventDefault() ,必須使用 resolve() 或 rejected() 恢複 changePage() 請求
  • options (object) 這個對象包含了需要傳遞給 $.mobile.loadPage() 的選項

最後引用 jQuery Mobile 官方的例子來說明如何阻止預設行為并進行自定義處理,為了進一步說明,Kayo 會修改一下這些例子。

調用 resolve()

1 2 3 4 5 6 7 8 9 10 11 12

$( document ).bind(

"pagebeforeload"

,

function

( event, data ){

// 阻止預設行為,告知浏覽器本次事件由事件的回調函數(即本函數)處理

event.preventDefault();

// 自定義處理,如簡單的彈出一個提示

alert(

'本次事件由開發者作出一些處理'

);

// 在本函數或者其它異步方法中調用 resolve()

// 告知 $.mobile.changePage() 方法繼續頁面請求并執行 done() 回調函數隊列

data.deferred.resolve( data.absUrl, data.options, page );

});

調用 reject()

1 2 3 4 5 6 7 8 9 10 11 12

$( document ).bind(

"pagebeforeload"

,

function

( event, data ){

// 阻止預設行為,告知浏覽器本次事件由事件的回調函數(即本函數)處理

event.preventDefault();

// 自定義處理,如簡單的彈出一個提示

alert(

'本次事件由開發者作出一些處理'

);

// 在本函數或者其它異步方法中調用 rejected()

// 告知 $.mobile.changePage() 方法繼續頁面請求并直接執行 fail() 回調函數隊列

data.deferred.rejected( data.absUrl, data.options );

});

pageload

pageload 事件的觸發流程相對 pagebeforeload 來說則較為簡單,當頁面成功加載并插入到 DOM 後會觸發 pageload 事件,這個事件也會傳遞一個 data 參數作為事件回調函數的第二個參數,但這個 data 參數與 pagebeforeload 的屬性有些不同,下面列出完整的屬性。

  • url (string) 通過回調函數傳遞絕對或者相對的 url 到 $.mobile.loadPage()
  • absUrl (string) 絕對 url
  • dataUrl (string) 當識别頁面或者更新浏覽器位址的時候使用過濾過的絕對 url
  • options (object) 這個對象包含了需要傳遞給 $.mobile.loadPage() 的選項
  • xhr (object) 當嘗試加載頁面時,會使用 jQuery XMLHttpRequest 對象,這個也是 $.ajax() 成功回調函數的第三個參數
  • textStatus (null or string) 根據 jQuery 核心文檔,請求時會以一個字元串描述狀态,這也是 $.ajax() 失敗回調函數的第二個參數

可以看出,jQuery Mobile 沒有為 pageload 提供 deferred 對象屬性,這說明頁面請求成功後,隻能按預設情況執行 done() 隊列,不能利用 deferred 對象直接執行 fail() 隊列。

pageloadfailed

pageloadfailed 事件是頁面請求失敗時觸發的,參數與 pageload 相似,但這裡再次引入 deferred 對象,這表明 jQuery Mobile 允許開發者即使頁面請求失敗,仍可利用 deferred 對象選擇執行 done() 隊列或 fail() 隊列 。具體的 data 屬性如下。

  • url (string) 通過回調函數傳遞絕對或者相對的 url 到 $.mobile.loadPage()
  • absUrl (string) 絕對 url
  • dataUrl (string) 當識别頁面或者更新浏覽器位址的時候使用過濾過的絕對 url
  • deferred (object) 回調函數中調用 preventDefault() ,必須使用 resolve() 或 reject() 恢複 changePage() 請求
  • options (object) 這個對象包含了需要傳遞給 $.mobile.loadPage() 的選項
  • xhr (object) 當嘗試加載頁面時,會使用 jQuery XMLHttpRequest 對象,這個也是 $.ajax() 成功回調函數的第三個參數
  • textStatus (null or string) 根據 jQuery 核心文檔,請求時會以一個字元串描述狀态,這也是 $.ajax() 失敗回調函數的第二個參數
  • errorThrown (null, string, object) 根據 jQuery 核心文檔,這個屬性可能會被用作 HTTP 狀态的一部分,這也是 $.ajax() 失敗回調函數的第三個參數。

上面介紹 pagebeforeload 事件時,已經介紹了使用 preventDefault() 阻止事件預設行為,然後進行自定義的處理,并且重新使用 deferred 對象的 resolve() 或 rejected() 方法恢複頁面請求。而在 pageloadfailed 事件中也有類似的做法,這裡的自定義處理通常是在頁面請求失敗後加載另一個頁面,下面舉例說明。

調用 resolve()

1 2 3 4 5 6 7 8 9 10 11 12 13

$( document ).bind(

"pageloadfailed"

,

function

( event, data ){

// 阻止預設行為,告知浏覽器本次事件由事件的回調函數(即本函數)處理

event.preventDefault();

// 自定義處理,嘗試加載另一個頁面

// 在本函數或者其它異步方法中調用 resolve()

// 告知 $.mobile.changePage() 方法繼續頁面請求并執行 done() 回調函數隊列

data.deferred.resolve( data.absUrl, data.options, page );

});

調用 rejected()

1 2 3 4 5 6 7 8 9 10 11 12 13

$( document ).bind(

"pageloadfailed"

,

function

( event, data ){

// 阻止預設行為,告知浏覽器本次事件由事件的回調函數(即本函數)處理

event.preventDefault();

// 自定義處理,嘗試加載另一個頁面

// 在即本函數或者其它異步方法中調用 rejected()

// 告知 $.mobile.changePage() 方法繼續頁面請求并直接執行 fail() 回調函數隊列

data.deferred.reject( data.absUrl, data.options );

});

下面給出一個完整例子來驗證自定義處理的方法中需要代替的是頁面處理和觸發 pageload 或 pageloadfailed 事件,本例子中,Kayo 阻止了 pagebeforeload 的預設行為,注意,Kayo 在例子中綁定了 pageload 事件,并在其回調函數中添加彈出提示,但因為進行自定義處理,不會産生 pageload 事件,是以不會彈出加載 pageload 事件的提示,隻有“自定義處理”的彈出提示。另外,讀者可以嘗試在 Demo 中的兩個頁面中來回點選幾次,會發現隻有第一次點選會彈出“自定義處理”提示,這是因為第一次加載另一個頁面後,該頁面已經存在于 DOM 中,再次加載不會觸發 pagebeforeload 事件。讀者可以在 Demo 中驗證以上兩點。

頁面加載事件 Demo(建議使用 PC 上的 Firefox、Chrome 等現代浏覽器和 IE9+ 或 Android , iPhone/iPad 的系統浏覽器浏覽,下同)

二.頁面跳轉事件 (Page change events)

頁面跳轉事件與頁面加載事件類似,但因為沒有直接使用 deferred 對象,是以這系列的事件會較為簡單。

頁面跳轉事件由 $.mobile.changePage() 産生,第一個為 pagebeforechange ,第二個是 pagechange 或 pagechangefailed 。

$.mobile.changePage() 方法調用後會對頁面參數進行一些處理,然後觸發 pagebeforechange 事件,若頁面請求成功,即上面的 $.mobile.loadPage() 調用了 done() 隊列,并且頁面外部處理(history 等)完成後,會觸發 pagechange ,若請求失敗,即上面的 $.mobile.loadPage() 直接調用了 fail() 隊列,則觸發 pagechangefailed 事件。

在上面 Kayo 已經說明過,實質處理頁面加載的是 $.mobile.loadPage() 事件,是以需要進行頁面加載前後的自定義處理還是用阻止 pagebeforeload , pageload, pageloadfail 等事件并進行自定義處理比較合适,盡管這樣,阻止 pagebeforechange 仍具有意義 —— 它可以直接阻止一個頁面加載,在某些情況下,可能你會需要這樣做,就像有些情況下需要阻止 click 的預設跳轉行為一樣。

這三個事件的回調函數的第二個參數 data 有如下兩個屬性:

  • toPage (object or string) 這個屬性值應該為一個希望被激活的頁面(即需要進入的頁面),實際取值可以為一個包含頁面 DOM 對象的 jQuery 對象或者一個外部頁面的絕對或相對連結,這個值同時也是 $.mobile.changePage() 方法的第一個參數。
  • options (object) 這個值是一個包含頁面選項的對象,同時也會用于 $.mobile.changePage() ,作為第二個參數。

利用上面兩個屬性,可以在事件回調函數中作出一些處理。

值得注意的是,通過了解加載頁面的流程,可以知道,一次正确的頁面跳轉中,事件産生的順序應該是,pagebeforechange , pagebeforeload , pageload , pagechange ,在開發時需要注意這些事件的觸發順序。

三.頁面顯示/隐藏事件 (Page show/hide events)

在頁面轉場前後,會觸發一些事件,舉一個例子:

B 頁面中有一個 id 為 page-B 的 page 元件 ,然後在 A 頁面的 head 中作出如下的事件綁定:

1 2 3 4 5 6 7 8 9 10 11 12 13 14

$(

'#page-B'

).live(

'pagebeforeshow'

,

function

(event, ui){

alert(

'pagebeforeshow'

);

});

$(

'#page-B'

).live(

'pagebeforehide'

,

function

(event, ui){

alert(

'pagebeforehide'

);

});

$(

'#page-B'

).live(

'pageshow'

,

function

(event, ui){

alert(

'pageshow'

);

});

$(

'#page-B'

).live(

'pagehide'

,

function

(event, ui){

alert(

'pagehide'

);

});

從 A 頁面點選連結跳轉到 B 頁面,然後從 B 頁面跳轉回 A 頁面,則觸發事件的情況如下,

  • pagebeforeshow:從 A 跳轉到 B,B 頁面被顯示時(轉場開始)觸發
  • pagebeforehide:從 A 跳轉到 B,A 頁面被隐藏時(轉場結束)觸發
  • pageshow:從 B 跳轉到 A,A 頁面被顯示時(轉場開始)觸發
  • pagehide:從 B 跳轉到 A,B 頁面被隐藏時(轉場結束)觸發

是以,觸發哪種頁面顯示/隐藏事件,要視乎頁面是被顯示還是被隐藏。

這些事件會由轉場實作函數控制觸發,它們的回調函數的第二個參數 data 隻有一個屬性,分别為:

  • pagebeforeshow:prevPage (object) 這個屬性值應為包含正在離開的頁面 DOM 對象的 jQuery 對象,即上面的 $(A)
  • pagebeforehide: nextPage (object) 這個屬性值應為包含正在進入的頁面 DOM 對象的 jQuery 對象,即上面的 $(B)
  • pageshow: prevPage (object) 這個屬性值應為包含正在離開的頁面 DOM 對象的 jQuery 對象,即上面的 $(B)
  • pagehide: nextPage (object) 這個屬性值應為包含正在進入的頁面 DOM 對象的 jQuery 對象,即上面的 $(A)

下面給出一個 Demo ,示範上面 A 與 B 頁面的例子

頁面顯示/隐藏事件 Demo

四.頁面初始化事件 (Page initialization events)

頁面初始化中的初始化指的是頁面增強,即 jQuery Mobile 對頁面元件的樣式、功能和互動進行豐富并增強的過程,在這個過程中也會觸發一系列事件。

pagebeforecreate

在頁面開始初始化之前觸發,這時 DOM 已被 jQuery Mobile 獲得,但仍未進行 jQuery Mobile 增強,是以綁定這個事件并在回調函數中作出處理可以在頁面被增強前進行自定義處理。

另外若阻止事件的預設行為,會禁止頁面元件初始化。

是否覺得這個流程與 pagebeforeload 的流程相似,的确,在 jQuery Mobile 中,很多事件的産生函數(如 pagebeforeload 的産生函數 pagebeforeload)内部都有阻止預設行為的處理,即用判斷語句判斷觸發阻止預設行為後自動 return ,阻止後面的處理發生,了解這個流程對于了解 jQuery Mobile 的工作原理很有幫助。

pagecreate

在頁面初始化時,即 DOM 剛開始被建立,并且需要進行 jQuery Mobile 的元素已經準備好(即加入待增強清單),但仍未實際開始進行 jQuery Mobile 增強前觸發。

pageinit

當頁面初始化後觸發,jQuery Mobile 建議使用這個事件的綁定代替 jQuery 中常用的 DOM ready() ,因為觸發這個事件意味着頁面已經直接或者通過 Ajax 加載好并增強,更适合用在 jQuery Mobile 開發中。

五.頁面移除事件 (Page remove events)

頁面移除事件隻有一個 —— pageremove ,當 jQuery Mobile 準備從 DOM 中移除一個外部頁面時會觸發這個事件,阻止這個事件的預設行為可以阻止一個頁面被移除。

六.頁面事件觸發順序

上面的說明中,基本說明了頁面加載的完整流程,同時對不同僚件的觸發順序作出了間接的說明,下面再給出一個例子,來直接說明在一次頁面跳轉中各個頁面事件的觸發順序。

頁面事件觸發順序 Demo

轉載于:https://www.cnblogs.com/loveailsa10000/p/3817867.html