Internal Site UI
Internal Site UI

DataTables - Subtitle

DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, and will add advanced interaction controls to any HTML table. For more info see the official site.

Sub Datatable Example

This example shows the DataTables table with an expandable row. The sub-rows data are generated by using DataTable's template methods with simple JS data objects that can either be generated locally or retrieved from an API endpoint.
Order IDCreatedCustomerTotalProfitStatus
Product name
Product description
Cost
1
Qty
1
Total
name
On hand
32
#XGT-3465 July 2022, 7:05 amEmma Smith$630.00$86.70Pending
#YHD-0475 July 2022, 6:13 amMelody Macy$25.00$4.20Confirmed
#SRR-6785 July 2022, 3:05 amMax Smith$1,630.00$203.90Pending
#PXF-5344 July 2022, 7:05 amSean Bean$119.00$12.00Shipped
#XGD-2493 July 2022, 7:05 amBrian Cox$660.00$52.26Shipped
#SKP-0352 July 2022, 7:05 amBrian Cox$290.00$29.00Rejected
<table class="table align-middle table-row-dashed fs-6 gy-4" id="kt_docs_datatable_subtable">
 <!--begin::Table head-->
 <thead>
  <!--begin::Table row-->
  <tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
   <th class="min-w-100px">Order ID</th>
   <th class="text-end min-w-100px">Created</th>
   <th class="text-end min-w-150px">Customer</th>
   <th class="text-end min-w-100px">Total</th>
   <th class="text-end min-w-100px">Profit</th>
   <th class="text-end min-w-50px">Status</th>
   <th class="text-end"></th>
  </tr>
  <!--end::Table row-->
 </thead>
 <!--end::Table head-->


 <!--begin::Table body-->
 <tbody class="fw-bold text-gray-600">
  <!--begin::SubTable template-->
  <tr data-kt-docs-datatable-subtable="subtable_template" class="d-none">
   <td colspan="2">
    <div class="d-flex align-items-center gap-3">
     <a href="#" class="symbol symbol-50px bg-secondary bg-opacity-25 rounded">
      <img src="/assets/media/stock/ecommerce/" alt="" data-kt-docs-datatable-subtable="template_image" />
     </a>
     <div class="d-flex flex-column text-muted">
      <a href="#" class="text-dark text-hover-primary fw-bold" data-kt-docs-datatable-subtable="template_name">Product name</a>
      <div class="fs-7" data-kt-docs-datatable-subtable="template_description">Product description</div>
     </div>
    </div>
   </td>
   <td class="text-end">
    <div class="text-dark fs-7">Cost</div>
    <div class="text-muted fs-7 fw-bold" data-kt-docs-datatable-subtable="template_cost">1</div>
   </td>
   <td class="text-end">
    <div class="text-dark fs-7">Qty</div>
    <div class="text-muted fs-7 fw-bold" data-kt-docs-datatable-subtable="template_qty">1</div>
   </td>
   <td class="text-end">
    <div class="text-dark fs-7">Total</div>
    <div class="text-muted fs-7 fw-bold" data-kt-docs-datatable-subtable="template_total">name</div>
   </td>
   <td class="text-end">
    <div class="text-dark fs-7 me-3">On hand</div>
    <div class="text-muted fs-7 fw-bold" data-kt-docs-datatable-subtable="template_stock">32</div>
   </td>
   <td></td>
  </tr>
  <!--end::SubTable template-->

  <tr>
   <!--begin::Order ID-->
   <td>
    <a href="#" class="text-dark text-hover-primary">#XGT-346</a>
   </td>
   <!--end::Order ID-->

   <!--begin::Crated date-->
   <td class="text-end">
    10 Nov 2021, 10:30 am
   </td>
   <!--end::Created date-->

   <!--begin::Customer-->
   <td class="text-end">
    <a href="" class="text-dark text-hover-primary">Emma Smith</a>
   </td>
   <!--end::Customer-->

   <!--begin::Total-->
   <td class="text-end">
    $630.00
   </td>
   <!--end::Total-->

   <!--begin::Profit-->
   <td class="text-end">
    <span class="text-dark fw-bold">$86.70</span>
   </td>
   <!--end::Profit-->

   <!--begin::Status-->
   <td class="text-end">
    <span class="badge py-3 px-4 fs-7 badge-primary">Confirmed</span>
   </td>
   <!--end::Status-->

   <!--begin::Actions-->
   <td class="text-end">
    <button type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary toggle h-25px w-25px"
     data-kt-docs-datatable-subtable="expand_row">
     <span class="svg-icon svg-icon-3 m-0 toggle-off">...</span>
     <span class="svg-icon svg-icon-3 m-0 toggle-on">...</span>
    </button>
   </td>
   <!--end::Actions-->
  </tr>

   ...
 </tbody>
 <!--end::Table body-->
</table>
var table;
var datatable;
var template;

