Meteor #each loop ready

2020-06-06 05:32发布

I wonder is there any way to know if #each loop is "ready". By "ready" I mean it rendered all nodes and inserted into the DOM. I don't even speak about onRendered callback (old rendered). I tried

<template name="myTemplate">
<div class="some-class">
    {{#if Template.subscriptionsReady}}
       {{#each messages}}
          <div>{{text}}</div>
       {{/each}}
       <script>
           $(".some-class").trigger("LOOP_READY")
       </script>
    {{/if}}
</div>
</template>


Template.myTemplate.onRendered(function(){
    this.$(".some-class").on("LOOP_READY", doSomething)
})

But it does not work either. I don't want to use timers.

UPDATE

messages is a collection of text messages for current dialog, dialogId is a reactive var stored in the Session.

Template.myTemplate.onCreated(function(){
    var self = this;
    self.autorun(function(){
        self.subscribe("messages", Session.get("dialogId"))
    })
})

So if someone changes dialogId, myTemplate loads another dialog messages, and I want to know when this messages are ready, to scroll to a specific message. Simple onReady does not work, since I can not call scrollTop accurately before all messages are rendered and got its own height.

2条回答
小情绪 Triste *
2楼-- · 2020-06-06 06:01

@saimeunt provided elegant solution, but I implemented it other way, maybe someone find it helpful too.

Since I don't want my code been executed everytime a new message arrives, I can not trigger scrolling after autorun is finished. (You would say, OK, do it in autorun that depends only on dialogId. - then I can not get exact message count of this dialog since subscription should take some time)

HTML

<template name="myTemplate">
  <div class="chat">
    {{#if Template.subscriptionsReady}}
      <div class="messages">
          {{#each messages}}
            <div class="message">{{text}}</div>
          {{/each}}
      </div>
      <script>$(".chat").trigger("MESSAGES_READY_EVENT")</script>
    {{/if}}
  </div>
</template>

JS

Template.chat.onCreated(function () {
    var self = this;
    self.messages = function() {
        return Messages.find({dialogId:Session.get("dialogId")})
    }
    self.autorun(function(){
        self.subscribe("messages", Session.get("dialogId"));
    })
});
//helpers skipped
Template.chat.onRendered(function () {
    var self = this;
    self.$(".chat").on("MESSAGES_READY_EVENT", function () {
        var computationNumber = 0;
        var $messages = self.$(".messages");
        //how many massages we have to render
        var total = self.messages().count();
        //max tries
        var maxTries = 10;
        var intervalId = Meteor.setInterval(function () {
            var totalNodes = $messages.children().length;
            if (computationNumber++ >= maxTries || total <= totalNodes) {
                Meteor.clearInterval(intervalId);
                $messages.scrollTop(someValue);
            }
        }, 100);
    });
});
查看更多
该账号已被封号
3楼-- · 2020-06-06 06:11

There's no easy way to get notified when a Spacebars {{#each}} block has done rendering into the DOM every item its spanning.

The best solution is to use another reactive computation (Tracker.autorun) to observe your messages cursor changes.

Everytime your messages list is modified, you can run arbitrary code after every other reactive computations are done performing whatever their job is, using Tracker.afterFlush.

The {{#each}} block is one of those computations, whose role is to listen to the reactive data source you give it as argument and rerender its Template.contentBlock as many times as items fetched from the source being iterated over, with the current item as current data context.

By listening to the exact same reactive data source as the {{#each}} block helper and running your code AFTER it has finished its own reactive computation, you can get the actual requested behavior without relying on some weird setTimeout tricks.

Here is the full implementation of this pattern :

HTML

<template name="myTemplate">
  <div class="some-class">
    {{#if Template.subscriptionsReady}}
      {{#each messages}}
        <div class="message">{{text}}</div>
      {{/each}}
    {{/if}}
  </div>
</template>

JS

// declare your reactive data source once to reuse the same in multiple places
function messagesCursor(){
  return Messages.find();
}

Template.myTemplate.helpers({
  messages: messagesCursor
});

Template.myTemplate.onRendered(function(){
  this.autorun(function(){
    // we need to register a dependency on the number of documents returned by the
    // cursor to actually make this computation rerun everytime the count is altered
    var messagesCount = messagesCursor().count();
    //
    Tracker.afterFlush(function(){
      // assert that every messages have been rendered
      console.log(this.$(".messages") == messagesCount);
    }.bind(this));
  }.bind(this));
});
查看更多
登录 后发表回答