Here is a sample of index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Students</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/design.css">
</head>
<body>
<div class="page">
<div class="page-header cf">
<h2>Students</h2>
<!-- dynamically insert search form here (optional) -->
</div>
<ul class="student-list">
<li class="student-item cf">
<div class="student-details">
<img class="avatar" src="https://randomuser.me/api/portraits/thumb/women/67.jpg">
<h3>iboya vat</h3>
<span class="email">iboya.vat#example.com</span>
</div>
<div class="joined-details">
<span class="date">Joined 07/15/15</span>
</div>
</li>
</ul>
Here is App.js
const studentList=document.querySelector('.student-list');
const page=document.querySelector(".page");
//constants used in displayPage function
//listItems ===54
const listItems=document.querySelector(".student-list").children;
const numberOfItems=10;
displayPage=(list,pageNumber)=> {
const SI=(pageNumber*numberOfItems)-numberOfItems;
const EI=pageNumber*numberOfItems;
Array.from(list).forEach((item,index)=> {
if (index>= SI && index<EI) {
item.style.display="block";
} else {
item.style.display="none";
}
})
}
//number of pages to display
addPaginationLinks=(list)=> {
const pages=Math.floor(list.length/10)
let html=``;
for (let i=0; i<pages; i++) {
if (i===0) {
html+=`
<li>
<a class="active" href="#">${i+1}</a>
</li>`;
} else {
html+=`
<li>
${i+1}
</li>`;
}
}
const ul=document.createElement("ul");
ul.innerHTML=html;
const div=document.createElement("div");
div.classList.add("pagination");
div.appendChild(ul);
page.appendChild(div);
}
displayPage(listItems,1);
addPaginationLinks(listItems);
addEventListener=()=> {
const a=document.querySelectorAll("a");
a.forEach(item=> {
item.addEventListener('click', (e)=> {
a.forEach(item=> {
if (item.classList.contains("active")) {
item.classList.remove("active")
e.target.classList.add("active");
}
})
const pageNumber=parseInt(e.target.textContent);
displayPage(listItems,pageNumber);
})
})
}
addEventListener();
addSearchComponent=()=> {
const pageHeader=document.querySelector(".page-header.cf")
let html=`
<div class="student-search">
<input placeholder="Search for students...">
<button>Search</button>
</div>`;
pageHeader.insertAdjacentHTML('beforeend', html);
}
addSearchComponent()
const search=document.querySelector("input");
const studentDetails=document.getElementsByClassName("student-details");
noResultsItem=()=> {
const item=`
<li class="no-results" style="display: none;">
<h3>No Results Shown Found</h3>
</li>`;
studentList.insertAdjacentHTML('beforeend', item);
}
noResultsItem()
//select no results list item
//filtering student list based on keyup search value
//array length is 0 if no match was found
search.addEventListener('keyup', (e)=> {
const noResults=document.querySelector(".no-results");
const array=Array.from(studentDetails).filter(student=>student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length==0) {
Array.from(studentDetails).forEach(student=>{
student.parentNode.style.display="none";
});
noResults.style.display="block";
//show ones that match, hide ones that don't
} else if (array.length>0) {
noResults.style.display="none";
Array.from(studentDetails).forEach(student=>{
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display="block";
} else {
student.parentNode.style.display="none";
}
});
}
});
Here is a link to my JSfiddle:
https://jsfiddle.net/dt7q9h5x/1/
The index.html shown above actually includes, 54 li list items, but I only added one for the sample.
Heres my issue:
Although the number of list items changes depending on the value in the input field, when I delete what I typed in and the search input becomes empty, all 54 items re-appear, which isn't supposed to happen because only 10 items are supposed to appear on each page.
I want it to go back to the original pagination setting, which was initially set before I typed anything into the search input. This was originally set by the function displayPages(listItems,1).
When you go to JSfiddle, you can see there are 10 items listed on each page. Once you start typing, things seem to work, but once you delete everthing, then all the items just re-appear.
I think the code at the bottom of app.js where I'm writing code for the event handler must be the culprit here.
Any suggestions?
Thanks in advance.
I checked your code, you didn't specify what to happen if the value of your search input becomes empty and this is where the problem occurs.
I changed your code a little and add a simple if statement and everything works just fine :)
search.addEventListener('keyup', (e) => {
// **************
// Check to see if search term is empty or not
// **************
if (e.target.value.length > 0) {
const noResults = document.querySelector(".no-results");
const array = Array.from(studentDetails).filter(student => student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length == 0) {
Array.from(studentDetails).forEach(student => {
student.parentNode.style.display = "none";
});
noResults.style.display = "block";
//show ones that match, hide ones that don't
} else if (array.length > 0) {
noResults.style.display = "none";
Array.from(studentDetails).forEach(student => {
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display = "block";
} else {
student.parentNode.style.display = "none";
}
});
}
}
// **************
// Display the initial state if search term is empty
// **************
else {
displayPage(listItems, 1);
}
});
Related
I have two sections: combinedCriteria and filteredServices. These tow sections are connected through knockoutjs script, when I hit click on any item in filteredService section, it adds that service in above section called combinedCriteria:
<section class="combine-list-container" data-bind="visible: combineSchedules()">
<ul>
<!-- ko foreach: combinedCriteria -->
<li>
<span class="icon-delete" data-bind="click: $parent.deleteCombinedSelection"></span>
<span data-bind="text: service.Name"></span>
<span><span class="min emp" data-bind="text: service.Duration, visible: false"></span></span>
</li>
<!-- /ko -->
</ul>
</section>
<section data-bind="visible: isServiceSectionVisible">
<!-- ko foreach: filteredSerivces -->
<header>
<span data-bind="text: ServiceTypeName"></span>
</header>
<ul data-bind="foreach: GroupedServices">
<li style="height:100%;" class="service">
</li>
</ul>
<!-- /ko -->
</section>
What I want to do, is when user clicks on some service in filtered services section, as it works now, to add it in above section combinedCriteria, but to show short effect with some background graying, and then back as it was.
function setServiceId(serviceId) {
var helperId = serviceId;
vm.selectedServiceId('');
vm.selectedServiceId(helperId);
vm.selectedServiceId(serviceId);
}
serviceIdSubscrier = vm.selectedServiceId.supsendableSubscribe(changeServiceId);
function changeServiceId() {
var currentService = getService();
if (vm.combineSchedules()) {
var isShownMessage = null;
if (vm.combinedCriteria().length > 4 && isShownMessage != true) {
var isShownMessage = true;
if (isShownMessage) {
var style = getDialogStyle();
theDialog = dialog.showMessage(vm.moreThen5SchedulesMessage(), ' ', [shell.cancelButtonText()], false, {
style: style
}).then(function (dialogResult) {
closeDialogForMoreThan5();
isShownMessage = false;
});
}
}
else {
vm.selectedService(currentService || {});
refreshAllowedTimes().then(function () {
setTimeByPreviousSelection();
checkToPushCriteria();
});
}
} else {
refreshOnServiceType();
}
}
function checkToPushCriteria() {
//if (vm.combinedCriteria().length > 4) {
// var style = getDialogStyle();
// theDialog = dialog.showMessage(vm.moreThen5SchedulesMessage(), ' ', [shell.cancelButtonText()], false, {
// style: style
// }).then(function (dialogResult) {
// closeDialogForMoreThan5();
// });
//}
//else {
if (vm.selectedService().Id) {
vm.combinedCriteria.push({
service: vm.selectedService() || {}
});
if (vm.combineSchedules() == 1) {
withSuspendition(employeeIdSubscriber, function () {
vm.employeeId('');
});
}
vm.selectedService({});
refreshCurrentDate();
}
//}
}
so basically, in the function called checkToPushCriteria() I need to catch event when it adds to an array: vm.combinedCriteria.push({service: vm.selectedService() || {}});
I would probabaly add something like jQuery(".someclass").css('background', 'red'); But I dont know which class is it (unkown identifier), also I dont know how to put highlight background color for some period of time (for example 0.5 seconds)
The foreach binding has a few callbacks you can use for exactly this purpose, specifically: afterRender and afterAdd. This piece of documentation should probably help you along.
Here's a small example: https://jsfiddle.net/thebluenile/9qarun4o/
Note that it's 2021 and there are nicer ways to make elements flash than using jQuery, such as CSS animations. But sometimes, you just gotta work with what you know/have...
Scenario: Just created a simple book List app, which can accept some inputs and display it on the UI and has other functionalities such as the use of localStorage.
Just as I added more custom alerts (e.g. show alert - on adding a book, - if author name contain numbers etc.) The whole alert stuff has gone AWOL. (Previously defined alerts worked fine before)
Also none of the questions that showed similarity proved useful to me
Method which handles all the alerts in the page
static showAlert(msg, className) {
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// check whether if an alertBox already exist in the UI
if(document.querySelector('.alert')) {
// remove alertBox after some time
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
And this is where I added new custom alerts:
if(bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
Problems facing:
Quick note: I have set the setTimeout delay to 3000
Existing alert is not removed after 3 seconds (which worked fine before)
If there is a need for another alert to exist in dom, then the alert showed previously should be removed and the new alert should be displayed; even if that happens in under 3 seconds.
I have added the code as snippet as I had some trouble setting JSFiddle
// Book class
class Book {
constructor(title, author, id) {
this.bookName = title;
this.authorName = author;
this.bookId = id;
}
}
// Dom class : handle user interface / dom
class Dom {
static displayBooks() {
//dummy data to mimic data in localStorage
const savedBooks = [
{
bookName: 'Vikings',
authorName: 'Michael Hirst',
bookId: 9764363,
},
{
bookName: 'The Soldier',
authorName: 'David Gary',
bookId: 5764363,
},
]
//const books = Store.getBooks();
const books = savedBooks;
books.forEach((book) => Dom.addBookToList(book))
}
static addBookToList(bookInfo) {
const tbody = document.querySelector('#book-list')
const bookRow = document.createElement('tr');
bookRow.innerHTML =
`
<tr>
<td>${bookInfo.bookName}</td>
<td>${bookInfo.authorName}</td>
<td>${bookInfo.bookId}</td>
<td>Delete</td>
</tr>
`
tbody.appendChild(bookRow);
}
static deleteBook(target) {
if(target.classList.contains('deleteBtn')) {
target.parentElement.parentElement.remove()
// show alert if user deletes a book
Dom.showAlert('Book deleted successfully', 'warning');
}
}
static showAlert(msg, className) {
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// check whether if an alertBox already exist in the UI
if(document.querySelector('.alert')) {
// remove alerBox after an interval
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
static clearFields() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#bookId').value = '';
}
}
// Store class - deals with localStorage
/*class Store {
static getBooks() {
let books;
if(localStorage.getItem('books') === null) {
// if there is no book in the localstorage, then we initialize an empty array (of objects);
books = [];
} else {
// since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBooks(book) {
const books = Store.getBooks();
books.push(book);
localStorage.setItem('books', JSON.stringify(books))
}
static removeBooks(bookId) {
const books = Store.getBooks();
books.forEach((book, index) => {
if(book.bookId === bookId) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}*/
// Events in Dom
document.addEventListener('DOMContentLoaded', Dom.displayBooks);
// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
event.preventDefault();
const bookName = document.querySelector('#title').value;
const authorName = document.querySelector('#author').value;
const bookId = document.querySelector('#bookId').value;
// input validation
if(bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
} else {
// object instantiation of book
const book = new Book(bookName, authorName, bookId);
// Add submitted book to the list
Dom.addBookToList(book);
// Add Submitted bookk to the local Storage
//Store.addBooks(book);
// show alert after adding the book
Dom.showAlert('Book added successfully', 'success');
// Clear input fields after submitting
Dom.clearFields();
}
})
// Remove / Delete a book from the list
document.querySelector('#book-list')
.addEventListener('click', event => {
// Remove book from the Dom
Dom.deleteBook(event.target);
// Remove book from the local storage
//Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
});
<!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>Book List app</title>
<!-- fontAwesome script -->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
<!-- bootstrap css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/css/bootstrap.min.css" type="text/css">
</head>
<body>
<div class="container mt-4">
<h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>
<form action="#" id="book-form">
<div class="form-group mb-2">
<label for="title">Book Title</label>
<input type="text" name="title" id="title" class="form-control">
</div>
<div class="form-group mb-2">
<label for="author">Author Name</label>
<input type="text" name="author" id="author" class="form-control">
</div>
<div class="form-group">
<label for="book-id">Reference Id</label>
<input type="text" name="title" id="bookId" class="form-control">
</div>
<input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
</form>
<table class="table table-striped mt-4">
<thead>
<tr>
<th>Book Title</th>
<th>Author Name</th>
<th>Reference Id</th>
<th class="text-warning"><small>Delete book</small></th>
</tr>
</thead>
<tbody id="book-list"></tbody>
</table>
</div>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
The issue I notice with your showAlert:
showAlert(msg, className) {
// ...
if (document.querySelector('.alert')) {
/*
#1. At the first time this function called, this will always be null. Since '.alert' is not in the DOM yet (it wil be added to the DOM at the following 'else' block.
Therefore, setTimeout here won't be called, since the code never enters here at the first time.
#2. At the second time and forth, this will be called.
However, it will not enter the `else` block which is to add the alert into the DOM
*/
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
If we break it down your expected behavior, the code should probably somewhere like this;
static showAlert(msg, className) {
// If there's an existing alert, instantly remove it.
if (document.querySelector('.alert')) {
document.querySelector('.alert').remove();
}
// Create the alert
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// Insert alert to the DOM.
container.insertBefore(alertBox, form);
// Remove the alert above after 3s
setTimeout(() => {
alertBox.remove();
}, 3e3);
}
Example final code as following;
// Book class
class Book {
constructor(title, author, id) {
this.bookName = title;
this.authorName = author;
this.bookId = id;
}
}
// Dom class : handle user interface / dom
class Dom {
static displayBooks() {
//dummy data to mimic data in localStorage
const savedBooks = [{
bookName: 'Vikings',
authorName: 'Michael Hirst',
bookId: 9764363,
},
{
bookName: 'The Soldier',
authorName: 'David Gary',
bookId: 5764363,
},
]
//const books = Store.getBooks();
const books = savedBooks;
books.forEach((book) => Dom.addBookToList(book))
}
static addBookToList(bookInfo) {
const tbody = document.querySelector('#book-list')
const bookRow = document.createElement('tr');
bookRow.innerHTML =
`
<tr>
<td>${bookInfo.bookName}</td>
<td>${bookInfo.authorName}</td>
<td>${bookInfo.bookId}</td>
<td>Delete</td>
</tr>
`
tbody.appendChild(bookRow);
}
static deleteBook(target) {
if (target.classList.contains('deleteBtn')) {
target.parentElement.parentElement.remove()
// show alert if user deletes a book
Dom.showAlert('Book deleted successfully', 'warning');
}
}
static showAlert(msg, className) {
// If there's an existing alert, instantly remove it.
if (document.querySelector('.alert')) {
document.querySelector('.alert').remove();
}
// Create the alert
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// Insert alert to the DOM.
container.insertBefore(alertBox, form);
// Remove the alert above after 3s
setTimeout(() => {
alertBox.remove();
}, 3e3);
}
static clearFields() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#bookId').value = '';
}
}
// Store class - deals with localStorage
/*class Store {
static getBooks() {
let books;
if(localStorage.getItem('books') === null) {
// if there is no book in the localstorage, then we initialize an empty array (of objects);
books = [];
} else {
// since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBooks(book) {
const books = Store.getBooks();
books.push(book);
localStorage.setItem('books', JSON.stringify(books))
}
static removeBooks(bookId) {
const books = Store.getBooks();
books.forEach((book, index) => {
if(book.bookId === bookId) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}*/
// Events in Dom
document.addEventListener('DOMContentLoaded', Dom.displayBooks);
// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
event.preventDefault();
const bookName = document.querySelector('#title').value;
const authorName = document.querySelector('#author').value;
const bookId = document.querySelector('#bookId').value;
// input validation
if (bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
} else {
// object instantiation of book
const book = new Book(bookName, authorName, bookId);
// Add submitted book to the list
Dom.addBookToList(book);
// Add Submitted bookk to the local Storage
//Store.addBooks(book);
// show alert after adding the book
Dom.showAlert('Book added successfully', 'success');
// Clear input fields after submitting
Dom.clearFields();
}
})
// Remove / Delete a book from the list
document.querySelector('#book-list')
.addEventListener('click', event => {
// Remove book from the Dom
Dom.deleteBook(event.target);
// Remove book from the local storage
//Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
});
<!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>Book List app</title>
<!-- fontAwesome script -->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
<!-- bootstrap css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/css/bootstrap.min.css" type="text/css">
</head>
<body>
<div class="container mt-4">
<h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>
<form action="#" id="book-form">
<div class="form-group mb-2">
<label for="title">Book Title</label>
<input type="text" name="title" id="title" class="form-control">
</div>
<div class="form-group mb-2">
<label for="author">Author Name</label>
<input type="text" name="author" id="author" class="form-control">
</div>
<div class="form-group">
<label for="book-id">Reference Id</label>
<input type="text" name="title" id="bookId" class="form-control">
</div>
<input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
</form>
<table class="table table-striped mt-4">
<thead>
<tr>
<th>Book Title</th>
<th>Author Name</th>
<th>Reference Id</th>
<th class="text-warning"><small>Delete book</small></th>
</tr>
</thead>
<tbody id="book-list"></tbody>
</table>
</div>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
Here is the case. I have a page, on which I have several options to choose. Choice is being made by clicking a link, which leads to an anchor and displays necessary content. Unfortunately this could not be changed. The problem is, that I also have a language panel, which leads me to the same page but already translated to necessary locale. My goal is to be able change language, but also save the chosen anchor from a clicked link. Was not able to find solution yet, could someone please give any idea, how that could be done?
Here is link to a fiddle https://jsfiddle.net/william_eduards/psuLrv02/87/
let contents = Array.from(document.querySelectorAll('.content'));
let contentOne = document.querySelector('.content--a');
let contentTwo = document.querySelector('.content--b');
const handleClick = (e) => {
e.preventDefault();
contents.forEach(node => {
node.classList.remove('active');
});
e.currentTarget.classList.add('active');
if (document.querySelector('.content-a').classList.contains('active')) {
contentOne.classList.add('show');
} else {
contentOne.classList.remove('show');
}
if (document.querySelector('.content-b').classList.contains('active')) {
contentTwo.classList.add('show');
} else {
contentTwo.classList.remove('show');
}
}
contents.forEach(node => {
node.addEventListener('click', handleClick);
});
.content--a, .content--b {
display: none;
}
.show {
display: block;
}
<div class="container">
<div class="select-option">
<a class="content content-a" href="#contentA">Content A</a>
<a class="content content-b" href="#contentB">Content b</a>
</div>
<div class="options">
<div class="content--a" id="contentA">
<p>I am content a</p>
</div>
<div class="content--b" id="contentB">
<p>I am content b</p>
</div>
</div>
</div>
To show your contents, just use CSS with pseudo class :target (remove all your js)
.content--a, .content--b {
display: none;
}
.options > *:target {
display: block;
}
If your page is reloaded when you select language, use sessionStorage to remember which link was last clicked, and restore it on next page load. Following code assumes the URL is unchanged when you switch languages:
window.addEventListener('DOMContentLoaded', e => {
const contents = document.querySelectorAll('.content');
//Save selection on click as url#anchor
contents.forEach(node => {
node.addEventListener('click', e => {
const href = node.getAttribute('href');
console.log(href);
if (href) {
const spl = href.split('#');
if (spl.length > 1 && spl[1].length) {
const spl2 = window.location.href.split('#');
sessionStorage.setItem('shown-content', spl2[0] + '#' + spl[1]);
console.log(sessionStorage.getItem('shown-content'));
}
}
});
});
//Restore selection on load if URL is the same up until anchor
const anch = sessionStorage.getItem('shown-content');
if (anch) {
const spl = anch.split('#');
if (spl.length) {
const spl2 = window.location.href.split('#');
console.log(spl[0], spl2[0]);
if (spl[0] == spl2[0])
window.location.href = anch;
}
}
});
Adapt above code to your needs if URL changes between languages
Full example (doesn't run as a snippet. Use server or local html file)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test</title>
<style>
.content--a, .content--b {
display: none;
}
.options > *:target {
display: block;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', e => {
const contents = document.querySelectorAll('.content');
//Save selection on click as url#anchor
contents.forEach(node => {
node.addEventListener('click', e => {
const href = node.getAttribute('href');
console.log(href);
if (href) {
const spl = href.split('#');
if (spl.length > 1 && spl[1].length) {
const spl2 = window.location.href.split('#');
sessionStorage.setItem('shown-content', spl2[0] + '#' + spl[1]);
console.log(sessionStorage.getItem('shown-content'));
}
}
});
});
//Restore selection on load if URL is the same up until anchor
const anch = sessionStorage.getItem('shown-content');
if (anch) {
const spl = anch.split('#');
if (spl.length) {
const spl2 = window.location.href.split('#');
console.log(spl[0], spl2[0]);
if (spl[0] == spl2[0])
window.location.href = anch;
}
}
});
</script>
</head>
<body>
<div class="container">
<div class="languages">
English
Français
</div>
<div class="select-option">
<a class="content content-a" href="#contentA">Content A</a>
<a class="content content-b" href="#contentB">Content b</a>
</div>
<div class="options">
<div class="content--a" id="contentA">
<p>I am content a</p>
</div>
<div class="content--b" id="contentB">
<p>I am content b</p>
</div>
</div>
</div>
</body>
</html>
You can simply save the element which is active. No big deal.
let contents = Array.from(document.querySelectorAll('.content'));
...
let activeEl = null;
const handleClick = (e) => {
...
e.currentTarget.classList.add('active');
activeEl = e.currentTarget; // adding the `<a/>` tag in a global
...
}
I can't seem to get local storage to work. The goal is to keep the todo list items on the page upon refresh. Every time I refresh the page it goes poof. The syntax seems right.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO LIST</title>
<link rel="stylesheet" href="./styles/style.css">
</head>
<body>
<main id="main">
<h1>THE TO-DO LIST:</h1>
<form action="" id="add-task">
<label for="todo">Add Task:</label>
<input type="text" id="todo">
<button>Add Task</button>
</form>
<p class="center">To complete task, click on text.</p>
<ul id="task-list">
<li class="task-complete">example_1 <button>Remove Task</button></li>
</ul>
</main>
<script src="./script/index.js"></script>
</body>
</html>
const form = document.querySelector('#add-task');
const input = document.querySelector('#todo');
const taskList = document.querySelector('#task-list');
let taskID = 0;
taskList.addEventListener('click', function(e) {
if (e.target.tagName === 'BUTTON') {
e.target.parentElement.remove();
let inputTask = document.getElementById('todo');
localStorage.setItem('email', inputTask.value);
} else if (e.target.tagName === 'LI') {
e.target.classList.toggle('task-complete');
}
});
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log(input.value);
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
let savedInput = input.value;
removeBtn.innerText = 'Remove Task';
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
input.value = '';
console.log(localStorage);
});
.task-complete {
text-decoration: line-through;
}
Joshua, here are a few things from looking at your sample:
First, you're setting the localStorage to a single item, with the current input value, not a collection of tasks like an array
It also seems that you're not getting the saved data on page reload, that's why nothing happens when page reloads
Remember that you can only save strings to localStorage, in a todo list you might want to save an array (a collection of todos), but since you can't do it you need to convert it to a string while saving (JSON.stringify(yourArray) will help you with that), and parse it back to an Array when loading (JSON.parse)
const form = document.querySelector('#add-task');
const input = document.querySelector('#todo');
const taskList = document.querySelector('#task-list');
let taskID = 0;
let tasks = [] // here will hold your current todos collection
// a function that will retrieve the saved todos from local storage
//
// note that 'tasks' can be any string identifier that you want — 'todos'
// would also work — but you need to use the same for localStorage.getItem
// and localStorage.setItem
function getTasksFromLocalStorage(){
// it will return `null` if nothing's there
tasks = localStorage.getItem('tasks') || []
if (tasks) {
// convert it to an array so you can loop over it
tasks = JSON.parse(tasks)
}
}
function addTask(text) {
// CREATE DOM ELEMENTS
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
removeBtn.innerText = 'Remove Task';
// set the text to the provided value
newTask.innerText = text;
// append the remove button
newTask.appendChild(removeBtn);
// append it to the dom so we can see it
taskList.appendChild(newTask)
}
// on page load get tasks from local storage
// then loop over it, create the DOM elements and append them to
// the taskList
document.addEventListener('DOMContentLoaded', function() {
getTasksFromLocalStorage()
// if we have saved tasks, loop over them and render to the dom
tasks.forEach(function(savedTaskText) {
addTask(savedTaskText)
})
})
// then on your code, you need to update to push
// the current inputed `task` to the `tasks` collection (Array)
// then save the entire collection to the local storage
// then add the new task to the DOM
// and finally reset the input
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log(input.value);
// save it to the current holding list
tasks.push(input.value)
// save a copy of the updated list to the localStorage, so when you
// reload the page you get saved items!
localStorage.setItem('tasks', tasks)
// add it to DOM
addTask(input.value);
// reset the input
input.value = '';
});
There's more things you need to do, if you want tasks to have unique ids (since, so you can remove them later), but the code was simplified for brevity of explanation (and yet you got a long answer anyways).
Here's so docs and suggested reading:
MDN Docs for LocalStorage
MDN Docs for JSON (parse and stringify)
There's plenty vanilla javascript tutorials (written and youtube) for "creating a todo lists using localStorage", that go into more detail than we can go in a SO answer, I suggest you skim through those as well!
Good luck and Happy coding ✌️
There are 2 problems with your code.
First, you are not saving each to-do task entered by user upon form submit. If you want to save each to-do task entered by user in localStorage, then modify the form submit handler as below:
form.addEventListener('submit', function(e) {
e.preventDefault();
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
let savedInput = input.value;
removeBtn.innerText = 'Remove Task';
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
localStorage.setItem('Task'+taskID, input.value);
taskID++;
input.value = '';
});
Second, you are not utilizing the previously saved data in localStorage to show the list of to-dos that were entered by user before the page was loaded. You can achieve that by using below function code:
function showSavedToDos() {
const keys = Object.keys(localStorage);
let i = keys.length;
while (i--) {
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
removeBtn.innerText = 'Remove Task';
newTask.innerText = localStorage.getItem(keys[i]);
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
}
}
showSavedToDos();
You are not using de localStorage API, please take a look to this example. here I am using template to display the tasks. In the html file is the only change
<main id="main">
<h1>THE TO-DO LIST:</h1>
<form action="" id="add-task">
<label for="todo">Add Task:</label>
<input type="text" id="todo" />
<button>Add Task</button>
</form>
<p class="center">To complete task, click on text.</p>
<ul id="task-list">
<li class="task-complete">example_1 <button>Remove Task</button></li>
</ul>
</main>
<template id="task">
<li class="task-complete">
<span></span>
<button>Remove task</button>
</li>
</template>
In JavaScript I create a render function that will collect the task stored in localstorage. Populated when calling store(input.value) in the submit handler
const form = document.querySelector("#add-task");
const input = document.querySelector("#todo");
const taskList = document.querySelector("#task-list");
let taskID = 0;
taskList.addEventListener("click", function (e) {
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove();
let inputTask = document.getElementById("todo");
localStorage.setItem("email", inputTask.value);
} else if (e.target.tagName === "LI") {
e.target.classList.toggle("task-complete");
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
console.log(input.value);
const newTask = document.createElement("li");
const removeBtn = document.createElement("button");
let savedInput = input.value;
removeBtn.innerText = "Remove Task";
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
store(input.value);
input.value = "";
console.log(localStorage);
});
function getTasks() {
return localStorage.tasks ? JSON.parse(localStorage.tasks) : [];
}
function store(task) {
const tasks = getTasks();
tasks.push(task);
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function render() {
const tasks = getTasks();
tasks.forEach((task) => {
const newTask = createTask(task);
taskList.appendChild(newTask);
});
}
function createTask(task) {
const template = document.querySelector("#task");
const taskNode = template.content.cloneNode(true);
taskNode.querySelector("span").innerText = task;
return taskNode;
}
render();
The render function run every first render of the page, so tasks list will be populated
Sorry if title confuses. Hard to think of how to describe the problem in few words.
I have 3 different lists containing "level", "category", "time" for the game. Originally none of the lists' items have any class. When selected an item from each list, it gets a class 'activeSelection'.
I need to check if in each of the lists there is an item that has that class. If not, then show the warning under the list (there is a warning for each list) that at the moment is hidden. Also not let "startGame()" function to execute.
I need it to work so if 2 lists are missing that class, the warning would be shown under both of those lists. The same if all 3 lists are missing the class.
const levelList = document.querySelector('.levelList');
const categoryList = document.querySelector('.categoryList');
const timeList = document.querySelector('.timeList');
$(document).ready(function() {
$(playButton).on('click', function() {
if ($(levelList).children("li").hasClass('activeSelection') == false) {
$(levelList).find('.warning').show();
} else {
startGame();
} /** that's how I check one of the lists. I need to check all 3
and if at least one does not have a class give a warning, but not
stop checking until all 3 lists are checked to give the rest of
the warnings if needed **/
});
});
.warning {
display: none;
}
<div class="list">
<div class="levelList">
<h2>level</h2>
<ul>
<li>easy</li>
<li>medium</li>
<li>hard</li>
</ul>
<p class="warning">! please select level !</p>
</div>
<div class="categoryList">
<h2> category</h2>
<ul>
<li>movies</li>
<li>songs</li>
<li>people</li>
<li>animals</li>
<li>random things</li>
</ul>
<p class="warning">! please select Category !</p>
</div>
<div class="timeList">
<h2>time</h2>
<ul>
<li>1 min</li>
<li>2 min</li>
<li>3 min</li>
</ul>
<p class="warning">! please select time !</p>
</div>
</div>
You can try doing all 3 checks separately:
$(document).ready(function() {
$(playButton).on('click', function() {
var status = 0;
if ($(levelList).children("li").hasClass('activeSelection') == false) {
$(levelList).find('.warning').show();
} else {
status += 1;
}
if ($(categoryList).children("li").hasClass('activeSelection') == false) {
$(categoryList).find('.warning').show();
} else {
status += 1;
}
if ($(timeList).children("li").hasClass('activeSelection') == false) {
$(timeList).find('.warning').show();
} else {
status += 1;
}
if (status === 3)
startGame();
});