I want to display several images on my webpage via javascript using Firebase Storage.
I use:
getDownloadURL(ref(storage, imageIndexPathRoot)).then((url) =>{
img.setAttribute('src', url);
The problem is that only the last image is displayed. If I have e.g. 5 pictures in my folder, the getDownload line with the imageIndexPathRoot is correct executed for all 5 images, but only at the last image the line img.setAttribute... is executed and unly this image was displayed on the webpage.
// Now we get the references of these images
listAll(listRef).then((res) => {
res.items.forEach(function(imageRef) {
// And finally display them
console.log(imageRef);
displayImage(imageRef);
});
}).catch((error) => {
// Handle any errors
console.log("Error 1");
});
function displayImage(imageRef) {
const img = document.getElementById('tierFoto');
img.src = imageRef.fullPath;
getDownloadURL(ref(storage, imageIndexPathRoot)).then((url) =>{
img.setAttribute('src', url);
})
.catch((error) => {
console.log(error);
});
}
}
Every time displayImage is called, it does:
const img = document.getElementById('tierFoto')
So it sets each image to the same HTML element, which explains why you only see the last of the images.
If you want to show a separate image each time you call displayImage, you'll need to get (or pass in) a different HTML element each time. How to do that, depends on your HTML structure.
For example, if your HTML has img elements with numbered IDs, you could do:
res.items.forEach(function(imageRef, i) { // 👈 get index from forEacj
displayImage(imageRef, i); // 👈 pass it to displayImage
});
And then
function displayImage(imageRef, index) { // get index from caller
const img = document.getElementById('tierFoto-'+index); // use index to look up element
...
Related
I have a button to add a photo to a webpage. The button is currently set up to input a dummy image. I'm trying to get it to allow me to select my own image and then have it save in local storage to stay on the page when it's reloaded. I've tried a few things to get this but I can't. I've been sat looking at the code trying to work it out for ages.
The two tasks are as follows:
When an image entry is added to the DOM, make an image item containing the image data, and store it in local storage.
When the file input element selection changes, use a FileReader object to convert the file to a data URL and add an image entry to the DOM using this data URL.
The add image part of the code is as follows:
function addImageEntry(key, data) {
// Create a image element
var imgElement = new Image();
imgElement.alt = "Photo entry";
imgElement.src = data;
addSection(key, imgElement);
}
function addEntryClick() {
var key = "diary" + Date.now();
var initialText = "";
var isNewEntry = true;
addTextEntry(key, initialText, isNewEntry);
}
function addPhotoClick() {
var inputElement = document.querySelector("#image input");
inputElement.click();
}
function processFile(inputChangeEvent) {
console.log("processFile called with arguments:", {
inputChangeEvent,
});
function addImage(data) {
console.log("addImage called with arguments:", {
data,
});
var key = "diary" + Date.now();
addImageEntry(key, data);
// TODO: Task 1 of 2
// Make an image item using the given data URL
// (demonstrated elsewhere in this file)
// Store the item in local storage using the given key
// (demonstrated elsewhere in this file)
var imageData = data;
var item = makeItem("image", imageData);
localStorage.setItem(key, item);
}
// Add a 'dummy' image entry
addImage(window.DUMMY_DATA_URL);
// TODO: Task 2 of 2
// Complete this function to read a file when it is selected:
// (imgElement and messageElement do not exist in this app so remove that code)
// ...then call addImage using the data URL you obtain
// ...and comment out line above using window.DUMMY_DATA_URL
// Clear the file selection (allows selecting the same file again)
inputChangeEvent.target.value = "";
}
The TODO parts is where I am working and have some comments with them. I think I have task 1 done as when the dummy image is added and the page is refreshed it still appears.
I have some image files that are previewed as part of a file uploader. A well as validations happening in the backend (PHP) I am setting up frontend validations too.
When the images are attached to the uploader's file <input> element, a preview thumbnail is generated using URL.createObjectURL()
These previews are looped over and a decode() method inside the loop detects if they fail certain criteria. If they fail then an error message is outputted / attached to the specific image file (preview thumbnail) that fails.
If this error message is detected, a generic error message appears at the top of the page telling the user to check the errors shown on the image previews thumbnails.
The Issue
The generic error message at the top of the page is outputted, but because this all happens inside a loop, if 5 files are attached, and only one fails, the generic error message flashes because it is being detected on each instance of the loop.
The Question
How do I detect the last instance of a loop and only output the generic error message when the last loop instance happens?
Note: I have included a simplified example of the code below, but would be happy to show the full code (but I am thinking this might be overkill in relation to the issue).
submitData = new DataTransfer();
// initial 'change' event listener on the files input element which is stored in a variable 'attachFiles'
attachFiles.addEventListener("change", (e) => {
// stuff here handles the data transfer method and detects any change in the number of files attached etc
// then run the 'showFiles function on each file attached
[...submitData.files].forEach(showFiles);
});
function showFiles(file) {
let previewImage = new Image();
// Set relevant <img> attributes
previewImage.className = "upload-preview-image";
previewImage.src = URL.createObjectURL(file);
// get the original width and height values of the thumbnail using the decode() method
previewImage.decode().then((response) => {
let w = previewImage.naturalWidth;
let h = previewImage.naturalHeight;
// error message that is appended to each image when it fails
let resError = `<p class="upload-preview-error">Image must be bigger than 2MP</p>`
if(w * h < 2000000) {
// append the above errorMessage to the specific image preview in question
previewImage.insertAdjacentHTML('beforebegin', resError);
}
// store HTML class from the above template string in a variable
let imgError = document.querySelectorAll('.upload-preview-error');
// set the variable that changes to true when an error message is detected
let imgErrorMessage = false;
/* SOMEHOW RUN THIS NEXT CODE ON THE LAST INSTANCE OF THE LOOP,
SO THAT THE MAIN ERROR MESSAGE AT THE TOP OF THE PAGE IS ONLY OUTPUTTED ONCE
*/
if(imgError.length) {
if (imgErrorMessage === false) {
// Append this generic message into a <div> with the class '.js-upload-error'
document.querySelector('.js-upload-error').innerHTML = `<p class="warning">YOU HAVE ERRORS. CHECK YOUR IMAGES BELOW</p>`
imgErrorMessage = true;
}
}
}).catch((encodingError) => {
// Do something with the error.
});
} // end of showfiles(file)
You look to have a dummy error handling block here:
}).catch((encodingError) => {
// Do something with the error.
});
When there are problems, consider throwing an error inside showFiles and then handling it in the caller by using Promise.allSettled to wait for all calls to settle (to either fulfill or reject).
From a UI perspective, you almost certainly also want to actually do something to tell the user when there's some other error - in other words, drop the empty // Do something with the error. block entirely and let the caller deal with it. (Or, if you actually do do something with the error in that block, you can re-throw it so that the caller's .allSettled can see that there was a problem.)
function showFiles(file) {
const previewImage = new Image();
previewImage.className = "upload-preview-image";
previewImage.src = URL.createObjectURL(file);
return previewImage.decode().then((response) => {
const w = previewImage.naturalWidth;
const h = previewImage.naturalHeight;
const resError = `<p class="upload-preview-error">Image must be bigger than 2MP</p>`
if (w * h < 2000000) {
previewImage.insertAdjacentHTML('beforebegin', resError);
throw new Error('Image too small');
}
});
}
and replace
[...submitData.files].forEach(showFiles);
with
Promise.allSettled([...submitData.files].map(showFiles))
.then((results) => {
if (results.some(result => result.status === 'rejected')) {
document.querySelector('.js-upload-error').innerHTML = `<p class="warning">YOU HAVE ERRORS. CHECK YOUR IMAGES BELOW</p>`;
// you can put more error handling here
// or you can put it in the .catch block if you re-throw
}
});
If there's an error other than the image being too small, you might add such an error message in the .catch, such as:
return previewImage.decode().then((response) => {
// ...
})
.catch((error) => {
if (error.message !== 'Image too small') {
previewImage.insertAdjacentHTML('beforebegin', '<p class="upload-preview-error">Decoding error</p>');
}
throw error;
});
or use the .then(success, fail) syntax.
I have coded a small API gallery that fetches an image from an API, but I need to know how do I store and save an image?
My codepen: https://codepen.io/aaron_1986/pen/VwdvqWB
Below is my JavaScript code that I used to create the API gallery that fetches an image from an API and displays the image on screen.
function get_image() {
let access_key = 'YmMDTJCtZaK6veBdER5WkjyqmgGBRyH6Bpdqt7WcrM4';
let url = `https://api.unsplash.com/photos/random /?client_id=YmMDTJCtZaK6veBdER5WkjyqmgGBRyH6Bpdqt7WcrM4`;
let imageElement = document.querySelector('.image');
fetch(url)
.then(function(response){
//console.log(response.json())
return response.json();
})
.then(function(jsonData){
imageElement.src = jsonData.urls.regular;
})
}
//// Array
let selected_images = [];
document.querySelectorAll('.large-image').forEach(function(img, idx) {
img.src = "" + idx;
img.addEventListener('click', function({target: src}){
if(!selected_images.includes(src)) {
selected_images.push(src);
}
console.log(selected_images);
});
});
Below is a basic example on how to save an email and image using sessionStorage similar to the example provided by OP.
NOTE
OP mentioned they have to recreate the example site they provided (indicating some sort of assignment), however this example is not a full solution to that. This is only to answer the specific question asked by OP "how do I store and save an image?".
const _SaveData = () => {
// Get elements
let img = document.querySelector('.image'),
email = document.querySelector('#email').value;
// Validate email address format
if(!/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
console.log(`Invalid Email Address!`)
return
}
// Set the new data and retrive any previously stored data
let newData = {
email: email,
img: img.src
},
data = (sessionStorage.getItem("data")) ? JSON.parse(sessionStorage.getItem("data")) : []
// Update data
data = [...data, newData]
// Save data
sessionStorage.setItem("data", JSON.stringify(data))
console.log(`Data Saved!`)
}
As long as you add a field with the id email and set your Save Data! button's onclick value to _SaveData(), this will do the intended purpose. As far as recreating that site goes, there are additional things you will need to do (such as validating duplicate images/emails and generating new images upon save).
I'm doing an API for a gallery; so, I create a method that let copy an image from the database.
Now, I want to add a number at the end of the copy-image name. For example:
-original image name: image
-copy image name: image(1)
-2nd copy image name: image(2)
How can I add the number to the name of copied name automatically?
'use strict'
let imageObject= require('../models/image-object');
let fs=require('fs');
let path= require('path');
let gallery_controllers={
copyImage:function(req,res){
//get the id param of the image to copy
let imageId=req.params.id;
if(imageId==null) return res.status(404).send({message:"no ID defined"});
//I search the requiere image on the database
imageObject.findById(imageId,(err,image)=>{
if(err) return res.status(500).send({message:'err to response data'});
if(!image) return res.status(404).send({message:'image not found'});
if(image){
//set a new model-object
let imageCopied= new imageObject();
imageCopied.name= image.name;
imageCopied.image=image.image;
//save image copied on the database
imageCopied.save((err,image_copied)=>{
if(err) return res.status(500).send({message:"error 500"});
if(!image_copied) return res.status(404).send({message:"error 404"});
return res.status(200).send({
image:image_copied
})
})
}
})
},
}
Here's a function that looks in the directory passed to it for files of the name file(nnn) where nnn is some sequence of digits and returns back to you the full path of the next one in sequence (the one after the highest number that already exists).
This function pre-creates a placeholder file by that name to avoid concurrency issues with multiple asynchronous operations calling this function and potentially conflicting (if it only returned the filename without creating the file). To further handle conflicts, it creates the placeholder file in a mode that fails if it already exists (so only one invocation of this function will ever create that particular file) and it automatically retries to find a new number if it gets a conflict (e.g. someone else created the next available file before we got to it). All of this logic is to avoid the subtleties of possible race conditions in creating the next filename in the sequence.
Once the caller has a unique filename that this resolves to, then it is expected that they will overwrite the placeholder contents with their own contents.
// pass directory to look in
// pass base file name so it will look for next in sequence as in "file(3)"
// returns the full path of the unique placeholder file it has created
// the caller is then responsible for that file
// calling multiple times will create a new placeholder file each time
async function findNextName(dir, base) {
let cntr = 0;
const cntr_max = 5;
const regex = new RegExp(`^${base}\\((\\d+)\\)$`);
async function run() {
const files = await fs.promises.readdir(dir);
let highest = 0;
for (let f of files) {
let matches = f.match(regex);
if (matches) {
let num = parseInt(matches[1]);
if (num > highest) {
highest = num;
}
}
}
let name = `${base}(${highest + 1})`;
// create placeholder file with this name to avoid concurrency issues
// of another request also trying to use the same next file
try {
// write to file, fail if the file exists due to a concurrency issue
const fullPath = path.resolve(path.join(dir, name));
await fs.promises.writeFile(fullPath, "placeholder", { flag: "wx" });
return fullPath;
} catch (e) {
// if this fails because of a potential concurrency issue, then try again
// up to cntr_max times to avoid looping forever on a persistent error
if (++cntr < cntr_max) {
return run();
} else {
throw e;
}
}
}
return run();
}
You could call it like this:
findNextName(".", "file").then(filename=> {
console.log(filename);
}).catch(err => {
console.log(err);
});
I am trying to implement a multiple image file selector in my React app.
This is my input element:
<input
type="file"
multiple
onChange={handleImageChange}
/>
{renderPhotos(venueImages)}
These are the functions that are called when files are chosen:
const [venueImages, setVenueImages] = useState([]);`
const renderPhotos = source => {
console.log(source); ////////log 1
return source.map(photo => {
return <img src={photo} key={photo} />;
});
};
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files);
console.log(filesArray); ///////// log 2
filesArray.forEach(file => {
const tempUrl = URL.createObjectURL(file);
console.log(tempUrl); ////// log 3
setVenueImages([...venueImages, tempUrl]);
});
}
};
I call renderPhotos to show a preview off all the selected photos before uploading.
The issue I'm facing is as follow:
If I choose, for example, 5 photos, only 1 would end up being rendered on screen.
I've inserted console logs in handleImageChange, and what I get logged is confusing me even more.
The second log (I've numbered them in my code) prints an array of 5 files.
After from log 3 that I'll get 5 logs of the newly generated temporary URLs for each of the files.
But log 1, would only get printed once.
Now - if I'll click the input element to choose more files, I'll end up with another rendered image.
So basically everytime I choose images, no matter how many I've chosen, I'll only get one more image rendered.
The problem is that you are referencing the venueImages array in your setVenueImages call. Because the state update performed by setVenueImages is asynchronous, then you cannot be certain that venueImages contains all of the previous state updates.
The set state function can be passed a function that takes the old value, instead of just passing it the new value. This will ensure that your state updates are sequential. Replace your setVenueImages call with this:
setVenueImages(prevImages => [...prevImages, tempUrl]);
An additional change that I will suggest is to perform a concatenation of all images, instead of adding them one by one. This should be faster.
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files).map(file => URL.createObjectURL(file));
console.log(filesArray); ///////// log 2
setVenueImages(prevImages => prevImages.concat(filesArray));
}
};
That is happening because when you are saving the tempUrl, only one url is getting saved. Also do not set the state by adding images one by one.
Updated version of your handleImageChange function can be
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files);
const tempUrls = filesArray.map(file => URL.createObjectURL(file)))
setVenueImages([...venueImages, ...tempUrls])
}
};