JQuery / table total issue

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
Here's a fiddle: https://jsfiddle.net/382Lmsbb/

I'm learning jQuery but having grief getting footerCallback firing to update a table total.
In the code, basically everything fires OK:


  • Enter a value in Col2 and Col3 to see the product in Col4

  • Click 'New Row'

  • Click 'Delete'

What I can't get working is the footerCallback - I'm not even going to do the summing at this point, all I want is for it to say 'Working!' in column 4 in the footer (to the right of the word Total) but that piece is not firing. Clearly something is amiss....

Code:
$('#blahtable').dataTable({
        "footerCallback": function(row, data, start, end, display) {
          $(api.column(4).footer()).html('Working');
        }
});
Any ideas?
 

kanzen

Senior Member
Joined
Jul 16, 2014
Messages
633
Don't forget to declare the api variable: var api = this.api();

Also, what are you seeing in the browser console?
 

_kabal_

Executive Member
Joined
Oct 24, 2005
Messages
5,922
Have you put a console.log in the footer callback to see if it really isn't firing?
 

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
Don't forget to declare the api variable: var api = this.api();

Also, what are you seeing in the browser console?
There is an odd message but I'm not sure it's relevant... here:
https://dl.dropboxusercontent.com/u/101926939/test/test.html
Code:
Uncaught TypeError: Cannot read property 'length' of undefined
jquery.dataTables.min.js:23
Going to investigate whether the issue is with the callback.


Have you put a console.log in the footer callback to see if it really isn't firing?
I have, but nothing, see the link above - console.log runs for all functions but not that one.

This stuff drives one :mad: :p
 
Last edited:

kanzen

Senior Member
Joined
Jul 16, 2014
Messages
633
Usually if it doesn't pick up the target element it fires off that exception. Just to confirm, are you only trying to make it work in JSFiddle? I had the console let me know Datatables is undefined.
 

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
Usually if it doesn't pick up the target element it fires off that exception. Just to confirm, are you only trying to make it work in JSFiddle? I had the console let me know Datatables is undefined.

Yes, in fiddle, those external references are defined separately. Try this:
https://dl.dropboxusercontent.com/u/101926939/test/test.html

Here's the markup and js
Code:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.css">
    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.css">
    <link href="./ie10-viewport-bug-workaround.css" rel="stylesheet">
  </head>

<body>

    <div class="container theme-showcase" role="main">

      <table id="blahtable" class="display" width="100%" cellspacing="0">
        <thead>
            <tr>
                <th class="text-center">Col1</th>
                <th class="text-center">Col2</th>
                <th class="text-center">Col3</th>
                <th class="text-center">Col4</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <th></th>
                <th></th>
                <th class="text-right">Total:</th>
                <th></th>
            </tr>
        </tfoot>
        <tbody>
            <tr id='item0'>
                <td><input type="text" class="form-control col1"></td>
                <td><input type="text" class="form-control col2"></td>
                <td><input type="text" class="form-control col3"></td>
                <td class="col4"></td>
            </tr>
            <tr id='item1'></tr>
        </tbody>
      </table>
        <div class='btn-group'><a id='add_row' class='btn btn-default'>New Row</a></div>
        <div class='btn-group'><a id='delete_row' class='btn btn-default'>Delete</a></div>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.js"></script>

    <script> 
    $(document).ready(function()
    {

   		$('input.col2').on('change', function(){
          console.log('Col2 change');
        	var col2val = $(this).val(); //col2val = the value after change
          var col3val = $(this).closest('tr').find('input.col3').val();
          $(this).closest('tr').find('.col4').html(parseFloat(col2val)*parseFloat(col3val));
        });

        $('input.col3').on('change', function(){
          console.log('Col3 change');
        	var col3val = $(this).val();
          var col2val = $(this).closest('tr').find('input.col2').val();
          $(this).closest('tr').find('.col4').html(parseFloat(col2val)*parseFloat(col3val));
        });

        var i=1;

        $("#add_row").click(function(){ 
          console.log('AddRow');
          $('#item'+i).html("<td><input type='text' class='form-control name'></td><td><input type='text' class='form-control col2'></td><td><input type='text' class='form-control col3'></td><td class='col4'></td>");
          $('#blahtable').append('<tr id="item' + (i+1) + '"></tr>');
          $('#item'+i+' input.col2').on('change', function()
          {
            console.log('Col2 change');
            var col2val = $(this).val();
            var col3val = $(this).closest('tr').find('input.col3').val();
            $(this).closest('tr').find('.col4').html(parseFloat(col2val)*parseFloat(col3val));
          });
          $('#item'+i+' input.col3').on('change', function()
          {
            console.log('Col3 change');
            var col3val = $(this).val();
            var col2val = $(this).closest('tr').find('input.col2').val();
            $(this).closest('tr').find('.col4').html(parseFloat(col2val)*parseFloat(col3val));
          });
          i++; 
        });

        $("#delete_row").click(function(){
         console.log('Delete Row');
         if(i>1)
          {
            $("#item"+(i-1)).html('');
            i--;
          }
        });

        $('#blahtable').dataTable({
          "footerCallback": function ( row, data, start, end, display ) 
          {
            console.log ("Footer Callback");
            $( api.column( 3 ).footer() ).html('Working');
          }
        });

    });

