0

I'm currently learning JS and jQuery and I tried to make a shopping list using dynamic tables. My code is here: https://jsfiddle.net/AdelinaLipsa/ax3p48jy/6/ The problem is I'm kinda stuck at the Mark as bought button which appears when you add an item to the list. Having two columns in a table which I dynamically populate I want to make that button so that when it is pressed, it would strike a line through the item on the left.

This is what I tried to use:

$("td:button").click(function () {
  $('#bought').wrap("<strike>");
  //$(this).css("text-decoration", "line-through");
});

or

function bought(element) {
  let prevEl = $(element).prev();
  if (prevEl.hasClass('bought')) {
    prevEl.removeClass('bought')
  } else {
    prevEl.addClass('bought')
  }
}

https://jsfiddle.net/AdelinaLipsa/ax3p48jy/6/

This is how the website should work:
https://gph.is/g/Z7k3nPZ
enter image description here

yunzen
  • 32,854
  • 11
  • 73
  • 106

1 Answers1

2
  1. Don't use ids if you have more than one
  2. Better not use onclick or onkeyup attributes
    • use <element>.addEventListener() or $(<element>).on() for this
  3. When you have to listen to events on dynamically created elements use a trick

    • The element which should listen to the event should be the whole document (1). When the event (2) occurs, it bubbles up until it hits a listener and that listener's callback function (4) can than act upon the event.target
    $('#table').on('click', '.buy', function(e) { /* ... */})
    1--^^^^^^
    2---------------^^^^^
    3------------------------^^^^
    4-------------------------------^^^^^^^^^^^^^^^^^^^^^^^^
    
    

    Here (3) acts as a filter which is provided by jQuery. Only those events whose targets match the specified selector, really do fire the event

  4. Use the closest() function of jQuery to travel the DOM tree up until the closest common ancestors of the button and the text you want to strike, and the use find() to travel down the tree to your wanted target.

console.clear()

function addItem(e) {
  var list = document.getElementById("item").value;
  var table = "<tr><td class='listedItem'>" + list + "</td><td><button class='buy'>Mark as bought</button></td>";
  document.getElementById('thead').innerHTML = "<tr><td><b>Item description<b><td><b>Action</b></td></tr>";
  document.getElementById('tbody').innerHTML += table;
}



$('#table').on('click', '.buy', function(e) {
  $(e.target).closest('tr').find('.listedItem').addClass('bought');
  //$(this).css("text-decoration", "line-through");
});



function sortAsc() {
  var table, rows, switching, i, x, y, shouldSwitch;
  table = document.getElementById("table");
  switching = true;

  while (switching) {
    switching = false;
    rows = table.rows;

    for (i = 1; i < (rows.length - 1); i++) {
      shouldSwitch = false;
      x = rows[i].getElementsByTagName("td")[0];
      y = rows[i + 1].getElementsByTagName("td")[0];
      if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
        shouldSwitch = true;
        break;
      }
    }
    if (shouldSwitch) {
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
    }
  }
}

function sortDesc() {
  var table, rows, switching, i, x, y, shouldSwitch;
  table = document.getElementById("table");
  switching = true;
  while (switching) {
    switching = false;
    rows = table.rows;
    for (i = 1; i < (rows.length - 1); i++) {
      shouldSwitch = false;
      x = rows[i].getElementsByTagName("td")[0];
      y = rows[i + 1].getElementsByTagName("td")[0];
      if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
        shouldSwitch = true;
        break;
      }
    }
    if (shouldSwitch) {
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
    }
  }
}


document.getElementById('addButton').addEventListener('click', addItem);
document.getElementById('sort_asc').addEventListener('click', sortAsc);
document.getElementById('sort_desc').addEventListener('click', sortDesc);
* {
  box-sizing: border-box;
}

body {
  background-color: #4abdac;
  min-width: 250px;
}

h2 {
  text-align: center;
  font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
}

#container {
  background-color: #FC4A1A;
  max-width: 500px;
  height: 100%;
  margin: auto;
  color: #f7b733;
  border-radius: 25px;
  padding: 15px;
}

#item {
  margin: 0;
  border: none;
  border-radius: 0;
  width: 75%;
  padding: 10px;
  float: left;
  font-size: 16px;
}

#addButton,
#sort_asc,
#sort_desc {
  padding: 10px;
  width: 25%;
  background: #d9d9d9;
  color: #555;
  float: left;
  text-align: center;
  font-size: 12px;
  cursor: pointer;
  transition: 0.3s;
  border-radius: 0;
}

#sort_desc:hover {
  background-color: #bbb;
}

#sort_asc:hover {
  background-color: #bbb;
}

#addButton:hover {
  background-color: #bbb;
}

td:hover {
  background: #ddd;
  transition: 500ms;
}

table {
  clear: left;
  word-break: break-all;
}

.bought {
  text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
  <div id="shopping_list">
    <h2>YOUR SHOPPING LIST</h2>
    <form>
      <input type="text" id="item">
      <button id="addButton" type="button">Add item</button>
    </form>
    <button id="sort_asc">Sort Asc</button>
    <button id="sort_desc">Sort Desc</button>
    <table id="table">
      <thead id="thead"></thead>
      <tbody id="tbody"></tbody>
    </table>
  </div>
</div>
yunzen
  • 32,854
  • 11
  • 73
  • 106
  • Damn you beat me to it +1 was making a similar one, could you add to your point 2 that they have to use $().on() listener instead of click because click doesn't bind to dynamically created elements and only binds the handler to already existing elements. Just so they know why their click event never triggered. – mrdeadsven May 08 '19 at 14:18
  • I understood it perfectly, thank you so much for your help and time. I'm really grateful – Adelina Lipşa May 08 '19 at 14:47
  • @mrdeadsven `.on()` also does not bind to dynamically created elements, but only to existing ones as well. See here: [jQuery binding events to dynamically created elements](https://codepen.io/HerrSerker/pen/ac2d97ed5b9ec294e5f8b1651ba28d8f) – yunzen May 09 '19 at 07:11
  • @mrdeadsven And look here: [jQuery .on()](https://api.jquery.com/on/#direct-and-delegated-events): `Event handlers are bound only to the currently selected elements; they must exist at the time your code makes the call to .on()` – yunzen May 09 '19 at 07:14
  • @yunzen I think I phrased myself wrongly in my first message. you are indeed correct in what you send and I now see that my phrasing was weird. what I meant to say was that you can't create a new element with an onclick in it for example if I take your code and adjust it a bit to this: $('SPAN').insertAfter(e.target). if I were to add the onclick someFunction() in this new element the function will never be defined. Hope this clears a bit up at what I tried to say in my first comment. – mrdeadsven May 09 '19 at 07:22
  • @mrdeadsven My list item (3) covers that – yunzen May 09 '19 at 07:24
  • Yes, but you never stated anywhere that the event handler doesn't bind to the dynamically created elements. your point (3) just says hey you need to do it this way but not why their way was wrong and this might leave them in the dark on why their first onclick inside the dynamic element didn't work. anyway it seems they got the point and understood everything so let's just call it a job well done :). – mrdeadsven May 09 '19 at 07:38