昨天发了版,最近空闲一点,继续更新。
客户端的架子搭好了,现在来搞服务器端。
在服务器端我们使用JSF2.1,Primefaces 4.0.x (elite版,现在为了支持IE7还卡在4.0.16,预计明年1月IE8的官方支持过期,我们就可以考虑放弃IE7了),OmniFaces。
首先,我们需要让JSF组件支持我们的data-binding, data-vm等自定义属性。由于在JSF中,组件类自身的内部状态和标签上的属性使用一样的API来储存和访问(通过组件类上getter/setter或getAttributes()方法)。渲染器不能无脑地把组件类上所有属性都渲染到最终的HTML标签上,而必须采用一套硬编码的passthru属性表,渲染器仅把它自己认可的passthru属性渲染出来。这就导致了渲染器不认识的自定义的属性不会出现在渲染结果中。JSF2.2提供了新的passthough属性命名空间来解决这个问题,但在JSF2.1中,需要我们自己动手来实现。
OmniFaces针对HTML5的新属性(比如input上的placeholder属性)提供了一个Html5RenderKit。但这个解决方案是在ResponseWriter的startElement方法中做手脚,这导致如果一个组件会渲染出多个标签时,属性有时会被重复渲染到这些标签上。对一些表单组件来说这个问题不算严重,一来表单组件通常都是单一标签,二来就算重复渲染了,这些属性放在非表单标签上是没有任何效果的。但我们的场景要求更加严格,重复渲染data-bind就会导致意外的绑定,引起意外的UI动作。因此,我们可以借鉴OmniFaces的思路,并进行一些改进。
整体思路是,采用一个自定义的RenderKitWrapper来创建一个自定义的ResonseWriter,在这个ResponseWriter的startElement方法中,仅仅记录当前组件的clientId。改为在writeAttribute方法中,当发现当前渲染的属性为id时,把id值与clientId值进行比较,如果相等的话,说明当前正在渲染的标签是当前组件的主要标签,我们就把data-bind属性渲染到这个标签上。由于JSF内部是依赖clientId来对组件进行局部渲染,并且在decode阶段,通常是根据http请求中的clientId来定位组件,因此依赖clientId找主要标签的方案是完全可行的。
代码如下,首先写个RenderKitWrapper的实现,它的主要工作就是创建我们自定义的CustomAttributeResponseWriter。
** 从下面的三段代码可以看出,这个解决方案连续使用了三次装饰者模式,这是JSF自身所提供的扩展机制。
public class CustomAttributeRenderKit extends RenderKitWrapper
{
private RenderKit wrapped;
/**
* Construct a new custom render kit around the given wrapped render kit.
*
* @param wrapped
* The wrapped render kit.
*/
public CustomAttributeRenderKit(RenderKit wrapped)
{
this.wrapped = wrapped;
}
/**
* Returns a new CustomAttributeResponseWriter which in turn wraps the default
* response writer.
*/
@Override
public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding)
{
return new CustomAttributeResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
}
@Override
public RenderKit getWrapped()
{
return wrapped;
}
}
CustomAttributeResponseWriter源码如下:
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import org.omnifaces.util.Components;
class CustomAttributeResponseWriter extends ResponseWriterWrapper
{
private static final Set<String> CUSTOM_ATTRIBUTES = new HashSet<String>("data-bind", "data-vm");
private static final Set<String> IGNORED_TAGS = new HashSet<String>("option", "script", "style");
private String currentClientId = "";
private String currentTag = "";
private ResponseWriter wrapped;
public CustomAttributeResponseWriter(ResponseWriter wrapped)
{
this.wrapped = wrapped;
}
@Override
public ResponseWriter cloneWithWriter(Writer writer)
{
return new CustomAttributeResponseWriter(super.cloneWithWriter(writer));
}
/**
* 在startElement中保持当前组件的clientId。由于ResponseWriter不会跨线程使用,可以保持在对象属性中。
*/
@Override
public void startElement(String name, UIComponent component) throws IOException
{
this.currentTag = name;
super.startElement(name, component);
if (component == null)
{
component = Components.getCurrentComponent();
}
if (component != null)
{
this.currentClientId = component.getClientId();
}
}
@Override
public void writeAttribute(String name, Object value, String property) throws IOException
{
super.writeAttribute(name, value, property);
/* Knockout attributes should only bind to the main element with same client id as the component */
if (name != null && !IGNORED_TAGS.contains(this.currentTag.toLowerCase()) &&
"id".equals(name) && this.currentClientId != null && this.currentClientId.equals(value)) {
if (component == null) component = Components.getCurrentComponent();
if (component != null) {
writeCustomAttributesIfNecessary(component.getAttributes(), CUSTOM_ATTRIBUTES);
}
}
}
private void writeCustomAttributesIfNecessary(Map<String, Object> attributes, Collection<String> names) throws IOException
{
for (String name : names)
{
Object value = attributes.get(name);
if (value != null)
{
super.writeAttribute(name, value, null);
}
}
}
}
然后我们还需要一个创建这个RenderKit的RenderKitFactory
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import java.util.Iterator;
public class CustomAttributeRenderKitFactory extends RenderKitFactory
{
private RenderKitFactory wrapped;
public CustomAttributeRenderKitFactory(RenderKitFactory wrapped) {
this.wrapped = wrapped;
}
@Override
public void addRenderKit(String renderKitId, RenderKit renderKit) {
wrapped.addRenderKit(renderKitId, renderKit);
}
/**
* 如果传入的renderKitId等于{@link RenderKitFactory#HTML_BASIC_RENDER_KIT},返回一个封装了原renderKit的CustomAttributeRenderKit实例。
* 否则直接返回原来的renderKit
*/
@Override
public RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new CustomAttributeRenderKit(renderKit) : renderKit;
}
@Override
public Iterator<String> getRenderKitIds() {
return wrapped.getRenderKitIds();
}
}
最后,在faces-config.xml中配置使用这个自定义的RenderKitFactory。值得再次提醒的是,JSF的render-kit-factory配置使用的是装饰者模式,框架在调用这里配置的RenderKitFactory的构造方法时,会传入系统原本的RenderKitFactory ( 通常就是HTML_BASIC_RENDERKIT_FACTORY ),我们自定义的RenderKitFactory可以在此基础上进行扩展。
<faces-config
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0" >
<factory>
<render-kit-factory>net.danieldeng.faces.common.renderkits.CustomAttributeRenderKitFactory</render-kit-factory>
</factory>
</faces-config>
大功告成,现在可以在JSF组件上直接使用data-bind,data-vm属性了。
<h:panelGroup id="DemoDiv" display="block" data-vm="DemoVM">
<h:inputText id="username" value="#{myBean.username}" data-bind="inputText: username"/>
</h:panelGroup>
<scirpt>
app.createVM({
username: "#{myBean.username}"
}, "DemoVM").bind("#DemoDiv")
</script>
很容易发现,这里有个缺陷,knockout.js的常规开发方式是用Javascript模型来驱动视图,因此模型的初始化需要放在Javascript中。而JSF的常规做法是组件直接绑定后台ManagedBean属性。因此这里不得不同时在组件的value属性上以及Javascript的模型初始化代码中重复使用#{myBean.username}这个EL表达式。并且,在Javascript嵌入EL的做法会使系统很容易受到脚本注入攻击。
下一节我们将尝试解决这一问题。
分享到:
相关推荐
Knockout.JS是一个轻量级的JavaScript库,专门用于实现MVVM(Model-View-ViewModel)模式。MVVM模式在客户端开发中非常流行,因为它简化了数据绑定和UI更新的过程。Knockout.JS的核心功能包括数据绑定、依赖跟踪和...
Knockout.js是一个非常流行的JavaScript库,它遵循MVVM(模型-视图-视图模型)设计模式,主要被用来帮助开发者实现JavaScript应用程序中丰富的用户界面。在本文中,我们将详细介绍Knockout.js的核心概念以及如何在...
Knockout.JS是一款强大的JavaScript库,它提供了数据绑定和依赖跟踪功能,使得在浏览器端创建复杂的用户界面变得更加简单。而ASP.NET MVC是一个流行的服务器端框架,用于构建动态、数据驱动的Web应用。 首先,了解...
这个插件是为了解决在使用Knockout.js进行数据绑定时,对JavaScript对象与视图模型之间映射的问题。Knockout.js是一个MVVM(Model-View-ViewModel)框架,它使得JavaScript可以轻松地实现数据双向绑定,简化了DOM...
Knockout.JS 是一个JavaScript库,专门用于实现MVVM(Model-View-ViewModel)模式,简化了前端数据绑定和动态用户界面的创建。在ASP.NET MVC 4项目中,Knockout.JS可以无缝集成,帮助开发者实现在客户端实时更新视图...
3. **服务器端与客户端通信**:在MVC4中,控制器可以返回JSON或ViewModel对象,供Knockout.js在客户端使用。通常,这可以通过AJAX请求实现,例如使用jQuery的`$.ajax`或`$.getJSON`。 4. **页面初始化**:在HTML...
《ASP.NET MVC 5 with Bootstrap and Knockout.js》是由O'Reilly出版社于2015年出版的一本专业书籍,主要面向的是希望掌握ASP.NET MVC 5开发技术,并结合Bootstrap和Knockout.js构建现代Web应用程序的开发者。...
### Knockout.js Succinctly:全面解析与应用实践 #### 一、Knockout.js简介与特点 **Knockout.js**是一款轻量级的客户端MVC(Model-View-Controller)框架,专为简化现代Web应用程序开发而设计。其核心功能之一是...
Beginning with a vital overview of Knockout.js, including the MVVM design pattern, you will create a simple but powerful application capable of integrating with ASP.NET MVC as well as gain a thorough ...
基于 BOOTSTRAP 和 KNOCKOUT.JS 的 ASP.NET MVC 开发实战。 利用动态服务端Web内容和响应Web设计共同构建的网站,在任何分辨率、桌面或移动设备下都可以进行良好的显示。通过本书的实践应用,你将可以学习对ASP.NET ...
With this practical book, you’ll learn how by combining the ASP.NET MVC server-side language, the Bootstrap front-end framework, and Knockout.js—the JavaScript implementation of the Model-View-...
简单的knockout.js/websockets/node.js实时游戏 这是一个非常简单的实时游戏,使用 node.js、knockout.js 和 socket.io 创建。 ##目标 尝试比其他玩家更快地解决简单的数学方程。 每场比赛都有时间限制(默认...
Knockout.JS is an emerging JavaScript presentation framework that promotes a solid solution design in combination with Jasmine, a well-known unit testing library. They both ensure rapid development ...
KnockOut抠图插件,支持32、64位版系统(与photoshop位数无关) 目前测试使用win10(64位)、photoshop cs6 13.0(32位) 1.下载后放在photoshop安装目录中的Plug-ins中(解压后Plug-ins文件夹中应该会包含:KnockOut.8bf...
**Knockout.js与jQuery Mobile的整合:使用Shim** 在Web开发中,Knockout.js和jQuery Mobile是两个非常流行的库,分别用于数据绑定和移动应用的UI交互。它们结合使用可以创建出功能强大且用户友好的动态移动界面。...
Knockout.js能够很好地与其他JavaScript库和框架协同工作,例如可以轻松地将Bootstrap或其他UI框架的组件与Knockout.js的绑定相结合。 #### 更改button的状态 通过绑定`disabled`属性,可以基于数据条件动态地启用...
With this practical book, you'll learn how by combining the ASP.NET MVC server-side language, the Bootstrap front-end framework, and Knockout.js - the JavaScript implementation of the Model-View-...