1

This problem probably has been asked before, but I cannot seem to figure out an easy way to do it.

I have a Meteor page that shows messages posted by users in chronological order (newest message at the bottom). I want the page to:

#1) Subscribe to the messages (using publish/subscribe) based on a parameter supplied in the URL

#2) Render the page

#3) Display the messages on the page

#4) Scroll to the bottom.

There's no apparent way to know when 1, 2, and 3 are complete before initiating the scroll to bottom. Meteor does have an observe/added function to do an event when new messages are added to a subscription, however that's only when documents are insertted into Mongo, and it does not trigger when displaying results to the initial subscription.

I'll show code for the steps above:

#1: Subscribe to the messages using publish/subscribe: in /client/messages.js

Template.messages.created = function() {
  this.autorun( function() {
    this.subscription = Meteor.subscribe("messages", Router.current().params.category);
  }.bind(this));
};

#2 Render the page, in /client/messages.html

<template name="messages">
  {{#each messages}}
    {{messageText}}<br><br>
  {{/each}}
</template>

#3: Display the mssages on the page: /client/messages.js

Template.messages.helpers({
  messages: function() {
    var category = Router.current().params.category;
    return Messages.find({category: category}, { sort: { messageDate: 1 } });
  },
});

All this works, but does not automatically scroll to the bottom.

I cannot add a jquery scroll command to the Meteor onRendered section because it runs BEFORE the data is written to the DOM. I can do a Meteor.setTimeout to wait 1 second before scrolling to the bottom, but does not work if it takes longer than a second to fill the DOM with subscribed data.

Here's another thing complicating the matter. I am supplying the category variable in the URL. When the client selects another category, using Meteor/Blaze pathFor,

{{pathFor 'messages' channelid='new'}}

the browser does not reload/rerender the page, it simply updates the URL parameter which triggers the autorun to change what messages it has subscribed to. It simply writes the new data to the DOM. Because of this, I cannot to a jquery $(document).ready to detect whether the page is ready (because the page is always ready), and I cannot use some fancy handlebars thing like {{scrollToBottom}} at the end of my {{#each}} in messages.html because that it not re-run.

So, is there a simple way to detect when Meteor/Blaze completely finishes writing new data to the browser?

Crash Override
  • 1,337
  • 3
  • 13
  • 26
  • I suspect the answer to this question is basically the same as my answer to [this question](https://stackoverflow.com/questions/31187876/how-do-i-run-client-side-code-after-a-new-collection-item-has-been-added-to-a-me). Rendering isolation nearly always boils down to adding an `onRendered` to a sub-template. – David Weldon Jul 06 '15 at 22:22
  • `Template.messages.rendered = function() { ... }` – sbeliv01 Jul 06 '15 at 22:23
  • @sbeliv01 and OP, FYI you two seem to be using old Meteor code (that's fine - your app might just be built using an earlier version, but just an FYI). Now it's `Template.messages.onRendered(function(){ //stuff })` - http://docs.meteor.com/#/full/template_onRendered – fuzzybabybunny Jul 06 '15 at 23:29
  • 1
    Template.messages.onRendered and Template.messages.rendered seem to do the exact same thing, at least to me. Also FYI that Meteor's "onRendered" does not mean when the html/blaze is fully rendered with data. It is when the DOM is done rendering the page without data. That way, the page appears fast, and the data is filled in after. I want to know if there is a trigger when the data is done being filled in, as in, when the data cursor reaches its end for now. – Crash Override Jul 07 '15 at 00:08

1 Answers1

-1

If I understand correctly, you really just want to know when all of your data is published from the server to the client (so you can then do something, in this case, scroll to the bottom).

All calls to Meteor.subscribe() return a subscription handle. This has a ready() method, that tells you all the data has been sent. This includes those done at a template level (which you might want to consider if appropriate for your use-case). More information here: http://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe

There are several ways to use this - you need to decide what is appropriate for you. As ready() is reactive, you can just use this. However, you might find it easier to implement an onReady callback within your subscription (see documentation above).

I suspect you will need to implement an autorun within your template rendered. This is to avoid the reverse scenario where the data arrives before the rendering (and so the callback fires too early). Something like this should do the trick (noting you have already set your subscription handle in your template creation function to this.subscription):

Template.messages.onRendered(function() {
  var self = this;
  this.autorun(function(computation) {
    if(self.subscription.ready()){
      $(document).scrollTop($(document).height());
      computation.stop()
    }
  });
});

This should ensure both that the function is only called after rendering and the data from the subscription is complete. The autorun is also stopped after executing the scroll to stop continued calling should new documents cause ready() to no longer be truthy (and then become ready again) - which might surprise people with the page re-scrolling to the bottom!