David Kleiman
David Kleiman

Reputation: 627

How do I scroll to the bottom of a div as data is added in Meteor?

I'm writing a messenger application in Meteor, and I want to set it up such that when either user types a message it scrolls down to the bottom of the div for both of them. I am storing the message in a list called messages in a conversation document in the collection Conversations. I am using cursor.observeChanges, and it seems that the callback fires before the data is rendered on the client side so it doesn't scroll all the way to the bottom.

Here is the html:

<template name="chat">  
    {{> chatBox}}
</template>

<template name="chatBox">
    <div class="chat-box">
        <div id="chat-messages">
            {{#each chatMessages}}
                <div class="individual-message">
                    {{message}}
                </div>
            {{/each}}
        </div>
        <form id="chat-input">
            <input class="add-message" autocomplete="off" placeholder="Write something..." name="text" type="text">
      </form>
    </div>
</template>

Here's the relevant css:

#chat-messages {
    overflow-y: scroll;
    width: 250px;
    height: 450px;
    padding: 15px;
    position: absolute;
}

And here's the js:

Tracker.autorun(function(){
    ...
    Conversations.find(conversationId).observeChanges({
      changed: function(id, fields){
         $("#chat-messages").scrollTop($("#chat-messages").prop("scrollHeight"));
      }
    });
});

Upvotes: 4

Views: 2120

Answers (2)

J&#252;rgen Fink
J&#252;rgen Fink

Reputation: 3535

Different Approach via tracking of Template helpers:

I took advantage of the Template helper, as it already tracks reactively all changes (and new) messages. Hence, there you can place your scroll-down command.

I assume you have something like this in your JS file:

chatBox.js

Template.chatBox.helpers({
    chatMessages: function() {
       return Conversations.find({conversationId: conversationId},
      {sort: {d: -1}, limit: 20}).fetch().reverse();
    },
});

(Being chatMessages.d the date of posting and conversationId your reactive variable for the specific chat-room, sorted by date from the end, and displayed by reverse order, so that last chat would appear at the end of your chat-messages div, limited to the last 20 messages)

Just add your scroll-down command there, hence you would have:

Template.chatBox.helpers({
    chatMessages: function() {
       //scroll down not animated
       $('#chat-messages').scrollTop($('#chat-messages').prop('scrollHeight'));
       return Conversations.find({conversationId: conversationId},
      {sort: {d: -1}, limit: 20}).fetch().reverse();
    }
});

Or make it animated for smooth scrolling:

Template.chatBox.helpers({
    chatMessages: function() {
       //scroll down with animation
       $('#chat-messages').animate({scrollTop: $('#chat-messages').prop('scrollHeight')}, 500);
       return Conversations.find({conversationId: conversationId},
      {sort: {d: -1}, limit: 20}).fetch().reverse();
    }
});

This would trigger your "scroll-down" at any change in your Conversations-Collection.

Hope this helps.

Upvotes: 0

CaptSaltyJack
CaptSaltyJack

Reputation: 16045

Whenever I run into an issue where Blaze hasn't had a chance to render something in time for a Javascript function to be evoked on it, I use Tracker.afterFlush. This waits until the render cycle is done before running some code, e.g.:

// Inside a Meteor event callback
Tracker.afterFlush(function () {
  var $someItem = $('....');

  $(window).scrollTop($someItem.offset().top);
});

http://docs.meteor.com/#/full/tracker_afterflush

Upvotes: 9

Related Questions