除了ko内建的绑定,还可以自定义绑定,灵活地封装复杂的行为使之可重用。

自定义绑定

注册自定义绑定

向 ko.bindingHandles
添加一个子属性来注册一个绑定。

ko.bindingHandlers.yourBindingName = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever any observables/computeds that are accessed change
        // Update the DOM element based on the supplied values here.
    }
};

然后就可以向DOM元素使用它了。

<div data-bind="yourBindingName: someValue"> </div>

回调函数

创建自定义绑定时需要提供回调函数 init
和 update
,可以只提供其中一个。

ko会在绑定应用到元素上时调用 init
,之后便不再调用,一般用于初始化元素状态和注册事件的回调函数。

ko会在初始绑定应用到元素上时调用 update
,然后在其访问的任意依赖变化时也会调用。

回调函数的参数:

  • element:使用绑定的DOM元素。
  • valueAccessor:返回绑定的模型属性的js函数。例如调用 valueAccessor()
    来获取当前模型属性值。为了能兼容监控变量和普通值,使用 ko.unwrap
    ,如 ko.unwrap(valueAccessor())
  • allBindings:可以用来访问DOM元素绑定的所有模型值的js对象。例如 allBindings.get(“name”)
    获取 name
    绑定的值,不存在时为 undefined
    。可以用 allBindings.has(“name”)
    来检测其是否存在。
  • bindingContext:包含该元素的绑定可用的上下文的对象。(在v3版本之前,该参数是绑定的试图模型 viewModel
    ,现在可用 bindingContext.$data
    或 bindingContext.$rawData
    来代替。)

例如,可以用 visible
绑定来控制元素的可见性,但希望在元素状态变化时添加动画。可以使用jQuery的 slideUp
/ slideDown
创建一个自定义绑定。

ko.bindingHandlers.slideVisible = {
    update: function(element, valueAccessor, allBindings) {
    // First get the latest data that we"re bound to
    var value = valueAccessor();
    // Next, whether or not the supplied model property is observable, get its current value
    var valueUnwrapped = ko.unwrap(value);
    // Grab some more data from another binding property
    var duration = allBindings.get("slideDuration") || 400; // 400ms is default duration unless otherwise specified
    // Now manipulate the DOM element
    if (valueUnwrapped == true)
        $(element).slideDown(duration); // Make the element visible
    else
        $(element).slideUp(duration);   // Make the element invisible
    }
};
<div data-bind="slideVisible: giftWrap, slideDuration:600">You have selected the option</div>
<label><input type="checkbox" data-bind="checked: giftWrap" /> Gift wrap</label>
 
<script type="text/javascript">
    var viewModel = {
        giftWrap: ko.observable(true)
    };
    ko.applyBindings(viewModel);
</script>

如果希望 slideVisible
在页面第一次加载时不使用动画,只是在用户改变模型状态时才出现动画,可以添加 init
方法:

ko.bindingHandlers.slideVisible = {
    init: function(element, valueAccessor) {
        var value = ko.unwrap(valueAccessor()); // Get the current value of the current property we"re bound to
        $(element).toggle(value); // jQuery will hide/show the element depending on whether "value" or true or false
    },
    update: function(element, valueAccessor, allBindings) {
        // Leave as before
    }
};

DOM变化后更新监控属性

上面展示了如何使用 update
来根据监控属性变化更新DOM元素,那怎样在用户操作DOM元素之后更新关联的监控属性呢?

可以在 init
中注册一些事件的回调函数来更新监控属性。

ko.bindingHandlers.hasFocus = {
    init: function(element, valueAccessor) {
        $(element).focus(function() {
            var value = valueAccessor();
            value(true);
        });
        $(element).blur(function() {
            var value = valueAccessor();
            value(false);
        });
    },
    update: function(element, valueAccessor) {
        var value = valueAccessor();
        if (ko.unwrap(value))
            element.focus();
        else
            element.blur();
    }
};
<p>Name: <input data-bind="hasFocus: editingName" /></p>
 
<!-- Showing that we can both read and write the focus state -->
<div data-bind="visible: editingName">You"re editing the name</div>
<button data-bind="enable: !editingName(), click:function() { editingName(true) }">Edit name</button>
 
<script type="text/javascript">
    var viewModel = {
        editingName: ko.observable()
    };
    ko.applyBindings(viewModel);
</script>

控制后代的自定义绑定

默认情况下,绑定只会作用于应用它的元素。如果希望作用于所有后代元素,只需在 init
中 return { controlsDescendantBindings: true }

