Muelli
Muelli

Reputation: 33

How to hand over variables when creating buttons dynamically?

I am currently writing a WhatsApp-Chat-Analyzer. Therefore, I have to loop through the chat's participants and create buttons each representing one participant. When creating those buttons parameters or variables have to be handed over to a function which then executes code that further processes the parameters/variables.

However, regardless of what I am trying, the function that is called when clicking the button does not use the right variable; it always uses the first one of the array which is used to loop-over in the for-loop.

At the same time, printing out the variables while creating the buttons does work properly - all the unique variables are printed out and not only the first one.

As far as I know, all this indicates that there is something wrong with the scopes.

Coming from Java, working with javascript variables is very confusing since some of them appear to be global even though they are declared within a function or a loop. That's why I have tried to enclose the function that is handed over to the dynamically created button. Sadly, setting up a function that returns another function and handing over the first function does not work although it actually should work.

Furthermore, I am aware of the fact that "let" should be used in this case instead of "var" to create a local variable.

The following code represents what I am doing and which solution I have tried so far.

let participantsArray = ["Fred", "Kim", "Donald", "Xi"];



function displayParticipants() {

    let parentElementChatSection = document.getElementById("participantButtons");
    let participants = participantsArray;

    for (let participant of participants) {
        participantsElement(participant, parentElementChatSection);
    }
}

function participantsElement(participant, parentElement) {

    let participantDiv1 = createHTMLElement(parentElement, "div", "participantDiv");
    let button = createButtonElement(participantDiv1, participant, "participantButton", (function(variable) {
        return function() {
            handleParticipant(variable);
        };
    })(participant));
}

function createButtonElement(parentElement, text, className, 
functionToExecute) {
    let element = document.createElement("input");
    element.type = "button";
    element.value = text;
    element.className = className + "Hidden";
    element.id = className + "Hidden";
    element.onclick = functionToExecute;
    parentElement.appendChild(element);

    let label = document.createElement("label");
    label.htmlFor = element.id;
    label.className = className;
    parentElement.appendChild(label);
    element.style.display = "none";
    let textElement = createTextElement(label, text, className + "Text");

    return [element, label, textElement];
}

function handleParticipant(participant) {

		alert(participant);
}

function createTextElement(parentElement, text, className) {
    let element = document.createElement("p");
    let elementText = document.createTextNode(text);
    element.className = className;
    element.appendChild(elementText);
    parentElement.appendChild(element);
    return element;
}

function createHTMLElement(parentElement, type, className) {
    let element = document.createElement(type);
    element.className = className;
    parentElement.appendChild(element);
    return element;
}

displayParticipants();
.participantButtonText {
  box-shadow: 0px 5px 10px 1px rgba(18, 140, 126, 0.25);
  padding: 0.5em;
  margin: 0.5em;
  background-color: #128C7E;
  color: white;
}

.participantButtonText:hover {
  background-color: #075E54;
}

.participantDiv {
  position: relative;
  float: left;
  height: auto;
  flex-shrink: 1;
  flex-grow: 1;
  min-width: 30px;
  transition: 0.1s;
  transition-property: background-color, color, transform;
  text-align: center;
}

#participantButtons {
  width: 100%;
  clear: both;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: stretch;
  flex-wrap: wrap;
}

.displayedParticipantButton {
  background-color: rgb(172, 117, 0);
}

.displayedParticipantButton:hover {
  background-color: rgb(172, 92, 0);
}
<div id="participantButtons">

</div>

If the code worked properly every button should store the variable of the participant it represents. When pressed, the button should then execute the function "handleParticipant" using the variable that was handed over beforehand and print out the participant.

And last but not least; a live demonstration of the problem: https://jsfiddle.net/Muelli21/21cv7eja/

Upvotes: 3

Views: 378

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370799

The problem is that you have duplicate IDs in the HTML. Here's a more minimal example (click on the labels, and watch which button gets clicked):

document.querySelectorAll('input').forEach((input) => {
  input.onclick = () => console.log(input.value);
});
<input type="button" value="Fred" id="participantButtonHidden"><label for="participantButtonHidden">label</label>
<input type="button" value="Kim" id="participantButtonHidden"><label for="participantButtonHidden">label</label>

The for attribute points to the ID of the button to click. But there should only be one element of a particular ID in any document. What happens here is the browser finds the first element matching that ID, clicks it, and stops there. But every input has the same ID, so the first input will be clicked every time.

Use separate IDs instead:

element.id = text + "Hidden";

let participantsArray = ["Fred", "Kim", "Donald", "Xi"];



function displayParticipants() {

    let parentElementChatSection = document.getElementById("participantButtons");
    let participants = participantsArray;

    for (let participant of participants) {
        participantsElement(participant, parentElementChatSection);
    }
}

