I am using the NASA API which displays images, and when the image is clicked, it displays a modal with the images and details of the image.
However, in the tutorial, there is a save to favourites feature that adds the image and details to the favourites section into local storage.
My question is, how do I implement the add to favourites feature link into the modals, and then save the image and details to the local storage favourites section?
const resultsNav = document.getElementById("resultsNav");
const favoritesNav = document.getElementById("favoritesNav");
const imagesContainer = document.querySelector(".images-container");
const saveConfirmed = document.querySelector(".save-confirmed");
const loader = document.querySelector(".loader");
// NASA API
const count = 3;
const apiKey = 'DEMO_KEY';
const apiUrl = `https://api.nasa.gov/planetary/apod?api_key=${apiKey}&count=${count}`;
let resultsArray = [];
let favorites = {};
// Show Content
function showContent(page) {
window.scrollTo({
top: 0,
behavior: "instant"
});
if (page === "results") {
resultsNav.classList.remove("hidden");
favoritesNav.classList.add("hidden");
} else {
resultsNav.classList.add("hidden");
favoritesNav.classList.remove("hidden");
}
loader.classList.add("hidden");
}
// Create DOM Nodes
function createDOMNodes(page) {
const currentArray =
page === "results" ? resultsArray : Object.values(favorites);
currentArray.forEach((result) => {
// Card Container
const card = document.createElement("div");
card.classList.add("card");
// Link that wraps the image
const link = document.createElement("a");
// Get the modal
var modal = document.getElementById("myModal");
// Get the image and insert it inside the modal - use its "alt" text as a caption
var img = document.getElementById("myImg");
var modalImg = document.getElementById("img01");
var captionText = document.getElementById("caption");
img.onclick = function() {
modal.style.display = "block";
modalImg.src = event.target.src;
captionText.innerHTML = event.target.alt;
}
// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];
// When the user clicks on <span> (x), close the modal
span.onclick = function() {
modal.style.display = "none";
}
// Image
const image = document.createElement("img");
image.src = result.url;
// image.alt = "NASA Picture of the Day";
image.alt = result.title + "<br>" + result.explanation + "<br>" + ' ' + result.date;
image.loading = "lazy";
image.classList.add("card-img-top");
// Card Body
const cardBody = document.createElement("div");
cardBody.classList.add("card-body");
// Card Title
const cardTitle = document.createElement("h5");
cardTitle.classList.add("card-title");
cardTitle.textContent = result.title;
// Save Text
const saveText = document.createElement("p");
saveText.classList.add("clickable");
if (page === "results") {
saveText.textContent = "Add To Favorites";
saveText.setAttribute("onclick", `saveFavorite('${result.url}')`);
} else {
saveText.textContent = "Remove Favorite";
saveText.setAttribute("onclick", `removeFavorite('${result.url}')`);
}
// Card Text
const cardText = document.createElement("p");
cardText.textContent = result.explanation;
// Footer Conatiner
const footer = document.createElement("small");
footer.classList.add("text-muted");
// Date
const date = document.createElement("strong");
date.textContent = result.date;
// Copyright
const copyrightResult =
result.copyright === undefined ? "" : result.copyright;
const copyright = document.createElement("span");
copyright.textContent = ` ${copyrightResult}`;
// Append everything together
footer.append(date, copyright);
cardBody.append(cardTitle, saveText, cardText, footer);
link.appendChild(image);
card.append(link); // hide cardBody
// Append to image container
imagesContainer.appendChild(card);
});
}
// Update the DOM
function updateDOM(page) {
// Get favorites from local storage
if (localStorage.getItem("nasaFavorites")) {
favorites = JSON.parse(localStorage.getItem("nasaFavorites"));
}
imagesContainer.textContent = "";
createDOMNodes(page);
showContent(page);
}
// Get 10 images from NASA API
async function getNasaPictures() {
// Show Loader
loader.classList.remove("hidden");
try {
const response = await fetch(apiUrl);
resultsArray = await response.json();
updateDOM("results");
} catch (error) {
// Catch Error Here
}
}
// Add result to favorites
function saveFavorite(itemUrl) {
// Loop through the results array to select favorite
resultsArray.forEach((item) => {
if (item.url.includes(itemUrl) && !favorites[itemUrl]) {
favorites[itemUrl] = item;
// Show save confirmation for 2 seconds
saveConfirmed.hidden = false;
setTimeout(() => {
saveConfirmed.hidden = true;
}, 2000);
// Set Favorites in Local Storage
localStorage.setItem("nasaFavorites", JSON.stringify(favorites));
}
});
}
// Remove item from favorites
function removeFavorite(itemUrl) {
if (favorites[itemUrl]) {
delete favorites[itemUrl];
localStorage.setItem("nasaFavorites", JSON.stringify(favorites));
updateDOM("favorites");
}
}
// On Load
getNasaPictures();
An interesting question, I check the Nasa.api and i find that the sever will return you an array that contain object, so a good option is to use an array to store all the liked item and it will be easier for you display.
My suggestion for you is to directly store the info that nasa.api give to you to the array and store in localStorage. It will be easier for you to display later.
I also created an example for you that use nasa.api that you could use it since I doesn't have your full code and how you get the code/display it, but you could just see the part that how i modify the localStorage
So in the example I created, every time user click on the liked button, it will save to liked (array) and save to localStorage. Every second time, user click the button , we will delete the item from the liked array and update localStorage.
let source;
let xhr = new XMLHttpRequest();
let link = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&count=5"
xhr.open('GET', link, true);
xhr.send();
let liked = JSON.parse(localStorage.getItem("likeditem"));
if (liked == null) liked = [];
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
source = JSON.parse(xhr.responseText);
create()
}
} //Check if liked is null, if it is, assign empty array value to it.
function create() {
for (let i = 0; i < source.length; i++) {
console.log(source[i])
document.body.innerHTML += `
<img src=${source[i].url}>
<div >${source[i].title}</div>
<div >Date: ${source[i].date}</div>
<div>Copyright: ${source[i].copyright}</div>
<div>Media_type: ${source[i].media_type}</div>
<div>Service_version: ${source[i].service_version}</div>
<div>Explanation: ${source[i].explanation}</div>
<button onclick='save(${i})'>Save to like</button>
`
//TO save/remove liked item from local Storage when button clicked
window.save = function(index) {
let thisbutton = document.querySelectorAll('button')[index]
if (thisbutton.textContent == 'Save to like') {
liked.push(source[index])
localStorage.setItem('likeditem', JSON.stringify(liked));
thisbutton.textContent = "Saved"
} else {
//Remove it from local storage when user click it every two times
liked.splice(liked.indexOf(source[index]), 1)
localStorage.setItem('likeditem', JSON.stringify(liked));
thisbutton.textContent = 'Save to like'
}
}
}
}
In term of how to display the element from localStorage, also an example.
We have already saved all the info about the image to localStorage before , so we just use the way we display the info from nasa.api.
let liked = JSON.parse(localStorage.getItem("likeditem"));
for (let i in liked){
//Immediately return the for loop if no value stored in loal storage
if(!liked) break;;
document.body.innerHTML += `
<img src=${liked[i].url}>
<div >${liked[i].title}</div>
<div >Date: ${liked[i].date}</div>
<div>Copyright: ${liked[i].copyright}</div>
<div>Media_type: ${liked[i].media_type}</div>
<div>Service_version: ${liked[i].service_version}</div>
<div>Explanation: ${liked[i].explanation}</div>
`
}
Update:
Save to localStorage:
Display from localStorage:
EDIT : This is edit to my previous answer. This piece of of code should help you. (NOTE: this code snippet wont work here, try it from your system as localStorage is not supported here.)
let nasaImages = [
{
id:1,
url:"https://example.com/image-url-1"
},
{
id:2,
url:"https://example.com/image-url-2"
},
{
id:3,
url:"https://example.com/image-url-3"
}
];
let favs = [];
//check if key exists in localstorage
if(localStorage.nasaFavs){
favs = JSON.parse(localStorage.nasaFavs)
}
renderImages();
function renderImages(){
$("#images").html(nasaImages.map(image => (
`<div>
id: ${image.id}
<img src='${image.url}'>
${favBtn(image.id)}
</div>`
)
).join(""));
}
//render the fav button
function favBtn(id){
if(favs.includes(id)){
return `<button onclick="favUnfav(false,${id})">Remove from fav</button>`;
}
else{
return `<button onclick="favUnfav(true,${id})">Add to fav</button>`;
}
}
function favUnfav(isFav=true, id=null){
//write your logic to add to fav or remove from fav
if(isFav){
favs.push(id);
}
else{
favs = favs.filter(e => e!=id);
}
//save in localstorage
localStorage.nasaFavs = JSON.stringify(favs);
//re render the DOM
renderImages()
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="images"></div>
As #ba2sik already posted, first get familiar with localStorage API.
You are already saving the whole favorites object into local storage under nasaFavorites key.
In order to read it, use JSON.parse(localStorage.getItem('nasaFavorites')) to deserialize the content every time you want to access it.
Just note that the code above doesn't handle edge cases or errors so you might want to handle it with try/catch.
Related
I'm actually building an extension for auto liking and sharing tweets.
it likes and share tweets but not with hashtag in if condition. can you help me out?
let content = document.querySelectorAll(".r-kzbkwu");
[].forEach.call(content, function (h) {
// console.log(h);
if (h.textContent.includes("#letsconnect")) {
let retweet = document.getElementsByClassName("r-bt1l66");
let check = retweet[3].getAttribute("data-testid");
//if already has not been liked then click like button
// console.log(check);
if (check != "unlike") {
retweet[3].click();
}
let retweetCheck = retweet[2].getAttribute("data-testid");
if (retweetCheck != "unretweet") {
retweet[2].click();
let dropdown = document.getElementById("layers");
// console.log(dropdown);
// reach to retweet dropdown
let reqDev =
dropdown.children["1"].children["0"].children["0"].children["0"]
.children[1].children["2"].children["0"];
let attt = reqDev.getAttribute("data-testid");
// if already has not been retweeted then click button
// console.log(attt);
if (attt != "unretweetConfirm") {
reqDev.click();
}
}
}
});
I am combining 2 concepts of XHR get (which fetches JSON URL from an attribute in web component) and displaying the JSON data as a list. All that is working fine.
But when I want to toggle the list on / off, somehow the query selector All doesnt work. but it does work in browser console.
Maybe because in connected callback, the DOM hasnt been rendered, hence it cannot be edited ? I have tried rendered Callback / render / set timeout but nothing works. Also tried 2 connectedCallback functions, but query selector still doesnt detect the JSON list.
In browser console, document.querySelectorAll('.history-list') works fine. What should I do to make it work. Many thanks.
Here is the code :
connectedCallback(){
let lock_time_ul = document.querySelector('#lock-time-ul');
// fetch data from JSON file (WORKING)
// fetch JSON URL value (ONLY if attribute has been set)
if (this.hasAttribute('json-url'))
{
this.json_url = this.getAttribute('json-url');
// fetch data from this URL
const xhr = new XMLHttpRequest();
xhr.open('GET', this.json_url);
xhr.onload = function(){
this.json_output = JSON.parse(xhr.response);
// (WORKING FINE)
this.json_output.forEach(el => {
// change el.type from enrollment -> locked and cancellation -> unlocked
if (el.type == "enrollment")
{
el.type = "Locked";
}
else if(el.type == "cancellation")
{
el.type = "Unlocked";
}
var li = document.createElement('li');
var span = document.createElement('span');
span.className = "date";
li.className = "history-list";
span.appendChild(document.createTextNode(el.date));
var div_lock_wrapper = document.createElement('div');
div_lock_wrapper.className = "lock-wrapper";
div_lock_wrapper.style.fontWeight = 500;
div_lock_wrapper.appendChild(document.createTextNode(el.type));
li.appendChild(span);
li.appendChild(div_lock_wrapper);
lock_time_ul.appendChild(li);
})
}
xhr.send();
}
// javascript for sidebar show / hide (NOT WORKING, QUERY SELECTOR ALL history-list SHOWS NULL)
let toggle_lock_history_btn = document.querySelector(".history-title");
let toggle_lock_history_list = document.querySelectorAll(".history-list");
let toggle_show_all_text = document.querySelector(".show-all");
toggle_lock_history_btn.addEventListener('click', function()
{
// check the history-title first
if (toggle_lock_history_btn.textContent == "Hide lock history")
{
// toggle off
toggle_lock_history_list.forEach(el => {
el.style.display = "none";
})
// hide show-all
toggle_show_all_text.style.display = "none";
// change Hide to Show in history-title
toggle_lock_history_btn.textContent = "Show lock history";
}
else if (toggle_lock_history_btn.textContent == "Show lock history")
{
// toggle on
toggle_lock_history_list.forEach(el => {
el.style.display = "";
})
// show show-all
toggle_show_all_text.style.display = "";
// change Show to Hide in history-title
toggle_lock_history_btn.textContent = "Hide lock history";
}
})
} // connectedCallback ENDS
My HTML
<test json-url="data.json"></test>
Got the solution :
window.onload = init;
function init(){
// the code to be called when the dom has loaded
}
since the element I was trying to get by ID was created dynamically (loaded by ajax and created by script).
I have a function addBookToList() that creates an HTML row of elements. I have a button inside the row along with other elements.
I'm trying to add an "onclick" event with a toggle() function to that button in order to change the read status of a book (yes or no).
Is there a way I can click on that button and have the function toggle() changing the value of the previous element sibling (value.read) and back? I added the event but I don't know how to target the button and value.read.
// Book constructor
function Book(author, title, pages, read) {
this.title = title;
this.author = author;
this.pages = pages;
this.read = read;
}
// Empty array to store books
let myLibrary = [];
// Event listener when clicking SUBMIT button on the form
form.addEventListener('submit', (e) => {
e.preventDefault();
// Hide form and show home page
document.querySelector('.table-box').style.display = 'block';
document.querySelector('.para-div').style.display = 'block';
form.style.display = 'none';
// Get values from User
let title = document.querySelector('#title').value;
let author = document.querySelector('#author').value;
let pages = document.querySelector('#num-pages').value;
let read = getRead();
// Instantiate book
const book = new Book(author, title, pages, read);
// Push book to the library, show it on the UI and clear the form
myLibrary.push(book);
addBookToList();
// Add book to Local Storage
addBook();
// Show success alert
showAlert('Book added!', 'success');
// Clear form
form.reset();
});
// Get value of radio button
function getRead() {
const radioBtn = document.querySelectorAll('input[name="radio"]');
let selectValue;
for(const i of radioBtn) {
if(i.checked) {
selectValue = i.value;
}
}
return selectValue;
}
function addBookToList() {
// Create new row element
const row = document.createElement('tr');
// Loop through myLibrary array
myLibrary.forEach(value => {
// Add the book to the table
row.innerHTML = `
<td>${value.title}</td>
<td>${value.author}</td>
<td>${value.pages}</td>
<td>${value.read}</td>
<td><button class="toggle" onclick="toggle()">Change read status</button></td>
<td>X</td>`;
});
// Append the row to list
list.appendChild(row);
}
In your function addBookToList, you can use IIFE.
function addBookToList() {
// Create new row element
const row = document.createElement('tr');
// Loop through myLibrary array
myLibrary.forEach(value => {
// Add the book to the table
row.innerHTML = `
<td>${value.title}</td>
<td>${value.author}</td>
<td>${value.pages}</td>
<td>${value.read}</td>
<td><button class="toggle">Change read status</button></td>
<td>X</td>`;
});
// Append the row to list
list.appendChild(row);
const toggleBtn = document.querySelector('.toggle');
toggleBtn.onclick = (function(value) {
return function(){
// update read property on value
value.read = !value.read;
}
})(value);
}
I am a bit stuck and hoping someone can help me, please.
Basically I have coded a shopping cart and am currently trying to get the cart to display a message saying "Cart is empty" after all of the cart items have been removed.
Everything is working ok apart from the "Cart is empty" message being re-displayed after the cart is empty.
I have tried a few things but cannot seem to get the emptyCartMessage to display when removing the last cart item.
Just for extra context my cart items each have an independent 'remove' button attached to them.
My code is below.
Thank you for any help, I do appreciate it!
const currentCartItems = document.getElementsByClassName('cart-item');
const emptyCartMessage = document.createElement('p');
emptyCartMessage.innerHTML = 'Your cart is empty.';
// EMPTY CART ITEM DISPLAY MESSAGE
shoppingCart.appendChild(emptyCartMessage);
// SHOPPING AREA BUTTON EVENT LISTENER
for (var i = 0; i < addToCartButton.length; i++) {
addToCartButton[i].addEventListener('click', createCartItem);
}
function createCartItem(event) {
//CREATE CART LI ITEM
const newItem = document.createElement('li');
newItem.className = 'cart-item';
//newItem.innerHTML = event.target.value;
//GET AND SET SHOP/CART ITEM VALUE
const itemValue = document.createElement('p');
itemValue.innerHTML = event.target.value;
//CREATE CART ITEM DESCRIPTION
const p = document.createElement('p');
p.innerHTML = itemDescription;
//CREATE CANCEL CART ITEM BUTTON
const cancelItemImage = document.createElement('img');
cancelItemImage.className = "remove-button";
cancelItemImage.src = "images/cancel-icon.png";
cancelItemImage.alt = "red remove icon";
newItem.appendChild(itemValue);
newItem.appendChild(p);
newItem.appendChild(cancelItemImage);
shoppingCart.appendChild(newItem);
if (currentCartItems.length > 0) {
emptyCartMessage.className = 'hide-empty-cart';
} else if (currentCartItems.length <= 0) {
emptyCartMessage.classList.remove('hide-empty-cart');
}
}
// REMOVE CART ITEMS BUTTON
shoppingCart.addEventListener('click', (e) => {
if (e.target.className === 'remove-button'){
const li = e.target.parentNode;
const ol = li.parentNode;
ol.removeChild(li);
}
});
Please remove this line
const currentCartItems = document.getElementsByClassName('cart-item');
We will use this variable inside the function 'createCartItem' and inside 'removeCartItem' tha i just created.
So when calling createCartItem we can always show the cart items, because this function adds new items, so the cart is not empty.
Inside remove function first we getting the count of current items, then checking if it is less or equal 0 then we hide cart.
So the final version would be.
const emptyCartMessage = document.createElement('p');
emptyCartMessage.innerHTML = 'Your cart is empty.';
// EMPTY CART ITEM DISPLAY MESSAGE
shoppingCart.appendChild(emptyCartMessage);
// SHOPPING AREA BUTTON EVENT LISTENER
for (var i = 0; i < addToCartButton.length; i++) {
addToCartButton[i].addEventListener('click', createCartItem);
}
function createCartItem(event) {
//CREATE CART LI ITEM
const newItem = document.createElement('li');
newItem.className = 'cart-item';
//newItem.innerHTML = event.target.value;
//GET AND SET SHOP/CART ITEM VALUE
const itemValue = document.createElement('p');
itemValue.innerHTML = event.target.value;
//CREATE CART ITEM DESCRIPTION
const p = document.createElement('p');
p.innerHTML = itemDescription;
//CREATE CANCEL CART ITEM BUTTON
const cancelItemImage = document.createElement('img');
cancelItemImage.className = "remove-button";
cancelItemImage.src = "images/cancel-icon.png";
cancelItemImage.alt = "red remove icon";
newItem.appendChild(itemValue);
newItem.appendChild(p);
newItem.appendChild(cancelItemImage);
shoppingCart.appendChild(newItem);
// Always show because after every adding, we know that there is
// at least one item, so we always showing cart
emptyCartMessage.className = 'hide-empty-cart';
}
function removeCartItem(event){
if (event.target.className === 'remove-button'){
const li = e.target.parentNode;
const ol = li.parentNode;
ol.removeChild(li);
// Get cart's current items
const currentCartItems = document.getElementsByClassName('cart-item');
// If cart items less then or equal to 0 then hide
if (currentCartItems.length <= 0) {
emptyCartMessage.classList.remove('hide-empty-cart');
}
}
}
// REMOVE CART ITEMS BUTTON
shoppingCart.addEventListener('click', removeCartItem);
I am making a todo list. Here is my function that builds my todo list item:
function createTodoElement(todo) {
//todo div
const todoDiv = document.createElement('div');
todoDiv.classList.add('todos');
//complete btn
const completeBtn = document.createElement('button');
completeBtn.setAttribute('data-id', todo.id);
completeBtn.classList.add('complete-btn');
completeBtn.innerText = ('o(-_-)o');
completeBtn.onclick = completeTodo;
//todo content
const todoContent = document.createElement('div');
todoContent.innerText = todo.content;
todoContent.classList.add('todo-content');
//delete button
const deleteBtn = document.createElement('button');
deleteBtn.setAttribute('data-id', todo.id);
deleteBtn.classList.add('todo-delete-btn');
deleteBtn.innerText = 'X';
deleteBtn.onclick = deleteTodo;
todoDiv.appendChild(completeBtn);
todoDiv.appendChild(todoContent);
todoDiv.appendChild(deleteBtn);
return todoDiv;
}
and I am trying to update the todo item's content to say 'done' and update the completed status as 'true' but it doesn't seem to be totally working out for me. here is my function:
function completeTodo(e) {
const btn = e.currentTarget;
btn.innerText = '\\(^_^)/';
let targetId = (btn.getAttribute('data-id'));
console.log(targetId);
let hasId = ls.getTodoList();
let item = hasId.find(obj => obj.id == targetId);
item.content = "done";
item.completed = 'true';
localStorage.setItem('content', JSON.stringify(item.content));
}
my object looks somewhat like this: array[{id: 23435352, content: 'make soup', completed: 'false'}, {id:48283749, content: 'study', completed: 'false'}]
everything seems to be working okay but the updating the new data to locale storage. I also want to save the new button inner text. what do I need to change in my function to make this happen?