Loop doubles the number of notes in note app - javascript

I'm making a small project for keeping notes. Everytime I click "Add a new note" note is added. After clicking second or more times the "Add" button, the loop keeps adding wrong amount of notes. First is 1 then 3,6,10 and so on.
document.querySelector('#newNoteBtn').addEventListener('click', onNewNote);
function onNewNote() {
const title = document.querySelector('#noteTitle').value;
const content = document.querySelector('#noteContent').value;
const note = {
title: title,
content: content,
colour: '#ff1455',
pinned: false,
createDate: new Date()
}
notes.push(note);
console.log(note);
localStorage.setItem(lsNotesKey, JSON.stringify(notes));
const notesFromLocalStorage = JSON.parse(localStorage.getItem(lsNotesKey));
const convertedNotes = notesFromLocalStorage.map( note => {
note.createDate = new Date(note.createDate);
return note;
});
const notesContainer = document.querySelector('main');
for (const note of convertedNotes) {
const htmlNote = document.createElement('section');
const htmlTitle = document.createElement('h1');
const htmlContent = document.createElement('p');
const htmlTime = document.createElement('time');
const htmlButton = document.createElement('button');
htmlNote.classList.add('note');
htmlTitle.innerHTML = note.title;
htmlContent.innerHTML = note.content;
htmlTime.innerHTML = note.createDate.toLocaleString();
htmlButton.innerHTML = 'remove';
htmlButton.addEventListener('click', removeNote);
htmlNote.appendChild(htmlTitle);
htmlNote.appendChild(htmlContent);
htmlNote.appendChild(htmlTime);
htmlNote.appendChild(htmlButton);
notesContainer.appendChild(htmlNote);
}
}

You just never clean up your container, but adding whole set of nodes on each call.
Easiest way to solve that is to cleanup notesContainer:
// ...
const notesContainer = document.querySelector('main');
notesContainer.innerHTML = ''; // <-- this line cleans up your container.
for (const note of convertedNotes) {
// ...
This isn't optimal. From performance prospective, it better to add only newly created note, but this should hightlight the issue.

Looks like you are never clearing the contents of noteContainer:
// before the loop
notesContainer.innerHtml = ""
Good luck!

Related

Why won't all of my new notes inherit the button function?

I have the following JavaScript code that is triggered once DOM is loaded:
const add_note = () => {
// Creates a new note and its props
const new_note = document.createElement("div");
const new_input = document.createElement("input");
const new_form = document.createElement("form");
const new_ol = document.createElement("ol");
const new_button = document.createElement("button");
//Populates the new note with inputs and checkboxes
for (let i = 0; i < 5; i++) {
let new_li = document.createElement("li");
let new_checkbox = document.createElement("input");
new_checkbox.setAttribute("type", "checkbox");
let new_li_input = document.createElement("input");
new_li_input.classList.add("li_input");
new_ol.appendChild(new_li);
new_li.appendChild(new_checkbox);
new_li.appendChild(new_li_input);
}
//New note's settings
new_note.setAttribute("id", "note");
new_note.classList.add("note");
new_note.appendChild(new_input);
new_input.classList.add("note_input");
new_input.setAttribute("placeholder", "Note's title");
new_note.appendChild(new_form);
new_form.appendChild(new_ol);
new_ol.setAttribute("id", "ol_id");
new_note.appendChild(new_button);
new_button.classList.add("more_items");
new_button.setAttribute("id", "more_items");
//Inserts the new note and button
const note_block = document.getElementById("notes");
note_block.appendChild(new_note);
const button = document.getElementById("more_items");
button.addEventListener("click", add_more_items);
button.innerHTML = "+";
};
However, once the notes are created, only the first note button inherits its functions as seen on the following image:
Code loaded
I've tried about everything I can but couldn't figure it out. Anyway thanks in advance.
IDs are unique in html, you can't have multiple elements with the same id
Either remove these lines, make them into classes, or somehow distinguish them:
new_note.setAttribute("id", "note");
...
new_ol.setAttribute("id", "ol_id");
...
new_button.setAttribute("id", "more_items");
And just refer to the button with the variable:
const note_block = document.getElementById("notes");
note_block.appendChild(new_note);
new_button.addEventListener("click", add_more_items);
new_button.innerHTML = "+";
You could actually even move these last two lines up to the rest of your code before appending the note block.

trying to make a mark off for the to do App

I am currently busy trying to make a mark off to the list when it is done for the to-do app.
here is the JS code for it:
let tasks = [];
function todo(text) {
const todo = {
text,
checked: false,
id: Date.now(),
};
tasks.push(todo);
displaytasks(todo);
}
const list = document.querySelector('.list')
list.addEventListener('click', event =>{
if(event.target.classList.contains('js-tick')){
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemkey);
}
});
function toggleDone(key)
{
const index = tasks.findIndex(item => item.id === Number(key));
tasks[index].checked =!tasks[index].checked;
displaytasks(tasks[index]);
}
I have the tasks displaying, but it does not mark off when I click the circle that makes a tick and a line through.
In the console, I do not see it firing at all. As you can see I passed through the toggleDone(itemkey) to the function toggleDone(key)
changed
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemkey);
to
toggleDone(itemKey);
which fixed it.

