通過JSF 2實(shí)現(xiàn)可重用的Ajax化組件
51CTO向讀者們具體介紹過Ajax支持包,以及利用“Ajax聽取JSF 2客戶端處理代碼中的事件和錯(cuò)誤”。今天51CTO編輯向大家推薦這篇文章結(jié)合復(fù)合組件和Ajax輕而易舉地實(shí)現(xiàn)支持Ajax自定義組件。
關(guān)于本系列JSFfu系列建立在DavidGeary的同名簡介文章的概念的基礎(chǔ)之上。本系列將深入探究JSF2及其生態(tài)系統(tǒng),同時(shí)還將介紹如何將一些JavaEE技術(shù),如Contexts和DependencyInjection,與JSF相集成。
在本文中,我將向您介紹如何實(shí)現(xiàn)自動(dòng)完成組件,它將使用Ajax管理其完成項(xiàng)列表。在此過程中,您將了解如何將Ajax集成到您自己的復(fù)合組件中。
本系列的代碼基于在企業(yè)容器,如GlassFish或Resin,中運(yùn)行的JSF2。本文的最后一部分將詳細(xì)討論如何使用GlassFish來安裝和運(yùn)行本文的代碼。
JSF自動(dòng)完成自定義組件
因谷歌搜索字段而聞名的自動(dòng)完成字段(也稱作建議框)是許多Web應(yīng)用程序的組合。它們也是Ajax的典型應(yīng)用。自動(dòng)完成字段隨帶了許多Ajax框架,比如Scriptaculous和JQuery,如圖1—AjaxDaddy的自動(dòng)完成組件集成(參閱參考資料)—所示:
圖1.AjaxDaddy自動(dòng)完成組件
本文將討論如何使用JSF來實(shí)現(xiàn)支持Ajax的自動(dòng)完成字段。您將了解如何實(shí)現(xiàn)如圖2所示的自動(dòng)完成字段,其中將顯示一個(gè)簡短的虛擬國家列表(選自Wikipedia的“虛擬國家列表”一文;請參閱參考資料):
圖2.自動(dòng)完成字段
#p#
圖3和圖4顯示了運(yùn)行中的自動(dòng)完成字段。在圖3中,在字段中輸入Al之后,國家列表將縮減至使用這兩個(gè)字母開頭的名稱:
圖3.使用Al開頭的完成項(xiàng)目
同樣,圖4顯示了在字段中輸入Bar之后顯示的結(jié)果。列表僅顯示以Bar開頭的國家名。
圖4.以Bar開頭的完成項(xiàng)目
使用自動(dòng)完成組件
復(fù)合組件:基礎(chǔ)如果您不熟悉如何實(shí)現(xiàn)JSF2復(fù)合組件,那么您可以從“JSF2簡介,第2部分:模板及復(fù)合組件”這篇文章中了解基本知識。
.Locations自動(dòng)完成字段是一個(gè)JSF復(fù)合組件,并應(yīng)用于facelet,如清單1所示:
清單1.facelet
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:util="http://java.sun.com/jsf/composite/util">
- <h:head>
- <title>#{msgs.autoCompleteWindowTitle}</title>
- </h:head>
- <h:body>
- <div style="padding: 20px;">
- <h:form>
- <h:panelGrid columns="2">
- #{msgs.locationsPrompt}
- <util:autoComplete value="#{user.country}"
- completionItems="#{autoComplete.countries}" />
- </h:panelGrid>
- </h:form>
- </div>
- </h:body>
- </html>
清單1中的facelet通過聲明適當(dāng)?shù)拿Q空間—util—以及借助組件的相關(guān)標(biāo)記(<util:autoComplete>)來使用autoComplete復(fù)合組件。
注意清單1中<util:autoComplete>標(biāo)記的兩個(gè)屬性:
•value是名稱為user的托管bean的國家屬性。
•completionItems是字段的完成項(xiàng)目的初始集。
User類是一個(gè)簡單的托管bean,專為本例而設(shè)計(jì)。其代碼如清單2所示:
#p#
清單2.User類
- package com.corejsf;
- import java.io.Serializable;
- import javax.inject.Named;
- import javax.enterprise.context.SessionScoped;
- @Named()
- @SessionScoped
- public class User implements Serializable {
- private String country;
- public String getCountry() { return country; }
- public void setCountry(String country) { this.country = country; }
- }
請注意@Named注釋,它與@SessionScoped一起實(shí)例化了一個(gè)名稱為user的托管bean,并在JSF第一次在facelet中遇到#{user.country}時(shí)將它置于session作用域中。此應(yīng)用程序中唯一的#{user.country}引用發(fā)生在清單1中,其中,我將user托管bean的country屬性指定為<util:autoComplete>組件的值。
清單3顯示了AutoComplete類,該類定義了countries屬性,即自動(dòng)完成組件的完成項(xiàng)目列表:
清單3.完成項(xiàng)目
- package com.corejsf;
- import java.io.Serializable;
- import javax.enterprise.context.ApplicationScoped;
- import javax.inject.Named;
- @Named
- @ApplicationScoped
- public class AutoComplete implements Serializable {
- public String[] getLocations() {
- return new String[] {
- "Abari", "Absurdsvanj", "Adjikistan", "Afromacoland",
- "Agrabah", "Agaria", "Aijina", "Ajir", "Al-Alemand",
- "Al Amarja", "Alaine", "Albenistan", "Aldestan",
- "Al Hari", "Alpine Emirates", "Altruria",
- "Allied States of America", "BabaKiueria", "Babalstan",
- "Babar's Kingdom","Backhairistan", "Bacteria",
- "Bahar", "Bahavia", "Bahkan", "Bakaslavia",
- "Balamkadar", "Baki", "Balinderry", "Balochistan",
- "Baltish", "Baltonia", "Bataniland, Republic of",
- "Bayview", "Banania, Republica de", "Bandrika",
- "Bangalia", "Bangstoff", "Bapetikosweti", "Baracq",
- "Baraza", "Barataria", "Barclay Islands",
- "Barringtonia", "Bay View", "Basenji",
- };
- }
- }
自動(dòng)完成組件的使用方法已經(jīng)介紹完畢。現(xiàn)在,您將了解它的工作原理。
#p#
自動(dòng)完成組件的工作原理
自動(dòng)完成組件是一個(gè)JSF2復(fù)合組件,因此,與大多數(shù)復(fù)合組件相同,它是在XHTML文件中實(shí)現(xiàn)的。組件包括一個(gè)文本輸入和一個(gè)列表框,以及一些JavaScript代碼。最開始,列表框style是display:none,其作用是讓列表框不可見。
自動(dòng)完成組件響應(yīng)三個(gè)事件:
◆文本輸入中的keyup事件
◆文本輸入中的blur(失焦)事件
◆列表框中的change(選擇)事件
當(dāng)用戶在文本輸入中鍵入內(nèi)容時(shí),自動(dòng)完成組件會(huì)調(diào)用每個(gè)keyup事件的JavaScript函數(shù)。該函數(shù)結(jié)合鍵盤輸入事件,以不大于350ms的間隔定期調(diào)用Ajax。因此,在響應(yīng)文本輸入中的keyup事件時(shí),自動(dòng)完成組件會(huì)以不大于350ms的間隔定期向服務(wù)器發(fā)起Ajax調(diào)用。(其作用是防止快速輸入時(shí)的大量Ajax調(diào)用將服務(wù)器淹沒。在實(shí)踐中,結(jié)合事件的頻率可能會(huì)稍高,但這足以演示如何在JavaScript中結(jié)合事件,同時(shí)這是一個(gè)非常實(shí)用的工具。)
當(dāng)用戶從列表框中選擇項(xiàng)目時(shí),自動(dòng)完成組件會(huì)向服務(wù)器發(fā)起另一個(gè)Ajax調(diào)用。
文本輸入和列表框都附加了監(jiān)聽程序,它們在Ajax調(diào)用期間完成與服務(wù)器相關(guān)的大部分有意義的工作。在響應(yīng)keyup事件時(shí),文本輸入的監(jiān)聽程序會(huì)更新列表框的完成項(xiàng)目。在響應(yīng)列表框的選擇事件時(shí),列表框的監(jiān)聽程序會(huì)將列表框的選中項(xiàng)目復(fù)制到文本輸入中,并隱藏列表框。
現(xiàn)在,您已經(jīng)了解了自動(dòng)完成組件的工作原理。接下來,我們來看看它的具體實(shí)現(xiàn)。
實(shí)現(xiàn)自動(dòng)完成組件
自動(dòng)完成組件實(shí)現(xiàn)包括以下工件:
◆一個(gè)復(fù)合組件
◆一系列JavaScript函數(shù)
◆一個(gè)用于更新完成項(xiàng)目的值變更監(jiān)聽程序
我將從清單4開始復(fù)合組件:
清單4.autoComplete組件
- <ui:composition xmlns="http://www.w3.org/1999/xhtml"
- xmlns:ui="http://java.sun.com/jsf/facelets"
- xmlns:f="http://java.sun.com/jsf/core"
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:composite="http://java.sun.com/jsf/composite">
- <!-- INTERFACE -->
- <composite:interface>
- <composite:attribute name="value" required="true"/>
- <composite:attribute name="completionItems" required="true"/>
- </composite:interface>
- <!-- IMPLEMENATION -->
- <composite:implementation>
- <div id="#{cc.clientId}">
- <h:outputScript library="javascript"
- name="prototype-1.6.0.2.js" target="head"/>
- <h:outputScript library="javascript"
- name="autoComplete.js" target="head"/>
- <h:inputText id="input" value="#{cc.attrs.value}"
- onkeyup="com.corejsf.updateCompletionItems(this, event)"
- onblur="com.corejsf.inputLostFocus(this)"
- valueChangeListener="#{autocompleteListener.valueChanged}"/>
- <h:selectOneListbox id="listbox" style="display: none"
- valueChangeListener="#{autocompleteListener.completionItemSelected}">
- <f:selectItems value="#{cc.attrs.completionItems}"/>
- <f:ajax render="input"/>
- </h:selectOneListbox>
- <div>
- </composite:implementation>
- </ui:composition>
清單4的實(shí)現(xiàn)部分完成了三項(xiàng)任務(wù)。首先,該組件發(fā)起Ajax調(diào)用以響應(yīng)文本輸入中的keyup事件,并通過分配給文本輸入中的keyup和blur事件的JavaScript函數(shù)在文本輸入失焦時(shí)隱藏列表框。
其實(shí),該組件通過JSF2的<f:ajax>標(biāo)記來發(fā)起Ajax調(diào)用來響應(yīng)列表框中的change事件。當(dāng)用戶從列表框中進(jìn)行選擇時(shí),JSF會(huì)向服務(wù)器發(fā)起一個(gè)Ajax調(diào)用,并在Ajax調(diào)用返回時(shí)更新文本輸入的值。
在<div>中封裝復(fù)合組件清單4中的復(fù)合組件通過復(fù)合組件的客戶機(jī)標(biāo)識符將其實(shí)現(xiàn)封裝在<div>中。這樣,其他組件便可通過其客戶機(jī)ID來引用自動(dòng)完成組件。舉例來說,另一個(gè)組件可能會(huì)希望在Ajax調(diào)用期間執(zhí)行或呈現(xiàn)一個(gè)或多個(gè)自動(dòng)完成組件。
.第三,文本輸入和列表框都附加了相應(yīng)的值變更監(jiān)聽程序,因此當(dāng)JSF發(fā)起Ajax調(diào)用來響應(yīng)用戶在文本輸入中的鍵入操作時(shí),JSF會(huì)調(diào)用服務(wù)器上的文本輸入的值變更監(jiān)聽程序。當(dāng)用戶從列表框中選擇項(xiàng)目時(shí),JSF會(huì)向服務(wù)器發(fā)起一個(gè)Ajax調(diào)用并調(diào)用列表框的值變更監(jiān)聽程序。
#p#
清單5顯示了自動(dòng)完成組件所使用的JavaScript:
清單5.JavaScript
- if (!com)
- var com = {}
- if (!com.corejsf) {
- var focusLostTimeout
- com.corejsf = {
- errorHandler : function(data) {
- alert("Error occurred during Ajax call: " + data.description)
- },
- updateCompletionItems : function(input, event) {
- var keystrokeTimeout
- jsf.ajax.addOnError(com.corejsf.errorHandler)
- var ajaxRequest = function() {
- jsf.ajax.request(input, event, {
- render: com.corejsf.getListboxId(input),
- x: Element.cumulativeOffset(input)[0],
- y: Element.cumulativeOffset(input)[1]
- + Element.getHeight(input)
- })
- }
- window.clearTimeout(keystrokeTimeout)
- keystrokeTimeout = window.setTimeout(ajaxRequest, 350)
- },
- inputLostFocus : function(input) {
- var hideListbox = function() {
- Element.hide(com.corejsf.getListboxId(input))
- }
- focusLostTimeout = window.setTimeout(hideListbox, 200)
- },
- getListboxId : function(input) {
- var clientId = new String(input.name)
- var lastIndex = clientId.lastIndexOf(':')
- return clientId.substring(0, lastIndex) + ':listbox'
- }
- }
- }
清單5中的JavaScript包括三個(gè)函數(shù),我把它們放置在com.corejsf名稱空間的內(nèi)部。我實(shí)現(xiàn)了名稱空間(從技術(shù)上說是一個(gè)JavaScript字面對象),以防止其他人有意或無意修改我的三個(gè)函數(shù)。
如果這些函數(shù)未包含在com.corejsf中,則其他人可以實(shí)現(xiàn)自己的updateCompletionItems函數(shù),從而將我的實(shí)現(xiàn)替換成它們。一些JavaScript庫可以實(shí)現(xiàn)一個(gè)updateCompletionItems函數(shù),但最理想的情況是任何人都不用設(shè)計(jì)com.corejsf.updateCompletionItems。(相反,拋棄com,并使用corejsf.updateCompletionItems可能已經(jīng)足夠,但有時(shí)會(huì)難以控制。)
因此,這些函數(shù)做了些什么?updateCompletionItems()函數(shù)向服務(wù)器發(fā)起Ajax請求—通過調(diào)用JSF的jsf.ajax.request()函數(shù)—要求JSF僅在Ajax調(diào)用返回時(shí)呈現(xiàn)列表框組件。updateCompletionItems()函數(shù)還傳遞了兩個(gè)額外的參數(shù)到j(luò)sf.ajax.request()中:列表框左上角的x和y坐標(biāo)。jsf.ajax.request()函數(shù)會(huì)將這些函數(shù)參數(shù)轉(zhuǎn)換為通過Ajax調(diào)用發(fā)送的請求參數(shù)。
JSF會(huì)在文本輸入失焦時(shí)調(diào)用inputLostFocus()函數(shù)。該函數(shù)的作用是使用Prototype的Element對象來隱藏列表框。
updateCompletionItems()和inputLostFocus()將它們的功能存儲在一個(gè)函數(shù)中。然后,它們安排自己的函數(shù)分別在350ms和200ms時(shí)執(zhí)行。換句話說,每個(gè)函數(shù)都有各自的任務(wù),但它會(huì)讓任務(wù)延時(shí)350ms或200ms。文本輸入會(huì)在keyup事件后延時(shí),因此,updateCompletionItems()方法會(huì)最多每隔350ms發(fā)送一個(gè)Ajax請求。其思想是,如果用戶輸入速度極快,則不會(huì)讓Ajax調(diào)用淹沒服務(wù)器。
inputLostFocus函數(shù)會(huì)在文本輸入失焦時(shí)調(diào)用,并延時(shí)其任務(wù)200ms。這種延時(shí)是必要的,因?yàn)樵撝禃?huì)在Ajax調(diào)用返回時(shí)復(fù)制到列表框之外,并且列表框必須為可見才能確保它正常運(yùn)行。
最后,請注意getListBoxId()函數(shù)。這個(gè)幫助器函數(shù)會(huì)從文本輸入的客戶機(jī)標(biāo)識符中獲取列表框的客戶機(jī)標(biāo)識符。該函數(shù)可以完成此任務(wù),因?yàn)樗鼘⑴c清單4中的autoComplete組件相結(jié)合。autoComplete組件將input和listbox分別指定為文本框和列表框的組件標(biāo)識符,因此getListBoxId()函數(shù)會(huì)刪除input并附加listbox,以便獲取文本輸入的客戶機(jī)標(biāo)識符。
#p#
清單6顯示了監(jiān)聽程序的最終實(shí)現(xiàn):
清單6.監(jiān)聽程序
- package com.corejsf;
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- import javax.enterprise.context.SessionScoped;
- import javax.faces.component.UIInput;
- import javax.faces.component.UISelectItems;
- import javax.faces.component.UISelectOne;
- import javax.faces.context.FacesContext;
- import javax.faces.event.ValueChangeEvent;
- import javax.inject.Named;
- @Named
- @SessionScoped
- public class AutocompleteListener implements Serializable {
- private static String COMPLETION_ITEMS_ATTR = "corejsf.completionItems";
- public void valueChanged(ValueChangeEvent e) {
- UIInput input = (UIInput)e.getSource();
- UISelectOne listbox = (UISelectOne)input.findComponent("listbox");
- if (listbox != null) {
- UISelectItems items = (UISelectItems)listbox.getChildren().get(0);
- Map<String, Object> attrs = listbox.getAttributes();
- List<String> newItems = getNewItems((String)input.getValue(),
- getCompletionItems(listbox, items, attrs));
- items.setValue(newItems.toArray());
- setListboxStyle(newItems.size(), attrs);
- }
- }
- public void completionItemSelected(ValueChangeEvent e) {
- UISelectOne listbox = (UISelectOne)e.getSource();
- UIInput input = (UIInput)listbox.findComponent("input");
- if(input != null) {
- input.setValue(listbox.getValue());
- }
- Map<String, Object> attrs = listbox.getAttributes();
- attrs.put("style", "display: none");
- }
- private List<String> getNewItems(String inputValue, String[] completionItems) {
- List<String> newnewItems = new ArrayList<String>();
- for (String item : completionItems) {
- String s = item.substring(0, inputValue.length());
- if (s.equalsIgnoreCase(inputValue))
- newItems.add(item);
- }
- return newItems;
- }
- private void setListboxStyle(int rows, Map<String, Object> attrs) {
- if (rows > 0) {
- Map<String, String> reqParams = FacesContext.getCurrentInstance()
- .getExternalContext().getRequestParameterMap();
- attrs.put("style", "display: inline; position: absolute; left: "
- + reqParams.get("x") + "px;" + " top: " + reqParams.get("y") + "px");
- attrs.put("size", rows == 1 ? 2 : rows);
- }
- else
- attrs.put("style", "display: none;");
- }
- private String[] getCompletionItems(UISelectOne listbox,
- UISelectItems items, Map<String, Object> attrs) {
- Strings] completionItems = (String[])attrs.get(COMPLETION_ITEMS_ATTR);
- if (completionItems == null) {
- completionItems = (String[])items.getValue();
- attrs.put(COMPLETION_ITEMS_ATTR, completionItems);
- }
- return completionItems;
- }
- }
JSF在Ajax調(diào)用期間調(diào)用監(jiān)聽程序的valueChanged()方法來響應(yīng)文本輸入中的keyup事件。該方法會(huì)創(chuàng)建一組新的完成項(xiàng)目,然后將列表框的項(xiàng)目設(shè)置為這個(gè)新的項(xiàng)目集。該方法還會(huì)設(shè)置列表框的樣式屬性,以確定Ajax調(diào)用返回時(shí)是否顯示列表框。
清單6中的setListboxStyle()方法將使用x和y請求我在發(fā)起清單5中的Ajax調(diào)用時(shí)指定的參數(shù)值。
JSF會(huì)在Ajax調(diào)用期間調(diào)用監(jiān)聽程序的其他公共方法completionItemSelected(),以響應(yīng)列表框中的選擇事件。該方法會(huì)將列表框的值復(fù)制到文本輸入中,并隱藏列表框。
請注意,valueChanged()方法還會(huì)將原始完成項(xiàng)目存儲在列表框的某個(gè)屬性中。由于每個(gè)autoComplete組件都維護(hù)自己的完成項(xiàng)目列表,因此多個(gè)autoComplete組件可以在相同頁面中和諧共存,而不會(huì)影響彼此的完成項(xiàng)目。
#p#
使用GlassFish和Eclipse運(yùn)行示例
本系列中的代碼適合在JEE6容器中運(yùn)行,比如GlassFish或Resin。您可以通過調(diào)整讓它們適應(yīng)servlet容器,但這并非理想方案。因此,我的目標(biāo)是側(cè)重于充分發(fā)揮JSF2和JEE6的潛力,而不是配置問題。我仍然堅(jiān)持使用GlassFishv3。
在本文的其余部分,我將向您展示如何使用GlassFishv3和Eclipse來運(yùn)行本文的示例代碼。此處的說明還適用于本系列其他文章的代碼。(我將使用Eclipse3.4.1,因此最好是使用與之相近的版本。)
圖5展示了本文代碼的目錄結(jié)構(gòu)。其中的autoComplete目錄包含應(yīng)用程序和一個(gè)空的Eclipse工作空間目錄。
圖5.本文下載部分的源代碼
現(xiàn)在,您已經(jīng)下載了代碼,接下來就可以運(yùn)行它了。首先,您需要GlassFishEclipse插件,可從https://glassfishplugins.dev.java.net下載它,如圖6所示:
圖6.GlassFishEclipse插件
請依照插件的安裝說明操作。
要安裝本文的代碼,請?jiān)贓clipse中創(chuàng)建一個(gè)DynamicWeb項(xiàng)目。為此,可以通過File>New菜單來實(shí)現(xiàn):如果未看到DynamicWebProject,那么請選擇Other,并在接下來的對話框中打開Web文件夾并選擇DynamicWebProject,如圖7所示:
圖7.創(chuàng)建一個(gè)DynamicWeb項(xiàng)目
#p#
下一步是配置項(xiàng)目。在NewDynamicWebProject向?qū)У牡谝粋€(gè)頁面中做出以下選擇,如圖8所示:
1.在Projectcontents下,保留Usedefault框?yàn)槲催x中狀態(tài)。在Directory字段中,輸入(或?yàn)g覽到)示例代碼的autoComplete目錄。
2.對于TargetRuntime,請選擇GlassFishv3JavaEE6。
3.對于DynamicWebModuleversion,請輸入2.5。
4.對于Configuration,請選擇DefaultConfigurationforGlassFishv3JavaEE6。
5.在EARMembership下,保留AddprojecttoanEAR框?yàn)槲催x中狀態(tài),并在EARProjectName:字段中輸入autoCompleteEAR。
圖8.配置應(yīng)用程序,步驟1
單擊Next,然后輸入如圖9所示的值:
1.對于ContextRoot:,輸入autoComplete。
2.對于ContentDirectory:,輸入web。
3.對于JavaSourceDirectory:,輸入src/java。保留Generatedeploymentdescriptor框?yàn)槲催x中狀態(tài)。
圖9.配置應(yīng)用程序,步驟2
現(xiàn)在,您應(yīng)該已經(jīng)建立了一個(gè)autoComplete項(xiàng)目,它將顯示在Eclipse的ProjectExplorer視圖中,如圖10所示:
圖10.autoComplete項(xiàng)目
#p#
現(xiàn)在,選擇項(xiàng)目,右鍵單擊它并選擇RunonServer,如圖11所示:
圖11.使用Eclipse在服務(wù)器上運(yùn)行
從RunOnServer對話框的服務(wù)器列表中選擇GlassFishv3JavaEE6,如圖12所示:
圖12.選擇GlassFish
#p#
單擊Finish。Eclipse應(yīng)該會(huì)相繼啟動(dòng)GlassFish和autoComplete應(yīng)用程序,如圖13所示:
13.在Eclipse中運(yùn)行
結(jié)束語
借助JSF2,開發(fā)人員可以輕松地創(chuàng)建功能強(qiáng)大、支持Ajax的自定義組件。您不需要在XML中實(shí)現(xiàn)基于Java的組件或呈現(xiàn)器,或者集成第三方JavaScript來發(fā)起Ajax調(diào)用。借助JSF2,您只需要使用幾乎與任何JSF2facelet視圖相同的標(biāo)記來創(chuàng)建一個(gè)復(fù)合組件,并根據(jù)需求添加一些JavaScript或Java代碼,以及voilà—您將實(shí)現(xiàn)一個(gè)奇妙自定義組件,為應(yīng)用程序用戶提供極為方便的數(shù)據(jù)輸入功能。
在JSFfu的下一期中,我將討論實(shí)現(xiàn)Ajax化JSF自定義組件的更多方面,比如將<f:ajax>標(biāo)記集成到您的自定義組件中,以參與其他人發(fā)起的Ajax。
【編輯推薦】