Callback when all Template items finished rendering in Meteor?

前端 未结 7 971
-上瘾入骨i
-上瘾入骨i 2020-12-19 09:30

I\'m getting a collection of records and placing them in a Template, having them rendered {{#each}} and I want to display a loading icon until the last DOM node

相关标签:
7条回答
  • 2020-12-19 10:03

    In my case, a partial solution is this:

    Add the onComplete() callback to the subscriptions of my Items collection, which is the official end of any loading process. So, no more hacky setTimeouts in Template.rendered, just set loading_stuff to true when data is queried (in Template.stuff.items) and then to false in the onComplete() callback in the subscribe function:

    Server:

    Meteor.publish('items', function (some_param) {
        return Items.find({some_field: some_param});
    });
    

    and Client:

    Meteor.subscribe('items', some_param, function onComplete() {
        Session.set('loading_stuff', false);
    });
    

    ...

    Template.stuff.items = function () {
        Session.set('loading_stuff', true);
        return Items.find({owner: Meteor.userId()}, {sort: {created_time: -1}});
    };
    

    It's a partial solution because knowing when the data has arrived from the server and when the DOM has finished rendering are two separate issues.

    0 讨论(0)
  • 2020-12-19 10:06

    You can use a jquery plugin like livequery to follow-up dom insertions for a specific selector:

    $('.my-rendered-element').livequery(function () {
        console.log('I've been added to the DOM');
    });
    

    If you also need to make sure images have been loaded and everything is well rendered you can use some other utility like this other plugin imagesLoaded, and do something like:

    $('.my-rendered-element').livequery(function () {
        $(this).imagesLoaded(function() {
            console.log('My images have been loaded');
        });
    });
    

    It's a workaround and it maybe doesn't integrate well with Meteor, but I found these 2 guys very useful in many situations.

    0 讨论(0)
  • 2020-12-19 10:06

    I find it helpful to run a callback in the onRendered block of the next template:

    <template name="stuff">
      {{#each items}}
        <div class="coolView">{{cool_stuff}}</div>
      {{/each}}
      {{> didMyStuffLoad}}
    </template>
    

    then:

    Template.didMyStuffLoad.rendered = function () {
      Session.set('loading_stuff', false);
    }
    

    Everything will be loaded into the DOM before this onRendered block executes.

    0 讨论(0)
  • 2020-12-19 10:11

    I found that JQuery's livequery plugin doesn't work when waiting for elements from a template to be written to the DOM. This is because the plugin listens on JQuery methods like append, but Meteor doesn't use JQuery. It uses an object called LiveRange to write to the DOM. I modified my livequery JavaScript file to work with this.

    Near the bottom of the file, there is a line that calls registerPlugin(). I modified it to look like this:

    $.livequery.registerPlugin([
        {object: $.fn, methods: ['append', 'prepend', 'after', 'before', 'wrap', 'attr',    'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html']},
        {object: LiveRange.prototype, methods: ['insertBefore', 'insertAfter']} 
    ]);
    

    Then I modified the registerPlugin() method to look like this:

    registerPlugin: function(registration) {
        for (var i = 0; i < registration.length; i++) {
            var object = registration[i].object;
            var methods = registration[i].methods;
    
            $.each( methods, function(i,n) {
                // Short-circuit if the method doesn't exist
                if (!object[n]) return;
    
                // Save a reference to the original method
                var old = object[n];
    
                // Create a new method
                object[n] = function() {
                    // Call the original method
                    var r = old.apply(this, arguments);
    
                    // Request a run of the Live Queries
                    $.livequery.run();
    
                    // Return the original methods result
                    return r;
                }
            });
        }
    }
    

    This will check to see if the element exists when insertBefore() or insertAfter() is called on the LiveRange object and will still work with JQuery as it did before.

    0 讨论(0)
  • 2020-12-19 10:14

    There are a few options:

    1. As you mentioned in your final comment, you could create your reactive context and handle changes there.
    2. You could create a special value, ie. _id = "END" and look for that value in the template rendering.
    3. My favorite: instead of using a collection, populate the template as a result of a Meteor.call and Meteor.methods call pair. You could also put the result in a reactive variable so the template automatically re-renders.

    As you may have already discovered, the onReady event of subscribe() fires when the publish method calls ready(), not when all the data has shipped over.

    Here's a simple example of #3 above:

    In the client:

      Meteor.call('get_complete_email',id, function(err,doc) {
        if (err === undefined) {
          // Note current_compose is reactive
          current_compose.set(new _ComposePresenter(action, doc));
        } else {
          log_error('compose','get complete email ',err);
        }
      }); 
    

    In the server:

     Meteor.methods({
       'get_complete_email': function(id) {
         return Emails.findOne({_id:id, user_id:Meteor.userId}, body_message_fields);
       }
     });
    

    In your presenter or viewer code: (the data temp variable could be eliminated - it's legacy and hasn't been refactored out yet).

    Template.compose_to_cc_bcc_subject.prefilled = function(part) {
      if (Current && Current.compose()) { 
        var data = Current.compose();
        if (data == undefined || data[part] == undefined) { return; }
        return data[part]; 
      } else {
        return;
      }
    }  
    

    Obviously, you'll need to wire up current_compose, Current and your own objects a little differently.

    0 讨论(0)
  • 2020-12-19 10:15

    I do it yet another DIY way. Map an index/rank onto your cursor and then check that index against a count of the same query in your .rendered callback:

    items: function() {   
       return Items.find({whatever: whatever}).map(function(item, index) {
          item.rank = index + 1;
          return item;
       });
    }
    
    Template.stuff.rendered = function() { 
      if(this.data.rank === Items.find({whatever: this.data.whatever}).count()) {
         //  Do something cool like initializing a sweet
         //  JS plugin now that ALL your template instances 
         //  are all rendered
         $("#coolplugindiv").coolJSPluginInit();
      }
    }
    

    I'm sure it's slightly more taxing on the server, but it works. Thought I'm curious about template.lastNode and if there's some way to use that to have the same effect.

    0 讨论(0)
提交回复
热议问题