`
kidneyball
  • 浏览: 328953 次
  • 性别: Icon_minigender_1
  • 来自: 南太平洋
社区版块
存档分类
最新评论

Knockout.js与Primefaces整合日志 3

阅读更多
昨天发了版,最近空闲一点,继续更新。

客户端的架子搭好了,现在来搞服务器端。

在服务器端我们使用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的做法会使系统很容易受到脚本注入攻击。

下一节我们将尝试解决这一问题。
0
0
分享到:
评论

相关推荐

    MVC使用Knockout.JS实现的单页应用程序(SPA)1

    Knockout.JS是一个轻量级的JavaScript库,专门用于实现MVVM(Model-View-ViewModel)模式。MVVM模式在客户端开发中非常流行,因为它简化了数据绑定和UI更新的过程。Knockout.JS的核心功能包括数据绑定、依赖跟踪和...

    深入学习 Knockout.JS

    Knockout.js是一个非常流行的JavaScript库,它遵循MVVM(模型-视图-视图模型)设计模式,主要被用来帮助开发者实现JavaScript应用程序中丰富的用户界面。在本文中,我们将详细介绍Knockout.js的核心概念以及如何在...

    MVC使用Knockout.JS实现的单页应用程序(SPA)2

    Knockout.JS是一款强大的JavaScript库,它提供了数据绑定和依赖跟踪功能,使得在浏览器端创建复杂的用户界面变得更加简单。而ASP.NET MVC是一个流行的服务器端框架,用于构建动态、数据驱动的Web应用。 首先,了解...

    前端项目-knockout.mapping.zip

    这个插件是为了解决在使用Knockout.js进行数据绑定时,对JavaScript对象与视图模型之间映射的问题。Knockout.js是一个MVVM(Model-View-ViewModel)框架,它使得JavaScript可以轻松地实现数据双向绑定,简化了DOM...

    基于ASP.NET MVC 4 +Knockout.JS+Web API+FluentData+EasyUI 通用权限管理

    Knockout.JS 是一个JavaScript库,专门用于实现MVVM(Model-View-ViewModel)模式,简化了前端数据绑定和动态用户界面的创建。在ASP.NET MVC 4项目中,Knockout.JS可以无缝集成,帮助开发者实现在客户端实时更新视图...

    mvc+knockout.js联动

    3. **服务器端与客户端通信**:在MVC4中,控制器可以返回JSON或ViewModel对象,供Knockout.js在客户端使用。通常,这可以通过AJAX请求实现,例如使用jQuery的`$.ajax`或`$.getJSON`。 4. **页面初始化**:在HTML...

    ASP.NET MVC 5 with Bootstrap and Knockout.js Oreilly 2015

    《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 Succinctly:全面解析与应用实践 #### 一、Knockout.js简介与特点 **Knockout.js**是一款轻量级的客户端MVC(Model-View-Controller)框架,专为简化现代Web应用程序开发而设计。其核心功能之一是...

    Getting Started with Knockout.js for .NET Developers(PACKT,2015)

    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 开发实战

    基于 BOOTSTRAP 和 KNOCKOUT.JS 的 ASP.NET MVC 开发实战。 利用动态服务端Web内容和响应Web设计共同构建的网站,在任何分辨率、桌面或移动设备下都可以进行良好的显示。通过本书的实践应用,你将可以学习对ASP.NET ...

    ASP.NET.MVC.5.with.Bootstrap.and.Knockout.js.1491914394

    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-...

    一个非常简单的websockets/knockout.js/node.js/socket.io实时游戏_HTML_代码_下载

    简单的knockout.js/websockets/node.js实时游戏 这是一个非常简单的实时游戏,使用 node.js、knockout.js 和 socket.io 创建。 ##目标 尝试比其他玩家更快地解决简单的数学方程。 每场比赛都有时间限制(默认...

    Web App Testing Using Knockout.JS(PACKT,2014)

    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 ...

    ASP.NET MVC 5 with Bootstrap and Knockout

    第3章Knockout.js介绍 安装Knockout.js 基本示例 何为MVVM? 创建ViewModel 小结 第4章数据库应用 Entity Framework介绍 Code First Database First 创建测试数据 小结 第二部分数据处理 第5章表的查询...

    KnockOut 2.88 支持32及64位版.rar

    KnockOut抠图插件,支持32、64位版系统(与photoshop位数无关) 目前测试使用win10(64位)、photoshop cs6 13.0(32位) 1.下载后放在photoshop安装目录中的Plug-ins中(解压后Plug-ins文件夹中应该会包含:KnockOut.8bf...

    knockout.js---jquery.mobile.js-using-shim:Knockout.js + jquery.mobile.js

    **Knockout.js与jQuery Mobile的整合:使用Shim** 在Web开发中,Knockout.js和jQuery Mobile是两个非常流行的库,分别用于数据绑定和移动应用的UI交互。它们结合使用可以创建出功能强大且用户友好的动态移动界面。...

    Knockout js 基礎入門教學

    Knockout.js能够很好地与其他JavaScript库和框架协同工作,例如可以轻松地将Bootstrap或其他UI框架的组件与Knockout.js的绑定相结合。 #### 更改button的状态 通过绑定`disabled`属性,可以基于数据条件动态地启用...

Global site tag (gtag.js) - Google Analytics