1

I have trying to make a functionality for adding, cloning and deleting of table row and column. My table has 4 columns initially.

<tbody>
  <tr>
     <td></td>
     <td></td>
     <td></td>
     <td></td>
  </tr>
</tbody>

I have mostly made the functionality. But, I'm facing an issue at the time of adding row. The main functionality of adding row I have written is:

if there is any existing row
Clone last Row and add this clone row after the last row
else
Add a row with 4 columns

Well, I am adding 4 columns inside else{}. Because, initially my table has 4 columns. This should be fine. But, the problem is there are options for deleting/adding column too. For example, If anybody delete a column, then total number of column will be 3. Then if anybody accidentally delete all the row, then try to add a row again, it will add a new row with 4 columns where the table has now 3 columns. To avoid this kind of situation, I should not add static 4 columns inside else {} But, I don't understand how to handle this issue. Please, help me to fix this issue.

****************************Update**************************

After seeing some solution, I think my problem is not clear to you.

(1) Well, consider there are 4 columns:

(2) After removing a column, there will be 3 columns now:

(3) Removing one more, there are 2 columns now:

(4) Now, let's remove all the rows:

(5) Now, let's try to add a new row:

Because, I always adding 4 fixed columns when there in no row for cloning (inside else{}). So, after total number of columns changing, when there is no row for cloning, it can't create a new row with exact column number.

Problematic jQuery:

$('body').on('click', '.add-row', function() {  
    var tr = $(this).parents('.table-content').find('.table tbody tr:last');
    if(tr.length > 0) {
      var clone = tr.clone();
        clone.find(':text').val('');
        tr.after(clone);
    } else {
      $(this).parents('.table-content').find('.table tbody').append('<tr> <td><span class="remove remove-row">x</span></td><td> <input type="text" class="form-control"> </td><td> static element </td><td> static element </td></tr>');
    }
  });

Fiddle Work

3 Answers 3

2

In the else section replace:

$(this).closest('.table-content').find('.table tbody').append('<tr> <td><span class="remove remove-row">x</span></td><td> <input type="text" class="form-control"> </td><td> static element </td><td> static element </td></tr>');

With the following and you should be good; the trick is to check number of current columns before adding a row:

  var cols = $(this).closest('.table-content').find('th').length,
      tr0 = $('<tr/>');
  tr0.html('<td><span class="remove remove-row">x</span></td><td> <input type="text" class="form-control"> </td>');
  for(var i=2; i < cols; i++) {
    tr0.append('<td> static element </td>')
  }
  $(this).closest('.table-content').find('.table tbody').append( tr0 );

// Code goes here

$(document).ready(function() {
  // add row
  $('body').on('click', '.add-row', function() {
    var tr = $(this).parents('.table-content').find('.table tbody tr:last');
    if (tr.length > 0) {
      var clone = tr.clone();
      clone.find(':text').val('');
      tr.after(clone);
    } else {
      var cols = $(this).closest('.table-content').find('th').length,
        tr0 = $('<tr/>');
      tr0.html('<td><span class="remove remove-row">x</span></td><td> <input type="text" class="form-control"> </td>');
      for (var i = 2; i < cols; i++) {
        tr0.append('<td> static element </td>')
      }
      $(this).closest('.table-content').find('.table tbody').append(tr0);
      //$(this).closest('.table-content').find('.table tbody').append('<tr> <td><span class="remove remove-row">x</span></td><td> <input type="text" class="form-control"> </td><td> static element </td><td> static element </td></tr>');
    }
  });

  // delete row
  $('body').on('click', '.remove-row', function() {
    $(this).parents('tr').remove();
  });

  // add column
  $('body').on('click', '.add-col', function() {
    $(this).parent().find('.table thead tr').append('<th><input type="text" class="form-control pull-left" value=""> <span class="pull-left remove remove-col">x</span></th>');
    $(this).parent().find('.table tbody tr').append('<td>static element</td>');
  });

  // remove column
  $('body').on('click', '.remove-col', function(event) {
    // Get index of parent TD among its siblings (add one for nth-child)
    var ndx = $(this).parent().index() + 1;
    // Find all TD elements with the same index
    $('th', event.delegateTarget).remove(':nth-child(' + ndx + ')');
    $('td', event.delegateTarget).remove(':nth-child(' + ndx + ')');
  });
});
/* Styles go here */

