Pass Element ID from one function to another in the same closure

How would you pass z2.id into another function inside the same closure? The + i adds one each time. So when the first function runs the z2.id could be myId1 or myId2. I have another function that needs to be able to update the z2 element when called. And having a time figuring out how to get the value of i since it’s dynamic. There could be up to myId10. So how do I update the correct element and distinguish between the correct id in another function?

function myFunctionName() {
z2.id = "myId" + i;
// This needs to pass myId1 or myId2 or whatever the i was when the first function ran.
z2.onclick = myFunction(z2);
}

function myFunction(z2) {
if (document.getElementById('checkbox').checked) {
// How to get myId1 or myId2 in here?
document.getElementById("z2").style.color = "blue";
} else {
document.getElementById("z2").style.color = "red";
}

I have been developing this online trivia game over the years and the following is very similar in what you want to do -

        /*
         * Create Buttons then insert answers into buttons that were
         * create.
         */
        gameData.answers.forEach((value, index) => {


            let gameButton = buttonContainer.appendChild(d.createElement('button'));
            gameButton.id = 'answer' + (index + 1);
            gameButton.className = 'answerButton';
            gameButton.setAttribute('data-correct', (index + 1).toString());
            gameButton.addEventListener('click', clickHandler, false);
            /*
             * Don't Show Answers that have a Blank Field
             */
            if (value !== "") {
                gameButton.appendChild(d.createTextNode("📷 " + value));
            } else {
                gameButton.appendChild(d.createTextNode(" "));
                gameButton.style.pointerEvents = "none"; // Disable Click on Empty Field
            }
        });
    };

The full code can be found here:
game.js

1 Like

You would be better off adding one event listener to the main container and then you can use event bubbling to detect which element was clicked even if it is dynamically added later.

I’m not sure of your use case but I adapted this demo from your other thread as I assumed you were building on it but the demo in itself doesn’t make much sense other than to show how to add the event listener.

The checkbox can be toggled and then you can click the label and it will change color. (This is probably something you could do in CSS using the :checked pseudo class but depends on your actual use case).

As I said the demo doesn’t really make sense and is probably not the flow you were looking for but the event listener portion should do what you want.

@PaulOB you are referencing a static class name in function(e)
z2.className = “z2Class”;

What if z2Class is incrementing by 1 every time function addDiv is run?

Trying to learn how to pass the current value of i to another function so I can reference the i in getelementbyid or get element by class etc. Most of my elements all have a number behind their static id or class name.

Sorry not trying to add another data point. I appreciate your help!

I think you are missing the point a bit. :slight_smile: I only added a classname so that all the added elements of that type can be found easily. The IDs are being incremented on each one.

You don’t need to know what the id of the element is to find it because you just clicked that element and the event listener already knows what element you clicked.

e.g. You had this.

document.getElementById("z2").style.color = "blue";

Whereas in my code we know which element was clicked because the click handler tells us that.

event.target.style.color = "blue";

event.target is the element that was clicked. Why do we care about its iD?

I added the classname so the event listener was only interested in those z2 elements and that was an easy way to separate them from anything else in that container.

If you run my code above you can add as many elements as you like and you will see they know which one was clicked and change color according to what state the checkbox was in.

You don’t need to know the ID although you could find it from the event.target (i.e. e.target.id).

Or did you have something else in mind?

@PaulOB let me try to implement this. I really appreciate your help.

@PaulOB okay, I can understand what you are doing.

When you click the label it changes the label to red. But what about when you click the label it changes the input on it’s left to red or blue? The input on the left has a dynamic id (ie.) input1, input2, input3.

So the if you click the label it can change the current label because it knows the current element that was clicked but it won’t work to change it’s corresponding inputs say border color. Do you understand?

You could do that by adding a class to the parent div when the label is clicked and that allows you to target anything in that section.

Or you could use js to find the previous element assuming that was the input. Or you could use the ‘for’ attribute of the label to identify the iD of input that you want to target.

There are many ways to do it but it all depends on your set up and what happens next.

I’m just offline for an hour but if the above doesn’t help then I’ll put up another demo :slight_smile:

@PaulOB

I can see how adding a class would work. Then you could target the inputs inside the class for the container.

The issue I still see is the checkbox has an id that increments by 1 and each time you click add div a new checkbox is created.

z.id = "checkbox" + i;

