Knockout在渲染项目后从自定义元素计算容器高度

时间:2015-06-26 12:20:13

标签: javascript knockout.js

我正在编写一个自定义绑定,其中包括在运行某些显示逻辑之前需要知道它的高度。内容以foreach绑定呈现。

通常情况下,KO会在渲染任何这些项目之前调用我的绑定init,这意味着高度不正确。

然后我使用{ controlsDescendantBindings: true }并在使用高度之前手动绑定子项;这在init

中工作正常

问题是,我希望绑定触发update并在项目更新时重新计算高度,如果我更改You cannot apply bindings multiple times to the same element,上面的模式会给我initupdate,这是可以理解的。

这是减少代码:

HTML:

<a href="#" data-bind="click: rerun">Re-run</a>
<hr/>
<div class="container" data-bind="test: true">
    <!-- ko foreach: Items -->
        <div class="item" data-bind="text: ID"></div>
    <!-- /ko -->
</div>
<hr/>
<b id="res"></b>

CSS:

hr {
    clear: both;
}

.item {
    width: 50px;
    height: 50px; 
    background: red;
    margin: 5px;
    display: inline-block;
}

CODE:

ko.bindingHandlers.test = {
    init: 
        function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
        {
            var val = ko.unwrap(f_valueaccessor());

            if(!!val)
            {   
                ko.applyBindingsToDescendants(bindingcontext, el);

                $('#res').html($(el).height());

                return { controlsDescendantBindings: true };
            }
        }
};

function Model(data, mapping)
{
    var _this = this;
    var _mapping = $.extend({}, {}, mapping);

    this.Items = ko.observableArray();        

    if(data)
        ko.mapping.fromJS(data, _mapping, _this);

    this.rerun = function(model, e)
    {
        _this.Items(_this.genItems());
    }

    this.genItems = function()
    {        
        var a = [];

        for(var i = 0; i < 10 + (Math.random() * 20); i++)
            a.push({ID:i});

        return a;
    }

    this.rerun();
}

var model = new Model();
ko.applyBindings(model);

小提琴:https://jsfiddle.net/whelkaholism/tzrxbojj/

我可以看到两种方法来解决这个问题:

  1. 将对象传递给包含函数引用的绑定,当项目被更改为强制重新计算时,模型可以调用该函数引用(这很好但很笨重)

  2. afterRender上使用foreach(这也可以,但会为模型添加代码)

  3. afterRender解决方案运行正常,可能会成为推荐的方法;但它确实意味着我必须将此添加到任何需要类似功能的模型,并且自定义绑定看起来像一个非常好的抽象。

    使用容器上的单个自定义绑定是否有一种好的(即优于#1)方式让它工作?

1 个答案:

答案 0 :(得分:1)

您的解决方案中缺少的主要内容是自定义绑定需要了解项目模型,以便订阅其更改并重新计算元素的高度。我也使高度成为可观察的解决方案,使其更像是Knockout的用途。

我的解决方案也不需要controlsDescendantBindings: true

HTML:

<a href="#" data-bind="click: rerun">Re-run</a>
<hr/>
<div class="container" data-bind="test: {heightValue: containerHeight, itemsModel: Items}">
    <!-- ko foreach: Items -->
        <div class="item" data-bind="text: ID"></div>
    <!-- /ko -->
</div>
<hr/>
<b id="res" data-bind="text: containerHeight"></b>

结合:

ko.bindingHandlers.test = {
    init: 
        function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
        {
            var data = f_valueaccessor();
            var recalculate = function () {
                setTimeout(function () {
                    data.heightValue($(el).height());
                }, 0);
            }
            data.itemsModel.subscribe(function () {
                recalculate();
            });

            recalculate();
        }
};

setTimeout为零有助于在渲染完成后触发高度重新计算。

除了为容器高度添加了observable之外,视图模型大致相同:

this.containerHeight = ko.observable();

JSFiddle