0

I try to build js dynamically table generator with content editable cells. It works when table cells (td) are empty since new table structure replace the old one.

My problem starts when user type on table and then want to make structure change (i.e. more/less cells etc).

  const theTable = document.querySelectorAll('.adTable')[0]; /* table el */
  let codeToSave = []; /* this eventually send to server */
  let tableContent = ''; /* dynamically table content */
  adTableGenerator();
  
  /* Change Event triggers generator */
  const wizardInputs = document.querySelectorAll('form input[type="number"]');
  for (var i=0; i < wizardInputs.length; i++) {
    wizardInputs[i].addEventListener('change', adTableGenerator);
  }
  
  function adTableGenerator() {
    const tableRows = Number(document.querySelector('#rowNum').value); /* get current rows number */
    const tableCells = Number(document.querySelector('#cellsNum').value); /* get current cells number */
    let contentCheck = theTable.querySelectorAll('td'); /* check if table is empty by it's TD elements */
    // console.log(contentCheck.length); 
    if (contentCheck.length > 0) { /* if table NOT empty: */
      for (var i=0; i < contentCheck.length; i++) { /* loop over TD's */
        console.log('innerHTML: '+contentCheck[i].innerHTML); /* this sometime return <br> (if you type inside cells?) but not text */
        /* this ALWAYS return <empty string> */
        console.log('textContent: '+contentCheck[i].textContent); 
        /* try this because https://stackoverflow.com/questions/3593626/get-the-text-content-from-a-contenteditable-div-through-javascript 
        but it still ALWAYS returns <empty string> */
        console.log('innerText: '+contentCheck[i].innerText); 
        
        if ( (contentCheck[i].textContent && contentCheck[i].textContent.trim() ) || (contentCheck[i].innerText && contentCheck[i].innerText.trim() ) !== '') { 
          alert('Table: YES Content YES');
          tableContent = 'need to be done';          
          //theTable.innerHTML = tableContent;
        }
        else {
          alert('Table: YES Content NO');
          /* Fill table structure */
          tableContent = 
          '<thead><tr>' + 
            new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') + 
          '</tr></thead><tbody>' +
            new Array(tableRows).fill('<tr>' + 
              new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') + 
            '</tr>').join('')+
          '</tbody>'
          ;
          theTable.innerHTML = tableContent;
          return;
        };
      };      
    }
    else {
      alert('Table: No');
      tableContent = 
      '<thead><tr>' + 
        new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') + 
      '</tr></thead><tbody>' +
        new Array(tableRows).fill('<tr>' + 
          new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') + 
        '</tr>').join('')+
      '</tbody>'
      ;
      theTable.innerHTML = tableContent;  
    }; 
    
  };

  /* Save table changes on array - this will be "undo" later on */
  function saveState() {
    codeToSave.push( theTable.parentElement.innerHTML );
    //console.log(codeToSave);
  };

  /* Reset table */
  const resetBtn = document.querySelectorAll('button[type="reset"]')[0];
  resetBtn.addEventListener('click', function(){
    document.querySelectorAll('.adTable')[0].innerHTML = '';    
    codeToSave.length = 0;
    adTableGenerator();
  });  
/* demo css*/   
  label {display: block;}
  form {width: 270px; float: left;}
  div > form + div {width: calc(100% - 280px); min-width: 300px; float: right; overflow-x: auto;} 
  table {width: 100%; border-collapse: collapse;}
  td, th {border: 1px solid black; height: 2em;} 
<div>
  <form>
    <fieldset>
      <label for="cellsNum">
        <span>Cells</span>
        <input type="number" min="2" value="8" id="cellsNum" />
      </label>
      <label for="rowNum">
        <span>Rows</span>
        <input type="number" min="2" value="8" id="rowNum" />
      </label>
    </fieldset>
    <button type="reset">reset table</button>        
  </form>  
  <div>
    <table class="adTable"></table>
  </div>
</div>

Try to type on cells and then change rows/cells number and you get this.

Please advice.

Alireza Ahmadi
  • 8,579
  • 5
  • 15
  • 42
A. Meshu
  • 4,053
  • 2
  • 20
  • 34

2 Answers2

1

The thing is that actually only the first table cell (the most top-left one) is checked cause your for loop stops after one iteration.
(You can verify that by entering text on the top left cell in your original code, it will work)

Here I slightly modified your code so that your for loop won't stop after checking the top-left cell.
I also changed all alert into console.log.

