How to avoid using onclick multiple times - javascript

I'm searching for a vanilla JS country code selector from few days and now I have found a perfect code for my project but the problem I'm facing right now is that the code is using onclick() in every option to update the clicked data to select menu, which looks ugly and reduced the speed of code execution.
Instead of using onclick on every li option can I use the 'click' event using addEventListener to update the data. if I can do this can you please explain me how can I do this. actually I'm new in JS so that I need expert guidance.
Please have a look on my JS Snippet. addCountry FUNCTION and Search EVENT
const wrapper = document.querySelector(".wrapper"),
selectBtn = wrapper.querySelector(".select-btn"),
searchInp = wrapper.querySelector("input"),
options = wrapper.querySelector(".options");
let countries = [
"<span class='flag-icon flag-icon-afg'></span> Afghanistan (+93)",
"<span class='flag-icon flag-icon-bel'></span> Belgium (+32)",
"<span class='flag-icon flag-icon-chn'></span> China (+86)",
];
function addCountry(selectedCountry) {
options.innerHTML = "";
countries.forEach(country => {
let isSelected = country == selectedCountry ? "selected" : "";
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
let li = `<li onclick="updateName(this)" class="${isSelected}">${country}</li>`;
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
options.insertAdjacentHTML("beforeend", li);
});
}
addCountry();
function updateName(selectedLi) {
searchInp.value = "";
addCountry(selectedLi.innerHTML);
wrapper.classList.remove("active");
filterData = /(<span\b[^<>]*><\/span>\s*)\w+(?:\s+\w+)*\s*\((\+[\d-]+)\)/g;
selectBtn.firstElementChild.innerHTML = selectedLi.innerHTML.replace(filterData, `$1$2`);;
}
searchInp.addEventListener("keyup", () => {
let arr = [];
let searchWord = searchInp.value.toLowerCase();
arr = countries.filter(data => {
return data.toLowerCase().includes(searchWord);
}).map(data => {
let isSelected = data == selectBtn.firstElementChild.innerHTML ? "selected" : "";
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
return `<li onclick="updateName(this)" class="${isSelected}">${data}</li>`;
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
}).join("");
options.innerHTML = arr ? arr : `<p style="margin-top: 10px;">Oops! Country not found</p>`;
});
selectBtn.addEventListener("click", () => wrapper.classList.toggle("active"));
document.addEventListener('click', (event)=> {
if (!wrapper.contains(event.target)) {
wrapper.classList.remove("active");
}else{
wrapper.classList.add("active");
}
});
/* Import Google Font - Poppins */
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#400;500;600;700&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body{
background: #4285f4;
}
::selection{
color: #fff;
background: #4285f4;
}
.wrapper{
width: 370px;
margin: 85px auto 0;
}
.select-btn, li{
display: flex;
align-items: center;
cursor: pointer;
}
.select-btn{
height: 65px;
padding: 0 20px;
font-size: 22px;
background: #fff;
border-radius: 7px;
justify-content: space-between;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.select-btn i{
font-size: 31px;
transition: transform 0.3s linear;
}
.wrapper.active .select-btn i{
transform: rotate(-180deg);
}
.content{
display: none;
padding: 20px;
margin-top: 15px;
background: #fff;
border-radius: 7px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.wrapper.active .content{
display: block;
}
.content .search{
position: relative;
}
.search i{
top: 50%;
left: 15px;
color: #999;
font-size: 20px;
pointer-events: none;
transform: translateY(-50%);
position: absolute;
}
.search input{
height: 50px;
width: 100%;
outline: none;
font-size: 17px;
border-radius: 5px;
padding: 0 20px 0 43px;
border: 1px solid #B3B3B3;
}
.search input:focus{
padding-left: 42px;
border: 2px solid #4285f4;
}
.search input::placeholder{
color: #bfbfbf;
}
.content .options{
margin-top: 10px;
max-height: 250px;
overflow-y: auto;
padding-right: 7px;
}
.options::-webkit-scrollbar{
width: 7px;
}
.options::-webkit-scrollbar-track{
background: #f1f1f1;
border-radius: 25px;
}
.options::-webkit-scrollbar-thumb{
background: #ccc;
border-radius: 25px;
}
.options::-webkit-scrollbar-thumb:hover{
background: #b3b3b3;
}
.options li{
height: 50px;
padding: 0 13px;
font-size: 21px;
}
.options li:hover, li.selected{
border-radius: 5px;
background: #f2f2f2;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Custom Select Menu</title>
<link rel="stylesheet" href="style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://amitdutta.co.in/flag/css/flag-icon.css">
</head>
<body>
<div class="wrapper">
<div class="select-btn">
<span>Select Country</span>
<i class="uil uil-angle-down"></i>
</div>
<div class="content">
<div class="search">
<i class="uil uil-search"></i>
<input spellcheck="false" type="text" placeholder="Search">
</div>
<ul class="options"></ul>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

Using a technique called event delegation, you can apply the event listener only to the parent element instead of each child, and perform a check to make sure that the child was targeted in the click event.
parent.addEventListener("click", (e) => {
if (!e.target.classList.includes("child")) return;
action();
});
You need some sort of check to make sure only elements you want to be clicked on trigger the action. Otherwise if there is spacing between the children, or the parent has other children that aren't these list items you have, it may create bugs.
In your case it would be like so:
options.addEventListener("click", (e) => {
if (!e.target.classList.contains("country_btn")) return;
updateName(e.target);
});
In your case I added a class name to each list item to be used for this check. If you prefer, you can check that the element tag is a li. You can also change the class name to be something else instead.
Here is a CodeSandbox demo of your code improved.
Here are some articles with more in-depth explanations:
https://javascript.info/event-delegation
https://www.freecodecamp.org/news/event-delegation-javascript/
https://dmitripavlutin.com/javascript-event-delegation/

you can get all the li's using js then add click listener to them using a for loop, this way ou don't see the onClick event on the li elemnt an the code works as expected
const LiElements = Array.from(document.querySelectorAll(".options li"))
LiElements.forEach(option => {
option.addEventListener("click", updateName)
})
here is an exapmle in your code
const wrapper = document.querySelector(".wrapper"),
selectBtn = wrapper.querySelector(".select-btn"),
searchInp = wrapper.querySelector("input"),
options = wrapper.querySelector(".options");
let countries = [
"<span class='flag-icon flag-icon-afg'></span> Afghanistan (+93)",
"<span class='flag-icon flag-icon-bel'></span> Belgium (+32)",
"<span class='flag-icon flag-icon-chn'></span> China (+86)",
];
function addCountry(selectedCountry) {
options.innerHTML = "";
countries.forEach((country) => {
let isSelected = country == selectedCountry ? "selected" : "";
let li = `<li class="${isSelected}">${country}</li>`;
options.insertAdjacentHTML("beforeend", li);
});
AssignListeners();
}
addCountry();
function AssignListeners(){
const LiElements = Array.from(document.querySelectorAll(".options li"))
LiElements.forEach(option => {
option.addEventListener("click", updateName)
})
}
function updateName(selectedLi) {
searchInp.value = "";
addCountry(selectedLi.innerHTML);
wrapper.classList.remove("active");
filterData = /(<span\b[^<>]*><\/span>\s*)\w+(?:\s+\w+)*\s*\((\+[\d-]+)\)/g;
selectBtn.firstElementChild.innerHTML = selectedLi.innerHTML.replace(
filterData,
`$1$2`
);
}
searchInp.addEventListener("keyup", () => {
let arr = [];
let searchWord = searchInp.value.toLowerCase();
arr = countries
.filter((data) => {
return data.toLowerCase().includes(searchWord);
})
.map((data) => {
let isSelected =
data == selectBtn.firstElementChild.innerHTML ? "selected" : "";
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
return `<li onclick="updateName(this)" class="${isSelected}">${data}</li>`;
// I DONT WANT TO SHOW THIS onclick="updateName(this)" IN EVERY LI OPTION //
})
.join("");
options.innerHTML = arr
? arr
: `<p style="margin-top: 10px;">Oops! Country not found</p>`;
});
selectBtn.addEventListener("click", () => wrapper.classList.toggle("active"));
document.addEventListener("click", (event) => {
if (!wrapper.contains(event.target)) {
wrapper.classList.remove("active");
} else {
wrapper.classList.add("active");
}
});

Related

How to submit an list when one of the list item is selected

hello i am creating a search bar that has auto complete feature can someone tell how to auto submit when one of the option is selected
some other possible solutions are autosubmit when the input field contains $
{i will add a $ swmbol in each of the array}
you can check the whole program on codepen by clicking here
https://codepen.io/simplyrajatgupta/pen/wvmXPJv
here is the codes
let suggestions = [
"Channel",
"CodingLab",
"CodingNepal",
"YouTube",
"YouTuber",
"YouTube Channel",
"Blogger",
"Bollywood",
"Vlogger",
"Vechiles",
"Facebook",
"Freelancer",
"Facebook Page",
"Designer",
"Developer",
"Web Designer",
"Web Developer",
"Login Form in HTML & CSS",
"How to learn HTML & CSS",
"How to learn JavaScript",
"How to become Freelancer",
"How to become Web Designer",
"How to start Gaming Channel",
"How to start YouTube Channel",
"What does HTML stands for?",
"What does CSS stands for?",
];
// getting all required elements
const searchWrapper = document.querySelector(".search-input");
const inputBox = searchWrapper.querySelector("input");
const suggBox = searchWrapper.querySelector(".autocom-box");
const icon = searchWrapper.querySelector(".icon");
let linkTag = searchWrapper.querySelector("a");
let webLink;
// if user press any key and release
inputBox.onkeyup = (e)=>{
let userData = e.target.value; //user enetered data
let emptyArray = [];
if(userData){
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${userData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
emptyArray = suggestions.filter((data)=>{
//filtering array value and user characters to lowercase and return only those words which are start with user enetered chars
return data.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase());
});
emptyArray = emptyArray.map((data)=>{
// passing return data inside li tag
return data = `<li>${data}</li>`;
});
searchWrapper.classList.add("active"); //show autocomplete box
showSuggestions(emptyArray);
let allList = suggBox.querySelectorAll("li");
for (let i = 0; i < allList.length; i++) {
//adding onclick attribute in all li tag
allList[i].setAttribute("onclick", "select(this)");
}
}else{
searchWrapper.classList.remove("active"); //hide autocomplete box
}
}
function select(element){
let selectData = element.textContent;
inputBox.value = selectData;
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${selectData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
searchWrapper.classList.remove("active");
}
function showSuggestions(list){
let listData;
if(!list.length){
userValue = inputBox.value;
listData = `<li>${userValue}</li>`;
}else{
listData = list.join('');
}
suggBox.innerHTML = listData;
}
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#200;300;400;500;600;700&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body{
background: #644bff;
padding: 0 20px;
}
::selection{
color: #fff;
background: #664AFF;
}
.wrapper{
max-width: 450px;
margin: 150px auto;
}
.wrapper .search-input{
background: #fff;
width: 100%;
border-radius: 5px;
position: relative;
box-shadow: 0px 1px 5px 3px rgba(0,0,0,0.12);
}
.search-input input{
height: 55px;
width: 100%;
outline: none;
border: none;
border-radius: 5px;
padding: 0 60px 0 20px;
font-size: 18px;
box-shadow: 0px 1px 5px rgba(0,0,0,0.1);
}
.search-input.active input{
border-radius: 5px 5px 0 0;
}
.search-input .autocom-box{
padding: 0;
opacity: 0;
pointer-events: none;
max-height: 280px;
overflow-y: auto;
}
.search-input.active .autocom-box{
padding: 10px 8px;
opacity: 1;
pointer-events: auto;
}
.autocom-box li{
list-style: none;
padding: 8px 12px;
display: none;
width: 100%;
cursor: default;
border-radius: 3px;
}
.search-input.active .autocom-box li{
display: block;
}
.autocom-box li:hover{
background: #1bb361;
color: white;
}
.search-input .icon{
position: absolute;
right: 0px;
top: 0px;
height: 45px;
;
width: 45px;
text-align: center;
line-height: 45px;
font-size: 20px;
color: white;
cursor: pointer;
background: #1a74f2;
right: 5px;
top: 5px;
bottom: 5px;
border-radius: 5px;
}
.search-input
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Autocomplete Search Box | CodingNepal</title>
<link rel="stylesheet" href="style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"/>
</head>
<body>
<form action="https://www.google.com/search?q=">
<div class="wrapper">
<div class="search-input">
<a href="" target="_blank" hidden></a>
<input type="text" id="ra"placeholder="Enter question or chapter...">
<div class="autocom-box">
<!-- here list are inserted from javascript -->
</div>
<div class="icon" id="ca"type="submit"><i class="fas fa-search"></i></div>
</div>
</div>
</form>
<script src="js/suggestions.js"></script>
<script src="js/script.js"></script>
<script>
var input = document.getElementById("ra");
input.addEventListener("keypress", function(event) {
if (event.key === "Enter") {
event.preventDefault();
document.getElementsByClassName("ca").click();
}
});
</script>
</body>
</html>
Instead write code for clicking search button, you can just call window.open('http://url-goes-here.com').
inputBox.onkeydown = (e) => {
let firstSuggestion = suggestions.filter(el =>
el.toLowerCase().includes(e.target.value.toLowerCase()))[0];
if((e.keyCode == 13) && firstSuggestion)
e.preventDefault();
window.open(`https://www.google.com/search?q=${firstSuggestion}`)
}
}
This function make input that matched with first suggestion auto-submitted when enter key pressed. If you wanna use this function, make sure you delete another function that also record enter key in input.
.onkeydown make any key pressed in inputBox will be 'record'. If the key pressed is enter (enter keycode = 13) and first suggestion exist (not null), so you will be redirect to google url + first suggestion query.
firstSuggestion in here, defined as filtered suggestion list, which only include suggestion that contains character of your search query.

How to add a edit to-do list item feature?

Ive been studying javascript and following along on this online tutorial for a to-do list. I switched up and added a few of my own features, but I am not sure how would go about adding a feature where I can edit a individual list item?
I started off creating a function editTodo(key), I know I would have to append my new text to the old list text? If someone could give me a hint or guide me in the right direction?
//array that holds todo list items
let listItems = [];
//Function will create a new list item based on whatever the input value
//was entered in the text input
function addItem (text) {
const todo = {
text, //whatever user types in
checked: false, //boolean which lets us know if a task been marked complete
id: Date.now(), //unique identifier for item
};
//it is then pushed onto the listItems array
listItems.push(todo);
renderTodo(todo);
}
function checkDone(key) {
//findIndex is an array method that returns position of an element in array
const index = listItems.findIndex(item => item.id === Number(key));
//locates the todo item in the listItems array and set its checked property
//to opposite. 'true' will become 'false'
listItems[index].checked = !listItems[index].checked;
renderTodo(listItems[index]);
}
function deleteTodo(key) {
//find todo object in the listItems array
const index = listItems.findIndex(item => item.id === Number(key));
//create a new object with properties of the current list item
//delete property set to true
const todo = {
deleted: true,
...listItems[index]
};
//remove the list item from the array by filtering it out
listItems = listItems.filter(item => item.id !== Number(key));
renderTodo(todo);
}
//edits list item
function editTodo(key) {
//find todo object in the listItems array
const index = listItems.findIndex(item => item.id === Number(key));
}
//selects form element
const form = document.querySelector('.js-form');
const addGoal = document.getElementById('addBtn');
//adds a submit event listener
function selectForm(event) {
//prevent page refresh on form submission
event.preventDefault();
//select the text input
const input = document.querySelector('.js-todo-input');
//gets value of the input and removes whitespace beginning/end of string
//we then save that to new variable -> text
const text = input.value.trim();
//checks whether 2 operands are not equal, returning true or false (boolean)
//if input value is not equal to blank, add user input
if (text !== '') {
addItem(text);
input.value = ''; //value of text input is cleared by setting it to empty
input.focus(); //focused so user can add many items to list witout focusing the input
}
};
addGoal.addEventListener('click', selectForm, false);
form.addEventListener('submit', selectForm, false);
function renderTodo(todo) {
//saves local storage items, convert listItems array to JSON string
localStorage.setItem('listItemsRef', JSON.stringify(listItems));
//selects the first element with a class of 'js-to'list'
const list = document.querySelector('.js-todo-list');
//selects current todo (refer to top) list item in DOM
const item = document.querySelector(`[data-key='${todo.id}']`);
//refer to function deleteTodo(key)
if (todo.deleted) {
//remove item from DOM
item.remove();
return
}
//use the ternary operator to check if 'todo.checked' is true
//if true, assigns 'done' to checkMarked. if not, assigns empty string
const checkMarked = todo.checked ? 'done' : '';
//creates list 'li' item and assigns it to 'goal'
const goal = document.createElement('li');
//sets the class attribute
goal.setAttribute('class', `todo-item ${checkMarked}`);
//sets the data-key attribute to the id of the todo
goal.setAttribute('data-key', todo.id);
//sets the contents of the list item 'li'
goal.innerHTML = `
<input id="${todo.id}" type="checkbox" />
<label for="${todo.id}" class="tick js-tick"></label>
<span>${todo.text}</span>
<button class="edit-todo js-edit-todo"><i class="fa-solid fa-pencil"></i></button>
<button class="delete-todo js-delete-todo">X</button>
`;
//if item already exists in the DOM
if (item) {
//replace it
list.replaceChild(goal, item);
}else {
//otherwise if it doesnt (new list items) add at the end of the list
list.append(goal);
}
}
//selects entire list
const list = document.querySelector('.js-todo-list');
//adds click event listener to the list and its children
list.addEventListener('click', event => {
if (event.target.classList.contains('js-tick')) {
const itemKey = event.target.parentElement.dataset.key;
checkDone(itemKey);
}
//add this 'if block
if (event.target.classList.contains('js-delete-todo')) {
const itemKey = event.target.parentElement.dataset.key;
deleteTodo(itemKey);
}
})
//render any existing listItem when page is loaded
document.addEventListener('DOMContentLoaded', () => {
const ref = localStorage.getItem('listItemsRef');
if (ref) {
listItems = JSON.parse(ref);
listItems.forEach(t => {
renderTodo(t);
});
}
});
#import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
body {
font-family: 'Montserrat', sans-serif;
line-height: 1.4;
}
.container {
width: 100%;
max-width: 500px;
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
color: rgb(43, 43, 43);
height: 90vh;
margin-top: 20vh;
margin-bottom: 5vh;
overflow-y: auto;
}
.app-title {
text-align: center;
margin-bottom: 20px;
font-size: 80px;
opacity: 0.5;
}
.todo-list {
list-style: none;
margin-top: 20px;
}
.todo-item {
margin-bottom: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.todo-item span {
flex-grow: 1;
margin-left: 10px;
margin-right: 10px;
font-size: 22px;
}
.done span {
background-color:#0099e5;
color:#fff;
}
input[type="checkbox"] {
display: none;
}
#addBtn {
padding: 8px 16px;
font-size:16px;
font-weight:bold;
text-decoration: none;
background-color:#0099e5;
color:#fff;
border-radius: 3px;
border: 3px solid #333;
margin-left:10px;
cursor:pointer;
}
#addBtn:hover {
background-color:#0078b4;
}
.tick {
width: 30px;
height: 30px;
border: 3px solid #333;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.tick::before {
content: '✓';
font-size: 20px;
display: none;
}
.done .tick::before {
display: inline;
}
.delete-todo {
border: none;
font-size:16px;
background-color:red;
color:#fff;
outline: none;
cursor: pointer;
width: 28px;
height: 28px;
border-radius:20px;
}
.edit-todo {
border: none;
font-size:16px;
background-color:green;
color:#fff;
outline: none;
cursor: pointer;
width: 28px;
height: 28px;
border-radius:20px;
}
.empty-warning {
flex-direction:column;
align-items:center;
justify-content:center;
display:none;
}
.todo-list:empty {
display:none;
}
.todo-list:empty + .empty-warning {
display:flex;
}
.empty-warning-title {
margin-top:15px;
opacity: 0.8;
color: rgb(43, 43, 43);
}
form {
width: 100%;
display: flex;
justify-content: space-between;
margin-left:5px;
}
input[type="text"] {
width: 100%;
padding: 10px;
border-radius: 4px;
border: 3px solid #333;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List</title>
<link rel = "stylesheet" href = "style.css">
<script src="https://kit.fontawesome.com/67e5409c20.js" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<h1 class="app-title">To Do List</h1>
<form class="js-form">
<input autofocus type="text" aria-label="Enter a new todo item" placeholder="Ex - Walk the dog" class="js-todo-input">
<input type="button" id="addBtn" value="Add">
</form>
<ul class="todo-list js-todo-list"></ul>
<div class="empty-warning">
<h2 class="empty-warning-title">Add your first goal</h2>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
I added the edit feature to the event handler for click events on the list:
Figure I
if (event.target.matches('.edit-todo') && event.target !== event.currentTarget) {
const text = event.target.previousElementSibling;
text.toggleAttribute('contenteditable');
if (text.contenteditable) {
text.focus();
}
}
Basically, when the user clicks an edit button the contenteditable attribute is toggled (true/false) on the <span> that sits right before the button (hence .previousElementSibling).
I also added 2 CSS rulesets as well:
Figure II
.fa-pencil { pointer-events: none }
[contenteditable] { outline: 3px inset blue }
For some reason my mouse cannot click font-awesome icons, I have no idea why. So I disabled click events on the edit icon in order to click the edit button. Others might have the same problem as I do -- I'm 99% sure there's no harm in keeping that ruleset since it just makes the edit button 100% the origin element on the event chain. The second ruleset is a visual cue to the user that the <span> is editable.
let listItems = [];
function addItem(text) {
const todo = {
text,
checked: false,
id: Date.now(),
};
listItems.push(todo);
renderTodo(todo);
}
function checkDone(key) {
const index = listItems.findIndex(item => item.id === Number(key));
listItems[index].checked = !listItems[index].checked;
renderTodo(listItems[index]);
}
function deleteTodo(key) {
const index = listItems.findIndex(item => item.id === Number(key));
const todo = {
deleted: true,
...listItems[index]
};
listItems = listItems.filter(item => item.id !== Number(key));
renderTodo(todo);
}
function editTodo(key) {
const index = listItems.findIndex(item => item.id === Number(key));
}
const form = document.querySelector('.js-form');
const addGoal = document.getElementById('addBtn');
function selectForm(event) {
event.preventDefault();
const input = document.querySelector('.js-todo-input');
const text = input.value.trim();
if (text !== '') {
addItem(text);
input.value = '';
input.focus();
}
};
addGoal.addEventListener('click', selectForm, false);
form.addEventListener('submit', selectForm, false);
function renderTodo(todo) {
// localStorage.setItem('listItemsRef', JSON.stringify(listItems));
const list = document.querySelector('.js-todo-list');
const item = document.querySelector(`[data-key='${todo.id}']`);
if (todo.deleted) {
item.remove();
return
}
const checkMarked = todo.checked ? 'done' : '';
const goal = document.createElement('li');
goal.setAttribute('class', `todo-item ${checkMarked}`);
goal.setAttribute('data-key', todo.id);
goal.innerHTML = `
<input id="${todo.id}" type="checkbox" />
<label for="${todo.id}" class="tick js-tick"></label>
<span>${todo.text}</span>
<button class="edit-todo js-edit-todo"><i class="fa-solid fa-pencil"></i></button>
<button class="delete-todo js-delete-todo">X</button>
`;
if (item) {
list.replaceChild(goal, item);
} else {
list.append(goal);
}
}
const list = document.querySelector('.js-todo-list');
list.addEventListener('click', function(event) {
if (event.target.classList.contains('js-tick')) {
const itemKey = event.target.parentElement.dataset.key;
checkDone(itemKey);
}
if (event.target.classList.contains('js-delete-todo')) {
const itemKey = event.target.parentElement.dataset.key;
deleteTodo(itemKey);
}
if (event.target.matches('.edit-todo') && event.target !== event.currentTarget) {
const text = event.target.previousElementSibling;
text.toggleAttribute('contenteditable');
if (text.contenteditable) {
text.focus();
}
}
})
/*
document.addEventListener('DOMContentLoaded', () => {
const ref = localStorage.getItem('listItemsRef');
if (ref) {
listItems = JSON.parse(ref);
listItems.forEach(t => {
renderTodo(t);
});
}
});*/
#import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
body {
font-family: 'Montserrat', sans-serif;
line-height: 1.4;
}
.container {
width: 100%;
max-width: 500px;
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
color: rgb(43, 43, 43);
height: 90vh;
margin-top: 20vh;
margin-bottom: 5vh;
overflow-y: auto;
}
.app-title {
text-align: center;
margin-bottom: 20px;
font-size: 80px;
opacity: 0.5;
}
.todo-list {
list-style: none;
margin-top: 20px;
}
.todo-item {
margin-bottom: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.todo-item span {
flex-grow: 1;
margin-left: 10px;
margin-right: 10px;
font-size: 22px;
}
.done span {
background-color: #0099e5;
color: #fff;
}
input[type="checkbox"] {
display: none;
}
#addBtn {
padding: 8px 16px;
font-size: 16px;
font-weight: bold;
text-decoration: none;
background-color: #0099e5;
color: #fff;
border-radius: 3px;
border: 3px solid #333;
margin-left: 10px;
cursor: pointer;
}
#addBtn:hover {
background-color: #0078b4;
}
.tick {
width: 30px;
height: 30px;
border: 3px solid #333;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.tick::before {
content: '✓';
font-size: 20px;
display: none;
}
.done .tick::before {
display: inline;
}
.delete-todo {
border: none;
font-size: 16px;
background-color: red;
color: #fff;
outline: none;
cursor: pointer;
width: 28px;
height: 28px;
border-radius: 20px;
}
.edit-todo {
border: none;
font-size: 16px;
background-color: green;
color: #fff;
outline: none;
cursor: pointer;
width: 28px;
height: 28px;
border-radius: 20px;
}
.empty-warning {
flex-direction: column;
align-items: center;
justify-content: center;
display: none;
}
.todo-list:empty {
display: none;
}
.todo-list:empty+.empty-warning {
display: flex;
}
.empty-warning-title {
margin-top: 15px;
opacity: 0.8;
color: rgb(43, 43, 43);
}
form {
width: 100%;
display: flex;
justify-content: space-between;
margin-left: 5px;
}
input[type="text"] {
width: 100%;
padding: 10px;
border-radius: 4px;
border: 3px solid #333;
}
.fa-pencil {
pointer-events: none
}
[contenteditable] {
outline: 3px inset blue
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List</title>
<link rel="stylesheet" href="style.css">
<script src="https://kit.fontawesome.com/67e5409c20.js" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<h1 class="app-title">To Do List</h1>
<form class="js-form">
<input autofocus type="text" aria-label="Enter a new todo item" placeholder="Ex - Walk the dog" class="js-todo-input">
<input type="button" id="addBtn" value="Add">
</form>
<ul class="todo-list js-todo-list"></ul>
<div class="empty-warning">
<h2 class="empty-warning-title">Add your first goal</h2>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

Autocomplete Search bar won't read my values

im trying to make a search bar for my website to redirect pepole on other pages of my website by selecting values from autocomplete suggestions, but when i select a suggestion (example:"Home") and hit search nothing happends, instead when i write the value (example:"chat") it works just fine and it redirects me to another page, my question is: what im doing wrong and why my autocompleted values are not seen by the searchbar?
Here is the code example for values "chat, Home, youtube"
function ouvrirPage() {
var a = document.getElementById("search").value;
if (a === "chat") {
window.open("/index.html");
}
if (a === "Home") {
window.open("/customizedalert.html");
}
if (a === "youtube") {
window.open("https://www.youtube.com/");
}
}
And here is the entire thing:
https://codepen.io/galusk0149007/pen/LYeXvww
Try this in your IDE : Clicking the search icon will navigate to your urls.
// getting all required elements
const searchWrapper = document.querySelector(".search-input");
const inputBox = searchWrapper.querySelector("input");
const suggBox = searchWrapper.querySelector(".autocom-box");
const icon = searchWrapper.querySelector(".icon");
let linkTag = searchWrapper.querySelector("a");
let webLink;
let suggestions = ['chat','home', 'youtube']
// if user press any key and release
inputBox.onkeyup = (e)=>{
let userData = e.target.value; //user enetered data
let emptyArray = [];
if(userData){
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${userData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
emptyArray = suggestions.filter((data)=>{
//filtering array value and user characters to lowercase and return only those words which are start with user enetered chars
return data.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase());
});
emptyArray = emptyArray.map((data)=>{
// passing return data inside li tag
return data = `<li>${data}</li>`;
});
searchWrapper.classList.add("active"); //show autocomplete box
showSuggestions(emptyArray);
let allList = suggBox.querySelectorAll("li");
for (let i = 0; i < allList.length; i++) {
//adding onclick attribute in all li tag
allList[i].setAttribute("onclick", "select(this)");
}
}else{
searchWrapper.classList.remove("active"); //hide autocomplete box
}
}
function select(element){
let selectData = element.textContent;
inputBox.value = selectData;
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${selectData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
searchWrapper.classList.remove("active");
}
function showSuggestions(list){
let listData;
if(!list.length){
userValue = inputBox.value;
listData = `<li>${userValue}</li>`;
}else{
listData = list.join('');
}
suggBox.innerHTML = listData;
}
function ouvrirPage() {
var a = document.getElementById("search").value;
if (a === "chat") {
window.open("/index.html");
}
if (a === "Home") {
window.open("/customizedalert.html");
}
if (a === "youtube") {
window.open("https://www.youtube.com/");
}
}
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#200;300;400;500;600;700&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body{
background: #544b8b;
padding: 0 20px;
}
::selection{
color: #fff;
background: #7c71bd;
}
.wrapper{
max-width: 450px;
margin: 150px auto;
}
.wrapper .search-input{
background: #fff;
width: 100%;
border-radius: 5px;
position: relative;
box-shadow: 0px 1px 5px 3px rgba(0,0,0,0.12);
}
.search-input input{
height: 55px;
width: 100%;
outline: none;
border: none;
border-radius: 5px;
padding: 0 60px 0 20px;
font-size: 18px;
box-shadow: 0px 1px 5px rgba(0,0,0,0.1);
}
.search-input.active input{
border-radius: 5px 5px 0 0;
}
.search-input .autocom-box{
padding: 0;
opacity: 0;
pointer-events: none;
max-height: 280px;
overflow-y: auto;
}
.search-input.active .autocom-box{
padding: 10px 8px;
opacity: 1;
pointer-events: auto;
}
.autocom-box li{
list-style: none;
padding: 8px 12px;
display: none;
width: 100%;
cursor: default;
border-radius: 3px;
}
.search-input.active .autocom-box li{
display: block;
}
.autocom-box li:hover{
background: #efefef;
}
.search-input .icon{
position: absolute;
right: 0px;
top: 0px;
height: 55px;
width: 55px;
text-align: center;
line-height: 55px;
font-size: 20px;
color: #644bff;
cursor: pointer;
}
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"/>
</head>
<body>
<div class="wrapper">
<div class="search-input">
<a href="" target="_blank" hidden></a>
<input type="text" placeholder="Type to search..">
<div class="autocom-box">
</div>
<div class="icon" onclick="ouvrirPage()"><i class="fas fa-search"></i></div>
</div>
</div>
</body>

How can I add strikethrough property by clicking the button next to the element?

I am new to coding, and I am trying to make a shopping list app although I ran into some problem.
I am trying to select the elements of a table, it has two columns one for the shopping item and one for buttons next to each item. I want to click the button next to the item and with that add a css style of strikethrough but only for that respective item. But now i can click any "Mark as buyed button" and will apply the styles for all the items, and after that sort the items in ascending or descending order with two other buttons. A little help/hint would be appreciated.
Thank you in advance, if my post is not that clear please do tell
Here is my code:
My code
let addItem = document.querySelector('.add-item');
let input = document.querySelector('input[type="text"]');
let table = document.querySelector('.list');
let tbody = document.querySelector('.tbody');
let ascBtn = document.querySelector('.btn-asc');
let descBtn = document.querySelector('.btn-desc');
// let itemsToSort = document.querySelector('.shopping-list-item');
// let shoppingItems = [];
addItem.addEventListener('click', addItemToList);
// ascBtn.addEventListener('click', ascend);
// descBtn.addEventListener('click', descend);
function addItemToList() {
if (input.value !== '') {
let tableRow = document.createElement('tr');
let firstTableData = document.createElement('td');
let secondTableData = document.createElement('td');
let actionBtn = document.createElement('button');
actionBtn.className = 'action-btn btn';
actionBtn.innerText = 'Mark as buyed';
firstTableData.className = 'shopping-list-item';
tbody.appendChild(tableRow);
firstTableData.innerHTML = input.value;
tableRow.appendChild(firstTableData);
secondTableData.appendChild(actionBtn)
tableRow.appendChild(secondTableData);
input.value = '';
// console.log(firstTableData, secondTableData)
// shoppingItems.push(input.value);
}
}
input.addEventListener('keydown', keyPress);
function keyPress(e) {
if (input.value !== '' && e.keyCode == 13) {
let tableRow = document.createElement('tr');
let firstTableData = document.createElement('td');
let secondTableData = document.createElement('td');
let actionBtn = document.createElement('button');
actionBtn.className = 'action-btn btn';
actionBtn.innerText = 'Mark as buyed';
firstTableData.className = 'shopping-list-item';
tbody.appendChild(tableRow);
firstTableData.innerHTML = input.value;
tableRow.appendChild(firstTableData);
secondTableData.appendChild(actionBtn)
tableRow.appendChild(secondTableData);
input.value = '';
}
}
table.addEventListener('click', function (e) {
// console.log(e.target)
// console.log('this works')
let dynamicTd = document.querySelectorAll('.shopping-list-item');
if (e.target && e.target.className == 'action-btn btn') {
// console.log('this works too');
for (let i = 0; i < dynamicTd.length; i++) {
console.log(dynamicTd[i]);
dynamicTd[i].className = 'checked';
}
}
})
// function ascend(a, b) {
// let item = shoppingItems.value;
// console.log(item);
// }
// function descend() {
// }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Verdana, Geneva, Tahoma, sans-serif;
color: #6c757d;
}
.shopping-list {
width: 1000px;
margin: 0 auto;
background-color: #2a9d8f;
margin-top: 50px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 5px;
}
.btn {
display: inline-block;
}
.header {
text-align: center;
font-size: 60px;
font-weight: 200;
background-color: #f9f9f9;
width: 100%;
padding: 20px 0;
}
.form {
width: 500px;
padding: 30px 0;
}
.form input {
width: 60%;
padding: 10px 15px;
border-radius: 5px;
outline: none;
}
.form button {
width: 30%;
padding: 10px 10px;
margin-left: 20px;
border-radius: 5px;
outline: none;
}
.btn-container {
width: 50%;
}
.btn-container .btn {
width: 30%;
padding: 10px;
border-radius: 5px;
outline: none;
}
.list {
background-color: #a8dadc;
width: 50%;
margin-top: 20px;
border-radius: 5px;
padding: 10px 20px;
}
.list th {
padding: 10px 0;
}
.list td {
width: 60%;
}
.action-btn {
width: 100%;
padding: 10px;
border-radius: 5px;
outline: none;
}
.checked {
text-decoration: line-through;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Shopping List</title>
</head>
<body>
<section class="shopping-list">
<h2 class="header">Shopping List</h2>
<!-- prevent from submitting and disappearing of items add onsubmit -->
<form class="form" onsubmit="return false">
<input type="text">
<!-- prevent from submitting and disappearing of items add type='button' -->
<button type="button" class="add-item btn">Add Item</button>
</form>
<div class="btn-container">
<button class="btn-asc btn">Sort asc</button>
<button class="btn-desc btn">Sort desc</button>
</div>
<table class="list">
<thead>
<tr>
<th>Item</th>
<th>Action</th>
</tr>
</thead>
<tbody class="tbody">
</tbody>
</table>
</section>
<script src="app.js"></script>
</body>
</html>
You can traverse up the DOM using Node.parentNode before selecting the td using querySelector to apply the checked class.
table.addEventListener("click", function (e) {
if (e.target && e.target.className == "action-btn btn") {
const td = e.target.parentNode.parentNode.querySelector("td")
if (td) {
td.className = "checked";
}
}
});
Codepen
You can replace your for loop within method added to table as click listener to: e.target.parentElement.previousSibling.className = 'checked'.
But it is not the best solution. A better way to do this would be to store list items as objects in an array and add the dataset to the delete button. Then you can simply remove that array element based on dataset attribute.

can't delete custom todo list item from todo list

I made a todo list with some predefined todo list items and I also made it possible to set your own todos inside the list through the prompt command. My problem with this code is, when I try to delete the items that I created, I can't do it. Though I can delete the predefined items. Here's the code:
let addTodo = document.getElementById('add');
let delTodo = document.getElementById('delete');
let ul = document.querySelector('ul');
let li = document.querySelectorAll('li');
addTodo.addEventListener('click', () => {
let add = prompt('Add a new todo');
if (add.length >= 1) {
let newLi = document.createElement('li');
let newLiText = document.createTextNode = add;
newLi.append(newLiText);
ul.appendChild(newLi);
} else {
alert('An error has occurred. Please try again.');
}
});
delTodo.addEventListener('click', () => {
let deleteTodo = prompt('Which todo do you want to delete?');
let firstTodo = document.querySelector('#first');
let secondTodo = document.querySelector('#second');
let thirdTodo = document.querySelector('#third');
if (deleteTodo === 'Study') {
ul.removeChild(firstTodo);
} else if (deleteTodo === 'Eat') {
ul.removeChild(secondTodo);
} else if (deleteTodo === 'Watch') {
thirdTodo.style.display = 'none';
} else {
alert('error occurrred');
}
});
body {
margin: 0;
padding: 0;
background-color: coral;
font-family: 'Playfair Display', cursive;
letter-spacing: 0.1em;
color: firebrick;
}
.wrapper {
margin: 10px auto;
border: 3px solid firebrick;
max-width: 300px;
text-align: center;
}
.table {
margin: 20px auto;
padding: 10px;
}
.table ul {
list-style-type: none;
}
.table li {
text-align: left;
margin: 20px 0px 20px -40px;
border: 1px solid red;
padding: 10px;
}
.table h3 {
text-align: left;
}
button {
margin: 10px 10px;
padding: 5px;
font-family: 'Playfair Display';
background-color: coral;
color: firebrick;
border: 2px solid firebrick;
}
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Playfair+Display&display=swap" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<h1>Todo List</h1>
<div class="table">
<h3>List the todos here</h3>
<ul>
<li id="first">Study Math</li>
<li id="second">Eat Breakfast</li>
<li id="third">Watch a Movie</li>
</ul>
</div>
<button id="add">Add new todo</button>
<button id="delete">Delete todo</button>
</div>
<script src="sandbox.js"></script>
</body>
</html>
You haven't done anything wrong, you just haven't written the code to delete any TODOs.
I'm going to describe it for you, but you will need to research it and write the code.
When you add a new TODO, you need to set the id attribute on it.
You probably want to use the user-supplied name of the TODO with the spaces stripped out.
Then in your delete code, you strip the spaces out of the user input and look for an element with that as its id.
Don't do a massive if block. Just look for the element by the id, and if there is one that matches, remove it. If there is not one, do an alert to tell the user it is not found.
For the next level, put "__todo-" in front of the id when you create the TODOs, and show a drop-down box on the page to delete them, or otherwise (better UX), put a button on the TODO to allow the user to delete it.
To delete the todo you have to first identify it. For that add id attribute to the li element.
The below solution can fail if two todos have the same name because your IDs must be unique.
For that, you have to find a way to uniquely identify an element(li) while creating it dynamically.
let addTodo = document.getElementById('add');
let delTodo = document.getElementById('delete');
let ul = document.querySelector('ul');
let li = document.querySelectorAll('li');
addTodo.addEventListener('click', () => {
let add = prompt('Add a new todo');
if (add && add.length >= 1) {
let newLi = document.createElement('li');
newLi.id = add.trim();
let newLiText = document.createTextNode(add);
newLi.append(newLiText);
ul.appendChild(newLi);
} else {
alert('TODO name cannot be empty');
}
});
delTodo.addEventListener('click', () => {
let deleteTodo = prompt('Which todo do you want to delete?');
let toDeleteId = deleteTodo.toLowerCase().trim();
let toDeleteNode = document.querySelector(`#${toDeleteId}`);
if (toDeleteNode) {
ul.removeChild(toDeleteNode)
} else {
alert("TODO not found")
}
});
body {
margin: 0;
padding: 0;
background-color: coral;
font-family: 'Playfair Display', cursive;
letter-spacing: 0.1em;
color: firebrick;
}
.wrapper {
margin: 10px auto;
border: 3px solid firebrick;
max-width: 300px;
text-align: center;
}
.table {
margin: 20px auto;
padding: 10px;
}
.table ul {
list-style-type: none;
}
.table li {
text-align: left;
margin: 20px 0px 20px -40px;
border: 1px solid red;
padding: 10px;
}
.table h3 {
text-align: left;
}
button {
margin: 10px 10px;
padding: 5px;
font-family: 'Playfair Display';
background-color: coral;
color: firebrick;
border: 2px solid firebrick;
}
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Playfair+Display&display=swap" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<h1>Todo List</h1>
<div class="table">
<h3>List the todos here</h3>
<ul>
<li id="first">Study Math</li>
<li id="second">Eat Breakfast</li>
<li id="third">Watch a Movie</li>
</ul>
</div>
<button id="add">Add new todo</button>
<button id="delete">Delete todo</button>
</div>
</body>
</html>
You are not doing it dynamically, what I would do is get all li and find a text containing the text user inputted in the prompt, and remove the element afterwards.
let addTodo = document.getElementById('add');
let delTodo = document.getElementById('delete');
let ul = document.querySelector('ul');
let li = document.querySelectorAll('li');
addTodo.addEventListener('click', () => {
let add = prompt('Add a new todo');
if (add.length >= 1) {
let newLi = document.createElement('li');
let newLiText = document.createTextNode = add;
newLi.append(newLiText);
ul.appendChild(newLi);
} else {
alert('An error has occurred. Please try again.');
}
});
delTodo.addEventListener('click', () => {
let deleteTodo = prompt('Which todo do you want to delete?');
const listItems = document.querySelectorAll('li');
let listItemToRemove = null;
for (let li of listItems) {
if (li.innerText === deleteTodo) {
listItemToRemove = li;
}
}
if (listItemToRemove) {
listItemToRemove.remove();
}
});
body {
margin: 0;
padding: 0;
background-color: coral;
font-family: 'Playfair Display', cursive;
letter-spacing: 0.1em;
color: firebrick;
}
.wrapper {
margin: 10px auto;
border: 3px solid firebrick;
max-width: 300px;
text-align: center;
}
.table {
margin: 20px auto;
padding: 10px;
}
.table ul {
list-style-type: none;
}
.table li {
text-align: left;
margin: 20px 0px 20px -40px;
border: 1px solid red;
padding: 10px;
}
.table h3 {
text-align: left;
}
button {
margin: 10px 10px;
padding: 5px;
font-family: 'Playfair Display';
background-color: coral;
color: firebrick;
border: 2px solid firebrick;
}
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Playfair+Display&display=swap" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<h1>Todo List</h1>
<div class="table">
<h3>List the todos here</h3>
<ul>
<li id="first">Study Math</li>
<li id="second">Eat Breakfast</li>
<li id="third">Watch a Movie</li>
</ul>
</div>
<button id="add">Add new todo</button>
<button id="delete">Delete todo</button>
</div>
<script src="sandbox.js"></script>
</body>
</html>

Categories

Resources