soo, i am trying to use localstorage to store the classlist so it will remember whether or not something was added to their favorites list. i need to do this with JavaScript. The error provided comes from the console in my web browser (chrome)
The ERROR
main.js:99 Uncaught TypeError: Cannot read property 'value' of undefined
at storeFavo (main.js:99)
at HTMLButtonElement.favorites (main.js:71)
HTML
<!-- Showcase -->
<section class="showcase">
<div class="container grid ">
<div class="showcase-form card">
<h2>*Input Car Name*</h2>
<img alt="" src="#" >
<button id="favo" class="btn">Add to Favorites</button>
<button id="info" class="btn">More Info</button>
</div>
</div>
</section>
JavaScript
window.addEventListener('load', init);
const info = document.querySelector('#info');
const div1 = document.querySelector('.moreInfo');
const favo = document.querySelector('#favo');
let apiUrl = 'webservice/includes/actions.php';
let apiUrl2 = 'webservice/index.php';
let Favos = document.getElementById('favo').getElementsByClassName('favorited')[0];
function init() {
info.addEventListener('click', moreInfo);
favo.addEventListener('click', favorites);
if (typeof window.localStorage === "undefined") {
console.error('Local storage is not available in your browser');
return;
}
checkFromLocalStorage()
carList;
}
function carList() {
fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then(getAjaxSuccessHandler)
.catch(getAjaxErrorHandler);
}
function getAjaxSuccessHandler(data) {
console.log(data);
}
function getAjaxErrorHandler(data) {
console.error(data);
}
// Generate Cards
// getCars.map((item)=>{
// return (
// <div>
// <p> {item.brand}</p>
// <p> {item.type}</p>
// </div>
// );
// }),
// More Info Button
function moreInfo() {
let title= document.createElement('h1');
title.innerHTML = 'Info';
div1.appendChild(title)
console.log("info?")
if (div1.style.display == 'block'){
div1.style.display = 'none';
} else {
div1.style.display = 'block';
}
}
// Add To Favorites Button
function favorites() {
console.log("favo")
if (favo.classList == 'btn') {
favo.classList.remove('btn')
favo.classList.add('favorited')
favo.innerHTML = 'Remove from favorites';
storeFavo()
}
else {
favo.classList.remove('favorited')
favo.classList.add('btn')
favo.innerHTML = 'Add to Favorites';
deleteClickHandler()
}
}
//Check
/**
* Is local storage is available on page load? Let's fill the form
*/
function checkFromLocalStorage() {
if (localStorage.getItem('favos') !== null) {
Favos.value = localStorage.getItem('favos');
}
}
//Store
/**
* After submitting the form, let's save the values in the local storage
*
* #param e
*/
function storeFavo(e) {
localStorage.setItem('favos', Favos.value);
localStorage['Favos'] = document.getElementById("favo")
}
//Delete
/**
* Make sure we clean up the local storage again
*
* #param e
*/
function deleteClickHandler(e) {
localStorage.removeItem('favos');
}
CSS
.favorited {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: #470aed;
color: #ffffff;
border: none;
border-radius: 5px;
}
.btn {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: var(--primary-color);
color: #ffffff;
border: none;
border-radius: 5px;
}
I will do my very best to respond fast and to provide any needed information if asked for.
Check the documentation of localStorage.setItem https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem, you can write only string values, but you are trying to write an Object value (https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName). If you want to have some persistence fo your HTML element, you can save outerHTML https://developer.mozilla.org/ru/docs/Web/API/Element/outerHTML to the storage and parse it on load.
Related
This question already has answers here:
Wait for forEach with a promise inside to finish
(2 answers)
Closed 4 months ago.
Tried to get my head around this problem, but still couldn't(I feel like ELI5 would be appropriate approach for me when talking about Promises). I removed readMultipleFiles-function promise. Put Promise.all and tried async await, but the returned array is still empty. I'm completely lost and I would appreciate very much if you could point me my error(s) and maybe suggest some good YT lessons about these :/
Edited code and added all the required JS and HTML+CSS to create dropzone:
// Function to handle files user drag&drop to HTML page
const handleDrop = async (e) => {
console.log('1.0 handleDrop function starts... ');
// files are filelist-type
const dt = e.dataTransfer;
const files = dt.files;
let fileArray = [...files];
console.log('2. Starting to read files.');
let filesRead = await readMultipleFiles(fileArray);
// These gives still undefined
console.log('filesRead[1]: ',filesRead[1]);
console.log('filesRead: ',filesRead);
}
// Function to read multiple files in array. This should return array of files
const readMultipleFiles = async (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
return Promise.all(fileArray.map(f => {readFile(f)}));
}
// Function to read single file. This should return single file object:
// {filename: <somefilename>, content:<sometextcontent}
const readFile = (file) => {
console.log('\t2.2 readFile working...');
let myPromise = new Promise((resolve) => {
let singleFile;
// console.log('\t\t2.2.1 Reading single file...');
const reader = new FileReader();
reader.onload = (e) => {
// console.log('\t\t\t2.2.1 readFile -> file: ',e.target.result, 'typeof file:', typeof e.target.result);
fileContent = e.target.result;
fileName = file.name;
singleFile = {
'filename':file.name,
'content':e.target.result
};
// console.log('\t2.2.1 readFile --> fileRead: ', singleFile);
}
reader.readAsText(file);
resolve(singleFile);
})
return myPromise.then(res => {
// console.log('2.2.1.1 readfile -> myPromise.then res: ', res);
return res;
});
}
const initApp = () => {
const droparea = document.querySelector('.droparea');
const active = () => droparea.classList.add('green-border');
const inactive = () => droparea.classList.remove('green-border');
const prevents = (e) => e.preventDefault();
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evtName => {
droparea.addEventListener(evtName, prevents);
});
['dragenter', 'dragover'].forEach(evtName => {
droparea.addEventListener(evtName, active);
});
['dragleave', 'drop'].forEach(evtName => {
droparea.addEventListener(evtName, inactive);
});
droparea.addEventListener('drop', handleDrop);
}
document.addEventListener("DOMContentLoaded", initApp);
HTML page for this:
<html>
<head>
<meta charset="utf-8">
<title>Page</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div>
Page
</div>
<main>
<section class="droparea">
<i class="far fa-images"></i>
<p>Drop your files here</p>
</section>
</main>
<script type="text/javascript" src="index.js"></script>
CSS:
.hide {
display: none;
}
h3 {
margin: 0 auto;
padding-top: 10px;
}
.checkboxarea {
width: 99%;
overflow: hidden;
}
.checkboxitem {
width: 45%;
float: left;
}
.ui.segment {
border: 0;
box-shadow: none;
}
#drop_zone {
border: 5px solid blue;
width: 200px;
height: 100px;
}
.droparea {
margin: 1rem auto;
display: flex;
flex-direction: column;
justify-content: left;
align-items: center;
width: 384px;
max-width: 100%;
height: 160px;
border: 4px dashed grey;
border-radius: 15px;
}
.droparea i {
font-size: 3rem;
flex-grow: 1;
padding-top: 1rem;
}
.green-border {
border-color: green;
}
.selectResultData {
padding-right: 10px;
}
/* Style the button that is used to open and close the collapsible content */
.collapsible {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .collapsible:hover {
background-color: #ccc;
}
/* Style the collapsible content. Note: hidden by default */
.content {
padding: 0 18px;
display: none;
overflow: hidden;
background-color: #f1f1f1;
}
Original question:
I've been banging my head with this kind of problem for awhile now. I'm trying to use Javascript promises and async/await to fill array with object(txt-files dropped by user to HTML page) to be used later on my code but I cannot get around problem that array objects cannot be accessed although console.log shows array has objects. Seems to me that async/await is not waiting for the Array to be filled for some reason which I don't understand. Can you please help me with this?
//Code to handle user filedrops
const handleDrop = async (e) => {
console.log('1.0 handleDrop function starts... ');
// files are filelist-type
const dt = e.dataTransfer;
const files = dt.files;
let fileArray = [...files];
// Making sure files are in fileArray and they can be accessed
console.log('1.0 handleDrop -> fileArray.length',fileArray.length)
console.log('Files: ',fileArray);
console.log('2. Starting to read files.');
let filesRead = await readMultipleFiles(fileArray);
console.log('This log row should come after files have been read and saved to variable\nFiles read! filesRead: ', filesRead);
// **************************
// Why this gives undefined?
// **************************
console.log('filesRead[1]: ',filesRead[1]);
}
//Function to read Array of files and return them back in Array
const readMultipleFiles = (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
let myPromise = new Promise((resolve) => {
let filesRead = [];
fileArray.forEach(f => {
let file;
readFile(f).then(res => {
console.log('2.1.1 file returned by readFile', res, '\nf:',f);
file=res
return res;
}).then(res => filesRead.push(file));
});
resolve(filesRead);
})
return myPromise.then(res => res);
}
// Function to read single file and return it as an object
const readFile = (file) => {
console.log('\t2.2 readFile working...');
let myPromise = new Promise((resolve) => {
let singleFile;
console.log('\t\t2.2.1 Reading single file...');
const reader = new FileReader();
reader.onload = (e) => {
console.log('\t\t\t2.2.1 readFile -> file: ',e.target.result, 'typeof file:', typeof e.target.result);
fileContent = e.target.result;
fileName = file.name;
singleFile = {
'filename':file.name,
'content':e.target.result
};
console.log('\t2.2.1 readFile --> fileRead: ', singleFile);
resolve(singleFile);
}
reader.readAsText(file);
})
return myPromise.then(res => {
console.log('2.2.1.1 readfile -> myPromise.then res: ', res);
return res;
});
}
Your function readMultipleFiles returns a promise that resolves to an empty array because resolve is called before the asynchronous then callbacks have ran, and so filesRead is still [] when resolve(filesRead) is called.
Not your original problem, but it is an anti-pattern to create a promise with new Promise when you already have promises to work with (readFile(f)). In this case you probably want to get a promise from Promise.all
Here is how you can fix readMultipleFiles:
const readMultipleFiles = (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
return Promise.all(fileArray.map(readFile))
}
For simplicity sake, I didn't bother to have this line in the above correction:
console.log('2.1.1 file returned by readFile', res, '\nf:',f);
There is already a lot of debugging output there.
I have an image uploader that I'm building that allows the user to remove images/ image previews prior to form submission. I seem to have a come across a side effect that a) I don't know what is causing it, and b) how to fix it.
When an image is deleted from the preview the image is removed via a click event and the remove() method on the image's parent figure element.
If a user then re-selects the same image (that has just been removed) from their computer with the file picker / input the image doesn't show on the preview the second time around? But, even more confusingly, if a completely different image is attached, and then the user tries to re-attach the original image that previously didn't show the second time, that image does then show again (I hope this makes sense).
I have no idea what is going on here?
Codepen: https://codepen.io/thechewy/pen/qBYYMYV
let dropZone = document.getElementById("zone"),
showSelectedImages = document.getElementById("show-selected-images"),
fileUploader = document.getElementById("upload-files");
dropZone.addEventListener("click", () => {
// assigns the dropzone to the hidden input element, when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (e) => e.preventDefault());
// Prevent browser default when draging over
dropZone.addEventListener("drop", (e) => e.preventDefault());
fileUploader.addEventListener("change", (e) => {
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach(updateThumbnail);
console.log("fileUploader.files is: ", [...fileUploader.files]);
});
// updateThumbnail function
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
let uploadImageWrapper = document.createElement("figure"),
thumbnail = new Image(),
removeImage = `
<div class="remove-image jc-center flex"> Delete </div>
`;
// image thumbnail
thumbnail.classList.add("thumbnail");
thumbnail.src = URL.createObjectURL(file);
// appending elements
showSelectedImages.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(thumbnail); // image thumbnail
uploadImageWrapper.insertAdjacentHTML("afterbegin", removeImage); // 'x' to remove image
// get the original width and height values of the thumbnail using the decode() method
thumbnail
.decode()
.then((response) => {
thumbWidth = thumbnail.naturalWidth;
thumbHeight = thumbnail.naturalHeight;
// typical front end image validations
if (thumbWidth * thumbHeight < 4000000) {
// output the error message
}
})
.catch((encodingError) => {
// Do something with the error.
});
// Delete images from the preview
document
.querySelectorAll("#show-selected-images .remove-image")
.forEach((i) => {
i.addEventListener("click", (e) => {
if (e.target) {
var deleteFigureEl = e.target.closest("figure");
// removes the image via removing it's parent element
deleteFigureEl.remove();
}
});
});
}
} // end of 'updateThumbnail' function
body {
margin: 0;
display: flex;
justify-content: center;
font-family: arial;
}
form {
width: 50%;
max-width: 600px;
}
.select-files {
padding: 1rem;
background: red;
cursor: pointer;
color: #fff;
font-weight: bold;
}
#show-selected-images {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 2rem;
}
figure {
width: 100%;
margin: 0;
}
img {
display: block;
width: 100%;
height: auto;
}
.remove-image {
cursor: pointer;
padding: 1rem;
background: lightgrey;
}
<form id="upload-images-form" enctype="multipart/form-data" method="post">
<h1>Upload Your Images</h1>
<div id="zone">
<p class="select-files">SELECT FILES</p>
</div>
<div class="inner-wrapper">
<div class="upload-label-wrapper">
<input id="upload-files" style="display:none;" type="file" name="upload-files[]" multiple>
</div>
<button id="submit-images" oninput="updateThumbnail(this.files)">SUBMIT</button>
</div>
<div id="show-selected-images"></div>
</form>
let dropZone = document.getElementById("zone"),
showSelectedImages = document.getElementById("show-selected-images"),
fileUploader = document.getElementById("upload-files"),
files = [],
filesIndexes = new Set();
dropZone.addEventListener("click", () => {
// assigns the dropzone to the hidden input element, when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (e) => e.preventDefault());
// Prevent browser default when draging over
dropZone.addEventListener("drop", (e) => e.preventDefault());
fileUploader.addEventListener("change", (e) => {
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach((file) => {
updateThumbnail(file);
let i = files.length;
while (filesIndexes.has(i)) {
i++;
}
files.push({ file, index: i });
filesIndexes.add(i);
});
fileUploader.value = "";
console.log("fileUploader.files is: ", [...fileUploader.files]);
});
// updateThumbnail function
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
let uploadImageWrapper = document.createElement("figure"),
thumbnail = new Image(),
removeImage = `
<div class="remove-image jc-center flex" index="${files.length}"> Delete </div>
`;
// image thumbnail
thumbnail.classList.add("thumbnail");
thumbnail.src = URL.createObjectURL(file);
// appending elements
showSelectedImages.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(thumbnail); // image thumbnail
uploadImageWrapper.insertAdjacentHTML("afterbegin", removeImage); // 'x' to remove image
// get the original width and height values of the thumbnail using the decode() method
thumbnail
.decode()
.then((response) => {
thumbWidth = thumbnail.naturalWidth;
thumbHeight = thumbnail.naturalHeight;
// typical front end image validations
if (thumbWidth * thumbHeight < 4000000) {
// output the error message
}
})
.catch((encodingError) => {
// Do something with the error.
});
// Delete images from the preview
document
.querySelectorAll("#show-selected-images .remove-image")
.forEach((i) => {
i.onclick = (e) => {
console.log(e.currentTarget);
if (e.target) {
var deleteFigureEl = e.target.closest("figure");
let index = e.currentTarget.getAttribute("index");
files.forEach((obj, i) => {
if (obj.index == index) {
files.splice(i, 1);
filesIndexes.delete(Number(index));
}
});
// removes the image via removing it's parent element
deleteFigureEl.remove();
}
};
});
}
} // end of 'updateThumbnail' function
And then you should upload files like this
function upload(){
let formData = new FormData()
for (let index = 0; index < files.length; index++) {
formData.append(`file${files[index].index}`, files[index].file)
}
fetch("url", {method: "POST", body: formData})
}
I have a file uploader that shows preview images and it is currently set so if you click an image the image preview is deleted and the image is also effectively deleted from the FileList array before the form submission by creating a second array and setting the FileList to hold these values. All of this works OK.
The Context
Whilst setting this up and posting this as a question on StackOverflow in order to (theortically) keep the code simpler I removed the 'remove image' button and set it so the image was deleted both visually and from the FileList if you clicked the image itself. I'm now struggling to work out how to transfer this functionality so when the 'x' is clicked the same functionality happens.
In the current code if you click the image you get the expected behaviour.
I've now included code so that the image preview for each individual image is wrapped in a parent <figure> element with the 'x' .remove-image element included in this too. I've also included some commented out code at the bottom that if uncommented removes the <figure> element containing the image when the 'x' is clicked.
The Question
How do I get it so that instead of when the <img> is clicked the required functionality only happens when the 'x' is clicked?
I would think I need to somehow have the click event on .remove-image (the 'x') that uses the .closest method to go up to the figure element and down to the image i.e. removeImage.closest('figure').querySelector('.img') but I only want the required functionality when the 'x' is clicked, and not when either the 'x' or the image are clicked, which would be simple to fix.
Any help greatly appreciated.
Codepen: https://codepen.io/thechewy/pen/oNdrzjz
let attachFiles = document.getElementById("attach-files");
let previewWrapper = document.getElementById("show-selected-images");
let form = document.getElementById("upload-images-form");
let submitData = new DataTransfer();
attachFiles.addEventListener("change", (e) => {
const currentSubmitData = Array.from(submitData.files);
// For each addded file, add it to submitData if not already present
[...e.target.files].forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// Generate a preview <img> for each selected file
[...submitData.files].forEach(showFiles);
});
function showFiles(file) {
let uploadImageWrapper = document.createElement("figure");
let previewImage = new Image();
let removeImage = `<div class="remove-image"> X </div>`;
// Set relevant <img> attributes
previewImage.dataset.name = file.name;
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
// Adds click event listener to <img> preview (this needs moving to the .remove-image element)
previewImage.addEventListener("click", (e) => {
const target = e.currentTarget;
const name = target.dataset.name;
// Remove the clicked file from the submitData
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
// Reset the attachFiles FileList
attachFiles.files = submitData.files;
// Remove the <img> node from the DOM
target.remove();
});
// Append <figure> and <img> preview node to DOM
previewWrapper.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(previewImage); // <img>
uploadImageWrapper.insertAdjacentHTML('afterbegin', removeImage); // 'x' to remove the image
// // ===== Delete figure element that wraps the image =====
// document.querySelectorAll('#show-selected-images .remove-image').forEach(i => {
// i.addEventListener('click', (e) => {
// if (e.target) {
// let deleteFigureEl = e.target.closest('figure');
// // removes the image via removing it's parent element
// deleteFigureEl.remove();
// }
// })
// })
}
* {
position: relative;
}
form {
padding: 1rem 2rem;
width: 50%;
border: 1px solid;
}
input,
button {
display: block;
margin: 2rem 0;
}
.remove-image {
background: #000;
width: 30px;
height: 30px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
top: 10px;
left: -10px;
position: absolute;
z-index: 2;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
<form enctype="multipart/form-data" method="post" id="upload-images-form">
<input id="attach-files" type="file" name="attach-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="show-selected-images"></div>
</form>
The two dynamic values in the click listener are the target (which is the previewImage itself) and the name (which is the same as the file.name already in scope). So, all you'd really need to do is change those variable references inside a removeImage event listener (and make removeImage an actual element, not just a string, so that .addEventListener can be called on it).
const removeImage = document.createElement('div');
removeImage.className = 'remove-image';
removeImage.textContent = ' X ';
removeImage.addEventListener('click', () => previewImage.click());
removeImage.addEventListener("click", () => {
const name = file.name;
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
attachFiles.files = submitData.files;
previewImage.remove();
removeImage.remove();
});
let attachFiles = document.getElementById("attach-files");
let previewWrapper = document.getElementById("show-selected-images");
let form = document.getElementById("upload-images-form");
let submitData = new DataTransfer();
attachFiles.addEventListener("change", (e) => {
const currentSubmitData = Array.from(submitData.files);
// For each addded file, add it to submitData if not already present
[...e.target.files].forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// Generate a preview <img> for each selected file
[...submitData.files].forEach(showFiles);
});
function showFiles(file) {
let uploadImageWrapper = document.createElement("figure");
let previewImage = new Image();
const removeImage = document.createElement('div');
removeImage.className = 'remove-image';
removeImage.textContent = ' X ';
removeImage.addEventListener('click', () => previewImage.click());
removeImage.addEventListener("click", () => {
const name = file.name;
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
attachFiles.files = submitData.files;
previewImage.remove();
removeImage.remove();
});
// Set relevant <img> attributes
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
// Append <figure> and <img> preview node to DOM
previewWrapper.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(previewImage); // <img>
uploadImageWrapper.insertAdjacentElement('afterbegin', removeImage); // 'x' to remove the image
}
* {
position: relative;
}
form {
padding: 1rem 2rem;
width: 50%;
border: 1px solid;
}
input,
button {
display: block;
margin: 2rem 0;
}
.remove-image {
background: #000;
width: 30px;
height: 30px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
top: 10px;
left: -10px;
position: absolute;
z-index: 2;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
<form enctype="multipart/form-data" method="post" id="upload-images-form">
<input id="attach-files" type="file" name="attach-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="show-selected-images"></div>
</form>
What I am trying to achieve is when my device size is less than 736 px, the button should animate. I got the button working correctly, however, I’m struggling to work with the specific screen size.
$(window).resize(function() {
if ($(window).width() <= 736) {
// do something
let myBtn = document.querySelector(".btn");
let btnStatus = false;
myBtn.style.background = "#FF7F00";
function bgChange() {
if (btnStatus == false) {
myBtn.style.background = "#FF0000";
btnStatus = true;
}
else if (btnStatus == true) {
myBtn.style.background = "#FF7F00";
btnStatus = false;
}
}
myBtn.onclick = bgChange;
}
});
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="btn">CLICK ME</button>
Here's an implementation of what you're trying to do that uses:
class to alter button styling instead of style,
vanilla JavaScript instead of jQuery.
Using class is a good idea, as it keeps the styling in the CSS and out of the JavaScript code.
Using vanilla JavaScript whenever you can is preferable.
Here are the two new classes:
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
.btn-small-screen class is applied when the window is small, .btn-clicked is toggled whenever the button is clicked.
Here's the JavaScript code:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup mode on resize
window.addEventListener('resize', setButtonMode);
// setup mode at load
window.addEventListener('load', setButtonMode);
References:
Document.querySelector()
Window.innerWidth
Element.classList
DOMTokenList.toggle()
DOMTokenList.add()
DOMTokenList.remove()
EventTarget.addEventListener()
A working example:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup small mode on resize
window.addEventListener('resize', setButtonMode);
// setup small mode at load
window.addEventListener('load', setButtonMode);
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
<button class="btn">CLICK ME</button>
Note: There is one optimization that I left out, so the code would be easier to follow.
Notice that setButtonMode() changes the DOM every time, even though it might already be set to the desired mode. This is inefficient.
To improve efficiency and only change the DOM when necessary, you could introduce a state variable (call it smallMode), and set it true whenever appropriate. Like so:
let smallMode = false;
function setButtonMode () {
if (isSmallWindow()) {
if (!smallMode) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
smallMode = true;
}
} else if (smallMode) {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
smallMode = false;
}
}
I am trying to make a basic theme switching code using HTML and JavaScript. But if I hit back or refresh, the selection is cancelled and the page is back to normal.
Can I add any function in JavaScript so that the selection stays till cache clean or something like that?
var x = 0;
var themeValue = 0;
function changeTheme3() {
themeValue ++;
if (themeValue > 2) {
themeValue = 1;
}
if (themeValue == 1) {
document.body.style.backgroundColor = "grey";
} else if (themeValue == 2) {
document.body.style.backgroundColor = "white";
}
}
button {
display: block;
margin: 0 auto;
padding: 0;
width: 250px;
height: 70px;
border: 2px solid #1ECD97;
border-radius: 40px;
background: transparent;
color: #1ECD97;
letter-spacing: 1px;
font-size: 18px;
font-family: 'Montserrat', sans-serif;
}
button:hover {
background-color: #1ECD97;
color: #fff;
}
<div id="theme-button">
<button type="button" onclick="changeTheme3()">Change Theme</button>
</div>
I'd suggest making use of LocalStorage. If you want the user to be able to customize their experience, you need to save that somewhere. This is typically done through a Session or Database, however both of those are back-end solutions.
For front-end, you have the options of setting a cookie or using localstorage. If you want the theme selection to be limited to a single session, you can also use sessionStorage
For implementation
function changeTheme3() {
themeValue ++;
if (themeValue > 2) {
themeValue = 1;
localStorage.setItem('themeValue', themeValue);
}
if (themeValue == 1) {
document.body.style.backgroundColor = "grey";
} else if (themeValue == 2) {
document.body.style.backgroundColor = "white";
}
}
Then, somewhere when you load a page, you need to pull this value from storage. (Note, localStorage saves values as strings, so you may want to parseInt() your value when retrieving).
function loadTheme(){
if (localStorage && localStorage.getItem('themeValue'){
var storedTheme = parseInt(localStorage.getItem('themeValue'));
//do your logic to set the theme based on value
} else {
//do nothing or choose to set your default theme as the value into storage
}
}
One side note, depending on which browsers you are supporting, you may want to make sure you're checking to see if localStorage is supported. It has good support for any modern browser.
you can do that using sqlite html5 for example check this link http://www.html5rocks.com/en/tutorials/webdatabase/todo/ :D
here for simple example sir
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript">
var db = openDatabase('db', '1.0', 'theme selector', 1000); //db name, version, desc, size in byte
var msg;
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS THEME (id unique, theme)');
});
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM THEME WHERE id = 1', [], function (tx, results) {
var len = results.rows.length, i;
var value = 'theme_1';
if(len == 0 ){
tx.executeSql('INSERT INTO THEME (id, theme) VALUES (1, ?)', [value]);
}else{
value = results.rows.item(0).theme;
}
document.querySelector('#theme_selected').innerHTML = value;
document.getElementById('theme_select').value = value;
}, null);
});
function update (){
var selector = document.getElementById('theme_select');
var value = selector[selector.selectedIndex].value;
db.transaction(function (tx) {
tx.executeSql('UPDATE THEME SET theme = ? WHERE id=1', [value]);
document.querySelector('#theme_selected').innerHTML = value;
});
}
</script>
</head>
<body>
<select id="theme_select">
<option value="theme_1" >theme 1</option>
<option value="theme_2">theme 2</option>
</select>
<button onclick="update()">update</button>
<div id="theme_selected"></div>
</body>
</html>