knockout virtual scrolling binding
Asked Answered
C

2

21

KOGrid uses virtual scrolling to render content dynamically. I'm looking for something like that but more generic so it could be used for ul lists, Bootstrap rows, whatever. I saw something called giga-scroll, but I think it's gone now. The Git is dead.

Has anyone seen a custom binding for dynamic content via virtual scrolling?

Cockscomb answered 19/7, 2013 at 14:20 Comment(7)
What do you mean by virtual scrolling?Suffragist
I've also seen it called infinite scrolling. The user scrolls to the bottom of the scroll bar and then more items are dynamically inserted into the page so that the user can continue scrolling down.Cockscomb
I've been working on something, but it shouldn't be that hard to implement yourself in a custom binding.Suffragist
would you like to share?Cockscomb
It isn't complete, but what sort of data would you want to be passing to the binding? You essentially need to do two main things. Show a subset of an array of data (this can be handled as a computed observable of an observableArray). The second part involves handling the scroll event on the element being scrolled on and detecting/handling the bottom of the scroll area.Suffragist
Uh... you mean this?Guaiacol
take a look this post knockmeout.net/2011/06/…Bastion
A
33

A simple solution without using a custom binding:

Fiddler Example: http://jsfiddle.net/adrienne/Y2WUN/

Markup:

<div>
    <span data-bind="text: items().length"></span>
    <img src="http://rniemeyer.github.com/KnockMeOut/Images/loading.gif" data-bind="visible: pendingRequest" />
</div>
<div id="main" data-bind="foreach: items, event: { scroll: scrolled }">
    <div data-bind="text: name"></div>
</div>

ViewModel:

var viewModel = {
    items: ko.observableArray([]),
    scrolled: function(data, event) {
        var elem = event.target;
        if (elem.scrollTop > (elem.scrollHeight - elem.offsetHeight - 200)) {
            getItems(20);
        }
    },
    maxId: 0,
    pendingRequest: ko.observable(false)
};


function getItems(cnt) {
    if (!viewModel.pendingRequest()) {
        //create fake data to pass to echo service
        var entries = [];
        for (var i = 0; i < cnt; i++) {
            var id = viewModel.maxId++;
            entries.push({
                id: id,
                name: "Name" + id
            });
        }

        viewModel.pendingRequest(true);

        $.ajax({
            type: 'POST',
            url: '/echo/json/',
            data: {
                json: ko.toJSON(entries),
                delay: .1
            },
            success: function(entries) {
                ko.utils.arrayForEach(entries, function(entry) {
                    viewModel.items.push(entry);
                });
                viewModel.pendingRequest(false);
            },
            error: function() {
                viewModel.pendingRequest(false);
            },
            dataType: 'json'
        });
    }
}

ko.applyBindings(viewModel);

getItems(20);

A different solution using a custom binding scrolling the entire browser window:

http://figg-blog.tumblr.com/post/32733177516/infinite-scrolling-knocked-out.

Fiddler example: http://jsfiddle.net/8x4vG/2/

Use the binding like so:

<div data-bind="foreach: collection">
    <div>
    <span data-bind="text: $index()"></span>
    <span data-bind="text: $data"></span>
    </div>
</div>
<div data-bind="scroll: collection().length < 160, scrollOptions: { loadFunc: addSome, offset: 10 }">loading</div>

With a View Model looking something like this:

var viewModel = function(){            
    this.collection = ko.observableArray([]) 
    var disney = ["Mickey", "Donald", "Daffy", "Hewie", "Dewie", "Lewie"]
    var self = this;

    this.addSome = function(){ 
        for(var i = 0; i < 40; i++){
            self.collection.push(disney[Math.floor((Math.random()*6))]) 
        }
    }

    this.addSome(); 
}

The binding implementation:

ko.bindingHandlers.scroll = {

  updating: true,

  init: function(element, valueAccessor, allBindingsAccessor) {
      var self = this
      self.updating = true;
      ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(window).off("scroll.ko.scrollHandler")
            self.updating = false
      });
  },

  update: function(element, valueAccessor, allBindingsAccessor){
    var props = allBindingsAccessor().scrollOptions
    var offset = props.offset ? props.offset : "0"
    var loadFunc = props.loadFunc
    var load = ko.utils.unwrapObservable(valueAccessor());
    var self = this;

    if(load){
      element.style.display = "";
      $(window).on("scroll.ko.scrollHandler", function(){
        if(($(document).height() - offset <= $(window).height() + $(window).scrollTop())){
          if(self.updating){
            loadFunc()
            self.updating = false;
          }
        }
        else{
          self.updating = true;
        }
      });
    }
    else{
        element.style.display = "none";
        $(window).off("scroll.ko.scrollHandler")
        self.updating = false
    }
  }
 }
Archoplasm answered 17/12, 2013 at 21:12 Comment(0)
C
5

I thought I would share some other scrollers I have found...

https://github.com/thinkloop/knockout-js-progressive-filter

https://github.com/thinkloop/knockout-js-infinite-scroll

Cockscomb answered 16/6, 2015 at 16:12 Comment(1)
Exactly what I was looking for. Thanks for sharing!Gagliano

© 2022 - 2024 — McMap. All rights reserved.