0

Suppose I am rendering a bunch of posts.

{{#each posts}}
  {{>post}}
{{/each}}

I am getting a bunch of posts sorted by date.

Posts.find({}, {sort:{name: 1, date:-1}, limit: Session.get('limit')}).fetch()

I've got some reactive scrolling going on here as you'd expect from a mobile app. But the problem now is that when someone posts something new, everything shifts down. My scroll position maintains the same, but I'm no longer looking at the same post.

Any ideas how to get this to work?

EDIT:

I seems to me like the most elegant solution would be something like inverting the way we think of scrolling. If we sorted the posts the other way -- oldest at the top, then new posts would end up at the bottom and wouldnt mess up the scrollTop position.

EDIT 2:

I forgot to mention -- I don't know the height of the element I'm inserting. Else I could settle for and ugly brute force method.

Community
  • 1
  • 1
Chet
  • 18,421
  • 15
  • 69
  • 113
  • Just brainstorming here but if you use the collection hooks package you could have it trigger on scrollTo after Posts.find() function. Probably a simpler way thou. – Will Parker Mar 10 '15 at 09:23
  • I thought about using `Cursor.observe` but thats a huge pain and I'm sure it would have some jank to it. Check out my edit -- too long for here – Chet Mar 10 '15 at 09:56
  • one way you could do it is to make the posts slide in from the top (i.e. go from height 0 to 500px) and at the same time, make the page scroll at the same rate as the posts sliding in (i.e. make the posts slide in over 650ms, and scroll over 650ms as well) – josh Mar 10 '15 at 10:01
  • yes, but one more problem I should mention -- I don't know the height of the element I'm inserting... – Chet Mar 10 '15 at 10:03
  • You can use javascript to get the element heights, I know you can i jQuery sure there is a native JS equivalent. might just be .height actually – Will Parker Mar 10 '15 at 13:39

2 Answers2

0

Alright, I've been having the same issue and here is what I've come up with as a rough solution. Tell me if you find any holes in it.

Template.postsList.rendered = function (){
this.find('.wrapper')._uihooks = {
    insertElement: function (node, next){

        //hide newly added element, add a class to identify it as a post inserted 
        //after initial load.
        $(node).hide().insertBefore(next).addClass('insertedAfterLoad');

        //if it's at the top of the page, fade it in, otherwise it will stay hidden
        if (getDistance() === 0){
            $(node).fadeIn();
        } 
    }
},

//Continually watch the page while scrolling
$(document).scroll(function(){
        var scrollDistance = getDistance();

        //if any posts have the  'insertedAfterLoad' class, fade them into view.
        //this will only load new posts if the user scrolls back to the top
        if( scrollDistance === 0 && $('.post').hasClass('insertedAfterLoad') ){
            $('.insertedAfterLoad').fadeIn();
        }
});

//This will get the distance to the top of the page of any elements 
//inserted after the initial page load
var getDistance = function(){
        if( $('.post').hasClass('insertedAfterLoad') ){
            return $('.insertedAfterLoad').offset().top;
        }
    }
}
codycodes
  • 95
  • 8
0

I'm a bit of confused about what you really want to do, but if you want to have new posts on the bottom and scroll to them, you can do something similar to what I've been doing in this app using scrollTo for Meteor.

First, add scrollTo package into meteor app

$ meteor add natestrauser:jquery-scrollto

Inside template events for submiting posts from your form into collection, add setTimeout function with scrollTo that will fire few miliseconds after new post was added to collection and it will scroll to new post:

Template.posts.events({
    "submit .new-post": function (event) {
    var title = event.target.postTitle.value;
    var text  = event.target.postText.value;

    Posts.insert({
      title: title, 
      text: text, 
      date: new Date() // current time
    });

    setTimeout(function(){
        $(".posts-wrapper").scrollTo("max", 500); // change 500 miliseconds to whatever speed you want
    }, 100);

    // Clear form
    event.target.postTitle.value = "";
    event.target.postText.value = "";

    // Prevent default form submit
    return false;
  }
});

Template helper where we fetch posts, do sorting, etc.:

Template.posts.helpers({
    posts: function() {
        return Posts.find({}).fetch;
    }
}); 

Template with form to add posts and those posts rendered in div with class .posts-wrapper:

<template name="posts">
    <form class="new-post">
        <label>Post title: </label>
        <input type="text" name="postTitle">
        <label>Post text: </label>
        <input type="textarea" name="postText">
    </form>
    <div class="posts-wrapper">
        {{#each posts}}
            {{> post}}
        {{/each}}
    </div>
</template>

You can see this in action here and check the code inside this Meteorpad.

If you need scroll whole <body>, just use:

setTimeout(function(){
    $("body").scrollTo("max", 500); // change 500 miliseconds to whatever speed you want
}, 100);
Michal Takac
  • 37
  • 1
  • 6