? 按照Tim Berners-Lee的原始設想,互聯網的核心概念是超鏈接(hyperlink), 每一個可訪問的資源都具有自己的URI(Universal Resource Identifier), 我們通過唯一的url可以訪問到這些資源。從概念上說,每一個頁面可以由一個兩元組[title, url]來進行描述,title是url顯示在界面上時的可讀表述。在這一描述下,我們可以建立基本的頁面瀏覽模型,包括瀏覽歷史模型:我們可以把瀏覽過的頁面保存在瀏覽歷史列表中,通過選擇歷史列表中的條目實現頁面跳轉。但是隨著網頁的動態性不斷增加,頁面的資源語義逐漸喪失,url所對應的不再是一種靜態的資源表述概念,而逐漸演變成為一種動態的訪問函數. 例如
? /view.jsp?objectName=MyObj&objectEvent=ViewDetail&id=1
? 一個url模式所對應的網頁個數在理論上可能是無限多的. 從單一數據值到函數是系統復雜性本質上的一種飛躍. 為了不致在這種無限的世界中迷失方向,我們顯然需要對于瀏覽過程進行更加有效的組織,建立更加有約束性的導航模型. 一種常見的導航模式是在頁面的上方顯示一條線性的導航路徑, 與瀏覽歷史模型不同的是, 頁面轉換時不是
? list > view item 1 ==>? list > view item 1 > view item 2
而是
?? list > view item1 ==> list > view item2
? 為了支持導航路徑, 最簡單的方式是將頁面模型擴展為三元組[id, title, urlExpr], 其中urlExpr是動態表達式, 而id是固定的值. 例如 id=view, urlExpr=/view.jsp?objectName=MyObj&objectEvent=ViewDetail&id=${id}
? 導航路徑的變化規則為navHistory.removeAfter(newPage.id).add(newPage)
? 為了進一步約化導航路徑, 可以將頁面模型擴展為
? [id, title, urlExpr, group, before, beforeGroup],
? 其中group定義了頁面的分組, 同組的頁面在導航路徑中相互替代, 而before和beforeGroup定義了頁面在導航路徑中的相對順序. 例如對于
? [id='list' beforeGroup="detail"] [id='view' group='detail'] [id='update' group='detail']
在頁面轉換時, 導航路徑變化不是
? list > view item1 ==> list > view item1 > update item1
而是
? list > view item1 ==> list > update item1
? 在以上的頁面模型中, 每一個id對應的是一個不同的頁面模板(頁面布局), 但是有時我們也需要在同一個頁面布局中瀏覽無限分級的欄目, 此時可以將頁面模型擴展為
? [id, title, urlExpr, group, before, beforeGroup, path]
?
? 將以上的導航模型應用于一般的web應用時還需要克服一個小小的困難: 動態url的副作用. 例如/update.do?id=1&value=2這種具有動作語義的url訪問直接破壞了頁面的瀏覽模型,例如現在我們不再能夠按F5鍵刷新頁面,不能通過window.location=window.location調用來刷新頁面,在頁面回退時我們也將遇到種種困難。為了防止重復提交,一種常見的設計模式是分離POST和GET方法,即通過Form POST向上提交數據,處理完畢后通過redirect指令通知瀏覽器再次發起GET方法得到新的頁面.具體做法如下??
??? /redirect_after_action.do?id=1&value=2
? 在執行完action之后, 服務器調用response.sendRedirect(get_url), 通知前臺瀏覽器使用新的url重新取回頁面. 這樣最終我們可以確保所有頁面都是可以通過url直接訪問的(沒有隱含的post參數),而且是可以重復訪問的(無副作用因而可以反復刷新)!
? 另一種方式是使用ajax方式提交更新請求,當ajax訪問成功后, 在回調函數中進行頁面跳轉.例如:
? new js.Ajax().setObjectName('Test')
????? .setObjectEvent('Update').addForm(myForm)
????? .callRemote(function(returnValue){
??????? window.location = '/view.jsp?id=1';
????? })??
這里我們也可以根據returnValue的不同選擇跳轉到不同頁面.
? 在Witrix平臺中, 基于Jsplet框架我們定義了PageFlow對象, 它將可配置的導航模型引入到任意頁面的組織過程中. 在跳轉到一個新的頁面的時候, 訪問url如下:
? /pageflow.jsp?objectName=MyNav&objectEvent=NavToPage&_pageId=view&id=3
在重新訪問歷史頁面的時候,只需要
?/pageflow.jsp?objectName=MyNav&objectEvent=NavToHistoryPage&_pageId=view
? 基于jsplet框架的對象化特性,MyNav對象在session中保持了flow的所有狀態變量,不需要任何框架上特殊的支持。我們可以在任意頁面跳出pageflow, 并在任何時刻選擇跳回pageflow, 這些動作不會影響到flow對象的狀態。而通過objectScope的設置我們可以精細的控制flow對象的生命周期。同時基于對象化設置,訪問頁面時我們只需要資源的相對名稱(relative identifier), 例如對于各種不同的flow, 頁面id都可以叫做view, 通過_pageId=view來訪問。
? Apache軟件基金會旗下有一個beehive項目,
http://beehive.apache.org/docs/1.0m1/pageflow/pageflow_overview.html, 其中也定義了所謂pageflow的概念, 這是始創于BEA的一項技術. 但是與Witrix Page Flow不同的是, Beehive Page Flow的核心概念仍然是從struts引申而出的action的概念,所謂的flow是action的連接,而不是通過資源id之間的連接。雖然beeive宣稱支持各種對象injection, 并且管理了flow對象的生命周期,但是它并沒有規范出this指針這一面向對象最重要的概念之一。Witrix Page Flow關注的重點是已存在的頁面之間的有效組織, 它所描述的是某種具有靜態資源語義的頁面模板之間的關聯, 這種關聯是較松散的,而不是通過action強關聯的. 實際上基于Ajax技術和jsplet框架的消息路由機制, 在每一個頁面上都可以進行多種業務操作,甚至更換頁面,而不發生pageId的變動, 并不是所有的操作過程都需要在page flow描述中對外暴露。另外一個比較細節化的不同之處是Beehive使用Java Annotation, 而Witrix PageFlow使用xml描述文件, 并實現了pageflow的繼承和部分覆蓋等高級構造方法. 使用xml描述文件的必要性在于需要復用WebAction, 支持復雜的變換操作, 并集成tpl模板引擎等. Annotation的方式看似簡單,但這種簡單性同時將系統中的所有組分綁定到了一起, 它也不允許復雜的變換操作的存在. 實際上Beehive的目標并不是真正支持頁面編制(包括頁面上的頁面操作)和頁面組織的分離.