0

I created a table in my HTML code. It has 9 columns and 13 rows. It gets filled up completely by a JavaScript loop that fills it with names of people from a few arrays. However, I want to add a validation step that makes sure that no two cells within a row hold the same value and that the value of a each cell does not repeat in the cell directly beneath it.

Since I am only able to access the values of the cells of the table as a NodeList, I decided to make it into an array to use the IndexOf property to search through the array:

var table1 = document.getElementsByTagName("td");
var table1Array = [];    //number of "td" elements on page
for (i = 0; i < 117; i++) {
    table1Array[i] = table1[i].innerHTML;
}

I don't know of a more elegant method (sort of a beginner here). So I set up some nested loops to compare each cell to each element in the row array I create (for all 13 rows):

function validateCells() {
for (i = 0; i < 117; i = i + 9) { //increment for going to next column (after 9 cells or elements in array)
    for (j = 0; j < 8; j++) {
        var curCell = table1Array[i];
        var curRow = [];         //I'm ignoring the first column which is "date", so it has 8 elements rather than 9
        for (k = 0; k < 8; k++) {
            curRow[k] = document.getElementById("row" + j).children[k].innerHTML;
        }
        if (curRow.indexOf(curCell) != -1) {
            curCell = "CONFLICT"; //trying to change value in table. Doesn't work.
        }
    }
}
}

Unfortunately, this won't work. I don't even know if modifying the value of the curCell reference variable will actually change the value of the table1Array at that location, even less if it will change the data in the table.

Am I using the indexOf property correctly? Do I use the != comparison operator or !==? Does indexOf return the index as a Is there any other less complicated, more elegant to do this? I just started with jQuery, maybe it can help simplify the code and make it less error-prone.

Sorry for the all the questions, I'm really trying to understand how all of this works. Thanks!

d8k_irf
  • 251
  • 1
  • 3
  • 10
  • When you create an array like this `var curRow = [8];` it creates an array with only one element, which value is 8 – axelduch Jun 22 '14 at 03:10
  • So it would just be `var curRow = [];` to declare the array? And in the first one, too? I would have to change it to `var table1Array = [];` ? – d8k_irf Jun 22 '14 at 03:16
  • 1
    Tables have a *rows* collection, and rows have a *cells* collection. You should be able to access cells in adjacent rows using their index (e.g. compare the content of rows[i].cells[j] to rows[i+1].cells[j]. – RobG Jun 22 '14 at 03:47
  • @user96872 Yes that would be an improvement, and also, it is generally to use `Array.prototype.push` instead of directly setting the value at an index in an array when the given index does not exist yet. – axelduch Jun 22 '14 at 03:57

2 Answers2

2

You should get an array of rows, each row is an array of cells. That way the validation is much easier. I'm not sure about how you want to show the conflict. In this demo I've just highlight the duplicated cells (conflicted) in red (at least I like this kind of showing conflict rather than modifying the conflicted cells' text).

HTML:

<table>
  <tr><td>1</td><td>2</td><td>3</td></tr>
  <tr><td>1</td><td>5</td><td>6</td></tr>
  <tr><td>7</td><td>8</td><td>7</td></tr>
  <tr><td>8</td><td>9</td><td>10</td></tr>
</table>
<button>Check constraints</button>

CSS:

td {
  width:100px;
  height:50px;
  border:1px solid black;
}
table {
  border:1px solid black;
  border-collapse:collapse;
}
td.invalid {
  background:red;
}

JS:

$('td').attr('contenteditable',true);
var cell;     
function highlight(){    
  $(arguments).toggleClass('invalid',true);
}
function checkConstraints(e){
  //reset style before re-checking
  $('td.invalid').toggleClass('invalid');
  //get rows as an array of array
  var rows = $('tr').map(function(elem,i){
      return [$(this).children('td').toArray()];
  }).toArray();        
  //loop through the rows
  for(var i = 0; i < rows.length; i++){
    cell = {};        
    for(var j = 0; j < rows[i].length; j++){
        var cellText = $(rows[i][j]).text();
        if(cell[cellText]) {  
            highlight(cell[cellText], rows[i][j]);                
        } else {
            cell[cellText] = rows[i][j];
        }
        if(i < rows.length - 1 && 
           cellText == $(rows[i+1][j]).text()){
           highlight(rows[i][j],rows[i+1][j]);
        }            
    }        
  }
}
$('button').click(checkConstraints);

Demo.

Note that, I set contenteditable for all the cells (td), you can edit the cells text to what you want and click the button to test the demo.

Community
  • 1
  • 1
King King
  • 61,710
  • 16
  • 105
  • 130
  • 1
    Note that *innerText* is not part of any W3C standard and may not be supported everywhere (e.g. Firefox). – RobG Jun 22 '14 at 04:33
1

You can use the table rows and cells collections for the iteration. The following does a literal comparison of the text content, you may wish to process the text first to "normalise" it in regard to whitespace.

<table id="t0">
 <tr><td>foo<td>bar<td>fum</td>
 <tr><td>fum<td>bar<td>foo</td>
 <tr><td>foo<td>fum<td>fum</td>
</table>

<script>

compareRows(document.getElementById('t0'));

function compareRows(table) {
  var row, rows = table.rows;
  var cell, cells;
  var rowText;

  // For each row in the table
  for (var i=0, iLen=rows.length; i<iLen; i++) {
    row = rows[i];
    cells = row.cells;

    // Compare the text in each cell
    for (var j=0, jLen=cells.length; j<jLen; j++) {
      cell = cells[j];

      for (var k=0; k<jLen; k++)

        if (k != j && cells[k].textContent == cell.textContent) {
          // cell text is repeated in current row
          console.log('row ' + i + ' cell ' + j + ' text repeated in cell ' + k);
      }

      // Compare with the text in the cell immediately below (if there is one)
      if (i < iLen-2 && cell.textContent == rows[i+1].cells[j].textContent) {
        // cell text is repeated in next row
        console.log('row ' + i + ' cell ' + j + ' text repeated in row ' + (i+1));
      }
    }
  }
}
</script>

Note that repeated text in a row will be reported twice.

The above uses the textContent property, which may be supported as innerText in some user agents. It also runs about 10 times faster than the jQuery alternative.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • I forgot about the pure properties supported on the TABLE element, it's better to use them instead of querying for the cells using jQuery. – King King Jun 22 '14 at 05:20
  • Genius! I had no idea the textContent property even existed! But what do you mean by "normalizing" the text in regard to whitespace? I was seeing that the textContent property returns the textual content of specified node **and** all of its descendants. It does return them without spaces, is that what you mean? @RobG – d8k_irf Jun 23 '14 at 02:59
  • @user96872—the *textContent* property may return more whitespace than you think, and may be different in different browsers so it might be best to say trim leading and trailing whitespace and reduce internal spaces to single and maybe convert newlines to space (if that should compare the same). – RobG Jun 23 '14 at 03:30
  • @RobG how do I access the current cell as an HTML element so that I can change its CSS properties? I am trying to highlight these cells. Thanks! – d8k_irf Jun 24 '14 at 00:47