1

I have code that adds a row dynamically and within are add (+) and delete (-) buttons for the add row and delete row functions. Adding the row works with a new id for each added row, however I'm having issues passing the dynamic row id to the generated row button for deleting the row. Any help is appreciated. My code follows and thanks!

var i = 0;
var original = document.getElementById('addRow' + i);

function addRow() {
  var clone = original.cloneNode(true);
  clone.id = "addRow" + ++i;
  original.parentNode.appendChild(clone);
}

function deleteRow() {
  var original = document.getElementById();
  alert(original.id);
}
<div class="row" id="addRow0">
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="scienceField" name="scienceField" onchange="toggleInput()" required>
      <option value="">Field of Science...</option>
    </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="investigationSubject" name="investigationSubject" onchange="toggleInput()" required>
      <option value="">Subject of Investigation...</option>
    </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="knowledgeArea" name="knowledgeArea" required>
      <option value="">Knowledge Areas...</option>
    </select>
  </div>
  <div class="col-md-1 mb-3">
    <input type="text" class="form-control" id="classifiedActivitiesPercentage" name="classifiedActivitiesPercentage" placeholder="%" required>
    <div class="invalid-feedback">Please enter valid percent value.</div>
  </div>
  <button onclick="deleteRow()" class="btn btn-primary btn-md mb-3 mr-3">-</button>
  <button onclick="addRow()" class="btn btn-primary btn-md mb-3">+</button>
</div>
Alessio Cantarella
  • 5,077
  • 3
  • 27
  • 34
user670522
  • 11
  • 2
  • 1
    please add a codepen link of working code to your question – SayJeyHi Aug 05 '18 at 13:43
  • 2
    @Jafarrezaei Please don't ask for Codepen, Fiddles or any other 3rd party links to code. The code can just be placed, right here, in a code snippet. – Scott Marcus Aug 05 '18 at 13:48
  • @RamblinRose Please don't ask for Codepen, Fiddles or any other 3rd party links to code. The code can just be placed, right here, in a code snippet. – Scott Marcus Aug 05 '18 at 13:49

2 Answers2

0

While you could use id`s to make this work, it would require more code and logic than necessary and ultimately is a brittle solution that won't scale well.

The simplest approach for adding is to just clone the row that contains the add button that was just clicked. And for deleting, just delete the row that contains the delete button that was just clicked. This is accomplished with the element.closest() method.

Additionally, you should not be setting up your event handlers with HTML event attributes (onclick, etc.). That is a 25+ year old technique that is so pervasive, it just won't die like it should. There are many reasons not to set up events this way. Don't want to take my word for it? Read this then. Instead, do all your event handling in JavaScript. And, in your case, since you'll be adding new buttons dynamically, you'll need those buttons to be bound to event handlers. The best way to do that is with event delegation.

Lastly, we can use just one event handling function to deal with both the add and delete operations. We only need to know which button what pressed.

In the HTML below, notice that an add class has been added to your + button and a delete class has been added to your - button. Also, note that the onclick event attributes and the row ids have been removed completely.

// Set up delegated event handling so that all buttons will be wired
// to appropriate handlers
document.addEventListener("click", function(evt){
  let original = evt.target.closest(".row");  // Find the nearest ancestor that has the row class

  // Check the element that originally triggered the event
  // and see if "add" or "delete" is in its list of classes.
  if(evt.target.classList.contains("add") || evt.target.classList.contains("delete")){
    // Call a single event handler and pass it the event, the originating event element
    // and the content of that element
    addRemove(evt.target, original);  
  }  
});

function addRemove(target, original) {
  // If the originating event element contains the "add" class
  if(target.classList.contains("add")){
    // Clone the original row and append the clone to the document 
    original.parentNode.appendChild(original.cloneNode(true));
  } else {
    // It must be the delete button that was clicked
    original.remove();   // Remove the original row
  }
}
<div class="row">
  <div class="col-md-3 mb-3">
     <select class="custom-select d-block w-100" style="font-size: 1rem" id="scienceField" name="scienceField" onchange="toggleInput()" required>
       <option value="">Field of Science...</option>
     </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="investigationSubject" name="investigationSubject" onchange="toggleInput()" required>
      <option value="">Subject of Investigation...</option>
    </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="knowledgeArea" name="knowledgeArea" required>
      <option value="">Knowledge Areas...</option>
    </select>
  </div>
  <div class="col-md-1 mb-3">
    <input type="text" class="form-control" id="classifiedActivitiesPercentage" name="classifiedActivitiesPercentage" placeholder="%" required>
      <div class="invalid-feedback">Please enter valid percent value.</div>
  </div>
  <button class="btn btn-primary btn-md mb-3 mr-3 delete">-</button>
  <button class="btn btn-primary btn-md mb-3 add">+</button>
</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • There’s a syntax error in the code (`;` instead of `{` after the `if`), but apart from that, thank you for pointing out that inline handlers should go the way of the dodo. I would add a note about the fact that using `closest` raises your minimum browser version to Edge 15+, though. – Aankhen Aug 05 '18 at 18:38
  • @Aankhen Syntax error corrected :) As for `.closest()`. I've personally come to the point of not caring about IE compatibility anymore. However, your point is taken and the same approach could still be easily accomplished with a manual ancestor search for the element that contains the class row. – Scott Marcus Aug 05 '18 at 20:34
  • I understand, and I generally feel the same way, but I felt it was important to point out because we don’t know what the OP’s intended minimum version is. – Aankhen Aug 06 '18 at 10:39
0

You could also represent your row with a JavaScript object, then your "minus" button could refer to the HTML instantiated in the object, and you get rid of IDs.

Please see the snippet below:

var template = document.getElementById('rowTemplate');

function Row() {
  this.node = document.importNode(template.content, true);
  this.content = Array.prototype.slice.call(this.node.childNodes);
  
  this.minusButton = document.createElement("button");
  this.minusButton.innerHTML = '-';
  this.minusButton.className = 'btn btn-primary btn-md mb-3 mr-3';
  this.minusButton.addEventListener('click', () => {
    this.delete();
  });
  
  this.content.push(this.minusButton);
   
  this.plusButton = document.createElement("button");
  this.plusButton.innerHTML = '+';
  this.plusButton.className = 'btn btn-primary btn-md mb-3 mr-3';
  this.plusButton.addEventListener('click', () => {
    new Row();
  });
  
  this.content.push(this.plusButton);
  
  this.content.forEach(x => template.parentNode.appendChild(x));
}

Row.prototype.delete = function() {
  this.content.forEach(x => x.parentNode.removeChild(x));
}

new Row();
<template id="rowTemplate">
<div class="row">
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="scienceField" name="scienceField" onchange="toggleInput()" required>
      <option value="">Field of Science...</option>
    </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="investigationSubject" name="investigationSubject" onchange="toggleInput()" required>
      <option value="">Subject of Investigation...</option>
    </select>
  </div>
  <div class="col-md-3 mb-3">
    <select class="custom-select d-block w-100" style="font-size: 1rem" id="knowledgeArea" name="knowledgeArea" required>
      <option value="">Knowledge Areas...</option>
    </select>
  </div>
  <div class="col-md-1 mb-3">
    <input type="text" class="form-control" id="classifiedActivitiesPercentage" name="classifiedActivitiesPercentage" placeholder="%" required>
    <div class="invalid-feedback">Please enter valid percent value.</div>
  </div>
</div>
</template>
Guerric P
  • 30,447
  • 6
  • 48
  • 86