ko.bindingHandlers.allowBindings = {
    init: function(elem, valueAccessor) {
        // Let bindings proceed as normal *only if* my value is false
        var shouldAllowBindings = ko.unwrap(valueAccessor());
        return { controlsDescendantBindings: !shouldAllowBindings };
    }
};
<div data-bind="allowBindings: true">
    <!-- This will display Replacement, because bindings are applied -->
    <div data-bind="text: "Replacement"">Original</div>
</div>
<div data-bind="allowBindings: false">
    <!-- This will display Original, because bindings are not applied -->
    <div data-bind="text: "Replacement"">Original</div>
</div>

向后代绑定传递额外参数

通常使用 controlsDescendantBindings
的绑定也会调用 ko.applyBindingsToDescendants(someBindingContext, element)
,使后代绑定应用于一些修改的上下文。

例如,自定义的绑定 withProperties
向绑定上下文附加一些额外的属性,以便在后代绑定中能访问。

ko.bindingHandlers.withProperties = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // Make a modified binding context, with a extra properties, and apply it to descendant elements
        var innerBindingContext = bindingContext.extend(valueAccessor);
        ko.applyBindingsToDescendants(innerBindingContext, element);
        // Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
        return { controlsDescendantBindings: true };
    }
};

可以看到,绑定上下文有个 extend
方法,返回一个复制的并带有额外属性的上下文。 extend
方法接受的参数要么是一个带有属性的对象,要么是能返回这样的对象的函数。建议使用函数作为参数,这样后续的绑定值变化也会更新到绑定上下文中,并且不影响当前的绑定上下文,也就是说不影响兄弟层级的元素,只会影响后代元素。

<div data-bind="withProperties: { emotion: "happy" }">
    Today I feel <span data-bind="text: emotion"></span>. <!-- Displays: happy -->
</div>
<div data-bind="withProperties: { emotion: "whimsical" }">
    Today I feel <span data-bind="text: emotion"></span>. <!-- Displays: whimsical -->
</div>

在绑定上下文中添加额外的层级

with
和 foreach
绑定会在绑定上下文中添加额外的层级。这意味着它们的后代能够使用 $parent
、 $parents
、 $root
或 $parentContext
来访问外层数据。

如果在自定义绑定中实现该功能,除了使用 bindingContext.extend()
,还可以使用 bindingContext.createChildContext(someData)
。它会返回一个新上下文,该上下文的视图模型是 someData
,父上下文 $parentContext
是 bindingContext
。如果需要,还可以使用 ko.utils.extend
为该子上下文扩展额外的属性。

ko.bindingHandlers.withProperties = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // Make a modified binding context, with a extra properties, and apply it to descendant elements
        var childBindingContext = bindingContext.createChildContext(
            bindingContext.$rawData, 
            null, // Optionally, pass a string here as an alias for the data item in descendant contexts
            function(context) {
                ko.utils.extend(context, valueAccessor());
            });
        ko.applyBindingsToDescendants(childBindingContext, element);
        // Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
        return { controlsDescendantBindings: true };
    }
};

这样, withProperties
绑定就可以嵌套使用了,并且每个嵌套层级可以通过 $parentContext
访问父层。

<div data-bind="withProperties: { displayMode: "twoColumn" }">
    The outer display mode is <span data-bind="text: displayMode"></span>.
    <div data-bind="withProperties: { displayMode: "doubleWidth" }">
        The inner display mode is <span data-bind="text: displayMode"></span>, but I haven"t forgotten
        that the outer display mode is <span data-bind="text: $parentContext.displayMode"></span>.
    </div>
</div>

支持虚拟元素的自定义绑定

使用 ko.virtualElements.allowedBindings
告诉ko自定义绑定能够识别虚拟元素。

例如,创建一个对DOM节点随机排序的自定义绑定:

ko.bindingHandlers.randomOrder = {
    init: function(elem, valueAccessor) {
        // Pull out each of the child elements into an array
        var childElems = [];
        while(elem.firstChild)
            childElems.push(elem.removeChild(elem.firstChild));
        // Put them back in a random order
        while(childElems.length) {
            var randomIndex = Math.floor(Math.random() * childElems.length),
                chosenChild = childElems.splice(randomIndex, 1);
            elem.appendChild(chosenChild[0]);
        }
    }
};

这对普通DOM元素是可用的:

<div data-bind="randomOrder: true">
    <div>First</div>
    <div>Second</div>
    <div>Third</div>
</div>

但是对虚拟元素却不起作用,会报错“The binding “randomOrder” cannot be used with virtual elements”:

