昨天发了版,最近空闲一点,继续更新。
客户端的架子搭好了,现在来搞服务器端。
在服务器端我们使用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 ...
第3章Knockout.js介绍 安装Knockout.js 基本示例 何为MVVM? 创建ViewModel 小结 第4章数据库应用 Entity Framework介绍 Code First Database First 创建测试数据 小结 第二部分数据处理 第5章表的查询...
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`属性,可以基于数据条件动态地启用...