So here

 if (document.getElementById("checkbox").checked) { 

Won’t get the correct id.

Is there a way to pass the current value of i when the inputs are created to another function so it can target the correct id?

I might have one DIV that has two input or two labels for example and without the ID I would be applying changes to every input inside the DIV instead of being able to isolate the correct one. Anyways that’s why I figured having access to i would give more granular control.

For example, is my syntax incorrect? If it isn’t, then how do I make sure the i is the correct number of the element that was clicked and not the global value of i?

 if (document.getElementById("checkbox" + i).checked) { 

Ok, It may help if I can get the structure right for your use case and work from there :slight_smile:

Does that mean you create a div and inside that div you create a checkbox, an input and a label?

What would be the purpose of the checkbox? is it important to the function?

I’m thinking that perhaps you have html like this when you have added a couple of divs.

<div id="container">
  <div id="myDiv0">
    <input type="checkbox"  id="checkbox0">
    <input type="text" id="textDescription0">
    <label for="textDescription0" id="myLabelId0" class="z2Class">Label 0</label>
  </div>
  <div id="myDiv1">
    <input type="checkbox" id="checkbox1">
    <input type="text" id="textDescription1">
    <label for="textDescription1" id="myLabelId1" class="z2Class">Label 1</label>
  </div>
  <div id="myDiv2">
    <input type="checkbox"  id="checkbox2">
    <input type="text" id="textDescription2">
    <label for="textDescription2" id="myLabelId2" class="z2Class">Label 2</label>
  </div>
</div>

Is that html correct for what you are doing or is there something more complicated going on inside there?
Screen Shot 2022-01-08 at 20.49.14

If we can get the html correct then coding the solution is going to be easier.

@PaulOB

<div>
    <button id="1">Click me</button>
</div>
<div>
    <input type="checkbox" id="1">
</div>

<div>
    <button id="2">Click me</button>
</div>
<div>
    <input type="checkbox" id="2">
</div>

Separate Divs are created at the same time but from two different functions. If you click button1 how do you update the input1 and not input2? If you click button2 how do you update input2 and not input1?

IDs are all dynamic.

There’s a third function in the closure that runs when you click a click me button and needs to update the corresponding checkbox.

That html is not valid with duplicate ids so you need something like.


<div>
    <button id="btn1">Click me</button>
</div>
<div>
    <input type="checkbox" id="cbox1">
</div>

<div>
    <button id="btn2">Click me</button>
</div>
<div>
    <input type="checkbox" id="cbox2">
</div>

Assuming you are adding event listeners to the buttons then you can retrieve the ID from the button that was clicked e.g. btn1. Then assuming you have matched that button to the input called cbox1 all you have to do is retrieve the last digit from the id of the event target and use that to target the input. As long as all the elements you want to target in that section are consistent then you can follow that approach.

I updated my example to show the method but it doesn’t match your new html but the method is the same.

You don’t really need to pass anything between the function because you can get what you want for the function when it is clicked. It’s much cleaner than your idea of adding multiple event listeners and passing multiple ids each time. Especially if you are adding other elements dynamically also as you could also test for clicks on those in the single event listener on the parent (if that was required).

If that doesn’t help I’ll be back tomorrow. Also what would be useful is if you could create the full html for the finished section as you have done above but with everything in place. I don’t see what the checkbox is doing and how that ties into the scheme of things at the moment.

However the crux of your question is answered in that you can retrieve the id of the element that was clicked and the last digit of that id will be the last digit of the matching element. All you need to provide is the name you gave it at the start and add the digit.

e.g. in the event listener from my example.

 var index = e.target.id.slice(-1); // find last digit of id
 var theInput = d.querySelector("#" + textDescription + index);

Where textDescription was defined as const textDescription = "textDescription"; at the start.

I should also point out that there probably is a better way to do this if one of the JS gurus wants to jump in :slight_smile:

1 Like

I am closely reviewing your code to see how you got there.

@PaulOB if the Div tag which is Container is static this code would work. My wrappers ID is static but anytime you would click anywhere in the Container function (e) would run. That sounds like a lot of calls to a function that may or may not be needed. There’s an event listener set for the entire Container. Is there really no way to call function (e) when and only when someone was to click on click me?

   container.addEventListener("click", function (e) {
    var index = e.target.id.slice(-1); // find last digit
    var theInput = d.querySelector("#" + textDescription + index);
    // If the event target doesn't match bail
    if (!event.target.classList.contains("z2Class")) return;

I mean I understand ultimately nothing would happen as a result of clicking the container only unless you click click me but it does seem like a ton of calls to the function.

If you clicked the input or anywhere inside the container it would call function(e) correct? Or am I missing something?

I really appreciate your help!

Hopefully I’m not going to cause offense here, but I do find this confusing. What is ‘w’, what is ‘z’, what is ‘z2’? Personally I would go with more descriptive names.

function addDiv() {
    var w = d.createElement("DIV");
    var z = d.createElement("INPUT");
    var z2 = d.createElement("LABEL");
    var labelContent = d.createTextNode("Click Me " + i);
    z.setAttribute("type", "text");
    w.id = myDiv + i;
    z.id = textDescription + i;
    z2.setAttribute("for", z.id);
    z2.id = "myLabelId" + i;
    // lets add a class so we make them easier to find
    z2.className = "z2Class";
    z2.appendChild(labelContent);
    i++;
    w.append(z);
    w.append(z2);
    d.getElementById("container").appendChild(w);
  }

I think a better and less cumbersome alternative here is to go with a template string

My html isn’t quite the same, a mix of the above really, but should illustrate my point. You could go with this instead

const rowTemplate = function(count) {
  return `
    <div id='row-${count}' class='row'>
      <div>
          <label for='textDescription-${count}'>
          <input type='text' id='textDescription-${count}'>
      </div>
      <div>
          <button id="btn-${count}" data-for='textDescription-${count}'>Click me</button>
      </div>
    </div>
`
}

Note I have mimicked the label’s for by adding a dataset for property to the button e.g. data-for='textDescription-${count}'.

So no need to slice numbers we can access the textbox in the handler with
parent.querySelector(`#${target.dataset.for}`)

Another example of using a function to return a template. Here I am passing in an object with an id and an array. Dynamically I am then able to build a list using Array.map

const namesTemplate = ({id, names = []}) => (
`
  <ul id='${id}'>
    ${names.map(name => `<li>Name is ${name}</li>`).join('\n\t')}
  </ul>
`
)

console.log(
  namesTemplate({
    id: 'names-01', 
    names: ['Rod', 'Jane', 'Freddy']
  })          
)

//Output
/*
  <ul id='names-01'>
    <li>Name is Rod</li>
    <li>Name is Jane</li>
    <li>Name is Freddy</li>
  </ul>
*/

There are apparently security risks with this approach if the arguments to the template come form an outside source such as user input. That said there are methods to sanitize the html (DOMPurify seems to come highly recommended)

Anyway here is a codepen. I’m sure it doesn’t fit your brief exactly, but hopefully it is of use.

2 Likes

For what it is worth here is the code as well.

It could be refactored and using modules we could import the template.

const rowTemplate = function(count) {
  return `
    <div id='row-${count}' class='row'>
      <div>
          <label for='textDescription-${count}'>
          <input type='text' id='textDescription-${count}'>
      </div>
      <div>
          <button id="btn-${count}" data-for='textDescription-${count}'>Click me</button>
      </div>
    </div>
`
}

const getAddRowHandler = function(container) {
  
  let count = 0
  
  // return handler
  // both count and container will be scoped inside the handler's closure
  return function(event) {
    container.insertAdjacentHTML('beforeend', rowTemplate(count++))
  }
}

const rowClickHandler = function(event) {
  
  // event.target = what we clicked on
  const target = event.target

  // if it isn't a button return early
  if (!target.matches('button')) return
  
  // traverse from the button element up to the nearest .row class e.g. it's row container
  const row = target.closest('.row')

  // data-for or in JS the button's dataset.for holds the id to the textbox
  const textBox = row.querySelector(`#${target.dataset.for}`)

  // As a test console.log contents of text box.
  console.log(textBox.value)
}

const addRowButton = document.querySelector('#addDiv')
const container = document.querySelector('#container')

// pass the container element to the getAddRowHandler and get back the handler function
addRowButton.addEventListener('click', getAddRowHandler(container))

// Like Paul I am using event delegation and letting the events bubble up
container.addEventListener('click', rowClickHandler)

1 Like

@rpg_digital @PaulOB Thank you but I think your response is a bit complex for me at this point in time. I am still learning Javascript. Thank you for taking the time. I came across what seems to be rather simple way of accomplishing this task.

<button id="1" onClick="reply_click(this.id)">B1</button>

// Then the function can get the ID like so
<script>
  function reply_click(this.id)
  {
      alert(clicked_id);
  }
</script>

So my question is how do you write this function out when you are generating the HTML dynamically?

function myFunction() {
var w = d.createElement("checkbox");
w.id = "myCheckbox" + i;
w.onclick = myOtherFunction(this.id);
}

// I feel like something is not correct here.

function myOtherFunction(clicked_id) {
var index = e.target.id.slice(-1);
if (document.getElementById('clicked_id').checked) {
document.getElementById("otherElement" + index).style.color = "blue";
} else {
document.getElementById("otherElement" + index).style.color = "red";
}
}


@rpg_digital I wasn’t trying to steam roll pass your response.

It’s just the HTML is being created with Javascript whereas with your you are writing it out. I can see how your solution would work though.

@cssissimple with the template string and insertAdjacentHTML the string is being converted/parsed in the background and is essentially doing what you are doing in longform by creating and appending elements.

I think it is a great learning exercise what you are doing, but I think the template approach is a lot clearer and easier to read.

I would still recommend using descriptive names rather than single characters for your variables. When you come back to your code in the future it will be a lot easier to understand. But hey, that’s your choice :slight_smile:

ps. Sorry if my solution was confusing. Will bear that in mind.

2 Likes

I have a feeling you are probably one of the Worlds best programmers.