<!-- ko randomOrder: true -->
    <div>First</div>
    <div>Second</div>
    <div>Third</div>
<!-- /ko -->

要想 randomOrder
支持虚拟元素,需要配置ko:

ko.virtualElements.allowedBindings.randomOrder = true;

这样就不会报错了,但仍然不起作用,因为绑定中调用的是普通DOM的API,不识别虚拟元素。这就是为什么ko要求显式地声明支持虚拟元素。使用虚拟元素API更新自定义绑定:

ko.bindingHandlers.randomOrder = {
    init: function(elem, valueAccessor) {
        // Build an array of child elements
        var child = ko.virtualElements.firstChild(elem),
            childElems = [];
        while (child) {
            childElems.push(child);
            child = ko.virtualElements.nextSibling(child);
        }
        // Remove them all, then put them back in a random order
        ko.virtualElements.emptyNode(elem);
        while(childElems.length) {
            var randomIndex = Math.floor(Math.random() * childElems.length),
                chosenChild = childElems.splice(randomIndex, 1);
            ko.virtualElements.prepend(elem, chosenChild[0]);
        }
    }
};

现在该绑定对普通DOM元素和虚拟元素都支持了,因为 ko.virtualElements
的API是兼容普通DOM元素的。

虚拟元素API

  • ko.virtualElements.allowedBindings

    该对象的key决定哪些绑定可用于虚拟元素。如 ko.virtualElements.allowedBindings.mySuperBinding = true
    意味着允许 mySuperBinding
    用于虚拟元素。

  • ko.virtualElements.emptyNode(containerElem)

移除真实或虚拟元素 containerElem
的所有子节点(通过清除他们关联的任何数据来避免内存泄露)。

  • ko.virtualElements.firstChild(containerElem)

返回真实或虚拟元素 containerElem
的第一个子节点,不存在时返回null。

  • ko.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter)

添加 nodeToInsert
为真实或虚拟元素 containerElem
的子节点,位置在 insertAfter
之后( insertAfter
必须是 containerElem
的子节点)。

  • ko.virtualElements.nextSibling(node)

返回真实或虚拟元素的 node
的后的兄弟节点,不存在时返回null。

  • ko.virtualElements.prepend(containerElem, nodeToPrepend)

将 nodeToPrepend
添加为真实或虚拟元素 containerElem
的第一个子节点。

  • ko.virtualElements.setDomNodeChildren(containerElem, arrayOfNodes)

移除真实或虚拟元素 containerElem
的所有子节点(通过清除他们关联的任何数据来避免内存泄露),并将 arrayOfNodes
中的节点作为的新的子节点插入。

自定义DISPOSE

在使用模板绑定或流程控制绑定时,一些DOM元素会被动态添加或移除。当创建自定义绑定时,通常需要添加ko移除关联的元素时的清除逻辑。

为元素的释放注册回调函数

调用 ko.utils.domNodeDisposal.addDisposeCallback(node, callback)
来注册节点被删除的回调函数。

ko.bindingHandlers.myWidget = {
    init: function(element, valueAccessor) {
        var options = ko.unwrap(valueAccessor()),
            $el = $(element);
        $el.myWidget(options);
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            // This will be called when the element is removed by Knockout or
            // if some other part of your code calls ko.removeNode(element)
            $el.myWidget("destroy");
        });
    }
};

覆盖外部数据的清除逻辑

当ko移除元素时会执行清除逻辑来清除该元素关联的任何数据。在清除逻辑中,如果jQuery可用,那么是通过调用jQuery的 cleanData
方法。在一些高级的应用场景中,可能希望阻止或自定义数据移除的方式。可以通过覆盖 ko.utils.domNodeDisposal.cleanExternalData(node)
支持自定义逻辑。例如,要阻止调用 cleanData
,可以用一个空函数来替代标准的 cleanExternalData
实现。

ko.utils.domNodeDisposal.cleanExternalData = function () {
    // Do nothing. Now any jQuery data associated with elements will
    // not be cleaned up when the elements are removed from the DOM.
};

使用预处理(PREPROCESSING)扩展KO的绑定语法

从ko V3.0版本开始,ko支持通过添加一个可以在绑定过程中重写DOM节点并绑定字符串的回调函数来定义一个自定义语法。

预处理绑定字符串

可以为某个绑定处理器(click、visible或自定义绑定)提供一个绑定预处理器(binding preprocessor)来链接到ko的 data-bind
解释逻辑中。

通过对绑定处理器添加 preprocess
函数。

