Hide and show button in HTML template with event delegation

Hi guys, I have a issue with a todo list in JS… Basically I have HTML template with a div and inside the div I have an input (type=‘text’) and an edit button element. The input value is updated dynamically with JS and has a unique ID.

The edit button is hidden with CSS display: none and it only shows on the page when the user clicks the corresponding input field to type something.

I am trying to get the input value and hide the edit button whenever the user clicks on the button (after entering a new value in the input field) or if the user clicks outside the div on a different input element or anywhere else on the page.

I have an event delegation on the main container div(tasksContainer) that checks for an element(input) as event target. Then it loops through all the child elements of the parent div (input and button) and select the button that is created in the HTML template.

After that I have a onclick event on that button that gets the new value of the input.

The button goes away when I click it and update the input value but if I click on a different input element it still shows it on the page.

How can I hide the button when clicking a different input or when click anywhere in the page?

HTML TEMPLATE:

<!-- TASK TEMPLATE -->
  <template id="task-template">
    <div class="task-div">
      <input type="text" name="task-name">
      <button class="edit-btn-confirm"><i class="fa fa-check-circle"></i></i></button> 
      <div class="util-div">
        <input type="date" id="date-input" name="date-input" value="" readonly>
        <button class="remove-task-btn"><i class="fa fa-trash"></i></button>
      </div>
    </div>
  </template>

Event listener:

tasksContainer.addEventListener('click', function(e) {
  // Delete single task
  if(e.target.className === 'fa fa-trash') {

    selectedTaskID = e.target.parentElement.parentElement.previousElementSibling.id;
    projectList.forEach(project => {
      if(project.id === selectedProjectID) {

        Swal.fire({
          title: 'Are you sure?',
          icon: 'warning',
          showCancelButton: true,
          confirmButtonColor: '#3085d6',
          cancelButtonColor: '#d33',
          confirmButtonText: 'Yes, delete it!'
        }).then((result) => {

          if (result.isConfirmed) {
            project.tasks = project.tasks.filter(task => task.id !== selectedTaskID);
            saveAndRender(); 
          }
        })
      }
    })
  }

  if(e.target.name === 'task-name') {
    
    let editButtonConfirm;
    let oldTaskName = e.target.value;

    for(let i = 0; i < e.target.parentElement.childElementCount; i++) {

      if(e.target.parentElement.children[i].className === 'edit-btn-confirm') {
        editButtonConfirm = e.target.parentElement.children[i];
      }
    }
   
    editButtonConfirm.style.display = 'inline';

    editButtonConfirm.onclick = function() {
      
      let selectedProject = projectList.find(item => item.id === selectedProjectID);
      let newTaskName = e.target.value;

      // Swap new task title with old on
      selectedProject.tasks.forEach(item => {
        if(item.name === oldTaskName) {
          item.name = newTaskName;
          saveAndRender();
        }
      })
    }
  }
})

Use a form for each item. There are multiple benefits to this, but in your case specifically, can then use focus-within to only show a button when the form it’s within is focussed (ie the input is active). No JS needed, just CSS

(Edit: can just be done with the sibling selector, but as I say there are ancillary benefits to writing the HTML in a more accessible way)

2 Likes

This would make me a little nervous. I’m not 100% certain that all browsers would return the class names in this order. If you really want to make sure that both class names are on the element I would use classList.contains() for each class name you want to check.

2 Likes

Ok, I usually use classList.contains() but I wanted to try className, thanks!

Thanks, I don’t understand if I should apply the focus-within to the form or to the child element button and change the display to ‘inline’?

.the-selector-for-a-button {
  /* CSS that hides the button by default */
}

.the-selector-for-a-form:focus-within .the-selector-for-a-button {
  /* CSS for showing the button */
}

And it doesn’t need to select a specific Todo: only one can be focussed at any one time, so that’s the one that’s being used & so that has a button visible

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.