How do I combine selector and tab functions? - javascript

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)
})
})

Related

Why isn't my classlist removing when using this constructor function

So i'm trying to remove my classlist of selected items when they are clicked again:
const todo = []
const elements = []
const todoList = document.getElementById('todoList')
const addItem = document.getElementById('textBox')
const submit = document.getElementById('submit')
const todoListItem = document.querySelectorAll('.todoListItem')
const todoListItemArr = Array.from(todoListItem)
const deleteItem = document.getElementById('deleteButton')
let selected
class Div {
constructor(content) {
this.content = content
}
addToScreen(){
const element = document.createElement('div')
element.innerHTML = this.content
element.classList.add('todoListItem')
todoList.appendChild(element)
}
select(){
const element = document.querySelectorAll('.todoListItem')
element.forEach(item => {
item.addEventListener('click', () => {
item.classList.add('selectedItem')
selected = item
})
})
}
deselect() {
const elements = document.querySelectorAll('.selectedItem')
elements.forEach(item => {
item.addEventListener('click', () => {
item.classList.remove('selectedItem')
})
})
}
}
function createItem() {
const todoItem = new Div(addItem.value)
todoItem.addToScreen()
todo.push(todoItem.content)
console.log(todo)
addItem.value = ''
todoItem.select()
todoItem.deselect()
}
addItem.addEventListener('keypress', (e) => {
if(e.key == 'Enter') {
createItem()
}
})
submit.addEventListener('click', createItem)
also I'm trying to add a delete function, but everytime i try to remove the element in the constructor by adding a delete function, my delete button does nothing, i've removed those features for now.

Click event not executed if my blur event remove the element

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 = '';
}
});

Mousemove problem with classes. If i'm moving the mouse out content disappears

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);
}

Rendering array items after making changes to its properties

I'm having an issue re-rendering items in an array after changes are made to elements in the array. Whether I add by pushing or remove by splicing, when the array is rendered again on the page, it like more items are being added to the array. So if I push onto the array, the item is added, but the old items are then duplicated into the array. Something similar happens when I remove items. The item looks to be removed, but the elements that were in the array show on the page, they are then duplicated and the item that was spliced is gone.
I'm trying to avoid a location.reload('/edit.html') to refresh the page. Kind of cheating. It seems to work, but I'm trying to get the page to refresh with my renderIngredients function. The toggleIngredient function is also duplicating the list of items when I check an item.
import { initializeEditPage, generateLastEdited } from './views'
import { updateRecipe, removeRecipe, saveRecipes, getRecipes, createIngredient } from './recipes'
const titleElement = document.querySelector('#recipe-title')
const bodyElement = document.querySelector('#recipe-body')
const removeElement = document.querySelector('#remove-recipe')
const addElement = document.querySelector('#add-recipe')
const dateElement = document.querySelector('#last-updated')
const addIngredient = document.querySelector('#new-ingredient')
const recipeStatus = document.querySelector('#recipe-status')
const recipeId = location.hash.substring(1)
const recipeOnPage = getRecipes().find((item) => item.id === recipeId)
titleElement.addEventListener('input', (e) => {
const recipe = updateRecipe(recipeId, {
title: e.target.value
})
dateElement.textContent = generateLastEdited(recipe.updatedAt)
})
bodyElement.addEventListener('input', (e) => {
const recipe = updateRecipe(recipeId, {
body: e.target.value
})
dateElement.textContent = generateLastEdited(recipe.updatedAt)
})
addElement.addEventListener('click', () => {
saveRecipes()
location.assign('/index.html')
})
removeElement.addEventListener('click', () => {
removeRecipe(recipeId)
location.assign('/index.html')
})
addIngredient.addEventListener('submit', (e) => {
const text = e.target.elements.text.value.trim()
e.preventDefault()
if (text.length > 0) {
createIngredient(recipeId, text)
e.target.elements.text.value = ''
}
renderIngredients(recipeId)
saveRecipes()
//location.reload('/edit.html')
})
const removeIngredient = (text) => {
const ingredientIndex = recipeOnPage.ingredients.findIndex((ingredient)=> ingredient.text === text)
if (ingredientIndex > -1) {
recipeOnPage.ingredients.splice(ingredientIndex, 1)
}
saveRecipes()
renderIngredients(recipeId)
//location.reload('/edit.html')
}
const toggleIngredient = (text) => {
const ingredient = recipeOnPage.ingredients.find((ingredient) => ingredient.text === text)
if (ingredient.included) {
ingredient.included = false
} else {
ingredient.included = true
}
//location.reload('/edit.html')
}
const ingredientSummary = (recipe) => {
let message
const allUnchecked = recipeOnPage.ingredients.every((ingredient) => ingredient.included === false)
const allChecked = recipeOnPage.ingredients.every((ingredient) => ingredient.included === true)
if (allUnchecked) {
message = `none`
} else if (allChecked) {
message = `all`
} else {
message = `some`
}
return `You have ${message} ingredients for this recipe`
}
const generateIngredientDOM = (ingredient) => {
const ingredientEl = document.createElement('label')
const containerEl = document.createElement('div')
const checkbox = document.createElement('input')
const ingredientText = document.createElement('span')
const removeButton = document.createElement('button')
recipeStatus.textContent = ingredientSummary(recipeOnPage)
// Setup ingredient container
ingredientEl.classList.add('list-item')
containerEl.classList.add('list-item__container')
ingredientEl.appendChild(containerEl)
// Setup ingredient checkbox
checkbox.setAttribute('type', 'checkbox')
checkbox.checked = ingredient.included
containerEl.appendChild(checkbox)
// Create checkbox button in ingredient div
checkbox.addEventListener('click', () => {
toggleIngredient(ingredient.text)
saveRecipes()
renderIngredients(recipeId)
})
// Setup ingredient text
ingredientText.textContent = ingredient.text
containerEl.appendChild(ingredientText)
// Setup the remove button
removeButton.textContent = 'remove'
removeButton.classList.add('button', 'button--text')
ingredientEl.appendChild(removeButton)
// Create remove button in ingredient div
removeButton.addEventListener('click', () => {
removeIngredient(ingredient.text)
saveRecipes()
renderIngredients(recipeId)
})
return ingredientEl
}
const renderIngredients = (recipeId) => {
// Grab the ingredient display from the DOM
const ingredientList = document.querySelector('#ingredients-display')
const recipe = getRecipes().find((item) => {
return item.id === recipeId
})
// Iterate through the list of ingredients on the page and render all items from recipeDOM
recipe.ingredients.forEach((ingredient) => {
const recipeDOM = generateIngredientDOM(ingredient)
ingredientList.appendChild(recipeDOM)
})
}
renderIngredients(recipeId)
I believe the issue stems from my renderIngredients function but I can't figure out how to fix it. Again, when I refresh the page, the results I want display, but I want to avoid using location.reload. I'm expecting the removeIngredient function to remove the ingredient with a button click and the page refreshes with the renderIngredients function. Also expecting the toggleIngredient function to just display a checkbox next to the ingredient I checked off, but that's not what's happening. The Same thing is happening when I use the addIngredient function, the ingredient is being added, but the ingredient that was already on the page is being duplicated.
I guess you want to clear the list before adding the elements again:
ingredientList.innerHTML = "";

Buttons retrieved from local storage not working

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 :)

Categories