knockoutjs afterRender function in foreach binding

Viewed 15306

I'm trying to specify an entrance effect on elements being inserted using a knockoutjs foreach binding. Very simple setup:

myViewModel.myObservableArray.push({enter:function() { ... });

and in the markup:

foreach:{data:myObservableArray, afterRender:enter}

seems like it should work... right? But it doesn't find the enter function on the item. What I've found does work is:

myViewModel.enter = function(something, item) { item.enter(); };

foreach:{data:myObservableArray, afterRender:$root.enter}

adding an enter function to the root view model and binding afterRender to $root.enter. Enter is then passed the item as its second param so can in turn call the item's enter function, but it feels like a hack.

Can anyone explain what's going on here?

Thanks.

EDIT:

To clarify I've created a fiddle.

What this does is very simple, and is covered in more depth in the animated transitions example. It's running a function in the root view model for each dom element that's inserted using the foreach binding.

So the question is: what if I want item specific afterRender, afterAdd or beforeRemove functions? I could see this being useful. Especially if using the template binding to dynamically select a template (note 4). Is there a clean way of doing this? Right now I've got an enter function in the view model's root that simply calls the enter function on the item, but like I said above this feels like a hack.

3 Answers

Found another workaround without timeout, this technique is based on virtual element <!-- ko if: $parent.afterRender($index()) --><!-- /ko -->

function ReservationsViewModel() {
    // Editable data
    this.seats = ko.observableArray([
        { name: "Steve", meal: "Standard (sandwich)", price: 343},
        { name: "Steve", meal: "Premium (lobster)", price: 10},
        { name: "Steve", meal: "Ultimate (whole zebra)", price: 290}
    ]);

    this.afterRender = (i) => {
       // checking element rendered is last
       if (i === this.seats().length - 1) { 
           console.log('rendered');
           // our after rendered logic goes here...
       }
    };
}

And it's template is

<tbody data-bind="foreach: seats">
    <tr>
        <td data-bind="text: name"></td>
        <td data-bind="text: meal"></td>
        <td data-bind="text: price"></td>
    </tr>    
    <!-- ko if: $parent.afterRender($index()) --><!-- /ko -->
</tbody>

This extra logic i === this.seats().length - 1, will check last row is rendered.. then we can execute our afterRender logic inside.

Related