ko.bindingHandlers.yourBindingHandler.preprocess = function(stringFromMarkup) {
    // Return stringFromMarkup if you don"t want to change anything, or return
    // some other string if you want Knockout to behave as if that was the
    // syntax provided in the original HTML
}

例1:为绑定设置默认值

如果绑定是没有指定value,会默认为 undefined
。可以使用预处理器使绑定有个初始值。

ko.bindingHandlers.uniqueName.preprocess = function(val) {
    return val || "true";
}

uniqueName
将允许绑定时不指定value,而默认为 true

<input data-bind="value: someModelProperty, uniqueName" />

例2:为事件绑定表达式

如果希望为 click
事件绑定表达式(而不是ko期望的函数引用),可以使用预处理器。

ko.bindingHandlers.click.preprocess = function(val) {
    return "function($data,$event){ " + val + " }";
}
<button type="button" data-bind="click: myCount(myCount()+1)">Increment</button>

绑定预处理器

ko.bindingHandlers. .preprocess(value, name, addBindingCallback)

如果定义了该函数,那么在绑定被评估之前每个 <name>
的绑定时都会调用该函数。

preprocess
的参数如下:

  • value:ko试图解析绑定之前,绑定的值的语法。(例如 yourBinding: 1 + 1
    中,关联的值是字符串"1 + 1")
  • name:绑定的名称。(例如 yourBinding: 1 + 1
    中,名称是字符串"yourBinding")
  • addBinding:可选的回调函数,用于为当前元素添加另一个绑定。它有两个参数, name
    和 value
    。例如,在预处理方法中,调用 addBinding(“visible”, “acceptsTerms()”);
    来让ko表现得看起来就像元素已经有了 visible: acceptsTerms()
    绑定。

preprocess
必须返回一个新的字符串值来被解析并传递给绑定,或者返回 undefined
来移除绑定。返回非字符串值是没有意义的,因为HTML一定是字符串。例如将”value + ".toUpperCase()"”作为字符串返回,那么 yourBinding: "Bert"
会被解析成就像HTML中包含 yourBinding: "Bert".toUpperCase()
一样。ko会按照正常方式解析返回的值,因此必须是合法的js表达式。

预处理DOM节点

可以提供一个节点预处理器(node preprocessor)来链接到ko的DOM处理中。ko会在遍历每个DOM节点时调用该函数一次,包括初次绑定UI,和后续的任意DOM子树被注入时。

通过向 bindingProvider
提供一个 preprocessNode

ko.bindingProvider.instance.preprocessNode = function(node) {
    // Use DOM APIs such as setAttribute to modify "node" if you wish.
    // If you want to leave "node" in the DOM, return null or have no "return" statement.
    // If you want to replace "node" with some other set of nodes,
    //  - Use DOM APIs such as insertChild to inject the new nodes
    //    immediately before "node"
    //  - Use DOM APIs such as removeChild to remove "node" if required
    //  - Return an array of any new nodes that you"ve just inserted
    //    so that Knockout can apply any bindings to them
}

例3:虚拟模板元素

用常规的语法在虚拟元素上使用模板时比较复杂。通过使用预处理,可以添加一个使用单注解的模板格式:

ko.bindingProvider.instance.preprocessNode = function(node) {
    // Only react if this is a comment node of the form <!-- template: ... -->
    if (node.nodeType == 8) {
        var match = node.nodeValue.match(/^s*(templates*:[sS]+)/);
        if (match) {
            // Create a pair of comments to replace the single comment
            var c1 = document.createComment("ko " + match[1]),
                c2 = document.createComment("/ko");
            node.parentNode.insertBefore(c1, node);
            node.parentNode.replaceChild(c2, node);
            // Tell Knockout about the new nodes so that it can apply bindings to them
            return [c1, c2];
        }
    }
}

然后可以在视图中这样使用模板:

<!-- template: "some-template" -->

KO.BINDINGPROVIDER.INSTANCE.PREPROCESSNODE(NODE)

在绑定处理前每个DOM节点都会调用该函数。它可以修改、移除或替换节点 node
。任何新节点回被立刻插入到 node
之前,并且如果有节点被添加或 node
被移除,该函数必须返回html文档中 node
位置的新的节点数组。

参考资料:

KnockoutJS Documentation

