Frederick M. Rogers
Frederick M. Rogers

Reputation: 921

Javascript - How to Remove DOM elements using click events and classes?

I am having some difficulty using parentNode.removeChild(). I have a list of 'items' in an un-ordered list, each have there own delete button. I am trying bind a click event to each individual button that will delete it's respective parent 'item'.

My code so far:

<ul class="list">
  <h2>This is a list</h2>
  <li class="item">
    <h3>Some Item</h3>
    <button class="delete">Delete</div>
  </li>
  <li class="item">
    <h3>Some Item</h3>
    <button class="delete">Delete</div>
  </li>
  <li class="item">
    <h3>Some Item</h3>
    <button class="delete">Delete</div>
  </li> 
</ul>

var childElements = document.getElementsByClassName('item');
var buttonElement = document.getElementsByClassName('delete');

function deleteItem(buttonsClass, childClass) {
  for (var i=0;i<buttonsClass.length;i++) {
    var child = childClass[i];
    buttonsClass[i].addEventListener('click', function(child) {
       childClass[i].parentNode.removeChild(childClass[i]);
    }, false);
  }    
}

deleteItem(buttonElement, childElements);

I know there is an easier way to do this with jQuery but i really want to solve this with plain javascript. Thank you for any and all help.

Upvotes: 3

Views: 19094

Answers (3)

Greg Burghardt
Greg Burghardt

Reputation: 18783

This is a perfect case for event delegation. No need for jQuery at all:

(function(window, htmlElement) {

    'use strict';

    htmlElement.addEventListener("click", handleClick, false);

    function handleClick(event) {
        if (event.target.classList.contains("delete")) {
            event.preventDefault();
            removeItem(event.target);
        }
    }

    function removeItem(button) {
        var item = getItem(button),
            confirmMessage;

        if (item) {
            confirmMessage = item.getAttribute("data-confirm");

            if (!confirmMessage || window.confirm(confirmMessage)) {
                item.parentNode.removeChild(item);
            }
        }
        else {
            throw new Error("No item found");
        }
    }

    function getItem(button) {
        var element = button.parentNode,
            item = null;

        while (element) {
            if (element.nodeName === "LI" || element.nodeName === "TR") {
                item = element;
                break;
            }

            element = element.parentNode;
        }

        return item;
    }

})(this, this.document.documentElement);

You have one click handler for the entire page, regardless of how many delete buttons you have. This should also work for list items or table rows, and by specifying a data-confirm attribute on your buttons, it will pop up a confirm box before removing it.

<button type="button" class="delete"
        data-confirm="Are you sure you want to delete this item?">
    Delete
</button>

You can also easily change this so it uses another attribute to find the delete button:

<button type="button" class="delete"
        data-delete
        data-confirm="...">
    Delete
</button>

Just change the condition of the if statement in the handleClick function to:

if (event.target.hasAttribute("data-delete")) {
    event.preventDefault();
    removeItem(event.target);
}

This decouples your behavior from styling. You can use the delete class name for styling, and the data-delete attribute for JavaScript behavior. If you need to change the name of the CSS delete class for any reason, or use a different class, because you decide to use a third party CSS framework like Bootstrap, then you don't need to change a single line of JavaScript.

The last advantage here is that the click handler is attached to the document.documentElement object, which is available the moment JavaScript begins executing and represents the <html> element. No need for jQuery's document ready event handler. Just attach the click handler and import the script at any point on the page.

Upvotes: 3

gurvinder372
gurvinder372

Reputation: 68383

Looks like you want to bind a click event to delete buttons, and on that event delete that item.

You need to fetch the child classes individual buttonsClass elements.

function deleteItem( childElements ) 
{
   Array.prototype.slice.call( childElements ).forEach( function( item ){
      var deleteChildren = item.getElementsByClassName( "delete" );
      Array.prototype.slice.call( deleteChildren ).forEach( function( deleteBtn ){
        deleteBtn.addEventListener('click', function() 
        {
            this.parentNode.removeChild( this );
        }, false);        
      });
   });
}

or even more simply, just pass the list of buttons on clicking which parent item will be deleted

function deleteItem( buttonElement ) 
{
   Array.prototype.slice.call( buttonElement ).forEach( function( button ){
        button.addEventListener('click', function() 
        {
            this.parentNode.removeChild( this );
        }, false);        
   });
}

Upvotes: 1

Luca Rainone
Luca Rainone

Reputation: 16458

The problem is that your childClass[i] that you call when you click an element, is not what you expect when you define the function. You should use event.target for catch the element clicked

var childElements = document.getElementsByClassName('item');
var buttonElement = document.getElementsByClassName('delete');

var _handler = function(e) {
     e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}

function deleteItem(buttonsClass, childClass) {
  for (var i=0;i<buttonsClass.length;i++) {
    buttonsClass[i].addEventListener('click', _handler, false);
  }    
}

deleteItem(buttonElement, childElements);

-- edit --

If you want to use the original approach, then you can solve it in this way:

function deleteItem(buttonsClass, childClass) {
  for (var i=0;i<buttonsClass.length;i++) {
    (function(child) {
        buttonsClass[i].addEventListener('click', function(e) {
           child.parentNode.removeChild(child);
        }, false);
    })(childClass[i]);
  }    
}

With encapsulation (function(encapsulatedChild) { })(child) you can store the value of child in a context that does not change during the next cycle.

Upvotes: 3

Related Questions