2

I'm trying to use Knockout to make the usage for an infinite scroll plugin I am using a bit nicer, but struggling with how to bind it. Not sure if it's even possible in the current form.

The scroller calls a data function which loads the next block of data via AJAX. It then calls a factory function that converts that data into HTML, and it then loads the HTML into the container, and updates its internal state for the current content size.

I'm stuck on the fact that it expects an HTML string.

What I want to do is this:

<div class="scroller" data-bind="infiniteScroll: { get: loadItems }">
    <div class="item">
        <p>
            <span data-bind="text:page"></span>
            <span class="info" data-bind="text"></span>
        </p>
    </div>
</div>

And my binding, which I'm completely stuck on, is this - which is currently just hardcoding the response, obviously - that's the bit I need to replace with the template binding:

ko.bindingHandlers.infiniteScroll = {
    init: 
        function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
        {
            if($.fn.infiniteScroll)
            {
                // Get current value of supplied value
                var field = f_valueaccessor();
                var val = ko.unwrap(field);

                var options = {};

                if(typeof(val.get) == 'function')
                    options = val;
                else
                    options.get = val;

                options.elementFactory = options.elementFactory || 
                    function(contentdata, obj, config)
                    {
                        var s = '';

                        for(var i = 0; i < contentdata.length; i++)                         
                        {
                            var o = contentdata[i];

                            // NEED TO REPLACE THIS
                            s += '<div class="item"><p>Item ' + o.page + '.' + i + ' <span class="info">' + o.text + '</span></p></div>';
                        }

                        return s;
                    };

                $(el).infiniteScroll(options);

                return { controlsDescendantBindings: true };
            }
        }
};

contentdata is an array of objects e.g. [ { page:1, text:'Item1' }, { page:1, text:'Item2' } ... ]

Page sizes may differ between calls; I have no way of knowing what the service will return; it is not a traditional page, think of it more as the next block of data.

So in the element factory I want to somehow bind the contentdata array using the markup in .scroller as a template, similar to foreach, then return that markup to the scroller plugin.

Note that I can modify the infinite scroller source, so if if can't be done with strings, returning DOM elements would also be fine.

I just can't get how to a) use the content as a template, and b) return the binding results to the plugin so it can update its state.

NOTE: The page I eventually intend to use this is currently using a foreach over a non-trivial object model; thus the need to use the same markup; it needs to be pretty much a drop in replacement.

Whelkaholism
  • 1,413
  • 1
  • 17
  • 26
  • Are you trying to specifically roll your own solution or are you just looking to get it working? – Pseudonym Mar 27 '15 at 12:28
  • For the project, I would be open to using an existing KO infinite scroll plugin, provided it can load each set of new items over AJAX. For my own knowledge, I'd like to know how to do this; so while a link to an existing plugin would be handy, as an actual answer I'd like to know how to do it myself. – Whelkaholism Mar 27 '15 at 12:38
  • http://stackoverflow.com/questions/21932373/what-is-the-correct-way-to-implement-infinite-scroll-in-knockout Maybe that might help? – Pseudonym Mar 27 '15 at 13:01
  • 1
    Hmmm, it might. Seems to work okay. Does look like I might be better off ditching the existing scroll code and implementing a pure KO solution based on that code, I'll give it a go. – Whelkaholism Mar 27 '15 at 13:43
  • Okay, make sure to upvote that answer if it was helpful – Pseudonym Mar 27 '15 at 14:39

1 Answers1

0

I have actually found out how to do it using the existing scroller following this question: Jquery knockout: Render template in-memory

Basically, you use applyBindingsToNode(domelement, bindings), which will apply KO bindings to a nodeset, which importantly does not have to be connected to the DOM.

So I can store the markup from my bound element as the template, then empty it, then for the element factory, create a temporary node set using jQuery, bind it using the above function, then return the HTML.

Admittedly, this would probably be better off refactored to use a pure KO scroller, but this means I can continue to use the tested and familiar plugin, and the code might help people as this seems to be quite a common question theme.

Here is the new code for the binding (markup is as above).

ko.bindingHandlers.infiniteScroll = {
    init: 
        function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
        {
            if($.fn.infiniteScroll)
            {
                // Get current value of supplied value
                var field = f_valueaccessor();
                var val = ko.unwrap(field);

                var options = {};

                if(typeof(val.get) == 'function')
                    options = val;
                else
                    options.get = val;

                var template = $(el).html();

                options.elementFactory = options.elementFactory || 
                    function(contentdata, obj, config)
                    {
                        // Need a root element for foreach to use as a container, as it removes the root element on binding.
                        var newnodes = $('<div>' + template + '</div>');

                        ko.applyBindingsToNode(newnodes[0], { foreach: contentdata });

                        return newnodes.html();                         
                    };

                $(el)
                    .empty()                
                    .infiniteScroll(options);

                return { controlsDescendantBindings: true };
            }
        }
};
Community
  • 1
  • 1
Whelkaholism
  • 1,413
  • 1
  • 17
  • 26