1

I have a table of products ordered by the value of the last column of each row. I want that when I click on an input, the corresponding checkbox change directly to selected. In this way, when I click on the Save button, only the selected products will be managed to update in the database.

The original code has too many things that are not related to the question, so I have reduced it to this:

var c = 10;
var count = 1;
pageList.forEach(element => {
    document.getElementById("tabla_variantes").innerHTML+=`
        <tr style="background: white;">
            <th class="check_col" scope="row" style="padding: 0px; text-align: center; vertical-align: middle;">
                <input type="checkbox" value="${element.numero}" id="row${count}" name="row-check-pro">
            </th>
            <td> //image </td>
            <td> //product number </td>
            <td> //product name </td>
            <td> //product reference </td>
            <td> //product price </td>
            <td> //product stock </td>
            <td style="text-align:center; vertical-align: middle; padding: 8px 18px;">
                <input type="text" class="form-control text-end" id="posicion${element.numero}" value="${c}">
            </td>
        </tr>
    `;
    document.getElementById(`posicion${element.numero}`).addEventListener('input', (evt) => {
        console.log('run'); // Do something
        document.getElementById("row"+count).checked = true;
    });
    ++count;
    c += 10;
    if (c > list.length*10) c = 10;
});

As you can see I have tried to add a listener to each input, but nothing is showing on the console.

2 Answers2

3

You REALLY should delegate

Also I suggest using map instead of concatenating to the DOM.

This is a drop in replace

const container = document.getElementById("tabla_variantes");
const pageList = [{numero:1},{numero:2},{numero:3}];

container.addEventListener('input', (evt) => {
  const tgt = evt.target; 
  if (tgt.name==="row-check-pro") {
    if (!tgt.checked) tgt.closest("tr").querySelector(".text-end").value="";
  }  
  if (tgt.classList.contains("text-end")) {
    tgt.closest("tr").querySelector("[name=row-check-pro]").checked = tgt.value.trim() != ""; 
  }  
});


var c = 10;
var count = 1;
container.innerHTML+= pageList.map(element => {
  const str = `<tr style="background: white;">
            <th class="check_col" scope="row" style="padding: 0px; text-align: center; vertical-align: middle;">
                <input type="checkbox" value="${element.numero}" id="row${count}" name="row-check-pro">
            </th>
            <td style="text-align:center; vertical-align: middle; padding: 8px 18px;">
                <input type="text" class="form-control text-end" id="posicion${element.numero}" value="${c}">
            </td>
        </tr>
    `;
    ++count;
    c += 10;
    if (c > pageList.length*10) c = 10;
    return str;
}).join("");
<table id="tabla_variantes">
</table>
mplungjan
  • 134,906
  • 25
  • 152
  • 209
  • 1
    You don't **need** to, but you probably **should**. :-) – T.J. Crowder Apr 26 '21 at 09:00
  • I'm thinking about rendering the html with `insertAdjacentHTML` would be a better option. – Farid Shokri Apr 26 '21 at 09:02
  • 2
    @FaridShokri - Certainly in some cases. Here the OP seems to want to completely replace the contents of the target element, so building up one string and passing just one complete string to the browser to parse and build the entire structure in one go probably makes more sense. – T.J. Crowder Apr 26 '21 at 09:03
  • I don't understand really good how to delegate, so I will try what T.J. Crowder says. – Emili Bellot Pulido Apr 26 '21 at 09:04
  • 1
    @EmiliBellotPulido this IS delegation: `container.addEventListener('input', (evt) => { const tgt = evt.target; tgt.closest("tr").querySelector("[name=row-check-pro]").checked = true;});` - I delegate the click from the container, so any element in the container can detect the input and it will be processed as above. You can add to this function by testing the tgt.tagName or classList – mplungjan Apr 26 '21 at 09:06
  • But this checks all checkboxes or just the one in the same row? – Emili Bellot Pulido Apr 26 '21 at 09:09
  • JUST the one in the same row as what received input – mplungjan Apr 26 '21 at 09:09
  • Okey, I will try. Thanks – Emili Bellot Pulido Apr 26 '21 at 09:10
  • 1
    That is what `tgt.closest("tr")` does - access the closest tr of the target input field and find the checkbox from there – mplungjan Apr 26 '21 at 09:10
  • 1
    @EmiliBellotPulido - Where I said you should consider "event delegation and DOM traversal," the event delegation here is handling the click on `container`, and the DOM traversal is the `tgt.closest("tr").querySelector(...)` part. :-) – T.J. Crowder Apr 26 '21 at 09:18
  • I have tried but it's difficult for me to adapt all code I have to this delegate. Can somebody make a private chat with me to find a solution? – Emili Bellot Pulido Apr 26 '21 at 09:21
  • @EmiliBellotPulido No. But see the update. It is a drop in replacement of your code. I changed the checked a little to only check if the value was not empty – mplungjan Apr 26 '21 at 09:27
  • Okey, thanks. No I am getting this error: pageDetails.js:434 Uncaught DOMException: Failed to execute 'querySelector' on 'Element': 'name=row-check-pro' is not a valid selector. – Emili Bellot Pulido Apr 26 '21 at 09:34
  • You need `"[name=row-check-pro]"` - sorry – mplungjan Apr 26 '21 at 09:35
  • Nice, but now, when the check is checked and I click on it, the check doesn't unchecked. – Emili Bellot Pulido Apr 26 '21 at 09:39
  • Haha, ok. we have to narrow it – mplungjan Apr 26 '21 at 10:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231606/discussion-between-emili-bellot-pulido-and-mplungjan). – Emili Bellot Pulido Apr 26 '21 at 10:09
  • I made an update. If you uncheck, the textfield is cleared, if you change the textfield, the checkbox is checked if the field has value – mplungjan Apr 26 '21 at 10:14
  • 1
    Amazing. Thank you very much for your help – Emili Bellot Pulido Apr 26 '21 at 10:17
1

The main problem is that you're using += with innerHTML. That's never a good idea. When you do x.innerHTML += str, the browser has to work through all of the elements within the x element, build an HTML string, return that string to the JavaScript code, then accept the updated string from the JavaScript code, tear down all the previous child elements of x, and replace them with new child elements created by parsing the new string. All state for those elements is lost, including any event handlers they have on them.

The minimal change is to:

  1. Build up all the HTML in a string variable
  2. Assign it to innerHTML
  3. Find the elements and add event handlers to them

But you should consider using event delegation and DOM traversal rather than handlers on each individual element. More about event delegation in this answer on Stack Overflow, but the short version is you handle the event on a common ancestor element, then use event.target to determine what descendant was targeted by the event.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639