javascriptdatatablesasp.net-core-mvcbootstrap-5sparklines

Bootstrap DataTable Sparkline not working if Table set to Responsive


I have a Bootstrap DataTable with a sparkline in the last column, here is the full js:

$(document).ready(function() {
  var groupColumn = 0;

  let table = $('#example').DataTable({
    //responsive: true,
    autoWidth: true,
    processing: true,
    ordering: true,
    scrollY: '50vh',
    scrollCollapse: true,
    paging: false,
    searching: true,

    ajax: {
      url: "api/ApartmentsAvailables",
      type: "GET",
      contentType: "application/json; charset=utf-8",
      dataType: "json",
    },
    columnDefs: [{
        visible: false,
        targets: groupColumn
      },
      {
        targets: 7,
        render: DataTable.render.datetime('YYYY-MM-DDT00:00:00', 'MMMM D, YYYY', 'en'),
      },
      {
        responsivePriority: 1,
        targets: 0
      },
    ],
    order: [
      [groupColumn, 'asc']
    ],

    drawCallback: function(settings) {
      $('.sparkline')
        .map(function() {
          return $('canvas', this).length ? null : this;
        })
        .sparkline('html', {
          type: 'line',
          width: '250px'
        })

      var api = this.api();
      var rows = api.rows({
        page: 'current'
      }).nodes();
      var last = null;

      api
        .column(groupColumn, {
          page: 'current'
        })
        .data()
        .each(function(group, i) {
          if (last !== group) {
            $(rows)
              .eq(i)
              .before('<tr class="group" style="background-color:DarkGray; text-align:center;font-weight: bold; color:white;"><td  colspan="8">' + group + '</td></tr>');

            last = group;
          }
        })
    },
    columns: [

      {
        data: "building"
      },
      {
        data: "floor_Plan"
      },
      {
        data: "apt_Number"
      },
      {
        data: "rent"
      },
      {
        data: "bedrooms"
      },
      {
        data: "bathrooms"
      },
      {
        data: "sqft"
      },
      {
        data: "available_Date"
      },
      {
        data: 'prices',
        render: function(data, type, row, meta) {

          return type === 'display' ?
            '<span class="sparkline">' + data.toString() + '</span>' :
            data;
        }
      },
    ]
  });
  new $.fn.dataTable.FixedHeader(table);
  // Order by the grouping
  $('#example tbody').on('click', 'tr.group', function() {
    var currentOrder = table.order()[0];
    if (currentOrder[0] === groupColumn && currentOrder[1] === 'asc') {
      table.order([groupColumn, 'desc']).draw();
    } else {
      table.order([groupColumn, 'asc']).draw();
    }
  });
});

The problem occurs when I enable responsive: true, the sparkline column becomes hidden and when I click to expand the row to show the hidden columns it shows the whole array of Value and not the sparkline.

I guess that the

