knockout - execute code after the last item has been rendered
Asked Answered
D

5

7

i'm using jquery quicksearch to search a table that is being populated by knockout foreach loop. the quicksearch element needs to be initiate after the foreach finishes. I've tried several approaches, but was unsuccessful so far.

I've tries using 'afterRender', but was unable to determine if the current item is the last item on the collection, i've also tried using a bindingHandlers, but then i got a collection of length 0 instead of length 2005.

so:

  1. what's the best approach to finding the last element in a foreach loop ?
  2. what's the best way to implement it in this specific scenario?

here's my view:

    <tbody data-bind="foreach: containers">
        <tr>
            <td><span data-bind="text: code"></span></td>
            <td><span data-bind="text: typeName"></span></td>
            <td><span data-bind="text: parentClient"></span></td>
            <td><span data-bind="text: client"></span></td>
            <td>
                <a data-bind="attr: { onclick: deleteUrl }">
                    <i class="icon-trash"></i>@delete </a>
                |
                <a data-bind="attr: { href: editUrl }">
                    <i class="icon-edit"></i>@edit</a>
                |
                <a data-bind="attr: { href: qrUrl }" target="blank">
                    <i class="icon-qrcode"></i>
                    @printQr
                </a>
            </td>

        </tr>
    </tbody>

and here's my knockout code:

function Container(data) {
        var self = this;
        self.id = ko.observable(data.Id);
        self.code = ko.observable(data.Code);
        self.typeName = ko.observable(data.TypeName);
        self.parentClient = ko.observable(data.ParentClient);
        self.client = ko.observable(data.Client);
        self.deleteUrl = ko.computed(function () {
            return "GetModal('/Containers/Delete/" + data.Id + "','containerModal');";
        });
        self.editUrl = ko.computed(function () {
            return '/Containers/Edit/' + data.Id;
        });

        self.qrUrl = ko.computed(function () {
            return '/Qr/Index/10?entity=' + data.Id;
        });
    }
 function ContainersViewModel() {
        var self = this;
        self.containers = ko.observableArray([]);
        self.counter = ko.computed(function () {
            return self.containers().length;
        });

        $.getJSON("/Containers/Json", function (data) {
            var containers = $.map(data, function (item) {
                return new Container(item);
            });

            self.containers(containers);
        });

    };
    ko.applyBindings(new ContainersViewModel());

Thanks a lot !

Nir

Drewdrewett answered 5/5, 2013 at 14:43 Comment(0)
T
4

The foreach binding afterRender option can do what you are looking for, the trick part is to be able to know that the element is the last one. That can be solved using the provided arguments like shown in http://jsfiddle.net/n54Xd/.

The function:

this.myPostProcessingLogic = function(elements, data) {
    if(this.foreach[this.foreach.length-1] === data)
        console.log("list is rendered");
};

Will be called for every element but the "if" will make sure the code only runs for the last element.

Tague answered 5/5, 2013 at 21:16 Comment(2)
thanks Ricardo!, i've tried it, but this expression: ' this.foreach[this.foreach.length - 1] 'in th 'if statement' always evaluates to undefined. any thoughts ?Drewdrewett
my bad - i accidently wrote the function just outside the viewmodel itself (a morning thing ..) thanks!Drewdrewett
Z
2

Try to subscribe manually on the array changes. By using subscribe you will be notified on array modification.

If you subscribe after applyBindings call, you will be notified after the refresh process of the view.

See fiddle

var ViewModel = function () {
    var self = this;

    self.list = ko.observableArray();

    self.add = function () {
        self.list.push('Item');
    }
    self.list.subscribe(function () {
        alert('before');
    });
};
var vm = new ViewModel();
ko.applyBindings(vm);

vm.list.subscribe(function () {
    alert('after');
});

I hope it helps.

Zillah answered 5/5, 2013 at 15:22 Comment(2)
Thanks Damien!, but that's not quite what i had in mind. your example will work only after adding or removing elements, but that's not something that i'll be doing with this collection. the way it is now, it comes from the db to the view, and just get displayed. nothing more. i need the event to be fired right after the last item get's rendered in the loopDrewdrewett
Hi Nir, I added a timer to simulate a webservice call as you can see it notifies after having rendered items. jsfiddle.net/bUJmu/2Zillah
C
2

I was successful using Ricardo Medeiros Penna's answer with just one little tweak.

    this.myPostProcessingLogic = function(elements, data) {
      if(this.foreach._latestValue[this.foreach._latestValue.length-1] === data)
        console.log("list is rendered");
    };

I had to add _latestValue to get the length value and now it is working as desired.

Caveman answered 14/6, 2017 at 13:38 Comment(1)
_latestValue being a private variable. Not sure if it would be safe to access it directly. So using this.foreach() instead. Not really a big difference tho this.myPostProcessingLogic = function(elements, data) { var underLyingArray = this.foreach(); if(underLyingArray[underLyingArray.length - 1] === data) console.log("list is rendered"); };Diseased
M
1

Use throttle extender, this way computed will be called once after you finished populating the array:

  self.counter = ko.computed(function () {
        return self.containers().length;
    }).extend({ throttle: 100 });
Meseems answered 5/5, 2013 at 21:8 Comment(1)
I love this answer. So simple and just what I needed to solve a similar issue of table column balancing happening after every row was rendered. Thanks!Traynor
R
1

Late addendum, but the if statement in Ricardo's answer above should be:

if (this.foreach()[this.foreach().length - 1] === data) {
    //Your post processing logic
}

as foreach is a function. Most likely why Nir weiner has an issue with it evaluating.

Rubbico answered 26/4, 2018 at 16:44 Comment(1)
well, it is kind of late (5 years, give or take), but seems legit. thanks!Drewdrewett

© 2022 - 2024 — McMap. All rights reserved.