.table-content {
  padding: 20px;
}
.remove {
  margin-left: 10px;
  color: red;
}
.remove:hover {
  cursor: pointer;
}
.form-control {
  width: 90px;
}
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-1.11.2.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

    <div class="table-content">
      <button class="btn btn-link add-col">Add Column</button>
      <div class="table-responsive">
        <table class="table">
          <thead>
            <tr>
              <th></th>
              <th>Define</th>
              <th>
                <input type="text" class="form-control pull-left" value="Property">
                <span class="pull-left remove remove-col">x</span>
              </th>
              <th>
                <input type="text" class="form-control pull-left" value="Feature">
                <span class="pull-left remove remove-col">x</span>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><span class="remove remove-row">x</span></td>
              <td>
                <input type="text" class="form-control">
              </td>
              <td>
                static element
              </td>
              <td>
                static element
              </td>
            </tr>
          </tbody>
        </table>
      </div>
      <button class="btn btn-link add-row">Add row</button>
    </div>

Sign up to request clarification or add additional context in comments.

6 Comments

it then create a row with 2 column at that situation. But, what if by deleting a column, there will be three column and then, all row is deleted by "delete" icon and then a new row will be created by "add row button"? there is 3 column in the table and a new created row with 2 columns!
Not sure if I understand your concern. Give this a try; it should work fine as it always checks the number of columns before adding a row.
yes, I think you don't get my problem. I have updated my post with some screenshots. Hope, it'll help to understand.
The solution I have provided fixes just that. Can you create a demo using my code so we can do further troubleshooting if necessary?
@user1896653, please check out the demo I have added and let me know what's malfunctioning and I'll take care of it.
|
1

While you've already accepted an answer, I'd argue for a minor re-write; though mostly this is because of the way I prefer to write jQuery. That said, the differences are somewhat minor, mostly revolving around the removal of parents() (returns none, one or many matching elements) and replacing it with closest() (returns no, or one, element(s)), this might be useful should you nest <table> elements in future uses.

// we're binding a lot of different click event-handlers to this element
// there's no point looking it up every time we do so:
var body = $('body');

// binding the click event for the add-row button:
body.on('click', 'button.add-row', function() {
  // getting the relevant <table>:
  var table = $(this).closest('div.table-content'),
    // and the <tbody> and <thead> elements:
    tbody = table.find('tbody'),
    thead = table.find('thead');

  // if the <tbody> has children:
  if (tbody.children().length > 0) {
    // we find the last <tr> child element, clone it, and append
    // it to the <tbody>:
    tbody.find('tr:last-child').clone().appendTo(tbody);
  } else {
    // otherwise, we create the basic/minimum <tr> element:
    var trBasic = $('<tr />', {
        'html': '<td><span class="remove remove-row">x</span></td><td><input type="text" class="form-control" /></td>'
      }),
      // we find the number of columns that should exist, by
      // looking at the last <tr> element of the <thead>,
      // and finding out how many children (<th>) elements it has:
      columns = thead.find('tr:last-child').children().length;

    // a for loop to iterate over the difference between the number
    // of children in the created trBasic element and the current
    // number of child elements of the last <tr> of the <thead>:
    for (var i = 0, stopWhen = columns - trBasic.children.length; i < stopWhen; i++) {
      // creating a <td> element:
      $('<td />', {
        // setting its text:
        'text': 'static element'
          // appending that created <td> to the trBasic:
      }).appendTo(trBasic);
    }
    // appending the trBasic to the <tbody>:
    tbody.append(trBasic);
  }
});

body.on('click', 'span.remove-row', function() {
  $(this).closest('tr').remove();
});

