JSF理解组件和客户端标识符
理解组件和客户端标识符
我们接触了客户端标识符的概念,现在来看看它与在JSP中分配给组件的标识符有何不同。
我们说过,UI 组件跨越两个世界:在服务器端,它被表示为组件树中的一个对象;在客户端,它可以有多种表现形式。服务器是由一个Java虚拟机以及servlet、 JSF、应用代码和其他支持库组成。客户端则通常是能够显示诸如HTML之类的标记的浏览器。浏览器则是属于客户端脚本语言(如JavaScript或者 VBScript)、层叠样式表(CSS)之类的样式机制以及像锚和超链接之类的导航方案的世界。
在两个世界中都需要有找到特定组件的特定的方式。在服务器端,每个组件都可以通过组件标识符找到。如果你为某个组件指定了标识符,便可以在 Java 代码中访问该组件。在客户端,每个组件都可以通过其客户端标识符找到,而该标识符则衍生自组件标识符。客户端标识符是允许你通过JavaScript、 CSS和其他类似技术来操作组件的表现。
客户端标识符也在服务器和客户端之间架了一座桥。当用户提交表单时,客户端标识符也随表示用户对该组件所执行的动作的数据一起发送给服务器,然后被用来将用户数据映射到服务器上的组件实例,以便修改其值,产生事件,以及进行其他一些操作。
这似乎有些含混,所以我们来看一个例子。展示了运行于服务器端的组件、它们在客户端的表示以及使用标识符的技术种类之间的关系。下面的 JSP代码定义了图中所示的组件——一个 HtmlOutputText组件和一个具有两个HtmlInputText子组件的HtmlForm组件:
服务器中的组件实例是通过组件标识符来引用的。而在客户端它们则是通过客户端标识符来引用的。有时候两者相同
首先,需要指出的是,这里的id属性是组件标识符。另外,你将注意到第一个<h:inputText>元素并没有特定的标识符。这是因为标识符是可选的——如果没有指定一个,JSF将生成一个。
组件标识符必须由字母或下划线开始,并且由字母、数字、连字符和下划线组成。它们也应该简短一些,以便使返回给客户端的响应最小化。
现在,来看看对应的HTML输出:
HTML <span>元素是由HtmlOutputText组件产生的。其客户端标识符为outputText,与我们在JSP为其定义的组件标识符一样。<form> 元素是HtmlForm 组件的输出,其客户端标识符也与组件标识符相同。但是其所有子组件的客户端标识符却要以HtmlForm 组件的客户端标识符myForm开始。因为我们没有为第二个HtmlInputText组件指定组件标识符,JSF自动为其分配了一个标识符_id0(输入控件通常将id和name属性一起输出为客户端标识符)。
HtmlForm 组件的子组件的客户端标识符以HtmlForm的客户端标识符开始,因为HtmlForm组件是个命名容器。命名容器是下一节的内容。
注解 组件标识符通常是可选的,但是如果需要用一个组件引用另一个组件,或者通过已知的标识符在客户端或服务器端引用组件时,则是必要的。如果没有为其指定,JSF 将在服务器中自动产生一个,但是取决于具体的组件,你可能不能在呈现好的标记中看到客户端标识符。而如果指定了一个组件标志符,则尽可能地保持其简短,以便减小JSF响应的大小。
命名容器
命名容器是个组件,它的所有儿子都有唯一的组件标识符。所以不能在一个命名容器中存在两个具有相同组件标识符的组件。视图的根节点(UIViewRoot),即某个给定页面中所有组件的父亲,不是命名容器,但是的确需要使同一个视图中的顶层组件具有不同的标识符。
HtmlForm就是命名容器,所以位于同一个 HtmlForm 中的组件不能有相同的组件标识符。这是有意义的,因为如果具有两个名称都为foo的组件并且类型也相同,你将不能区分它们。另一个标准的命名容器是 HtmlDataTable。某些第三方组件(或者你自己编写的组件),也可以是命名容器。
如果在控件层次体系中有不只一个命名容器,客户端标识符可以不同于组件标识符。这是因为客户端标识符对整个页面必须保证唯一,而不管其中有多少命名组件。它们必须是唯一的,因为客户并不知道命名容器——它仅仅提交回表单数据给JSF 应用。应用必须能将数据映射到具体的组件,即必须能够区分针对某个组件的数据。
为了演示,来看看同一个视图中的两个HtmlForm组件。它们都包含一个具有相同组件标识符的HtmlInputText子组件:
这两个声明,除了HtmlForm 组件的标识符不同外,其余都是一样的。下面是与其对应的HTML输出:
UIViewRoot不产生任何输出;它仅是标明组件树的开始。然而重要的是,即便两个HtmlTextInput组件具有相同的组件标识符 inputText,但其客户端标识符却是不同的。因为第一个表单的客户端标识符是firstForm,其子控件的客户端标识符则为 firstForm:inputText。而后一个表单的客户端标识符是 secondForm,其子组件的客户端标识符为secondForm:inputText。如你所见,客户端标识符等于命名容器的客户端标识符加上冒号再加上其自身的组件标识符。
因为客户端标识符的默认分隔符为":",这在CSS样式,试图使用客户端标识符应用样式到某个组件时,可能会出现问题。解决方法是对组件应用CSS 类样式(基于此目的,所有的标准HTML 组件都有一个styleClass属性)。
你可能想知道命名容器将如何影响JSF开发人员的日常开发工作。知道哪个组件是命名容器,将有助于你了解一个组件的客户端标识符。并且,如果你认识客户端标识符,便可以在客户端引用它,并且在服务器中为其解码,这些都可以在后面的内容中看到。
引用标识符
好了,现在已经知道UI组件在客户端和服务器端都有标识符,并且知道如果涉及命名容器这些标识符可能不同。现在我们来看看如何使用这些标识符在客户端和服务器引用组件。
1.客户端引用
如前所述,客户端技术可以通过客户端标识符引用组件。请看下面的JSP 代码:
这里有个HtmlForm 组件,而它又分别有两个HtmlOutputLabel 和 HtmlInputText 子组件。HtmlOutputLabel组件有一个HtmlOutput子组件。这样产生了下面的HTML代码:
label元素的onmouseout 和 onmouseover属性中的JavaScript代码,通过input字段的name 属性(clientForm:myTextBox)引用了它,该名称也是HtmlInputText 组件的客户端标识符。用户的鼠标移到label上,文本框的值将变为"84"。用户移开鼠标,它将变为空。这并不是什么很有用的功能,但从这知道,你可以在JavaScript 中通过其客户端标识符访问文本字段。
针对HTML 浏览器,组件的客户端标识符映射至对应的HTML元素的name或者id属性。这意味着也可以在CSS中使用它,或者将其作为锚引用等(关于HTML 的快速参考,请访问W3School的站点[W3Schools])。
然而,请记住JSF 并不限于是HTML应用,可以是不同类型的浏览器、桌面客户、applet或者干脆完全不同的其他东西。不管使用何种技术,运行于客户端的代码都必须考虑客户端标识符,特别是在和服务器通信时。
2.服务器端引用
当你在服务器上和JSF组件交互时,编写的代码通常位于后台bean的事件监听器方法或者事件监听器类中。然而,它可以位于任何位置,只要具有对组件实例的引用。这是因为基本组件类—— UIComponent——具有一个快捷的findComponent方法。该方法使用类似于客户端标识符的特殊表达式来查找组件。
例如,假定在JSP中定义了如下的表单:
这样就定义了一个messageForm的 HtmlForm组件,以及一个outputMessage的HtmlOutputText子组件以及没有指定标识符的 HtmlCommandButton子组件。HtmlCommandButton 引用了testForm.sendMessage 事件监听器方法,该方法是:
这里,我们首先获取视图的根组件,然后使用 HtmlOutputText 组件的客户端标识符调用findComponent方法。接下来修改了它的颜色(使用CSS样式)和值。用户点击按钮时,此方法被调用,并且在页面重新显示时,HtmlOutputText控件将以蓝色显示“Who’s the Mann?”(后台bean也可以直接引用组件,特别是由IDE产生时