Sunday, August 20, 2006

翻译:JakataStruts 和JavaServerFace

翻译来源http://java.sys-con.com/read/260036.htm
设计一个包含有序选择的冗长列表需要非常复杂的设计模式。这种模式需要将可选项显示在一个列表中,同时还需要在另一个地方进行选择,所以需要用户的可以方便的进行选择和修改.
这种模式通常称为双重列表或者叫双重列表框选择器.它又被称为选择概要或者列表创建者模式.按照Java Look and Feel的设计规则,它又被称为增加-移除模式。
标准的Struts实现,需要试用JSP,Java和JavaScript。JavaServerFace不需要JSP技术,但是比起Struts要简单的多。
列表1是使用Struts技术的JSP代码,列表2是使用JavaServerFace技术的JSP代码。JSF的支持Bean,配置文件和其他文件的代码可以从JDJ网站下载。
Jakarta Struts的实现方式
使用Jakarta Struts技术,JSP需要定义一个表格来显示树状列。在第一列和第三列,可选列通过<html:select>标签和<html:optionCollection>来实现。为了实现国际化要求,label标签使用了JSP标准表情库中的<fmt:message>标签。很多人发现结合Struts和JSTL标签一切使用功能非常强大。
<table border="0" cellpadding="0" cellspacing="5">
<tr align="middle" valign="center">
<td>
<fmt:message key="titles.available"/><br />
<html:select property="availableValues"
multiple="true" size="7"
style="width:80px;" styleId="available"
onchange="doUpdate( false, true );">
<html:optionsCollection
property="availableList"/>
</html:select>
</td>
...
<td>
<fmt:message key="titles.chosen"/><br />
<html:select property="chosenValues"
multiple="true" size="7"
style="width:80px;" styleId="chosen"
onchange="doUpdate( true, false );">
<html:optionsCollection
property="chosenList"/>
</html:select> </td>
</tr>
</table>
Struts的表单Bean包含了用于JSP的“可见列表”,“可见值”,“可选列表”和“可选值”属性。
private List availableList = new ArrayList();
private String[] availableValues = new String[ 0 ];
private List chosenList = new ArrayList();
private String[] chosenValues = new String[ 0 ];
...
public List getAvailableList()
public void setAvailableList( List list )
public String[] getAvailableValues()
public void setAvailableValues( String[] list )
public List getChosenList()
public void setChosenList( List list )
public String[] getChosenValues()
public void setChosenValues( String[] list )
“availableList ”和“chosenList ”属性通过标签值Bean的方式封装了列表的可见值。“availableValues ”和“chosenValues ”以字符串数组的形式存储了可选的值。
对于增加-选择模式来说,不会关心这些可选值。对于服务器来说我们只要告诉它列表中显示什么值而无需告诉它那个值被选中了。然而当表单被提交后,只有被选中的值会被提交给服务器。这是通常使用的设计方法,但是这种设计模式存在问题。如图1.
下面有集中方法可以解决这个问题。一种方法是在每次列表内容发生变化的时候都向服务器提交一次值。者引发了过度的页面引用和网络负载。另一种方法是使用AJAX技术与服务器进行通信。这减少了页面的刷新,但是仍然会代理网络的负载。直到用户完成列表变更之前都不应该产生网络负载。
最好的解决方案是把选中的值存储在一个隐藏字段中。使用这种方法当表单提交时只向服务器提交一次值。在这个例子中我们把选中的值存储在一个隐藏字段中。利用这个隐藏字段,两个列表中见的按钮可以这样实现:

<td>
<br />
<input type="button" style="width:100px;" id="add" onclick="
doMove( 'available', 'chosen', false );" value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;" id="addAll" onclick=
"doMove( 'available', 'chosen', true );" value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;" id="remove" onclick=
"doMove( 'chosen', 'available', false );" value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;" id="removeAll" onclick=
"doMove( 'chosen', 'available', true );" value="<fmt:message key='removeAll'/>" />
<html:hidden styleId="chosenItem" property="chosenItem" />
</td>
这个按钮通过HTML标签<input>实现。<html:hidden>字段存储了选中的值。被选中的值通过JavaScript方法“doMove()”进行保存,所有4个按钮都执行这个方法。

