4

I have a list of Comments. These comments have an attribute called "vote" (users can vote comments) and they are initially sorted by votes (descending, onRender).

Right now, when users vote, the order of the comments is reactively updated to reflect the new votes.

Is it possible to somehow keep the initial sorting order intact? I would like to avoid confusing the user with comments automatically swapping order while she/he is on the page.

Is there any good way to solve this? I was thinking perhaps of a one-time sort when rendering the page, or somehow saving the order and then reapplying it whenever the collection is reactively refreshed.

Cos
  • 1,649
  • 1
  • 27
  • 50
  • it's an interesting problem. let's say you figure out a way to do it and now a new Comment shows up. where does it go in the list? – zim Feb 06 '17 at 16:13
  • I would do a method call to pull in the initial collection sorted the way you want it. Then I'd also have a subscription that checks if there was updates in the database and notify the user of them similar to how twitter does it "2 new comments" and have that UI element on click call the meteor method again. – 416serg Feb 06 '17 at 16:23
  • @zim Either at the very end of the list, or not at all. If I think about it, the only reason I need to keep reactivity at all is because I want a user's new votes to be reflected on the page. He doesn't need to see new or edited comments or other user's new votes right away. So perhaps an easy solution for me would be to 'fake it' by simply increasing the vote count client-side. The user would hardly notice the difference. But still, for the sake of knowledge, I'd still like to know if the proposed scenario (with new comments at the end of the list) would be doable. – Cos Feb 06 '17 at 16:32

3 Answers3

4

You could use a function to sort your Minimongo query. So something like:

const initialVotes = new Map();
Comments.find({}, {sort: (a, b) => {
  if (!initialVotes.has(a._id)) initialVotes.set(a._id, a.votes);
  if (!initialVotes.has(b._id)) initialVotes.set(b._id, b.votes);

  return initialVotes.get(b._id) - initialVotes.get(a._id);
});

This will make it so that comments are sorted by initial votes. If anything else changes (like user edits the comments), that will reactively propagate, if a new comment is made, it will reactively be added. But if votes change, order will not change (but the vote number maybe rendered will still update).

Mitar
  • 6,756
  • 5
  • 54
  • 86
2

You can do a non-reactive find with the reactive: false option, ex:

Comments.find({},{sort: {vote: -1}, reactive: false});

If you want to append new comments after the original list then I would render two lists of comments, one right after the other. First render the sorted list of existing comments then reactively render the list of comments that were created after the time of initial rendering in creation order. The second list will initially be empty and will not render at all but eventually new comments will appear there with their own vote counts.

Now, since the cursor above is non-reactive, how do we go about getting the vote counts to update reactively without changing the order? Answer: (in the case of Blaze) use a reactive helper!

Template.myTemplate.helpers({
  currentVote(){
    return Comments.findOne(this._id).vote;
  }
});

This is actually surprisingly cheap from a performance pov.

Michel Floyd
  • 18,793
  • 4
  • 24
  • 39
  • If I make it non-reactive, user's new votes are not visible on the page. As I mentioned in a comment above, this could be one solution, if I then simply increase the displayed vote count on the client side. But for the sake of the question, could I make it so comment changes are still reactive (with the exception of the sort order and even, if necessary, ignoring new comments)? – Cos Feb 08 '17 at 07:00
  • You could use a reactive *helper* to display the vote counts. See updated answer. – Michel Floyd Feb 09 '17 at 00:52
1

since you don't care about new Comments, only new votes, i would do the initial population with a Meteor.call() sorted the way you want, as mentioned by @sdybskiy. then i would set up a subscription for those comments to get the vote count.

in the onCreated() of your template, you could set up a subscription like this:

let voteCursor = Votes.find({commentIds: comments});
    voteCursor.observe({
        added: function(newDocument, oldDocument) {
            // the published vote would have a commentId, so here you
            // would go to your client store for the comments, find the 
            // comment, and increment the count
        },
        removed: function(oldDocument) {
            // same idea here, but process a vote being removed
        }
    });

notice i'm passing in the ids of all the comments returned from the method, so the publish would publish the votes only for those comments.

zim
  • 2,386
  • 2
  • 14
  • 13
  • Oh, perhaps I didn't make this bit clear: The Votes are not their own collection, but a simple number field which is part of the Comments collection. – Cos Feb 07 '17 at 06:56
  • @Cos, aha, nevermind then! it looks like Michel Floyd's answer is one you want. – zim Feb 07 '17 at 17:13