Custom binding for when foreach has finished rende

2019-07-13 18:53发布

I have an observable array with items bound to an ul. I need to calculate the width of the entire set of li items once the items have been added so I can set the width of the ul element to the total width of the child li elements.

How can I go about doing this with a foreach binding?

<div>
    <h2 data-bind="visible: items().length">Test</h2>
    <ul data-bind="foreach: { data: items, afterAdd: $root.myAfterAdd }">
        <li data-bind="text: name"></li>
    </ul>
</div>

And the JavaScript:

var viewModel = {
    items: ko.observableArray([
        { name: "one" },
        { name: "two" },
        { name: "three" }
        ]),
    myAfterAdd: function(element) {
        if (element.nodeType === 1) {
           alert("myAfterAdd");   
        }
    },
    addItem: function() {
        this.items.push({ name: 'new' });
    }
};


ko.applyBindings(viewModel);

// once foreach has finished rendering I have to set the width 
// of the ul to be the total width of the children!

I tried using afterAdd so I can 'update' the width of the ul after each element is added, unfortunately I have discovered that afterAdd does not fire for the initial items! It will only fire when pushing additional items...

Note that the content inside the li items will be of unknown width :)

See this fiddle for a sample scenario.

3条回答
女痞
2楼-- · 2019-07-13 19:28

I had a similar problem. You can use this to make that initial calculation:

ko.bindingHandlers.mybinding {
        init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            var foreach = allBindings().foreach,
                subscription = foreach.subscribe(function (newValue) {
                // do something here (e.g. calculate width/height, use element and its children)
                subscription.dispose(); // dispose it if you want this to happen exactly once and never again.
            });
        }
}
查看更多
成全新的幸福
3楼-- · 2019-07-13 19:28

After much 'googling' I have found the solution. The way to achieve this with a foreach is too register a custom event handler:

ko.bindingHandlers.runafter = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        setTimeout(function (element, value) {
            if (typeof value != 'function' || ko.isObservable(value))
                throw 'run must be used with a function';

            value(element);
        }, 0, element, value);
    }
};

This handler will fire whenever an update occurs (or just once if value is not an observable).

More importantly it will fire once the foreach loop has completed!

See here.

查看更多
趁早两清
4楼-- · 2019-07-13 19:29

You could do something like this :

var viewModel = {
    items: ko.observableArray([
        { name: "one" },
        { name: "two" },
        { name: "three" }
        ]),
    myAfterAdd: function(event) {
        if (event.originalEvent.srcElement.nodeType === 1) {
           alert("myAfterAdd");   
        }
    },
    addItem: function() {
        this.items.push({ name: 'new' });
    }
};


$('#bucket').bind('DOMNodeInserted DOMNodeRemoved', viewModel.myAfterAdd);

ko.applyBindings(viewModel);

Which bucket is the ul element

See fiddle

I hope it helps

查看更多
登录 后发表回答