Stop a foreach binding when the underlying observableArray is updated

383 views
Skip to first unread message

Jesse Garrison

unread,
Dec 15, 2014, 11:34:41 PM12/15/14
to knock...@googlegroups.com
I have a situation where I would like to stop the rendering of a foreach binding in my view when the underlying ko observableArray that it is iterating through is updated. Is this possible?

To expand on why I'm trying to do this, in my Durandal.js app I have run into a situation where I am iterating over a large (up to 400 or more) list of items and creating a child view/viewmodel for each item. These child views can take a while to render and there are pages of results to show. If I want to skip to the next page of results I am running into an error (similar to this one: (https://groups.google.com/forum/#!searchin/knockoutjs/containerNode.parentNode/knockoutjs/LNU_MrdGmDU/L1zou71YyzMJ) where the console is outputting: "Uncaught TypeError: Cannot read property 'insertBefore' of null". In this case the containerNode.parentNode is null. The error is being thrown in knockout.js's virtualElements 'prepend' method:

prepend: function(containerNode, nodeToPrepend) {
   
if (!isStartComment(containerNode)) {
       
if (containerNode.firstChild) containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
       
else containerNode.appendChild(nodeToPrepend);
   
} else {
       
// Start comments must always have a parent and at least one following sibling (the end comment)
        containerNode
.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
   
}
},


I think that this issue is being caused because when I am navigating to the next page of results I am replacing the observableArray with a new set of results, but the current set of results has not finished running through the foreach loop in my view. Here is a bit of my code to help clarify:
From my html (view), this is a block of code that is bound to the photos observableArray in my viewmodel and is running ko compose on each item in the array
<div id='photosContainer' data-bind="visible: !video_mode_on(), foreach: photos">
   
<!-- ko compose: $data --><!--/ko-->
</div>


From my javascript (viewmodel), this is the function that is used to replace the contents of the photos observableArray being bound to in the html (view)
newHome.prototype.getPhotos = function() {
       
var self = this;
       
       
var defs = [];
       
var arr = [];
       
var args, newArr;
   
       
self.rawPhotos().forEach( function( set ) {
            defs
.push( self.some_more_all( set.mf, set.images ) );
       
});
        $
.when.apply($, defs).done(function( res ) {
            args
= Array.prototype.slice.call(arguments, 0);
            newArr
= args.sort();
           
            newArr
.forEach( function( set ) {
               
set.arr.forEach( function( p ) {
                    arr
.push( self.addPhoto( p, self.mfOwnedByViewer( set.mf ) ? { ownedByViewer: true } : { ownedByViewer: false, owner_uuid: set.mf.owner_uuid } ) );
               
});
           
});
           
           
self.photos( arr );
       
});
   
};


Any ideas on what I can do to avoid this error would be greatly appreciated! I have spent hours trying to come up with a solution, but have yet to come up with anything.

Thank you for any ideas/input you can provide!
Jesse

Michael Best

unread,
Dec 16, 2014, 3:35:53 PM12/16/14
to knock...@googlegroups.com
Can you try replacing your virtual element with a normal element?

<div id='photosContainer' data-bind="visible: !video_mode_on(), foreach: photos">
    <div data-bind="ko compose: $data"></div>
</div>

-- Michael

Jesse Garrison

unread,
Dec 16, 2014, 3:58:21 PM12/16/14
to knock...@googlegroups.com
Wow Michael, you're a life saver! From what I can tell it looks like that solved my issue. I never realized I could use the compose binding with normal elements, and I wouldn't have expected it to act much differently than virtual elements anyway. It appears that by replacing my virtual elements with a normal div like the following results in ko not throwing any errors.
<div id='photosContainer' data-bind="visible: !video_mode_on(), foreach: photos">
   <div data-bind="compose: $data"></div>
</div>
Thanks a lot Michael, I really appreciate the help!

Jesse 

Michael Best

unread,
Dec 17, 2014, 4:30:35 PM12/17/14
to knock...@googlegroups.com
The documentation shows many examples of using compose with normal elements: http://durandaljs.com/documentation/Using-Composition.html

I'm not too familiar with Durandal, but my guess is that the problem you found happens because it's not correctly handing cleanup of the compose binding when the foreach is updated (or the cleanup is delayed) so that the binding tries to update the element after it's detached. This won't be a big issue with a normal element, but updating a virtual element requires it to have a parent.

-- Michael

Jesse Garrison

unread,
Dec 19, 2014, 2:53:05 PM12/19/14
to knock...@googlegroups.com
Very true! I've been working on my app for so long that I didn't even realize that I've used the compose binding on a normal <div> element before in a couple of places. I just didn't think that there would have been a huge difference between a normal element VS a virtual element. Thinking about it now it makes sense how this error could happen, but I'm fairly certain I never would have suspected that was the cause of my issue. I'm so glad you pointed me in the right direction on that!

Thanks again Michael.
Reply all
Reply to author
Forward
0 new messages