/**
* Move selected items between lists.
* <p>
* @param sourceId ID of source list
* @param destId ID of destination list
* @param all true iff moving all
*/
function doMove( sourceId, destId, all )
{
// Move the items between the lists.
var sourceElem = document.getElementById( sourceId );
var destElem = document.getElementById( destId );
for( var i = 0; ( i < sourceElem.length ); )
{ if( sourceElem.options[ i ].selected || all )
{ var newOption = document.createElement( "OPTION" );
newOption.text = sourceElem.options[ i ].text;
newOption.value = sourceElem.options[ i ].value;
destElem.options[ destElem.length ] = newOption;
sourceElem.remove( i );
}
else
i++;
}
// Update the button states.
doUpdate( false, false );
// Store the chosen items in a hidden field.
var chosenItem = document.getElementById( "chosenItem" );
var chosenList = document.getElementById( "chosen" );
chosenItem.value = "";
for( var i = 0; ( i < chosenList.length ); i++ )
{ chosenItem.value += chosenList.options[ i ].value + '|';
}
}
上面的方法非常易于理解,使用起来也很方便。按钮只要在需要的时候才会显示。比如如果没有列表值被选中,那么“Add”和“Remove”按钮就无法使用。当显示列表和选择列表都是空的时候,那么“Add All”和“Remove All”按钮就无法使用。
“doUPdate()”函数根据用户的选择和列表中包含的内容判断按钮是否可以显示。如图2
“doUpdate()”方法由上面由列表控件的OnChange方法触发的“doMove()”方法调用。或者在body的onload方法中调用。

/**
* Update the button states based on whether
* lists have contents and selected items.
* Deselect list items if requested to ensure
* at most one list contains selections.
* <p>
* @param offAvailable deselect available list
* @param offChosen deselect chosen list
*/
function doUpdate( offAvailable, offChosen )
{
// Get the lists and deselect if requested.
var availableList = document.getElementById( "available" );
var chosenList = document.getElementById( "chosen" );
if( offAvailable ) availableList.selectedIndex = -1;
if( offChosen ) chosenList.selectedIndex = -1;
// Update the button states.
document.getElementById( "addAll" ).disabled =
( availableList.length == 0 );
document.getElementById( "removeAll" ).disabled =
( chosenList.length == 0 );
document.getElementById( "add" ).disabled =
( availableList.selectedIndex < 0 );
document.getElementById( "remove" ).disabled =
( chosenList.selectedIndex < 0 );
}
使用JavaScript函数,列表中包含的内容被正确的更新,用户选择的值被保存在一个隐藏字段中。在这个例子中它被用于控制两个列表中值和用户的选择。
列表中的值被存储在表单Bean中的一个“languageList”属性里。这个列表中包含了所有可能的选项。move方法利用“chosenItem”列表中的值控制了“可见”和“可选”列表中的值。

private List languageList = new ArrayList();
private String chosenItem = "";
...
public List getLanguageList()
public void setLanguageList( List list )
public String getChosenItem()
public void setChosenItem( String items )
public void move( List sourceList, String[] sourceValues, List destList )
...
"languageList" ,"chosenItem" 属性和 "move"方法通过表单提交的Action中的“execute”方法执行。在Struts中是StrutsAction类的实例。

/**
* Populate the lists from the hidden field.
* <p>
* @param mapping action mapping
* @param form action form
* @param request HTTP servlet request
* @param response HTTP servlet reponse
* @throws Exception
*/
public ActionForward execute( ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response ) throws Exception
{
// Populate available list from language list.
ExampleForm eForm = ( ExampleForm )form;
eForm.getAvailableList().clear();
eForm.getAvailableList().addAll( eForm.getLanguageList());
// Populate chosen list from hidden field.
eForm.getChosenList().clear();
eForm.move( eForm.getAvailableList(),
eForm.getChosenItem().split( "\\|" ),
eForm.getChosenList());
}
JavaServerFace实现
那么JavaServerFace中是如何实现上面的模式呢?JSF由一群曾经设计过Struts的人设计,所以我们可以较容易的从Struts过度到JSF。
JSF中的JSP也包含了两个列表,因为JSF的标签更加紧密。代替<table>,<tr>和<td>标签,JSF使用了<h:panelGrid>标签。代替Struts中的<fmt:message>标签JSF使用了<h:outputText>。代替<html:select>和<html:optionsCollection>标签,JSF使用了<h:selectManyListBox>和<f:selectItems>。