body.on('click', 'span.remove-col', function() {
  // getting the closest <th> ancestor:
  var cell = $(this).closest('th'),
    // getting its index with jQuery's index(), though
    // cell.prop('cellIndex') would also work just as well,
    // and adding 1 (JavaScript is zero-based, CSS is one-based):
    index = cell.index() + 1;
  // finding the closest <table> ancester of the <th> containing the
  // clicked <span>:
  cell.closest('table')
    // finding all <td> and <th> elements:
    .find('th, td')
    // filtering that collection, keeping only those that match
    // the same CSS-based, using :nth-child(), index as the <th>
    // containing the clicked <span>:
    .filter(':nth-child(' + index + ')')
    // removing those cells:
    .remove();
});

body.on('click', 'button.add-col', function() {
  // finding the table (because we're using it to find both
  // the <thead> and <tbody>:
  var table = $(this).closest('div.table-content').find('table'),
    thead = table.find('thead'),
    // finding the last <tr> of the <thead>:
    lastTheadRow = thead.find('tr:last-child'),
    tbody = table.find('tbody');

  // creating a new <th>, setting its innerHTML to the string:
  $('<th>', {
    'html': '<input type="text" class="form-control pull-left" value="Property" /> <span class="pull-left remove remove-col">x</span>'
      // appending that created <th> to the last <tr> of the <thead>:
  }).appendTo(lastTheadRow);
  // creating a <td>:
  $('<td>', {
    // setting its text:
    'text': 'static element'
      // inserting the created <td> after every <td> element
      // that is a :last-child of its parent:
  }).insertAfter('td:last-child');
});
.table-content {
  padding: 20px;
}
.remove {
  margin-left: 10px;
  color: red;
}
.remove:hover {
  cursor: pointer;
}
.form-control {
  width: 90px;
}
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="table-content">
  <button class="btn btn-link add-col">Add Column</button>
  <div class="table-responsive">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th></th>
          <th>Define</th>
          <th>
            <input type="text" class="form-control pull-left" value="Property" /> <span class="pull-left remove remove-col">x</span>

          </th>
          <th>
            <input type="text" class="form-control pull-left" value="Feature" /> <span class="pull-left remove remove-col">x</span>

          </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><span class="remove remove-row">x</span>

          </td>
          <td>
            <input type="text" class="form-control" />
          </td>
          <td>static element</td>
          <td>static element</td>
        </tr>
      </tbody>
    </table>
  </div>
  <button class="btn btn-link add-row">Add row</button>
</div>

JS Fiddle demo - for forking/experimenting.

References:

1 Comment

It's a nice solution too. Thanks for your solution.
0

If you keep a row always stored in a variable and do the same operations to the stored row as to the live table you can use that as your clone when needed to add rows.

Make this copy right away when page loads

 var $storedRow = $('.table-content .table tbody tr:first').clone();

Example for add column

// add column
  $('body').on('click', '.add-col', function() { 
       var cell = '<td>static element</td>'; // cell html
       $(this).parent().find('.table thead tr').append('<th><input type="text" class="form-control pull-left" value=""> <span class="pull-left remove remove-col">x</span></th>');
       $(this).parent().find('.table tbody tr').append(cell);
       $storedRow.append(cell); // append to stored row also
  });

Then to add new row, make a clone of the stored row

$('.table tbody').append($storedRow.clone());

4 Comments

actually, I tried with this code. But, unfortunately, it didn't solve my problem. I am not sure was I failed to use or code properly or any other reason. Can you please, try with my fiddle link and give your working link at here (if you think, this code can solve the problem)? One thing I can say, something might be modified inside // else {} of add row function as it create the issue when columns number is not 4 anymore like initial state
I didn't modify remove column but my code is working for add column and add row jsfiddle.net/9dh5Lvb0/1 Will notice how much simpler add row code is now
I think you don't get my problem. I have updated my post with some screenshots. Hope, it'll help to understand.
you need to modify the stored row also when you remove columns. I didn't do that part ... I only showed you the concept using add row. A demo shouldn't be expected to modify your whole project

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.