why only inside of the for loop can I successfully find <li> or <span> tag under the <ul> with the getElementsByClassName method in JavaScript?

Viewed 69

I am learning blockchain with truffle by trying to build a simple TODO LIST project. At first, I could not successfully disable the display of task items in my todo list when clicking on X symbol.... now I accidently find out the solution but just can't figure out why and how it works....

my solution is as below codes( especially in the bottom, start from the comment //when click to X, list disappear)

my problems are :

  1. why can't I find the tags of li, span, close outside of this for loop (the return of console.log was null) with getElementsByClassName method while finding ul tag is possible ??

  2. Even I can only find those tags within for loop, why this for loop always running and waiting for me to click X?? Isn't the for loop supposed to be terminated after reiterating through the number of times we asked them to reiterate??!!

    render: function() {
  
       //Load account data
       if(web3.currentProvider.enable){
       //For metamask
         web3.currentProvider.enable().then(function(acc){
            App.account = acc[0];
            $("#accountAddress").html("Your Account: " + App.account);
        });
       }
       else{
         App.account = web3.eth.accounts[0];
         $("#accountAddress").html("Your Account: " + App.account);
       }
    
       // Load contract data
        App.contracts.myTodoList.deployed().then(function(instance) {
          myTodoListInstance = instance;
          return myTodoListInstance.TasksCount();
        }).then(function(TasksCount){

          var shownList = document.getElementById("todo-list")
                  
          TasksCount = TasksCount.toNumber();

          for (var i = 1; i <= TasksCount; i++) {
            myTodoListInstance.Tasks(i).then(function(task) {
                var id = task[0];
                var content = task[1];
                let completed = task[2];

                // create li node inside of for loop so that you won't appendChild the same 'li' over and over again 
                var newTaskNode = document.createElement('li');

                newTaskNode.innerHTML = content;
                
                // Add a "X" symbol in the end of each listed task
                var span = document.createElement("SPAN");
                var txt = document.createTextNode("x");
                span.className = "close";
                span.appendChild(txt);
                newTaskNode.appendChild(span);                

                // Render initial tasks Result
                shownList.appendChild(newTaskNode); 

                // when click to X, list disappear
                //(don't know how it worked... why can't i locate 'li','span','close' outside of this for loop ??)
                //(even i can only locate those tags within this loop, why this for loop always running and waiting for me to click X ??!!)
                var close = document.getElementsByClassName("close");
                var i;
                for (i = 0; i < close.length; i++) {
                  close[i].onclick = function() {
                    var div = this.parentElement;
                    div.style.display = "none";
                  }
                } 
            });             
          } 
        });             
    },
1 Answers

Can only access created elements in the for loop.

The li elements can't be accessed immediately after calling the render function because they haven't been created yet. List element creation requires promises for

  1. App.contracts.myTodoList.deployed() and
  2. myTodoListInstance.Tasks(i)

to be resolved first.

Why this for loop always running and waiting for me to click X?

[Edit: the previous version of this answer incorrectly implied that repeatedly setting the onclick property of an element set up multiple click handlers. Setting onevent attributes of an HTML element replaces an event handler added the same way.]

You may need to explain the symptoms of this issue in more detail. The for loop actually finishes iterating before any close and list elements are created and added to the DOM - the loop is only adding a then clause to each of the myTodoListInstance.Tasks(i) promises and doesn't wait for the handler to be called.

The code to add click handlers can be simplified (to only add handlers once) by replacing

var close = document.getElementsByClassName("close");
var i;
for (i = 0; i < close.length; i++) {
  close[i].onclick = function() {
    var div = this.parentElement;
    div.style.display = "none";
  }
}

with

span.addEventListener( "click", function() {
    var div = this.parentElement;
    div.style.display = "none";
});

when click to X, list disappear

I assume this is not a problem because the click handler is coded to set the display property of the ul parent of an li element to "none". Note the parent is probably not a div element as suggested by the variable name.

Other

  • Only set newTaskNode.innerHTML to content; if content is formatted using HTML markup and known to be safe. If content is plain text, set newTaskNode.textContent instead.

As presented,

  • there is no error handling for promise rejection,
  • no array of promises that must be fulfilled before all list items and close buttons are ready has been created. Such an array can be passed to Promise.all to create a single promise for when the list have been completed (and gets rejected if any promise in the array gets rejected)
  • Promise's get resolved asynchronously. There is nothing in the code that guarantees that list items presented on screen are in order of ascending i values. It may be better to create the list items and close buttons in a Promise.all handler which is passed an array of resolved values in the same order as its promise array argument.
Related