Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I break an eventListener if there has been changes?

I have created my own little image slider, and to get the loader working I had to create an addEventListener and then append the loaded image into the DOM.

However, there's a bug in this scenario: When an image takes a while to load and the user clicks past it before it is loaded to see the next image, the event listener is still working in the background, and when the image is then fully loaded it overwrites what the user is currently looking at.

My HTML:

<template name="ImageGallery">
    {{#each galleryImages}}
        {{#if viewingImage}}
            {{> Image}}
        {{/if}}
    {{/each}}
</template>

<template name="Image">
    <div class="image-in-gallery">LOADING</div>
</template>

Checking if the "image" the user wants to see is the one we have hit in the each iteration (thus displaying it):

Template.ImageGallery.helpers({
    viewingImage: function() {
        var self = this
        var galleryImages = Template.parentData().galleryImages
        var renderImage = false
        var viewing = Template.instance().viewingImage.get()
        var posOfThisImage = lodash.indexOf(galleryImages, self)
        if (viewing === posOfThisImage) {
            renderImage = true
        }
        return renderImage
    }
})

Enabling the user to see the next "image" and looping if we have hit the end:

Template.ImageGallery.events({
    'click .click-to-see-next-image': function(event, template) {
        var viewing = template.viewingImage.get()
        var imageCount = this.galleryImages.length
        var nextImage = ++viewing
        if (nextImage < imageCount) {
            template.viewingImage.set(nextImage)
        }
        else {
            template.viewingImage.set(0)
        }
    }
})

The loader:

Template.Image.onRendered({
    var imageUrl = Template.currentData().name + Template.parentData().name + '.jpg'
    var imageObj = new Image()
    imageObj.src = imageUrl

    imageObj.addEventListener('load', function() {
        $('.image-in-gallery').empty()
        $('.image-in-gallery').append($(imageObj))
    }
})

You can see where the problem lies: the empty() and append() of course overwrites the image the user currently looking at and sets it to be whatever is next loaded.

I want to add a break somehow in the addEventListener function to see if the image that is loaded is actually the one the user wants to see. But there are two problems:

1) The ReactiveVar variable isn't available in this template. Do I need to use a Session variable after all?

2) I have no idea how to break.

Can anyone help?

like image 922
Yeats Avatar asked Dec 18 '15 10:12

Yeats


2 Answers

Reading through your question made me think, that maybe it is possible to rebuild your templating setup without the need of this complicated code within templates and fix the root cause - inability to understand which image is currently visible and to easily set next one to be visible.

I have found that best bet is to forget about trying to access parent data context from child templates, because ReactiveVar is not accessible via data contexts anyway (As you have no doubt found out). And data context hierarchies are dependent on Blaze html, so it is not advised to use them in this way.

And other extreme is having Session variable, that would be very global.

Luckily there is a middle way.

Define ReactiveVar outside of Template namespace, and then it will be equally accessible by both parent and children templates. This is works especially well, if you are trying to write a package, and do not want to pollute global Session namespace.

For example put this in hello.html:

<head>
  <title>rangetest</title>
</head>

<body>
  <h1>Welcome to RangeTest!</h1>

  {{> hello}}
  {{> rangeList}}
</body>

<template name="hello">

  <p>And another test {{anotherTest}}</p>
</template>

<template name="rangeList">
  {{#each ranges}}
    {{> range}}
  {{/each}}
</template>


<template name="range">
  <p>{{name}}</p>  
  <input type="range" value="{{value}}" min="1" max="10">
</template>

and this in hello.js

if (Meteor.isClient) {
  anotherDict = new ReactiveDict('another');

  anotherDict.set('hey',2);

  Template.hello.helpers({  
    anotherTest: function() {
      return anotherDict.get('hey');
    }
  });

  Template.rangeList.helpers({
    ranges: function() {
      var ranges = [
        {
          'name':'first',
          'value': 5
        },
        {
          'name':'second',
          'value': 6
        },
        {
          'name':'third',
          'value': 7
        },
        {
          'name':'fourth',
          'value': 8
        }
      ];
      return ranges;
    },
  });

  Template.range.events({
   'input input[type="range"]': function(e,t) {  
    var value = parseInt(e.target.value);    
    anotherDict.set('hey',value);    
   }
  });
}    

you will see that reactive variables propagate nicely between templates.

Probably this does not answer your question about event listeners, but hopefully you will be able to replace your manually added event listener with the Template.Image.events({ event listeners after implementing reactivity the way I have proposed, and those will be tightly bound to particular child template and there will be no way that they would fire unpredictably.

p.s. I had example using ReactiveDict and range inputs, as this was the usecase where I needed to solve similar issue. I had all three options investigated, and this was the one that finally worked and felt more or less meteor way as well.

like image 118
Martins Untals Avatar answered Nov 06 '22 17:11

Martins Untals


I think there are two flaws in your code :

  1. You never remove your load event when your template is destroyed. So even if your template is destroyed because you clicked to get your next image, the image is still loading in your browser and the load event is launched. destroyed elements removeEventListener

  2. You are using $ in your onRendered. $ refers to the the global DOM whereas this.$ would only look for the local DOM of your template. If you had used this.$ it should have thrown an error because your local DOM was destroyed and the wrong image wouldn't have been showed.

This is how I would do it : https://github.com/darkship/ImageGallery

like image 2
user3636214 Avatar answered Nov 06 '22 19:11

user3636214