How do I insert multiple buttons with bound events? - javascript

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!

Related

Mouse click not working in button added in DOM

i am trying to add this code in my code where i want to have this button in my scene !
function performAnnotation(event: MouseEvent) {
console.log(event)
console.log('hello')
}
function loadAnnotationIntoScene() {
const menuPanel = document.getElementById('menu-panel') as HTMLDivElement
const _menuList = document.createElement('ul') as HTMLUListElement
menuPanel.appendChild(_menuList)
annotation.forEach((element, index) => {
const myList = document.createElement('li') as HTMLLIElement
const _list = _menuList.appendChild(myList)
const myButton = document.createElement('button') as HTMLButtonElement
myList.appendChild(myButton)
myButton.classList.add('annotationButton')
myButton.innerHTML = element['text']
myButton.addEventListener('click', performAnnotation, false)
})
}
i am trying to create an annotation menu above the scene in right or left hand side but i am not able to add button with event listener !!!
When i try in fiddle it works fine but in three.js, it doesn't and i believe this is because of the orbit controller that i have in my scene !!
Right now my scene is like this:
function performAnnotation(event) {
console.log("hello")
}
const annotation = [1, 2, 3, 4]
function loadAnnotationIntoScene() {
const menuPanel = document.getElementById('menu-panel')
const _menuList = document.createElement('ul')
menuPanel.appendChild(_menuList)
annotation.forEach((element, index) => {
const myList = document.createElement('li')
const _list = _menuList.appendChild(myList)
const myButton = document.createElement('button')
myButton.innerHTML = "hello"
myList.appendChild(myButton)
myButton.addEventListener('click', performAnnotation, false)
})
}
loadAnnotationIntoScene()
<div id="menu-panel"></div>
can any one suggest me what is the best thing that i can do so that i can have annotation menu in my project !!!

Cannot use addEventListener with for in loop

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>

Event listeners don't get re-attached to my HTML elements when using ES6 without re-feshing the page

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;
}

Calling object methods in javascript from html oninput() event

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>

Javascript: How can I select a functions internal properties via it's parameters

I have a function that creates a menu, it has some buttons assigned to it and accepts an args parameter. Part of the function is a method called setActiveButton. When I create a menu I would like to dictate which of the buttons is active by passing in an option args.
For example:
var createMenu = function (args) {
this.btnOne = new Button(); // construct a button
this.btnTwo = new Button();
this.btnTwo = new Button()
this.setActiveButton(args.desiredButton);
return this;
}
createMenu({ desiredButton: btnTwo });
How do I tell createMenu to use one of it's buttons via args? I can't pass in { desiredButton: this.btnTwo } - because at that point this.btnTwo is not defined.
I was thinking about passing in a string and then using conditional statements like this:
var createMenu = function (args) {
var buttonChoice;
this.btnOne = new Button();
this.btnTwo = new Button();
this.btnThree = new Button();
if (args.desiredButton === "button one") {
this.setActiveButton(this.btnOne);
}
if (args.desiredButton === "button two") {
this.setActiveButton(this.btnTwo);
}
if (args.desiredButton === "button three") {
this.setActiveButton(this.btnThree);
}
return this;
}
createMenu({ desiredButton: "button two" });
However, I feel that there should be a cleaner and more succinct way to do this.
What is your suggestion?
just pass the name of the button as a string, and access with brackets.
var createMenu = function (args) {
this.btnOne = new Button(); // construct a button
this.btnTwo = new Button();
this.btnTwo = new Button()
this.setActiveButton(this[args.desiredButton]); // access this[property]
return this;
}
createMenu({ desiredButton: 'btnTwo' }); // string name of property
It's a little unclear to me why you are returning an object of properties that have no values, but you could do it like this. In my example I set the properties equal to strings like 'button 1', 'button 2', etc:
// pass number of total buttons, index of active button
var createMenu = function (total, active) {
var obj = {};
for (var i = 0; i < total; i++) {
obj['btn'+(i+1)] = 'button '+(i+1);
}
setActiveButton(obj['btn'+active]);
return obj;
}
In your example you reference setActiveButton as a property of the function but it isn't defined, so I have referenced it as a separate function.

Categories