</script>

</body>

</html>
*Edit* - updated to use CDN for external JS and CSS and switched to non-min versions for troubleshooting.
 
Last edited:

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
OK... the error I'm getting is definitely relevant...
"jquery.dataTables.js:3054 Uncaught TypeError: Cannot read property 'length' of undefined"

It only appears with the callback in there
Code:
        $('#blahtable').dataTable({
          "footerCallback": function ( row, data, start, end, display ) 
          {
            debugger;
            console.log ("Footer Callback");
            $( api.column( 3 ).footer() ).html('Working');
          }
        });
It's related to this part of jquery.dataTables.js
Code:
			// Existing row object passed in
			tds = row.anCells;
	
			for ( var j=0, jen=[B]tds.length[/B] ; j<jen ; j++ ) {
				cellProcess( tds[j] );
			}
 
Last edited:

XennoX

Expert Member
Joined
Nov 15, 2007
Messages
2,205

I've looked at the DataTable docs, and some of the examples. Something I noticed that they're doing is:

Code:
var dt = $(table-selector);
var api = dt.api();
...
...
api.$(selector).some_property_or_method...
 

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
I've looked at the DataTable docs, and some of the examples. Something I noticed that they're doing is:

Code:
var dt = $(table-selector);
var api = dt.api();
...
...
api.$(selector).some_property_or_method...

Thanks, I'll get back to you on that, going to try later this morning!
 

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
OK, I think it's been resolved it, sort of...
The <tr id='item1'></tr> on the data side of the table is really just a stub with no other fields - designed to assist with the 'add row' function. If I remove that it seems to work. I'll have to figure out how to get the add row function working now. I'll probably just duplicate the function if it's the first add.

OK, finally resolved by removing the <tr id=item1></tr> and then adding

Code:
          if (i==1) {
            $('#devices').append('<tr id="item' + (i) + '"></tr>');
          }

to the beginning of the #add_row function.
 
Last edited:

halfmoonforever

Expert Member
Joined
Feb 1, 2016
Messages
1,196
dataTables is a jQuery plugin/addon. If you haven't already, I suggest learning JavaScript first, then move on to a framework.

You're also calling api.column within the footerCallback, I believe it should be row.column(3).footer() instead. When you get an unidentified error like that, it usually means the object you're referencing is wrong. context matters.
 

Willie Trombone

Honorary Master
Joined
Jul 18, 2008
Messages
60,038
^^Thanks, will look into it.
I'm also switching to the .add method instead of the .append method to insert rows - probably easier to use that and json rather than setting a row's .html property.
 

John_Phoenix

Expert Member
Joined
Jul 8, 2017
Messages
1,087
Maybe this will help...

Sabre,
Give this a smash, I added a raftload of comments :)

