最近在做项目的时候,碰到了一个JSF的问题,就是输入组件设置了readonly属性之后,提交的时候就得不到该组件的值了,仔细研究了一番,才找到原因和解决方法。
具体的现象是这样的,一个输入框需要设置为不能让用户更改,但可以通过javascript改变,改变后提交,则在服务器端得到改变后的值进行处理。这种需求应该也是非常常见的,可是在用JSF实现时,却碰到了问题。具体JSF代码如下:
<h:form id="theForm">
<h:inputText id="theValue" readonly="true" value="#{readonlyBean.theValue}"/>
<h:commandButton value="Submit" action="#{readonlyBean.submit}"/>
<af:outputText value="Click" οnclick="editReadonly();"/>
</h:form>
第一个<h:inputText>就是我们要研究的对象,第二个<h:commandButton>是提交的按钮,第三个<af:outputText>是为了有一个地方可以点击,点击后调用javascript来改变第一个组件的值,至于af是来自于ADF,ADF是Oracle的JSF实现,提供了更多的功能,为什么不用<h:outputText>呢?因为很奇怪,标准的JSF组件<h:outputText>竟然不接受onclick事件。总之,调用的editReadonly()的javascript代码如下
function editReadonly() {
document.getElementById('theForm:theValue').value = "ABCD";
}
在服务器端,Managed Bean中的代码如下:
public class ReadonlyBean {
private String theValue = "XYZ";
public void setTheValue(String aValue) {
this.theValue = aValue;
}
public String getTheValue() {
return theValue;
}
public String submit() {
System.out.println( this.getTheValue() );
return null;
}
}
以上代码就是开始给theValue以初始值XYZ,然后在提交时打印出theValue的值。
运行时,首先点击"Click",看到页面上的只读输入组件的值变成了ABCD,然后提交,发现服务器端打印出来的值仍然是XYZ,没有得到该输入框的新值。
然而如果直接用HTML来完成这一功能时,页面上有一个readonly的输入框,提交到一个servlet,在servlet中从request中得到该输入框的值打印出来,那么在用javascript改变了该输入框的值后,打印出来的值是变化之后的值,说明标准的HTML的readonly的值是会提交的。
怎么会这样呢?JSF的输入组件设为readonly之后似乎不提交自己的值?为了解决这一问题,首先查看JSF最后生成的HTML页面的源代码,似乎没有任何问题,也是解析成一个标准的HTML的<input>,那说明在页面这个值是确实存在,也被提交给JSF框架,只是在后台处理时JSF忽略了该组件,这非常奇怪,为什么要采取这个和标准HTML不一致的行为?是不是当前使用的JSF的实现的问题?在换了好几个JSF的实现之后(Oracle的实现,MyFaces的实现,Sun的RI实现),结果都是一样,看来只有看看实现的源代码才能找到原因了。
下载了MyFaces 1.2的源代码,然后首先找到HtmlInputText的renderer:HtmlTextRenderer,因为提交时首先调用组件的renderer的docode方法来解析request的参数。然后发现该类是扩展了HtmlTextRendererBase,之后发现是调用了HtmlRendererUtils.decodeUIInput()来解析的,在该方法的一开始,就发现如下语句:
if(isDisabledOrReadOnly(component))
return;
这里充分说明了在JSF中readonly和disable一样,都是不会将参数提交给后台的。
原因找到了,那为什么JSF会采用这种方式。解释可能要回到JSF是一个组件的框架这一点上来。因为<h:inputText>是一个组件,那一个只读的组件是不是就应该不能改变其值呢?
最后则说一下怎么绕过这一限制,从而实现我们一开始想要的功能。实现的方法则只能是加一个隐藏的输入组件,在javascript改变只读输入框时,也同时改变该隐藏的输入组件的值,那么提交后就能得到这个隐藏输入组件的值。