0

Assuming multiple sliders can exist on a webpage and they each need their own state of user interactions like touch-start X-position and a boolean of whether it is currently processing a swipe or not..

I'm trying to reuse a piece of code that handles swipe and mouse drag events but each slider should have its own value of initalX and isInSwipe:

   function slideInteractions(selector, callback) {
        return function() {
            let initialX = 0;
            let isInSwipe = false;
            document.querySelector(selector).addEventListener('mousedown', (ev) => {
                startInteraction(ev);
                ev.preventDefault();
            });
            document.querySelector(selector).addEventListener('touchstart', startInteraction);
            document.addEventListener('mouseup', (ev) => {
                endInteraction(ev, callback);
            });
            document.addEventListener('touchend', (ev) => {
                endInteraction(ev, callback);
            });
        };
    }

For each slider on the page I would use it like so, passing the slide container and the callback to call on accepted interaction:

slideInteractions('.project-slides', moveProject)();

I thought that since initialX and isInSwipe are defined in the function being returned then that would create a Closure over them so that each call of slideInteractions() would create its own copy of those variables but it seems that they go out of scope once the function returns.

Is there a way I can fix this keep the variables live on properly within the Closure?

EDIT

The variables are used in startInteraction() and endInteraction() but those functions don't really see those variables at all: they are undeclared within those functions which is where my confusion is because I assume that those functions would have access to the "closed" variables no?

frezq
  • 653
  • 8
  • 18
  • 1
    `initialX` and `isInSwipe` are inside the returned function and they are copies, for each function call. But you never use them. Their scope is inside the function, as usual; see [What is the scope of variables in JavaScript?](/q/500431/4642212). – Sebastian Simon Aug 24 '21 at 18:24
  • 3
    Yes, every call of `slideInteractions` has its own `initialX` and `isInSwipe` variables. Where are you using those variables? It doesn't look like you are using them in the scope where they are defined, and that's likely the problem. Please provide more information. – Felix Kling Aug 24 '21 at 18:25
  • You are not creating those variables in the `slideInteractions` function, but rather in the anonymous function that it returned. Which appears to be pretty pointless, if you don't even store it anywhere to call it multiple times or so. – Bergi Aug 24 '21 at 18:28
  • Where exactly do you currently have an undesired behaviour? Did you by any chance reference `initialX` or `isInSwipe` in `moveProject`? Is that what you are asking about? – trincot Aug 24 '21 at 18:31
  • SOunds like you expect endInteraction to use the variables, but they use global ones instead. Creating a class sounds like it would make more sense. – epascarello Aug 24 '21 at 18:40
  • @SebastianSimon @FelixKling @Bergi Thank you all for the comments! The confusion is that the initialX and isInSwipe are used in: `startInteraction()` to set the initial mouse/touch position and to set the isInSwipe flag; and in `endInteraction()` to do the calculations and stuff. – frezq Aug 25 '21 at 17:17
  • @trincot I get "undefined" variables in `startInteraction()` and `endInteraction()` like they were never defined. Thank you for the comment. – frezq Aug 25 '21 at 17:18
  • @epascarello Yes that's exactly what I expect: that `endInteraction` would use the variables but do they indeed use undefined global ones. A class is doable but I would like to learn how to do this with functions as well - I believe it should be possible, no? – frezq Aug 25 '21 at 17:22
  • 1
    Move the definition of `startInteraction` and `endInteraction` functions *inside* the anonymous `function` in which these variables are defined. If this is not possible (because those functions need to be called also from elsewhere), then you must pass those values as arguments and get their new values back as return value. – trincot Aug 25 '21 at 17:25

1 Answers1

1

To have the variable shared between the functions, they have to be in the same block scope. So that means you would have to define the startInteraction and endInteraction functions inside the same block as the variables are defined.

function slideInteractions(selector, callback) {
    return function() {

        let initialX = 0;
        let isInSwipe = false;

        function startInteraction(e,) {
          initialX = e.clientX;
        }
        function endInteraction(e, callback) { 
           console.log(initialX, e.clientX);
           if (callback) callback(initialX);
        }

        document.querySelector(selector).addEventListener('mousedown', (ev) => {
            startInteraction(ev);
            ev.preventDefault();
        });
        document.querySelector(selector).addEventListener('touchstart', startInteraction);
        document.addEventListener('mouseup', (ev) => {
            endInteraction(ev, callback);
        });
        document.addEventListener('touchend', (ev) => {
            endInteraction(ev, callback);
        });
    };
}

Another option is pass around an object. Functions can be outside of the block scope.

function startInteraction(e, data) {
  data.initialX = e.clientX;
}
function endInteraction(e, data, callback) { 
   console.log(data.initialX, e.clientX);
   if (callback) callback(data);
}

function slideInteractions(selector, callback) {
    return function() {

        const data = {
          initialX: 0,
          isInSwipe: false,
        };

        document.querySelector(selector).addEventListener('mousedown', (ev) => {
            startInteraction(ev, data);
            ev.preventDefault();
        });
        document.querySelector(selector).addEventListener('touchstart', startInteraction);
        document.addEventListener('mouseup', (ev) => {
            endInteraction(ev, data, callback);
        });
        document.addEventListener('touchend', (ev) => {
            endInteraction(ev, data, callback);
        });
    };
}

A better solution is to make a class

class ClickyThing {
  initialX = 0
  isInSwipe = false

  constructor(elem, callback) {
    this.elem = elem;
    this.callback = callback;
    this.bindEvents();
  }

  bindEvents() {
    this.elem.addEventListener('mousedown', (evt) => {
      evt.preventDefault();
      this.startInteraction(evt);
    });

    document.addEventListener('mouseup', (evt) => {
      this.endInteraction(evt);
    });
  }

  startInteraction(evt) {
    this.isInSwipe = true;
    this.initialX = evt.clientX;
  }

  endInteraction(evt) {
    if (!this.isInSwipe) return;
    console.log(this.initialX, evt.clientX);
    if (this.callback) this.callback(this.initialX, evt.clientX);
    this.isInSwipe = false;
  }

}

document.querySelectorAll(".test").forEach((elem) => {
  const clickyThing = new ClickyThing(elem, () => console.log('click'));
});
.test {
  width: 100px; height: 100px; margin: 10px; background-color: #CCC;
}
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
epascarello
  • 204,599
  • 20
  • 195
  • 236