Code:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
  <style>
    body{margin:0;font-family:sans-serif;color:#333;}
    i{pointer-events:none;}
    #spreadsheet{
      max-width:750px;
      margin-left:auto;
      margin-right:auto;
      table-layout:fixed;
    }
    #spreadsheet td{
      text-align:center;
    }
    #spreadsheet tr:nth-of-type(1) th{
      text-align:left;
      padding:1em;
    }
    #spreadsheet tr:nth-of-type(1) th button i{
      margin-right:1em;
    }
    #spreadsheet tr:nth-of-type(2) th{
      padding:0.25em;
      background-color:#222;
      color:#ccc;
    }
    #spreadsheet tfoot tr:nth-of-type(1) td{
      text-align:left;
      padding:0.5em;
      font-weight:700;
    }
    #spreadsheet input[type="number"]{
      width:100%;
      padding:0.15em;
      font-size:100%;
      box-sizing:border-box;
      outline:none;
      border:none;
    }
    #spreadsheet tr td{
      border:1px solid #ccc;
    }
  </style>
  <table width="100%" id="spreadsheet" border="0" cellpadding="0" cellspacing="0">
    <thead>
      <tr>
        <th colspan="5"><button id="add_row"><i class="fa fa-plus-square-o" aria-hidden="true"></i><span>Add New Row</span></button></th>
      </tr>
      <tr>
        <th>A</th>
        <th>B</th>
        <th>C</th>
        <th>A+B+C</th>
        <th>Controls</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="number"></td>
        <td><input type="number"></td>
        <td><input type="number"></td>
        <td data-type="result"></td>
        <td class="row-control"><button class="remove_row"><i class="fa fa-trash" aria-hidden="true"></i> Delete Row</button></td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <td colspan="5">Column Totals</td>
      </tr>
      <tr>
        <td>0</td>
        <td>0</td>
        <td>0</td>
        <td>0</td>
        <td></td>
      </tr>
    </tfoot>
  </table>
  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=" crossorigin="anonymous"></script>
  <script>
    'use strict';
    //always wrap your init code in a "ready" statement, it makes sure the code fires after your libs/page has loaded.
    
    /* 
      I prefer the vanilla javascript alternative...
      Why, cause with the $ symbol you are relying on jQuery to be loaded before the ready callback is called.
      With the vanilla javascript, the load event you call is already in the browser core.

      //vanilla JS
      window.addEventListener('load',function(){
        //init code goes here...
      });

      //jQuery
      jQuery(document).ready(function($){
        //init code goes here...
      });

      I'll be using jQuery, as that is what you are intent on learning.

      Please note that this is "Once off code", and is not what production looks like, this is just the basics.
      How long does this kind of things take, well this took about 30 minutes to code up...
      In production I would spend about 1h dependant on the functional requirements and styling.
    */

    jQuery(document).ready(function($){
      //here we create a cached string template for when the user inserts a new row.
      var rowTemplate = $('#spreadsheet tbody').html();
      
      //Lets add a click listener to the "Add Row Button"
      $('#add_row').on('click',function(){
        //Here we select the tbody element and append the row template underneath the current ones.
        $('#spreadsheet tbody').append($(rowTemplate));
      });

      //Lets add an Event Listener for when the user removes a row.
      //Handling multiple targets with a single listener is called "event delegation".
      $('#spreadsheet').on('click',function(e){
        //this code handles the remove button
        if($(e.target).hasClass('remove_row') === true){
          $(e.target).parentsUntil('tr').parent().remove();
        }
      });

      //here we use event bubbling to catch the event that the user performs when changing a number.
      //We use a single event listener for performance reasons, and its just easier.
      //Why not listen to EVERY new input? Imagine the user adds 2000 rows * 3 inputs! thats 6000 distinct events...
      $('#spreadsheet tbody').on('change',function(e){
        calcTable(e.target);
      });
    });

    //where the magic happens aka where the table calc gets done.
    function calcTable(target){
      var totals = {
        row:0
      };
      //we do the row calculation first.
      //find the input columns so we can add the values together.
      var row = $(target).parentsUntil('tr').parent();

      $(row).find('td').each(function(i,el){
        if($(el).children().is('input[type="number"]')){
          totals.row += parseInt(($(el).children().val() || 0),10);
        };
      });

      $(row).find('td[data-type="result"]').text(totals.row);

      //now we do the column calculation
      $('#spreadsheet tbody tr').each(function(tr_i,tr_el){
        $(tr_el).find('td').each(function(td_i,td_el){
          if($(td_el).children().is('input[type="number"]')){
            totals['col'+td_i] = totals['col'+td_i] || 0;
            totals['col'+td_i] += parseInt(($(td_el).children().val() || 0),10);
          };
          if($(td_el).is('td[data-type="result"]')){
            totals['col'+td_i] = totals['col'+td_i] || 0;
            totals['col'+td_i] += parseInt(($(td_el).text() || 0),10);
          };
        });
      });

      //now we map the totals to the Column Totals in the table footer
      $('#spreadsheet tfoot tr:nth-of-type(2) td').each(function(i,el){
        if(totals['col'+i] !== undefined){
          $(el).text(totals['col'+i]);
        }
      });
    };

  </script>
</body>
</html>
 
Top