How/When to remove child elements to clear search result?

Trying to clear my search result after I submit a new API call. Tried implementing gallery.remove(galleryItems); at different points but to no avail.
A bit disappointed I couldn't figure it out but happy I was able to get a few async functions going. Anyway, here's the code:
'use strict';
const form = document.querySelector('#searchForm');
const gallery = document.querySelector('.flexbox-container');
const galleryItems = document.getElementsByClassName('flexbox-item');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
const getRequest = async (search) => {
const config = { params: { q: search } };
const res = await axios.get('http://api.tvmaze.com/search/shows', config);
return res;
};
const tvShowMatches = async (shows) => {
for (let result of shows) {
if (result.show.image) {
// new div w/ flexbox-item class + append to gallery
const tvShowMatch = document.createElement('DIV')
tvShowMatch.classList.add('flexbox-item');
gallery.append(tvShowMatch);
// create, fill & append tvShowName to tvShowMatch
const tvShowName = document.createElement('P');
tvShowName.textContent = result.show.name;
tvShowMatch.append(tvShowName);
// create, fill & append tvShowImg to tvShowMatch
const tvShowImg = document.createElement('IMG');
tvShowImg.src = result.show.image.medium;
tvShowMatch.append(tvShowImg);
}
}
};
Thanks
Instead of gallery.remove(galleryItems); consider resetting gallery.innerHTML to an empty string whenever a submit event occurs
Like this:
form.addEventListener('submit', async (e) => {
e.preventDefault();
gallery.innerHTML = ''; // Reset here
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
I believe this will do it.. you were close.
const galleryItems = document.getElementsByClassName('flexbox-item');
// to remove
galleryItems.forEach(elem => elem.remove() );

Waiting for an iframe to be opened and scraped is too slow to scrape js

I'm trying to scrape an old website built with tr, br and iframe. Everything was going good so far before I started to want to extract data from an iframe, see iFrameScraping setTimeout, but the clicking is too fast for me to be able to get the datas. Would anyone have an idea of how to click, wait for the content to show and be scraped, then continue?
const newResult = await page.evaluate(async(resultLength) => {
const elements = document.getElementsByClassName('class');
for(i = 0; i < resultLength; i++) {
const companyArray = elements[i].innerHTML.split('<br>');
let companyStreet,
companyPostalCode;
// Get company name
const memberNumber = elements[i].getElementsByTagName('a')[0].getAttribute('href').match(/[0-9]{1,5}/)[0];
const companyName = await companyArray[0].replace(/<a[^>]*><span[^>]*><\/span>/, '').replace(/<\/a>/, '');
const companyNumber = await companyArray[0].match(/[0-9]{6,8}/) ? companyArray[0].match(/[0-9]{6,8}/)[0] : '';
// Get town name
const companyTown = await companyArray[1].replace('"', '');
// Get region name
const companyRegion = await companyArray[2].replace(/<span[^>]*>Some text:<\/span>/, '');
// Get phone number
const telNumber = await elements[i].innerHTML.substring(elements[i].innerHTML.lastIndexOf('</span>')).replace('</span>', '').replace('<br>', '');
const iFrameScraping = await setTimeout(async({elements, i}) => {
elements[i].getElementsByTagName('a')[0].click();
const iFrameContent = await document.getElementById('some-id').contentWindow.document.getElementById('lblAdresse').innerHTML.split('<br>');
companyStreet = iFrameContent[0].replace('"', '');
companyPostalCode = iFrameContent[2].replace('"', '');
}, 2000, {elements, i});
console.log(companyStreet, companyPostalCode)
};
}, pageSearchResults.length);
I fixed my issues after a while, so I'll share my solution.
I add to stop getting all the data with a loop from the evaluate because it's going to fast and creating a race condition. Instead I used a combination of page.$$ coupled with a for…of loop. Note that the forEach from es6 are causing race condition as well, since puppeteer does not wait for them to end to continue its execution.
Here is the example from my updated code:
const companies = await page.$$('.repmbr_result_item');
const companiesLinks = await page.$$('.repmbr_result_item a');
for(company of companies) {
const companyEl = await page.evaluate(el => el.innerHTML, company)
const companyElArray = companyEl.split('<br>');

Draftjs trying to remove an atomic block

I am trying to remove an atomic block from the draftjs editor with Modifier.removeRange. From what I can tell I am passing in all the right arguments, but the SelectionState for removal never gets removed.
Here is my code. This also adds in a new atomic block. That part works fine, it's the removal aspect that returns the same contentState that i pass in.
upload_block_selection_state is the SelectionState object for removal. This object is obtained from editorState.getSelection() when it is rendered. It looks like this.
upload_block_selection_state = {
anchorKey: "c1kpk",
anchorOffset: 0,
focusKey: "c1kpk",
focusOffset: 0,
isBackward: false
}
and here is the function that should both remove the upload block and then add a new block. Adding works, removal does nothing.
function addAndRemoveMediaBlock(
entityURL,
entityType,
editorState,
setEditorState,
upload_block_selection_state
){
const contentBeforeAtomicBlock = editorState.getCurrentContent();
const contentAfterSelectionRemoval = Modifier.removeRange(
contentBeforeAtomicBlock,
upload_block_selection_state,
'forward'
);
const contentStateWithEntity = contentAfterSelectionRemoval.createEntity(entityType, 'IMMUTABLE', { src: entityURL });
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const editorStateAfterAtomicBlock = AtomicBlockUtils.insertAtomicBlock(
editorState,
entityKey,
entityKey
);
/*
below here relevant only to removing the empty block.
*/
const selectionStateBeforeAtomicBlock = editorState.getSelection();
const anchorKeyBeforeAtomicBlock = selectionStateBeforeAtomicBlock.getAnchorKey();
const contentAfterAtomicBlock = editorStateAfterAtomicBlock.getCurrentContent();
const blockSelectedBefore = contentAfterAtomicBlock.getBlockForKey(anchorKeyBeforeAtomicBlock);
const finalEditorState = (() => {
if(blockSelectedBefore.getLength() === 0){
const keyBefore = blockSelectedBefore.getKey();
const newBlockMap = contentAfterAtomicBlock.blockMap.delete(keyBefore);
const contentWithoutEmptyBlock = contentAfterAtomicBlock.set('blockMap', newBlockMap);
const editorStateWithoutEmptyBlock = EditorState.push(editorStateAfterAtomicBlock, contentWithoutEmptyBlock, 'remove-block');
return EditorState.forceSelection(editorStateWithoutEmptyBlock, contentWithoutEmptyBlock.getSelectionAfter());
}else{
return editorStateAfterAtomicBlock;
}
})();
setEditorState({
editorState: finalEditorState,
});
};
I figured it out.
const contentAfterSelectionRemoval = contentBeforeAtomicBlock.blockMap.delete(blockToRemoveKey);
This seems like an anti-pattern within an Immutable state so I ended up approaching the problem from another angle and didn't actually use this working solution.
set focusOffset to 1 should work if any other parameters are fine.

Categories

Resources