drawCallback: function (settings) {
            $('.sparkline')
                .map(function () {
                    return $('canvas', this).length ? null : this;
                })
                .sparkline('html', {
                    type: 'line',
                    width: '250px'
                })

is not able to be applied to a column that is hidden.

Without the responsive option the HTML generated for the td is:

<td>
    <span class="sparkline">
        <canvas style="display: inline-block; width: 250px; height: 21px; vertical-align: top;"
                width="250"
                height="21"/>
    </span>
</td>

With the responsive set to true:

<td style="display: none;"
    class="dtr-hidden">
    <span class="sparkline">3446,3446,3416,3416,3416,3546,3546,3546,3546,3546,3546,3561,3556,3551,3396,3396,3396,3346,3306,3306,3306</span>
</td>

I presume that I should somehow capture the mouse click on the expand icon and then re-inject the canvas but I don't know how to do that.


Solution

  • There is an event you can use for that, provided as part of the DataTables Responsive extension:

    responsive-display

    This event fires whenever...

    The details for a row have been displayed, updated or hidden.

    So, for example, you can add the following to your script, and then place your standard sparklines logic in the event's function:

    table.on( 'responsive-display', function ( e, datatable, row, showHide, update )  {
        $('.sparkline')
          .map(function() {
            return $('canvas', this).length ? null : this;
          })
          .sparkline('html', {
            type: 'line',
            width: '250px'
          });
      } );
    

    This will re-build your sparkline from the raw data.


    You can see the official documentation for a list of all the events, API functions, and options available in the Responsive extension.


    Update

    "not able to make it work..."

    Here is my runnable demo in case it helps:

    $(document).ready(function() {
        var stock_data = [
            {
                "name": "ACME Gadgets",
                "symbol": "AGDTS",
                "last": "2.57, 2.54, 2.54, 2.56, 2.57, 2.58, 2.59"
            },
            {
                "name": "Spry Media Productions",
                "symbol": "SPMP",
                "last": "1.12, 1.11, 1.08, 1.08, 1.09, 1.11, 1.08"
            },
            {
                "name": "Widget Emporium",
                "symbol": "WDEMP",
                "last": "3.40, 3.39, 3.46, 3.51, 3.50, 3.48, 3.49"
            },
            {
                "name": "Sole Goodman",
                "symbol": "SGMAN",
                "last": "16.20, 16.40, 16.36, 16.35, 16.61, 16.46, 16.19"
            },
            {
                "name": "Stanler Bits and Bobs",
                "symbol": "SBIBO",
                "last": "82.51, 83.47, 83.40, 83.68, 83.81, 83.29, 83.72"
            }
        ];
     
        let table = $('#example').DataTable({
            
            responsive: true,
        
            ajax: function(dataSent, callback, settings) {
                let data = this.api().ajax.json();
                if(data == undefined) {
                    data = stock_data;
                    for(i = 0; i < data.length; i++) {
                        data[i].last = data[i].last.split(",").map(element => {
                            return Number(element);
                        });
                    }
                } else {
                    data = data.data;
                    for(i = 0; i < data.length; i++) {
                        data[i].last.push(data[i].last.shift())
                    }
                }
                callback({data: data});
            },
            paging: false,
            initComplete: function() {
                let api = this.api();
                //setInterval(function() {
                //    api.ajax.reload();
                //}, 5000);
            },
            drawCallback: function() {
                $('.sparkline')
                    .map(function() {
                        return $('canvas', this).length ? null : this;
                    })
                    .sparkline('html', {
                        type: 'line',
                        width: '250px'
                    })
            },
            columns: [
                {
                    data: 'name'
                },
                {
                    data: 'symbol'
                },
                {
                    data: null,
                    render: function(data, type, row, meta) {
                        return row.last[row.last.length - 1].toFixed(2);
                    }
                },
                {
                    data: null,
                    render: function(data, type, row, meta) {
                        var val = (row.last[row.last.length - 1] - row.last[row.last.length - 2]).toFixed(2);
                        var colour = val < 0 ? 'red' : 'green'
                        return type === 'display' ?
                            '<span style="color:' + colour + '">' + val + '</span>' :
                            val;
                    }
                },
                {
                    data: 'last',
                    render: function(data, type, row, meta) {
                        return type === 'display' ?
                            '<span class="sparkline">' + data.toString() + '</span>' :
                            data;
                    }
                }
            ]
        });
        
        
        table.on( 'responsive-display', function ( e, datatable, row, showHide, update ) {
          $('.sparkline')
            .map(function() {
              return $('canvas', this).length ? null : this;
            })
            .sparkline('html', {
              type: 'line',
              width: '250px'
            });
        } );
        
        
    });
    <!doctype html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>Demo</title>  
      
      <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
      <script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-sparklines/2.1.2/jquery.sparkline.min.js"></script>
      <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.min.css">
      <link rel="stylesheet" type="text/css" href="https://datatables.net/media/css/site-examples.css">
      
        <!-- responsive plug-in -->
      <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.6/css/responsive.dataTables.css"/>
      <script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.6/js/dataTables.responsive.js"></script>
    
    
    </head>
    
    <body>
    
    <div style="margin: 20px;">
    
        <table id="example" class="display nowrap" style="width:100%">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Symbol</th>
                    <th>Price</th>
                    <th>Difference</th>
                    <th>Last</th>
                </tr>
            </thead>
            <tfoot>
                <tr>
                    <th>Name</th>
                    <th>Symbol</th>
                    <th>Price</th>
                    <th>Difference</th>
                    <th>Last</th>
                </tr>
            </tfoot>
        </table>
    
    </div>
    
    
    
    </body>
    </html>

    I don't have the + and - icons, so you just have to click on the cell instead, to show/hide data.

    I also commented out the setInterval function, as that causes the display to be reset every 5 seconds.