3

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/

3
  • The participant variable -> variable variable looks like it should take the right value. Do you have a JSFiddle or similar, or can you make a Stack Snippet that demonstrates the problem? (it'll be a lot easier to debug with something runnable) Commented Sep 9, 2019 at 0:06
  • Using some dummied-up code to fill in context not in the question, I can't reproduce the problem jsfiddle.net/z3pnxt6v Commented Sep 9, 2019 at 0:15
  • jsfiddle.net/Muelli21/21cv7eja Commented Sep 9, 2019 at 1:17

1 Answer 1

1

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>

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the fast reply. I was fully obsessed by the weird scopes in JavaScript. Thus I totally missed the ids as possible sources of error. Besides, have you found any other flaws or some sort of bad style? I am pretty new to JavaScript and still learning, which means that I am thankful for any tips, tricks or suggestions to improve my code.
It's hard to go into specifics without a bit more context, but: With the current code, the input elements look useless, because they're hidden - I'd prefer to just use the labels (but change them to <span>s, since they'd no longer be connected to an input). Rather than createTextNode, it's easier to just assign to the textContent of an element, eg: element.textContent = text. There's no need for the immediately invoked function for the variable either

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.