const theTable = document.querySelectorAll('.adTable')[0]; /* table el */
  let codeToSave = []; /* this eventually send to server */
  let tableContent = ''; /* dynamically table content */
  adTableGenerator();
  
  /* Change Event triggers generator */
  const wizardInputs = document.querySelectorAll('form input[type="number"]');
  for (var i=0; i < wizardInputs.length; i++) {
    wizardInputs[i].addEventListener('change', adTableGenerator);
  }
  
  function adTableGenerator() {
    const tableRows = Number(document.querySelector('#rowNum').value); /* get current rows number */
    const tableCells = Number(document.querySelector('#cellsNum').value); /* get current cells number */
    let contentCheck = theTable.querySelectorAll('td'); /* check if table is empty by it's TD elements */
    // console.log(contentCheck.length); 
    
    // new code
    let hasContent = false;
    if (contentCheck.length > 0) { /* if table NOT empty: */
      for (var i=0; i < contentCheck.length; i++) { /* loop over TD's */
        console.log('innerHTML: '+contentCheck[i].innerHTML); /* this sometime return <br> (if you type inside cells?) but not text */
        /* this ALWAYS return <empty string> */
        console.log('textContent: '+contentCheck[i].textContent); 
        /* try this because https://stackoverflow.com/questions/3593626/get-the-text-content-from-a-contenteditable-div-through-javascript 
        but it still ALWAYS returns <empty string> */
        console.log('innerText: '+contentCheck[i].innerText); 


        // new code
        
        
        if ( (contentCheck[i].textContent && contentCheck[i].textContent.trim() ) || (contentCheck[i].innerText && contentCheck[i].innerText.trim() ) !== '') { 
          // changed code
          hasContent = true;
          break;
          /*
          console.log('Table: YES Content YES');
          tableContent = 'need to be done';    
          */
          //theTable.innerHTML = tableContent;
        }
        else {
          // changed code
          
          // console.log('Table: YES Content NO');
          /* Fill table structure */
          /*
          tableContent = 
          '<thead><tr>' + 
            new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') + 
          '</tr></thead><tbody>' +
            new Array(tableRows).fill('<tr>' + 
              new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') + 
            '</tr>').join('')+
          '</tbody>'
          ;
          theTable.innerHTML = tableContent;
          return;
          */
        }
      }
      if (hasContent) {
          console.log('Table: YES Content YES');
          tableContent = 'need to be done';    
      } else {
          console.log('Table: YES Content NO');
          tableContent = 
          '<thead><tr>' + 
            new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') + 
          '</tr></thead><tbody>' +
            new Array(tableRows).fill('<tr>' + 
              new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') + 
            '</tr>').join('')+
          '</tbody>'
          ;
          theTable.innerHTML = tableContent;
          return;
      }
    }
    else {
      console.log('Table: No');
      tableContent = 
      '<thead><tr>' + 
        new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') + 
      '</tr></thead><tbody>' +
        new Array(tableRows).fill('<tr>' + 
          new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') + 
        '</tr>').join('')+
      '</tbody>'
      ;
      theTable.innerHTML = tableContent;  
    }; 
    
  };

  /* Save table changes on array - this will be "undo" later on */
  function saveState() {
    codeToSave.push( theTable.parentElement.innerHTML );
    //console.log(codeToSave);
  };

  /* Reset table */
  const resetBtn = document.querySelectorAll('button[type="reset"]')[0];
  resetBtn.addEventListener('click', function(){
    document.querySelectorAll('.adTable')[0].innerHTML = '';    
    codeToSave.length = 0;
    adTableGenerator();
  });
/* demo css*/   
  label {display: block;}
  form {width: 270px; float: left;}
  div > form + div {width: calc(100% - 280px); min-width: 300px; float: right; overflow-x: auto;} 
  table {width: 100%; border-collapse: collapse;}
  td, th {border: 1px solid black; height: 2em;}
<div>
  <form>
    <fieldset>
      <label for="cellsNum">
        <span>Cells</span>
        <input type="number" min="2" value="8" id="cellsNum" />
      </label>
      <label for="rowNum">
        <span>Rows</span>
        <input type="number" min="2" value="8" id="rowNum" />
      </label>
    </fieldset>
    <button type="reset">reset table</button>        
  </form>  
  <div>
    <table class="adTable"></table>
  </div>
