mapped button info is out of bounds - javascript

function AddDocument(Name, TTid) {
auth.onAuthStateChanged(user => {
if(user) {
const colUser = collection(fsinfo, 'User');
// goes to database colelction "user"
const colUser2 = doc(colUser, user.uid);
// goes to the document in colUser named "one"
const colUser3 = collection(colUser2, 'MoviesLiked');
whenSignedIn.hidden = false;
whenSignedOut.hidden = true;
setDoc(doc(colUser3, Name), {
movieliked: TTid,
})
}
else {
whenSignedIn.hidden = true;
whenSignedOut.hidden = false;
//userDetails.innerHTML = '';
console.log( "while logged out" );
console.log("notloggedin");
}
})
};
// query can either be a title or a tt id
function searchMovie(query) {
const url = `https://imdb8.p.rapidapi.com/auto-complete?q=${query}`;
fetch(url, options)
.then(response => response.json())
.then(data => {
var outname = null;
var outdetail = null;
const movieList = document.querySelector('.movielist');
movieList.addEventListener('click', handleClick);
const list = data.d;
//array of list with data from the movie search
//data.d is the specific datas that is outputted from the api
//list is an array that holds that data
console.log(list)
// ^ will output what list holds
const html = list.map(obj => {
const name = obj.l; // holds the name of movie
outname = name;
const poster = obj.i.imageUrl; // holds the poster, i is the image
const detail = obj.id
outdetail = detail;
return `
<section class="movie">
<img src="${poster}"
width = "500"
height = "800"/>
<h2>${name}</h2>
<section class = "details">${detail}</section>
<button type="button">Movie Details</button>
</section>
`;
}).join('');
// Insert that HTML on to the movie list element
function handleClick(e) {
if (e.target.matches('button')) {
const details = e.target.previousElementSibling;
details.classList.toggle('show');
AddDocument(outname, outdetail);
}
}
movieList.insertAdjacentHTML('beforeend', html);
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
});
I have a function that will take search to an API call and then load the info from the API to a list.
It should then output said each of said list using list.map(obj) and each item will have a name, poster, and a button attached to it.
Outside the map I have a function that will react when the button is pressed which will toggle and then load the details of the movie to a database in the AddDocument function. My issue is that I am not sure how to make it so that when the button is pressed AddDocument will add whichever obj.name is connected to the button.
Currently, AddDocument will only add the last item in the list and not the item that I pressed the button for.
I know that it is because the function is outside where the mapping is done, and thus the items that are held in outname, and outdetail are the last items that have been mapped. But I just can't figure out a way to make the button press add the correct document to the database.
(I really didn't want to ask this, but I had spent hours thinking and searching and couldn't seem to find a solution. Thank you for any form of feedback that there may be.)

Related

Interactive form for storing user information

I am learning to use HTML, CSS, JavaScript and JDBC. I want to integrate all the said together and arrive at a webpage that accepts user information that can be used to make a detailed profile of a family that can act as a repository for the family to access that information from anywhere by just logging into the website. I also want to do CURD operations to apply to all the data submitted. Need cascading dropdown menus that populate by themselves when one option is selected by user
I started off by making cascading dropdown menu that when province is selected the next dropdown selects only the corresponding cities in that province only. I was able to populate the first dropdown but could not connect it to subsequent dropdown
below is the JavaScript that I prepared for any number of cascading dropdowns but its not working and now I am unable to identify whats wrong with the code
function makeDropDown(data, filtersAsArray, targetElement) {
const filteredArray = filterArray(data, filtersAsArray);
//const filteredArray = data.filter((r) => r[0] === level1Filter);
//const uniqueOptions = new Set();
// using for each item of the array from the second place and put the valused in the uniqueoptions set
//filteredArray.forEach((r) => uniqueOptions.add(r[1]));
// deconstruting the set to form the array again
//const uniqueList = [...uniqueOptions];
const uniqueList = getUniqueValues(filteredArray, filtersAsArray.length);
populateDropDown(targetElement, uniqueList);
//selectLevel2.innerHTML = "";
//uniqueList.forEach((item) => {
//const option = document.createElement("option");
//option.textContent = item;
//selectLevel2.appendChild(option);
//});
}
function applyDropDown() {
const selectLevel1Value = document.getElementById("level1").value;
const selectLevel2 = document.getElementById("level2");
makeDropDown(myData, [selectLevel1Value], selectLevel2);
applyDropDown2();
}
function applyDropDown2() {
const selectLevel1Value = document.getElementById("level1").value;
const selectLevel2Value = document.getElementById("level2").value;
const selectLevel3 = document.getElementById("level3");
makeDropDown(myData, [selectLevel1Value, selectLevel2Value], selectLevel3);
}
function afterDocumnetLoads() {
populateFirstLevelDropDown();
applyDropDown();
}
function getUniqueValues(data, index) {
const uniqueOptions = new Set();
data.forEach((r) => uniqueOptions.add(r[index]));
return [...uniqueOptions];
}
function populateFirstLevelDropDown() {
const uniqueList = getUniqueValues(myData, 0);
const ele = document.getElementById("level1");
populateDropDown(ele, uniqueList);
}
function populateDropDown(ele, listAsArray) {
ele.innerHTML = "";
listAsArray.forEach((item) => {
const option = document.createElement("option");
option.textContent = item;
ele.appendChild(option);
});
}
function filterArray(data, filtersAsArray) {
return data.filter((r) => filtersAsArray.every((item, i) => item === r[i]));
}
document.getElementById("level1").addEventListener("change", applyDropDown);
document.getElementById("level2").addEventListener("change", applyDropDown2);
document.addEventListener("DOMContentLoaded", afterDocumnetLoads);

Problem with sessionStorage: I am not displaying the first item correctly

I am having a problem with sessionStorage; in particular, I want the id of the ads to be saved in the session where the user puts the like on that particular favorite article.
However, I note that the array of objects that is returned contains the ids starting with single quotes, as shown below:
['', '1', '7']
but I want '1' to be shown to me directly.
While if I go into the sessionStorage I notice that like is shown as:
,1,7
ie with the leading comma, but I want it to start with the number directly.
How can I fix this?
function likeAnnunci(){
let likeBtn = document.querySelectorAll('.like');
likeBtn.forEach(btn => {
btn.addEventListener('click', function(){
let id = btn.getAttribute('ann-id');
//sessionStorage.setItem('like', [])
let storage = sessionStorage.getItem('like').split(',');
//console.log(storage);
if(storage.includes(id)){
storage = storage.filter(id_a => id_a != id);
} else {
storage.push(id);
}
sessionStorage.setItem('like', storage)
console.log(sessionStorage.getItem('like').split(','));
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
})
})
};
function setLike(id){
if(sessionStorage.getItem('like')){
let storage = sessionStorage.getItem('like').split(',');
if(storage.includes(id.toString())){
return `fas`
} else {
return `far`
}
} else {
sessionStorage.setItem('like', '');
return`far`;
}
}
The main issue you're having is that you're splitting on a , instead of using JSON.parse().
Also, you've got some other code issues and logical errors.
Solution:
function likeAnnunci() {
const likeBtn = document.querySelectorAll('.like');
likeBtn.forEach((btn) => {
btn.addEventListener('click', function () {
let id = btn.getAttribute('ann-id');
//sessionStorage.setItem('like', [])
let storage = JSON.parse(sessionStorage.getItem('like') || '[]');
//console.log(storage);
if (!storage.includes(id)) {
storage.push(id);
}
sessionStorage.setItem('like', JSON.stringify(storage));
console.log(JSON.parse(sessionStorage.getItem('like')));
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
});
});
}
More modular and optimal solution:
const likeBtns = document.querySelectorAll('.like');
// If there is no previous array stored, initialize it as an empty array
const initLikesStore = () => {
if (!sessionStorage.getItem('likes')) sessionStorage.setItem('likes', JSON.stringify([]));
};
// Get the item from sessionStorage and parse it into an array
const grabLikesStore = () => JSON.parse(sessionStorage.getItem('likes'));
// Set a new value for the likesStore, automatically serializing the value into a string
const setLikesStore = (array) => sessionStorage.setItem('likes', JSON.stringify(array));
// Pass in a value.
const addToLikesStore = (value) => {
// Grab the current likes state
const pulled = grabStorage();
// If the value is already there, do nothing
if (pulled.includes(value)) return;
// Otherwise, add the value and set the new array
// of the likesStore
storage.push(value);
setLikesStore(pulled);
};
const likeAnnunci = (e) => {
// Grab the ID from the button clicked
const id = e.target.getAttribute('ann-id');
// Pass the ID to be handled by the logic in the
// function above.
addToLikesStore(id);
console.log(grabLikesStore());
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
};
// When the dom content loads, initialize the likesStore and
// add all the button event listeners
window.addEventListener('DOMContentLoaded', () => {
initLikesStore();
likeBtns.forEach((btn) => btn.addEventListener('click', likeAnnunci));
});

How to make react stop duplicating elements on click

The problem is that every time I click on an element with a state things appear twice. For example if i click on a button and the result of clicking would be to output something in the console, it would output 2 times. However in this case, whenever I click a function is executed twice.
The code:
const getfiles = async () => {
let a = await documentSpecifics;
for(let i = 0; i < a.length; i++) {
var wrt = document.querySelectorAll("#writeto");
var fd = document.querySelector('.filtered-docs');
var newResultEl = document.createElement('div');
var writeToEl = document.createElement('p');
newResultEl.classList.add("result");
writeToEl.id = "writeto";
newResultEl.appendChild(writeToEl);
fd.appendChild(newResultEl);
listOfNodes.push(writeToEl);
listOfContainers.push(newResultEl);
wrt[i].textContent = a[i].data.documentName;
}
}
The code here is supposed to create a new div element with a paragraph tag and getting data from firebase firestore, will write to the p tag the data. Now if there are for example 9 documents in firestore and i click a button then 9 more divs will be replicated. Now in total there are 18 divs and only 9 containing actual data while the rest are just blank. It continues to create 9 more divs every click.
I'm also aware of React.Strictmode doing this for some debugging but I made sure to take it out and still got the same results.
Firebase code:
//put data in firebase
createFileToDb = () => {
var docName = document.getElementById("title-custom").value; //get values
var specifiedWidth = document.getElementById("doc-width").value;
var specifiedHeight = document.getElementById("doc-height").value;
var colorType = document.getElementById("select-color").value;
parseInt(specifiedWidth); //transform strings to integers
parseInt(specifiedHeight);
firebase.firestore().collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.add({
documentName: docName,
width: Number(specifiedWidth), //firebase-firestore method for converting the type of value in the firestore databse
height: Number(specifiedHeight),
docColorType: colorType,
creation: firebase.firestore.FieldValue.serverTimestamp() // it is possible that this is necessary in order to use "orderBy" when getting data
}).then(() => {
console.log("file in database");
}).catch(() => {
console.log("failed");
})
}
//get data
GetData = () => {
return firebase.firestore()
.collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.orderBy("creation", "asc")
.get()
.then((doc) => {
let custom = doc.docs.map((document) => {
var data = document.data();
var id = document.id;
return { id, data }
})
return custom;
}).catch((err) => {console.error(err)});
}
waitForData = async () => {
let result = await this.GetData();
return result;
}
//in render
let documentSpecifics = this.waitForData().then((response) => response)
.then((u) => {
if(u.length > 0) {
for(let i = 0; i < u.length; i++) {
try {
//
} catch(error) {
console.log(error);
}
}
}
return u;
});
Edit: firebase auth is functioning fine so i dont think it has anything to do with the problem
Edit: This is all in a class component
Edit: Clicking a button calls the function createFileToDb
I think that i found the answer to my problem.
Basically, since this is a class component I took things out of the render and put some console.log statements to see what was happening. what i noticed is that it logs twice in render but not outside of it. So i took the functions out.
Here is the code that seems to fix my issue:
contain = () => {
const documentSpecifics = this.waitForData().then((response) => {
var wrt = document.getElementsByClassName('writeto');
for(let i = 0; i < response.length; i++) {
this.setNewFile();
wrt[i].textContent = response[i].data.documentName;
}
return response;
})
this.setState({
docs: documentSpecifics,
docDisplayType: !this.state.docDisplayType
})
}
As for creating elements i put them in a function so i coud reuse it:
setNewFile = () => {
const wrt = document.querySelector(".writeto");
const fd = document.querySelector("#filtered-docs");
var newResultEl = document.createElement('div');
newResultEl.classList.add("result");
var wrtEl = document.createElement('p');
wrtEl.classList.add("writeto");
fd.appendChild(newResultEl);
newResultEl.appendChild(wrtEl);
}
The firebase and firestore code remains the same.
the functions are called through elements in the return using onClick.

Trouble creating unique ID as it is being overwritten each refresh

This function is called as a form submit, and further calls a new function for rendering the list of divs. After this is done the website is refreshed because of drag and drop functionality. The problem is that I cant seem to find a way to create an unique ID that persists through page refresh and isnt overwritten on page load because of ex: "let taskId = 0".
Any ideas? :)
function createNewTask(event){
if(document.querySelector("[name='description']").value === "") {
alert("Cannot add empty task.");
} else {
event.preventDefault();
let taskId = 0;
const description = document.querySelector("[name='description']").value;
const givenTo = document.querySelector("[name ='givenTo']").value;
const createdByName = document.querySelector("[name = 'workerName']").value;
const task = {taskId, description, givenTo, createdByName, section: 'task-section'};
const taskList = JSON.parse(window.localStorage.getItem("taskList")) || [];
taskId++;
taskList.push(task);
window.localStorage.setItem("taskList", JSON.stringify(taskList));
// renderTaskList();
renderStoredList();
//Reload page after createNewTask to activate draggable
location.reload();
}
}
Use length to get the next taskId.
function createNewTask(event){
if(document.querySelector("[name='description']").value === ""){
alert("Cannot add empty task.");
} else {
event.preventDefault();
const tasklist = JSON.parse(window.localStorage.getItem("taskList")) || []
let taskId = tasklist.length;
const description = document.querySelector("[name='description']").value;
const givenTo = document.querySelector("[name ='givenTo']").value;
const createdByName = document.querySelector("[name = 'workerName']").value;
const task = {taskId, description, givenTo, createdByName, section: 'task-section'};
taskList.push(task);
window.localStorage.setItem("taskList", JSON.stringify(taskList));
// renderTaskList();
renderStoredList();
//Reload page after createNewTask to activate draggable
location.reload();
}
}
As I cannot add a comment yet, I'll post it here as an answer.
What I would do on my end to keep track of the taskId is to also store the latest taskId that was last used in my localStorage, that way, it would persist.
window.localStorage.setItem('lastTaskId', taskId);
And then simply take that each time the page loads.
Hope this helps!
What if you assign taskId based on previous length of the taskList:
const taskList = JSON.parse(window.localStorage.getItem("taskList")) || [];
let taskId = taskList.length

