In javascript I have a list of objects of the same class.
Every object has the same variables and methods.
For each object that is added to the list, an input field is added in the html code.
Now, when the value of an input field is changed (<input oninput=...>)
I want to call the inputModified()method of the exact object in the list that was added to the list along with the input field.
How would you achieve this?
Also, if an object is removed from the list, than the html code that was added for that object is removed as well.
Is an incremental id the only way?
Also, please note that I can not use jquery or anything similar. Thanks!
The best way is not to use <input oninput=...> at all. Use modern event handling. Then you either attach a handler that closes over the entry in the list, or you include the position in the list on the element and use a delegated input handler on the container these inputs are in to figure out which entry on the list to update.
Here's an example of that first option, closing over the entry in the list:
function addToDOM(entry) {
const input = document.createElement("input");
input.type = "text";
input.value = entry.value;
// The event handler closes over `input` and `entry`
input.addEventListener("input", function() {
entry.value = input.value;
});
return input;
}
const list = [];
const container = document.getElementById("container");
for (let n = 0; n < 10; ++n) {
const entry = {
value: `Value ${n + 1}`
};
container.appendChild(addToDOM(entry));
list.push(entry);
}
// Adding another later
setTimeout(() => {
const entry = {
value: "Added later"
};
container.appendChild(addToDOM(entry));
list.push(entry);
}, 800);
document.getElementById("btnShowAll").addEventListener("click", function() {
list.forEach(({value}, index) => {
console.log(`[${index}].value = ${value}`);
});
});
<input type="button" id="btnShowAll" value="Show All">
<div id="container"></div>
Here's an example of the second option with a single delegated handler. Note, though, that if you modify the list, the index we're storing as a data-* attribute gets out of date:
function addToDOM(entry, index) {
const input = document.createElement("input");
input.type = "text";
input.value = entry.value;
input.setAttribute("data-index", index);
return input;
}
const list = [];
const container = document.getElementById("container");
container.addEventListener("input", function(e) {
const input = e.target;
const index = +input.getAttribute("data-index");
list[index].value = input.value;
});
for (let n = 0; n < 10; ++n) {
const entry = {
value: `Value ${n + 1}`
};
container.appendChild(addToDOM(entry, list.length));
list.push(entry);
}
// Adding another later
setTimeout(() => {
const entry = {
value: "Added later"
};
container.appendChild(addToDOM(entry, list.length));
list.push(entry);
}, 800);
document.getElementById("btnShowAll").addEventListener("click", function() {
list.forEach(({value}, index) => {
console.log(`[${index}].value = ${value}`);
});
});
<input type="button" id="btnShowAll" value="Show All">
<div id="container"></div>
Related
I'm trying to use the for...in loop to add the event listener to the button because I don't want to repeat myself in case there are many buttons/elements. But it gives me an error of key.addEventListener is not a function. What am I doing wrong here?
const firstBtn = document.querySelector('.first-btn');
const secondBtn = document.querySelector('.second-btn');
const data = { firstBtn:'apple', secondBtn:'orange' };
for(const key in data) {
key.addEventListener('click', function() {
console.log(data[key]);
});
}
<button class="first-btn">First</button>
<button class="second-btn">Second</button>
key is the string key of the object, not the DOM element in the variable with the same name.
Put the DOM elements in the object as well.
const firstBtn = document.querySelector('.first-btn');
const secondBtn = document.querySelector('.second-btn');
const data = [{
el: firstBtn,
msg: 'apple'
},
{
el: secondBtn,
msg: 'orange'
}
];
for (const obj of data) {
obj.el.addEventListener('click', function() {
console.log(obj.msg);
});
}
<button class="first-btn">First</button>
<button class="second-btn">Second</button>
document.querySelectorAll('button'); this statment selects all buttons from DOM.
buttons.forEach foreach loop through all the buttons and add click event it's easier and readable.
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => {
btn.addEventListener('click', function() {
console.log(btn);
});
})
<button class="first-btn">First</button>
<button class="second-btn">Second</button>
I have some h3 elements with the same class. I need to get text from h3 when i click on one of the elements.
document.querySelector(`.block__item__name`).onclick = function() {
let value = this.textContent;
console.log(value);
};
This code works with first h3 only. How can i fix it?
const content = document.getElementsByClassName("titel");
for (let i = 0; i < content.length; i++) {
content[i].addEventListener("click", function () {
let value = this.textContent;
console.log(value);
});
}
You can try it out here: https://jsitor.com/EKnmSAr5J
Per the documentation, querySelector gives you the first result for your query. If you want all results, use querySelectorAll
document.querySelectorAll(`.block__item__name`)forEach(item => {
item.onclick = function() {
let value = this.textContent;
console.log(value);
};
});
I am printing a simple string to the screen. When clicking on one of its letters, is should be removed from wherever it is the string and added at the end. After I click on one letter and the new string is getting printed to the page, the letters don't preserve their event listeners. This is the JS code and here is all the code https://codesandbox.io/s/reverse-array-forked-fvclg?file=/src/index.js:0-1316:
const appBox = document.getElementById("app");
const convertString = (string) => {
let stringToArray = string.split("");
return stringToArray;
};
let stringToArray = convertString("Hello world!");
const printArrayToPage = (string) => {
string.forEach((element) => {
const textBox = document.createElement("div");
if (element !== " ") {
textBox.classList.add("letter");
} else {
textBox.classList.add("emptySpace");
}
const text = document.createTextNode(element);
textBox.appendChild(text);
appBox.appendChild(textBox);
});
};
window.onload = printArrayToPage(stringToArray);
const moveLetter = (event) => {
const targetLetter = event.target;
const letterToRemove = targetLetter.innerHTML;
targetLetter.classList.add("invisible");
if (stringToArray.includes(letterToRemove)) {
const index = stringToArray.indexOf(letterToRemove);
stringToArray.splice(index, 1);
}
stringToArray.push(letterToRemove);
appBox.innerHTML = "";
printArrayToPage(stringToArray);
};
const allLetters = document.querySelectorAll(".letter");
allLetters.forEach((element) => element.addEventListener("click", moveLetter));
const allSpaces = document.querySelectorAll(".emptySpace");
allSpaces.forEach((element) => element.addEventListener("click", moveLetter));
I tried moving the even assignments (this block)
const allLetters = document.querySelectorAll(".letter");
allLetters.forEach((element) => element.addEventListener("click", moveLetter));
const allSpaces = document.querySelectorAll(".emptySpace");
allSpaces.forEach((element) => element.addEventListener("click", moveLetter));
inside printArrayToPage function but because I am using ES6 syntax, I can't use a function before its definition. If I change to functions created used the function keyword, everything works as expected. How can I fix this issue using ES6 so that after I click on a letter and the divs get re-added to the page, event listeners are re-assigned?
If you want to move the letter node to the end, you can use the Node.parentNode property, and append the child node to the end. You don't need to create the nodes every time an item is clicked:
const moveLetter = (event) => {
const targetLetter = event.target;
const parent = targetLetter.parentNode;
parent.appendChild(targetLetter); // move the element to the end
};
See: https://codesandbox.io/s/reverse-array-forked-lf333?file=/src/index.js
Use event delegation. Here's a simple example snippet:
// adding an event listener to the document
document.addEventListener("click", evt => {
console.clear();
const origin = evt.target;
// ^ where did the event originated?
if (origin.closest("#bttn1")) {
console.log("you clicked button#bttn1");
} else if (origin.closest("#bttn2")) {
console.log("you clicked button#bttn2")
}
});
createElement("h3", "header", "Wait 2 seconds...");
// creating buttons after a while. The earlier created
// event listener will detect clicks for the new buttons
setTimeout( () => {
createElement("button", "bttn1", "click me");
createElement("button", "bttn2", "click me too");
}, 2000);
function createElement(nodetype, id, text) {
document.body.appendChild(
Object.assign(document.createElement(nodetype), {id, textContent: text})
);
}
body {
font: normal 12px/15px verdana, arial;
margin: 2rem;
}
button {
margin: 0 0.6rem 0 0;
}
I have a factory that creates buttons,
var btnFactory = (fn, text) => {
var btn = $(`<button>${text}</button>`);
btn.bind("click", fn);
return btn;
};
I want to be able insert multiple buttons, events already bound to handlers, into an element so I end up with,
<div>
<button>Button1</button>
<button>Button2</button>
</div>
I'm trying to figure out to use .html() for it, but so far it's eluded me.
You don't need jQuery (and it's more efficient)
// reusable template element for cloning
const btnTemplate = (() => {
const bt = document.createElement("button")
bt.type = "button"
// other things you want all buttons to have, classname, etc.
return bt
})()
const btnFactory = { fn, text } => {
const btn = btnTemplate.cloneNode(false)
btn.onclick = fn
btn.innerHTML = text
return btn
}
Can be used like
const items = [
{ text: "Button1", fn: e => console.log("Button1 clicked") },
{ text: "Button2", fn: e => console.log("Button2 clicked") }
]
// Higher-order helper to fold a collection and a factory into
// a documentFragment
const intoDocFrag = (factoryFn, xs) =>
xs.reduce((frag, x) => {
frag.appendChild(factoryFn(x))
return frag
}, document.createDocumentFragment())
document.body.appendChild(intoDocFrag(btnFactory, items))
I think what you're asking is how to use this function to generate the button? I put a couple different ways to do that in the snippet below:
var btnFactory = (fn, text) => {
var btn = $(`<button>${text}</button>`);
btn.bind("click", fn);
return btn;
};
// method 1
$('body').html(
btnFactory(
(function () {
console.log('test 1')
}),
'test 1'
)
)
// method 2
$('body').append(
btnFactory(
(function () {
console.log('test 2');
}),
'test 2'
)
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
If you mean inserting a series of buttons with a for loop, then it's quite simple. You need to give the div element an ID, and create an variable like so: var divElement = document.getElementById('divElement1');. Then you create a for loop, and insert the amount of buttons like so:
var docFrag = document.createDocumentFragment()
for (var i = 1; i < (amount of buttons you want); i++)
{
var button = document.createElement("button");
button.addEventListener("click", fn);
button.value = "Button" + i;
docFrag.appendChild(button);
}
divElement.appendChild(docFrag);
Hope this helps!
This code originally took input.value and added it to the page. I added local storage to this project and the code is already written but I’m having a hard time displaying the input to the page from localStorage. The input is stored in local storage as objects in an array. I wrote a for loop to loop through those values and pass them to functions that builds the element and appends it to the li and later appends to the ul. It’s not displaying on the page and I’m not getting any errors in the console. I'm not sure where to turn so heres my code:
function fetchInvite() {
const rsvpInvite = JSON.parse(localStorage.getItem("Invitees"));
const rsvpList = document.getElementById('invitedList');
for(var i = 0; i < rsvpInvite.length; i++) {
const name = rsvpInvite[i].name;
const confirm = rsvpInvite[i].confirmed;
createLI(name, confirm);
function createLI(name, confirm) {
const li = document.createElement('li');
function createElement(elementName, property, value) {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
function appendToLI (elementName, property, value) {
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
appendToLI('span', 'textContent', name);
appendToLI('label', 'textContent', confirm)
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLI('button', 'textContent', 'edit');
appendToLI('button', 'textContent', 'remove');
return li;
}
}
}
The full project is available here: https://github.com/tianniNakiMyers/RSVP_TeamTreeHouse/blob/master/app.js
The problem with your code is that you probably never called fetchInvite.
Apart from that, here is a refactoring of your code:
function elt(parent, tag, text) { // create element of tagname tag and append it to a parent (if provided) then set its textContent to text and return it
var el = document.createElement(tag);
if(parent) {
parent.appendChild(el);
}
el.textContent = text;
return el;
}
function fetchInvite() {
const rsvpInvite = JSON.parse(localStorage.getItem("Invitees"));
const rsvpList = document.getElementById('invitedList');
for(var i = 0; i < rsvpInvite.length; i++) {
const name = rsvpInvite[i].name;
const confirm = rsvpInvite[i].confirmed;
const li = elt(rsvpList, 'li', ''); // create an li element with empty text and append it to rsvpList
elt(li, 'span', name); // create a span whose text is name and append it to li
elt(elt(li, 'label', confirm), 'input', '').type = 'checkbox'; // create an input append it to a label element that contain the text confirm and that is appended to li, then set type of input to 'checkbox'
elt(li, 'button', 'edit'); // ...
elt(li, 'button', 'remove');
}
}
fetchInvite(); // don't forget to call it