實現圖形JSF組件--很簡單地構建一個純HTML無法輕松實現的圖形Web應用程序組件
開發人員認為,如果有合適的工具來創建交互式Web界面,他們就能將時間集中在核心需求和定制上,并在規定時間內及時得交付應用程序。與其他技術如JavaServer Pages或Apache Struts 相比,JavaServer Faces (JSF)技術為創建交互式Web應用程序帶來了很多便利。JSF在程序邏輯和GUI表示之間劃出一條清晰的界限,提高了對Web程序的維護能力,并為Web用戶界面組件的開發和重用提供了一個框架。
如今,許多Web應用程序開發人員都在轉而使用JSF,但是他們發現,預先定制的JSF UI組件受到基本DHTML窗口部件的限制。監管或業務流程監控之類的高級應用程序需要能與JSF框架兼容的高級可視化組件。JSF框架的標準化使它易于開發能夠重用的自定義Web GUI組件。另外,Web組件開發商現在能提供更復雜的組件,并承諾Web應用程序開發人員能夠輕松地使用這些組件。此類JSF用戶界面組件必須集成并部署到JSF運行時框架中去,并在其中完全展開,還必須在設計時很好地集成到提供JSF支持的IDE中去。
盡管JSF帶來了標準用戶界面框架,但對于開發第一個自定義JSF組件而言,還是存在幾個缺陷和漏洞。讓我們看看怎樣創建一個純HTML無法輕松創建的圖形JSF組件。圖形JSF組件的特點不僅要求生成DHTML,而且還需要對圖像生成和客戶端交互提供補充支持。我們將以一個圖形組件的例子來闡述這些特點。該圖形組件能夠提供曲線圖,并為各種客戶端導航和交互提供便利。我們還會了解到將該圖形組件集成到JSF-enabled IDE中所需要的步驟。通過理解圖形組件的設計方法,您將會更好地理解如何實現JSF組件,而這應該能使您開發出定制的JSF圖形組件。
什么是JSF?
JSF是一種能夠簡化Web應用程序表示層結構的標準服務器端框架。定義JSF框架的JSR 127(參見參考資料)帶有一個能提供基本UI組件(如輸入欄和按紐)的參考實現。您可以將可重用用戶界面組件集中起來創建Web頁,將這些組件綁定到應用數據源上,并用服務器端事件控制程序處理客戶端事件。根據說明書介紹,組件供應商能編寫與JSF運行時框架集成的組件,并將其集成到在設計時與JSF兼容的IDE中去。
從很大程度上講,JSF組件同在HTML 2.0技術要求下可用的HTML組件和標簽直接相符合。對許多Web應用程序而言,這套相對簡單的組件是夠用的。然而,許多應用程序如監管或監控程序需要更復雜的數據顯示與交互,比如制表、制圖和映射。由于JSF組件在HTML中直接提交復雜圖形小部件的能力有限,所以設計這些高級組件的能力并不突出。解決方案要求服務器端組件向客戶傳輸圖像,卻會給自身帶來問題,因為在基本HTML圖像上進行交互要受到限制。最后,使用JavaScript時,必須能調用客戶端交互來使用戶能對數據進行導航和交互。
讓我們看看開發一個簡單的、將CSS輸入HTML頁面的JSF組件需要哪些步驟。當開發高級JSF圖形組件時,這一簡單組件的描述和代碼樣本會作為背景。圖1顯示了如何使用即將開發的組件,并顯示將要得到的結果。使用這種組件的好處是能夠通過改變某個JSF動作的組件值,來改變整個頁面的外觀。
圖1:顯示了我們如何使用一個非常簡單的JSF組件將CSS輸入某個HTML頁面并得出結果。
開發組件
JSF組件包含若干個Java類和配置文件。為創建一個自定義JSF組件,您需要開發一個擴展JSF基本組件類的Java類;為默認呈現軟件包開發呈現程序;開發一個將在JSP頁面中用于描述標簽的Java類;編寫一個標簽庫定義(TLD)文件;編寫JSF配置文件。讓我們更深入地了解這5個步驟。
開發組件Java類。組件類負責管理代表組件狀態的屬性。因此,我們必須根據組件的行為(如輸入組件或輸出組件),給組件選擇適當的基類(參見清單1)。這里描述的組件可進行擴展javax.faces.component.UIOutput,以顯示指向某個樣式表文件的URL,或內聯式樣式表的內容。該組件可用于在JSF動作中將某個樣式表轉換成另一個樣式表。關聯屬性規定著值的類型:要么是一個URL,要么是內聯樣式。該組件還必須能夠在向服務器發送請求期間,使用經過JSF框架處理的對象,來存儲并修復自己的狀態。組件的狀態由重建對象所需的重要屬性值組成。JSF框架自動調用saveState()和restoreState()方法,我們可以在組件中實現這兩種方法來達到這一目標。
清單1. 組件類管理顯示組件狀態的屬性。可依據組件的行為,為其選擇一個適當的基類。在本例中,該組件擴展javax.faces.component.UIOutput,以顯示指向某個樣式表文件的URL,或者某個內聯式樣式表的內容。
import javax.faces.component.*;
public class CSSComponent extends UIOutput {
private Boolean link;
public String getFamily() {
return "faces.CSSFamily";
}
public boolean isLink() {
if (link != null)
return link.booleanValue();
ValueBinding vb = getValueBinding("link");
if (vb != null) {
Boolean bvb = (Boolean) vb.getValue(
FacesContext.getCurrentInstance());
if (bvb != null)
return bvb.booleanValue();
}
return false;
}
public void setLink(boolean link) {
this.link = new Boolean(link);
}
public Object saveState(FacesContext context) {
return new Object[] { super.saveState(context),
link };
}
public void restoreState(FacesContext context,
Object stateObj) {
Object[] state = (Object[]) stateObj;
super.restoreState(context, state[0]);
link = (Boolean) state[1];
}
}
開發呈現程序。呈現程序有兩個作用。第一,呈現程序負責發送適當的HTML程序段,該程序段能在客戶端中呈現組件。通常情況下,這個HTML程序段由一些適于呈現整個Web瀏覽器的HTML標簽組成。此JSF生存周期稱作編碼階段或呈現—響應階段。該響應階段還能發送增強客戶端交互性的JavaScript代碼。
呈現程序的第二個作用是解析來自客戶端的數據,從而對服務器端的組件狀態進行更新(如用戶在文本字段輸入的文本)。標準呈現程序軟件包具有強制性,但也可以提供其他呈現程序軟件包,用于提供可替換的客戶端表示法或SVG之類的語言(參見參考資料)。通過檢驗組件的連接屬性,您實現的呈現程序(參見清單2)將選擇在HTML頁面中發送的CSS樣式。
清單2. 標準呈現程序軟件包具有強制性,但是,您可以使用其他呈現程序軟件包,來提供可替換的客戶端表示法或語言。通過檢驗組件的連接屬性,您實現的呈現程序將選擇在HTML頁面中發出的CSS樣式。
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
public class CSSRenderer extends Renderer {
public void encodeEnd(FacesContext context,
UIComponent component)
throws IOException {
super.encodeEnd(context, component);
if (component instanceof CSSComponent) {
CSSComponent cssComponent =
(CSSComponent) component;
String css = (String)cssComponent.getValue();
boolean isLink = cssComponent.isLink();
if (css != null)
if (isLink)
context.getResponseWriter().write(
"<link type='text/css' rel='stylesheet'
href='" + css + "'/>");
else
context.getResponseWriter().write(
"<style>;\n" + css + "\n<style/>\n");
}
}
}
開發標簽類。同樣,JSF框架提供了用于擴展的基類,來編寫與組件相關的標簽。該標簽類負責定義并呈現將在faces-config.xml文件中應用的組件樣式(這種樣式的描述很簡短)。它還負責創建JSF組件(由JSF框架來處理),傳遞JSF標簽中所包含的屬性,該屬性用于初始化組件(參見清單3)。
清單3. 該標簽類定義了將在faces-config.xml文件中應用的組件的樣式和組件呈現方式。
import javax.faces.webapp.UIComponentTag;
public class CSSTag
extends UIComponentTag {
private String value;
private String link;
public String getComponentType() {
return "faces.CSSComponent";
}
public String getRendererType() {
return “HTML.LinkOrInlineRenderer";
}
protected void setProperties(UIComponent component) {
super.setProperties(component);
Application app =
getFacesContext().getApplication();
if (value != null)
if (isValueReference(value))
component.setValueBinding("value",
app.createValueBinding(value));
else
component.getAttributes().put("value", value);
if (link != null)
if (isValueReference(link))
component.setValueBinding("link",
app.createValueBinding(link));
else
component.getAttributes().put("link",
new Boolean(link));
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
該標簽提供setter和getter來管理鏈接和值屬性。組件一旦創建,便會調用setProperties()方法,對標簽屬性進行初始化。每個標簽屬性都無外乎兩種:要么是文字值,要么是bean屬性的一個綁定。
編寫標簽庫定義(TLD)。TLD是一個XML文件,它通過將標簽名與相應的Java類相關聯來描述標簽。TLD還描述了標簽所允許的屬性(參見清單4)。這個TLD定義了一個名為css的標簽,該標簽綁定到CSSTag類。它還聲明了鏈接和值標簽屬性。
清單4. TLD是一個通過將標簽名與相應的Java類相關聯來描述標簽的XML文件。TLD定義了名為css的標簽,使其與CSSTag類綁定。它還聲明了鏈接和值標簽屬性。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC
"-//Sun Microsystems, Inc.//
DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>custom</short-name>
<uri>http://www.ilog.com/jviews/tlds/css.tld</uri>
<description>This tag library contains a tag for a
sample custom JSF Component.</description>
<tag>
<name>css</name>
<tag-class>path.to.CSSTag</tag-class>
<description>A component that displays the style
inline or a link a to a css file</description>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The id of this component.
</description>
</attribute>
<attribute>
<name>binding</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The value binding expression
linking this component to a property in a
backing bean. If this attribute is set, the
tag does not create the component itself but
retrieves it from the bean property. This
attribute must be a value binding.
</description>
</attribute>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The inline css text or the url to
the css file to link.</description>
</attribute>
<attribute>
<name>link</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>Whether the value is a link or
the inline style.</description>
</attribute>
</tag>
</taglib>
編寫JSF配置文件。為了將某個JSF組件集成到框架中,您必須提供一個名為faces-config.xml的配置文件。該文件將組件類型和呈現程序類型(用于JSP定制標簽處理程序)與對應的Java類關聯起來。它還能描述與每個組件一同使用的呈現程序(參見清單5)。該文件定義了faces.CSSFamily組件家族。在本例中,該家族由faces.CSSComponent這一個組件類型(該類型與CSSComponent類綁定)組成。最后,HTML.LinkOrInlineRenderer類型的呈現程序(由CSSComponent類實現)要與faces.CSSFamily家族相關聯。
清單5. 該文件將組件類型和呈現程序類型與對應的Java類聯系起來,并描述與每個組件一同使用的呈現程序。它還定義了faces.CSSFamily組件家族。
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//
DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<component>
<component-type>faces.CSSComponent
</component-type>
<component-class>path.to.CSSComponent
</component-class>
<component-extension>
<component-family>faces.CSSFamily
</component-family>
<renderer-type>HTML.LinkOrInlineRenderer
</renderer-type>
</component-extension>
</component>
<render-kit>
<renderer>
<component-family>faces.CSSFamily
</component-family>
<renderer-type> HTML.LinkOrInlineRenderer
</renderer-type>
<renderer-class>path.to.CSSRenderer
</renderer-class>
</renderer>
/render-kit>
</faces-config>
開始制圖
如果您希望將自己的組件集成到JSF-enabled IDE中,您還可以提供補充說明。比如說,除提供其他的設計時信息外,還可以提供一個名為sun-faces-config.xml的XML配置文件,用于描述應在IDE中公開的組件屬性。
既然已經看到如何創建一個簡單的JSF組件,不妨再來看看怎樣創建一個圖形JSF組件。我們將遵循同樣的基本步驟來設計一個高級JSF圖形組件。讓我們以一個圖形組件(如ILOG JSF圖形組件)為例,通過一組分類,該組件為數據值分布提供了可視化表示。該圖形能夠以條型統計圖、圓形分格統計圖和氣泡式統計圖等各種顯示方法來顯示數據集合。該JSF圖形組件有兩個初始設計限制:
我們已經擁有Java圖形bean組件,它具備所有圖形顯示能力。該組件可以顯示很多圖形,而且可定制性很高。在理想情況下,我們希望利用bean組件,使用它的功能來構成我們的JSF組件的基礎。
普通JSF應用程序需要重新載入整個頁面以更新視圖。這種方法適合基于表單的應用程序,但在很多情況下卻不適用于高度圖形化的用戶界面。因此,我們的JSF圖形組件必須能在不更新整個頁面的前提下處理某些簡單的導航,以提供更好的用戶體驗。
以下是滿足這些需求的解決方案:該JSF圖形組件將管理圖形bean組件,包括創建圖形bean、定制該bean以及使該bean可用于服務器端操作。呈現JSF組件將分為兩個階段完成。JSF呈現程序會產生一個<img>標簽和一套JavaScript對象(參見圖2)。客戶端將請求服務器發回一張圖像。這一請求由某個servlet完成,該servlet獲得圖形bean,并利用圖形提供的方法生成一幅圖像(參見圖3)。任何只改變該圖形外觀的進一步用戶交互(放大、掃視、更改樣式表等)都會引起圖形的一次增量更新。如果客戶端不只是要求對圖形圖像進行更新,那么將提交該頁面(參見圖4)。
圖2JSF圖形組件管理圖形bean組件,包括創建圖形bean、對其進行定制,并使其可用于服務器端動作。JSF呈現程序生成一個<img>標簽和一套JavaScript對象。
圖3 客戶機通過servlet要求服務器獲得一張圖像。該servlet獲得圖形bean,并通過由圖形提供的方法生成一幅圖像。
圖4如果客戶端不只是要求對圖形外觀的進行更新,那么頁面將被提交。
JSF圖形組件還配有一套附加的JSF組件。overview可顯示該圖形整體視圖,顯示一個代表圖形視圖的長方形,還應允許用戶掃描可視區域。legend組件可顯示數據集合的相關信息,還能自行在圖形中顯示,依被顯示數據的樣式而定。也能提供客戶端的interactors如掃描和放大,這些功能可看成是客戶端交互,表示與圖形的交互不會像一次正常的JSF交互那樣重新載入整個頁面。
要想呈現圖形組件,只需使用chartView標簽:
<jvcf:chartView id="c" style="width:500px;height:300px" … />
該數據在HTML頁面中作為圖像顯示。該圖像由servlet創建,旨在響應一次HTTP請求(該請求包括指定結果圖像、生成圖像映射以及生成內聯式圖例等各種參數)。結果圖像隨之被嵌入客戶端DOM,頁面中只有圖像自身這一部分被更新。
應用程序核心部件
讓我們看看簡單的定制JSF組件和高級圖形組件之間的一些區別。JSF圖形組件類很像一個標準組件,不過是多了一個可訪問圖形bean(該圖形bean負責生成在HTML頁面中顯示的圖像)的圖形屬性。JSF組件可以通過某個綁定值或在當前會話中對這個圖形bean進行局部檢索。當JSF圖形組件成為某個應用程序的核心部件時,可選的JSF組件(如概覽或圖例)便與主圖形相關聯,來顯示附加信息(見清單6)。
清單6. 當JSF圖形組件成為某個應用程序的核心部件時,可選的JSF組件便與主圖形相關聯,來顯示附加信息。
<jvcf:chartZoomInteractor id="chartZoomInteractor"
XZoomAllowed="true"
YZoomAllowed="true" />
<jvcf:chartView id="chartView"
chart="#{myBean.chart}"
servlet="demo.ImageMapServlet"
interactorId="chartZoomInteractor"
width="500"
height="300"
styleSheets="/data/line.css"
waitingImage="data/images/wait.gif"
imageFormat="PNG" />
<jvcf:chartOverview id="chartOverview"
style="height:100;width:150px"
viewId="chartView"
lineWidth="3"
lineColor="red" />
<jvcf:chartLegend id="legendView"
viewId="chartView"
width="400"
height="180"
layout="vertical"
waitingImage="data/images/wait.gif" />
呈現程序是實現這個JSF的一大難點。如前所述,呈現程序并不生成簡單的HTML,而是生成由HTML(<IMG> tag)和JavaScript proxy(代理程序)組成的動態HTML(DHTML)。
Proxy是一個負責管理客戶機組件圖像顯示的JavaScript類實例。該對象是服務器端Java組件類在客戶端顯示;它與組件類具有相同屬性。頁面上的每個組件、圖形及其配件都有一個proxy實例。呈現JavaScript時,在每個可視的JavaScript變量上使用facesContext.getExternalContext().encodeNamespace(name)方法是個很好的實踐。這樣做在今后方便地將組件集成到到JSR 168-compliant端口環境中。
為舉例說明客戶機上的proxy,必須在頁面上導入JavaScript支持庫。為保持客戶端盡量瘦,需要基于JavaScript庫支持的proxy類,對JavaScript庫進行模塊化。因此,需要給每個proxy類輸入一套不同的、有可能重疊的庫。圖形呈現的困難部分,出現在發送這些script庫的階段。每個組件的呈現程序都要聲明自己需要哪個庫,以及什么時候發送引用的庫,必須認清已發送的庫,以避免重復。僅在頁面呈現期間的存在script管理器負責這項篩選工作。每當呈現程序想要發送整套庫輸入時,它都會向篩選出已發送庫的script管理器提供列表。
客戶端proxy的目的在于允許編寫腳本,并避免不必要的頁面更新。一旦呈現了圖形,在客戶端便可使用proxy,以便動態安裝interactor,并顯示或隱藏圖像映射。Proxy對象也可供支持JavaScript鼠標事件處理的常規JSF組件使用。
<jvcf:chartView id=
"chartView" .. />
<h:selectBooleanCheckbox id=
"genImageMap" onclick=
"chartView.setGenerateImageMap(
this.checked ? true : false,
true);" />
對組件客戶端proxy進行局部修改的問題在于,其狀態不再與服務器上的Java組件的狀態同步。為解決這個問題,proxy使用一個隱藏的輸入標簽(<INPUT TYPE="HIDDEN">)來保存客戶機上的新狀態。當執行某個標準JSF動作并提交頁面時,呈現程序將解析該隱藏狀態,使客戶機與服務器同步。這種行為需要呈現程序類中有專門的破解行為。標準破解方法得以改進,以便解析來自客戶機的狀態,并更新服務器端組件的狀態。
測試實例
圖形及其相關組件之間的關聯由標記引用與綁定來完成。為使頁面設計具有靈活性,一個組件可以在呈現之前被引用。因此,在呈現時間內,如果某個組件屬性引用另一個尚未呈現的組件,那么,將延遲發送依賴于客戶機進行解決的JavaScript代碼,直到呈現已引用的組件。此工作可由依賴性管理器完成。
為證實這一點,不妨看一個典型實例,該實例涉及某個概覽,該概覽引用一張圖形。
<jvcf:overview viewId=
"chart" [...] />
<jvcf:chartView id=
"chart" [....] />
存在兩種情況。被引用圖形組件已經呈現,因此不存在任何問題
JSP:
<jvcf:chartView id=
"chart" [....] />
<jvcf:overview viewId=
"chart" id="overview" [...] />
render:
[...]
var chart =
new IlvChartViewProxy ( .. );
[...]
var overview=
new IlvFacesOverviewProxy (
.. );
overview.setView(chart);
[...]
已引用圖形的組件在依賴的概覽組件之前不會呈現。既然如此,可在依賴性管理器上注冊一個組件創建監視器。已引用圖形組件最終呈現時,其呈現程序會通知自己創建的依賴性管理器。此時,將發送解決依賴性所需的代碼:
JSP:
<jvf:overview viewId=
"chart" id="overview" [...] />
<jvdf:chartView id=
"chart" [....] />
render:
[...]
var overview =
new IlvFacesOverviewProxy (
.. );
[...]
var chart =
new IlvChartViewProxy ( .. );
overview.setView(chart);
[...]
開發JSF組件的目的之一,是能夠將它們應用于任何與JSF兼容的IDE。盡管如此,JSF兼容性并不足以保證這種設計時集成將會有效。下面是在開發JSF組件過程中,為了便于在今后與IDE集成需要注意的一些簡單思想:
首先,定制JSF組件應該提供一個基本HTML呈現程序。在設計時,JSF IDE不能呈現請求有效數據或app服務器連接的動態圖形組件。因此,帶有復雜的或非傳統的(比如不是HTML)呈現程序的組件,應該使用Beans.isDesignTime()來確定是提供一個基本HTML表示法,還是提供真正的組件呈現程序。
另一個設計時問題是組件的位置和大小。不同IDE使用不同的標志和屬性。能夠調整大小的組件(如一幅圖像)應能處理定義大小的不同方式。
最后,為了與IDE集成,組件必須提供尚未被JSF說明定義的補充信息。遺憾的是,當前每個IDE都需要特殊處理程序來集成組件,即:在一種情況就需要XML文件,而在另一種情況下需要eclipse插件,如此等等。下一個JSF JSR(2.0版)的主要目的之一,將是指定附加的元數據格式。
如您所見,編寫一個簡單的JSF組件并不難,因為框架已經完成了大部分工作。JSF框架管理著組件狀態、呈現程序等等。在本文中,我們已經擴展了這些基本概念,來設計一個能夠顯示復雜元數據、提供增量更新、支持大量客戶端交互并與配套組件協作的高級圖形JSF組件。支持這些特點需要對基本JSF組件的結構進行許多改進。當然,增量更新的概念今后對JSF框架將是一個很好的完善,因為它只允許呈現頁面已改變的部分,避免了更新整個頁面。按照JSF說明書工作往往不足以確保組件完全集成到JSF IDE中;一個新JSR應能及時解決這些難題。盡管存在缺陷,JSF框架仍能極大地加快Web組件開發速度、方便的融合來自各種資源的組件,以創建完整的、復雜的Web應用程序。
參考資料
作者簡介
Marc Durocher是ILOG的一名軟件架構師,ILOG是企業級軟件組件和服務的主要提供商。Marc Durocher在ILOG負責開發ILOG JViews生產線上的JSF組件。可以通過mdurocher@ilog.fr聯系Marc。
原文出處
http://www.ftponline.com/weblogicpro/2005_03/magazine/features/mdurocher/