function participantsElement(participant, parentElement) {

    let participantDiv1 = createHTMLElement(parentElement, "div", "participantDiv");
    let button = createButtonElement(participantDiv1, participant, "participantButton", (function(variable) {
        return function() {
            handleParticipant(variable);
        };
    })(participant));
}

function createButtonElement(parentElement, text, className, 
functionToExecute) {
    let element = document.createElement("input");
    element.type = "button";
    element.value = text;
    element.className = className + "Hidden";
    element.id = text + "Hidden";
    element.onclick = functionToExecute;
    parentElement.appendChild(element);

    let label = document.createElement("label");
    label.htmlFor = element.id;
    label.className = className;
    parentElement.appendChild(label);
    element.style.display = "none";
    let textElement = createTextElement(label, text, className + "Text");

    return [element, label, textElement];
}

function handleParticipant(participant) {

		alert(participant);
}

function createTextElement(parentElement, text, className) {
    let element = document.createElement("p");
    let elementText = document.createTextNode(text);
    element.className = className;
    element.appendChild(elementText);
    parentElement.appendChild(element);
    return element;
}

function createHTMLElement(parentElement, type, className) {
    let element = document.createElement(type);
    element.className = className;
    parentElement.appendChild(element);
    return element;
}

displayParticipants();
.participantButtonText {
  box-shadow: 0px 5px 10px 1px rgba(18, 140, 126, 0.25);
  padding: 0.5em;
  margin: 0.5em;
  background-color: #128C7E;
  color: white;
}

.participantButtonText:hover {
  background-color: #075E54;
}

.participantDiv {
  position: relative;
  float: left;
  height: auto;
  flex-shrink: 1;
  flex-grow: 1;
  min-width: 30px;
  transition: 0.1s;
  transition-property: background-color, color, transform;
  text-align: center;
}

#participantButtons {
  width: 100%;
  clear: both;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: stretch;
  flex-wrap: wrap;
}

.displayedParticipantButton {
  background-color: rgb(172, 117, 0);
}

.displayedParticipantButton:hover {
  background-color: rgb(172, 92, 0);
}
<div id="participantButtons">

</div>

Or, even more elegantly, avoid IDs entirely, and attach a click event listener to the label:

let participantsArray = ["Fred", "Kim", "Donald", "Xi"];



function displayParticipants() {

    let parentElementChatSection = document.getElementById("participantButtons");
    let participants = participantsArray;

    for (let participant of participants) {
        participantsElement(participant, parentElementChatSection);
    }
}

function participantsElement(participant, parentElement) {

    let participantDiv1 = createHTMLElement(parentElement, "div", "participantDiv");
    let button = createButtonElement(participantDiv1, participant, "participantButton", (function(variable) {
        return function() {
            handleParticipant(variable);
        };
    })(participant));
}

function createButtonElement(parentElement, text, className, 
functionToExecute) {
    let element = document.createElement("input");
    element.type = "button";
    element.value = text;
    element.className = className + "Hidden";
    element.id = text + "Hidden";
    parentElement.appendChild(element);

    let label = document.createElement("label");
    label.htmlFor = element.id;
    label.className = className;
    label.addEventListener('click', functionToExecute);
    parentElement.appendChild(label);
    element.style.display = "none";
    let textElement = createTextElement(label, text, className + "Text");

    return [element, label, textElement];
}

function handleParticipant(participant) {

		alert(participant);
}

function createTextElement(parentElement, text, className) {
    let element = document.createElement("p");
    let elementText = document.createTextNode(text);
    element.className = className;
    element.appendChild(elementText);
    parentElement.appendChild(element);
    return element;
}

function createHTMLElement(parentElement, type, className) {
    let element = document.createElement(type);
    element.className = className;
    parentElement.appendChild(element);
    return element;
}

displayParticipants();
.participantButtonText {
  box-shadow: 0px 5px 10px 1px rgba(18, 140, 126, 0.25);
  padding: 0.5em;
  margin: 0.5em;
  background-color: #128C7E;
  color: white;
}

.participantButtonText:hover {
  background-color: #075E54;
}

.participantDiv {
  position: relative;
  float: left;
  height: auto;
  flex-shrink: 1;
  flex-grow: 1;
  min-width: 30px;
  transition: 0.1s;
  transition-property: background-color, color, transform;
  text-align: center;
}

#participantButtons {
  width: 100%;
  clear: both;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: stretch;
  flex-wrap: wrap;
}

.displayedParticipantButton {
  background-color: rgb(172, 117, 0);
}

.displayedParticipantButton:hover {
  background-color: rgb(172, 92, 0);
}
<div id="participantButtons">

</div>

Upvotes: 1

Related Questions