Library Project - Updating the object when book is removed

Problem:
When the user creates a book, the information that is in the input fields will be displayed. There is a remove button that the user can click and it deletes the book. However, when I use filter() I'm just returning the book parameter, so what can I change about my deleteBook() to be able to delete a book? I don't want the UI to work but I just want the library array to update.
Repl: https://repl.it/#antgotfan/library
What I've tried:
I've tried manipulating the document and whenever the user clicked on the remove then it would be deleted but not update the object to show that it was actually deleted
// Variables
const addBook = document.querySelector("#add");
let library = [];
// Event Listeners
addBook.addEventListener("click", render);
document.addEventListener("click", deleteBook);
// Constructor
function Book(title, author, pages, isRead) {
this.title = title;
this.author = author;
this.pages = pages;
this.isRead = isRead;
}
// Prototypes
Book.prototype.toggleIsRead = function() {
if (this.isRead == "Read") {
this.isRead = "Not read";
} else {
this.isRead = "Read";
}
}
function deleteBook(event) {
if (event.target.id == "remove") {
library.filter(book => {
return book;
});
}
}
// Functions
function addBookToLibrary() {
let authorOfBook = document.querySelector("#author").value;
let bookTitle = document.querySelector("#book-title").value;
let numberOfPages = document.querySelector("#pages").value;
let status = document.querySelector("#isRead").value;
let newBook = new Book(bookTitle, authorOfBook, numberOfPages, status);
library.push(newBook);
return newBook;
}
function updateStatus() {
}
function emptyInputs() {
const inputs = Array.from(document.querySelectorAll("input"));
inputs.forEach(input => input.value = "");
}
function render() {
addBookToLibrary();
emptyInputs();
let newBook = library[library.length - 1];
let table = document.querySelector("table");
let createTr = document.createElement("tr");
table.appendChild(createTr);
createTr.innerHTML = `<td>${newBook.title}</td>
<td>${newBook.author}</td>
<td>${newBook.pages}</td>
<td><button class="table-buttons" id="not-read">${newBook.isRead}</button></td>
<td><button class="table-buttons" id="remove">Delete</button></td>`;
}
Error messages:
No errors but just not having an updated object to show what was kept or deleted.
Since the books are in an array you can use a function that takes the book properties like author, title etc
and then uses that info to find the book in the array and delete it.
let books = [{ title: "book1" }, { title: "book2" }];
console.log(books);
deleteBook("book2");
console.log(books);
function deleteBook(title) {
let i = books.findIndex(b => b.title == title);
books.splice(i, 1);
}
//Outputs
[ { title: 'book1' }, { title: 'book2' } ] //before delete called
[ { title: 'book1' } ] //after delete called
This soloution worked for me:
function deleteBook(event) {
if (event.target.id == "remove") {
const table = document.querySelector('table');
const tr = event.target.parentNode.parentNode;
table.removeChild(tr);
}
}
Basically, you grab the table and then remove the child node that had the event fired on it.
You can grab the table using the querySelector() function, and you may want to consider giving that table a unique id or something down the line.
const table = document.querySelector('table');
Then, you take the target of the event, which is the <button> element, and get it's grandparent by calling parentNode twice. The first parent is the <td> element, the next one is the <tr> element, which is what we want to remove from the table.
const tr = event.target.parentNode.parentNode;
Finally, you can call removeChild() on the <table> element and have it remove the row that the button was pushed from.
table.removeChild(tr);

Categories

Resources