- 浏览: 326903 次
- 性别:
- 来自: 南太平洋
最新评论
-
yhxf_ie:
Ace Jump和IdeaVim,您都是如何安装的啊?在int ...
IDEA Intellij小技巧和插件 -
xiduoli:
这里没有看懂。我对着设置了一下。 然后查了一下 setting ...
IdeaVim插件使用技巧 -
kutekute:
分享快乐,谢啦
推荐一个颇好用的Scala REPL脚本控制台 -
lord_is_layuping:
“很难找到这样的两种语言(Pascal和Lisp),它们能如 ...
SICP读书笔记(2)——扉页,序 -
793059909:
kidneyball 写道793059909 写道下载页面打不 ...
推荐一个颇好用的Scala REPL脚本控制台
解决了JSF组件添加属性的问题,现在其实已经可以比较流畅地在JSF页面中使用knockout了。
常用的交互方式有两种,一是提交时使用正常的命令组件(p:commandButton, p:commandLink等),响应时使用Primefaces的RequestContext或OmniFaces的Ajax工具类返回JSON并更新ViewModel。
举例来说,以下是一段纯粹的JSF代码
它的功能是当选中dataGrid中的一行或多行时,显示有多少行被选中了,并且激活Delete按钮让用户选择。上面代码的问题是显而易见的,首先是无论多简单的动作,都必须通过ajax请求服务器端进行局部渲染。其次是可以看到上面代码中只有myBean.selected集合的尺寸在改变,但由于与其相关的界面元素位于不同位置,所以在每次改变selected集合时,都要在发起请求的组件上显式通知这些区域进行重新渲染。上面的例子只涉及到两个组件,渲染两处目标区域,但实际应用很可能涉及更多的组件和更多的目标区域。在实际开发中,往往为了方便而放弃精确的指向确实需要渲染的位置,而是渲染一个较大的区域(比如在上例中,改为update整个form),这样一来却又增加了不必要的网络流量负担。
那么改为结合knockout.js实现,将会是这样的:
在服务器端,我们需要调用
来构造响应的json数据。
咋看上去,似乎代码量还多了。但这种写法有以下好处:
1. 即使我们将来继续添加与selectedCount相关的界面元素,两个p:ajax的代码都不用改动了
2. 有需要的话,我们可以非常方便地调用客户端API实现模拟多行选中的效果 (直接调用app.vm.UserListVM.selectedCount(数字)就可以更新各处界面元素)
3. 服务器端的响应只是一个非常简短的json ({count:数字}),而不是各处目标区域的html代码。
在这个基础上,下面我们来对客户端的knockout体系进行一些扩展,让其更为易用。前面已经提过,与Avalon相比,原生的knockout.js库缺少一些提高易用性的功能,但好在knockout非常易于扩展,现在我们来把这些功能补上。
##显示/隐藏 的动画效果##
在显示/隐藏元素时使用动画效果是最常见的功能,因此我们加入slideVisible与fadeVisible两个自定义绑定:
使用data-bind="slideVisible: condition"绑定滑动效果,data-bind="fadeVisible: condition"绑定淡出效果。并且可以配合duration绑定来指定动画时间:data-bind="fadeVisible: condition, duration: 200"
##鼠标移入##
旧式浏览器不支持:hover伪类,因此我们引入一个hover绑定,当鼠标移入绑定元素时,添加指定的class。移出时去除。
用法
##几个常用属性的简写绑定##
knockout.js对所有元素属性统一使用attr来绑定,比如
比较冗长,我们可以为href, src, disabled这几个常用属性提供专门的绑定。
现在我们可以这样写
##反义绑定##
从上面的例子可以看出,我们往往需要使用data-bind="disabled: !valid()"这样在语义上双重否定的写法,既不方便也不直观。因此我们为常用的disabled和visible这两个布尔类型的绑定提供其相反的绑定:
现在我们可以这样写
##开关绑定##
让用户选择开关的最常用做法是使用checkbox
但在现实中,出于UI设计考虑,我们往往需要用div来模拟
显然这个function() {isSendEmail(!isSendEmail())}的写法略显丑陋,我们可以引入一个toggle绑定来对付这种需求。
现在可以这样写:
##扩展css样式类绑定##
knockout.js自身提供的css样式类绑定有两种,一是根据布尔值添加或移除class
也可以直接把一个字符串值作为class添加进去,并且当值改变时移除旧的class。
但这种做法有两个缺陷:
1. 会把元素上用class属性定义的静态样式类冲掉。
2. 如果有多个class动态改变,必须在模型中先把class的字符串拼装好,再一次性绑上去,非常不直观。
所以考虑引入一组styleClass-*绑定,其中*是1到9的数字,可以使用这套绑定直接分别绑定字符值。当然如果只需要绑定一个时也可以只使用styleClass。
用法:
至此,针对knockout.js自身的简单扩展就差不多了。下面开始重头戏,扩展knockout.js令其更适合与JSF协作。
##从元素值反向初始化模型值##
前面已经提过,knockout.js原本的设计理念是由js驱动页面。所有模型值都需要先在javascript层面初始化,否则就会冲掉元素上的值。通常这是通过载入静态模板页面后再发一个加载数据的请求来实现的。但JSF本身就是动态页面,而且组件绑定服务器端模型值,在首次渲染就把值渲染进去了。再加载一次是画蛇添足,如果不用额外请求加载,就需要在页面上把数据用javascript形式再渲染一次。这种做法不但累赘,而且容易受到脚本注入攻击。
所以,最方便的做法是,加入一个keepInitialValue的绑定,让knockout.js在首次绑定时反过来用元素值来初始化模型值。这样我们只需要保证视图模型的架子搭起来就行了,不需要在javascript层面上用业务数据进行初始化。
显然,有这种需求的必然是一些绑定到可写监控值上的双向绑定,比如说textInput, checked, value, selectedOptions等。我们逐个来扩展
首先是textInput和value,它们的机制相似,可以放到一起处理。
绑定用法如下:
在javascript中,我们只需要定义好firstName属性,随便给它一个初始值(最好保证类型相同)就行了。
同样,我们下面处理checked属性,由于checked属性要兼顾radio与checkbox两种元素,并且checkbox又分为绑定布尔和绑定数组两种情况,相对复杂一点
最后,处理selectedOptions
大功告成,现在主要的几种双向绑定都支持keepInitialValue绑定了。
##绑定内部元素##
虽然我们通过替换ResponseWriter的方式解决了大部分JSF组件渲染data-bind属性的问题。但还是对以下两种场景无能为力:
1. 某些渲染出复杂内部结构的组件,我们想绑定其内部某个元素时。比如说,绑定日期组件的年份下拉框。
2. 某些由父组件负责其子组件渲染的组件,我们想绑定子组件时。比如说,想绑定p:menuButton的其中一个p:menuItem,你会发现,p:menuItem对应的li元素根本没有clientId,它是由p:menuButton组件负责渲染的。
这类情况,最原始的解决办法是在实施绑定之前,用jQuery把data-bind强行加到元素上。但这样一来,元素标签和data-bind就分开了,代码变得很不直观。一个更好的解决方案是,引入一组data-bind-child-* (其中*为数字)绑定,把绑定子元素属性的绑定放到邻近的父元素上,这样至少保证了绑定的定义和绑定的目标不会离开太远。自然,为了触发knockout框架对这些属性进行处理,我们需要引入一个标志性的knockout绑定,命名为bindChildren。也就是说,当元素的data-bind中包含bindChildren绑定时,就触发对元素上data-bind-child-*属性的处理。在每个data-bind-child-*属性中,使用“绑定内容@子元素选择器”的格式,把绑定内容添加到符合选择器的子元素上。
比如说如果要绑定一个菜单项,可以这样写:
bindChildren绑定的实现代码如下:
##跳过区域##
knockout.js不支持视图模型嵌套,如果试图在同一个元素试图实施绑定两次,就会抛出错误。在JSF中,由于大量使用Facelet来组合页面,往往很难避免区域的嵌套。比如说在页面A的绑定区域中嵌入了页面B,而页面B又包含了一个绑定区域。那么当页面A的绑定区域完全渲染完毕,开始进行绑定时,其内部属于页面B的那个区域事实上已经绑定过了,这样A的绑定就会中途出错无法完成。
为了避免这种情况,我们需要引入一个告诉knockout.js跳过某个区域的标志,这样我们就可以用这个标志把区域B保护起来,其内部由页面B的代码独立绑定。而在对A进行绑定时,则跳过此区域。
好在knockout.js其实已经提供了非常方便的解决方案,但却没有提供现成的绑定,需要我们自己定义。
在这里我们定义了stop和noKnockout两个绑定,实现代码和功能是完全一样的:告诉knockout.js跳过其内部区域。但在业务语义上有所不同:
* stop用来保护嵌套的内部区域不被外部区域误绑定。也就是解决上面提到的问题
* noKnockout用来提醒开发者,其内部不应该有任何knockout绑定。由于前面已经提到过,被knockout绑定组件不能单独进行局部渲染(局部渲染后必须重新绑定),我们可以使用noKnockout来提醒开发者,这个区域内部不会有任何knockout绑定的元素,可以自由使用JSF的局部渲染。当然这只是一种构想的用法,实际效果如何需要在实际使用中评估。
用法示例如下:
页面A
页面B
至此,在客户端针对JSF整合的扩展就差不多了。下一节我们将回过头去完善服务器端的API。
常用的交互方式有两种,一是提交时使用正常的命令组件(p:commandButton, p:commandLink等),响应时使用Primefaces的RequestContext或OmniFaces的Ajax工具类返回JSON并更新ViewModel。
举例来说,以下是一段纯粹的JSF代码
<h:form> <h:panelGroup id="countMessage"> <h:outputText value="#{myBean.selected.size()"/> Row(s) selected. </h:panelGroup> <p:dataGrid ... > <p:ajax event="rowSelect" action="#{myBean.selectionChanged}" update="countMessage,actionPanel"/> <p:ajax event="rowUnselect" action="#{myBean.selectionChanged}" update="countMessage,actionPanel"/> <!-- Columns --> ... </p:dataGrid> <h:panelGroup id="actionPanel"> <p:commandButton id="deleteBtn" value="Delete" disabled="#{myBean.selected.size() eq 0}" action="#{myBean.deleteSelected}"/> </h:panelGroup> </h:form>
它的功能是当选中dataGrid中的一行或多行时,显示有多少行被选中了,并且激活Delete按钮让用户选择。上面代码的问题是显而易见的,首先是无论多简单的动作,都必须通过ajax请求服务器端进行局部渲染。其次是可以看到上面代码中只有myBean.selected集合的尺寸在改变,但由于与其相关的界面元素位于不同位置,所以在每次改变selected集合时,都要在发起请求的组件上显式通知这些区域进行重新渲染。上面的例子只涉及到两个组件,渲染两处目标区域,但实际应用很可能涉及更多的组件和更多的目标区域。在实际开发中,往往为了方便而放弃精确的指向确实需要渲染的位置,而是渲染一个较大的区域(比如在上例中,改为update整个form),这样一来却又增加了不必要的网络流量负担。
那么改为结合knockout.js实现,将会是这样的:
<h:form data-vm="UserListVM"> <div> <span data-bind="text: selectedCount"> </span> Row(s) selected. </div> <p:dataGrid ... > <p:ajax event="rowSelect" action="#{myBean.selectionChanged}" oncomplete="app.vm.UserListVM.selectedCount(args.count)"/> <p:ajax event="rowUnselect" action="#{myBean.selectionChanged}" oncomplete="app.vm.UserListVM.selectedCount(args.count)"/> <!-- Columns --> ... </p:dataGrid> <h:panelGroup id="actionPanel"> <p:commandButton id="deleteBtn" value="Delete" data-bind="attr: {disabled: selectedCount() == 0}" action="#{myBean.deleteSelected}"/> </h:panelGroup> <script> app.createVM({ selectedCount: 0 }, "UserListVM").bind("[data-vm=UserListVM]") </script> </h:form>
在服务器端,我们需要调用
RequestContext.getCurrentInstance().addCallbackParam("count", this.selected.size())
来构造响应的json数据。
咋看上去,似乎代码量还多了。但这种写法有以下好处:
1. 即使我们将来继续添加与selectedCount相关的界面元素,两个p:ajax的代码都不用改动了
2. 有需要的话,我们可以非常方便地调用客户端API实现模拟多行选中的效果 (直接调用app.vm.UserListVM.selectedCount(数字)就可以更新各处界面元素)
3. 服务器端的响应只是一个非常简短的json ({count:数字}),而不是各处目标区域的html代码。
在这个基础上,下面我们来对客户端的knockout体系进行一些扩展,让其更为易用。前面已经提过,与Avalon相比,原生的knockout.js库缺少一些提高易用性的功能,但好在knockout非常易于扩展,现在我们来把这些功能补上。
##显示/隐藏 的动画效果##
在显示/隐藏元素时使用动画效果是最常见的功能,因此我们加入slideVisible与fadeVisible两个自定义绑定:
;(function($){ ko.bindingHandlers.slideVisible = { init: function(element, valueAccessor) { var value = ko.unwrap(valueAccessor()); if (!value) $(element).hide() }, update: function(element, valueAccessor, allBindings) { var value = ko.unwrap(valueAccessor()); var duration = allBindings.get('duration') || 400; if (value) $(element).slideDown(duration); else $(element).slideUp(duration); } }; })(jQuery) ;(function($){ ko.bindingHandlers.fadeVisible = { init: function(element, valueAccessor) { var value = ko.unwrap(valueAccessor()); if (!value) $(element).hide() }, update: function(element, valueAccessor, allBindings) { var value = ko.unwrap(valueAccessor()); var duration = allBindings.get('duration') || 400; if (value) $(element).fadeIn(duration); else $(element).fadeOut(duration); } }; })(jQuery)
使用data-bind="slideVisible: condition"绑定滑动效果,data-bind="fadeVisible: condition"绑定淡出效果。并且可以配合duration绑定来指定动画时间:data-bind="fadeVisible: condition, duration: 200"
##鼠标移入##
旧式浏览器不支持:hover伪类,因此我们引入一个hover绑定,当鼠标移入绑定元素时,添加指定的class。移出时去除。
;(function($) { ko.bindingHandlers.hover = { init: function(element, valueAccessor) { var className = ko.unwrap(valueAccessor()) $(element).hover( function() {$(this).addClass(className)}, function() {$(this).removeClass(className)} ) } }; })(jQuery)
用法
<!-- 鼠标悬浮时给DIV加上.hover class --> <div data-bind="hover: 'hover'">...</div>
##几个常用属性的简写绑定##
knockout.js对所有元素属性统一使用attr来绑定,比如
<button data-bind="attr: {disabled: !valid()}"> </button>
比较冗长,我们可以为href, src, disabled这几个常用属性提供专门的绑定。
ko.bindingHandlers.href = { update: function (element, valueAccessor) { ko.bindingHandlers.attr.update(element, function () { return { href: valueAccessor()} }); } }; ko.bindingHandlers.src = { update: function (element, valueAccessor) { ko.bindingHandlers.attr.update(element, function () { return { src: valueAccessor()} }); } }; ko.bindingHandlers.disabled = { update: function (element, valueAccessor) { ko.bindingHandlers.attr.update(element, function () { return { disabled: valueAccessor()} }); } };
现在我们可以这样写
<button data-bind="disabled: !valid()"> </button>
##反义绑定##
从上面的例子可以看出,我们往往需要使用data-bind="disabled: !valid()"这样在语义上双重否定的写法,既不方便也不直观。因此我们为常用的disabled和visible这两个布尔类型的绑定提供其相反的绑定:
ko.bindingHandlers.enabled = { update: function (element, valueAccessor) { var value = ko.unwrap(valueAccessor()); ko.bindingHandlers.attr.update(element, function () { return { disabled: !value} }); } }; ko.bindingHandlers.hidden = { update: function (element, valueAccessor) { var value = ko.unwrap(valueAccessor()); ko.bindingHandlers.visible.update(element, function () { return !value; }); } };
现在我们可以这样写
<button data-bind="enable: valid"> </button>
##开关绑定##
让用户选择开关的最常用做法是使用checkbox
<input type="checkbox" data-bind="checked: isSendEmail"/>
但在现实中,出于UI设计考虑,我们往往需要用div来模拟
<div data-bind="css: {checked: isSendEmail}, click: function() {isSendEmail(!isSendEmail())}">...</div>
显然这个function() {isSendEmail(!isSendEmail())}的写法略显丑陋,我们可以引入一个toggle绑定来对付这种需求。
ko.bindingHandlers.toggle = { init: function (element, valueAccessor) { var value = valueAccessor() ko.applyBindingsToNode(element, { click: function () { value(!value()) } }); } };
现在可以这样写:
<div data-bind="css: {checked: isSendEmail}, toggle: isSendEmail">...</div>
##扩展css样式类绑定##
knockout.js自身提供的css样式类绑定有两种,一是根据布尔值添加或移除class
<div data-bind="css: {valid: isValid(), 'ui-disabled': isDisabled()}">
也可以直接把一个字符串值作为class添加进去,并且当值改变时移除旧的class。
<div data-bind="css: severity">
但这种做法有两个缺陷:
1. 会把元素上用class属性定义的静态样式类冲掉。
2. 如果有多个class动态改变,必须在模型中先把class的字符串拼装好,再一次性绑上去,非常不直观。
所以考虑引入一组styleClass-*绑定,其中*是1到9的数字,可以使用这套绑定直接分别绑定字符值。当然如果只需要绑定一个时也可以只使用styleClass。
;(function() { var factory = function(n) { return { init: function(element, valueAccessor) { var classNames = ko.unwrap(valueAccessor()) var el=$(element); el.addClass(classNames).data("prev-class-" + n, classNames) }, update: function(element, valueAccessor) { var el = $(element) var prev = el.data("prev-class-" + n) var current = " " + ko.unwrap(valueAccessor()) + " " var toRemove = ko.utils.arrayFilter((prev || "").split(/\s+/), function(item) { return item.trim().length > 0 && current.indexOf(" " + item + " ") < 0 }).join(" ") if (toRemove.length > 0) el.removeClass(toRemove) el.addClass(current) } }; } ko.bindingHandlers.styleClass = factory(0) for (var i = 1; i < 10; i++) { ko.bindingHandlers['styleClass' + i] = factory(i) } })(jQuery)
用法:
<div data-bind="styleClass: severity()">...</div> <div data-bind="styleClass: severity(), styleClass-1: progressStatus, styleClass-2: removed() ? 'removed' : ''"
至此,针对knockout.js自身的简单扩展就差不多了。下面开始重头戏,扩展knockout.js令其更适合与JSF协作。
##从元素值反向初始化模型值##
前面已经提过,knockout.js原本的设计理念是由js驱动页面。所有模型值都需要先在javascript层面初始化,否则就会冲掉元素上的值。通常这是通过载入静态模板页面后再发一个加载数据的请求来实现的。但JSF本身就是动态页面,而且组件绑定服务器端模型值,在首次渲染就把值渲染进去了。再加载一次是画蛇添足,如果不用额外请求加载,就需要在页面上把数据用javascript形式再渲染一次。这种做法不但累赘,而且容易受到脚本注入攻击。
所以,最方便的做法是,加入一个keepInitialValue的绑定,让knockout.js在首次绑定时反过来用元素值来初始化模型值。这样我们只需要保证视图模型的架子搭起来就行了,不需要在javascript层面上用业务数据进行初始化。
显然,有这种需求的必然是一些绑定到可写监控值上的双向绑定,比如说textInput, checked, value, selectedOptions等。我们逐个来扩展
首先是textInput和value,它们的机制相似,可以放到一起处理。
//Wrap the textInput and value handler to handle keepInitialValue binding ;(function($) { var textInputHandler = ko.bindingHandlers.textInput; var valueHandler = ko.bindingHandlers.value; var initViewModel = function(element, valueAccessor, allBindings) { if (allBindings.has("keepInitialValue")) { if (ko.isWritableObservable(valueAccessor())) { valueAccessor()($(element).val()) } } } ko.bindingHandlers.textInput = { 'init': function (element, valueAccessor, allBindings) { initViewModel(element, valueAccessor, allBindings) textInputHandler.init(element, valueAccessor, allBindings) } } ko.bindingHandlers.textInput.__proto__ = textInputHandler ko.bindingHandlers.value = { 'init': function (element, valueAccessor, allBindings) { initViewModel(element, valueAccessor, allBindings) valueHandler.init(element, valueAccessor, allBindings) } } ko.bindingHandlers.value.__proto__ = valueHandler })(jQuery);
绑定用法如下:
<h:inputText value="#{myBean.firstName}" data-bind="textInput: firstName, keepInitialValue"/>
在javascript中,我们只需要定义好firstName属性,随便给它一个初始值(最好保证类型相同)就行了。
app.registerConstructor("MyBeanVM", function() { this.firstName = ko.observable("") })
同样,我们下面处理checked属性,由于checked属性要兼顾radio与checkbox两种元素,并且checkbox又分为绑定布尔和绑定数组两种情况,相对复杂一点
//Wrap the checked handler to handle keepInitialValue binding ;(function() { var checkedHandler = ko.bindingHandlers.checked; var initViewModel = function(element, valueAccessor, allBindings) { function updateModel() { /* A simplified version of internal helper function in ko.bindingHandlers.checked */ var isCheckbox = element.type == "checkbox", isRadio = element.type == "radio", isChecked = element.checked; var useCheckedValue = isRadio || isValueArray; var elemValue = useCheckedValue ? checkedValue() : isChecked; // We can ignore unchecked radio buttons, because some other radio // button will be getting checked, and that one can take care of updating state. if (isRadio && !isChecked) { return; } var isValueArray = isCheckbox && (ko.utils.unwrapObservable(valueAccessor()) instanceof Array) if (isValueArray) { app.console.error('"keepInitialValue" does not support array value on the "checked" binding.') } else { valueAccessor()(elemValue) } }; if (allBindings.has("keepInitialValue")) { if (ko.isWritableObservable(valueAccessor())) { updateModel() } } } ko.bindingHandlers.checked = { 'init': function (element, valueAccessor, allBindings) { initViewModel(element, valueAccessor, allBindings) checkedHandler.init(element, valueAccessor, allBindings) } } ko.bindingHandlers.checked.__proto__ = checkedHandler })();
最后,处理selectedOptions
;(function($) { var selectedOptionsHandler = ko.bindingHandlers.selectedOptions; ko.bindingHandlers.selectedOptions = { 'init': function (element, valueAccessor, allBindings) { if (allBindings.has("keepInitialValue")) { (function () { var value = valueAccessor(), valueToWrite = []; ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) { if (node.selected) valueToWrite.push(ko.selectExtensions.readValue(node)); }); value(valueToWrite); })(); } selectedOptionsHandler.init(element, valueAccessor, allBindings) }, 'update': function(element, valueAccessor, allBindings, viewModel, bindingContext) { selectedOptionsHandler.update(element, valueAccessor, allBindings, viewModel, bindingContext) } } ko.bindingHandlers.selectedOptions.__proto__ = selectedOptionsHandler })(jQuery);
大功告成,现在主要的几种双向绑定都支持keepInitialValue绑定了。
##绑定内部元素##
虽然我们通过替换ResponseWriter的方式解决了大部分JSF组件渲染data-bind属性的问题。但还是对以下两种场景无能为力:
1. 某些渲染出复杂内部结构的组件,我们想绑定其内部某个元素时。比如说,绑定日期组件的年份下拉框。
2. 某些由父组件负责其子组件渲染的组件,我们想绑定子组件时。比如说,想绑定p:menuButton的其中一个p:menuItem,你会发现,p:menuItem对应的li元素根本没有clientId,它是由p:menuButton组件负责渲染的。
这类情况,最原始的解决办法是在实施绑定之前,用jQuery把data-bind强行加到元素上。但这样一来,元素标签和data-bind就分开了,代码变得很不直观。一个更好的解决方案是,引入一组data-bind-child-* (其中*为数字)绑定,把绑定子元素属性的绑定放到邻近的父元素上,这样至少保证了绑定的定义和绑定的目标不会离开太远。自然,为了触发knockout框架对这些属性进行处理,我们需要引入一个标志性的knockout绑定,命名为bindChildren。也就是说,当元素的data-bind中包含bindChildren绑定时,就触发对元素上data-bind-child-*属性的处理。在每个data-bind-child-*属性中,使用“绑定内容@子元素选择器”的格式,把绑定内容添加到符合选择器的子元素上。
比如说如果要绑定一个菜单项,可以这样写:
<div data-bind="bindChildren" data-bind-child-1="visible: selectedCount > 0 @ li.delete-all"> <p:menuButton> <p:menuItem styleClass="delete-all" value="Delete All" action="#{myBean.deleteAll}"/> </p:menuButton> </div>
bindChildren绑定的实现代码如下:
;(function($){ ko.bindingHandlers.bindChildren = { init: function(element) { $.each(element.attributes, function() { try { var attr = this, name, value, parts if (attr.specified) { name = attr.name if (name.match(/^data-bind-child(?:-\d+)?$/i)) { value = attr.value parts = value.split(/\s*@\s*/) if (parts.length == 2) { $(element).find(parts[1]).each(function() { $(this).attr("data-bind", parts[0]) }) } else { app.console.error("Invalid data-bind-child content: " + value) } } } } catch (err) { app.console.error(err) } }) } } })(jQuery)
##跳过区域##
knockout.js不支持视图模型嵌套,如果试图在同一个元素试图实施绑定两次,就会抛出错误。在JSF中,由于大量使用Facelet来组合页面,往往很难避免区域的嵌套。比如说在页面A的绑定区域中嵌入了页面B,而页面B又包含了一个绑定区域。那么当页面A的绑定区域完全渲染完毕,开始进行绑定时,其内部属于页面B的那个区域事实上已经绑定过了,这样A的绑定就会中途出错无法完成。
为了避免这种情况,我们需要引入一个告诉knockout.js跳过某个区域的标志,这样我们就可以用这个标志把区域B保护起来,其内部由页面B的代码独立绑定。而在对A进行绑定时,则跳过此区域。
好在knockout.js其实已经提供了非常方便的解决方案,但却没有提供现成的绑定,需要我们自己定义。
ko.bindingHandlers.stop = ko.bindingHandlers.noKnockout = { init: function() { return { controlsDescendantBindings: true }; } } ko.virtualElements.allowedBindings.stop = true ko.virtualElements.allowedBindings.noKnockout = true
在这里我们定义了stop和noKnockout两个绑定,实现代码和功能是完全一样的:告诉knockout.js跳过其内部区域。但在业务语义上有所不同:
* stop用来保护嵌套的内部区域不被外部区域误绑定。也就是解决上面提到的问题
* noKnockout用来提醒开发者,其内部不应该有任何knockout绑定。由于前面已经提到过,被knockout绑定组件不能单独进行局部渲染(局部渲染后必须重新绑定),我们可以使用noKnockout来提醒开发者,这个区域内部不会有任何knockout绑定的元素,可以自由使用JSF的局部渲染。当然这只是一种构想的用法,实际效果如何需要在实际使用中评估。
用法示例如下:
页面A
<div data-vm="vmA"> ... <ui:include src="b.xhtml"/> ... </div> <script> app.createVM("vmA").bind("[data-vm=vmA]") </script>
页面B
<div data-bind="stop"> <div data-vm="vmB"> ... </div> <script> app.createVM("vmB").bind("[data-vm=vmB]") </script> </div>
至此,在客户端针对JSF整合的扩展就差不多了。下一节我们将回过头去完善服务器端的API。
相关推荐
knockout 教程 中文版 Knockout使用js代码达到双向绑定的目的,类似Silverlight/WPF里的绑定一样,我们主要就是利用相关的特性进行开发的,极大地减少了代码开发量。
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 ...
MVC使用Knockout.JS实现的单页应用程序(SPA)1
MVC使用Knockout.JS实现的单页应用程序(SPA)2
Knockout.js.2014.12.pdf
基于 BOOTSTRAP 和 KNOCKOUT.JS 的 ASP.NET MVC 开发实战。 利用动态服务端Web内容和响应Web设计共同构建的网站,在任何分辨率、桌面或移动设备下都可以进行良好的显示。通过本书的实践应用,你将可以学习对ASP.NET ...
ASP.NET MVC 5 with Bootstrap and Knockout.js Oreilly 2015 (英文pdf+源码)
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 创建。 ##目标 尝试比其他玩家更快地解决简单的数学方程。 每场比赛都有时间限制(默认...
一套完整的框架及源代码,含数据初始数据,支持vs2010、2013,sql2008,学习真实项目及easyui、ko、fluent开发的资源
Diving into Knockout.JS
前端项目-knockout.mapping,Object mapping plugin for Knockout
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 ...
JavaScript捆绑与最小化 小结 第14章构建数据模型 Code—First模型 定义DbContext并初始化数据 视图模型 小结 第15章布局实现 共享布局 购物车摘要 分类菜单 小结 第16章图书列表 主页 特色图书 按...
Knockout.js 入门书籍 Knockout 是轻量级客户端 MVC 首选框架,核心能力为客户端数据绑定
https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.js https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.min.js jsdelivr ...
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 Mapping plugin v2.4.1.debug.js
KnockOut抠图插件,支持32、64位版系统(与photoshop位数无关) 目前测试使用win10(64位)、photoshop cs6 13.0(32位) 1.下载后放在photoshop安装目录中的Plug-ins中(解压后Plug-ins文件夹中应该会包含:KnockOut.8bf...