使用Xkins为Web应用增加皮肤
——为你的Web应用增加换肤能力
原文出处:http://www.javaworld.com/javaworld/jw-10-2004/jw-1025-xkins.html
注:图片请参看原文。
摘要
在这篇文章中,Guillermo Meyer说明了为Web应用换肤的过程,解释了如何一种管理皮肤的框架——Xkins来为你的应用换肤。Xkins可以和其他的UI框架,如Struts和Titles等,一起使用。这里Meyer将和你一起时实现一个需要两个皮肤的例子,同时介绍如何给它添加唯一的皮肤。
皮肤指用户界面的外观,它为Web应用带来不同的视觉感受。当用户单击按钮以后,皮肤改变了用户界面,但是并不改变UI的动作。皮肤的更换导致应用外观的改变,但是为了达到这种改变,Web应用必须懂得如何使用皮肤。
为什么你需要首先为Web应用增加皮肤呢?关于使用皮肤,可能有很多目的,但是这些目的都不是必须的。在一个简单的应用里面,增加皮肤会不合算,但是在一些情况下,你必须用皮肤来处理。这些情况是:
l 当皮肤是系统需要:当用户可以选择个性化的皮肤,甚至创建个性化的皮肤时。
l 当你想给企业组件框架增加皮肤能力:如果你为不同的客户端创建了不同的解决方案,你可以重用所有的组件(标签库)。如果你的组件用皮肤能力,只需要简单改变每个客户端的皮肤而已。
l 当依照一个特定的商业情形需要增加一个不同的皮肤:例如,在一个市场或跨银行的应用中,不同的人群在同一个系统中工作,你需要依据用户群体的图片来修饰你的应用。
为Web应用增加皮肤不是一个容易的任务。你可以使用层叠样式表来改变图片的路径,但是你会被CSS的能力所限制。如果你有一个在每个皮肤中看起来根本不同的组件,也就是说,在每个皮肤中HTML不同,CSS将无法帮你。然而,如果就是简单改变格式就可以解决你的问题,你就可以使用CSS。
一个创建皮肤的好办法就是,限定用户界面的每个部分,然后将每部分结合起来组成完整的界面。例如,如果在皮肤A中你有一个简单表格构成的界面组件,在皮肤B中是一个具有页眉、页脚、图片甚至声音的复杂表格,为每个皮肤结构将生成不同的HTML(很多<tr>和<td>标签)。作为一个例子,让我们假如在皮肤A中,必须产生一个代表标签的HTML:
<p>This is my Label</p>
在皮肤B中,标签将这样生成:
<table background="/images/tablebg.gif">
<tr>
<td bgcolor="#0000FF">
</td>
<td background="/images/cellbg.gif">
This is my Label
</td>
<td bgcolor="#0000FF">
</td>
</tr>
</table>
这样你可以发现,这两个UI在每个皮肤中完全不同。它们具有相同的信息(This is my Label),但是是用不同的HTML标记表达的。这个功能CSS不能单独完成。可能使用XML转换或者XSL可以完成。或者你可以使用Xkins。
l 什么是Xkins
Xkins是一种为Web应用管理皮肤的框架。在早期的服务器端Java编程中,你需要在servlet中手动输入HTML。接着,JSP(Java Server Pages)的出现,你可以在Java代码以外书写HTML。现在,我们在Java代码中使用具有HTML标签的标签库时,遇到了同样的问题。使用Xkins,你可以把使用一个新增的而且是非常有用的特性——皮肤,把HTML书写在代码的外边。更多的细节,可以查看Xkins的主页。
图1 Xkins在Web应用中的位置
通过标签使用Xkins和Struts的Web应用,遵循如下的生命周期:
n Struts使用Xkins插件初始化Xkins
n Struts控制器接收HTTP请求
n Struts执行方法,定向到JSP页面显示
n JSP页面使用标签库显示页面
n 标签库通过Xkins外壳——XkinProcessor使用Xkins
n XkinProcessor获得用户皮肤和标签指令代表的模板
n XkinProcessor使用TemplateProcessor和模板关联
n TemplateProcessor显示组成皮肤的UI各部分的类,TemplateProcessor可以使用Velocity、JBYTE (Java By Template Engine)、Groovy或者其他模板引擎来显示输出。
n TemplateProcessor使用皮肤中的资源(元素和路径),返回模板处理好的标签。
n 模板处理好的标签传输给浏览器进行显示
Xkins地址皮肤依据以下的基础原则进行管理:
n 在Java代码以外产生HTML:标签就是产生HTML代码,改变这些代码需要改变Java代码和重新部署应用。Xkins允许你以将产生的HTML放置在自定义的文件(XML文件)中。另外,Xkins允许你将HTML格式标签放置在JSP页面以外,以便以后扩展应用的视觉感受。
n 定义皮肤结构:模板、资源和路径组成一个皮肤。资源可以是一个常数,也可以是像图片和CSS文件之类的元素。定义路径可以帮你组织你的皮肤文件。定义模板可以重用应用中的UI部分。
n 允许扩展Xkins框架:你可以根据自己的需要,使用自己的模板语言来扩展Xkins。例如,如果你需要产生图片,你可以实现一个产生图片的模板处理器。Xkins的模板处理是基于Velocity和JBYTE的。例如,如果你倾向于使用Groovy,你可以创建一个Groovy模板处理器来处理你的UI部分。
n 将UI划分成基本元素:在Xkins中,你可以分离所有的UI组件,创建一个模板。通过这种方式,你可以重用各个部分和改变其中的任何部分使得皮肤显得不同。
n 使用继承减少皮肤的维护:在Xkins中,一个皮肤可以继承其它的皮肤,使用继承皮肤中的所有模板、路径和资源。这样,就可以减少模板的维护。
n 使用合成来创造皮肤:作为继承的延伸,Xkins允许用户合成,以减少维护和增加模板的重用。由于这些特性,用户可以通过从已有的皮肤中选择不同的UI部分来创建自己个性化的皮肤。
n 定义皮肤类型:使用皮肤类型,你可以确定在Xkins实例中的所有皮肤至少有一个和这个类型具有相同的模板。皮肤类型是在一个Xkins实例中所有其它皮肤都唯一的皮肤。在此,皮肤实例是指在Web应用中被一起导入的一组皮肤。
Xkins提供的最大好处就是,所有的HTML都存在一个地方,如果你需要改变的话,只需要简单的修改模板即可。比如,如果你的页面文件过大,发现生成的过多HTML的位置或者那些图片可以被分离,接着改变模板来较少页面文件的大小。你也可以为那些网络速度慢的用户提供一个轻量级的皮肤,为宽带用户提供重量级的。
注意:你可以将Xkins和CSS一起使用。事实上,CSS是因为字体类型和颜色才被推荐使用的,因为重用CSS文件可以避免每次明确的指出字体的外观,从而降低了文件的大小。
在Web应用中,皮肤可以压缩成一个简单的文件(zip文件)以便容易部署。如果你定义了皮肤类型,如果第三方的皮肤符合你声明的皮肤类型,也可以添加到你的Web应用中。
你可以以多种方式来使用Xkins,但是将Xkins和标签库一起使用是Web应用中最好的方式。你可以使用这些标记来生成你的页面,或者装饰已有的标记。
l 定义皮肤
这里有一些定义皮肤的提示:
n 决定皮肤颜色;使用全局常量,以便其它皮肤可以继承和覆盖它们。
n 为每个标签库创建可重用的模板。
n 使用可以被继承的皮肤覆盖的元素来创建模板,这样整个模板不需要为了改变UI外观而重写。
n 为你的Web应用创建一个基础的皮肤,使用它作为你的Xkins实例的类型。
n 避免将HTML写在Java代码内部。如果你有一个包含HTML代码的标签库、servlet、甚至一个JSP页面,可以考虑将HTML移植到Xkins模板里面。
l 示例
现在我们来看一下,在一个需要皮肤管理的简单Web应用中,定义、设计、开发和部署Xkins的操作。在这个例子中,我们实现一个为两个在线书店——Amazing和Barnie & Nibble实现注册用户的简单应用。这个应用将在两个站点中使用(通过框架、portlet或者书店选择的其它格式),但是必须为每个书店提供特定的外观。
为了实现我们的应用,我们遵循如下步骤:
1、 获得包含每个皮肤的HTML页面。
2、 确定皮肤模板。
3、 创建皮肤。
4、 使用皮肤。
5、 部署Web应用。
l 获得包含每个皮肤的HTML页面
首先,我们获得每个书店提供的图形页面设计。这些素材可能是页面原型,必须包含应用中所有可能的页面元素(在我们的例子中,只是一个页面)。如图2和图3。
图2 Amazing的外观
图3 Barnie & Nibble的外观
和我们看到的一样,两个页面具有不同的颜色、图片和布局。另外,信息收集器不同,加上Amazing的按钮是GIF格式,而Barnie&Nibble的是一个带格式的HTML按钮。
l 确定皮肤模板
现在我们必须整理页面部分来为我们的应用生成一些模板。我们可以从零开始,或者我们可以在一个用来创建表单的简单皮肤的基础上来分解我们的HTML。基础皮肤以Xkins表单标签的形式存在于Xkins框架中。Xkins表单是一个使用Xkins为Web应用产生表单的标签库实现。
基础皮肤定义了框架(frame)、字段、按钮等等。我们应该使用这个皮肤,同时添加我们项目需要的模板(例如频道)。这个基础皮肤也允许我们使用Xkins表单标签产生我们的JSP页面。
下面让我们看一下我们需要的模板列表:
n frame:包含整个表单的表格
n frameMandatoryCaption:标记强制字段的文字
n field:标签和输入框的布局
n fieldLabel:包含一个标签的一段文字
n fieldLabelMandatory:标记一个命令标签的一段文字
n fieldInput:控制输入框
n fieldInputMandatory:标记必须填写的文本框
n button:执行命令的命令按钮
n
branding:对应每个书店的频道
l 创建皮肤
一旦我们UI的不同部分被决定,我们就可以使用Xkins创建两个皮肤。我们从在xkins-definition.xml文件中命名它们开始:
<?xml version="1.0" encoding="UTF-8"?>
<xkins>
<skin name="base" url="/skins/forms/base" definition="/definition.xml"/>
<skin name="amazing" url="/skins/forms/amazing" definition="/definition.xml"/>
<skin name="bn" url="/skins/forms/bn" definition="/definition.xml"/>
</xkins>
现在,我们必须根据图4所示在Web应用根目录下创建一个目录结构:
图4 皮肤目录
在每个子目录,我们都要放置definition.xml文件来描述皮肤。我们来一起看看一些皮肤模板。如果需要查看所有的例子模板,可以从文件面末尾提供的链接中下载源代码。
让我们来看看在包含Amazing的皮肤的definition.xml中皮肤的定义语法:
<skin name="amazing" extends="base">
</skin>
base是默认的皮肤,它和Xkins表单一起帮助我们给应用添加皮肤。Amazing的皮肤继承了它(Barnie&Nibble的也是)。我们现在开始为每个皮肤符覆盖base皮肤的模板,从覆盖field模板开始:
<skin name="amazing" extends="base">
<template name="field" group="field">
<content><![CDATA[ $label $input ]]></content>
</template>
<template name="fieldLabel" group="field">
<content><![CDATA[
<td align=right
style="font-family: verdana,arial,helvetica,sans-serif; font-size: x-small;"><b>$label:</b></td>
]]></content>
</template>
<template name="fieldLabelMandatory" group="field">
<content><![CDATA[
<td align=right
style="font-family: verdana,arial,helvetica,sans-serif; font-size: x-small;"><b>$label:</b></td>
]]></content>
</template>
<template name="fieldInput" group="field">
<content><![CDATA[
<td colspan="$colspan"
style="font-family: verdana,arial,helvetica,sans-serif; font-size: x-small;">$input (Optional)</td>
]]></content>
</template>
<template name="fieldInputMandatory" group="field">
<content><![CDATA[
<td colspan="$colspan"><strong>$input</strong></td>
]]></content>
</template>
</skin>
上面所有的模板都是Velocity模板。注意参数,象$colspan,是传递给模板的,可以被模板使用。这些参数在被标签库调用的XkinsProcessor传递。
下面,我们开始从模仿的页面中把HTML剪切和粘贴到Xkins模板中。接着,我们使用相同的方法处理框架(frame)、按钮和频道。下面的代码显示了Barnie&Nibble的一块皮肤(只是field模版):
<skin name="bn" extends="base">
<path name="images" url="/images" />
<element name="spacer" path="images" url="/cleardot.gif"/>
<!-- field templates -->
<template name="field" group="field">
<content><![CDATA[
<td width="100%">
<table border=0 cellpadding=0 cellspacing=0 width="100%">
<tr>
$label
</tr>
<tr>
$input
</tr>
</table>
]]></content>
</template>
<template name="fieldLabel" group="field">
<content><![CDATA[
<td WIDTH="25%"><font size="-1"
face="arial, helvetica, sans-serif">$label</font></td>
]]></content>
</template>
<template name="fieldLabelMandatory" group="field">
<content><![CDATA[
<td WIDTH="25%"><font size="-1" face="arial, helvetica, sans-serif">$label</font></td>
]]></content>
</template>
<template name="fieldInput" group="field">
<content><![CDATA[
<td colspan="$colspan" style="font-family: verdana,arial,helvetica,sans-serif; font-size: x-small;">
$input <br><b>(Optional)
</b></td>
]]></content>
</template>
<template name="fieldInputMandatory" group="field">
<content><![CDATA[
<td WIDTH="25%">$input <img src="$res_mandatory" border="0"/></td>
]]></content>
<element name="mandatory" path="images" url="/mandatory.gif"/>
</template>
<template name="nestedField" group="field">
<!--
jsp:bodyContent
-->
<content><![CDATA[
<td colspan="$res_fieldColspan" style="font-family: verdana,arial,helvetica,sans-serif; font-size: x-small;">
$bodyContent
</td>
]]></content>
</template>
<!-- end field templates -->
<!-- The rest of the templates go here -->
</skin>
l 使用皮肤
现在我们具有了一个base皮肤和两个需要的皮肤,我们可以使用这些皮肤来创建JSP页面。为了完成这个工作,我们使用Xkins表单标签,因为他们使用了在base皮肤中定义了的模板。你可以象Xkins表单一样使用Xkins创建自己的标签库,这样你就不再需要使用Xkins表单了。但是Xkins表单和Struts框架兼容的很好,我们这个应用中就使用它。
我们需要两个页面:
n index.jsp:执行数据登录
n done.jsp:打印结果
我们的例子只是一个演示,所以不必真实的处理提交的请求,只需要从index.jsp重定向到done.jsp即可。在实际的应用中,在这些页面间的这个过程必须被完成。
注意在我们的例子应用中Xkins是如何和Struts集成的。Xkins标签库并不替代Struts标签库,他们只是修饰页面的。例如,你不能使用<table>这个HTML标签,而是<forms:frame key="frame.title" width="30%">来容纳表单和添加皮肤能力。如果你的应用使用Struts,而且你想使用Xkins,只需要将在你的页面中放置Xkins表单标签库来修饰。接着,把所有在JSP页面中使用到的HTML标签传递给Xkins的模板,让Xkins产生视觉外观。
虽然在例子中没有用到,但是Title可以和Xkins一起使用。Xkins可以和Titles没有交替的一起工作:让Titles管理页面布局,Xkins管理组件。
下面的代码显示了实现例子中表单的JSP页面,使用Xkins表单和Struts标签库:
<html:form action="/subscribe" focus="lastName">
<xkin:template name="branding"/>
<forms:frame key="frame.title" width="30%">
<forms:row>
<forms:field key="subscription.name" mandatory="true" >
<html:text property="name"/>
</forms:field>
</forms:row>
<forms:row>
<forms:field key="subscription.email" mandatory="true" >
<html:text property="email"/>
</forms:field>
</forms:row>
<forms:row>
<forms:field key="subscription.document" mandatory="true" >
<html:text property="document"/>
</forms:field>
</forms:row>
<forms:row>
<forms:field key="subscription.birthday">
<html:select property="month">
<option value="month">Month</option>
</html:select>
<html:select property="day">
<option value="day">Day</option>
</html:select>
</forms:field>
</forms:row>
<forms:buttons>
<forms:button key="button.continue" default="true" onclick="document.subscribeForm.submit();"/>
</forms:buttons>
</forms:frame>
</html:form>
在我们的例子中,我们使用了Struts,已解决了国际化问题,来作为UI框架。我们使用<xkins:template/>标签来在页面的上部实现branding模板,使用<forms:*/>来实现表单。我们有一个包含多行的frame。每行具有一个或者多个field。当框架被实现,它要求每行都实现自身。同时每行都要求字段实现自身。在每个例子中,所有这些表齐都使用在皮肤中定义的模板,所以只需要改变Xkins定义文件就可以改变页面的视觉外观,而不需要改变JSP。
buttons标签包含了页面按钮,这些按钮通过参数传递给frame模板,所以你可以把这些表单按钮放置在模板指定的位置。这里,我们使用button标签来实现皮肤要求的按钮。在Amazing皮肤中,我们创建一个具有图片的按钮(在表格中),在Barnie&Nibble皮肤中,我们使用HTML按钮即可。注意JSP也没有包含HTML和CSS类:HTML实现和格式都委派给了Xkins。
l 部署Web应用
现在所有的部分都被设置好了,你只用把war文件部署到servlet容器中,使用示例应用启动即可。在例子中,我们将Xkins和Struts集成起来。你可以这样在struts-config.xml中配置XkinsPlugin:
<struts-config>
<!-- Struts specific configuration goes here -->
<plug-in className="ar.com.koalas.xkins.struts.XkinsPlugin">
<set-property property="config" value="/xkins-forms-definition.xml"/>
<set-property property="autoReload" value="2000"/>
<set-property property="skinType" value="base"/>
</plug-in>
</struts-config>
l 商业在扩展!
在成功实现我们我应用以后,一个新的客户端,Box书店接受我们的服务。公司给了我们它的应用的视觉外观,我们开发皮肤,如图5所示:
图5 Box书店皮肤
这个皮肤和其他书店的皮肤不同,显示了Xkins框架在开发皮肤感知用户界面时的灵活。你可以在源代码中获得该皮肤。
l 其它皮肤用法
Xkins不只被用在Web应用中。Xkins结构允许你:
n 在每个批附中创建浮动的图片。你可以创建GIF或者Vector标记语言,如果浏览器支持,根据浏览器决定皮肤。
n 根据客户端需要创建不同的输出。你可以具有一个创建HTML的皮肤,另一个创建WML,另外一个创建XML,根据客户端设备类型来决定皮肤。
n 根据用户参数创建报表。一个皮肤创建PDF的,一个创建CSV的,另外一个创建HTML的。
n 根据浏览器类型创建不同的HTML。你可以创建一个针对IE的皮肤,一个针对Netscape的,另外一个针对Linux。
总之,你可以在你的应用为针对所有模板的单一点使用Xkins。
甚至即使你不需要使用Xkins从模板中创建一段HTML的能力,只需要使用CSS和图片,你可以简单地使用Xkins来组织文件。例如,你可以声明图片路径,CSS文件名等,如下面的Xkins定义:
<xkins>
<skin name="organizer" url="/images">
<processor type="ar.com.koalas.xkins.processor.VelocityTemplateProcessor"/>
<path name="images-bg" url="/backgrounds"/>
<path name="images-icons" url="/icons"/>
<path name="css" url="/css"/>
<element name="logoffIcon" path="images-icons" url="/logoff.gif"/>
<template name="stylesheet">
<content><![CDATA[
<link href="$res_stylesheet" type=text/css rel=stylesheet/>
]]></content>
<element name="stylesheet" path="css" url="/formats.css"/>
</template>
</skin>
</xkins>
在JSP页面中,你可以这样来使用这些定义:
<%@ taglib uri="/WEB-INF/tld/xkins.tld" prefix="xkins" %>
<xkins:template name="stylesheet"/>
<table background="<xkins:path name='images-bg'/>/myBg.gif">
<tr>
<td>
<img src="<xkins:resource name='logoffIcon'/>"/>
</td>
</tr>
</table>
正如你所看到的,你可以使用Xkins来组织你的文件,如果你改变一个路径或者一个图片的名称呢搞,只需要改变Xkins定义就足够了。
l Xkins的未来:Xkins Faces
如我们看到的,Xkins是建立在从标签库代表具体的组件的基础上的。这个概念和JSF(Java Server Faces)规范吻合。JSF使用实现为它的组件产生HTML(或其他ML)。Xkins也可以被用作产生代理和增加皮肤能力。
Xkins Faces是一个使用Xkins实现的JSF。主要的实现应用了Decorator模式来实现JSF。Xkins Faces定义了一个皮肤类型,所以第三方提供者可以创建很多特征类似的皮肤来型。这样,如果你使用JSF和Xkins Faces来开发一个应用,从所有Xkins皮肤类型在Xkins Faces需要的模板中定义以后,你可以为你的Web应用下载一个新的皮肤,部署它,可以不经任何修改就是用它。所以在不久的将来,很多皮肤将用来被下载和在Web应用中使用,或者你的用户可以在已有皮肤的基础上创建自己的皮肤,直接从Internet中使用这些模板而不需要部署它们。
源代码:
http://www.javaworld.com/javaworld/jw-10-2004/xkins/jw-1025-xkins.war
Xkins主页:http://xkins.sourceforge.net/