This question already has answers here:
How do I detect a click outside an element?
(91 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
My current code opens up an input via a click by adding a class. I'm having trouble adding a second click that removes the added class when the user clicks off the input. I added a second click event but it just stops my first click event from working.
Is there a different way to approach this using pure JavaScript?
(Commented out failed attempt.)
let searchElement = document.querySelector('#searchElement');
let offCanvas = document.querySelector('#main');
searchElement.addEventListener('click', () => {
searchElement.classList.add('extendInputSearch');
});
// offCanvas.addEventListener('click', () => {
// searchElement.classList.remove('extendInputSearch');
// });
* {
padding: 0;
margin: 0;
}
html,
body {
height: 100%;
width: 100%;
}
main {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: #e1e2f1;
}
form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
input {
width: 100%;
height: 32px;
border: none;
outline: none;
font-size: 1rem;
}
.inputSearch {
display: flex;
align-items: center;
width: 100%;
max-width: 15px;
padding: 8px 20px;
background-color: #ffffff;
border: 1px solid transparent;
border-radius: 6px;
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.inputSearch:hover {
border: 1px solid #7e51fa;
}
.inputSearch i {
color: #7e51fa;
margin-right: 20px;
}
.extendInputSearch {
max-width: 400px;
}
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
<main id="main">
<form>
<div id="searchElement" class="inputSearch">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search">
</div>
</form>
</main>
The problem with your attempt is that the event listener to remove the class is applied to the entire #main element, which includes searchElement. That means both event listeners are applied to the searchElement, and when you click on the searchElement, the class is first added (with the first listener) and then removed (with the second listener).
To make it work, you need to change the second listener to specifically exclude the searchElement. For example, in this code, we add a click listener to the whole document. In the listener, we check if the click is outside the searchElement by using the Node.contains() function on the event parameter. If the clicked element is not a child of searchElement (that is, the click is outside searchElement), we remove the extendInputSearch class from searchElement.
let searchElement = document.querySelector('#searchElement');
searchElement.addEventListener('click', () => {
searchElement.classList.add('extendInputSearch');
});
document.addEventListener('click', (e) => {
if(!searchElement.contains(e.target)){
searchElement.classList.remove('extendInputSearch');
}
});
* {
padding: 0;
margin: 0;
}
html,
body {
height: 100%;
width: 100%;
}
main {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: #e1e2f1;
}
form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
input {
width: 100%;
height: 32px;
border: none;
outline: none;
font-size: 1rem;
}
.inputSearch {
display: flex;
align-items: center;
width: 100%;
max-width: 15px;
padding: 8px 20px;
background-color: #ffffff;
border: 1px solid transparent;
border-radius: 6px;
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.inputSearch:hover {
border: 1px solid #7e51fa;
}
.inputSearch i {
color: #7e51fa;
margin-right: 20px;
}
.extendInputSearch {
max-width: 400px;
}
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
<main id="main">
<form>
<div id="searchElement" class="inputSearch">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search">
</div>
</form>
</main>
try it:
let searchElement = document.querySelector('#searchElement');
let offCanvas = document.querySelector('#main');
searchElement.addEventListener('click', () => {
searchElement.classList.add('extendInputSearch');
});
document.addEventListener('click', (e) => {
const searchElement = document.querySelector('#searchElement');
if (e.target != searchElement && e.target != searchElement.querySelector('i') && e.target != searchElement.querySelector('input')) {
searchElement.classList.remove('extendInputSearch');
}
});
* {
padding: 0;
margin: 0;
}
html,
body {
height: 100%;
width: 100%;
}
main {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: #e1e2f1;
}
form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
input {
width: 100%;
height: 32px;
border: none;
outline: none;
font-size: 1rem;
}
.inputSearch {
display: flex;
align-items: center;
width: 100%;
max-width: 15px;
padding: 8px 20px;
background-color: #ffffff;
border: 1px solid transparent;
border-radius: 6px;
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.inputSearch:hover {
border: 1px solid #7e51fa;
}
.inputSearch i {
color: #7e51fa;
margin-right: 20px;
}
.extendInputSearch {
max-width: 400px;
}
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
<main id="main">
<form>
<div id="searchElement" class="inputSearch">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search">
</div>
</form>
</main>
Related
let myLibrary = [
{
id: 0,
title: "The Once and Future King",
author: "White",
pages: 654,
read: false,
},
{
id: 1,
title: "The Hobbit",
author: "Tolkien",
pages: 304,
read: false,
},
];
const bookContent = document.getElementById("content");
function displayBook(book) {
const addBook = document.createElement("div");
addBook.className = "book";
addBook.id = book.id;
bookContent.appendChild(addBook);
addBook.innerHTML = `
<div class="title">
<p class="bookTitle">
<span>${book.title}</span>
</p>
</div>
<div class="body">
<p>
Author: <span>${book.author}</span>
</p>
<p>
Pages: <span>${book.pages}</span>
</p>
</div>
<div class="read">
<label class="switch" data-book="0">
<input type="checkbox" />
<span class="slider round"></span>
</label>
</div>
<div class="delete">
<button class="delete-btn">DELETE</button>
</div>`;
}
// Display your original object list
myLibrary.forEach((book) => {
displayBook(book);
});
// Handle your object creation
const form = document.getElementById("form");
form.addEventListener("submit", updateLibrary);
function updateLibrary(event) {
// Need this so it doesn't refresh page
event.preventDefault();
const title = document.getElementById("title").value;
const author = document.getElementById("author").value;
const pages = document.getElementById("pages").value;
const book = {
title: title,
author: author,
pages: parseInt(pages),
read: false,
};
// Adds object to array
myLibrary.push(book);
// Displays new book
displayBook(book);
// Reset form
resetForm();
// Close form
document.getElementById("addBookForm").style.display = "none";
console.log(myLibrary);
}
// Resets the form so user can input another book
function resetForm() {
document.getElementById("form").reset();
}
// The form is automatically set to hidden. This loads it up for the user
const openForm = function () {
document.getElementById("addBookForm").style.display = "block";
document.getElementById("title").focus();
};
// Sets the form display back to none
const closeForm = () =>
(document.getElementById("addBookForm").style.display = "none");
/* .main {
} */
.header {
display: flex;
flex-direction: column;
background-color: #c689c6;
height: 150px;
border: 1px solid #3b133b;
}
.btn {
margin: 0 auto;
margin-top: 55px;
display: block;
text-align: center;
background-color: #4649ff;
padding: 0.75rem 1.25rem;
border-radius: 10rem;
color: #fff;
cursor: pointer;
font-size: 1rem;
letter-spacing: 0.15rem;
transition: all 0.3s;
}
.btn:hover {
background-color: #3134fa;
}
.content {
display: flex;
flex-flow: row wrap;
align-content: flex-start;
justify-content: flex-start;
background-color: #fffdfa;
height: auto;
}
.book {
border: 2px solid #ffa94d;
background-color: #ffd8a8;
color: #d9480f;
width: 280px;
height: 365px;
margin: 10px;
}
.title {
border-bottom: 2px solid #ffa94d;
}
.title p {
display: flex;
align-items: center;
text-align: center;
justify-content: center;
font-size: larger;
}
.title span {
color: #3c4048;
}
.body {
border: 1px solid transparent;
height: 200px;
background-color: #fff4e6;
}
.body p {
padding-left: 20px;
}
p span {
color: #3c4048;
}
.read {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
border-top: 2px solid #ffa94d;
text-align: center;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: #3d8361;
}
input:focus + .slider {
box-shadow: 0 0 1px #3d8361;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.delete {
height: 50px;
border-top: 2px solid #ffa94d;
}
.delete-btn {
margin: 0 auto;
margin-top: 8px;
display: block;
text-align: center;
background-color: #e94560;
padding: 0.5rem 0.75rem;
border-radius: 10rem;
color: #fff;
cursor: pointer;
letter-spacing: 0.15rem;
transition: all 0.3s;
}
.delete-btn:hover {
background-color: #e7082d;
}
.close-btn {
color: #e7082d;
font-size: large;
background-color: #c689c6;
border: none;
float: right;
cursor: pointer;
}
/* THE FORM */
.form-content {
display: flex;
justify-content: center;
}
.form {
display: none;
position: fixed;
margin-top: 5px;
border: 2px solid #3b133b;
animation: openForm 0.5s;
z-index: 1;
}
#keyframes openForm {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
.form h1 {
text-align: center;
}
.form-container {
background-color: #c689c6;
border: 2px solid black;
max-width: 300px;
padding: 10px;
}
.form-container h1 {
padding-left: 20px;
}
.form-container input[type="text"],
.form-container input[type="number"] {
width: 80%;
padding: 15px;
margin: 5px 0 22px 0;
border: none;
}
.form-container input[type="text"]:focus,
.form-container input[type="number"]:focus {
outline: none;
}
<!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="styles.css" />
<title>Library</title>
</head>
<body>
<div class="main">
<div class="header">
<button class="btn" id="btn" onclick="openForm()">Add a Book</button>
</div>
<div class="form-content">
<div class="form" id="addBookForm">
<form id="form" action="" class="form-container">
<button type="button" class="close-btn" onclick="closeForm()">
x
</button>
<h1>Add a Book</h1>
<label for="title">Title</label>
<input
type="text"
placeholder="Title"
name="title"
id="title"
required
/>
<label for="author">Author</label>
<input
type="text"
placeholder="Author"
name="author"
id="author"
required
/>
<label for="pages">Pages</label>
<input
type="number"
placeholder="Pages"
name="pages"
required
id="pages"
/>
<button type="submit" id="submit-btn">Submit</button>
</form>
</div>
</div>
<div id="content" class="content"></div>
</div>
</body>
</html>
I have a form where the user inputs information from a book they are reading and upon hitting submit, the information is sent as its own object inside an array. I also have a forEach method running which loops through the array and displays each object as a div on the web page.
let myLibrary = [];
const book = {
title: title,
author: author,
pages: parseInt(pages),
read: false,
};
myLibrary.push(book)
As you can see from the code above, the three properties that the user fills out are the books title, author and page count. There's also a property that is automatically added called the read property and it is automatically set as false.
The Problem
My problem is this. I have the following code displayed at the bottom of each div.
<div class="read">
<label class="switch">
<input type="checkbox" />
<span class="slider round"></span>
</label>
</div>
This code is very simple. It's a toggle switch which I found here.
I want it so when the toggle switch is grayed out, the read status is set to false. But when the toggle switch is turned on, the read property is set to true. I am having a very difficult time figuring out how to get this done.
What I've Tried
I was able to use an onclick to select the toggle switch's parent element, and I tested it using console.log however I am unsure of where to go from there. I attempted to update the book.read to true using the ternary operator but it was out of scope and resulted in an error.
document.querySelector(".main").onclick = (ev) => {
let el = ev.target.classList.contains("switch")
? ev.target.parentElement
: ev.target.classList.contains("slider")
? ev.target
: false;
if (el) {
let toggle = el.parentElement.parentElement.parentElement;
let index = [...toggle.parentElement.children].indexOf(toggle);
myLibrary[index].read = false ? false : true;
console.log(myLibrary[index].read);
}
console.log(myLibrary);
};
Change this
function displayBook(book) {
...
<label class="switch" data-book="0">
to
function displayBook(book,bookIndex) {
...
<label class="switch" data-book="${bookIndex}">
and
myLibrary.forEach((book) => {
displayBook(book);
});
to
myLibrary.forEach((book,i) => {
displayBook(book,i);
});
lastly change
// Displays new book
displayBook(book);
to
// Displays new book
displayBook(book,myLibrary.length-1);
Here is the code inclusive a delete function
It would be slightly simpler if we had an associate array on book_id
Note I removed the numeric ID because it is not needed since the index of the array is the same
let myLibrary = [{
title: "The Once and Future King",
author: "White",
pages: 654,
read: false,
},
{
title: "The Hobbit",
author: "Tolkien",
pages: 304,
read: false,
},
];
const bookContent = document.getElementById("content");
const formDiv = document.getElementById("addBookForm");
function displayBook(book, idx) {
const addBook = document.createElement("div");
addBook.className = "book";
addBook.id = `book_${idx}`;
bookContent.appendChild(addBook);
addBook.innerHTML = `
<div class="title">
<p class="bookTitle">
<span>${book.title}</span>
</p>
</div>
<div class="body">
<p>
Author: <span>${book.author}</span>
</p>
<p>
Pages: <span>${book.pages}</span>
</p>
</div>
<div class="read">
<label class="switch" data-book="${idx}">
<input type="checkbox" />
<span class="slider round"></span>
</label>
</div>
<div class="delete">
<button class="delete-btn">DELETE</button>
</div>`;
}
// Display your original object list
myLibrary.forEach((book, i) => {
displayBook(book, i);
});
const deleteBook = (e) => {
const parent = e.target.closest("div.book");
const idx = parent.querySelector(".switch").dataset.book;
parent.remove();
console.log(idx);
myLibrary.splice(idx, 1);
console.log(myLibrary);
content.querySelectorAll("div.book").forEach((book, i) => { // reset the hard way
book.id = `book_${i}`;
book.querySelector("label.switch").dataset.book = i;
})
};
content.addEventListener("click", function(e) {
const tgt = e.target;
if (!tgt.matches(".delete-btn")) return; // not the delete
deleteBook(e); // pass the event to the delete
})
// Handle your object creation
const form = document.getElementById("form");
form.addEventListener("submit", updateLibrary);
function updateLibrary(event) {
// Need this so it doesn't refresh page
event.preventDefault();
const title = document.getElementById("title").value;
const author = document.getElementById("author").value;
const pages = document.getElementById("pages").value;
const book = {
title: title,
author: author,
pages: parseInt(pages),
read: false,
};
// Adds object to array
myLibrary.push(book);
// Displays new book
displayBook(book);
// Reset form
resetForm();
// Close form
formDiv.style.display = "none";
console.log(myLibrary);
}
// Resets the form so user can input another book
function resetForm() {
document.getElementById("form").reset();
}
// The form is automatically set to hidden. This loads it up for the user
const openForm = function() {
formDiv.style.display = "block";
document.getElementById("title").focus();
};
// Sets the form display back to none
const closeForm = () => formDiv.style.display = "none";
/* .main {
} */
.header {
display: flex;
flex-direction: column;
background-color: #c689c6;
height: 150px;
border: 1px solid #3b133b;
}
.btn {
margin: 0 auto;
margin-top: 55px;
display: block;
text-align: center;
background-color: #4649ff;
padding: 0.75rem 1.25rem;
border-radius: 10rem;
color: #fff;
cursor: pointer;
font-size: 1rem;
letter-spacing: 0.15rem;
transition: all 0.3s;
}
.btn:hover {
background-color: #3134fa;
}
.content {
display: flex;
flex-flow: row wrap;
align-content: flex-start;
justify-content: flex-start;
background-color: #fffdfa;
height: auto;
}
.book {
border: 2px solid #ffa94d;
background-color: #ffd8a8;
color: #d9480f;
width: 280px;
height: 365px;
margin: 10px;
}
.title {
border-bottom: 2px solid #ffa94d;
}
.title p {
display: flex;
align-items: center;
text-align: center;
justify-content: center;
font-size: larger;
}
.title span {
color: #3c4048;
}
.body {
border: 1px solid transparent;
height: 200px;
background-color: #fff4e6;
}
.body p {
padding-left: 20px;
}
p span {
color: #3c4048;
}
.read {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
border-top: 2px solid #ffa94d;
text-align: center;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked+.slider {
background-color: #3d8361;
}
input:focus+.slider {
box-shadow: 0 0 1px #3d8361;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.delete {
height: 50px;
border-top: 2px solid #ffa94d;
}
.delete-btn {
margin: 0 auto;
margin-top: 8px;
display: block;
text-align: center;
background-color: #e94560;
padding: 0.5rem 0.75rem;
border-radius: 10rem;
color: #fff;
cursor: pointer;
letter-spacing: 0.15rem;
transition: all 0.3s;
}
.delete-btn:hover {
background-color: #e7082d;
}
.close-btn {
color: #e7082d;
font-size: large;
background-color: #c689c6;
border: none;
float: right;
cursor: pointer;
}
/* THE FORM */
.form-content {
display: flex;
justify-content: center;
}
.form {
display: none;
position: fixed;
margin-top: 5px;
border: 2px solid #3b133b;
animation: openForm 0.5s;
z-index: 1;
}
#keyframes openForm {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
.form h1 {
text-align: center;
}
.form-container {
background-color: #c689c6;
border: 2px solid black;
max-width: 300px;
padding: 10px;
}
.form-container h1 {
padding-left: 20px;
}
.form-container input[type="text"],
.form-container input[type="number"] {
width: 80%;
padding: 15px;
margin: 5px 0 22px 0;
border: none;
}
.form-container input[type="text"]:focus,
.form-container input[type="number"]:focus {
outline: none;
}
<div class="main">
<div class="header">
<button class="btn" id="btn" onclick="openForm()">Add a Book</button>
</div>
<div class="form-content">
<div class="form" id="addBookForm">
<form id="form" action="" class="form-container">
<button type="button" class="close-btn" onclick="closeForm()">
x
</button>
<h1>Add a Book</h1>
<label for="title">Title</label>
<input type="text" placeholder="Title" name="title" id="title" required />
<label for="author">Author</label>
<input type="text" placeholder="Author" name="author" id="author" required />
<label for="pages">Pages</label>
<input type="number" placeholder="Pages" name="pages" required id="pages" />
<button type="submit" id="submit-btn">Submit</button>
</form>
</div>
</div>
<div id="content" class="content"></div>
</div>
I have a todo list and I want to have editable task so I gave contenteditable = "true" attribute to p tag which contains the task content but when I left click on it, it does not work but when I right click it works! I gave this attribute to heading and it works just fine but its not working on tasks( p tag).
so my question is how can I make tasks editable when I click on them?(not by right click I want left click)
here is my code, please first make a task buy clicking on + button
const taskInput = $(".main__input");
const taskRow = $(".task-row");
const task = $("li");
let id = 0;
// adding task
const addTask = function() {
const markup = `
<li data-id=${id}>
<div class="main">
<div class="task__checkbox">
<input type="checkbox" class="checkbox">
</div>
<p class="task" contenteditable="true">${taskInput.val()}</p>
</div>
<div class="btn-task btn-delete icon" data-id=${id}>
<i class="fa-sharp fa-solid fa-trash"></i>
</div>
</li>
`;
taskRow.append(markup);
id++;
taskInput.val("");
};
$(".btn-plus").on("click", function() {
if (taskInput.val() !== "") addTask();
});
// remove element first solution
taskRow.on("click", ".btn-delete", function(e) {
$(this).parent().remove();
});
taskRow.on("change", ".task__checkbox", function() {
$(this).siblings(".task").toggleClass("checked");
});
$(window).on("keypress", function(e) {
if (e.which === 13 && taskInput.val() !== "") addTask();
});
taskRow.sortable();
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
list-style: none;
}
html {
font-size: 62.5%;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.todo {
width: 40rem;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 0 10px rgb(122, 122, 122);
}
h1 {
background-color: rgb(46, 89, 89);
font-size: 3rem;
font-weight: bold;
color: #fff;
padding: 2rem 1rem;
text-align: center;
}
.row {
display: flex;
}
.main__input {
padding: 1.4rem;
border: none;
/* border-bottom: 1px solid rgb(116, 116, 116);
border-left: 1px solid rgb(116, 116, 116); */
width: 100%;
}
.main__input:focus {
outline: none;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(37, 115, 115);
color: #fff;
width: 20%;
font-size: 2rem;
transition: all 0.3s;
}
.icon:hover {
cursor: pointer;
background-color: rgb(52, 81, 81);
}
.icon:active {
background-color: rgb(37, 115, 115);
}
.task-row {
display: flex;
}
.task-block {
width: 80%;
display: flex;
}
.task__checkbox {
width: 10%;
}
.main {
display: flex;
width: 80%;
}
.task__checkbox {
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(37, 115, 115);
padding: 1rem;
}
.checkbox {
width: 1.8rem;
height: 1.8rem;
}
.task {
font-size: 1.8rem;
font-weight: bold;
padding: 1.4rem 1rem;
border: 0.01px solid #999;
width: 100%;
}
.task-row {
flex-direction: column;
}
li {
width: 100%;
display: flex;
}
.checked {
text-decoration: line-through;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js"></script>
<div class="todo">
<h1 class="heading" contenteditable="true">To-do list</h1>
<div class="todo-body">
<div class="row add-block">
<div class="main">
<input type="text" class="main__input" placeholder="A task you want to compelete">
</div>
<div class="btn-plus icon">
<i class="fa-solid fa-plus"></i>
</div>
</div>
<ul class="row task-row"></ul>
</div>
</div>
As #cbroe and I have mentioned in the comments, the behavior is caused by jQuery UI Sortable Plugin.
The fix is fairly easy, we'll simply use the cancel option of the sortable method to prevent sorting if you start on elements matching the selector. Source: Sortable Widget - jQuery UI
We will pass the p[conteneditable] selector (or any other selector you see fit) to prevent dragging from the p tag that contains the todo text.
Here's a live demo:
const taskInput = $(".main__input");
const taskRow = $(".task-row");
const task = $("li");
let id = 0;
// adding task
const addTask = function() {
const markup = `
<li data-id=${id}>
<div class="main">
<div class="task__checkbox">
<input type="checkbox" class="checkbox">
</div>
<p class="task" contenteditable="true">${taskInput.val()}</p>
</div>
<div class="btn-task btn-delete icon" data-id=${id}>
<i class="fa-sharp fa-solid fa-trash"></i>
</div>
</li>
`;
taskRow.append(markup);
id++;
taskInput.val("");
};
$(".btn-plus").on("click", function() {
if (taskInput.val() !== "") addTask();
});
// remove element first solution
taskRow.on("click", ".btn-delete", function(e) {
$(this).parent().remove();
});
taskRow.on("change", ".task__checkbox", function() {
$(this).siblings(".task").toggleClass("checked");
});
$(window).on("keypress", function(e) {
if (e.which === 13 && taskInput.val() !== "") addTask();
});
/** drag & drop is disabled on p tags having content attribute set. You still able to move the item around by dragging from elsewhere (from the delete button for example) */
taskRow.sortable({
cancel: 'p[contenteditable=true]'
});
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
list-style: none;
}
html {
font-size: 62.5%;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.todo {
width: 40rem;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 0 10px rgb(122, 122, 122);
}
h1 {
background-color: rgb(46, 89, 89);
font-size: 3rem;
font-weight: bold;
color: #fff;
padding: 2rem 1rem;
text-align: center;
}
.row {
display: flex;
}
.main__input {
padding: 1.4rem;
border: none;
/* border-bottom: 1px solid rgb(116, 116, 116);
border-left: 1px solid rgb(116, 116, 116); */
width: 100%;
}
.main__input:focus {
outline: none;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(37, 115, 115);
color: #fff;
width: 20%;
font-size: 2rem;
transition: all 0.3s;
}
.icon:hover {
cursor: pointer;
background-color: rgb(52, 81, 81);
}
.icon:active {
background-color: rgb(37, 115, 115);
}
.task-row {
display: flex;
}
.task-block {
width: 80%;
display: flex;
}
.task__checkbox {
width: 10%;
}
.main {
display: flex;
width: 80%;
}
.task__checkbox {
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(37, 115, 115);
padding: 1rem;
}
.checkbox {
width: 1.8rem;
height: 1.8rem;
}
.task {
font-size: 1.8rem;
font-weight: bold;
padding: 1.4rem 1rem;
border: 0.01px solid #999;
width: 100%;
}
.task-row {
flex-direction: column;
}
li {
width: 100%;
display: flex;
}
.checked {
text-decoration: line-through;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js"></script>
<div class="todo">
<h1 class="heading" contenteditable="true">To-do list</h1>
<div class="todo-body">
<div class="row add-block">
<div class="main">
<input type="text" class="main__input" placeholder="A task you want to compelete">
</div>
<div class="btn-plus icon">
<i class="fa-solid fa-plus"></i>
</div>
</div>
<ul class="row task-row"></ul>
</div>
</div>
I also recommend looking at portlets as they provide a better way (in my opinion) to introduce drag & drop into your elements.
I'm trying to create a project card with user info when the user clicks on a button. When the user clicks the new project button, a modal form pops up that takes the user info and has a create button. The program should add a new project card whenever the user clicks the create button. To achieve this I added a click event listener to the Add new project button and another to Create button. I nested the create event listener inside the add new project event listener.
Here's the event listener.
addTileBtn.addEventListener("click", (e) => {
e.preventDefault();
modal.style.display = "block";
const titleField = document.querySelector("#title");
const descriptionField = document.querySelector("#description");
const create = document.querySelector("#create");
const close = document.querySelector("#close");
create.addEventListener("click", (e) => {
e.preventDefault();
title = titleField.value;
description = descriptionField.value;
function createProjectTile() {
const projectTile = document.createElement("div");
projectTile.classList.add("cards-grid__tile");
projectTile.textContent = title;
console.log(title, description);
return projectTile;
}
cardsGrid.appendChild(createProjectTile());
modal.style.display = "none";
});
close.addEventListener("click", (e) => {
e.preventDefault();
modal.style.display = "none";
});
});
The problem is that when I create the first card it works fine. But the second time, it creates two cards and 3 cards on the 3rd time and so on.
Here is the JSFiddle link for the full code.
I've edited your code, this should be what you wanted:
const logoutBtn = document.querySelector("#logout");
const addTileBtn = document.querySelector("#add-tile");
const cardsGrid = document.querySelector(".cards-grid");
const modal = document.querySelector(".modal");
const titleField = document.querySelector("#title");
const descriptionField = document.querySelector("#description");
const create = document.querySelector("#create");
const close = document.querySelector("#close");
addTileBtn.addEventListener("click", (e) => {
e.preventDefault();
modal.style.display = "block";
titleField.value = "";
descriptionField.value = "";
});
create.addEventListener("click", (e) => {
e.preventDefault();
title = titleField.value;
description = descriptionField.value;
function createProjectTile() {
const projectTile = document.createElement("div");
projectTile.classList.add("cards-grid__tile");
projectTile.textContent = title;
console.log(title, description);
return projectTile;
}
cardsGrid.appendChild(createProjectTile());
modal.style.display = "none";
});
close.addEventListener("click", (e) => {
e.preventDefault();
modal.style.display = "none";
});
:root {
--main-red: #be3144;
--main-white: #f0f0f0;
--main-gray: #303841;
--main-blue: #45567d;
--main-blue3: #1c262f;
--main-blue2: #27333d;
--main-blue1: #2e3d49;
--main-light-blue: #02b3e4;
--main-black: #000000;
--main-light-black: #3a3d40;
--main-dark-black: #181719;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
img {
display: block;
max-width: 100%;
height: auto;
}
body {
font-family: "Poppins", sans-serif;
font-size: 1.8rem;
font-weight: 400;
line-height: 1.4;
color: var(--main-white);
}
h1,
h2 {
font-family: "Raleway", sans-serif;
font-weight: 700;
text-align: center;
}
h1 {
font-size: 3.5rem;
}
h2 {
font-size: 2rem;
color: var(--main-blue);
display: block;
}
p {
font-size: 1.8rem;
font-weight: 200;
font-style: italic;
color: var(--main-white);
}
a {
text-decoration: none;
text-align: center;
display: block;
}
.main {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 100vh;
background-color: var(--main-white);
}
.box {
border: none;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%), 0 8px 16px rgb(0 0 0 / 10%);
border-radius: 8px;
background-color: #fff;
}
.box__fields {
padding: 20px;
}
.box-field {
margin-bottom: 20px;
display: flex;
justify-content: center;
}
.icon {
position: absolute;
padding: 10px;
color: gray;
min-width: 50px;
text-align: center;
font-size: 20px;
top: 50%;
transform: translateY(-50%);
}
input,
textarea {
font-size: 17px;
padding: 14px 16px;
width: 300px;
cursor: text;
border: 1px solid var(--main-gray);
border-radius: 6px;
outline: none;
padding-left: 45px;
}
.box-btn {
border: none;
border-radius: 6px;
font-size: 20px;
line-height: 48px;
padding: 0 16px;
}
.app,
.main-content {
height: 100%;
}
.title-area {
background-color: var(--main-blue3);
width: 100%;
font-size: 18px;
min-height: 60px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
position: fixed;
top: 0;
left: 0;
}
.title-area__item {
padding: 10px 30px;
}
.logout-btn {
border: none;
border-radius: 6px;
font-size: 20px;
line-height: 30px;
padding: 0 16px;
width: 100px;
cursor: pointer;
color: #fff;
background-color: var(--main-light-blue);
}
.logout-btn:hover {
background-color: #029be4;
}
.content-area {
margin-top: 60px;
width: 100%;
height: 100%;
overflow: auto;
background-color: var(--main-blue);
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 50px;
margin-bottom: 3rem;
padding: 5rem 3rem;
}
.cards-grid__tile {
background-color: var(--main-blue2);
max-width: 200px;
padding: 30px;
text-align: center;
font-size: 1.2rem;
}
.add-tile-btn {
border: none;
border-radius: 6px;
font-size: 15px;
line-height: 48px;
padding: 0 10px;
cursor: pointer;
color: #fff;
background-color: var(--main-light-blue);
}
.modal {
display: none;
width: 350px;
font-size: 1.2rem;
position: relative;
top: 10%;
left: 50%;
transform: translateX(-50%);
overflow: visible;
}
.box {
background-color: var(--main-blue1);
opacity: 0.95;
}
.box-field {
flex-direction: column;
}
input,
textarea {
padding: 5px 10px;
resize: none;
}
.wrapper {
position: relative;
text-align: center;
}
.create-btn {
width: 100px;
cursor: pointer;
color: #fff;
background-color: var(--main-light-blue);
}
.close {
cursor: pointer;
position: absolute;
top: 20px;
left: 290px;
}
#modal-form {
position: absolute;
}
.icon {
color: var(--main-blue3);
}
<div class="app">
<div class="nav-wrapper">
<nav id="nav" class="nav"></nav>
</div>
<div class="main-content">
<div class="title-area">
<div class="title-area__item">Menu</div>
<div class="title-area__item">Boards</div>
<div class="title-area__item">
<button id="logout" class="logout-btn btn">
Logout
</button>
</div>
</div>
<div class="content-area">
<div class="modal">
<form id="modal-form" class="box">
<a id="close" class="close">
<i class="fa fa-times icon">close</i>
</a>
<div class="box__fields">
<div class="box-field">
<label for="title"> Title </label>
<input
id="title"
type="text"
name="title"
required
autofocus
/>
</div>
<div class="box-field">
<label for="description">
Description
</label>
<textarea
id="description"
name="title"
rows="6"
cols="40"
></textarea>
</div>
<div class="box-field">
<div class="wrapper">
<button
id="create"
class="create-btn box-btn btn"
>
Create
</button>
</div>
</div>
</div>
</form>
</div>
<div class="cards-grid">
<div class="cards-grid__tile">
<button id="add-tile" class="add-tile-btn btn">
+ Add new project
</button>
</div>
</div>
</div>
</div>
</div>
I'm trying to create a chat, but after some number of messages the new message doesn't appear in screen and i want to overflow, to user scroll down the messages, but just a few messages appears and after that number nothing happens, just appears the previous messages in a static way, i'm using React and Socket.io.
Code:
const [messagesAndAuthors, setMessagesAndAuthors] = useState<any>([]);
useEffect(() => {
socket.on('receivedMessage', (newMessage:{}) => {
messagesAndAuthors([...messagesAndAuthors, newMessage])
});
})
function sendingMessages(e : FormEvent) {
e.preventDefault();
if(message.trim()) {
const messageObject = {
userName,
message,
roomId
};
socket.emit('sendMessage', messageObject);
}
setMessage('');
}
----------------------------BACK-END(SOCKET.IO):
socketInfo.on('sendMessage', (data:any) => {
socketInfo.broadcast.emit('receivedMessage', data);
});
.chat-container {
margin: 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
font: 400 1rem 'Sulphur Point';
background-color: #254441;
padding: 2rem 0;
border-radius: 2rem;
justify-content: space-between;
text-align: center;
max-height: 77.5vh;
overflow: auto;
}
.chat-container h1 {
background-color: #ff6f59;
padding: 0.2rem 4rem;
border-radius: 2rem;
text-align: center;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.messages-container {
text-align: start;
background-color: #254441;
border-radius: 2rem;
height: 90%;
margin-bottom: 0.5rem;
overflow: auto;
}
.messages-mine-style {
width: 15rem;
border: transparent;
border-radius: 1.5rem;
margin-top: 2rem;
text-decoration: none;
text-align: start;
list-style: none;
font: 700 1.4rem 'Sulphur Point';
padding: 0.2rem 0.6rem;
margin: 1rem 0;
}
.messages-mine-style li{
margin-left: 1rem;
}
.messages-mine-style li+li {
font: 400 1.4rem 'Sulphur Point';
}
.chat-container input {
background-color: #ff6f59;
padding: 1rem 4rem;
border-radius: 2rem;
text-align: center;
font: 700 1.2rem 'Sulphur Point';
outline: none;
border: transparent;
}
.chat-container input::placeholder {
color: #254441;
}
<form onSubmit={sendingMessages} className="chat-container">
<h1>Chat</h1>
<div className="messages-container">
{messagesAndAuthors.map((messageWithTheAuthor:any) => {
return (
<>
<ul className="messages-mine-style">
<li key={messageWithTheAuthor.author}>{messageWithTheAuthor.author}: </li>
<li>{messageWithTheAuthor.message}</li>
</ul>
</>
)
})}
</div>
<input value={message} onChange={(e) => {setMessage(e.target.value)}} type="text" placeholder="Digite sua mensagem"/>
</form>
Here's an example. You'll need to modify it to make it work for you, but it should serve as a good starting point.
function updateScroll() {
let el = document.getElementsByClassName("chat")[0];
let offset = el.scrollHeight - el.scrollTop;
if (offset < 120) el.scrollTop = el.scrollHeight;
}
var messageNumber = 1;
function addMessage() {
let msg = "<li class=\"chat-message\">new message "+ messageNumber++ +"</li>";
document.getElementsByClassName("chat-messages")[0].innerHTML += (msg);
updateScroll();
}
setInterval(addMessage, 2000);
addMessage();
.chat {
width: 250px;
height: 100px;
overflow: auto;
background-color: darkgray;
border: solid black 1px;
border-radius: 2px;
}
.chat-message {
list-style-type: none;
}
.chat-messages {
padding: 0px;
padding-left: 5px;
margin: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chat">
<ul class="chat-messages">
</ul>
</div>
This example only scrolls down if the user has scrolled down. If the user has scrolled up (to read previous messages, for example), then it will not scroll down.
I am trying to create an animation effect that moves two buttons when I click on them. I have the flex direction set up as a column in the container div and I essentially just want them to position as a flex row when I click on one of them (probably with a 1s animation). When I click on them currently nothing happens. Here is my code sample:
HTML
<header>
<div class="container">
<h1>Choose Your Allegiance</h1>
<div id="buttons">
<button class="fill"><img src="/assets/Jedi.png" alt="Jedi" /></button>
<button class="fill sith">
<img src="/assets/Sith.png" alt="Sith" />
</div>
</button>
</div>
</header>
CSS
.container {
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
}
.container.click {
flex-direction: row;
justify-content: space-around;
}
h1 {
margin-left: 5vw;
color: black;
font-family: "Poller One", cursive;
font-variant: small-caps;
font-size: 3rem;
margin-top: 6vh;
}
button {
color: white;
transition: 0.25s;
float: left;
margin: 2%;
}
button:hover,
button:focus {
border: 2px solid red;
color: black;
}
.fill {
height: 120px;
width: 150px;
background: transparent;
margin-top: 4vh;
outline: none;
border: none;
}
.fill:hover,
.fill:focus {
box-shadow: inset 0 0 0 4.5em #add8e6;
}
.sith:hover,
.sith:focus {
box-shadow: inset 0 0 0 4.5em black;
}
#buttons {
height: 100%;
width: 100vw;
padding-top: 10vh;
display: flex;
justify-content: center;
}
JS
document.querySelector("button").addEventListener("click", () => {
document.querySelector(".container").classList.toggle(".container.click");
});
There are a couple of changes you need to make to get this to work:
1. document.querySelector("button") is only selecting the first button. There are 2 you can add an event listener to the buttons
use document.querySelectorAll("button") to get all the buttons, and then you can loop through them adding an event Listener to each one:
document.querySelectorAll("button").forEach(function(button) {
button.addEventListener("click", () => {
document.querySelector(".container").classList.toggle("click");
});
});
A better way is to add an event listener to the buttons container - you can get the element using getElementById and then add the listener to it:
var buttons = document.getElementById("buttons");
buttons.addEventListener("click", (e) => {
document.querySelector(".container").classList.toggle("click");
});
2. You just use the class name when passing a class into toggle- you don't need the .. Also, you only need to toggle the click class as the container class will always apply. So what you need to use is .toggle("click");
Working Example (without your images):
var buttons = document.getElementById("buttons");
buttons.addEventListener("click", (e) => {
document.querySelector(".container").classList.toggle("click");
});
.container {
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
}
.container.click {
flex-direction: row;
justify-content: space-around;
}
h1 {
margin-left: 5vw;
color: black;
font-family: "Poller One", cursive;
font-variant: small-caps;
font-size: 3rem;
margin-top: 6vh;
}
button {
color: red;
transition: 0.25s;
float: left;
margin: 2%;
}
button:hover,
button:focus {
border: 2px solid red;
color: black;
}
.fill {
height: 120px;
width: 150px;
background: transparent;
margin-top: 4vh;
outline: none;
border: none;
}
.fill:hover,
.fill:focus {
box-shadow: inset 0 0 0 4.5em #add8e6;
}
.sith:hover,
.sith:focus {
box-shadow: inset 0 0 0 4.5em black;
}
#buttons {
height: 100%;
width: 100vw;
padding-top: 10vh;
display: flex;
justify-content: center;
}
<header>
<div class="container">
<h1>Choose Your Allegiance</h1>
<div id="buttons">
<button class="fill">Jedi</button>
<button class="fill sith">Sith</button>
</div>
</div>
</header>
As for animating this change, unfortunately CSS animations cannot be applied to flexbox direction property.
You are using classList.toggle wrong. You'll need to do this instead:
const container = document.querySelector(".container");
container.classList.toggle("click");
Documentation on Element.classList here.