<h:panelGrid columns="3" rowClasses="center">
<h:outputText value="#{bundle.available}" />
<h:outputText value="" />
<h:outputText value="#{bundle.chosen}" />
<h:selectManyListbox style="width:100px; height:120px;"
id="available" value="#{example.availableValues}"
onchange="doUpdate( false, true );">
<f:selectItems value="#{example.availableList}"/>
</h:selectManyListbox>
...
<h:selectManyListbox style="width:100px; height:120px;"
id="chosen" value="#{example.chosenValues}"
onchange="doUpdate( true, false );">
<f:selectItems value="#{example.chosenList}"/>
</h:selectManyListbox>
</h:panelGrid>
就像Struts中实现的那样,<h:selectManyListBox>标签映射了支持Bean中的“availableList”“availableValues”和“chosenList”以及“chosenValues”属性,他们等同于Struts中的相应属性。
就像Struts实现中的一样,四个按钮也由标准HTML的<input>标签实现。
在JSF中这些标签还必须附上<f:verbatim>标签。除此之外这些标签和Struts中的没什么不同。这里有一点与Struts类似的就是在每个JavaScript方法中经常需要包含一个“form:”前缀。

<h:panelGrid columns="1">
<f:verbatim>
<input type="button" style="width:100px;"
id="add" onclick="doMove( 'form:available', 'form:chosen', false );"
value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;"
id="addAll" onclick="doMove( 'form:available', 'form:chosen', true );"
value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;"
id="remove" onclick="doMove( 'form:chosen', 'form:available', false );"
value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;"
id="removeAll" onclick="doMove( 'form:chosen', 'form:available', true );"
value="<fmt:message key='removeAll'/>" />
</f:verbatim>
<h:inputHidden id="chosenItem" value="#{example.chosenItem}" />
</h:panelGrid>
除此之外,JSF中的doMove方法和doUpdate方法没什么不同。
JSF的优势是它控制了提交事件。在JSF中你可以明确的定义一个按钮具体执行那个请求。
<h:commandButton action="#{example.submit}" value="#{bundle.submit}" />
“example:submit”方法会读取隐藏字段中内容并更新列表。因为这个方法直接关联在一个提交按钮上,所以你不必单独实现一个Action对象。
public String submit()
{
// Populate the available list from the language list.
availableList.clear();
availableList.addAll( languageList );
// Populate the chosen list from the hidden field.
chosenList.clear();
move( availableList, chosenItem.split( "\\|" ), chosenList );
...
return( "success" );
}
JSF中不需在学什么,虽然标签不同,但是通常和JSP中的类似。Java代码也差不多。
需要强调的是JSF中的组件架构。如果你下载了免费的SUN Java Studio Creator,你会发现它包含了一整套可以在你的GUI中任意增加-删除的组件。SUN的组件包含很多功能,比如“Move Up”和“Move Down ”按钮可以改变列表中值的顺序。我们还可以列举很多这样的有用组件。
结论

这篇文章展示了一个从两个列表中取值的标准UI设计模式。通过Jakarta Struts和JavaServer Face两种方式实现。
JSF提供了一种从Struts平滑过度的方法。JSP的标签非常简单那;支持Bean的代码也和Struts类似;并且,如果你需要使用JavaScript在JSF中的用法也是一样的。
你可以把JSF当成是一个简单的,组件化的Struts。它增加了很多有用的特性,并且减少了复杂性。
对于大多数刚接触Web项目的开发人员,JavaServer Faces是一个很好的选择。对于熟悉Struts的开发人员,过度到JSF也很容易。

No comments: