Waypoints infinite for loading chat messages when scrolled to top of scrollable element

786 Views Asked by At

I'm trying to load chat messages when the user scrolls to the top of the div (the div is scrollable, so the context isn't the window, it's the div). Also when the page loads the div is automatically scrolled to the bottom. I'm doing this in django and when running this code the waypoint is triggered when the user is scrolled to the bottom, but the div that should be triggering it (id=trigger-load) is above the viewport. I've tried adding a set timeout for the initialization of infinite_chat but that also didn't work. This is the code:

class ChatDetail(LoginRequiredMixin, generic.ListView):
    login_url = reverse_lazy('login')
    model = Message
    paginate_by = 20
    context_object_name = 'messages'
    template_name = 'index.html'
Html:
```
<div class="overflow-y-scroll  px-5 pt-5 flex-1 infinite-container-chat" id="chat-content">
         {% if page_obj.has_next %}
               {% include 'partials/common/loader.html' %}
         {% endif %} 
         <div class="" id="trigger-load"></div>
         {% include 'partials/friend_hidden_message.html' %}
         {% include 'partials/user_hidden_message.html' %}
                        
         {% for message in messages reversed %}
            {% if forloop.first %}
               <div class="infinite-item">
                    {% if user.id == message.user.id %}
                          {% include 'partials/user_message.html' %}
                    {% else %}
                          {% include 'partials/friend_message.html' %}
                     {% endif %}
               </div>
            {% else %}
                <div class="infinite-item">
                    {% if user.id == message.user.id %}
                        {% include 'partials/user_message.html' %}
                    {% else %}
                        {% include 'partials/friend_message.html' %}
                    {% endif %}
          </div>
            {% endif %}

  {% endfor %}
                        
                    
</div>
```
(function() {
    
    var $ = window.jQuery
    var Waypoint = window.Waypoint

    /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */
    function InfinitePrepend(options) {
      this.options = $.extend({}, InfinitePrepend.defaults, options)
      this.container = this.options.element
      
      if (this.options.container !== 'auto') {
        this.container = this.options.container
      }
      
      this.$container = $(this.container)
      this.$more = $(this.options.more)
      if (this.$more.length) {
        this.setupHandler()
        this.waypoint = new Waypoint(this.options)
      }
    }
  
    /* Private */
    InfinitePrepend.prototype.setupHandler = function() {
      this.options.handler = $.proxy(function() {
        this.options.onBeforePageLoad()
        this.destroy()
        this.$container.addClass(this.options.loadingClass)
        
        $.get($(this.options.more).attr('href'), $.proxy(function(data) {
          var $data = $($.parseHTML(data))
          var $newMore = $data.find(this.options.more)
  
          var $items = $data.find(this.options.items)
          if (!$items.length) {
            $items = $data.filter(this.options.items)
          }
  
          this.$container.after($items)
          this.$container.removeClass(this.options.loadingClass)
  
          if (!$newMore.length) {
            $newMore = $data.filter(this.options.more)
          }
          if ($newMore.length) {
            this.$more.replaceWith($newMore)
            this.$more = $newMore
            this.waypoint = new Waypoint(this.options)
          }
          else {
            this.$more.remove()
          }
  
          this.options.onAfterPageLoad($items)
        }, this))
      }, this)
    }
  
    /* Public */
    InfinitePrepend.prototype.destroy = function() {
      if (this.waypoint) {
        this.waypoint.destroy()
      }
    }
  
    InfinitePrepend.defaults = {
      context: $("#chat-content")[0],
      element: $("#trigger-load")[0],
      container: $("#trigger-load")[0], 
      
      offset: 0,
      items: '.infinite-item',
      more: '.infinite-more-link',
      loadingClass: 'infinite-loading',
      onBeforePageLoad: $.noop,
      onAfterPageLoad: $.noop
    }
  
    Waypoint.InfinitePrepend = InfinitePrepend
  }())
  ;
  
  $(function(){
    $(".infinite-container-chat").fadeIn(500).scrollTop($(".infinite-container-chat")[0].scrollHeight);
    if($('.infinite-container-chat')[0]){
        let infinite_chat = new Waypoint.InfinitePrepend({
            onBeforePageLoad: function () {
            $('.spinner-border').show();
            },
            onAfterPageLoad: function () {
            $('.spinner-border').hide();
            },
        })
    }
})

I want to trigger the waypoint when div with id trigger-load hits the top of the viewport (div with id chat-content). All the loaded items should be after the div that triggers the waypoint(I changed the append method to after).

1

There are 1 best solutions below

0
Bratix On

I figured out the issue. The code in the setupHandler didn't calculate the new waypoint in the way I was expecting. I simply changed the code to not destroy the current waypoint when adding items after the selected container. I only destroy it when there aren't any new items. I changed the js and html to be like this:

(function() {
    
    var $ = window.jQuery
    var Waypoint = window.Waypoint

    /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */
    function InfinitePrepend(options) {
      this.options = $.extend({}, InfinitePrepend.defaults, options)
      //this.container = this.options.element
      
      
      this.container = this.options.container
      
      
      this.$container = $(this.container)
      this.$more = $(this.options.more)
      if (this.$more.length) {
        this.setupHandler()
        this.waypoint = new Waypoint(this.options)
      }
    }
  
    /* Private */
    InfinitePrepend.prototype.setupHandler = function() {
      this.options.handler = $.proxy(function() {
        console.log("waypoint hit")
        this.options.onBeforePageLoad()
        //this.destroy() We want to use the same waypoint not create a new one
        this.$container.addClass(this.options.loadingClass)
        
        $.get($(this.options.more).attr('href'), $.proxy(function(data) {
          var $data = $($.parseHTML(data))
          var $newMore = $data.find(this.options.more)
  
          var $items = $data.find(this.options.items)
          if (!$items.length) {
            $items = $data.filter(this.options.items)
          }
  
          this.$container.after($items)
          this.$container.removeClass(this.options.loadingClass)
  
          if (!$newMore.length) {
            $newMore = $data.filter(this.options.more)
          }
          if ($newMore.length) {
            this.$more.replaceWith($newMore)
            this.$more = $newMore
            //this.waypoint = new Waypoint(this.options)
            // We don't need to create a new waypoint as we use the same one
          }
          else {
            this.destroy()
            //destroy the waypoint when there is no link to more items
            this.$more.remove()
          }
  
          this.options.onAfterPageLoad($items)
        }, this))
      }, this)
    }
  
    /* Public */
    InfinitePrepend.prototype.destroy = function() {
      if (this.waypoint) {
        console.log("destroyed waypoint")
        this.waypoint.destroy()
      }
    }
  
    InfinitePrepend.defaults = {
      context: $("#chat-content")[0],
      //context represents the scrollable div
      element: $("#trigger-load")[0],
      //this is the element which will trigger the waypoint when it hits the top of the div
      container: $("#after-this")[0],
      //I added this as the container so that I can add elements after it not inside of it,
      //so that the waypoint stays up above the newly loaded messages
      offset: -100,
      //this offset makes it so that when the element is 100px above the viewport (in this
      //case the scrollable div it will trigger the loading
      items: '.infinite-item',
      more: '.infinite-more-link',
      loadingClass: 'infinite-loading',
      onBeforePageLoad: $.noop,
      onAfterPageLoad: $.noop
    }
  
    Waypoint.InfinitePrepend = InfinitePrepend
  }())
  ;
  
  $(function(){
    
    if($('.infinite-container-chat')[0]){
        $(".infinite-container-chat").fadeIn(500).scrollTop($(".infinite-container-chat")[0].scrollHeight);

        setTimeout(() => {
            var infinite_waypoint = new Waypoint.InfinitePrepend({
                onBeforePageLoad: function () {
                    $('.spinner-border').show();
                },
                onAfterPageLoad: function () {
                    $('.spinner-border').hide();
                },
              })
            console.log(infinite_waypoint.waypoint)
        }, 1000);
    }
<div class="overflow-y-scroll  px-5 pt-5 flex-1 infinite-container-chat" id="chat-content">
        {% if page_obj.has_next %}
            {% include 'partials/common/loader.html' %}
        {% endif %} 
        <div class="hidden" id="trigger-load"></div>
        <div class="hidden" id="after-this"></div>
        {% include 'partials/friend_hidden_message.html' %}
        {% include 'partials/user_hidden_message.html' %}

        {% for message in messages reversed %}
          {% if forloop.first %}
              <div class="infinite-item">
                  {% if user.id == message.user.id %}
                      {% include 'partials/user_message.html' %}
                  {% else %}
                      {% include 'partials/friend_message.html' %}
                  {% endif %}
              </div>
          {% else %}
              <div class="infinite-item">
                  {% if user.id == message.user.id %}
                      {% include 'partials/user_message.html' %}
                  {% else %}
                      {% include 'partials/friend_message.html' %}
                  {% endif %}
              </div>
          {% endif %}

        {% endfor %}


</div>

The only issue with this code is that it triggers once while loading the page even when I delay the initialization of the waypoint after scrolling to the bottom of the div, but in my case that behavior is tolerable. If somebody finds a fix for this feel free to share. I also was a little lazy with the default options so I commented them out so that you know how to configure it yourself.