0

How can I detect if a mouse click was not done on a table row?

I created this table, which can be modified on the fly and highlights the selected row, if selected. Now I try to establish a function, which removes the row highlighting as soon as a mouse click was not done in one of those table rows. I do not want to add another eventListener for the remaining body or other html elements located in the background.

I thought about the following:

if (row highlighted) { next outside click removes row highlight }

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <title>Table Test</title>
    <!-- d3.js framework -->
    <script src="https://d3js.org/d3.v6.js"></script>
    <!-- jquery import-->
    <script   src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- fontawesome stylesheet https://fontawesome.com/ -->
    <script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>

<style>
    #myTable {
        border-collapse: collapse;
    }

    #myTable td {
        border: 1px solid black;
        padding: 8px;
    }

    #delRow {
        width: auto;
        height: 30px;
    }

</style>

<body>
    <table id="myTable">
        <th>
           Property 
        </th>
        <th>
            Value
        </th>
        <tbody>
            <!-- filled by script -->
        </tbody>
    </table>

    <button id="dataToArray">dataToArray</button>

    <script>
        data = [
            {
                "property": "animal",
                "value": "dog"
            },
            {
                "property": "car",
                "value": "audi"
            },
            {
                "property": "snacks",
                "value": "chips"
            }
        ]


        //populate table
        var myTable = document.getElementById("myTable")
        
        for (var i = 0; i < data.length; i++ ) {
            var row =   `<tr>
                            <td contenteditable='true'>${data[i].property}</td>
                            <td contenteditable='true'>${data[i].value}</td>
                        </tr>`

            myTable.innerHTML += row   
        }

        //higlight row
        let lastClickedRow = null
        for (var i = 0, tableRow; tableRow = myTable.rows[i]; i++) {
                tableRow.addEventListener("click", function() {
                    if (lastClickedRow != null) {
                        lastClickedRow.style.backgroundColor = null
                    }
    
                    lastClickedRow = this
    
                    lastClickedRow.style.backgroundColor = "greenyellow"
                })
            }

        //get table data
        var dataToArray = document.getElementById("dataToArray")

        dataToArray.addEventListener("click", function() {
            var exportData = []
            $("table#myTable tr").each(function() {
                var rowDataObject = new Object
                var actualData = $(this).find("td");
                if (actualData.length > 0) {
                        rowDataObject.property = actualData[0].innerText;
                        rowDataObject.value = actualData[1].innerText;
                        exportData.push(rowDataObject) 
                };
            })
            console.log(exportData)
        })
    </script>
</body>

</html>
ICoded
  • 319
  • 3
  • 18
  • Could you please create a StackBlitz so it’s gonna be easier for us to help – Raz Luvaton Aug 11 '21 at 07:03
  • @RazLuvaton - there's a working example right here in the page. StackBlitz offers no advantage I can see. ;) – enhzflep Aug 11 '21 at 07:04
  • IMO, It help us play with the code – Raz Luvaton Aug 11 '21 at 07:05
  • 1
    @RazLuvaton - That's one of the purposes of the "Copy snippet to answer" button. ;) – enhzflep Aug 11 '21 at 07:07
  • I don’t see that button but thanks, ohhhh I was on mobile, my apologies! – Raz Luvaton Aug 11 '21 at 07:07
  • Is there a reason you don’t want to add another listener for body or something? – Raz Luvaton Aug 11 '21 at 07:19
  • @RazLuvaton yes because I have many other HTML elements in my main projects and want to avoid adding eventListener to each of them. As it should work if click was done anywhere beside in the table itself. – ICoded Aug 11 '21 at 07:21
  • 1
    If you don’t use stop event propagation, you could just add 1 listener to the body and check if the event.target is not a row or not the highlighted row (the solution is problematic as if you ever add that it will cause bugs) – Raz Luvaton Aug 11 '21 at 07:23
  • Does this answer your question? [How do I detect a click outside an element?](https://stackoverflow.com/questions/152975/how-do-i-detect-a-click-outside-an-element) – Raz Luvaton Aug 11 '21 at 07:39

1 Answers1

3

Much simpler solution for this will be

  • Add an event listner to the click event of body.
  • Check whether the ckick event is either on the button or on the table with Node.contains in Javascript Reference
  • If the table row and button is not clicked, reset the color.

data = [
  {
    "property": "animal",
    "value": "dog"
  },
  {
    "property": "car",
    "value": "audi"
  },
  {
    "property": "snacks",
    "value": "chips"
  }
]


//populate table
var myTable = document.getElementById("myTable")

for (var i = 0; i < data.length; i++) {
  var row = `<tr>
                  <td contenteditable='true'>${data[i].property}</td>
                  <td contenteditable='true'>${data[i].value}</td>
              </tr>`
  myTable.innerHTML += row
}

//higlight row
let lastClickedRow = null;

function resetColor() {
  for (var i = 0, tableRow; tableRow = myTable.rows[i]; i++) {
    tableRow.style.backgroundColor = null;
  }
}

for (var i = 0, tableRow; tableRow = myTable.rows[i]; i++) {
  tableRow.addEventListener("click", function (e) {
    if (lastClickedRow != null) {
      lastClickedRow.style.backgroundColor = null
    }
    lastClickedRow = this
    lastClickedRow.style.backgroundColor = "greenyellow";
  })
}

var tableElement = document.getElementById('myTable');
var buttonElement = document.getElementById('dataToArray');


document.addEventListener('click', function(event) {
  const isInsideClicked = tableElement.contains(event.target) || buttonElement.contains(event.target);
  if(!isInsideClicked) {
    resetColor();
  }
})

//get table data
var dataToArray = document.getElementById("dataToArray")
dataToArray.addEventListener("click", function (e) {
  var exportData = []
  $("table#myTable tr").each(function () {
    var rowDataObject = new Object
    var actualData = $(this).find("td");
    if (actualData.length > 0) {
      rowDataObject.property = actualData[0].innerText;
      rowDataObject.value = actualData[1].innerText;
      exportData.push(rowDataObject)
    };
  })
  console.log(exportData)
})
#myTable {
  border-collapse: collapse;
}

#myTable td {
  border: 1px solid black;
  padding: 8px;
}

#delRow {
  width: auto;
  height: 30px;
}
<table id="myTable">
  <th>
    Property
  </th>
  <th>
    Value
  </th>
  <tbody>
    <!-- filled by script -->
  </tbody>
</table>

<button id="dataToArray">dataToArray</button>
Nitheesh
  • 19,238
  • 3
  • 22
  • 49
  • This won’t work in case there is another element with `stopPropegation`, also look at the note at [this](https://stackoverflow.com/a/153047/5923666) SO answer – Raz Luvaton Aug 11 '21 at 07:36
  • 1
    @RazLuvaton I have updated my answer to a much generic solution. My previous implementation had some bugs and performance issue. – Nitheesh Aug 11 '21 at 07:38
  • The problem persist when clicking some element that there click handler call `stopPropagation` – Raz Luvaton Aug 11 '21 at 07:50