AMD Zen Processors
Name Speed Cores Threads Price
SR7 3.5Ghz 8 16 $499
SR7 3.2GHz 8 16 $349
SR5 3GHz 6 12 $250

Adding and removing items from a shopping list is a very common UI pattern, one often accomplished with a framework (or two, or three…). This week I realised that the pattern could be achieved using CSS and vanilla JavaScript

The Base

The fundamental structure of the table is presented in HTML, and is rendered without any UI elements; that way, if the JavaScript fails, or is somehow blocked, the user will still see the list; they just won’t be able to edit it.

<table id="zen">
    <caption>AMD Zen Processors</caption>
    <thead>
        <tr><th>Name<th>Speed<th>Cores<th>Threads<th>Price
    <tbody>
        <tr><td>SR7<td>3.5Ghz<td>8<td>16<td>$499
        <tr><td>SR7<td>3.2GHz<td>8<td>16<td>$349
        <tr><td>SR5<td>3GHz<td>6<td>12<td>$250
</table>

The table has some associated CSS rules:

#zen {
  border-collapse: collapse;
}
#zen tr td, #zen tr th { 
    padding: .4rem;
}
#zen tr td:nth-of-type(3), #zen tr td:nth-of-type(4) { 
  text-align: center;
}
#zen thead {
  border-bottom: 2px solid #333;
}
#zen tbody tr {
  border-bottom: 1px solid #ddd;
}

Adding the Buttons

The buttons are inserted by adding them to a table cell that is concatenated to the end of each row in the body of the table:

let procRows = zen.querySelectorAll("tbody tr");
for (let i = 0; i < procRows.length; i++) {
  procRows[i].innerHTML += '<td><button  title="Remove"></td>';
}

In the CSS, these buttons use a background of an SVG image for the trashcan:

#zen button {
  width: 36px; height: 36px;
  background-image: url(trashcan.svg);
  margin-left: 1rem; border: none;
  background-position: center 4px;
  background-repeat: no-repeat;
  background-size: 25px 25px;
  opacity: .6;
  transition: .5s;
}

There’s also a :hover state to change the appearance of the button when the mouse moves over it:

tbody tr button:hover {
  opacity: .9;
  cursor: pointer;
}

Later the script uses event bubbling to listen for a click in the <tbody> of the table. If this click is on the <button> element, the script finds the cell that the button is inside of, then finds the table row surrounding that element, and applies a class to it; it also removes the button.

zen.querySelector("tbody").addEventListener("click", function(e) {
    if (e.target.nodeName == "BUTTON") {
        let cell = e.target.parentNode;     
        cell.parentNode.classList.add("hidden");
        e.target.remove();
    }
})

Going Schmooop

CSS cannot animate the height of an element; however, the height of a td containing text content (and thus it’s containing row) is calculated from just two things:

  1. The text font-size
  2. The padding on the table cell

…and we can animate both of those properties:

@keyframes hide {
  to {
    font-size: 0;
    padding: 0;
  }
}

That animation sequence is called when a class of hide is added to the table row:

tr.hidden td {
  animation: hide 1s 1s forwards;
}
tr.hidden {
  opacity: 0;
  border-bottom: none;
}

The animation has a 1 second execution after a 1 second delay.

Conclusion

The associated script and CSS creates a straightforward “remove” animation for a selected table row; if you wanted to fully remove the row from the DOM (rather than merely hiding it), you could remove it after the animation completed.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/LbOaKz