javascript
Seam - 无缝集成 JSF,第 3 部分: 用于 JSF 的 Ajax
Seam - 無縫集成 JSF,第 3 部分: 用于 JSF 的 Ajax用 Seam Remoting 和 Ajax4jsf 無縫熔接客戶機和服務器 |
|
級別: 中級
Dan Allen (dan.allen@mojavelinux.com), 高級 Java 工程師, CodeRyte, Inc.
2007 年 6 月 25 日
JSF 基于組件的方法論促進了抽象,但大多數 Ajax 實現由于公開了底層的 HTTP 交換而使之大受干擾。在 無縫集成 JSF 系列 最后的這篇文章中,Dan Allen 展示了如何使用 Seam Remoting API 和 Ajax4jsf 組件與服務器上的受管 bean 通信,就好像這些 bean 與瀏覽器同在本地一樣。您將了解利用 Ajax 作為 JSF 事件驅動架構的一種自然改進是多么地容易,以及如何在不影響 JSF 組件模型的前提下實現這一目的。時下,大多數 Java? 開發人員都很看好 mashup,所以您可能會困惑:Seam 與號稱 Web 2.0 的技術,尤其是 Ajax,如何能集成。若能使用 Seam 啟動 JSF 中的部分頁面更新或者用 Google Map 協助 JSF 應用程序 mashup,那將非常酷,不是么?您不僅能這么做,而且還非常容易。
|
在 無縫集成 JSF 系列 的最后一篇文章中,我將為您展示如何使用 Seam Remoting API 和 Ajax4jsf 組件來協助基于 JSF 應用程序中的 Ajax 風格的交互。正如您將會看到的,結合 Seam 和 Ajax 的最大好處在于它讓您可以享用所有 Web 2.0 的奢侈東西,而同時又不需要 陷于使用 JavaScript XMLHttpRequest 對象的痛苦之中。借助 Seam Remoting 和 Ajax4jsf,可以與服務器上的受管 bean 通信,就好像這些 bean 與瀏覽器同在本地一樣。瀏覽器和服務器狀態保持同步,而且永遠無需處理促成它們之間通信的低層 API。
我首先會為您展示 Seam 是如何推動 Ajax 編程的基于組件的新方式的。您將學會如何使用 Seam Remoting API 來通過 Ajax 進行 JavaScript 和服務器端對象間的通信。一旦理解了這種面向 Ajax 的新(且簡單的)方式,您就可以使用它來增強 Open 18 應用程序,方法如下:
- 在 Open 18 球場目錄和 Google Maps 之間創建一個 mashup。
- 使用 Ajax4jsf 合并應用程序的球場目錄頁和球場細節頁。
- 重新訪問應用程序的 Spring 集成并讓 Spring bean 在 Seam Remoting 的生命周期可用。
|
Open 18 和 Google Maps 之間的 mashup 讓用戶可以定位地圖中的高爾夫球場目錄中的位置。將此球場目錄和球場細節頁合并起來(并將低層代碼 Ajax 化) 可以讓您顯示球場的細節信息而無需加載新頁。將 Spring bean 和 Seam Remoting 相集成讓您可以捕獲 Google Maps 位置標記的重定位并能將相關球場的經度和緯度存儲到數據庫中。如您所見,結果就是會產生所有高爾夫球員都喜歡使用的 Web 2.0 風格的應用程序,這真是讓人印象深刻!
如果您曾經深受涉及到大量 JavaScript 的過于復雜的 Ajax 編程之苦,如果到目前為止,您都由于不想面對其復雜性而一直盡量避免使用 Ajax,那么本文所要教授的內容將會幫助您免除這種擔心。在重構應用程序時,您需要進行一些 JavaScript 編碼,但與大多數 Ajax 實現不同,JavaScript 并不會占據您代碼中的大部分;相反,它只擴展了服務器端的 Java 對象。
通向 Ajax 的不同之路
正如在應用程序中希望避免顯式的內存管理一樣,您亦不 希望必須要處理低層的 Ajax 請求協議。這么做只會帶來更大的麻煩(更確切地說,是更多的麻煩),比如多瀏覽器支持、數據封送處理、并發沖突、服務器負載以及定制 servlet 和 servlet 過濾器。其中您想要避免的最大的麻煩是無意間公開的無狀態的請求-響應范例,但該范例是基于組件的框架,比如 JSF,所想要隱藏的。
JSF 生命周期通過對底層的 servlet 模型屏蔽應用程序代碼促進了面向組件的設計。為了保持處理 Ajax 的這種抽象性,您可以將低層的這些瑣碎工作交由 Seam Remoting 或 Ajax4jsf 處理。這兩個庫均可負責通過 Ajax 交互將 JSF 組件熔合到瀏覽器時所需的管道處理。圖 1 顯示了實際應用中的 Seam Remoting。當事件觸發時,比如用戶單擊了一個按鈕,消息就會異步發送給服務器上的組件。一旦收到響應,它就會用來對此頁進行增量更新。用來處理瀏 覽器和服務器端組件間的交互的低層通信協議都藏于 API 之后。
|
在圖 1 所示的用例中,用戶能看到單擊按鈕后所發生的方法調用的結果。在研究此用例時,有兩個要點需要注意: (1) 該頁永遠無法刷新;(2) 客戶端代碼與組件上的方法進行透明通信,而不是顯式地構建然后再請求 URL。標準的 HTTP 請求在后臺使用,但客戶端代碼永遠無需直接與 HTTP 協議交互。
圖 1. Seam Remoting 熔合 JSF 組件和瀏覽器
Seam Remoting 和 Ajax4jsf
Seam Remoting 和 Ajax4jsf 是兩個獨特的庫,可分別服務于 JSF 的 “Ajax 化” 的目的。兩個庫均使用 Ajax 來引入交互模型,其中瀏覽器和服務器間的通信可以在后臺異步發生,并對用戶不可見。沒有必要為了執行服務器上的方法而浪費用戶頁面重載的時間。在這些庫所 發出的 Ajax 請求中由服務器檢索到的信息可用來增量地 “實時” 更新頁面的狀態。兩個庫均可配備生命周期,此生命周期可以在瀏覽器需要的時候恢復(restore)組件的狀態。這種 Ajax 交互并不是真的請求而是一種 “恢復并執行”。瀏覽器像是 “敲敲” 服務器的 “肩膀”,請它在服務器端的一個受管 bean 上執行一個方法并返回結果。
雖然這兩個庫工作起來有些差別,但它們并不是相互排斥的。由于它們都采用的是 JSF 組件模型,所以二者可以很容易地相互結合,這將在本文后面的部分詳細介紹。目前,我們只需分別考慮二者各自將 Ajax 風格的交互引入 JSF 應用程序的方式:
- Seam Remoting 提供了 JavaScript API,可以使用這些 API 來像訪問本地對象一樣來訪問 JavaScript 中的服務器端組件,以便通過方法調用發送和檢索數據。Seam Remoting 使用定制的、非 JSF 生命周期來使該瀏覽器能夠與服務器端的組件通信。只有 Seam 容器和其組件可以在這些請求期間被恢復。透明協議是 Ajax,但您無需費心數據包如何傳輸的細節。
- Ajax4jsf 則通過完全隱藏 JavaScript 的使用讓抽象更進了一步。它將所有邏輯都包裹在基本 UI 組件內。Ajax4jsf 通過完整的 JSF 生命周期接受 Ajax 請求。因而,支持 Ajax 的組件可以在不觸發瀏覽器導航事件的前提下執行動作處理程序、升級 JSF 組件樹以及重新呈現該頁的某些部分。同樣地,通信也是通過 Ajax 實現的,但所有這些均在后臺發生,頁面開發人員不可見。Ajax4jsf 面向組件的方法讓 Ajax 功能得以成為 JSF 很自然的一部分,而不是格格不入的外來者。
我將深入探究這些方式,但我們還是先來看看 Ajax 的基礎知識吧。
|
縮小差異
要 想讓應用程序成為 Ajax/Web 2.0 意義上的 “富” 應用程序,Web 瀏覽器(也即客戶機)必須能直接訪問服務器上的組件。由于在客戶機和服務器間存在著巨大差異,讓這個目標成為現實還是很有挑戰性的。差異的一個方面(即網 絡)存在于客戶機瀏覽器,另一方面存在于服務器和其組件。Ajax 應用程序的一個目標是讓它們可以相互 “通話”。
實際上,在大多數傳統的 Web 應用程序中,客戶機和服務器可以正常通信,但交互性就完全是另一回事了。服務器發起對話,瀏覽器收聽對話。您如果以前曾陷于這 類對話之中,實在不足為怪。在沒有 Ajax 通信的世界里,瀏覽器可以發送對任何 URL 的同步請求,但它必須要呈現服務器發回的 HTML。這類交互性的另一個不足之處是等待時間很多。
若 只有 HTTP 這種沒有什么發展的語言,瀏覽器客戶機將對服務器如何生成 HTML 完全束手無策,進而也就無從知道其組件的內容。從瀏覽器的立場上看,頁面生成過程完全是個黑盒子。瀏覽器可以以 URL 形式詢問服務器不同的問題并將打包成請求參數和 POST 數據的提示一并傳遞,但它其實并不 “講” 服務器的語言。如果想要給瀏覽器提供一個到應用程序服務器端動作的視圖,則需要建立更復雜的通信手段。這種面向頁面的確定性方法對此無能為力。
Web 瀏覽器作為 Ajax 客戶機
Seam Remoting 和 Ajax4jsf 對打破客戶和瀏覽器組件的隔閡所采用的方式有所不同,所以很有必要知道如何利用好這二者。Seam Remoting 提供了瀏覽器本地語言 JavaScript 形式的 API,通過這些 API 服務器端組件上的方法就可以被訪問到。若要將訪問權賦予這些方法,它們必須通過 @Remote 注釋被顯式地標記為 “remote”。
Seam Remoting 中的調用機制與 Java RMI 類似,二者都使用本地代理對象或 “存根” 來允許 JavaScript 調用位于遠程服務器上的組件。就客戶而言,該存根對象就是 遠端對象。該存根負責實現實際遠端對象上的方法執行。當遠端方法被調用時,響應就會將此方法調用的返回值封裝起來。返回值被編制處理成一個 JavaScript 對象。因此,Seam Remoting 就使瀏覽器可以 “講” 服務器的本地語言。Java 代碼和 JavaScript 現已合二為一,這對于那些認為這二者原本就是一種語言的人多少有點出乎意料。
|
另 一方面,Ajax4jsf 提供了 JSF 組件標記來聲明性地關聯 UI 事件和服務器端受管 bean 上的動作處理程序。這些動作處理程序方法不需要被標記成 “remote”。相反,它們都是一些傳統的 JSF 動作處理程序,或者是受管 bean 上的公共方法,這些方法或者無實參或者接受 ActionEvent。
與 Seam Remoting 不同,Ajax4jsf 可以將 JSF 組件樹中的更改返回到瀏覽器。這些更改以 XHTML 片段的形式返回。這些段與此頁中單個的 JSF 組件相關聯并證明其自身為部分頁更新。因此,此頁的隔離區域可以通過新的 標記由瀏覽器重新呈現。這些段都是特殊請求的,或者通過使用 Ajax4jsf 組件標記的 reRender 屬性,或者通過 將模板的多個區域封裝到用屬性 ajaxRendered="true" 標示的輸出面板中。reRender 屬性表示的是應該被重現的一個特定的組件集,由其組件 ID 加以引用。相比之下,使用 ajaxRendered="true" 的方式就有些太過全面了,要求只要 Ajax4jsf 管理的 Ajax 請求完成,所有 “由 Ajax 呈現” 的區域都要更新。
Ajax4jsf 和 Seam Remoting 使瀏覽器從基本的 HTML 呈現器成長為一種成熟的 Ajax 客戶機。當集成這兩種框架時,應用程序的確 會讓人興奮不已。實際上(這個消息有點出人意料,所以您最好坐下來聽),綜合 Seam Remoting 和 Ajax4jsf 的功能可以讓您從開發自己定制的 Ajax JSF 組件中解脫出來!您可以在 Ajax 通信中采用任何現有的、非 Ajax 啟用的 JSF 組件,方法是在其聲明中嵌套 a4j:support 標記。如果您工作于 UI 組件之外(正如在后面的 Google Maps mashup 中所做的那樣)且需要查詢服務器端的組件以獲取信息、更新該組件或指導它來執行操作,您可以用 Seam Remoting 管理此交互。
當 Seam Remoting 和 Ajax4jsf 在功能上有些重疊時,二者都可以很利于將 Ajax 風格的交互添加到應用程序。此外,您很快就會看到,兩個庫都為 JSF 應用程序提供了無縫的 Ajax 解決方案。
|
Seam Remoting 快速入門
如 果 Seam Remoting 實現起來不是如此復雜的話,那么它真是一種理想的解決方案。不要擔心!Seam Remoting 并不會如您曾領教過的那些遠端 EJB 對象那樣可怕。使用 Seam Remoting API 啟用 JavaScript 代碼來與服務器端組件進行交互最好的一面是過程異乎尋常地簡單。Seam 真的可以為您完成所有的辛苦工作 —— 您甚至都無需編輯一行 XML,就可以開始使用它!(如果目前您進行 XML 編程要比 Java 編程多,那么這真是一個振奮人心的消息。)
讓我們先來快速看看使用 Seam Remoting 來 “Ajax 化” JSF 應用程序所需的步驟。
公開 bean
將服務器端對象方法對遠端 Ajax 公開有兩個要求:此方法必須是 Seam 組件的公共成員且必須配備 @WebRemote 注釋。就這兩點!
實際的簡單性在清單 1 中可見一斑,其中 Seam 組件 ReasonPotAction 為了遠端執行而 向 Ajax 客戶公開了單一一個方法,即 drawReason()。每次這個方法在此存根上被調用的時候,該調用都會跨 Internet 傳遞到服務器并會通過在服務器端使用對應的方法隨機選擇所列的 “在項目中采用 Seam 的十大理由” 之一。服務器隨后向客戶返回該值(有關這十個理由的更多信息,請參見 參考資料)。
清單 1. 公開遠端方法調用
| @Name("reasonPot") @Scope(ScopeType.SESSION) public class ReasonPotAction { private static String[] reasons = new String[] { "It's the quickest way to get /"rich/".", "It's the easiest way to get started with EJB 3.0.", "It's the best way to leverage JSF.", "It's the easiest way to do BPM.", "But CRUD is easy too!", "It makes persistence a breeze.", "Use annotations (instead of XML).", "Get hip to automated integration testing.", "Marry open source with open standards.", "It just works!" }; private Random randomIndexSelector = new Random(); @WebRemote public String drawReason() { return reasons[randomIndexSelector.nextInt(reasons.length)]; } } |
服務于資源
服務器端組件設置好后,需要讓瀏覽器準備好來調用 @WebRemote 方法。Seam 使用定制 servlet 來處理 HTTP 請求以便執行遠端方法并返回其結果。無需擔心:您將不必直接與那個 servlet 進行交互。Seam Remoting JavaScript 庫負責處理所有的與 Seam servlet 的交互,而處理的方式也與它管理 XMLHttpRequest 對象的所有方面相同。
您 需要考慮使用這個 servlet 的惟一情況是在應用程序中設置 Seam Remoting 的時候。更好的是您只需配置 Seam 的定制 servlet 一次,而不管有多少 Seam 特性需要定制 servlet 的服務。與為每個特性使用特定的 servlet 相反 —— 該特性可能是將資源(比如一個 JavaScript 文件)提供給瀏覽器或處理一個非 JSF 請求(像 Ajax remoting 調用),Seam 將所有這些任務捆綁于單一一個控制器 Resource Servlet 之下。這個 servlet 使用一個委托鏈模型,將這些任務傳遞到注冊的處理程序。例如,Remoting 對象(我稍后就會介紹)會注冊其自身來接收所有由 Seam Remoting JavaScript 庫發出的 Ajax 請求。
Resource Servlet 的 XML 定義如清單 2 所示,且必須安裝于應用程序的 web.xml 文件中 Faces Servlet 之下:
清單 2. Seam Resource Servlet 的 XML 定義
| <servlet> <servlet-name>Seam Resource Servlet</servlet-name> <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Seam Resource Servlet</servlet-name> <url-pattern>/seam/resource/*</url-pattern> </servlet-mapping> |
自引導 API
Seam Remoting 機制真正的幕后原因是 JavaScript 庫。Resource Servlet 將作業指派給 Remoting 對象以服務這些庫。所發出的兩個 JavaScript 庫建立適合瀏覽器的掛鉤以便借助 JavaScript 調用遠端方法。
必 須要將這兩個 JavaScript 庫都導入頁面,如清單 3 所示。第一個庫 remote.js 是靜態的,將 Seam 的客戶端 remoting 框架傳遞給瀏覽器。第二個庫 interface.js 是在每個請求上動態創建的。它包含遠端存根和與服務器端組件進行交互所需的復雜類型。注意,Seam 并不為所有指定一個方法為遠端的所有組件都生成存根和復雜類型。相反,它會解析 interface.js URL 中的 ? 字符后面的該查詢字符串來收集要公開的組件名。每一個組件名都由 & 符號分隔。使用這種堆棧式的方法可以使瀏覽器為外部 JavaScript 文件所進行的請求的數量為最小。Seam 之后會只為這些組件生成這些存根和復雜類型。作為一個例子,清單 3 中的這兩個 JavaScript 導入將會加載 Seam Remoting 庫并指示 Seam 準備好 reasonPot 和 anotherName 組件:
清單 3. 導入客戶端框架和 API
| <script type="text/javascript" src="seam/resource/remoting/resource/remote.js"></script> <script type="text/javascript" src="seam/resource/remoting/interface.js?reasonPot&anotherName"></script> |
有了這些,您就可以著手了!您已經完成了 Seam Remoting 設置,現在可以開始進行一些遠程調用了。
|
進行遠程調用
所有用于 Seam Remoting 庫的客戶端框架代碼都封裝在 Seam JavaScript 對象中。實際上,這個高層對象只是一個名稱空間 —— 一個可以將一組功能捆綁在惟一一個名字之下的容器。在 JavaScript 中,名稱空間不過是一個可以加載靜態屬性、靜態方法和其他一些嵌套名稱空間對象的對象。需要注意的重要一點 是 Seam Remoting 中的 JavaScript 與其他 JavaScript 可以很好地合作。這意味著您可以利用諸如 Prototype 或 Dojo 這樣的庫來讓 JSF UI 與服務器端組件交互。在 JSF UI 中使用第三方 JavaScript 庫過去一直是 JSF 開發人員所面臨的一個巨大挑戰,所以 Seam JavaScript 對象備受歡迎:
Seam Remoting 庫的功能在 Seam.Component 和 Seam.Remoting 名稱空間對象間分配。Seam.Component 的靜態方法提供了對遠端 Seam 組件的訪問,而靜態 Seam.Remoting 方法則用來控制 remoting 設置并創建定制數據類型。正如您從本示例中所見的,定制數據類型是任何需要本地創建以調用遠端方法的非原語對象。
執行細節
要執行一個遠端方法,首先要獲得持有此方法的組件的一個實例。如果想要使用現有的 Seam 組件實例(意味著它已經存在于服務器),就可以使用 Seam.Component.getInstance();如果想要在進行調用之前先實例化一個新實例,則可以使用 Seam.Component.newInstance()。 這些實例均是由 Seam 動態構建的遠端存根。存根代理所有的方法調用,將組件名、方法和參數編排處理成 XML,并通過一個 Ajax 請求將 XML 有效負載傳遞到服務器端組件。服務器上的 remoting 框架代碼獲取這個請求,對這個 XML 進行解除編排處理,提取出組件名、方法和實參,然后再在服務器端組件實例上調用此方法。最后,此服務器以 Ajax 調用的響應向客戶機發回返回值。同樣,所有這些工作都藏于存根的背后,極大地簡化了 JavaScript 代碼。
至此,有關背景的介紹已經足夠了。清單 4 顯示了該如何執行 conversation 作用域內的 reasonPot 組件上的 drawReason() 方法,該組件在 清單 1 中被標記為遠端服務。一旦獲得了組件存根,執行此方法就如同任何其他的 JavaScript 方法調用一樣,不是么?
清單 4. 調用遠端方法
| <script type="text/javascript"> Seam.Component.getInstance("reasonPot").drawReason(displayReason); function displayReason(reason) { alert(reason); } </script> |
在清單 4 中,應該注意到存根上的方法和服務器端組件上的 “實際” 方法存在一個重要差異。您覺察到了么?每個在組件存根上的調用都是異步完成的。這意味著遠端方法調用的結果并不能立即對執行線程可用。正因為如此,存根上 的方法并沒有要返回的值,而不管匹配的服務器端方法是否有要返回的值。當遠端存根上的方法執行時,它實際上像是在說:“好吧,我稍后再答復您,請留下您的 聯系電話。”您所提供的碼實際上就是 “回調” JavaScript 函數。其任務是捕獲實際的返回值。
回調函數 是異步 API 的標準結構。當來自 Ajax 請求的響應回到瀏覽器時,它由 Seam Remoting JavaScript 框架執行。如果服務器端組件上的方法具有非空返回值,該值就會作為其惟一的實參傳遞進此回調函數。另一方面,如果服務器端組件上的方法具有一個空返回類 型,則可以忽略此可選參數。
conversation 作用域內的 bean
對 于大多數 remoting 需求,您都可以使用到目前為止所介紹的 API 加以應對。若請求的給出是為了檢索服務器端組件或執行其公開的方法中的一個,那么該組件必須在 remoting 生命周期內可見。引用 conversation 作用域內的 bean 不需要任何額外的工作,原因是這些 bean 總是對在同一個瀏覽器對話中發出的 HTTP 請求可用。
conversation 作用域內的 bean 需要更多技巧,因為它們基于對話令牌的存在而與 HTTP 請求相關聯。當處理 conversation 作用域內的 bean 時,您必須要能夠在遠程調用期間建立正確的對話上下文。此對話通過與遠程請求一同發送對話令牌來重新激活。這種交互的細節由 Seam Remoting 框架處理,只要您提供對話令牌即可。
請記住,Ajax 請求完全可以從相同的窗口發出以與此服務器上的兩個或更多的對話通信。如果在檢索組件實例存根之前指定了對話 ID,那么 Seam 就可以對它們進行區分。使用 Seam.Remoting.getContext().setConversationId("#{conversation.id} ") 可以實現這一目的。
Seam 總是在 #{conversation.id} 值綁定表達式下公開當前的對話。JavaScript 實際上可以看到解析后的值,通常是一個數值,而不是表達式占位符。您可以與 remoting 內容一起注冊該值,它然后會由隨后的請求傳遞。相反,您可以讀在之前的請求中分配的這個對話 ID,方法是在遠端調用發起后,調用 Seam.Remoting.getContext().getConversationId()。通過這種方式,remoting 調用就可以充分利用 conversation 作用域的益處并 參與狀態行為。
|
Google map 和 Open 18 應用程序
清單 1 中的 Reason Pot 示例(“使用 Seam 的十大理由”)很有趣,但現在是時候該讓 Seam Remoting 庫大顯身手了!將重點重新放到 “無縫集成 JSF,第 2 部分: 借助 Seam 進行對話” 中所介紹的 Open 18 應用程序。您可能記得 Open 18 是個高爾夫球場目錄。用戶可以瀏覽球場的列表并隨后深入查看單個球場的細節。Open 18 還允許用戶創建、更新和刪除球場。在其 Web 1.0 版本中,用戶和這個應用程序間的每個交互都會使頁面重載。
借助 Ajax,可以有多種改進 Open 18 應用程序的方法,在我們繼續之前,您可以自己嘗試其中的一些方法。第一個可以做的事情是在 Open 18 和 Google Map 之間創建一個 mashup。 時下,若不采用 mapping 實現,在 Internet 就走不多遠,而如果添加此特性,您的用戶當然會非常高興。將 Seam Remoting API 和 Google Maps Geocoder API 結合起來讓您可以定位 Google map 上的球場目錄中的每個球場。
|
借用 Google 的世界視圖
地理空間繪制乍聽起來需要很多技巧,但若 Google Maps JavaScript API 能代您完成很多工作的話,那就另當別論了。GMap2 類可以繪制地圖并負責視圖端口中的滾動和縮放事件。另一個 Google Maps 類 GClientGeocoder 則基于地址字符串解析地理空間的坐標。您的工作只不過就是初始化地圖并為每個球場添加標記。
要在地圖上放上標記,首先要通過遠端方法調用從服務器端組件獲取球場集。接下來,用 GClientGeocoder 將每個球場的地址翻譯成地理空間點(經度和緯度)。最后,使用該點來在地圖的相應坐標上放置標記。作為一個額外的小特性,您還可以將編輯圖標旁邊的羅盤圖 標裝備在目錄中的每行。當單擊了目錄行中的羅盤圖標時,地圖就會縮放直到所選球場出現在視圖內。與此同時,此地圖還會在標記上呈現一個氣球,顯示給定球場 的地名、地址、電話和 Web 站點。通過直接單擊地圖上的一個標記也可以彈出相同的氣球。圖 2 顯示的是完成后的應用程序的預覽:
圖 2. Google Maps mashup 的一個屏幕截圖
總 的來說,地圖組件和面向位置數據的集成很有趣,此 mashup 尤其具啟迪意義。用戶現在可以實際查看地圖上每個球場的邊界!在 Map 模式,高爾夫球場屬性用淡綠色呈現。如果縮放功能足夠強大,那么在球場區域還會出現一個標簽覆蓋圖,顯示此球場的名稱。Satellite 模式則更有趣,它顯示了球場的面貌。若縮放的程度足夠,甚至還可以看到每個球洞的特征,比如發球臺、球道和果嶺!高爾夫球場位置的 mashup 以及互動性的地圖視圖就能讓您的努力有所回報。
|
編織進 Google Maps
Google Maps 很易于集成和嵌入到 Web 應用程序。正如我已經提到的,它負責了所有呈現地圖的所有細節并會提供一個 API 來繪制地圖上的地理空間位置。GClientGeocoder 對象擔負所有解析地理空間位置和回送所需的經度和緯度數據這樣的艱巨任務。
將 地址解析為地理空間點的方法存根與 Seam Remoting 方法存根的工作原理相同。當該方法被調用來獲取返回值時,一個回調函數會傳遞給此方法。在那時,還會向 Google HQ 發送一個 Ajax 請求,而且當響應回至瀏覽器時,此回調函數會執行。Google Maps API 的智能界面讓定位變得非常文字化,因為所基于的只有郵寄地址。您無需再為每個球場維護地理空間的坐標,那樣只會增加混亂!這個 API 上的額外的例程之后會使用緯度和經度數據來為地圖上的這些位置構建呈現標記。
|
與地圖集成相關的 API 方法有兩個:Geocoder.getLatLng() 和 GMap.addOverlay()。首先,Geocoder.getLatLng() 方法將地址字符串解析為一個 GLatLng 點。數據對象只用來包裝經度和緯度值對。此方法的第二個實參是一個回調 JavaScript 函數,一旦與 Google HQ 的通信完成,該函數即會執行。此函數繼續執行以通過使用 GMap.addOverlay() 來將一個標記覆蓋圖添加到地圖上。默認地,標記以紅色的回形針表示。回形針的頂點指向地圖上的地址位置。
清單 5 顯示了設置 Google map 并向它添加標記的 JavaScript 代碼。函數按執行順序排列。除了新導入的 Google Maps API 腳本之外,您還應該識別出 清單 3 中曾經用到的 Seam Remoting 腳本。
清單 5. 映射 Open 18 和 Geocoder
| <script type="text/javascript" src="http://maps.google.com/maps?file=api&v=2.x&key=GOOGLE_KEY"></script> <script type="text/javascript" src="seam/resource/remoting/resource/remote.js"></script> <script type="text/javascript" src="seam/resource/remoting/interface.js?courseAction"></script> <script type="text/javascript"> // <![CDATA[ var gmap = null; var geocoder = null; var markers = {}; var mapIsInitialized = false; GEvent.addDomListener(window, 'load', initializeMap); /** * Create a new GMap2 Google map and add markers (pins) for each of the * courses. */ function initializeMap() { if (!GBrowserIsCompatible()) return; gmap = new GMap2(document.getElementById('map')); gmap.addControl(new GLargeMapControl()); gmap.addControl(new GMapTypeControl()); // center on the U.S. (Lebanon, Kansas) gmap.setCenter(new GLatLng(38.2, -95), 4); geocoder = new GClientGeocoder(); GEvent.addDomListener(window, 'unload', GUnload); addCourseMarkers(); } /** * Retrieve the collection of courses from the server and add corresponding * markers to the map. */ function addCourseMarkers() { function onResult(courses) { for (var i = 0, len = courses.length; i < len; i++) { addCourseMarker(courses[i]); } mapIsInitialized = true; } Seam.Remoting.getContext().setConversationId("#{conversation.id}"); Seam.Component.getInstance("courseAction").getCourses(onResult); } /** * Resolve the coordinates of the course to a GLatLng point and adds a marker * at that location. */ function addCourseMarker(course) { var address = course.getAddress(); var addressAsString = [ address.getStreet(), address.getCity(), address.getState(), address.getPostalCode() ].join(" "); geocoder.getLatLng(addressAsString, function(latlng) { createAndPlaceMarker(course, latlng); }); } /** * Instantiate a new GMarker, add it to the map as an overlay, and register * events. */ function createAndPlaceMarker(course, latlng) { // skip adding marker if no address is found if (!latlng) return; var marker = new GMarker(latlng); // hide the course directly on the marker marker.courseBean = course; markers[course.getId()] = marker; gmap.addOverlay(marker); function showDetailBalloon() { showCourseInfoBalloon(this); } GEvent.addListener(marker, 'click', showDetailBalloon); } /** * Display the details of the course in a balloon caption for the specified * marker. You should definitely escape the data to prevent XSS! */ function showCourseInfoBalloon(marker) { var course = marker.courseBean; var address = course.getAddress(); var content = '<strong>' + course.getName() + '</strong>'; content += '<br />'; content += address.getStreet(); content += '<br />'; content += address.getCity() + ', ' + address.getState() + ' ' + address.getPostalCode(); content += '<br />'; content += course.getPhoneNumber(); if (course.getUri() != null) { content += '<br />'; content += '<a href="' + course.getUri() + '" target="_blank">' + course.getUri().replace('http://', '') + '</a></div>'; } marker.openInfoWindowHtml(content); } // ]]> </script> |
清單 5 乍看起來真是有點嚇人,但實際上,它所包含的內容并不多。頁面加載后,Google map 就會使用 GMap2 構造函數進行初始化、插入到目標 DOM 元素并會以美國為中心。成功了! 您現在就有了 Google Maps 顯示。比您所想的要容易一些,對么?一旦地圖初始化,球場標記就會添加進來。代碼的難點就是創建這些標記,所以讓我們重點看看 addCourseMarkers() 函數,其中嵌入了 Seam Remoting API。
定位球場
為 了節省服務器資源,您希望遠端調用檢索相同的球場列表,這些球場是在頁面被 JSF 呈現時加載進 conversation 作用域內的。要想確保持有這個集合的對話能夠在遠端調用的過程中被重新激活,此對話 ID 必須建立在 remoting 內容的基礎上,正如本文之前討論的那樣。引用當前對話 ID 的這個值綁定表達式 #{conversation.id} 在頁面呈現時被解析,而其值隨后會通過 setConversationId() 方法傳遞給 Remoting Context。任何借助組件存根到遠端方法的后續調用都會傳遞該值并激活相關的對話。對話激活后,同樣的球場列表才會可用。
|
要找到球場,下一步是使用 Seam.Remoting.getInstance() 獲取到 courseAction 組件的引用,然后執行存根上的 getCourses() 方法。正如先前所介紹的,存根方法會采用回調函數作為其最后的實參,該實參用來捕獲響應中的球場列表。注意到返回值不再是原語類型,而是用戶定義的 JavaBean 集。Seam 創建 JavaScript 結構來模仿服務器端組件的方法簽名中所用的 JavaBean 類。在本例中,Seam 通過相同的 getters 和 setters 來將響應反編排到代表 Course 條目的 JavaScript 對象。清單 5 中的很多地方都使用了球場對象上的 getter 方法來讀取球場數據。
在把球場放到地圖上的最后一個步驟中,每個球場的地址都被 GClientGeocoder 轉換成 GLatLng 點。該值隨后被用來創建 GMarker 小部件,這個小部件作為覆蓋圖添加到地圖上。
沒有羅盤的地圖
現 在,您的地圖已經點綴了所有這些光鮮的標記,但還不可能將球場目錄中的行與地圖上的標記關聯起來。這時就需要羅盤圖標發揮作用了。我們將添加多一點 JavaScript,以便在該球場的羅盤圖標被單擊時,能使用 Google Maps API 來縮放和居中地圖。清單 6 顯示了羅盤圖標的組件標記。該組件被插入到目錄中每一行的編輯圖標之前。球場編輯功能在 “無縫集成 JSF,第 2 部分: 借助 Seam 進行對話” 中有詳細的介紹。)
清單 6. 能呈現羅盤圖標的組件標記
| <h:graphicImage value="/images/compass.png" alt="[ Zoom ]" title="Zoom to course on map" |-------10--------20--------30--------40--------50--------60--------70--------80--------9| |-------- XML error: The previous line is longer than the max of 90 characters ---------| οnclick="focusMarker(#{_course.id}, true);" /> |
JavaScript 事件處理程序 focusMarker() 被注冊用來處理羅盤圖標上的 onclick 事件。清單 7 中所示的 focusMarker() 方法在全局注冊表中為該球場查找之前注冊的 GMarker ,然后將此工作分配給來自 清單 5 的 showCourseInfoBalloon() 函數:
清單 7. focusMarker() 事件處理程序著重于選定球場上的地圖
| /** * Bring the marker for the given course into view and display the * details in a balloon. This method is registered in an onclick * handler on the compass icons in each row in the course directory. */ function focusMarker(courseId, zoom) { if (!GBrowserIsCompatible()) return; if (!mapIsInitialized) { alert("The map is still being initialized. Please wait a moment and try again."); return; } var marker = markers[courseId]; if (!marker) { alert("There is no corresponding marker for the course selected."); return; } showCourseInfoBalloon(marker); if (zoom) { gmap.setZoom(13); } } |
存儲球場集
清單 8 給出了服務器端組件上負責公開之前獲取的球場的那個方法(為了簡便起見,此清單中沒有包括為 無縫集成 JSF,第 2 部分: 借助 Seam 進行對話 中的 Open 18 實現所創建的 CRUD 動作處理程序)。
清單 8. CourseAction 組件公開 getCourses 方法
| @Name("courseAction") @Scope(ScopeType.CONVERSATION) public class CourseAction implements Serializable { /** * During a remote call, the FacesContext is <code>null</code>. * Therefore, you cannot resolve this Spring bean using the * delegating variable resolver. Hence, the required flag tells * Seam not to complain. */ @In(value="#{courseManager}", required=false) private GenericManager<Course, Long> courseManager; @DataModel private List<Course> courses; @DataModelSelection @In(value="course", required=false) @Out(value="course", required=false) private Course selectedCourse; @WebRemote public List<Course> getCourses() { return courses; } @Begin(join=true) @Factory("courses") public void findCourses() { System.out.println("Retrieving courses..."); courses = courseManager.getAll(); } // .. additional CRUD action handlers .. } |
正如之前所解釋的,方法存根可以包括一個可選的回調 JavaScript 函數作為其最后一個實參。這也是為什么客戶端存根上的 getCourses() 對象只接受單一一個實參,而服務器端組件上的相應方法則不接受任何實參。getCourses() 方法返回在呈現頁面時所出現的球場集。
|
沒有 FacesContext 的生活
至 此,您可能會奇怪所有這些 Seam Remoting 請求是如何與 JSF 生命周期協作的。實際上,從典型意義上講,它們并非如此。不驚動 JSF 生命周期才是讓這些請求如此輕量的原因。從 JavaScript 在組件存根上調用方法時,從 Seam Resource Servlet 傳遞調用并由 Remoting 指派對象進行處理。由于 Resource Servlet 不 通過 JSF 生命周期接受請求,所以 FacesContext 在遠端調用過程中就不 可用。相反,Resource Servlet 使用其自身的生命周期來并入 Seam 組件容器。底線是:當方法通過 Seam Remoting API 執行時,任何不解析成由 Seam 管理的對象的值綁定表達式都將是 null 的。此外,由 backing bean 使用的 JSF 組件綁定亦不可用。
在 Open 18 應用程序的初始配置中,CourseAction bean 依賴于 Spring 框架的 JSF 變量解析器通過值綁定表達式 #{courseManager} 來將 CourseManager 對象注入到 backing bean。其中的問題是,Spring 變量解析器依賴于 FacesContext 定位 Spring 容器。如果 FacesContext 不可用,則變量解析器就不能訪問任何的 Spring bean。因而,當通過 Seam Remoting API 訪問組件時,值綁定表達式 #{courseManager} 將會是 null。
如果在遠端方法調用過程中需要大量使用 CourseManager 對象,那么上述情形就會遇到問題了。因為 Seam 的默認行為是強制依賴項存在,您需要用屬性 required=false 標注 @In 注釋,以表明它是可選的。這么做會讓 Seam 在調用通過遠端存根進行時,不會有什么怨言。
下一節顯示了如何使用 Ajax4jsf 來合并應用程序球場目錄頁和球場細節頁。這種增強的確 需要服務層對象來保持對球場的更改。然而,由于 Ajax4jsf 在調用動作處理程序時使用的是完整的生命周期,因而 #{courseManager} 表達式會正常解析。
|
用 Ajax4jsf 進行重呈現
Google Maps mashup 確實讓人興奮不已,但將它添加到 Open 18 應用程序卻會引入一個小的設計問題。當用戶為了查看有關此球場的詳細信息而單擊目錄中的球場名時,頁面就會刷新。結果,地圖就會頻繁地重載。這些重載會將太多負荷添加給瀏覽器和服務器,還會將地圖恢復到其原始位置。
|
要 防止地圖重載,必須將所有動作都限制為單一頁面加載。不幸的是,提交表單的處理與頁面請求緊密相連。這種緊密的耦合是 Web 瀏覽器的默認行為。您所需要的是數據能在瀏覽器不再請求頁面的前提下被發送到服務器,而這恰恰是 Ajax4jsf 組件庫的設計初衷。在目錄清單中的 Name 列,用 a4j:commandLink 替換 h:commandLink 標記讓 Ajax4jsf 在任何單擊了球場鏈接的地方都能發揮其作用。當鏈接的 Ajax4jsf 版本被激活時,它就會執行在動作屬性 #{courseAction.selectCourseNoNav} 中通過方法綁定表達式表明的方法,然后返回包含要被插入到目錄中的球場細節的替代 markup。
selectCourseNoNav() 方法執行與 selectCourse() 方法相同的邏輯,只不過它沒有返回值來確保 JSF 不尋求導航事件。畢竟,Ajax 請求的意義就是防止瀏覽器再次請求頁面。所返回的 XHTML markup 插入到地圖下面的區域之內,而且不會破壞地圖的狀態。清單 9 中所示的組件代替了 “無縫集成 JSF,第 2 部分: 借助 Seam 進行對話” 中 courses.jspx 的原始版本中所用的 h:commandLink:
清單 9. 用于選擇球場的 Ajax4jsf 鏈接
| <a4j:commandLink id="selectCourse" action="#{courseAction.selectCourseNoNav}" value="#{_course.name}" /> |
正如之前提到的,有兩種方法來指明頁面中的哪個區域需要增量更新。一種方法是針對單個組件,Ajax4jsf 動作組件的 reRender 屬性中指定它們的組件 ID。另一種方法是在 a4j:outputPanel 標記中包裹頁面中的區域并借助該標記的 ajaxRendered 屬性將這些區域標識為 “由 Ajax 呈現的”。清單 10 顯示了查看模板中能輸出所選球場細節的那個區域,方法是采用輸出-面板方式:
清單 10. 用 Ajax 呈現的細節面板
| <a4j:outputPanel id="detailPanel" ajaxRendered="true"> <h:panelGroup id="detail" rendered="#{course.id gt 0}"> <h3>Course Detail</h3> <!-- table excluded for brevity --> </h:panelGroup> </a4j:outputPanel> |
配置 Ajax4jsf
|
在運行所 更新的代碼之前,需要配置您應用程序中的 Ajax4jsf。首先,向應用程序類路徑添加 jboss-ajax4jsf.jar 庫及其依賴項 oscache.jar。其次,向 web.xml 添加 Ajax4jsf 過濾器。該過濾器的定義如清單 11 所示。請注意 Ajax4jsf 過濾器必須是在 web.xml 文件內定義的第一個過濾器。與 Seam 的 Resource Servlet 非常類似,這個過濾器處理由 Ajax4jsfs 組件發起的遠端調用。與 Seam 的遠端調用不同,JSF 生命周期在 Ajax4jsf 異步請求過程中是 活動的,因而需要 servlet 過濾器而非常規的 servlet。此外,還需要將 Facelets 視圖處理程序的配置從 faces-config.xml 文件移到 web.xml,在后者中,該配置在 servlet 上下文參數中定義。
清單 11. 配置 Ajax4jsf
| <context-param> <param-name>org.ajax4jsf.VIEW_HANDLERS</param-name> <param-value>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</param-value> </context-param> <filter> <filter-name>Ajax4jsf Filter</filter-name> <filter-class>org.ajax4jsf.Filter</filter-class> </filter> <filter-mapping> <filter-name>Ajax4jsf Filter</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping> |
一旦將清單 11 中的庫和安裝好的 markup 包括到 web.xml 文件,就可以使用 Ajax4jsf 了!但需要確保將 Ajax4jsf 標記庫定義 xmlns:a4j="https://ajax4jsf.dev.java.net/ajax" 添加到將要使用 Ajax4jsf 標記的視圖模板。當然,還有使用這個庫可以實現的許多其他功能,本文未能詳述。有關使用 Ajax4jsf 的更多信息和示例,請參看 參考資料。
|
再返回來!
至 此,Open 18-Google Maps mashup 就已完全只讀。圍繞 Ajax 有很多令人興奮的事情,其核心就是能在不重載頁面的情況下更改后端數據。我們不妨為我們的 Google map 添加一些有趣的東西以使其能發送回一些數據。為了演示,假設即使 Google 能將郵寄地址解析為經度和緯度,用戶還是想要為 pin 指定一個定制的位置。更準確地說,是用戶想讓 pin 指向俱樂部會所或給定球場的第一個發球臺。不幸的是,Google Map API 讓這個對 Open 18 應用程序的最后增強不大容易實現。
首先,讓標記能被拖動。要開啟此特性,只需在被實例化時向 GMarker 添加 draggable 標志。接下來,注冊一個 JavaScript 函數來偵聽當用戶結束拖動標記時所激發的 “拖動結束” 事件。在這個回調函數中,為了保存新的點,在 courseAction 存根上執行了一個新方法 setCoursePoint() 。
清單 12 給出了修改后的 createAndPlaceMarker 函數。考慮到放置這個標記時在 Course 條目上的那個定制點,addCourseMarker() 函數也進行了修改。
清單 12. 啟用了標記拖動功能的更新后的 JavaScript
| function addCourseMarker(course) { var address = course.getAddress(); if (course.getPoint() != null) { var point = course.getPoint(); var latlng = new GLatLng(point.getLatitude(), point.getLongitude()); createAndPlaceMarker(course, latlng); } else { var addressAsString = [ address.getStreet(), address.getCity(), address.getState(), address.getPostalCode() ].join(" "); geocoder.getLatLng(addressAsString, function(latlng) { createAndPlaceMarker(course, latlng); }); } } function createAndPlaceMarker(course, latlng) { // skip adding marker if no address is found if (!latlng) return; var marker = new GMarker(latlng, { draggable: true }); // hide the course directly on the marker marker.courseBean = course; markers[course.getId()] = marker; gmap.addOverlay(marker); function showDetailBalloon() { showCourseInfoBalloon(this); } function assignPoint() { var point = Seam.Remoting.createType("com.ibm.dw.open18.Point"); point.setLatitude(this.getPoint().lat()); point.setLongitude(this.getPoint().lng()); var courseActionStub = Seam.Component.getInstance("courseAction"); courseActionStub.setCoursePoint(this.courseBean.getId(), point); } GEvent.addListener(marker, 'click', showDetailBalloon); GEvent.addListener(marker, 'dragstart', closeInfoBalloon); GEvent.addListener(marker, 'dragend', assignPoint); } |
雖然還沒有大功告成,但已經離之不遠了!還需要向服務器端組件添加一個方法以保存這個 GLatLng 點。再堅持一會,因為您需要重新訪問 Open 18 應用程序的 Spring 集成后才能實現 mashup 的這個最后的特性。(參看 “無縫集成 JSF,第 2 部分: 借助 Seam 進行對話” 來重新訪問 Spring 集成的詳細信息。)
|
Spring 集成回顧
正 如之前所討論的,我最初將 Spring 容器集成到 Seam 中所采用的變量解析器方法有其自身的局限。坦白地講,該方法己經發展到了盡頭,現在該是和它說再見的時候了。大多數的 Seam 特性均涉及到了 JSF,而 Seam 的某些屬性卻工作于 JSF 生命周期之外。要獲得與 Spring 的真正集成,需要比定制變量解析器更好的解決方案。所幸的是,Seam 的開發人員已經開始著手解決這種需求,并添加了針對 Spring 的 Seam 擴展。Spring 集成包利用了 Spring 2.0 中的新特性來創建基于模式的擴展點。這些擴展在 bean 定義文件和名稱空間處理程序中啟用了定制 XML 名稱空間,在 Spring 容器的啟動過程中對這些標記進行操作。
用于 Spring 的 Seam 名稱空間處理程序(您可能需要大聲讀幾遍才能明白其中的含義)有幾種與 Spring 容器進行交互的方式。要去除定制的變量解析器,需要將 Spring bean 作為 Seam 組件公開。seam:component 標記可專門用于此目的。將此標記放置于 Spring bean 聲明之內會通知 Seam Spring bean 應該被代理或包裹成一個 Seam 組件。在這一點上,Seam 雙射機制會將這個 bean 視為仿佛它已經被 @Name 注釋,只有現在,您才能不需要 FacesContext 來填補 Seam 和 Spring 間的差距!
配置 Seam-Spring 集成異常容易。第一步都完全無需涉及任何代碼更改!所有您需要做的只是確保使用了 Spring 2.0 和 Seam 1.2.0 或后續發布。(自本系列的第一篇文章發表以來,Seam 已經有了迅速發展,所以應該準備好進行 一次升級。)IOC 集成打包成一個單獨的 JAR,jboss-seam-ioc.jar,所以除了已經提到的 JAR 之外,應該將它也包含到應用程序的類路徑中。
第 二個步驟也不會涉及到 Seam 配置。這次,您看到的是 Spring 配置。首先,向其中定義了 bean 的 Spring 配置 XML 添加 Seam XML 模式聲明。這個文件的標準名稱是 applicationContext.xml,但示例應用程序使用了一個更為合適的名字 spring-beans.xml。接下來,在任何您想要公開給 JSF 的 Spring bean 的定義內添加 seam:component 標記。在 Open 18 應用程序中,將此標記嵌套在 courseManager bean 定義內。要查看整個 bean 列表,請參考本文 示例應用程序 的 spring-beans.xml 文件。
有了改進后的 Spring 集成,就無需在 courseManager 屬性頂上的 @In 注釋中指定值綁定了,亦無需 required=false 屬性。取而代之的是 create=true 屬性以便 Seam 能夠知道從 Spring 容器中去獲取 bean 并將其裝飾成 Seam 組件。清單 13 中的代碼顯示了 CourseAction 類中支持球場的地理空間點更新的相關部分:
清單 13. CourseAction 上的新的 setPoint 方法
| @WebRemote public void setCoursePoint(Long id, Point point) { System.out.println("Saving new point: " + point + " for course id: " + id); Course course = courseManager.get(id); course.setPoint(point); courseManager.save(course); } |
現在您盡可以放松一下。沒錯,您已經成功地使自己的應用程序 “Ajax 化” 了!
|
結束語
在本文中,您看到 Open 18 應用程序從一個異常簡單的 CRUD 實現擴展成了一個十分復雜的 mashup 并集成了 Ajax 風格的頁面以呈現 Google Maps。您也看到了如何利用 Seam Remoting API 和 Ajax4jsf 并學習了集成 Seam 和 Spring 框架的一種更為復雜的方法。
Seam Remoting 和 Ajax4jsf 通過擴展 JFS 組件模型使其包括 Ajax 通信極大地補充了該模型。本文中給出的代碼均無需直接與 XMLHttpRequest JavaScript 對象交互。相反,所有工作都藏于較高層 API 背后,讓您可以直接查詢和處理服務器端組件,也可以調用動作和更新給定頁面的某些部分。得益于 Ajax 的魔力,所有的工作均可異步發生。
在本系列的這三篇文章中,您學習了大量有關結合了 Seam 的 JSF 開發的內容,但我還未能盡述 Seam 的所有特性!就讓 無縫集成 JSF 系列中的討論和示例作為您學習如何將 Seam 用在 JSF 項目中以使其更容易創建的很好的開始點吧。越多地接觸 Seam,您就越能發現它的可愛之處。
|
下載
| j-seam3.zip | 277K | HTTP |
| 關于下載方法的信息 | ||||
注意:
參考資料
學習- 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。
- “Seam-無縫集成 JSF,第 1 部分: 為 JSF 量身定做的應用程序框架”(Dan Allen,developerWorks,2007 年 4 月):概述了 Seam 對 JSF 生命周期的增強,包括第 2 部分中使用的內容。
- “無縫集成 JSF,第 2 部分: 借助 Seam 進行對話”(Dan Allen,developerWorks,2007 年 5 月):使用 Seam 構建一個有狀態的 CRUD 應用程序。
- “使用 JavaServer Faces 構建 Apache Geronimo 應用程序,第 3 部分:使用 ajax4jsf 添加 Ajax 功能”(Dale de los Reyes,developerWorks,2006 年 10 月):在無需編寫任何 JavaScript 代碼的前提下將 Ajax 引入到現有的 JSF 應用程序。
- “Ajax 技術資源中心:來自 developerWorks 社區的文章、教程、討論、下載等等諸多內容。
- Ten Good Reasons To Use Seam:JBoss 給出的原因列表,其中即有富客戶機開發,又有 “一旦使用就不能釋手”,真是 “包羅萬象” 呀。
- “Google Maps API Tutorial”(Mike Williams,Mike 的一個小 Web 頁):學習如何定制您的 Google map 的顯示。
- “Integrating the Google Web Toolkit and JSF using G4jsf”(Sergey Smirnov,TheServerSide.com,2006 年 8 月):G4jsf 是 Ajax4jsf 開發人員帶給您的又一個令人興奮不已的技術。
- “AJAX without the J: Thoughts on AJAX from a JSF Perspective”(Sergey Smirnov,Exadel Blogs,2006 年 2 月):有關無 JavaScript 的 Ajax 編程的更多信息。
- developerWorks Java 技術專區:這里有數百篇關于 Java 編程方方面面的文章。
獲得產品和技術
- 下載 JBoss Seam:獲得完整的發行版,包括附帶的例子應用程序。
- Ajax4jsf:將 Ajax 功能添加給現有的 JSF 組件。
- RichFaces:構建在 Ajax4jsf 之上來為 JSF Web 應用程序添加開箱即用的豐富性。
- Firebug:該 Firefox 插件是一個在處理 Ajax 應用程序時不可或缺的工具。
- 下載 Maven 2:源碼示例中使用的軟件工程管理和綜合工具。Maven 會自動在構建過程中下載依賴項。
- Facelets:用于 Seam 應用程序的首選 JSF 視圖處理程序。
- Appfuse:實際上向您提供了非常全面的基于 Maven 2 的項目框架,可用于根據它構建自己的應用程序。
討論
- Seam blog:了解其他用戶對 Seam 的看法并在 Seam 社區論壇 提交反饋。
- developerWorks Ajax 論壇: 面向學習或已經使用 Ajax 的 Web 開發人員的一個活躍社區。
- 通過參與 developerWorks blogs 加入 developerWorks 社區。
關于作者
Dan Allen 目前是 CodeRyte 的一名高級 Java 工程師。他還是一名熱情的開放源碼擁護者,每當他看到企鵝時就會有一點瘋狂。 從 Cornell 大學畢業并獲得材料科學與工程學位,Dan 對 Linux 和開放源碼軟件非常著迷。從那以后,他就沉浸在 Web 應用程序領域,最近幾年則專攻 Java 相關的技術,包括 Spring、Hibernate、Maven 2 和豐富的 JSF 堆棧。您可以在 http://www.mojavelinux.com 訂閱 Dan 的 blog,以跟蹤他的開發經驗。 | ||
轉載于:https://www.cnblogs.com/lanzhi/archive/2008/11/23/6469796.html
總結
以上是生活随笔為你收集整理的Seam - 无缝集成 JSF,第 3 部分: 用于 JSF 的 Ajax的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu gbk,utf-8 转换
- 下一篇: gradle idea java ssm