KNOCKOUTJS DOCUMENTATION-绑定(BINDINGS)-自定义绑定的更多相关文章

  1. Knockout应用开发指南 第五章:创建自定义绑定

    原文:Knockout应用开发指南 第五章:创建自定义绑定 创建自定义绑定 你可以创建自己的自定义绑定 – 没有必要非要使用内嵌的绑定(像click,value等).你可以你封装复杂的逻辑或行为,自定 ...

  2. WCF - 自定义绑定

    自定义绑定 当系统提供的某个绑定不符合服务的要求时,可使用 CustomBinding 类.所有绑定都是从绑定元素的有序集构造而来的.自定义绑定可以从一组系统提供的绑定元素生成,也可以包含用户定义的自 ...

  3. KnockoutJS 3.X API 第五章 高级应用(1) 创建自定义绑定

    您不仅限于使用内置的绑定,如click,value绑定等,您可以创建自己的绑定. 这是如何控制视图模型如何与DOM元素进行交互,并且为您提供了大量的灵活性,以便于以复用的方式封装复杂的行为. 注册绑定 ...

  4. Maven自定义绑定插件目标:创建项目的源码jar

    <build> <plugins> <!-- 自定义绑定,创建项目的源码jar --> <plugin> <groupId>org.apac ...

  5. 5.Knockout.Js(自定义绑定)

    前言 你可以创建自己的自定义绑定 – 没有必要非要使用内嵌的绑定(像click,value等).你可以你封装复杂的逻辑或行为,自定义很容易使用和重用的绑定.例如,你可以在form表单里自定义像grid ...

  6. 【Knockout】五、创建自定义绑定

    概述 除了上一篇列出的KO内置的绑定类型(如value.text等),你也可以创建自定义绑定. 注册你的binding handler ko.bindingHandlers.yourBindingNa ...

  7. WCF - 绑定后续之自定义绑定

    自定义信道基类 WCF是一个极具扩展性的通信框架 无论在服务模型层还是信道层 都具有很多扩展点 而信道层的扩展点主要体现在实现自定义信道以增强信道栈处理信息的能力 比如我们可以扩展信道层 实现一个自定 ...

  8. Maven-07: 插件的自定义绑定

    除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更多更富特色的任务. 一个常见的例子是创建项目的源码jar包.内置的插 ...

  9. (七)Knockout 创建自定义绑定

    创建自定义绑定 你可以创建自己的自定义绑定 – 没有必要非要使用内嵌的绑定(像click,value等).你可以你封装复杂的逻辑或行为,自定义很容易使用和重用的绑定.例如,你可以在form表单里自定义 ...

随机推荐

  1. Spring Boot Jar包转War包 部署到Tomcat下

    原文:https://my.oschina.net/sdlvzg/blog/1562998 我们都知道springBoot中已经内置了tomcat,是不需要我们额外的配置tomcat服务器的,但是有时 ...

  2. 什么是软件project?

    Normal 0 7.8 pt 0 2 false false false MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNo ...

  3. linux 监控CPU 内存情况

    htop

  4. NFS CIFS SAMBA 的联系和区别

    Common Internet File System, CIFS Server Message Block, SMB Network File System, NFS 在早期网络世界当中,档案数据在 ...

  5. 【tyvj五月有奖赛 暨Loi 55 Round #1】

    解题报告: 傻逼错误天天犯QAQ 第一题:简单DP,f[i][j]表示第 i 道题选 j 的最大得分,可以从f[i-1][j-1],f[i-1][j],f[i-1][j+1]转移过来,其实是可以滚动数 ...

  6. CF 327B. Hungry Sequence

    B. Hungry Sequence time limit per test 1 second memory limit per test 256 megabytes input standard i ...

  7. 原创BULLET物理的DEMO

    原创BULLET物理的DEMO 按空格和0,1,2,3,4,5,6会发射不同的刚体. 具体的使用说明: 鼠标右键按下并拖动         旋转视角WSAD                       ...

  8. Python的知识点 plt.plot()函数细节

    1.plt.plot(x,y,format_string,**kwargs) 转自点击打开链接x轴数据,y轴数据,format_string控制曲线的格式字串 format_string 由颜色字符, ...

  9. Android下的Junit测试

    Android SDK 1.5已经将JUnit包含进来了,用过一次,昨天晚上重新用的时候还出了一点问题,还是决定写一篇比较详细的文章,供大家和自己以后使用,写起来也挺方便的,Android下的Juni ...

  10. 系统出现bootmgr is missing解决方式,戴尔dellserver装系统须要特别注意的问题

    系统出现bootmgr is missing解决方式,戴尔dellserver装系统须要特别注意的问题 欢迎关注http://blog.csdn.net/aaa123524457 转载请注明出处: h ...