I've created a custom element:
const templ = document.createElement('template');
templ.innerHTML = `
<span><slot></slot></span>
`;
class SlideButton extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the element.
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(tmpl.content.cloneNode(true));
this.span = shadowRoot.querySelector('span');
this.triggerEvent = new CustomEvent("trigger", {
bubbles: false,
cancelable: false,
});
this.initMouseEvents();
}
initMouseEvents() {
this.span.addEventListener('mousedown', (e) => {
//Watch and calculate slide amount..
this.addEventListener('mousemove', this.slide, false);
});
//When button is released...
document.addEventListener('mouseup', handleMouseUp = (e) => {
this.removeEventListener('mousemove', this.slide, false);
document.removeEventListener('mouseup', handleMouseUp);
//If slided enough, dispatch event...
if (Math.abs(this.slideAmount) > (this.maxSlide * 0.75)) {
console.log('firing event');
this.dispatchEvent(this.triggerEvent);
}
//Reset button to normal state...
}, false);
}
}
Somewhere else in my code..
class SpotLightModal {
//Constructor..
//code..
//code..
init() {
this.actions.querySelector('slide-button[type="den"]').addEventListener('trigger', e => {
console.log(e);
console.log('den');
//Do stuff..
});
}
//code...
//code...
}
Everything works as expected except that the callback in the event listener is runs twice and the output is:
firing event
CustomEvent {...}
den
CustomEvent {...}
den
Both e.stopPropagation()
and e.preventDefault()
have no effect and trying to use them did nothing..
I've edited to include this.span
and also moved the "mouseup" event listener outside the "mousedown" event listener but that didn't work, infact when logging this
, now, it gives another different element (of the same kind, <slide-button>
, the first on the page), the "mouseover" listener doesn't get removed, and the event isn't fired.
Am I doing something wrong in here? or what am I missing exactly?
Thanks in advance..
I've created a custom element:
const templ = document.createElement('template');
templ.innerHTML = `
<span><slot></slot></span>
`;
class SlideButton extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the element.
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(tmpl.content.cloneNode(true));
this.span = shadowRoot.querySelector('span');
this.triggerEvent = new CustomEvent("trigger", {
bubbles: false,
cancelable: false,
});
this.initMouseEvents();
}
initMouseEvents() {
this.span.addEventListener('mousedown', (e) => {
//Watch and calculate slide amount..
this.addEventListener('mousemove', this.slide, false);
});
//When button is released...
document.addEventListener('mouseup', handleMouseUp = (e) => {
this.removeEventListener('mousemove', this.slide, false);
document.removeEventListener('mouseup', handleMouseUp);
//If slided enough, dispatch event...
if (Math.abs(this.slideAmount) > (this.maxSlide * 0.75)) {
console.log('firing event');
this.dispatchEvent(this.triggerEvent);
}
//Reset button to normal state...
}, false);
}
}
Somewhere else in my code..
class SpotLightModal {
//Constructor..
//code..
//code..
init() {
this.actions.querySelector('slide-button[type="den"]').addEventListener('trigger', e => {
console.log(e);
console.log('den');
//Do stuff..
});
}
//code...
//code...
}
Everything works as expected except that the callback in the event listener is runs twice and the output is:
firing event
CustomEvent {...}
den
CustomEvent {...}
den
Both e.stopPropagation()
and e.preventDefault()
have no effect and trying to use them did nothing..
I've edited to include this.span
and also moved the "mouseup" event listener outside the "mousedown" event listener but that didn't work, infact when logging this
, now, it gives another different element (of the same kind, <slide-button>
, the first on the page), the "mouseover" listener doesn't get removed, and the event isn't fired.
Am I doing something wrong in here? or what am I missing exactly?
Thanks in advance..
Share Improve this question edited Sep 16, 2019 at 18:38 Nasa asked Sep 16, 2019 at 15:59 NasaNasa 4195 silver badges15 bronze badges 2- I think this is happening because you are nesting events within events. – Chris Hemmens Commented Sep 16, 2019 at 16:03
- @ChrisHemmens I tried ordering them differently but nothing worked.. other orders cause the event to fir multiple times or not at all – Nasa Commented Sep 16, 2019 at 16:07
4 Answers
Reset to default 14If anyone else is having a similar problem try using:
event.stopImmediatePropagation()
into your callback function like so
window.addEventListener('message', (event) => {
event.stopImmediatePropagation();
console.log('detail:', event.detail);
})
In my case this seemed to do the trick, but it's definitely super hacky and would remend trying to find out the root cause of your issue if it needs to be super reliable.
https://developer.mozilla/en-US/docs/Web/API/Event/stopImmediatePropagation
In my case, I was using event bus to dispatch custom event which will trigger a callback function in which the double events happened.
Initially, the custom event listener was in the constructor()
constructor() {
EventBus.addEventListener('someEvent', this.doSomething);
}
And I stopped seeing double events once I moved that line to
connectedCallback()
:
connectedCallback() {
EventBus.addEventListener('someEvent', this.doSomething);
}
The problem lies in the nesting of your code.
First you add the first mousedown
event listener which will trigger on when the mouse is clicked and not released.
this.span.addEventListener('mousedown', (e) => { ...
Then inside of your mousedown
event listener you listen for the mouseup
event on the document
.
document.addEventListener('mouseup', handleMouseUp = (e) => { ...
So now, whenever you click both the mousedown
and the mouseup
will be triggered. Even when you remove the event listener inside the mouseup
event listener. The code inside there is already executing.
That is what causes your code to fire the CustomEvent
twice.
Prevent the nesting of event listeners, except when one would rely on the other. Like in the mousedown
event listener where you need to add the mousemove
event listener. Now instead of nesting add another event listener that listens to mouseup
adjecent to the mousedown
event listener. Remove the mousemove
event there. Example below:
class SlideButton extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the element.
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(tmpl.content.cloneNode(true));
this.span = shadowRoot.querySelector('span');
this.triggerEvent = new CustomEvent("trigger", {
bubbles: false,
cancelable: false,
});
}
connectedCallback() {
// It's preferred to set the event listeners in the connectedCallback method.
// This method is called whenever the current element is connected to the document.
this.initMouseEvents();
}
initMouseEvents() {
// Bind slider-button as this context to the slide method.
// In case you use the this keyword inside of the slide method
// you would need to do this to keep using the methods of this class.
const slide = this.slide.bind(this);
// Create a toggle to indicate that the slide-button has been clicked so that ONLY then the event listeners will be added.
let targetIsClicked = false;
// Mouse is clicked, mousemove added.
document.addEventListener('mousedown', (e) => {
// Check if this button has been clicked.
const slideButton = e.target.closest('slide-button');
if (slideButton === this) {
// Toggle true and add mousemove event.
targetIsClicked = true;
document.addEventListener('mousemove', slide, false);
}
});
// Mouse is released, remove mousemove and fire event.
document.addEventListener('mouseup', (e) => {
// Only do something if this button has been clicked in the mousedown event.
if (targetIsClicked === true) {
document.removeEventListener('mousemove', slide, false);
// Reset toggle value.
targetIsClicked = false;
//If slided enough, dispatch event...
if (Math.abs(this.slideAmount) > (this.maxSlide * 0.75)) {
console.log('firing event');
this.dispatchEvent(this.triggerEvent);
}
//Reset button to normal state...
}
});
}
}
Edit
I've changed the event listener's target to document
. You explained that you want the mousemove
and mouseup
events to work when triggering them outside the button.
In the mousedown
I check if the current target that has been clicked is in fact this button. And if so the targetIsClicked
value will toggled to indicate that the correct button has been clicked.
In the mouseup
event first check if the targetIsClicked
is true
. Meaning you clicked this button and execute the rest of your code.
Although, if you'd have multiple <slide-button>
elements there would be multiple mousedown
, mousemove
and mouseup
listeners attached to the document
, which could create some weird outes.
Note
I've moved the this.initMouseEvents()
function call to the connectedCallback()
method. It is good practice to do this because now you are interacting with the lifecycle of the custom element. Add the event listeners in connectedCallback()
when the element is created and remove event listeners in disconnectedCallback()
when the element is removed. The element itself triggers these methods, so you don't have to call them.
There's a "bubbles" option that you can set to false, which should eliminate the mouse-down/up bubbling phases:
var evt = new CustomEvent(name, {
detail : data,
bubbles: true, // <-- try setting to false
cancelable: true
});
document.dispatchEvent(evt);