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?

9
  • 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?. Commented Aug 24, 2021 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. Commented Aug 24, 2021 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. Commented Aug 24, 2021 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? Commented Aug 24, 2021 at 18:31
  • 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. Commented Aug 25, 2021 at 17:25

1 Answer 1

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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.