I am working on an lit-element image component that has two views.
I can switch between the two views at the press of a button. When I am in 'grid view' I have setup a way to select an image when you click on it it will be marked as selected.
When I load images in and switch to grid view I can select and deselect the images with no issues. If I click back to single view then return to grid view I cannot select any images. If I do it a third time I am able to select Images again.
With debugging I found that the event listener that is added to the image is not being removed. I have tried several different ways through my research on this issue with no luck with resolving this issue.
here is the code that I have and would like some second eyes on to help out
case 'grid':
this.changeViewState(buttonName);
imageItems.forEach((image) => {
image.addEventListener('click', this.selectImage.bind(this,image), false);
});
hiImage.viewMode = 'grid';
break;
selectImage(image){
let hiImage = this._getImageSection().querySelector("hi-images");
hiImage.dispatchEvent(new CustomEvent('selected-images', {
detail: { image: image }
}));
image.removeEventListener('click', this.selectImage.bind(this,image));
}
Thanks in advance for any help I can get still new at javascript and lit element.
Update: This is now what i currently have . this is the button click to switch to grid view
case 'grid':
this.changeViewState(buttonName);
imageItems.forEach((image) => {
const boundFn = this.selectImage.bind(this, image);
this.boundFnsByImage.set(image, boundFn);
image.addEventListener('click', boundFn);
});
hiImage.viewMode = 'grid';
break;
this is my button click to switch to single view this is where the event listeners should be removed.
case 'single':
console.log('gridswitch button was clicked');
this.boundFnsByImage.forEach((boundFn, image) => {
image.removeEventListener('click', boundFn);
});
this.changeViewState(buttonName);
hiImage.viewMode = 'single';
hiImage.dispatchEvent(new CustomEvent('set-nav-icons'));
break;
The SelectImage function which pushes the event dispatch
selectImage(image) {
let hiImage = this._getImageSection().querySelector("hi-images");
hiImage.dispatchEvent(new CustomEvent('selected-images', {
detail: { image: image }
}));
console.log('click selected image');
}
and the properties and constructor of this part of the component
static get properties() {
return {
name: { type: String }, // ID of the button
tooltip: { type: String },// sets a tooltip for the button
icon: { type: String }, // sets the icon of the button
for: { type: String }, // binds label to input control when it is used
confirmationType: { type: Boolean },
boundFnsByImage: { type: WeakMap }
}
}
constructor() {
super();
this.confirmationType = false;
this.boundFnsByImage = new WeakMap();
}
What Im looking to do So when the gridswitch button is clicked it goes from single view to gridview- where I can select one or more images.
Then when the single view button is clicked it switches back to singleview and should remove the event listeners from grid view images.
So when I switch back to grid view I can select them again
What is happening still. The event listener is getting added to the images on the second and subsequent switiches to grid view meaning it is doing it more than once so when i click on it it fires twice.
what is not happening the event listeners are not being removed from the images so they can be properly set again when switching back to grid view .
When you .bind
a function, you create an entirely new function, one which is not ===
to any function that may have existed previously (even if created with the same procedure). So
this.selectImage.bind(this,image) === this.selectImage.bind(this,image)
will evaluate to false
- the expressions on each side of the ===
reference different function objects.
Because selectImage
looks to remove the listener, one option would be to instead use { once: true }
to ensure the listener only runs once:
imageItems.forEach((image) => {
image.addEventListener('click', this.selectImage.bind(this, image), { once: true });
});
The alternative would be to store the bound function somewhere so removeEventListener
can be called with it later, eg
const boundFnsByImage = new Map();
// ...
imageItems.forEach((image) => {
const boundFn = this.selectImage.bind(this, image);
boundFnsByImage.set(image, boundFn);
image.addEventListener('click', boundFn);
});
and then retrieve it with
selectImage(image){
let hiImage = this._getImageSection().querySelector("hi-images");
hiImage.dispatchEvent(new CustomEvent('selected-images', {
detail: { image: image }
}));
const boundFn = boundFnsByImage.get(image); // <----------------------
// if you want to remove the listener when clicked:
image.removeEventListener('click', boundFn);
}
Also note that there's no need to pass the third useCapture
argument to addEventListener
unless you want to use capturing - it'll default to false
anyway.
If you want to remove all listeners at once (like during a view switch), then iterate through the Map:
boundFnsByImage.forEach((boundFn, image) => {
image.removeEventListener('click', boundFn);
});