Home > Net >  jQuery closest not working on dynamically added table row
jQuery closest not working on dynamically added table row

Time:03-04

I'm adding table rows dynamically with a button "Add New Line". When clicked, this fires off a jQuery event which creates a new table row containing form fields. The form fields allow numeric values only.

When a value gets entered into the field, it calculates VAT and Line Total amounts. These totals then get appended to the closest() table row <tr>.

The code works fine for the first row. As soon as a new row is added, the code stops working and values are calculated for the second row.

I believe this has something to do with the fact the rows (and form fields) are dynamically created by jQuery and injected into the page, so closest() cant't find them.

Whats the solution to this?

Thanks in advance.

Javascript:

$(document).ready(function () {
    var counter = 2;

    $("#addRow").on("click", function () 
    {
        var newRow = $("<tr>");
        var cols = "";

        cols  = '<td>'   counter   '</td>';
        cols  = '<td><textarea  name="description['   counter   ']"></textarea></td>';
        cols  = '<td><input type="text"  name="quantity['   counter   ']" onkeypress="return isNumberKey(event)"></td>';
        cols  = '<td><input type="text"  name="unit_price['   counter   ']" onkeypress="return isNumberKey(event)"></td>';
        cols  = '<td >£0.00</td>';
        cols  = '<td >£0.00</td>';
        cols  = '<td><input type="button"   value="Delete"></td>';
        newRow.append(cols);
        $("table.table-striped").append(newRow);
        counter  ;
    });

    $("table.table-striped").on("click", ".ibtnDel", function (event) 
    {
        $(this).closest("tr").remove();
        counter -= 1
    });

    // Update line vat & subtotal
    $(".line-quantity, .line-price").on("blur", function (event)
    {
        var line_qty = 0;
        var line_price = 0;
        var line_subtotal = '0.00';
        var vat = '0.00';
        var line_total = '0.00';

        line_qty = $(this).closest('#quote_table').find('input.line-quantity').val();
        line_price = $(this).closest('#quote_table').find('input.line-price').val();

        // calculate total line price
        line_subtotal = line_price*line_qty;

        // Calculate VAT
        vat = line_subtotal*1.2-line_subtotal;

        // Line total
        line_total = line_subtotal vat;
        
        // Update view with totals
        $(this).closest('tr').find('td.line_vat').html(vat.toLocaleString('en-US', {style: 'currency',currency: 'GBP'}));
        $(this).closest('tr').find('td.line_total').html(line_total.toLocaleString('en-US', {style: 'currency',currency: 'GBP'}));

        // Calculate totals at end of table

    });
});

function isNumberKey(evt){
    var charCode = (evt.which) ? evt.which : evt.keyCode;
    if (charCode != 46 && charCode > 31 && (charCode < 48 || charCode > 57))
    return false;
    return true;
}

HTML:

<form method="post" action="{{ url('cp/quotes/create') }}">
    {{ csrf_field() }}
    <div >
      @if($contacts->isEmpty())
      <a href="/cp/contacts/create">Create first contact</a>
      @else
      <label for="contact">Contact</label>
      <!-- Get the selected contact and fetch properties -->
      <select  data-live-search="true" id="contact" name="contact" data-live-search-placeholder="Find contact...">
        <option value="">Select a contact...</option>
      @foreach($contacts as $contact)
        <option data-tokens="{{ $contact->id }}" value="{{ $contact->id }}">{{ $contact->contact_name }} ({{ $contact->first_name }} {{ $contact->last_name }})</option>
      @endforeach
      </select>
      <!-- List contact properties -->
      <div  role="group" aria-label="Property" id="property_list"></div>
      @endif
    </div>
    <div >
      <label  for="raised_date">
       Date
      </label>
      <div >
        <div >
          <i ></i>  
        </div>
        <input  id="raised_date" name="raised_date" type="text"/>
      </div>
    </div>
    <div >
      <label  for="raised_date">
       Expiry Date
      </label>
      <div >
        <div >
          <i ></i>  
        </div>
        <input  id="expiry_date" name="expiry_date" type="text"/>
      </div>
    </div>
    <table  id="quote_table">
      <thead>
        <tr>
          <th scope="col">Item #</th>
          <th scope="col">Description</th>
          <th scope="col">Quantity</th>
          <th scope="col">Unit Price</th>
          <th scope="col">VAT</th>
          <th scope="col">Amount</th>
          <th scope="col"></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td><textarea  name="description[]"></textarea></td>
          <td><input  name="quantity[]" type="text" onkeypress="return isNumberKey(event)"></td>
          <td><input  name="unit_price[]" type="text" onkeypress="return isNumberKey(event)"></td>
          <td >£0.00</td>
          <td >£0.00</td>
          <td></td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <td colspan="5" >Subtotal</td>
          <td colspan="2" >£0.00</td>
        </tr>
        <tr>
          <td colspan="5" >Total</td>
          <td colspan="2" >£0.00</td>
        </tr>
      </tfoot>
    </table>
    <a  href="#" id="addRow">Add Line  </a>
    <div >
      <br /><button type="submit" >Save Quote</button>
    </div>
  </form>

Application screenshot:

enter image description here

CodePudding user response:

I tested your code in my own development environment and found that the onblur functionality does not work properly for new dynamic added rows in table. So I searched the web and found this question that could solve your problem. After that I changed your HTML and JavaScript codes as follows:

$(document).ready(function () {

    var counter = 2;

    $("#addRow").on("click", function ()
    {
        var newRow = $("<tr>");
        var cols = "";

        cols  = '<td>'   counter   '</td>';
        cols  = '<td><textarea  name="description['   counter   ']"></textarea></td>';
        cols  = '<td><input type="text"  name="quantity['   counter   ']" onkeypress="return isNumberKey(event)"></td>';
        cols  = '<td><input type="text"  name="unit_price['   counter   ']" onkeypress="return isNumberKey(event)"></td>';
        cols  = '<td >£0.00</td>';
        cols  = '<td >£0.00</td>';
        cols  = '<td><input type="button"   value="Delete"></td>';
        newRow.append(cols);
        $("table.table-striped").append(newRow);
        counter  ;
    });

    $("table.table-striped").on("click", ".ibtnDel", function (event)
    {
        $(this).closest("tr").remove();
        /* -------------------------------------- */
        /* commenting decreasing "counter" for better functionality after deleting rows */
        /* -------------------------------------- */
        // counter -= 1
    });

    /* -------------------------------------- */
    /* converting line_qty and line_price to arrays to not have conflict between rows */
    /* -------------------------------------- */
    var line_qty = [0];
    var line_price = [0];

    /* -------------------------------------- */
    /* change "blur" event call according to other stack-overflow question for handling dynamically created DOM */
    /* -------------------------------------- */
    $("#quote_table").on("blur", ".line-quantity, .line-price", function () {
        console.log($(this));

        var line_subtotal = '0.00';
        var vat = '0.00';
        var line_total = '0.00';
        /* -------------------------------------- */
        /* using name "attr" to understand which index of "line_qty" and "line_price" arrays must be changed */
        /* -------------------------------------- */
        let indexBrackets = $(this).attr("name").indexOf("[")   1;
        let rowNum = $(this).attr("name").substr(indexBrackets, 1)

        /* -------------------------------------- */
        /* using "jQuery hasClass()" method to find which input in each row has blur event */
        /* -------------------------------------- */
        if ($(this).hasClass("line-quantity")) {
                line_qty[rowNum-1] = $(this).val();
        }

        console.log(line_qty);

        if ($(this).hasClass("line-price")) {
            line_price[rowNum-1] = $(this).val();
        }

        console.log(line_price);


        // calculate total line price
        /* -------------------------------------- */
        /* using arrays to calculate final values */
        /* -------------------------------------- */
        line_subtotal = line_price[rowNum-1]*line_qty[rowNum-1];

        // Calculate VAT
        vat = line_subtotal*1.2-line_subtotal;

        // Line total
        line_total = line_subtotal vat;
        console.log(line_total);

        // Update view with totals
        $(this).closest('tr').find('td.line_vat').html(vat.toLocaleString('en-US', {style: 'currency',currency: 'GBP'}));
        $(this).closest('tr').find('td.line_total').html(line_total.toLocaleString('en-US', {style: 'currency',currency: 'GBP'}));

        // Calculate totals at end of table
    });

});

function isNumberKey(evt){
    var charCode = (evt.which) ? evt.which : evt.keyCode;
    if (charCode != 46 && charCode > 31 && (charCode < 48 || charCode > 57))
        return false;
    return true;
}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

<form method="post" action="{{ url('cp/quotes/create') }}">
    {{ csrf_field() }}
    <div >
        @if($contacts->isEmpty())
        <a href="/cp/contacts/create">Create first contact</a>
        @else
        <label for="contact">Contact</label>
        <!-- Get the selected contact and fetch properties -->
        <select  data-live-search="true" id="contact" name="contact" data-live-search-placeholder="Find contact...">
            <option value="">Select a contact...</option>
            @foreach($contacts as $contact)
            <option data-tokens="{{ $contact->id }}" value="{{ $contact->id }}">{{ $contact->contact_name }} ({{ $contact->first_name }} {{ $contact->last_name }})</option>
            @endforeach
        </select>
        <!-- List contact properties -->
        <div  role="group" aria-label="Property" id="property_list"></div>
        @endif
    </div>
    <div >
        <label  for="raised_date">
            Date
        </label>
        <div >
            <div >
                <i ></i>
            </div>
            <input  id="raised_date" name="raised_date" type="text"/>
        </div>
    </div>
    <div >
        <label  for="raised_date">
            Expiry Date
        </label>
        <div >
            <div >
                <i ></i>
            </div>
            <input  id="expiry_date" name="expiry_date" type="text"/>
        </div>
    </div>
    <table  id="quote_table">
        <thead>
        <tr>
            <th scope="col">Item #</th>
            <th scope="col">Description</th>
            <th scope="col">Quantity</th>
            <th scope="col">Unit Price</th>
            <th scope="col">VAT</th>
            <th scope="col">Amount</th>
            <th scope="col"></th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td>1</td>
            <td><textarea  name="description[]"></textarea></td>
            <!-- ///////////////////////////// -->
            <!-- change "quantity[]" to quantity[1] and "unit_price[]" to unit_price[1] to use it in js codes -->
            <!-- ///////////////////////////// -->
            <td><input  name="quantity[1]" type="text" onkeypress="return isNumberKey(event)"></td>
            <td><input  name="unit_price[1]" type="text" onkeypress="return isNumberKey(event)"></td>
            <td >£0.00</td>
            <td >£0.00</td>
            <td></td>
        </tr>
        </tbody>
        <tfoot>
        <tr>
            <td colspan="5" >Subtotal</td>
            <td colspan="2" >£0.00</td>
        </tr>
        <tr>
            <td colspan="5" >Total</td>
            <td colspan="2" >£0.00</td>
        </tr>
        </tfoot>
    </table>
    <a  href="#" id="addRow">Add Line  </a>
    <div >
        <br /><button type="submit" >Save Quote</button>
    </div>
</form>


</body>
</html>

I tried to comment changes in codes, but the main changes are using new "blur" calling that mentioned in that question and also separating the event handling for each row by using arrays instead of numbers.

  • Related