Blaze :sadface: race conditions, DOM loading vs content loading


#1

Ugh, I’m running into this problem again. I’ve got a product detail page that has a gallery of thumbnails below the main image. Here’s the relevant code:

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      Tracker.afterFlush(() => {
        let $imgScroller = this.$('.image-scroller');
        let galleryWidth = 0;
        debugger;

        $imgScroller.find('img').each(function () {
          galleryWidth += $(this).outerWidth();
          console.log(`galleryWidth = ${galleryWidth}`);
        });
        $imgScroller.width(galleryWidth);

        [...]

The idea is to size the div containing the images to the width of the images (plus margins, etc). However, it’s setting a value of 25px (the total of the margins) because the images aren’t showing up yet. In fact, when Chrome hits that debugger statement, I can see the images aren’t there. On top of all that, these are temporary images not even in the database!

<div class="gallery-images">
  <div class="image-scroller">
    <img src="http://lorempixel.com/100/100"><img
    src="http://lorempixel.com/100/100"><img
    src="http://lorempixel.com/100/100"><img
    src="http://lorempixel.com/100/100"><img
    src="http://lorempixel.com/100/100">
  </div>
</div>

Why isn’t Tracker.afterFlush working this time (it usually does)?


#2

Not exactly sure why you would do it this way… Generally speaking you would want to set a size for the div and let the content adjust. That being said, if you absolute have to go this route you could try something like this.

Template.productDetail.events({
    "load img": function(){
        if (Template.subscriptionsReady()) {
            let $imgScroller = this.$('.image-scroller');
            let galleryWidth = 0;
            debugger;

            $imgScroller.find('img').each(function () {
                galleryWidth += $(this).outerWidth();
                console.log(`galleryWidth = ${galleryWidth}`);
            });
            $imgScroller.width(galleryWidth);
        }
    }
});

As for why afterFlush doesn’t work… Basically what afterFlush says is call this function after the next round of updates to the DOM. The render happens, afterFlush is called, but the browser is still waiting on the images to load asynchronously which could be any time between a few milliseconds to never because the connection timed out. Hopefully this helps a bit.


#3

Basically, .image-scroller should expand to fit its contents, but .gallery-images is a very limited width, and has overflow: hidden. I’m creating a scrolling div within a div of images. The problem is, the .image-scroller won’t expand past the width of its parent, making the images wrap.

I hadn’t thought of using an image load event. Thanks, I’ll give it a shot!


#4

Oh, yes, that makes much more sense then :smiley:


#5

What I wound up doing:

let imgCount;
let imgTally = 0;

Template.productDetail.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      Tracker.afterFlush(() => {
        imgCount = this.$('img').length;
      });
    }
  });
});

Template.productDetail.events({
  'load img': function (event, template) {
    if (++imgTally >= imgCount) {
      (template.view.template.imagesLoaded.bind(template))();
    }
  }
});

Now all you have to do is define a function that will get called as soon as the DOM and all images are loaded in this template:

Template.myTemplate.imagesLoaded = function () {
};

#6

The onRendered could probably be reduced to:

Template.productDetail.onRendered(function () {
  Tracker.afterFlush(() => {
    imgCount = this.$('img').length;
  });
});