ajaxcordovajquery-mobileknockout.jsiscroll

Refresh iScrollView after Knockout.js foreach


I am building a web app with jQuery Mobile/Knockout.js/iScrollView. I have a list which I'm filling using Knockout.js foreach. I make an ajax request, I get a JSON object, I push it into my observable array and my page updates by showing me everything the way I need it.

Now I want to use iScrollView so as the list grows I can have a nice smooth scroll down and take advantage of the pullup or pulldown functionality.

I cannot seem to figure out the proper way to call $(element).iscrollview('refresh') at the right time so that it adjusts the size of the scrolling window after more items have been added.

Is there a way to wait until the items are all inserted into the DOM and THEN call $(element).iscrollview('refresh').

Here's my HTML

<div data-role="page" id="List" data-bind="event: {pageshow: loadItems}">
      <div data-role="content" data-iscroll>
      <button value='load' data-bind="event: {click: loadItems}">load more stuff</button>
        <ul data-role="none" class="list" data-bind="foreach: items">
            <li style="visibility:hidden">
                <div class="header">
                    <h3 data-bind="text: name"></h3>
                    <p></p> 
                </div>
                <div class="feat-list-btn feat-list-action">
                    <img src="image.png" />
                </div>
                <div class="feat-list-btn feat-list-comments">
                    <img src="image.png" />
                </div>
                    <img data-bind="attr: {src: imageurl}" />
            </li>
        </ul>
      </div>
</div>

Here's how I'm getting the content:

function List(){
    var self = this;
    self.loadItems = function(){
        $('#List [data-role="content"]').fadeOut(0);
        $.mobile.showPageLoadingMsg();
            jQuery.ajax({
                url: mybackend.php',
                type: 'POST',
                data: 'howMany=10',
                dataType: 'jsonp',
                jsonp: 'jsoncallback',
                success: function(data, textStatus, jqXHR){
                        for (i=0;i<data.items.length;i++){
                            self.items.push(data.items[i]);
                            }   
                    },
                complete: function(){
                    $.mobile.hidePageLoadingMsg();
                    }
                });
        };
    self.items = ko.observableArray();
    self.updateScroller = function(){
        //????????
        };
    }

I can solve this by doing

setTimeout(function(){$('#List [data-role="content"]').iscrollview('refresh');}, 1000);

But I would rather not rely on a setTimeOut to get things like this done.


Solution

  • SO this was a lot simpler than I thought, I realized that what was happening was that my images were not yet loaded into memory which meant that the markup generated by Knockout had NOTHING where the images were supposed to be giving volume. I put in filler images taken from local source, but they won't necessarily have the right dimensions and I might end up having an inexact scroll height.

    What I tried and ultimately ended up working, was to preload the images before I added them to the ko.Observablearray. so I wrote this simpe image preloader as a jQuery plugin:

    (function($) {
        $.fn.preload = function( callback ) {           
            var count = 0,
                $this = this;
            for (i=0;i<this.length;i++){        
                var image = new Image();        
                $(image).on('load error', function(e){count++; if (count === $this.length){if ($.isFunction(callback)){callback.call()}}});
                image.src = this[i];;
                }
            }
        })(jQuery); 
    

    This will basically allow me to wait until all my images have been loading into memory

    Then I add to my ko.Observablearray, which inserts my markup WITH the images in place...and THEN call REFRESH like so:

            success: function(data, textStatus, jqXHR){
                var myImages = [];
                for (i=0;i<data.urls.length;i++){
                    myImages.push(data.urls[i]);
                    }
                    $(myImages).preload(function(){ //only execute insertion after all images are preloaded into memory
                        for (i=0;i<data.items.length;i++){
                            self.items.push(data.items[i]);
                            }   
                            $('#featList [data-role="content"]').iscrollview('refresh');//refresh iScroll
                            $.mobile.hidePageLoadingMsg(); 
                        });         
                },
    

    Than could probably be optimized some more but it's a working solution...