I have a dynamic dropdown list that items inside can be clicked(will do something)
When focus, it will show some suggestion items
When blur, it will remove the items inside the list node
The problem is when the blur/focusout event trigger, my element removed, and the click event do not trigger.
Here is the minimal reproduce in codepen: https://codepen.io/XiaoChu/pen/NWYJdvj?editors=1111
HTML:
<div class="search-wrapper">
<input id="keyword-search" type="text" placeholder="Type some keywords...">
<ul id="result-list"></ul>
</div>
JS:
const inputDom = document.getElementById('keyword-search');
const listDom = document.getElementById('result-list');
inputDom.addEventListener('focus', () => {
for(let i = 0; i < 5; i++) {
const li = document.createElement('li');
li.innerText = `item-${i + 1}`;
li.classList.add('item');
li.addEventListener('click', (e) => {
alert(e.currentTarget.innerText);
});
listDom.appendChild(li);
}
});
inputDom.addEventListener('blur', () => {
listDom.innerHTML = '';
});
I have tried setTimeout to wait some milliseconds, and it works. But I want to know is this solution a good way?
You can use a flag, mouseover and mouseleave like this
const inputDom = document.getElementById('keyword-search');
const listDom = document.getElementById('result-list');
let isInList;
listDom.addEventListener('mouseover', () => {
isInList = true;
console.log(isInList);
})
listDom.addEventListener('mouseleave', () => {
isInList = false;
console.log(isInList)
})
inputDom.addEventListener('focus', () => {
for(let i = 0; i < 5; i++) {
const li = document.createElement('li');
li.innerText = `item-${i + 1}`;
li.classList.add('item');
li.addEventListener('click', (e) => {
alert(e.currentTarget.innerText);
});
listDom.appendChild(li);
}
});
inputDom.addEventListener('blur', () => {
if(!isInList){
listDom.innerHTML = '';
}
});
Related
Hi I have created 9 event listeners that are very similar and I would like to write them in a more efficient manner if possible. each button opens up the same hidden form and populates the dropdown menu with a different value.
const ordermulch = document.getElementById('Mulch');
ordermulch.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 1;
});
const orderptopsoil = document.getElementById('prem-topsoil');
orderptopsoil.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 2;
});
const orderstopsoil = document.getElementById('screened-topsoil');
orderstopsoil.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 3;
});
const ordercgravel = document.getElementById('crushed-gravel');
ordercgravel.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 4;
});
const orderpgravel = document.getElementById('pea-gravel');
orderpgravel.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 5;
});
const orderrrock = document.getElementById('river-rock');
orderrrock.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 6;
});
const orderhpbed = document.getElementById('bedding');
orderhpbed.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 7;
});
const ordersand = document.getElementById('sand');
ordersand.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 8;
});
const ordergseed = document.getElementById('grass-seed');
ordergseed.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = 9;
});
<select> boxes come pre-built with this functionality without you having to manually set the selectedindex each time an item is selected. Instead of attaching the event to the click event of the individual selection items (stuff in the list), you can bind it to the <select> element's "change" listener and pull the selectedindex with much less code.
Example: assuming your html looks something like this...
var products = document.getElementById('select');
products.addEventListener('change', function(e) {
var index = products.selectedIndex;
console.log("Index: " + index + ' - ' + e.target.options[index].getAttribute('id'));
document.getElementById('input_9_11').value = e.target.options[index].getAttribute('id');
})
<form id="order-form">
<select id="select">
<option id="mulch">Mulch</option>
<option id="prem-topsoil">Premium Topsoil</option>
<option id="screened-topsoil">Screened Topsoil</option>
<option id="crushed-gravel">Crushed Gravel</option>
<option id="pea-gravel">Pea Gravel</option>
<option id="river-rock">River Rock</option>
<option id="bedding">Bedding</option>
<option id="sand">Sand</option>
<option id="grass-seed">Grass Seed</option>
</select>
<input type="text" id="input_9_11">
</form>
https://jsfiddle.net/0b8vz9fq/
This is simple, use loops!
Collect all your IDs in an array:
const ids = ['Mulch', 'prem-topsoil', 'screened-topsoil']; // and so on...
Loop through this array:
ids.forEach((value, i) => {
// todo
});
For each iteration you have to get the element first:
const element = document.getElementById(value);
And now you can add the EventListener to this element:
element.addEventListener('click', () => {
document.getElementById('order-form').style.display = 'block';
document.getElementById('input_9_11').selectedIndex = i+1;
});
All together:
const ids = ['Mulch', 'prem-topsoil', 'screened-topsoil']; // and so on...
ids.forEach((value, i) => {
const element = document.getElementById(value);
element.addEventListener('click', () => {
document.getElementById('order-form').style.display = 'block';
document.getElementById('input_9_11').selectedIndex = i+1;
});
});
Small side note: It's great that you're looking for optimizations and don't want to use redudant code. That's the only way to get better!
Make an array of all your IDs and then iterate it to add the event listeners:
const ids = ['Mulch', 'prem-topsoil', 'screened-topsoil']; // ...rest of IDs
ids.forEach((id, index) => {
const elem = document.getElementById(id);
elem.addEventListener('click', () => {
document.getElementById('order-form').style.display = "block";
document.getElementById("input_9_11").selectedIndex = index + 1;
});
})
You should also store order-form and input_9_11 in a variable instead of finding them on every iteration.
I would like to shorten this code, but can't figure out how.
The code works in the way that when you press the button in the selector, a map point and a text on the bottom of the map appear. It works in this way it is, but I am sure that there is a way to shorten it. I just have not enough knowledge on how to shorten it.
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.select__item').forEach( function(tabBtn) {
tabBtn.addEventListener('click', function(event) {
const path = event.currentTarget.dataset.path
document.querySelectorAll('.sketch__item',).forEach( function(tabContent) {
tabContent.classList.remove('block-active')
})
document.querySelectorAll('.details__item',).forEach( function(tabContent) {
tabContent.classList.remove('block-active')
})
document.querySelectorAll(`[data-target="${path}"]`).forEach( function(tabsTarget) {
tabsTarget.classList.add('block-active')
})
})
})
//*** tabs active
let tabsChange = document.querySelectorAll('.select__item')
tabsChange.forEach(button => {
button.addEventListener('click', function () {
tabsChange.forEach(btn => btn.classList.remove('active__tab'))
this.classList.add('active__tab')
})
})
})
let select = function () {
let selectHeader = document.querySelectorAll('.select__header');
let selectItem = document.querySelectorAll('.select__item');
selectHeader.forEach(item => {
item.addEventListener('click', selectToggle)
});
selectItem.forEach(item => {
item.addEventListener('click', selectChoose)
});
function selectToggle() {
this.parentElement.classList.toggle('is-active');
}
function selectChoose() {
let text = this.innerText,
select = this.closest('.partner__select'),
currentText = select.querySelector('.select__current');
currentText.innerText = text;
select.classList.remove('is-active');
}
};
//*** Tabs
select();
Delegation shortens the code.
If you delegate, you shorten the code. Never loop eventlisteners in a container. Use the container instead
I lost 20 lines and made code easier to debug
NOTE: I did not have your HTML so I may have created some errors or logic issues you will need to tackle
const selectChoose = e => {
const tgt = e.target;
let text = tgt.innerText,
select = tgt.closest('.partner__select'),
currentText = select.querySelector('.select__current');
currentText.innerText = text;
select.classList.remove('is-active');
};
const selectToggle = e => e.target.parentElement.classList.toggle('is-active');
window.addEventListener('load', function() {
const container = document.getElementById('container');
container.addEventListener('click', e => {
const tgt = e.target.closest('.select');
if (tgt) {
const path = tgt.dataset.path;
document.querySelectorAll('.item', ).forEach(tabContent => tabContent.classList.remove('block-active'))
document.querySelectorAll(`[data-target="${path}"]`).forEach(tabsTarget => tabsTarget.classList.add('block-active'))
}
})
const tabContainer = document.getElementById('tabContainer');
//*** tabs active
tabContainer.addEventListener('click', e => {
const tgt = e.target.closest('button');
if (tgt) {
tabContainer.querySelectorAll('.active__tab').forEach(tab => tabclassList.remove('active__tab'))
tgt.classList.add('active__tab')
}
}) const selContainer = document.getElementById('selectContainer');
selContainer.addEventListener('click', e => {
const tgt = e.target;
if (tgt.classList.contains('select__header')) selectToggle(e);
else if (tgt.classList.contains('select__item')) selectChoose(e)
})
})
I have a problem with the menu. Can somebody check?
If I'm moving the mouse out content disappears.
I think there is no problem with classes.
class Menu{
constructor(id){
this.id = id;
const linkBlock = document.querySelector(`.navigation__dropdown-block-${this.id}`);
const menuLink = document.querySelector(`.navigation__link-${this.id}`);
const menuLinks = document.querySelectorAll(`.navigation__link`);
const linkBlockA = document.querySelectorAll('.navigation__dropdown-block--active')
menuLink.addEventListener('mouseenter', () => {
linkBlock.classList.add('navigation__dropdown-block--active');
menuLink.classList.add('navigation__link--active');
});
linkBlock.addEventListener('mouseenter', () => {
linkBlock.classList.add('navigation__dropdown-block--active');
menuLink.classList.add('navigation__link--active');
});
menuLink.addEventListener('mouseleave', () => {
linkBlock.classList.remove('navigation__dropdown-block--active');
menuLink.classList.remove('navigation__link--active');
});
linkBlock.addEventListener('mouseleave', () => {
linkBlock.classList.remove('navigation__dropdown-block--active');
menuLink.classList.remove('navigation__link--active');
});
}
}
const menuLinks = document.querySelectorAll('.navigation__link');
for(let i = 0; i < menuLinks.length; i++){
new Menu(i + 1);
}
I have an array with the button objects.
When it is clicked, it gets the "button-active" tag and
when it is clicked again, it removes the "button-active" class
I want to removeEventListener when flag is true
let flag = false;
const buttonActive = () => {
arr.forEach(e => {
e.addEventListener("click", function eventListener(event){
event.preventDefault()
if(checkClass(e, "button-active")) removeClass(e, "button-active")
else addClass(e, "button-active")
})
})
}
button.addEventListener("click", (event) => {
event.preventDefault()
let input = document.createElement('div')
input.className = "info"
input.innerHTML += `...(some html with buttons that have weekday class)...`
info.appendChild(input)
flag = true;
arr = document.querySelectorAll('.weekday')
buttonActive()
})
I thought of a way of putting the eventListener function outside the buttonActive function, but the eventListener function uses the variable e.
How should I solve this problem?
simple~
let flag = false;
let cbs = [];
const buttonActive = (arr, active) => {
if (active) {
cbs = arr.map(e => {
const cb = (event) => {
event.preventDefault()
if(checkClass(e, "button-active")) removeClass(e, "button-active")
else addClass(e, "button-active")
}
e.addEventListener("click", cb)
return cb;
});
} else {
for (int i = 0; i < arr.length; ++i) {
arr[i].removeEventListener('click', cbs[i]);
}
}
}
// I guess this is the trigger button
button.addEventListener("click", (event) => {
event.preventDefault()
let input = document.createElement('div')
input.className = "info"
input.innerHTML += `...(some html with buttons that have weekday class)...`
info.appendChild(input)
flag = !!flag;
arr = document.querySelectorAll('.weekday')
buttonActive(arr, flag)
})
So I'm a newb at web dev, and trying to get my head around JavaScript by writing a.. yeah, you guessed it, a to do list.
I've been trying to set the items to the local storage, and then retrieve it, it sorta works, however when the list items are retrieved, the buttons do not seem to function, and I can't for the life of me figure out why... Any thoughts?
Here's the code:
document.addEventListener('DOMContentLoaded', () => {
const submitButton = document.querySelector('.submit');
submitButton.type = 'submit';
const inputField = document.querySelector('.createItem');
const toDoUl = document.querySelector('.toDoUl');
const completedUl = document.querySelector('.completedUl');
const form = document.querySelector('#header');
const tdContainer = document.getElementById('tdContainer');
const toDoItems = document.getElementById('toDoItems');
(function loadStorage() {
if (localStorage.getItem('todo')) {
tdContainer.innerHTML = localStorage.getItem('todo');
}
})();
function noChildren() {
if (toDoUl.hasChildNodes()) {
tdContainer.classList.remove('tdContainer');
} else {
tdContainer.className = 'tdContainer';
}
}
function createLi() {
const wrapper = document.getElementById('wrapper');
const doneButton = document.createElement('input');
const checkedLabel = document.createElement('label');
doneButton.type = 'checkbox';
checkedLabel.className = 'done';
checkedLabel.appendChild(doneButton);
const listItem = document.createElement('li');
const p = document.createElement('p');
const editButton = document.createElement('button');
const removeButton = document.createElement('button');
toDoUl.appendChild(listItem);
p.textContent = inputField.value;
inputField.value = '';
editButton.className = 'edit';
removeButton.className = 'remove';
listItem.appendChild(checkedLabel);
listItem.appendChild(p);
listItem.appendChild(editButton);
listItem.appendChild(removeButton);
doneButton.style.display = 'none';
editButton.addEventListener('click', () => {
listItem.contentEditable = 'true';
});
listItem.addEventListener('blur', () => {
listItem.contentEditable = 'false';
});
removeButton.addEventListener('click', e => {
const ul = e.target.parentNode.parentNode;
/*const li = e.target.parentNode.parentNode;*/
ul.removeChild(e.target.parentNode);
noChildren();
});
doneButton.addEventListener('click', e => {
if (e.target.parentNode.parentNode.parentNode.className === 'toDoUl') {
completedUl.appendChild(e.target.parentNode.parentNode);
e.target.parentNode.parentNode.className = 'removeTransition';
noChildren();
localStorage.setItem('todo', tdContainer.innerHTML);
} else {
toDoUl.appendChild(e.target.parentNode.parentNode);
e.target.parentNode.parentNode.className = 'addTransition';
noChildren();
}
});
}
form.addEventListener('submit', e => {
e.preventDefault();
noChildren();
createLi();
localStorage.setItem('todo', tdContainer.innerHTML);
});
});
You can see the working version here: http://kozyrev.site/todo/
i'm glad you're writing arrow functions and cool stuff :D
but it seems that you are setting event listeners within createLi function, that is dispatched on form's submit event.
But, when you loads localStorage, is setting HTML content like this:
tdContainer.innerHTML = localStorage.getItem('todo');
event listener is not attached to them, because all of these elements that you created from localStorage, is not create by createLi function :(
but you might write something like this:
// loads from localStorage
(function loadStorage() {
if (localStorage.getItem('todo')) {
tdContainer.innerHTML = localStorage.getItem('todo');
}
})();
// set listeners below
var liSelector = '.toDoUl > li'
var liElements = document.querySelectorAll(liSelector)
Array.prototype.forEach.call(liElements, (liElement) => {
var editButton = liElement.querySelector('.edit')
console.log(editButton)
// you can set listeners here
editButton.addEventListener('click', (e) => {
e.preventDefault()
console.log('yey, event has dispatched, do your magic :)')
})
})
UPDATE: example using named function to reuse them:
function createLi() {
....
const listItem = document.createElement('li');
....
editButton.addEventListener('click', () => {
listItem.contentEditable = 'true';
});
could be written like this
// this function at top of script
const setEditable = (listItem) => {
listItem.contentEditable = 'true';
}
// you can use setEditable within createLi
function createLi() {
....
const listItem = document.createElement('li');
....
editButton.addEventListener('click', () => {
setEditable(listItem)
});
also, after HTML was written from localStorage like this
// loads from localStorage
(function loadStorage() {
if (localStorage.getItem('todo')) {
tdContainer.innerHTML = localStorage.getItem('todo');
}
})();
// set listeners below
var liSelector = '.toDoUl > li'
var liElements = document.querySelectorAll(liSelector)
Array.prototype.forEach.call(liElements, (listItem) => {
var editButton = listItem.querySelector('.edit')
// you can set listeners here
editButton.addEventListener('click', (e) => {
setEditable(listItem)
})
})
I didn't tested, but i hope it works, and it's shows you that named function could be reused for setting listeners :)