</div>
attempt0
  • 639
  • 5
  • 14
  • Thank you I see what you mean. Seems that when "hasContent" flag is `true` it stop check `textContent`? Keep console.log the same thing even though i play with it (type on table, then chagne structure, then type more then change etc). – A. Meshu Aug 11 '21 at 11:01
  • @A.Meshu yep. You can also remove the line `break;` after `hasContent = true;` and it will check the remaining `textContent`. However, this is unnecessary because you already know there is content in the table. – attempt0 Aug 11 '21 at 11:03
  • Thank you for your help, But @AlirezaAhmadi answer was more then i could imagine. – A. Meshu Aug 11 '21 at 11:20
1

You need to create loop and check current row and columns has value or not:

let rows = theTable.querySelectorAll('tr');
var ContentWithValue = "";
for (var i = 0; i < tableRows; i++) {
    ContentWithValue += '<tr>';
    cells = i < rows.length - 1 ? rows[i + 1].querySelectorAll('td') : [];
    for (var j = 0; j < tableCells; j++) {
        ContentWithValue += '<td contenteditable="true" oninput="saveState()">' + (cells[j] ? cells[j].textContent : '') + '</td>'
    }

    ContentWithValue += '</tr>';
}

Here is working sample:

const theTable = document.querySelectorAll('.adTable')[0]; /* table el */
let codeToSave = []; /* this eventually send to server */
let tableContent = ''; /* dynamically table content */
adTableGenerator();

/* Change Event triggers generator */
const wizardInputs = document.querySelectorAll('form input[type="number"]');
for (var i = 0; i < wizardInputs.length; i++) {
    wizardInputs[i].addEventListener('change', adTableGenerator);
}

function adTableGenerator() {
    const tableRows = Number(document.querySelector('#rowNum').value); /* get current rows number */
    const tableCells = Number(document.querySelector('#cellsNum').value); /* get current cells number */
    let contentCheck = theTable.querySelectorAll('td');
    if (contentCheck.length > 0) {

        let rows = theTable.querySelectorAll('tr');
        var ContentWithValue = "";
        for (var i = 0; i < tableRows; i++) {
            ContentWithValue += '<tr>';
            cells = i < rows.length - 1 ? rows[i + 1].querySelectorAll('td') : [];
            for (var j = 0; j < tableCells; j++) {
                ContentWithValue += '<td contenteditable="true" oninput="saveState()">' + (cells[j] ? cells[j].textContent : '') + '</td>'
            }

            ContentWithValue += '</tr>';
        }

        tableContent =
            '<thead><tr>' +
            new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') +
            '</tr></thead><tbody>' + ContentWithValue + '</tbody>'
            ;
        theTable.innerHTML = tableContent;
        return;
    }
    else {
        alert('Table: No');
        tableContent =
            '<thead><tr>' +
            new Array(tableCells).fill('<th contenteditable="true" oninput="saveState()">Title</th>').join('') +
            '</tr></thead><tbody>' +
            new Array(tableRows).fill('<tr>' +
                new Array(tableCells).fill('<td contenteditable="true" oninput="saveState()"></td>').join('') +
                '</tr>').join('') +
            '</tbody>'
            ;
        theTable.innerHTML = tableContent;
    };

};

/* Save table changes on array - this will be "undo" later on */
function saveState() {
    codeToSave.push(theTable.parentElement.innerHTML);
    //console.log(codeToSave);
};

/* Reset table */
const resetBtn = document.querySelectorAll('button[type="reset"]')[0];
resetBtn.addEventListener('click', function () {
    document.querySelectorAll('.adTable')[0].innerHTML = '';
    codeToSave.length = 0;
    adTableGenerator();
});
 label {
            display: block;
        }

        form {
            width: 270px;
            float: left;
        }

        div > form + div {
            width: calc(100% - 280px);
            min-width: 300px;
            float: right;
            overflow-x: auto;
        }

        table {
            width: 100%;
            border-collapse: collapse;
        }

        td, th {
            border: 1px solid black;
            height: 2em;
        }
 <div>
     <form>
         <fieldset>
             <label for="cellsNum">
                 <span>Cells</span>
                 <input type="number" min="2" value="8" id="cellsNum" />
             </label>
             <label for="rowNum">
                 <span>Rows</span>
                 <input type="number" min="2" value="8" id="rowNum" />
             </label>
         </fieldset>
         <button type="reset">reset table</button>
     </form>
     <div>
         <table class="adTable"></table>
     </div>
 </div>
Alireza Ahmadi
  • 8,579
  • 5
  • 15
  • 42
  • @A.Meshu This is working sample but note that as I said in comment if you decrease the cells and rows you may lose the data and it's clear :) – Alireza Ahmadi Aug 11 '21 at 11:17