|
Customer Name | Company | Payment Method | Created Date | Actions |
---|
<!--begin::Wrapper-->
<div class="d-flex flex-stack mb-5">
<!--begin::Search-->
<div class="d-flex align-items-center position-relative my-1">
<span class="svg-icon svg-icon-2">...</span>
<input type="text" data-kt-docs-table-filter="search" class="form-control form-control-solid w-250px ps-15" placeholder="Search Customers"/>
</div>
<!--end::Search-->
<!--begin::Toolbar-->
<div class="d-flex justify-content-end" data-kt-docs-table-toolbar="base">
<!--begin::Filter-->
<button type="button" class="btn btn-light-primary me-3" data-bs-toggle="tooltip" title="Coming Soon">
<span class="svg-icon svg-icon-2">...</span>
Filter
</button>
<!--end::Filter-->
<!--begin::Add customer-->
<button type="button" class="btn btn-primary" data-bs-toggle="tooltip" title="Coming Soon">
<span class="svg-icon svg-icon-2">...</span>
Add Customer
</button>
<!--end::Add customer-->
</div>
<!--end::Toolbar-->
<!--begin::Group actions-->
<div class="d-flex justify-content-end align-items-center d-none" data-kt-docs-table-toolbar="selected">
<div class="fw-bolder me-5">
<span class="me-2" data-kt-docs-table-select="selected_count"></span> Selected
</div>
<button type="button" class="btn btn-danger" data-bs-toggle="tooltip" title="Coming Soon">
Selection Action
</button>
</div>
<!--end::Group actions-->
</div>
<!--end::Wrapper-->
<!--begin::Datatable-->
<table id="kt_datatable_example_1" class="table align-middle table-row-dashed fs-6 gy-5">
<thead>
<tr class="text-start text-gray-400 fw-bolder fs-7 text-uppercase gs-0">
<th class="w-10px pe-2">
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
<input class="form-check-input" type="checkbox" data-kt-check="true" data-kt-check-target="#kt_datatable_example_1 .form-check-input" value="1"/>
</div>
</th>
<th>Customer Name</th>
<th>Email</th>
<th>Company</th>
<th>Payment Method</th>
<th>Created Date</th>
<th class="text-end min-w-100px">Actions</th>
</tr>
</thead>
<tbody class="text-gray-600 fw-bold">
</tbody>
</table>
<!--end::Datatable-->
"use strict";
// Class definition
var KTDatatablesServerSide = function () {
// Shared variables
var table;
var dt;
var filterPayment;
// Private functions
var initDatatable = function () {
dt = $("#kt_datatable_example_1").DataTable({
responsive: true,
searchDelay: 500,
processing: true,
serverSide: true,
order: [[5, 'desc']],
stateSave: true,
select: {
style: 'os',
selector: 'td:first-child',
className: 'row-selected'
},
ajax: {
url: "https://preview.keenthemes.com/api/datatables.php",
},
columns: [
{ data: 'RecordID' },
{ data: 'Name' },
{ data: 'Email' },
{ data: 'Company' },
{ data: 'CreditCardNumber' },
{ data: 'Datetime' },
{ data: null },
],
columnDefs: [
{
targets: 0,
orderable: false,
render: function (data) {
return `
<div class="form-check form-check-sm form-check-custom form-check-solid">
<input class="form-check-input" type="checkbox" value="${data}" />
</div>`;
}
},
{
targets: 4,
render: function (data, type, row) {
return `<img src="${hostUrl}media/svg/card-logos/${row.CreditCardType}.svg" class="w-35px me-3" alt="${row.CreditCardType}">` + data;
}
},
{
targets: -1,
data: null,
orderable: false,
className: 'text-end',
render: function (data, type, row) {
return `
<a href="#" class="btn btn-light btn-active-light-primary btn-sm" data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end" data-kt-menu-flip="top-end">
Actions
<span class="svg-icon svg-icon-5 m-0">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon points="0 0 24 0 24 24 0 24"></polygon>
<path d="M6.70710678,15.7071068 C6.31658249,16.0976311 5.68341751,16.0976311 5.29289322,15.7071068 C4.90236893,15.3165825 4.90236893,14.6834175 5.29289322,14.2928932 L11.2928932,8.29289322 C11.6714722,7.91431428 12.2810586,7.90106866 12.6757246,8.26284586 L18.6757246,13.7628459 C19.0828436,14.1360383 19.1103465,14.7686056 18.7371541,15.1757246 C18.3639617,15.5828436 17.7313944,15.6103465 17.3242754,15.2371541 L12.0300757,10.3841378 L6.70710678,15.7071068 Z" fill="#000000" fill-rule="nonzero" transform="translate(12.000003, 11.999999) rotate(-180.000000) translate(-12.000003, -11.999999)"></path>
</g>
</svg>
</span>
</a>
<!--begin::Menu-->
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold fs-7 w-125px py-4" data-kt-menu="true">
<!--begin::Menu item-->
<div class="menu-item px-3">
<a href="#" class="menu-link px-3" data-kt-docs-table-filter="edit_row">
Edit
</a>
</div>
<!--end::Menu item-->
<!--begin::Menu item-->
<div class="menu-item px-3">
<a href="#" class="menu-link px-3" data-kt-docs-table-filter="delete_row">
Delete
</a>
</div>
<!--end::Menu item-->
</div>
<!--end::Menu-->
`;
},
},
],
// Add data-filter attribute
createdRow: function (row, data, dataIndex) {
$(row).find('td:eq(4)').attr('data-filter', data.CreditCardType);
}
});
table = dt.$;
// Re-init functions on every table re-draw -- more info: https://datatables.net/reference/event/draw
dt.on('draw', function () {
initToggleToolbar();
toggleToolbars();
handleDeleteRows();
KTMenu.createInstances();
});
}
// Search Datatable --- official docs reference: https://datatables.net/reference/api/search()
var handleSearchDatatable = function () {
const filterSearch = document.querySelector('[data-kt-docs-table-filter="search"]');
filterSearch.addEventListener('keyup', function (e) {
dt.search(e.target.value).draw();
});
}
// Filter Datatable
var handleFilterDatatable = () => {
// Select filter options
filterPayment = document.querySelectorAll('[data-kt-docs-table-filter="payment_type"] [name="payment_type"]');
const filterButton = document.querySelector('[data-kt-docs-table-filter="filter"]');
// Filter datatable on submit
filterButton.addEventListener('click', function () {
// Get filter values
let paymentValue = '';
// Get payment value
filterPayment.forEach(r => {
if (r.checked) {
paymentValue = r.value;
}
// Reset payment value if "All" is selected
if (paymentValue === 'all') {
paymentValue = '';
}
});
// Filter datatable --- official docs reference: https://datatables.net/reference/api/search()
dt.search(paymentValue).draw();
});
}
// Delete customer
var handleDeleteRows = () => {
// Select all delete buttons
const deleteButtons = document.querySelectorAll('[data-kt-docs-table-filter="delete_row"]');
deleteButtons.forEach(d => {
// Delete button on click
d.addEventListener('click', function (e) {
e.preventDefault();
// Select parent row
const parent = e.target.closest('tr');
// Get customer name
const customerName = parent.querySelectorAll('td')[1].innerText;
// SweetAlert2 pop up --- official docs reference: https://sweetalert2.github.io/
Swal.fire({
text: "Are you sure you want to delete " + customerName + "?",
icon: "warning",
showCancelButton: true,
buttonsStyling: false,
confirmButtonText: "Yes, delete!",
cancelButtonText: "No, cancel",
customClass: {
confirmButton: "btn fw-bold btn-danger",
cancelButton: "btn fw-bold btn-active-light-primary"
}
}).then(function (result) {
if (result.value) {
// Simulate delete request -- for demo purpose only
Swal.fire({
text: "Deleting " + customerName,
icon: "info",
buttonsStyling: false,
showConfirmButton: false,
timer: 2000
}).then(function () {
Swal.fire({
text: "You have deleted " + customerName + "!.",
icon: "success",
buttonsStyling: false,
confirmButtonText: "Ok, got it!",
customClass: {
confirmButton: "btn fw-bold btn-primary",
}
}).then(function () {
// delete row data from server and re-draw datatable
dt.draw();
});
});
} else if (result.dismiss === 'cancel') {
Swal.fire({
text: customerName + " was not deleted.",
icon: "error",
buttonsStyling: false,
confirmButtonText: "Ok, got it!",
customClass: {
confirmButton: "btn fw-bold btn-primary",
}
});
}
});
})
});
}
// Reset Filter
var handleResetForm = () => {
// Select reset button
const resetButton = document.querySelector('[data-kt-docs-table-filter="reset"]');
// Reset datatable
resetButton.addEventListener('click', function () {
// Reset payment type
filterPayment[0].checked = true;
// Reset datatable --- official docs reference: https://datatables.net/reference/api/search()
dt.search('').draw();
});
}
// Init toggle toolbar
var initToggleToolbar = function () {
// Toggle selected action toolbar
// Select all checkboxes
const container = document.querySelector('#kt_datatable_example_1');
const checkboxes = container.querySelectorAll('[type="checkbox"]');
// Select elements
const deleteSelected = document.querySelector('[data-kt-docs-table-select="delete_selected"]');
// Toggle delete selected toolbar
checkboxes.forEach(c => {
// Checkbox on click event
c.addEventListener('click', function () {
setTimeout(function () {
toggleToolbars();
}, 50);
});
});
// Deleted selected rows
deleteSelected.addEventListener('click', function () {
// SweetAlert2 pop up --- official docs reference: https://sweetalert2.github.io/
Swal.fire({
text: "Are you sure you want to delete selected customers?",
icon: "warning",
showCancelButton: true,
buttonsStyling: false,
showLoaderOnConfirm: true,
confirmButtonText: "Yes, delete!",
cancelButtonText: "No, cancel",
customClass: {
confirmButton: "btn fw-bold btn-danger",
cancelButton: "btn fw-bold btn-active-light-primary"
},
}).then(function (result) {
if (result.value) {
// Simulate delete request -- for demo purpose only
Swal.fire({
text: "Deleting selected customers",
icon: "info",
buttonsStyling: false,
showConfirmButton: false,
timer: 2000
}).then(function () {
Swal.fire({
text: "You have deleted all selected customers!.",
icon: "success",
buttonsStyling: false,
confirmButtonText: "Ok, got it!",
customClass: {
confirmButton: "btn fw-bold btn-primary",
}
}).then(function () {
// delete row data from server and re-draw datatable
dt.draw();
});
// Remove header checked box
const headerCheckbox = container.querySelectorAll('[type="checkbox"]')[0];
headerCheckbox.checked = false;
});
} else if (result.dismiss === 'cancel') {
Swal.fire({
text: "Selected customers was not deleted.",
icon: "error",
buttonsStyling: false,
confirmButtonText: "Ok, got it!",
customClass: {
confirmButton: "btn fw-bold btn-primary",
}
});
}
});
});
}
// Toggle toolbars
var toggleToolbars = function () {
// Define variables
const container = document.querySelector('#kt_datatable_example_1');
const toolbarBase = document.querySelector('[data-kt-docs-table-toolbar="base"]');
const toolbarSelected = document.querySelector('[data-kt-docs-table-toolbar="selected"]');
const selectedCount = document.querySelector('[data-kt-docs-table-select="selected_count"]');
// Select refreshed checkbox DOM elements
const allCheckboxes = container.querySelectorAll('tbody [type="checkbox"]');
// Detect checkboxes state & count
let checkedState = false;
let count = 0;
// Count checked boxes
allCheckboxes.forEach(c => {
if (c.checked) {
checkedState = true;
count++;
}
});
// Toggle toolbars
if (checkedState) {
selectedCount.innerHTML = count;
toolbarBase.classList.add('d-none');
toolbarSelected.classList.remove('d-none');
} else {
toolbarBase.classList.remove('d-none');
toolbarSelected.classList.add('d-none');
}
}
// Public methods
return {
init: function () {
initDatatable();
handleSearchDatatable();
initToggleToolbar();
handleFilterDatatable();
handleDeleteRows();
handleResetForm();
}
}
}();
// On document ready
KTUtil.onDOMContentLoaded(function () {
KTDatatablesServerSide.init();
});
<?php
include 'class-list-util.php';
class DataTableApi
{
public $columnsDefault = [
'RecordID' => true,
'OrderID' => true,
'Name' => true,
'Country' => true,
'CountryCode' => true,
'City' => true,
'Company' => true,
'Address' => true,
'Email' => true,
'Currency' => true,
'Notes' => true,
'Department' => true,
'Website' => true,
'Latitude' => true,
'Longitude' => true,
'Datetime' => true,
'TimeZone' => true,
'Money' => true,
'Gender' => true,
'CreditCardNumber' => true,
'CreditCardType' => true,
];
public function __construct()
{
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: *');
}
public function init()
{
if (isset($_REQUEST['columnsDef']) && is_array($_REQUEST['columnsDef'])) {
foreach ($_REQUEST['columnsDef'] as $field) {
$columnsDefault[$field] = true;
}
}
// get all raw data
$alldata = $this->getJsonDecode();
$data = [];
// internal use; filter selected columns only from raw data
foreach ($alldata as $d) {
$data[] = $this->filterArray($d, $this->columnsDefault);
}
// filter by general search keyword
if (isset($_REQUEST['search']['value']) && $_REQUEST['search']['value']) {
$data = $this->arraySearch($data, $_REQUEST['search']['value']);
}
// count data
$totalRecords = $totalDisplay = count($data);
// sort
if (isset($_REQUEST['order'][0]['column']) && $_REQUEST['order'][0]['dir']) {
$column = $_REQUEST['order'][0]['column'];
$dir = $_REQUEST['order'][0]['dir'];
usort($data, function ($a, $b) use ($column, $dir) {
$a = array_slice($a, $column, 1);
$b = array_slice($b, $column, 1);
$a = array_pop($a);
$b = array_pop($b);
if ($dir === 'asc') {
return $a > $b ? 1 : -1;
}
return $a < $b ? 1 : -1;
});
}
// pagination length
if (isset($_REQUEST['length'])) {
$data = array_splice($data, $_REQUEST['start'], $_REQUEST['length']);
}
$data = $this->reformat($data);
$result = [
'recordsTotal' => $totalRecords,
'recordsFiltered' => $totalDisplay,
'data' => $data,
];
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
public function filterArray($array, $allowed = [])
{
return array_filter(
$array,
function ($val, $key) use ($allowed) { // N.b. $val, $key not $key, $val
return isset($allowed[$key]) && ($allowed[$key] === true || $allowed[$key] === $val);
},
ARRAY_FILTER_USE_BOTH
);
}
public function filterKeyword($data, $search, $field = '')
{
$filter = '';
if (isset($search['value'])) {
$filter = $search['value'];
}
if (!empty($filter)) {
if (!empty($field)) {
if (strpos(strtolower($field), 'date') !== false) {
// filter by date range
$data = $this->filterByDateRange($data, $filter, $field);
} else {
// filter by column
$data = array_filter($data, function ($a) use ($field, $filter) {
return (boolean) preg_match("/$filter/i", $a[$field]);
});
}
} else {
// general filter
$data = array_filter($data, function ($a) use ($filter) {
return (boolean) preg_grep("/$filter/i", (array) $a);
});
}
}
return $data;
}
public function filterByDateRange($data, $filter, $field)
{
// filter by range
if (!empty($range = array_filter(explode('|', $filter)))) {
$filter = $range;
}
if (is_array($filter)) {
foreach ($filter as &$date) {
// hardcoded date format
$date = date_create_from_format('m/d/Y', stripcslashes($date));
}
// filter by date range
$data = array_filter($data, function ($a) use ($field, $filter) {
// hardcoded date format
$current = date_create_from_format('m/d/Y', $a[$field]);
$from = $filter[0];
$to = $filter[1];
if ($from <= $current && $to >= $current) {
return true;
}
return false;
});
}
return $data;
}
public function getJsonDecode(): mixed
{
return json_decode(file_get_contents('customers.json'), true);
}
/**
* @param array $data
*
* @return array
*/
public function reformat($data): array
{
return array_map(function ($item) {
// hide credit card number
$item['CreditCardNumber'] = '**** '.substr($item['CreditCardNumber'], -4);
$item['CreditCardType'] = $item['CreditCardType'] === 'americanexpress' ? 'american-express' : $item['CreditCardType'];
// reformat datetime
$item['Datetime'] = date('d M Y, g:i a', strtotime($item['Datetime']));
return $item;
}, $data);
}
public function arraySearch($array, $keyword)
{
return array_filter($array, function ($a) use ($keyword) {
return (boolean) preg_grep("/$keyword/i", (array) $a);
});
}
}
$api = new DataTableApi;
$api->init();
<?php
/**
* class-list-util.php
*
* List utility class
*/
/**
* List utility.
*
* Utility class to handle operations on an array of objects.
*/
class List_Util
{
/**
* The input array.
*
* @access private
* @var array
*/
private $input = array();
/**
* The output array.
*
* @access private
* @var array
*/
private $output = array();
/**
* Temporary arguments for sorting.
*
* @access private
* @var array
*/
private $orderby = array();
/**
* Constructor.
*
* Sets the input array.
*
*
* @param array $input Array to perform operations on.
*/
public function __construct($input)
{
$this->output = $this->input = $input;
}
/**
* Returns the original input array.
*
* @access public
*
* @return array The input array.
*/
public function get_input()
{
return $this->input;
}
/**
* Returns the output array.
*
* @access public
*
* @return array The output array.
*/
public function get_output()
{
return $this->output;
}
/**
* Filters the list, based on a set of key => value arguments.
*
*
* @param array $args Optional. An array of key => value arguments to match
* against each object. Default empty array.
* @param string $operator Optional. The logical operation to perform. 'AND' means
* all elements from the array must match. 'OR' means only
* one element needs to match. 'NOT' means no elements may
* match. Default 'AND'.
*
* @return array Array of found values.
*/
public function filter($args = array(), $operator = 'AND')
{
if (empty($args)) {
return $this->output;
}
$operator = strtoupper($operator);
if (!in_array($operator, array('AND', 'OR', 'NOT'), true)) {
return array();
}
$count = count($args);
$filtered = array();
foreach ($this->output as $key => $obj) {
$to_match = (array) $obj;
$matched = 0;
foreach ($args as $m_key => $m_value) {
if (array_key_exists($m_key, $to_match) && $m_value == $to_match[$m_key]) {
$matched++;
}
}
if (
('AND' == $operator && $matched == $count) ||
('OR' == $operator && $matched > 0) ||
('NOT' == $operator && 0 == $matched)
) {
$filtered[$key] = $obj;
}
}
$this->output = $filtered;
return $this->output;
}
/**
* Plucks a certain field out of each object in the list.
*
* This has the same functionality and prototype of
* array_column() (PHP 5.5) but also supports objects.
*
*
* @param int|string $field Field from the object to place instead of the entire object
* @param int|string $index_key Optional. Field from the object to use as keys for the new array.
* Default null.
*
* @return array Array of found values. If `$index_key` is set, an array of found values with keys
* corresponding to `$index_key`. If `$index_key` is null, array keys from the original
* `$list` will be preserved in the results.
*/
public function pluck($field, $index_key = null)
{
if (!$index_key) {
/*
* This is simple. Could at some point wrap array_column()
* if we knew we had an array of arrays.
*/
foreach ($this->output as $key => $value) {
if (is_object($value)) {
$this->output[$key] = $value->$field;
} else {
$this->output[$key] = $value[$field];
}
}
return $this->output;
}
/*
* When index_key is not set for a particular item, push the value
* to the end of the stack. This is how array_column() behaves.
*/
$newlist = array();
foreach ($this->output as $value) {
if (is_object($value)) {
if (isset($value->$index_key)) {
$newlist[$value->$index_key] = $value->$field;
} else {
$newlist[] = $value->$field;
}
} else {
if (isset($value[$index_key])) {
$newlist[$value[$index_key]] = $value[$field];
} else {
$newlist[] = $value[$field];
}
}
}
$this->output = $newlist;
return $this->output;
}
/**
* Sorts the list, based on one or more orderby arguments.
*
*
* @param string|array $orderby Optional. Either the field name to order by or an array
* of multiple orderby fields as $orderby => $order.
* @param string $order Optional. Either 'ASC' or 'DESC'. Only used if $orderby
* is a string.
* @param bool $preserve_keys Optional. Whether to preserve keys. Default false.
*
* @return array The sorted array.
*/
public function sort($orderby = array(), $order = 'ASC', $preserve_keys = false)
{
if (empty($orderby)) {
return $this->output;
}
if (is_string($orderby)) {
$orderby = array($orderby => $order);
}
foreach ($orderby as $field => $direction) {
$orderby[$field] = 'DESC' === strtoupper($direction) ? 'DESC' : 'ASC';
}
$this->orderby = $orderby;
if ($preserve_keys) {
uasort($this->output, array($this, 'sort_callback'));
} else {
usort($this->output, array($this, 'sort_callback'));
}
$this->orderby = array();
return $this->output;
}
/**
* Callback to sort the list by specific fields.
*
* @access private
*
* @param object|array $a One object to compare.
* @param object|array $b The other object to compare.
*
* @return int 0 if both objects equal. -1 if second object should come first, 1 otherwise.
* @see List_Util::sort()
*
*/
private function sort_callback($a, $b)
{
if (empty($this->orderby)) {
return 0;
}
$a = (array) $a;
$b = (array) $b;
foreach ($this->orderby as $field => $direction) {
if (!isset($a[$field]) || !isset($b[$field])) {
continue;
}
if ($a[$field] == $b[$field]) {
continue;
}
$results = 'DESC' === $direction ? array(1, -1) : array(-1, 1);
if (is_numeric($a[$field]) && is_numeric($b[$field])) {
return ($a[$field] < $b[$field]) ? $results[0] : $results[1];
}
return 0 > strcmp($a[$field], $b[$field]) ? $results[0] : $results[1];
}
return 0;
}
}
function list_filter($list, $args = array(), $operator = 'AND')
{
if (!is_array($list)) {
return array();
}
$util = new List_Util($list);
return $util->filter($args, $operator);
}