{{#if Template.subscriptionsReady}} and Pagination


#1

I wrap my table in the new Template.subscriptionsReady like this crude example:

{{#if Template.subscriptionsReady}}
   <table>{{#each data_context}} you_get_the_idea {{/each}}</table>
{{else}} 
   loading... 
{{/if}}
<button class="load_more_stuff">get more stuff</button>

When I select the button at the bottom of my page to load more messages, the new messages get loaded at the bottom of the table, but the page reloads and I get taken to the top of the page no matter what JQuery I come up with. Now I have to scroll back down to the bottom of the page to see the new messages.

When I remove the {{#if Template.subscriptionsReady}}, as I would expect, the new messages get loaded at the bottom of the table and there’s no ‘jumping’ to the top of the page.

Any Ideas on the why it’s happening and how to avoid it (prefer to keep it around if possible)?


#2

Hi, it is expected, because while new records are fetched from the server your DOM is shrinked to single loading.... The problem is your {{else}}. Just remove it. As for me - I do not use built in subscriptionsReady and manage all subscribtions by the hand.


#3

@mrzafod could you explain how removing {{else}} helps with jumping back to top? What do you mean handle your subscriptions manually?


#4

Maybe you could do this?:

<table>{{#each data_context}} you_get_the_idea {{/each}}</table>
{{#unless Template.subscriptionsReady}}
   <div>loading...</div>
{{/unless}}
<button class="load_more_stuff">get more stuff</button>

#5

That’s what I’ve been using here http://starport.meteor.com/academy/new Also have the load more button in an {{if hasMoreItems}} but the problem with that as you might see is that sometimes the load more button gets displayed a second before the items in {{each}} show on page. Could put it in a delay but it ain’t perfect.

Is there any kind of hook for just before the page gets shown to the user, when both subscriptions & the DOM are ready. Could really use it for example when you need to run a jquery function last second (like Isotope) when everything is truely ready. Instead of having to trigger them with a slight delay after subscriptions is ready like here http://starport.meteor.com/boardwalk Which just feels unreliable.


#6

Thanks for the feedback!

Will you provide code examples to help us better understand your approach?


#7

I don’t understand what your exact problem is, but here is an answer to this specific question (not tested):

Template.myTemplate.rendered = function() {
  var self = this;
  self.hasJustBeenRendered = true;
  this.autorun(function() {
    if (self.subscriptionsReady() && self.hasJustBeenRendered) {
      // You get here when both the DOM and all subscriptions are ready
      ...
    }
    self.hasJustBeenRendered = false;
  });
}


#8

Just tried it out, didn’t work. self.hasJustBeenRendered gets set to false before the subscription is ready so everything within the if statement never runs. Your code would work if the entire Template.myTemplate.rendered would run again when the subscription is ready but it doesn’t.

Without the self.hasJustBeenRendered would look like this:

Template.myTemplate.onRendered = function() {
  var self = this;
  this.autorun(function() {
    if (self.subscriptionsReady()) {
     // > $Isotope function that changes the DOM & CSS
    }
  });
}

What’s supposed to happen:

  • Subscriptions are ready
  • Items from the subscription updates the DOM {{each}{{/each}} blocks
  • Isotope function updates the DOM to move the items in the {{each}{{/each}} blocks to their correct position and applies CSS
  • The user gets to see the template

What actually happens:

  • Subscriptions are ready
  • Isotope function updates the DOM to move the items in {{each}{{/each}} blocks in their correct position in the DOM and applies CSS
  • Items from the subscription updates the DOM {{each}{{/each}} blocks
  • The user gets to see the template

The user gets to see a mangled up page because the Isotope function ran before all the items were placed in the DOM. So Isotope missed all the items / ran too early.

Only way I’ve been able to solve it so far is put a delay on Isotope to allow for the items be placed in the DOM {{each}} blocks first.


#9

My experiments suggest that the rendered (now onRendered) callback gets called when the outermost element of the template has been inserted into the DOM.

What I’ve managed to get working is to insert a dummy template at the end of the HTML and set up an onRendered callback for that. At least in my case (using the leaderboard example), all DOM elements have been inserted when that final onRendered callback is executed.

For your use case it should then be possible to use your jQuery selector in that final Template.foo.onRendered() callback.

YMMV :wink:


#10

The reason is a template markup. Imagen that you have a DOM rendering like:

{{#if value}}
    <div style='height: 1000px;'></div>
{{else}}
    <div style='height: 10px;></div>
{{/else}}

if the value change all your DOM here would be replaced. Thats why you can see DOM messed (and scroll position is dropped) while new data are coming from the server


#11

EDITED
see gist


#12

Could you show a code example? Tried a couple things but couldn’t get it working as intended.


#13

I updated our original template-level subscription example to use subscriptionsReady. Might be helpful:

http://meteorpad.com/pad/6t8wLBiGYmg7cee5t/Template-Level%20Subs%20v2


#14

thanks for the gist @mrzafod

I can’t read coffee all that well, so I used js2.coffee to translate your coffee into JS; here’s the translation of your coffee:

var ManagedSub,
  __slice = [].slice;
​
ManagedSub = (function() {
  function ManagedSub(name, options) {
    var enter, self;
    if (options == null) {
      options = {};
    }
    self = this;
    self.sub = null;
    enter = function() {
      return typeof options.enter === "function" ? options.enter() : void 0;
    };

    self.enter = _.once(enter);
    self.exit = function() {
      return typeof options.exit === "function" ? options.exit() : void 0;
    };

    self.handle = _.debounce(false, options.delay || 1000, function() {
      var args, tmpSub;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return tmpSub = Meteor.subscribe.apply(Meteor, [name].concat(__slice.call(args), [function() {
        var _ref;
        self.enter = _.once(enter);
        self.exit();
        if ((_ref = self.sub) != null) {
          if (typeof _ref.stop === "function") {
           _ref.stop();
          }
        }
        return self.sub = tmpSub;
      }]));
    });
  }
​
  ManagedSub.prototype.run = function() {
    var args;
    args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    this.enter();
    return this.handle.apply(this, args);
  };
​
  ManagedSub.prototype.ready = function() {
    var _ref;
    return (_ref = this.sub) != null ? typeof _ref.ready === "function" ? _ref.ready() : void 0 : void 0;
  };
​
  ManagedSub.prototype.stop = function() {
    var _ref;
    return (_ref = this.sub) != null ? typeof _ref.stop === "function" ? _ref.stop() : void 0 : void 0;
  };
​
  return ManagedSub;
​
})();
​

After the translation, I’m still confused. For example, Is this Meteor code?


#15

I ran the code again this morning and this time it didn’t play nicely.

So it’s back to the drawing board :worried:


#16

Here is a little sample:

Template.usersList.created = function () {
	self = this;
	self.sub = new ManagedSub('fullUsersList', {
		enter: function() {
			self.find('#listEntry').classList.add('loading');
		},
		exit: () {
			self.find('#listEntry').classList.remove('loading');
		}
	});


	self.state = new ReactiveDict();

	self.autorun(function() {
		userGroup = self.state.get('userGroup');
		userLimit = self.state.get('userLimit');
		self.sub.run(userGroup, userLimit);
	})
}

Template.usersList.destroyed = function () {
	this.sub.stop();
}

#17

Thanks for this @mrzafod.

Trying to understand your code, where is sub.run() and sub.stop() ?


#18

This helps a lot, thank you!


#19

run’ starts and ‘stop’ stops the reactivity on the object, right?


#20

thats right. You can just try it. Pass {enter: function(), exit: function()} to handle subscribtion start fetching and ready. Also it doesnt drop current documents state until new data is comed