// Private methods
const initDatatable = () => {
 // Set date data order
 const tableRows = table.querySelectorAll('tbody tr');

 tableRows.forEach(row => {
  const dateRow = row.querySelectorAll('td');
  const realDate = moment(dateRow[1].innerHTML, "DD MMM YYYY, LT").format(); // select date from 2nd column in table

  // Skip template
  if (!row.closest('[data-kt-docs-datatable-subtable="subtable_template"]')) {
   dateRow[1].setAttribute('data-order', realDate);
   dateRow[1].innerText = moment(realDate).fromNow();
  }
 });

 // Get subtable template
 const subtable = document.querySelector('[data-kt-docs-datatable-subtable="subtable_template"]');
 template = subtable.cloneNode(true);
 template.classList.remove('d-none');

 // Remove subtable template
 subtable.parentNode.removeChild(subtable);

 // Init datatable --- more info on datatables: https://datatables.net/manual/
 datatable = $(table).DataTable({
  "info": false,
  'order': [],
  "lengthChange": false,
  'pageLength': 6,
  'ordering': false,
  'paging': false,
  'columnDefs': [
   { orderable: false, targets: 0 }, // Disable ordering on column 0 (checkbox)
   { orderable: false, targets: 6 }, // Disable ordering on column 6 (actions)
  ]
 });

 // Re-init functions on every table re-draw -- more info: https://datatables.net/reference/event/draw
 datatable.on('draw', function () {
  resetSubtable();
  handleActionButton();
 });
}

// Subtable data sample
const data = [
 {
  image: '76',
  name: 'Go Pro 8',
  description: 'Latest  version of Go Pro.',
  cost: '500.00',
  qty: '1',
  total: '500.00',
  stock: '12'
 },

 ...
];

// Handle action button
const handleActionButton = () => {
 const buttons = document.querySelectorAll('[data-kt-docs-datatable-subtable="expand_row"]');

 // Sample row items counter --- for demo purpose only, remove this variable in your project
 const rowItems = [4, 1, 5, 1, 4, 2];

 buttons.forEach((button, index) => {
  button.addEventListener('click', e => {
   e.stopImmediatePropagation();
   e.preventDefault();

   const row = button.closest('tr');
   const rowClasses = ['isOpen', 'border-bottom-0'];

   // Get total number of items to generate --- for demo purpose only, remove this code snippet in your project
   const demoData = [];
   for (var j = 0; j < rowItems[index]; j++) {
    demoData.push(data[j]);
   }
   // End of generating demo data

   // Handle subtable expanded state
   if (row.classList.contains('isOpen')) {
    // Remove all subtables from current order row
    while (row.nextSibling && row.nextSibling.getAttribute('data-kt-docs-datatable-subtable') === 'subtable_template') {
     row.nextSibling.parentNode.removeChild(row.nextSibling);
    }
    row.classList.remove(...rowClasses);
    button.classList.remove('active');
   } else {
    populateTemplate(demoData, row);
    row.classList.add(...rowClasses);
    button.classList.add('active');
   }
  });
 });
}

// Populate template with content/data -- content/data can be replaced with relevant data from database or API
const populateTemplate = (data, target) => {
 data.forEach((d, index) => {
  // Clone template node
  const newTemplate = template.cloneNode(true);

  // Stock badges
  const lowStock = `<div class="badge badge-warning">Low Stock</div>`;
  const inStock = `<div class="badge badge-success">In Stock</div>`;

  // Select data elements
  const image = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_image"]');
  const name = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_name"]');
  const description = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_description"]');
  const cost = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_cost"]');
  const qty = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_qty"]');
  const total = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_total"]');
  const stock = newTemplate.querySelector('[data-kt-docs-datatable-subtable="template_stock"]');

  // Populate elements with data
  const imageSrc = image.getAttribute('src');
  image.setAttribute('src', imageSrc + d.image + '.gif');
  name.innerText = d.name;
  description.innerText = d.description;
  cost.innerText = d.cost;
  qty.innerText = d.qty;
  total.innerText = d.total;
  if (d.stock > 10) {
   stock.innerHTML = inStock;
  } else {
   stock.innerHTML = lowStock;
  }

  // New template border controller
  // When only 1 row is available
  if (data.length === 1) {
   let borderClasses = ['rounded', 'rounded-end-0'];
   newTemplate.querySelectorAll('td')[0].classList.add(...borderClasses);
   borderClasses = ['rounded', 'rounded-start-0'];
   newTemplate.querySelectorAll('td')[4].classList.add(...borderClasses);

   // Remove bottom border
   newTemplate.classList.add('border-bottom-0');
  } else {
   // When multiple rows detected
   if (index === (data.length - 1)) { // first row
    let borderClasses = ['rounded-start', 'rounded-bottom-0'];
    newTemplate.querySelectorAll('td')[0].classList.add(...borderClasses);
    borderClasses = ['rounded-end', 'rounded-bottom-0'];
    newTemplate.querySelectorAll('td')[4].classList.add(...borderClasses);
   }
   if (index === 0) { // last row
    let borderClasses = ['rounded-start', 'rounded-top-0'];
    newTemplate.querySelectorAll('td')[0].classList.add(...borderClasses);
    borderClasses = ['rounded-end', 'rounded-top-0'];
    newTemplate.querySelectorAll('td')[4].classList.add(...borderClasses);

    // Remove bottom border on last row
    newTemplate.classList.add('border-bottom-0');
   }
  }

  // Insert new template into table
  const tbody = table.querySelector('tbody');
  tbody.insertBefore(newTemplate, target.nextSibling);
 });
}

// Reset subtable
const resetSubtable = () => {
 const subtables = document.querySelectorAll('[data-kt-docs-datatable-subtable="subtable_template"]');
 subtables.forEach(st => {
  st.parentNode.removeChild(st);
 });

 const rows = table.querySelectorAll('tbody tr');
 rows.forEach(r => {
  r.classList.remove('isOpen');
  if (r.querySelector('[data-kt-docs-datatable-subtable="expand_row"]')) {
   r.querySelector('[data-kt-docs-datatable-subtable="expand_row"]').classList.remove('active');
  }
 });
}