zoukankan      html  css  js  c++  java
  • KNOCKOUTJS DOCUMENTATION-绑定(BINDINGS)-自定义绑定

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

    自定义绑定

    注册自定义绑定

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

    ko.bindingHandlers.yourBindingName {
        initfunction(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
        },
        updatefunction(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 {
        updatefunction(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 {
        initfunction(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
        },
        updatefunction(element, valueAccessor, allBindings{
            // Leave as before
        }
    };

    DOM变化后更新监控属性

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

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

    ko.bindingHandlers.hasFocus {
        initfunction(element, valueAccessor{
            $(element).focus(function({
                var value = valueAccessor();
                value(true);
            });
            $(element).blur(function({
                var value = valueAccessor();
                value(false);
            });
        },
        updatefunction(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 {
        initfunction(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 {
        initfunction(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 { controlsDescendantBindingstrue };
        }
    };

    可以看到,绑定上下文有个 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 {
        initfunction(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 { controlsDescendantBindingstrue };
        }
    };

    这样, 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 {
        initfunction(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(randomIndex1);
                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 randomOrdertrue -->
        <div>First</div>
        <div>Second</div>
        <div>Third</div>
    <!-- /ko -->

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

    ko.virtualElements.allowedBindings.randomOrder true;

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

    ko.bindingHandlers.randomOrder {
        initfunction(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(randomIndex1);
                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 {
        initfunction(element, valueAccessor{
            var options = ko.unwrap(valueAccessor()),
                $el = $(element);
            $el.myWidget(options);
            ko.utils.domNodeDisposal.addDisposeCallback(elementfunction({
                // 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

  • 相关阅读:
    5,MongoDB 之 "$" 的奇妙用法
    4,MongoDB 之 $关键字 及 $修改器 $set $inc $push $pull $pop MongoDB
    3,MongoDB之数据类型
    2,MongoDB之增删改查及pymongo的使用
    1,MongoDB简介和安装
    19,Ubuntu安装之python开发
    18,Shell编程实战
    17,saltstack高效运维
    16.2,docker网络
    如何使用 window api 转换字符集?
  • 原文地址:https://www.cnblogs.com/amylis_chen/p/6959581.html
Copyright © 2011-2022 走看看