Files
taxbaik/legacy/smartadmin/docs-smarttablesjs.html
T
kjh2064 40cffb3beb
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m26s
fix: implement Blazor-native login form to properly update authentication state
Problem: JavaScript login form saved tokens to localStorage but didn't notify
CustomAuthenticationStateProvider, causing [Authorize] pages to remain in
'loading' state indefinitely. The provider only reads tokens when:
1. GetAuthenticationStateAsync() is called (page load)
2. NotifyAuthenticationStateChanged() is triggered (UI updates)

But JavaScript login didn't trigger either, leaving the authentication state
stale.

Solution: Convert AdminLoginForm from HTML+JavaScript to pure Blazor component.
Now the login flow is:
1. User enters credentials in Blazor form
2. HttpClient POST to /api/auth/login
3. Save tokens to localStorage
4. Call CustomAuthenticationStateProvider.LoginAsync() directly
5. Blazor detects auth state change and re-evaluates [Authorize] pages
6. Dashboard [Authorize] page renders successfully

Result: Immediate authentication state update, no loading timeout on protected pages.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-03 13:03:53 +09:00

4073 lines
221 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-bs-theme="light" class="set-nav-dark">
<!-- Mirrored from getwebora.com/smartadmin/demo/docs-smarttablesjs.html by HTTrack Website Copier/3.x [XR&CO'2014], Tue, 30 Jun 2026 04:28:01 GMT -->
<head>
<meta charset="utf-8">
<title> smartTables.js | SmartAdmin - Enterprise Admin Dashboard by Webora</title>
<meta name="description" content="Page Description">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=5">
<!-- Standard favicon for browsers -->
<link rel="icon" href="img/favicon-32x32.png" type="image/png" sizes="32x32">
<link rel="icon" href="img/favicon-16x16.png" type="image/png" sizes="16x16">
<!-- Apple Touch Icon (iOS) -->
<link rel="apple-touch-icon" href="img/apple-touch-icon.png" sizes="180x180">
<!-- Android/Chrome (Progressive Web App) -->
<link rel="icon" href="img/favicon-192x192.png" type="image/png" sizes="192x192">
<!-- Call App Mode on ios devices -->
<meta name="mobile-web-app-capable" content="yes">
<!-- Remove Tap Highlight on Windows Phone IE -->
<meta name="msapplication-tap-highlight" content="no">
<link rel="stylesheet" href="plugins/highlightjs/night-owl.css">
<!-- Vendor css -->
<link rel="stylesheet" media="screen, print" href="plugins/waves/waves.min.css">
<!-- Base css -->
<link rel="stylesheet" media="screen, print" href="css/smartapp.min.css">
<!-- Icons css-->
<link rel="stylesheet" media="screen, print" href="webfonts/smartadmin/sa-icons.css">
<link rel="stylesheet" media="screen, print" href="webfonts/fontawesome/fontawesome.css">
<!-- Save/Load functionality JavaScript -->
<script src="scripts/core/saveloadscript.js"></script>
</head>
<body>
<div class="app-wrap">
<header class="app-header">
<!-- Collapse icon -->
<div class="d-flex flex-grow-1 w-100 me-auto align-items-center">
<!-- App logo -->
<a href="dashboard-control-center.html" class="app-logo flex-shrink-0" data-prefix="v5.5.0" data-action="playsound" data-soundpath="media/sound/" data-soundfile="sawhisp.mp3">
<!-- <img src="img/logo.svg" alt="logo"> -->
<svg class="custom-logo">
<use href="img/app-logo.svg#custom-logo"></use>
</svg>
<!-- Logo Backdrop Animation -->
<div class="logo-backdrop">
<div class="blobs">
<svg viewbox="0 0 1200 1200">
<g class="blob blob-1">
<path d="M 100 600 q 0 -700, 500 -500 t 500 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-2">
<path d="M 100 600 q -50 -400, 500 -500 t 450 550 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-3">
<path d="M 100 600 q 0 -400, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-4">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
<g class="blob blob-1 alt">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
<g class="blob blob-2 alt">
<path d="M 100 600 q 100 -600, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-3 alt">
<path d="M 100 600 q 0 -400, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-4 alt">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
</svg>
</div>
</div>
</a>
<button class="mobile-menu-icon me-2 d-flex d-sm-flex d-md-flex d-lg-none flex-shrink-0" data-action="toggle-swap" data-toggleclass="app-mobile-menu-open" aria-label="Toggle Mobile Menu">
<svg class="sa-icon">
<use href="icons/sprite.svg#menu"></use>
</svg>
</button>
<!-- Collapse icon -->
<button type="button" class="collapse-icon me-3 d-none d-lg-inline-flex d-xl-inline-flex d-xxl-inline-flex" data-action="toggle" data-class="set-nav-minified" aria-label="Toggle Navigation Size">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 8">
<polygon fill="#878787" points="4.5,1 3.8,0.2 0,4 3.8,7.8 4.5,7 1.5,4" />
</svg>
</button>
<form class="app-search" role="search" action="https://getwebora.com/smartadmin/demo/search.html" autocomplete="off">
<input type="text" class="form-control" placeholder="Search for anything">
</form>
</div>
<!-- Settings -->
<button type="button" class="btn btn-system hidden-mobile" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-drawer-settings" aria-label="Open Settings">
<svg class="sa-icon sa-icon-2x">
<use href="icons/sprite.svg#settings"></use>
</svg>
</button>
<!-- Theme modes -->
<button type="button" class="btn btn-system" data-action="toggle-theme" aria-label="Toggle Dark Mode" aria-pressed="false">
<svg class="sa-icon sa-icon-2x sa-mode-light">
<use href="icons/sprite.svg#sun"></use>
</svg>
<svg class="sa-icon sa-icon-2x sa-mode-dark">
<use href="icons/sprite.svg#moon"></use>
</svg>
</button>
<!-- Sidebar -->
<button type="button" class="btn btn-system d-none d-sm-block d-sm-none d-md-none d-lg-block" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-app-drawer" aria-label="Open Sidebar">
<svg class="sa-icon sa-icon-2x">
<use href="icons/sprite.svg#aperture"></use>
</svg>
</button>
<!-- Full Screen Mode -->
<button type="button" class="btn btn-system d-none d-sm-block d-sm-none d-md-none d-lg-block" data-action="app-fullscreen" aria-label="Toggle Dark Mode" aria-pressed="false">
<svg class="sa-icon sa-icon-2x sa-fullscreen-on">
<use href="icons/sprite.svg#minimize"></use>
</svg>
<svg class="sa-icon sa-icon-2x sa-fullscreen-off">
<use href="icons/sprite.svg#maximize"></use>
</svg>
</button>
<!-- Notifications -->
<button type="button" class="btn btn-system dropdown-toggle no-arrow" data-bs-toggle="dropdown" aria-expanded="false" aria-label="Open Notifications">
<span class="badge badge-icon pos-top pos-end">5</span>
<svg class="sa-icon sa-icon-2x">
<use href="icons/sprite.svg#bell"></use>
</svg>
</button>
<!-- Notifications dropdown -->
<div class="dropdown-menu dropdown-menu-animated dropdown-xl dropdown-menu-end p-0">
<div class="notification-header rounded-top mb-2">
<h4 class="m-0">
5 New
<small class="mb-0 opacity-80">User Notifications</small>
</h4>
</div>
<ul class="nav nav-tabs nav-tabs-clean" role="tablist">
<li class="nav-item d-none">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-default" role="tab" aria-selected="true">Hidden</a>
</li>
<li class="nav-item">
<a class="nav-link px-4 fs-md fw-500" data-bs-toggle="tab" href="#tab-messages" role="tab" aria-selected="false">Messages</a>
</li>
<li class="nav-item">
<a class="nav-link px-4 fs-md fw-500" data-bs-toggle="tab" href="#tab-feeds" role="tab" aria-selected="false">Feeds</a>
</li>
<li class="nav-item">
<a class="nav-link px-4 fs-md fw-500" data-bs-toggle="tab" href="#tab-events" role="tab" aria-selected="false">Events</a>
</li>
</ul>
<div class="tab-content tab-notification">
<!--Security message-->
<div class="tab-pane fade show active" id="tab-default" role="tabpanel">
<div class="d-flex h-100">
<div class="px-4 d-flex flex-column align-items-center justify-content-center">
<svg class="sa-icon sa-icon-5x sa-icon-primary">
<use href="icons/sprite.svg#arrow-up-circle"></use>
</svg>
<span class="text-center fw-300" style="font-size: 1.25rem;">
Select a tab above
</span>
<div class="mb-0 py-3 text-center fs-md fw-300 text-muted">
This blank page helps protect your privacy.
To change this default message, <a href="#">update your settings</a>.
</div>
</div>
</div>
</div>
<!-- Messages -->
<div class="tab-pane fade" id="tab-messages" role="tabpanel">
<div class="custom-scroll h-100">
<ul class="notification">
<li class="unread alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-c.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<!-- <span class="name">Melissa Ayre <span class="badge bg-primary fw-n position-absolute top-0 end-0 mt-1">INBOX</span></span> -->
<span class="name">Melissa Ayre</span>
<span class="msg-a fs-sm">Re: New security codes</span>
<span class="msg-b fs-xs">Hello again and thanks for being part...</span>
<span class="fs-nano text-muted mt-1">56 seconds ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="unread alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-a.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Adison Lee</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">2 minutes ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status status-success me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-b.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Oliver Kopyuv</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">3 days ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status status-warning me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-e.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Dr. John Cook PhD</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">2 weeks ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status status-success me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-h.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Sarah McBrook</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">3 weeks ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status status-success me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-m.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Anothony Bezyeth</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">one month ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center">
<span class="status status-danger me-2">
<span class="profile-image rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-j.png')"></span>
</span>
<span class="d-flex flex-column flex-1 ms-1">
<span class="name">Lisa Hatchensen</span>
<span class="msg-a fs-sm">Msed quia non numquam eius</span>
<span class="fs-nano text-muted mt-1">one year ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
</ul>
<div class="notification-empty-msg">
<svg class="sa-icon sa-icon-5x sa-icon-primary">
<use href="icons/sprite.svg#coffee"></use>
</svg>
<span>
No new messages
</span>
</div>
</div>
</div>
<!-- Feeds -->
<div class="tab-pane fade" id="tab-feeds" role="tabpanel">
<div class="custom-scroll h-100">
<ul class="notification">
<li class="unread alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<span class="d-flex flex-column flex-1">
<span class="name d-flex align-items-center">Administrator <span class="badge bg-success fw-n ms-1">UPDATE</span></span>
<span class="msg-a fs-sm">
System updated to version <strong>5.0</strong> <a href="buildnotes.html">(build notes)</a>
</span>
<span class="fs-nano text-muted mt-1">5 mins ago</span>
</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<div class="d-flex flex-column flex-1">
<span class="name">
Adison Lee <span class="fw-300 d-inline">replied to your video <a href="#" class="fw-400"> Cancer Drug</a> </span>
</span>
<span class="msg-a fs-sm mt-2">Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day...</span>
<span class="fs-nano text-muted mt-1">10 minutes ago</span>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<div class="d-flex flex-column flex-1">
<span class="name">
Troy Norman'<span class="fw-300">s new connections</span>
</span>
<div class="fs-sm d-flex align-items-center mt-2">
<span class="profile-image-md ms-1 rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-a.png'); background-size: cover;"></span>
<span class="profile-image-md ms-1 rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-b.png'); background-size: cover;"></span>
<span class="profile-image-md ms-1 rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-c.png'); background-size: cover;"></span>
<span class="profile-image-md ms-1 rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-e.png'); background-size: cover;"></span>
<div data-hasmore="+3" class="rounded-circle profile-image-md ms-1">
<span class="profile-image-md ms-1 rounded-circle d-inline-block" style="background-image:url('img/demo/avatars/avatar-h.png'); background-size: cover;"></span>
</div>
</div>
<span class="fs-nano text-muted mt-1">55 minutes ago</span>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<div class="d-flex flex-column flex-1">
<span class="name">Dr John Cook <span class="fw-300">sent a <span class="text-danger">new signal</span></span></span>
<span class="msg-a fs-sm mt-2">Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</span>
<span class="fs-nano text-muted mt-1">10 minutes ago</span>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<div class="d-flex flex-column flex-1">
<span class="name">Lab Images <span class="fw-300">were updated!</span></span>
<div class="fs-sm d-flex align-items-center mt-1">
<a href="#" class="ms-1 mt-1" title="Cell A-0012">
<span class="d-block img-share" style="background-image:url('img/thumbs/pic-7.png'); background-size: cover;"></span>
</a>
<a href="#" class="ms-1 mt-1" title="Patient A-473 saliva">
<span class="d-block img-share" style="background-image:url('img/thumbs/pic-8.png'); background-size: cover;"></span>
</a>
<a href="#" class="ms-1 mt-1" title="Patient A-473 blood cells">
<span class="d-block img-share" style="background-image:url('img/thumbs/pic-11.png'); background-size: cover;"></span>
</a>
<a href="#" class="ms-1 mt-1" title="Patient A-473 Membrane O.C">
<span class="d-block img-share" style="background-image:url('img/thumbs/pic-12.png'); background-size: cover;"></span>
</a>
</div>
<span class="fs-nano text-muted mt-1">55 minutes ago</span>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
<li class="alert alert-dismissable">
<div class="d-flex align-items-center show-child-on-hover">
<div class="d-flex flex-column flex-1 w-100">
<div class="name mb-2"> Lisa Lamar<span class="fw-300"> updated project</span>
</div>
<div class="row fs-b fw-300">
<div class="col text-start"> Progress </div>
<div class="col text-end fw-500"> 45% </div>
</div>
<div class="progress progress-sm d-flex mt-1">
<span class="progress-bar bg-primary progress-bar-striped" role="progressbar" style="width: 45%" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100"></span>
</div>
<span class="fs-nano text-muted mt-1">2 hrs ago</span>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</li>
</ul>
<div class="notification-empty-msg">
<svg class="sa-icon sa-icon-5x sa-icon-primary">
<use href="icons/sprite.svg#smile"></use>
</svg>
<span>
You are all set!
</span>
</div>
</div>
</div>
<!-- Events -->
<div class="tab-pane fade" id="tab-events" role="tabpanel">
<div class="d-flex flex-column h-100">
<div class="h-auto">
<table class="table-calendar m-0 w-100 h-100 border-0">
<tr>
<th colspan="7" class="pt-3 pb-2 px-3 text-center">
<div class="js-get-date h6 fw-600 mb-2">Fake Day, October 15th, 2090</div>
</th>
</tr>
<tr class="text-center">
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</tr>
<tr>
<td class="text-muted bg-faded">30</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>
<svg class="sa-icon sa-icon-warning m-1 position-absolute pos-left pos-top" style="--sa-icon-size: 0.85rem; --sa-fill-opacity: 0.5;">
<use href="icons/sprite.svg#star"></use>
</svg>
6
</td>
</tr>
<tr>
<td>7</td>
<td>8</td>
<td>9</td>
<td class="bg-primary-600 text-white pattern-0">10</td>
<td>11</td>
<td>12</td>
<td>13</td>
</tr>
<tr>
<td>14</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>18</td>
<td>19</td>
<td>20</td>
</tr>
<tr>
<td>21</td>
<td>
<svg class="sa-icon sa-icon-info m-1 position-absolute pos-left pos-top" style="--sa-icon-size: 0.85rem; --sa-fill-opacity: 0.5;">
<use href="icons/sprite.svg#shield"></use>
</svg>
22
</td>
<td>23</td>
<td>24</td>
<td>25</td>
<td>26</td>
<td>27</td>
</tr>
<tr>
<td>28</td>
<td>29</td>
<td>30</td>
<td>31</td>
<td class="text-muted bg-faded">1</td>
<td class="text-muted bg-faded">2</td>
<td class="text-muted bg-faded">3</td>
</tr>
</table>
</div>
<div class="flex-1 custom-scroll shadow-inset-3">
<div class="p-2">
<div class="d-flex align-items-center text-left mb-3">
<div class="width-5 text-primary align-self-start table-calendar-appointment-date fw-300 text-center">
15
</div>
<div class="flex-1">
<div class="d-flex flex-column">
<span class="l-h-n fs-md fw-500">
October 2020
</span>
<span class="l-h-n fs-nano fw-400 text-secondary">
Monday
</span>
</div>
<div class="d-flex flex-column gap-2 mt-2">
<div>
<strong>2:30PM</strong> - Doctor's appointment
</div>
<div>
<strong>3:30PM</strong> - Report overview
</div>
<div>
<strong>4:30PM</strong> - Meeting with Donnah V.
</div>
<div>
<strong>5:30PM</strong> - Late Lunch
</div>
<div>
<strong>6:30PM</strong> - Report Compression
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="py-2 px-3 d-block rounded-bottom text-end border-light border-bottom-0 border-end-0 border-start-0">
<a href="#" class="fs-xs fw-500 ms-auto">view all notifications</a>
</div>
</div>
<!-- Profile -->
<button type="button" data-bs-toggle="dropdown" title="drlantern@getwebora.com" class="btn-system bg-transparent d-flex flex-shrink-0 align-items-center justify-content-center" aria-label="Open Profile Dropdown">
<img src="img/demo/avatars/avatar-admin.png" class="profile-image profile-image-md rounded-circle" alt="Sunny A.">
</button>
<!-- Profile dropdown -->
<div class="dropdown-menu dropdown-menu-animated">
<div class="notification-header rounded-top mb-2">
<div class="d-flex flex-row align-items-center mt-1 mb-1 color-white">
<span class="status status-success d-inline-block me-2">
<img src="img/demo/avatars/avatar-admin.png" class="profile-image rounded-circle" alt="Sunny A.">
</span>
<div class="info-card-text">
<div class="fs-lg text-truncate text-truncate-lg">Sunny A.</div>
<span class="text-truncate text-truncate-md opacity-80 fs-sm">sunnya@sadim.com</span>
</div>
</div>
</div>
<div class="dropdown-divider m-0"></div>
<a href="#" class="dropdown-item" data-action="app-reset" role="button">
<span data-i18n="drpdwn.reset_layout">Reset Layout</span>
</a>
<a href="#" class="dropdown-item" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-drawer-settings" role="button">
<span data-i18n="drpdwn.settings">Settings</span>
</a>
<a href="#" class="dropdown-item d-block d-sm-block d-md-block d-lg-none" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-app-drawer" role="button">
<span data-i18n="drpdwn.settings">Virtual Assistant</span>
</a>
<div class="dropdown-divider m-0"></div>
<a href="#" class="dropdown-item d-flex justify-content-between align-items-center" data-action="app-fullscreen" aria-pressed="false" role="button">
<span data-i18n="drpdwn.fullscreen">Fullscreen</span>
<b class="text-muted fs-nano px-2 rounded font-monospace align-self-center border">F11</b>
</a>
<a href="#" class="dropdown-item d-flex justify-content-between align-items-center" data-action="app-print" role="button">
<span data-i18n="drpdwn.print">Print</span>
<span class="text-muted fs-nano px-2 rounded font-monospace align-self-center border">
<svg width="15" height="15">
<path d="M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953" stroke-width="1.2" stroke="currentColor" fill="none" stroke-linecap="square"></path>
</svg>
+ P
</span>
</a>
<div class="dropdown-multilevel dropdown-multilevel-left">
<div class="dropdown-item d-flex justify-content-between align-items-center">
<span data-i18n="drpdwn.language">Language</span>
<i class="sa sa-chevron-right"></i>
</div>
<div class="dropdown-menu">
<a href="#?lang=fr" class="dropdown-item" data-action="lang" data-lang="fr">Français</a>
<a href="#?lang=en" class="dropdown-item selected" data-action="lang" data-lang="en">English (US)</a>
<a href="#?lang=es" class="dropdown-item" data-action="lang" data-lang="es">Español</a>
<a href="#?lang=ru" class="dropdown-item" data-action="lang" data-lang="ru">Русский язык</a>
<a href="#?lang=jp" class="dropdown-item" data-action="lang" data-lang="jp">日本語</a>
<a href="#?lang=ch" class="dropdown-item" data-action="lang" data-lang="ch">中文</a>
</div>
</div>
<div class="dropdown-divider m-0"></div>
<a class="dropdown-item py-3 fw-500 d-flex justify-content-between" href="auth-login.html">
<span class="text-danger" data-i18n="drpdwn.page-logout">Logout</span>
<span class="d-block text-truncate text-truncate-sm">@sunnyahmed</span>
</a>
</div>
</header>
<aside class="app-sidebar d-flex flex-column">
<a href="dashboard-control-center.html" class="app-logo flex-shrink-0" data-prefix="v5.5.0" data-action="playsound" data-soundpath="media/sound/" data-soundfile="sawhisp.html">
<!-- <img src="img/logo.svg" alt="logo"> -->
<svg class="custom-logo">
<use href="img/app-logo.svg#custom-logo"></use>
</svg>
<!-- Logo Backdrop Animation -->
<div class="logo-backdrop">
<div class="blobs">
<svg viewbox="0 0 1200 1200">
<g class="blob blob-1">
<path d="M 100 600 q 0 -700, 500 -500 t 500 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-2">
<path d="M 100 600 q -50 -400, 500 -500 t 450 550 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-3">
<path d="M 100 600 q 0 -400, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-4">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
<g class="blob blob-1 alt">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
<g class="blob blob-2 alt">
<path d="M 100 600 q 100 -600, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-3 alt">
<path d="M 100 600 q 0 -400, 500 -500 t 400 500 t -500 500 T 100 600 z" />
</g>
<g class="blob blob-4 alt">
<path d="M 150 600 q 0 -600, 500 -500 t 500 550 t -500 500 T 150 600 z" />
</g>
</svg>
</div>
</div>
</a>
<form class="app-menu-filter-container px-4">
<input type="text" class="form-control" id="searchInput" placeholder="Filter" autocomplete="off">
<div class="js-filter-message nav-filter-msg badge bg-secondary btn" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Clear filter"></div>
</form>
<nav id="js-primary-nav" class="primary-nav flex-grow-1 custom-scroll">
<ul id="js-nav-menu" class="nav-menu">
<li class="nav-title"><span>Insights</span></li>
<li class="nav-item">
<a href="#" title="Dashboards" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#trello"></use>
</svg>
<span class="nav-link-text" data-i18n>Dashboards</span>
<span class="badge bg-danger-700 badge-end">New</span>
</a>
<ul>
<li>
<a href="dashboard-control-center.html">
<span class="nav-link-text" data-i18n>Control Center</span>
</a>
</li>
<li>
<a href="dashboard-subscription.html">
<span class="nav-link-text" data-i18n>Subscription & Billing</span>
</a>
</li>
<li>
<a href="dashboard-marketing.html">
<span class="nav-link-text" data-i18n>Marketing & Sales</span>
</a>
</li>
<li>
<a href="dashboard-project-management.html">
<span class="nav-link-text" data-i18n>Project Management</span>
</a>
</li>
</ul>
</li>
<li class="nav-title"><span>Config</span></li>
<li>
<a href="appintel.html">
<svg class="sa-icon">
<use href="icons/sprite.svg#cpu"></use>
</svg>
<span class="nav-link-text">Application Intel</span>
</a>
</li>
<li class="nav-item">
<a href="#" title="Documentation" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#book-open"></use>
</svg>
<span class="nav-link-text" data-i18n>Documentation</span>
</a>
<ul>
<li>
<a href="docs-build-notes.html">
<span class="nav-link-text" data-i18n>Build Notes</span>
</a>
</li>
<li>
<a href="docs-getting-started.html">
<span class="nav-link-text" data-i18n>Getting Started</span>
</a>
</li>
<li>
<a href="docs-smartappjs.html">
<span class="nav-link-text" data-i18n>App DOM</span>
<span class="badge bg-danger badge-end">P</span>
</a>
</li>
<li>
<a href="docs-smartnavigationjs.html">
<span class="nav-link-text" data-i18n>Smart Navigation</span>
<span class="badge bg-danger badge-end">P</span>
</a>
</li>
<li>
<a href="docs-smartfilterjs.html">
<span class="nav-link-text" data-i18n>Smart Filter</span>
<span class="badge bg-warning text-dark badge-end">D</span>
</a>
</li>
<li>
<a href="docs-sortablejs.html">
<span class="nav-link-text" data-i18n>Sortable</span>
<span class="badge bg-warning text-dark badge-end">D</span>
</a>
</li>
<li>
<a href="docs-bootstrapbundlejs.html">
<span class="nav-link-text" data-i18n>Bootstrap Bundle</span>
<span class="badge bg-warning text-dark badge-end">D</span>
</a>
</li>
<li>
<a href="docs-smartslimscrolljs.html">
<span class="nav-link-text" data-i18n>Smart Slim Scroll</span>
<span class="badge bg-success badge-end">O</span>
</a>
</li>
<li>
<a href="docs-wavesjs.html">
<span class="nav-link-text" data-i18n>Waves</span>
<span class="badge bg-success badge-end">O</span>
</a>
</li>
<li>
<a href="docs-apexbarchartjs.html">
<span class="nav-link-text" data-i18n>Apex Chart Js</span>
</a>
</li>
<li>
<a href="docs-easypiechartjs.html">
<span class="nav-link-text" data-i18n>Easy Pie Chart Js</span>
</a>
</li>
<li>
<a href="docs-fullcalendarjs.html">
<span class="nav-link-text" data-i18n>Full Calendar Js</span>
</a>
</li>
<li>
<a href="docs-peitychartsjs.html">
<span class="nav-link-text" data-i18n>Peity Chart Js</span>
</a>
</li>
<li>
<a href="docs-smarttablesjs.html">
<span class="nav-link-text" data-i18n>Smart Table Js</span>
</a>
</li>
<li>
<a href="docs-streamlinejs.html">
<span class="nav-link-text" data-i18n>Stream Line Js</span>
</a>
</li>
</ul>
</li>
<li class="nav-title"><span>Layouts</span></li>
<li class="nav-item">
<a href="#" title="Access Control" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#slash"></use>
</svg>
<span class="nav-link-text" data-i18n>Access Control</span>
</a>
<ul>
<li class="nav-item">
<a href="#" title="Authentication" data-filter-tags>
<span class="nav-link-text" data-i18n>Authentication Pages</span>
</a>
<ul>
<li>
<a href="auth-login.html">
<span class="nav-link-text" data-i18n>Login</span>
</a>
</li>
<li>
<a href="auth-register.html">
<span class="nav-link-text" data-i18n>Register</span>
</a>
</li>
<li>
<a href="auth-forgetpassword.html">
<span class="nav-link-text" data-i18n>Forget Password</span>
</a>
</li>
<li>
<a href="auth-twofactor.html">
<span class="nav-link-text" data-i18n>2FA</span>
</a>
</li>
<li>
<a href="auth-lockscreen.html">
<span class="nav-link-text" data-i18n>Lock Screen</span>
</a>
</li>
</ul>
</li>
<li>
<a href="emaildesign.html">
<span class="nav-link-text" data-i18n>Email Templates</span>
</a>
</li>
<li class="nav-item">
<a href="#" title="Error Pages" data-filter-tags>
<span class="nav-link-text" data-i18n>Error Pages</span>
</a>
<ul>
<li>
<a href="error-404.html">
<span class="nav-link-text" data-i18n>404 Not Found</span>
</a>
</li>
<li>
<a href="error-404-2.html">
<span class="nav-link-text" data-i18n>404 Not Found 2</span>
</a>
</li>
<li>
<a href="error-500.html">
<span class="nav-link-text" data-i18n>500 Internal Server</span>
</a>
</li>
</ul>
</li>
<li>
<a href="profile.html">
<span class="nav-link-text" data-i18n>User Profile</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Communication" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#message-square"></use>
</svg>
<span class="nav-link-text" data-i18n>Communication</span>
</a>
<ul>
<li>
<a href="messenger.html">
<span class="nav-link-text" data-i18n>Messenger & Chat</span>
</a>
</li>
<li class="nav-item">
<a href="#" title="Email" data-filter-tags>
<span class="nav-link-text" data-i18n>Email</span>
</a>
<ul>
<li>
<a href="systemmail.html">
<span class="nav-link-text" data-i18n>System Mail</span>
</a>
</li>
<li>
<a href="systemmail-read.html">
<span class="nav-link-text" data-i18n>Mail Read</span>
</a>
</li>
</ul>
</li>
<li>
<a href="usercontact.html">
<span class="nav-link-text" data-i18n>User Contact</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Miscellaneous" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#archive"></use>
</svg>
<span class="nav-link-text" data-i18n>Miscellaneous</span>
</a>
<ul>
<li>
<a href="forum.html">
<span class="nav-link-text" data-i18n>Forum General</span>
</a>
</li>
<li>
<a href="forum-threads.html">
<span class="nav-link-text" data-i18n>Forum Threads</span>
</a>
</li>
<li>
<a href="forum-discussion.html">
<span class="nav-link-text" data-i18n>Forum Discussions</span>
</a>
</li>
<li>
<a href="search.html">
<span class="nav-link-text" data-i18n>Search</span>
</a>
</li>
</ul>
</li>
<li>
<a href="landing.html" target="_blank">
<svg class="sa-icon">
<use href="icons/sprite.svg#zap"></use>
</svg>
<span class="nav-link-text">Landing</span>
</a>
</li>
<li class="nav-title"><span>Design</span></li>
<li class="nav-item">
<a href="#" title="UI Components" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#layers"></use>
</svg>
<span class="nav-link-text" data-i18n>UI Components</span>
</a>
<ul>
<li>
<a href="ui-alerts.html">
<span class="nav-link-text" data-i18n>Alerts</span>
</a>
</li>
<li>
<a href="ui-accordions.html">
<span class="nav-link-text" data-i18n>Accordions</span>
</a>
</li>
<li>
<a href="ui-badges.html">
<span class="nav-link-text" data-i18n>Badges</span>
</a>
</li>
<li>
<a href="ui-buttons.html">
<span class="nav-link-text" data-i18n>Buttons</span>
</a>
</li>
<li>
<a href="ui-buttongroup.html">
<span class="nav-link-text" data-i18n>Button Group</span>
</a>
</li>
<li>
<a href="ui-cards.html">
<span class="nav-link-text" data-i18n>Cards</span>
</a>
</li>
<li>
<a href="ui-breadcrumbs.html">
<span class="nav-link-text" data-i18n>Breadcrumbs</span>
</a>
</li>
<li>
<a href="ui-dropdowns.html">
<span class="nav-link-text" data-i18n>Dropdowns</span>
</a>
</li>
<li>
<a href="ui-navbars.html">
<span class="nav-link-text" data-i18n>Navbars</span>
</a>
</li>
<li>
<a href="ui-pagination.html">
<span class="nav-link-text" data-i18n>Pagination</span>
</a>
</li>
<li>
<a href="ui-scrollspy.html">
<span class="nav-link-text" data-i18n>ScrollSpy</span>
</a>
</li>
<li>
<a href="ui-collapse.html">
<span class="nav-link-text" data-i18n>Collapse</span>
</a>
</li>
<li>
<a href="ui-modal.html">
<span class="nav-link-text" data-i18n>Modal</span>
</a>
</li>
<li>
<a href="ui-tabs-pills.html">
<span class="nav-link-text" data-i18n>Tabs & Pills</span>
</a>
</li>
<li>
<a href="ui-tooltips.html">
<span class="nav-link-text" data-i18n>Tooltips</span>
</a>
</li>
<li>
<a href="ui-popovers.html">
<span class="nav-link-text" data-i18n>Popovers</span>
</a>
</li>
<li>
<a href="ui-toasts.html">
<span class="nav-link-text" data-i18n>Toasts</span>
</a>
</li>
<li>
<a href="ui-progressbars.html">
<span class="nav-link-text" data-i18n>Progress Bars</span>
</a>
</li>
<li>
<a href="ui-spinners.html">
<span class="nav-link-text" data-i18n>Spinners</span>
</a>
</li>
<li>
<a href="ui-carousels.html">
<span class="nav-link-text" data-i18n>Carousels</span>
</a>
</li>
<li>
<a href="ui-panels.html">
<span class="nav-link-text" data-i18n>Panels</span>
</a>
</li>
<li>
<a href="ui-list-filter.html">
<span class="nav-link-text" data-i18n>List Filter</span>
</a>
</li>
<li>
<a href="ui-sidepanels.html">
<span class="nav-link-text" data-i18n>Side Panels</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="System Utilities" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#package"></use>
</svg>
<span class="nav-link-text" data-i18n>System Utilities</span>
</a>
<ul>
<li>
<a href="utilities-borders.html">
<span class="nav-link-text" data-i18n>Borders</span>
</a>
</li>
<li>
<a href="utilities-display-property.html">
<span class="nav-link-text" data-i18n>Display Property</span>
</a>
</li>
<li>
<a href="utilities-responsivegrid.html">
<span class="nav-link-text" data-i18n>Responsive Grid</span>
</a>
</li>
<li>
<a href="utilities-position.html">
<span class="nav-link-text" data-i18n>Position</span>
</a>
</li>
<li>
<a href="utilities-colorpalette.html">
<span class="nav-link-text" data-i18n>Color Palette</span>
</a>
</li>
<li>
<a href="utilities-typography.html">
<span class="nav-link-text" data-i18n>Typography</span>
</a>
</li>
<li>
<a href="utilities-sizing.html">
<span class="nav-link-text" data-i18n>Sizing</span>
</a>
</li>
<li>
<a href="utilities-spacing.html">
<span class="nav-link-text" data-i18n>Spacing</span>
</a>
</li>
<li>
<a href="utilities-flexbox.html">
<span class="nav-link-text" data-i18n>Flexbox</span>
</a>
</li>
<li>
<a href="utilities-helpers.html">
<span class="nav-link-text" data-i18n>Helpers</span>
</a>
</li>
<li>
<a href="utilities-visibility-generator.html">
<span class="nav-link-text" data-i18n>Visibility Generator</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Iconography" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#heart"></use>
</svg>
<span class="nav-link-text" data-i18n>Iconography</span>
</a>
<ul>
<li>
<a href="icons-system.html">
<span class="nav-link-text" data-i18n>System Icons</span>
</a>
</li>
<li>
<a href="icons-fontawesome.html">
<span class="nav-link-text" data-i18n>FontAwesome 5.3</span>
</a>
</li>
<li>
<a href="icons-smartadmin.html">
<span class="nav-link-text" data-i18n>SmartAdmin Icons 1.0</span>
</a>
</li>
<li>
<a href="icons-stackgenerator.html">
<span class="nav-link-text" data-i18n>Stack Generator</span>
</a>
</li>
<li>
<a href="icons-stacklibrary.html">
<span class="nav-link-text" data-i18n>Stack Library</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Tables" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#table"></use>
</svg>
<span class="nav-link-text" data-i18n>Tables</span>
</a>
<ul>
<li>
<a href="tables-basic.html">
<span class="nav-link-text" data-i18n>Basic Tables</span>
</a>
</li>
<li>
<a href="tables-style-generator.html">
<span class="nav-link-text" data-i18n>Tables Style Generator</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Forms" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#edit"></use>
</svg>
<span class="nav-link-text" data-i18n>Forms</span>
</a>
<ul>
<li>
<a href="forms-inputs.html">
<span class="nav-link-text" data-i18n>Inputs</span>
</a>
</li>
<li>
<a href="forms-checkbox-radio.html">
<span class="nav-link-text" data-i18n>Checkbox & Radio</span>
</a>
</li>
<li>
<a href="forms-groups.html">
<span class="nav-link-text" data-i18n>Groups</span>
</a>
</li>
<li>
<a href="forms-validation.html">
<span class="nav-link-text" data-i18n>Validation</span>
</a>
</li>
</ul>
</li>
<li class="nav-title"><span>Data Visualization</span></li>
<li class="nav-item">
<a href="#" title="Smart Tables" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#database"></use>
</svg>
<span class="nav-link-text" data-i18n>Smart Tables</span>
<span class="badge bg-primary-500 badge-end">1.2.7</span>
</a>
<ul>
<li>
<a href="smarttables-minimal.html">
<span class="nav-link-text" data-i18n>Minimal Settings</span>
</a>
</li>
<li>
<a href="smarttables-responsive.html">
<span class="nav-link-text" data-i18n>Responsive Settings</span>
</a>
</li>
<li>
<a href="smarttables-importexport-data.html">
<span class="nav-link-text" data-i18n>Import & Export Data</span>
</a>
</li>
<li>
<a href="smarttables-json-source.html">
<span class="nav-link-text" data-i18n>JSON Data Source</span>
</a>
</li>
<li>
<a href="smarttables-manage-records.html">
<span class="nav-link-text" data-i18n>Manage Records</span>
<span class="badge bg-warning text-dark badge-end">New</span>
</a>
</li>
<li>
<a href="smarttables-fuzzy-matching.html">
<span class="nav-link-text" data-i18n>Fuzzy Matching</span>
</a>
</li>
<li>
<a href="smarttables-server-side.html">
<span class="nav-link-text" data-i18n>Server-Side Mode</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Apex Charts" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#pie-chart"></use>
</svg>
<span class="nav-link-text" data-i18n>Apex Charts</span>
</a>
<ul>
<li>
<a href="apex-area.html">
<span class="nav-link-text" data-i18n>Area Charts</span>
</a>
</li>
<li>
<a href="apex-bar.html">
<span class="nav-link-text" data-i18n>Bar Charts</span>
</a>
</li>
<li>
<a href="apex-box-whisker.html">
<span class="nav-link-text" data-i18n>Box & Whisker Charts</span>
</a>
</li>
<li>
<a href="apex-bubble.html">
<span class="nav-link-text" data-i18n>Bubble Charts</span>
</a>
</li>
<li>
<a href="apex-candlestick.html">
<span class="nav-link-text" data-i18n>Candlestick Charts</span>
</a>
</li>
<li>
<a href="apex-column.html">
<span class="nav-link-text" data-i18n>Column Charts</span>
</a>
</li>
<li>
<a href="apex-funnel.html">
<span class="nav-link-text" data-i18n>Funnel Charts</span>
</a>
</li>
<li>
<a href="apex-heatmap.html">
<span class="nav-link-text" data-i18n>Heatmap Charts</span>
</a>
</li>
<li>
<a href="apex-line.html">
<span class="nav-link-text" data-i18n>Line Charts</span>
</a>
</li>
<li>
<a href="apex-mixed-combo.html">
<span class="nav-link-text" data-i18n>Mixed/Combo Charts</span>
</a>
</li>
<li>
<a href="apex-pie-donut.html">
<span class="nav-link-text" data-i18n>Pie/Donuts</span>
</a>
</li>
<li>
<a href="apex-polar-area.html">
<span class="nav-link-text" data-i18n>Polar Area Charts</span>
</a>
</li>
<li>
<a href="apex-radar.html">
<span class="nav-link-text" data-i18n>Radar Charts</span>
</a>
</li>
<li>
<a href="apex-radialbars-circle.html">
<span class="nav-link-text" data-i18n>RadialBars/Circle Charts</span>
</a>
</li>
<li>
<a href="apex-range-area.html">
<span class="nav-link-text" data-i18n>Range Area Charts</span>
</a>
</li>
<li>
<a href="apex-scatter.html">
<span class="nav-link-text" data-i18n>Scatter Charts</span>
</a>
</li>
<li>
<a href="apex-slope.html">
<span class="nav-link-text" data-i18n>Slope Charts</span>
</a>
</li>
<li>
<a href="apex-sparkline.html">
<span class="nav-link-text" data-i18n>Sparklines</span>
</a>
</li>
<li>
<a href="apex-timeline.html">
<span class="nav-link-text" data-i18n>Timeline Charts</span>
</a>
</li>
<li>
<a href="apex-treemap.html">
<span class="nav-link-text" data-i18n>Treemap Charts</span>
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="#" title="Data Bites" data-filter-tags>
<svg class="sa-icon">
<use href="icons/sprite.svg#grid"></use>
</svg>
<span class="nav-link-text" data-i18n>Data Bites</span>
</a>
<ul>
<li>
<a href="peity-charts.html">
<span class="nav-link-text" data-i18n>Peity Charts</span>
</a>
</li>
<li>
<a href="streamline.html">
<span class="nav-link-text" data-i18n>Streamline</span>
</a>
</li>
<li>
<a href="easy-pie-chart.html">
<span class="nav-link-text" data-i18n>Easy Pie Chart</span>
</a>
</li>
</ul>
</li>
<li>
<a href="fullcalendar.html">
<svg class="sa-icon">
<use href="icons/sprite.svg#calendar"></use>
</svg>
<span class="nav-link-text">Full Calendar</span>
</a>
</li>
</ul>
<div class="no-results-msg pt-3 info-container">
<h6 class="mb-1"> No menu items found.</h6>
<p class="fs-sm">Try searching with different keywords.</p>
<div class="d-flex align-items-center gap-1 fs-xs fw-500 font-style-italic">
<kbd class="kbd-key">
<svg width="15" height="15" aria-label="Escape key" role="img">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2">
<path d="M13.6167 8.936c-.1065.3583-.6883.962-1.4875.962-.7993 0-1.653-.9165-1.653-2.1258v-.5678c0-1.2548.7896-2.1016 1.653-2.1016.8634 0 1.3601.4778 1.4875 1.0724M9 6c-.1352-.4735-.7506-.9219-1.46-.8972-.7092.0246-1.344.57-1.344 1.2166s.4198.8812 1.3445.9805C8.465 7.3992 8.968 7.9337 9 8.5c.032.5663-.454 1.398-1.4595 1.398C6.6593 9.898 6 9 5.963 8.4851m-1.4748.5368c-.2635.5941-.8099.876-1.5443.876s-1.7073-.6248-1.7073-2.204v-.4603c0-1.0416.721-2.131 1.7073-2.131.9864 0 1.6425 1.031 1.5443 2.2492h-2.956"></path>
</g>
</svg>
</kbd> to reset
</div>
</div>
</nav>
<div class="nav-footer">
<svg class="sa-icon sa-thin">
<use href="icons/sprite.svg#wifi"></use>
</svg>
</div>
</aside>
<div class="backdrop" data-action="toggle-swap" data-toggleclass="app-mobile-menu-open"></div>
<!-- Sidebar Link Active Script -->
<script>
const sideNavMenu = document.getElementById("js-nav-menu");
const currentUrl = window.location.href.split(/[?#]/)[0]; // Match current URL
const allLinks = sideNavMenu.querySelectorAll("a");
const allCollapses = sideNavMenu.querySelectorAll("li [aria-expanded='true']"); // Ensure only one collapse is open at a time
// Prevent default toggle behavior for toggle links
sideNavMenu.querySelectorAll("li .has-ul").forEach((toggle) => { toggle.addEventListener("click", (e) => e.preventDefault()); });
allCollapses.forEach((collapse) => {
console.log(collapse)
collapse.addEventListener("show.bs.collapse", (event) => {
const currentCollapse = event.target;
// Get all ancestor collapses of the current item
const ancestors = [];
let el = currentCollapse.parentElement;
while (el && el !== sideNavMenu) {
if (el.classList.contains("collapse")) {
ancestors.push(el);
}
el = el.parentElement;
}
allCollapses.forEach((other) => {
if (other !== currentCollapse && !ancestors.includes(other)) {
new bootstrap.Collapse(other, { toggle: false }).hide();
}
});
});
});
allLinks.forEach((link) => {
if (link.href === currentUrl) {
link.classList.add("active");
// Traverse up to activate all parents and show collapses
let currentElement = link.closest("li");
while (currentElement && currentElement !== sideNavMenu) {
currentElement.classList.add("active");
// Show parent collapses
const parentCollapse = currentElement.closest("[role='menu']");
if (parentCollapse) {
new bootstrap.Collapse(parentCollapse, { toggle: false }).show();
// Also mark the <li> that contains the collapse as active
const collapseParentLi = parentCollapse.closest("li");
if (collapseParentLi) {
collapseParentLi.classList.add("active");
}
currentElement = collapseParentLi;
} else {
currentElement = currentElement.parentElement;
}
}
}
});
</script>
<main class="app-body">
<div class="app-content">
<div class="content-wrapper">
<h1 class="subheader-title mb-2">smartTables.js</h1>
<nav class="app-breadcrumb" aria-label="breadcrumb">
<ol class="breadcrumb ms-0 text-muted mb-0">
<li class="breadcrumb-item">Config</li>
<li class="breadcrumb-item">Documentation</li>
<li class="breadcrumb-item active" aria-current="page">smartTables.js</li>
</ol>
</nav>
<h6 class="mt-3 mb-4 fst-italic"></h6>
<div class="main-content">
<div class="row">
<div class="order-2 order-xl-1 col-lg-12 col-xl-9">
<section id="smarttables" class="concept-section">
<h2 class="concept-title with-lead">Smarter Tables, Less Hassle!</h2>
<p>SmartTables.js is a powerful, feature-rich JavaScript plugin designed to enhance HTML tables with advanced
functionality such as sorting, searching, pagination, responsive behavior, and data export capabilities.
It is built with modern web development practices in mind, offering a flexible and extensible API for developers
to customize and extend its functionality.</p>
<p>
This plugin is ideal for developers who need to manage large datasets in a user-friendly way, providing a seamless
experience for end-users while maintaining a high level of customization and control.
</p>
<div class="pro-tip">
<strong>Pro Tip:</strong> SmartTables works with your existing HTML tables and automatically adapts to different screen sizes by hiding less important columns on smaller screens.
</div>
</section>
<section id="smarttables-installation" class="concept-section">
<h2 class="concept-title">Installation</h2>
<p>SmartTables.js is now available as an ES6 module. You can include it in your project in several ways:</p>
<h4>1. Using ES6 Modules (Recommended)</h4>
<div class="code-example">
<pre><code>// Import the SmartTables class
import { SmartTables } from './optional/smartTables/smartTables.bundle.js';
// Initialize with empty options object (required)
const myTable = new SmartTables('tableId', {});
// Or with custom options
const myTable = new SmartTables('tableId', {
perPage: 15,
search: true,
sort: true,
pagination: true,
export: true
});</code></pre>
</div>
<div class="warning">
<strong>Important:</strong>
<ul class="mb-0">
<li>You must pass an empty options object (<code>{}</code>) as the second parameter when initializing SmartTables, even if you don't need any custom options.</li>
<li>This is required because the constructor expects an options object to properly initialize default values.</li>
<li>If you don't pass the options object, you'll get an error: <code>Cannot read properties of undefined (reading 'responsive')</code>.</li>
</ul>
</div>
<h4>2. Using NPM (for modern build systems)</h4>
<div class="code-example">
<pre><code>// Install via npm
npm install smarttables
// Import in your JavaScript/TypeScript file
import { SmartTables } from 'smarttables';
// Use in your component
const myTable = new SmartTables('tableId', {
data: {
type: 'json',
source: myData,
columns: [
{ data: 'id', title: 'ID' },
{ data: 'name', title: 'Name' },
{ data: 'position', title: 'Position' }
]
}
});</code></pre>
</div>
<h4>3. Using with TypeScript</h4>
<div class="code-example">
<pre><code>import { SmartTables, SmartTablesOptions } from 'smarttables';
interface CustomOptions extends SmartTablesOptions {
customFeature?: boolean;
}
const options: CustomOptions = {
perPage: 10,
search: true,
customFeature: true
};
const myTable = new SmartTables('tableId', options);</code></pre>
</div>
<h4>HTML Structure</h4>
<div class="code-example">
<pre><code>&lt;table id="myTable" class="table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th data-priority="1"&gt;ID&lt;/th&gt;
&lt;th data-priority="2"&gt;Name&lt;/th&gt;
&lt;th data-priority="3"&gt;Position&lt;/th&gt;
&lt;th data-priority="4"&gt;Office&lt;/th&gt;
&lt;th data-priority="5"&gt;Age&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;John Doe&lt;/td&gt;
&lt;td&gt;Developer&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;!-- More rows... --&gt;
&lt;/tbody&gt;
&lt;/table&gt;</code></pre>
</div>
<div class="warning">
<strong>Important:</strong>
<ul class="mb-0">
<li>Use the <code>data-priority</code> attribute on table headers to control which columns are hidden first when the table becomes too narrow for the viewport.</li>
<li>When using ES6 modules, make sure your server is configured to serve JavaScript files with the correct MIME type (<code>application/javascript</code>).</li>
<li>For older browsers, you'll need to use a bundler like Webpack, Rollup, or Parcel to transpile the ES6 code.</li>
</ul>
</div>
</section>
<section id="smarttables-configuration" class="concept-section">
<h2 class="concept-title">Configuration Options</h2>
<p>SmartTables.js provides extensive configuration options to customize its behavior. All options are optional and will fall back to sensible defaults.</p>
<div class="code-example">
<pre><code>// Basic configuration
const table = new SmartTables('myTable', {
perPage: 10,
search: true,
sort: true
});
// Advanced configuration with TypeScript
interface CustomOptions extends SmartTablesOptions {
customFeature?: boolean;
}
const options: CustomOptions = {
perPage: 25,
search: true,
sort: true,
pagination: true,
export: true,
loading: {
enabled: true,
duration: 500,
minDuration: 300
},
responsive: {
enabled: true,
breakpoint: 992,
columnPriorities: {
0: 1, // ID column - highest priority
1: 2, // Name column - second highest priority
2: 3, // Position
3: 4, // Office
4: 5 // Age - lowest priority
}
},
debug: false,
fuzzyMatch: {
threshold: 0.6,
minMatchLength: 2,
multiWordThreshold: 0.5,
maxDistance: 3
},
customFeature: true
};
const table = new SmartTables('myTable', options);</code></pre>
</div>
<div class="table-responsive mb-4 mt-4">
<table class="table table-striped table-clean">
<thead>
<tr>
<th>Option</th>
<th>Default Value</th>
<th>Description</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<!-- Top-Level Options -->
<tr>
<td><code>perPage</code></td>
<td><strong>10</strong></td>
<td>Number of rows to display per page in pagination.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>search</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables the search functionality for the table.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>sort</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables sorting functionality for table columns.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>pagination</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables pagination controls for the table.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>export</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables export functionality (e.g., Excel, CSV, Copy).</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<!-- Loading Configuration -->
<tr>
<td><code>loading.enabled</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables the loading spinner/indicator during data loading or processing.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>loading.duration</code></td>
<td><strong>0</strong></td>
<td>Duration (in milliseconds) of an artificial delay for the loading indicator.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>loading.minDuration</code></td>
<td><strong>300</strong></td>
<td>Minimum duration (in milliseconds) to display the loading indicator.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<!-- Responsive Configuration -->
<tr>
<td><code>responsive.enabled</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables or disables responsive behavior (e.g., hiding columns on small screens).</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>responsive.breakpoint</code></td>
<td><strong>768</strong></td>
<td>Breakpoint (in pixels) at which responsive behavior starts.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>responsive.columnPriorities</code></td>
<td><strong>{}</strong></td>
<td>Object mapping column indices to their priority (1 being highest).</td>
<td><strong class="text-dark">Object&lt;number, number&gt;</strong></td>
</tr>
<tr>
<td><code>responsive.details.type</code></td>
<td><strong>'column'</strong></td>
<td>Type of detail display for hidden columns.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>responsive.details.target</code></td>
<td><strong>0</strong></td>
<td>Target column index for the detail control.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<!-- Debug Option -->
<tr>
<td><code>debug</code></td>
<td><strong class="text-primary">false</strong></td>
<td>Enables or disables debug logging for development and troubleshooting.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<!-- Fuzzy Match Configuration -->
<tr>
<td><code>fuzzyMatch.threshold</code></td>
<td><strong>0.7</strong></td>
<td>Threshold for fuzzy matching (0.01.0).</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>fuzzyMatch.minMatchLength</code></td>
<td><strong>2</strong></td>
<td>Minimum number of characters required for fuzzy matching.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>fuzzyMatch.multiWordThreshold</code></td>
<td><strong>0.5</strong></td>
<td>Threshold for multi-word search matches.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>fuzzyMatch.maxDistance</code></td>
<td><strong>2</strong></td>
<td>Maximum Levenshtein distance allowed for typo tolerance.</td>
<td><strong class="text-success">number</strong></td>
</tr>
<!-- Classes Configuration -->
<tr>
<td><code>classes.wrapper</code></td>
<td><strong>'st-wrapper'</strong></td>
<td>Class for the wrapper div containing the table.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>classes.table</code></td>
<td><strong>'st-table table table-striped table-hover'</strong></td>
<td>Class for the table element.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>classes.toolbar</code></td>
<td><strong>'st-toolbar d-flex justify-content-between mb-3'</strong></td>
<td>Class for the toolbar containing controls.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>classes.search</code></td>
<td><strong>'st-search form-control'</strong></td>
<td>Class for the search input field.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>classes.pagination</code></td>
<td><strong>'st-pagination pagination justify-content-center'</strong></td>
<td>Class for the pagination controls.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>classes.export</code></td>
<td><strong>'st-export btn-group'</strong></td>
<td>Class for the export button group.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<!-- Data Configuration -->
<tr>
<td><code>data.type</code></td>
<td><strong class="text-danger">null</strong></td>
<td>Type of data source ('json', 'csv', 'ajax', or null for DOM-based tables).</td>
<td><strong class="text-danger">string|null</strong></td>
</tr>
<tr>
<td><code>data.source</code></td>
<td><strong class="text-danger">null</strong></td>
<td>Source of the data (URL, string, or object depending on type).</td>
<td><strong class="text-danger">string|null|Object</strong></td>
</tr>
<tr>
<td><code>data.columns</code></td>
<td><strong>[]</strong></td>
<td>Array of column definitions.</td>
<td><strong class="text-info">Array</strong></td>
</tr>
<tr>
<td><code>data.processing</code></td>
<td><strong class="text-primary">false</strong></td>
<td>Enables or disables a processing indicator during data operations.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>data.serverSide</code></td>
<td><strong class="text-primary">false</strong></td>
<td>Enables or disables server-side processing for large datasets.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>data.method</code></td>
<td><strong>'GET'</strong></td>
<td>HTTP method for AJAX requests.</td>
<td><strong class="text-danger">string</strong></td>
</tr>
<tr>
<td><code>data.headers</code></td>
<td><strong>{}</strong></td>
<td>Custom headers for AJAX requests.</td>
<td><strong class="text-dark">Object</strong></td>
</tr>
<tr>
<td><code>data.params</code></td>
<td><strong>{}</strong></td>
<td>Additional parameters for AJAX requests.</td>
<td><strong class="text-dark">Object</strong></td>
</tr>
<tr>
<td><code>data.parser</code></td>
<td><strong class="text-danger">null</strong></td>
<td>Custom parser function for transforming raw data.</td>
<td><strong class="text-dark">Function|null</strong></td>
</tr>
<tr>
<td><code>data.prefetch</code></td>
<td><strong class="text-primary">true</strong></td>
<td>Enables prefetching of the next page data for smoother pagination.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>data.cacheDuration</code></td>
<td><strong>300000</strong></td>
<td>Duration in milliseconds to keep cached data (default: 5 minutes).</td>
<td><strong class="text-success">number</strong></td>
</tr>
<tr>
<td><code>data.withCredentials</code></td>
<td><strong class="text-primary">false</strong></td>
<td>Whether to include credentials in cross-domain requests.</td>
<td><strong class="text-primary">boolean</strong></td>
</tr>
<tr>
<td><code>data.url</code></td>
<td><strong class="text-danger">null</strong></td>
<td>The URL to fetch data from (aliased with source for compatibility).</td>
<td><strong class="text-danger">string|null</strong></td>
</tr>
<!-- Hooks Configuration -->
<tr>
<td><code>hooks</code></td>
<td><strong>{}</strong></td>
<td>Object containing various lifecycle and event hooks.</td>
<td><strong class="text-dark">Object</strong></td>
</tr>
<!-- Plugins Configuration -->
<tr>
<td><code>plugins</code></td>
<td><strong>[]</strong></td>
<td>Array of plugin objects to extend functionality.</td>
<td><strong class="text-info">Array</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="pro-tip">
<strong>Pro Tip:</strong> You can extend the default options by creating a custom interface that extends <code>SmartTablesOptions</code> when using TypeScript. This provides better type safety and autocompletion.
</div>
</section>
<section id="smarttables-advanced-configuration" class="concept-section">
<h2 class="concept-title">Advanced Configuration Example</h2>
<p>Here's an example of how to configure SmartTables with advanced options:</p>
<div class="code-example">
<pre><code>var myTable = new SmartTables('tableId', {
perPage: 25,
search: true,
sort: true,
pagination: true,
export: true,
loading: {
enabled: true,
duration: 500, // Show loading for at least 500ms
minDuration: 300
},
responsive: {
enabled: true,
breakpoint: 992,
columnPriorities: {
0: 1, // ID column - highest priority
1: 2, // Name column - second highest priority
2: 3, // Position
3: 4, // Office
4: 5 // Age - lowest priority
}
},
debug: false,
fuzzyMatch: {
threshold: 0.6, // Lower threshold = more matches
minMatchLength: 2, // Minimum characters to match
multiWordThreshold: 0.5,
maxDistance: 3 // Higher distance = more tolerance for typos
}
});</code></pre>
</div>
</section>
<section id="smarttables-data-loading" class="concept-section">
<h2 class="concept-title">Data Loading Methods</h2>
<p>SmartTables supports various data loading methods:</p>
<div class="code-example">
<pre><code>// Load from JSON data
var myTable = new SmartTables('tableId', {
data: {
type: 'json',
source: [
{ id: 1, name: 'John Doe', position: 'Developer', office: 'New York', age: 32 },
{ id: 2, name: 'Jane Smith', position: 'Designer', office: 'London', age: 28 }
],
columns: [
{ data: 'id', title: 'ID' },
{ data: 'name', title: 'Name' },
{ data: 'position', title: 'Position' },
{ data: 'office', title: 'Office' },
{ data: 'age', title: 'Age' }
]
}
});
// Load from AJAX
var myTable = new SmartTables('tableId', {
data: {
type: 'ajax',
source: 'api/users',
method: 'GET',
headers: { 'Authorization': 'Bearer token123' },
params: { limit: 100 }
}
});
// Load from CSV
var myTable = new SmartTables('tableId', {
data: {
type: 'csv',
source: 'data/employees.csv'
}
});</code></pre>
</div>
</section>
<section id="smarttables-data-import" class="concept-section">
<h2 class="concept-title">Data Import Functionality</h2>
<p>SmartTables provides built-in data import capabilities that allow users to import data from various file formats directly into the table. This feature can be enabled by setting the <code>import: true</code> option.</p>
<div class="code-example">
<pre><code>// Initialize table with import enabled
var myTable = new SmartTables('tableId', {
import: true, // Enable import functionality
data: {
type: 'json',
source: initialData,
columns: [
{ data: 'id', title: 'ID' },
{ data: 'name', title: 'Name' },
{ data: 'position', title: 'Position' }
]
}
});</code></pre>
</div>
<h4>Supported Import Formats</h4>
<ul class="list-spaced">
<li><strong>CSV Files:</strong> Import data from comma-separated value files</li>
<li><strong>JSON Files:</strong> Import data from JSON format files</li>
<li><strong>Excel Files:</strong> Import data from Excel spreadsheets (.xlsx)</li>
<li><strong>Text Files:</strong> Import data from plain text files with custom delimiters</li>
</ul>
<h4>Import Options</h4>
<div class="code-example">
<pre><code>var myTable = new SmartTables('tableId', {
import: {
enabled: true,
formats: ['csv', 'json', 'xlsx'], // Specify allowed formats
maxFileSize: 5 * 1024 * 1024, // 5MB max file size
onImportStart: function() {
console.log('Import started');
},
onImportComplete: function(data) {
console.log('Import completed', data);
},
onImportError: function(error) {
console.error('Import failed', error);
}
}
});</code></pre>
</div>
<h4>Example Usage</h4>
<div class="code-example">
<pre><code>&lt;!-- HTML Structure --&gt;
&lt;table id="myTable" class="table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th data-priority="1"&gt;ID&lt;/th&gt;
&lt;th data-priority="2"&gt;Name&lt;/th&gt;
&lt;th data-priority="3"&gt;Position&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;!-- Data will be populated by SmartTables --&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;!-- JavaScript --&gt;
import { SmartTables } from 'smarttables';
const table = new SmartTables('myTable', {
import: true,
data: {
type: 'json',
source: [], // Start with empty data
columns: [
{ data: 'id', title: 'ID' },
{ data: 'name', title: 'Name' },
{ data: 'position', title: 'Position' }
]
}
});</code></pre>
</div>
<div class="pro-tip">
<strong>Pro Tip:</strong> When importing data, SmartTables automatically detects the column structure and maps the imported data to the appropriate columns. You can also provide custom column mappings for more control over the import process.
</div>
<div class="warning">
<strong>Important:</strong>
<ul class="mb-0">
<li>Make sure your server is configured to handle file uploads if you're using the file import feature</li>
<li>Consider implementing file size limits and format validation on both client and server side</li>
<li>For large files, consider implementing chunked uploads or progress indicators</li>
<li>Always validate imported data before processing to prevent security issues</li>
</ul>
</div>
</section>
<section id="smarttables-event-hooks" class="concept-section">
<h2 class="concept-title">Event Hooks</h2>
<p>SmartTables provides hooks for various events in the table lifecycle:</p>
<div class="code-example">
<pre><code>var myTable = new SmartTables('tableId', {
hooks: {
// Table lifecycle hooks
beforeInit: function(instance) {
console.log('Before table initialization');
},
afterInit: function(instance) {
console.log('Table initialized!');
},
// Data hooks
beforeDataLoad: function(data, instance) {
console.log('About to load data');
},
afterDataLoad: function(data, instance) {
console.log('Data loaded successfully');
},
// Action hooks
onSort: function(column, direction, instance) {
console.log('Table sorted by column', column, 'in', direction, 'direction');
},
onFilter: function(searchTerm, filteredRows, instance) {
console.log('Table filtered with term:', searchTerm);
},
onPaginate: function(pageNumber, instance) {
console.log('Page changed to', pageNumber);
}
}
});</code></pre>
</div>
</section>
<section id="smarttables-search-capabilities" class="concept-section">
<h2 class="concept-title">Search Capabilities</h2>
<p>SmartTables includes powerful search capabilities with support for:</p>
<ul class="list-spaced">
<li><strong>Fuzzy matching:</strong> Finds results even with typos or partial matches</li>
<li><strong>Special data types:</strong> Intelligently searches dates, numbers, emails, phone numbers</li>
<li><strong>Comparison operators:</strong> Support for <code>&gt;</code>, <code>&lt;</code>, <code>=</code> with numeric values</li>
<li><strong>Multi-word search:</strong> Matches records containing any or all search terms</li>
</ul>
<div class="pro-tip">
<strong>Pro Tip:</strong> Users can search with operators like "<code>&gt;30</code>" to find all numeric values greater than 30, or use "<code>@gmail.com</code>" to find all Gmail addresses.
</div>
</section>
<section id="smarttables-responsive-behavior" class="concept-section">
<h2 class="concept-title">Responsive Behavior</h2>
<p>SmartTables automatically adapts to different screen sizes by:</p>
<ul class="list-spaced">
<li>Hiding less important columns on smaller screens based on priority</li>
<li>Providing an expand/collapse interface to view hidden column data</li>
<li>Automatically measuring and optimizing column widths</li>
</ul>
<div class="code-example">
<pre><code>&lt;!-- Set column priorities with data attributes --&gt;
&lt;th data-priority="1"&gt;ID&lt;/th&gt; &lt;!-- Highest priority (last to hide) --&gt;
&lt;th data-priority="2"&gt;Name&lt;/th&gt;
&lt;th data-priority="3"&gt;Position&lt;/th&gt;
&lt;th data-priority="4"&gt;Office&lt;/th&gt;
&lt;th data-priority="5"&gt;Age&lt;/th&gt; &lt;!-- Lowest priority (first to hide) --&gt;
&lt;!-- Force columns to always remain visible --&gt;
&lt;th class="always-visible"&gt;Actions&lt;/th&gt;</code></pre>
</div>
</section>
<section id="smarttables-export-options" class="concept-section">
<h2 class="concept-title">Export Options</h2>
<p>SmartTables provides built-in export functionality:</p>
<ul class="list-spaced">
<li><strong>Excel:</strong> Export to XLSX format</li>
<li><strong>CSV:</strong> Export to CSV format</li>
<li><strong>Copy:</strong> Copy table data to clipboard</li>
</ul>
<div class="code-example">
<pre><code>// Get table instance
var table = document.getElementById('myTable').__smartTable;
// Export to different formats
table.exportData('excel');
table.exportData('csv');
table.exportData('copy');</code></pre>
</div>
</section>
<section id="smarttables-api-methods" class="concept-section">
<h2 class="concept-title">API Methods</h2>
<p>SmartTables exposes several methods for programmatic control:</p>
<div class="code-example">
<pre><code>// Get table instance
var table = document.getElementById('myTable').__smartTable;
// Redraw the table
table.draw();
// Sort by column
table.sortBy(2, 'asc'); // Sort by 3rd column ascending
// Filter the table
table.handleSearch('developer');
// Export data
table.exportData('excel');
// Hide/show columns
table.hideColumn(3);
table.showColumn(3);
// Clear AJAX cache for server-side tables
table.clearAjaxCache();
// Manually trigger prefetch of next page
table.prefetchNextPage();
// Generate a unique cache key for current parameters
const cacheKey = table.generateCacheKey();
// Destroy the instance
table.destroy();</code></pre>
</div>
<div class="table-responsive mb-4 mt-4">
<table class="table table-striped table-clean">
<thead>
<tr>
<th>Method</th>
<th>Description</th>
<th>Parameters</th>
<th>Return Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>draw()</code></td>
<td>Redraws the table with current settings</td>
<td>None</td>
<td>void</td>
</tr>
<tr>
<td><code>sortBy(columnIndex, direction)</code></td>
<td>Sorts the table by specified column</td>
<td>columnIndex: number, direction: 'asc'|'desc'</td>
<td>void</td>
</tr>
<tr>
<td><code>handleSearch(query)</code></td>
<td>Filters the table using the search query</td>
<td>query: string</td>
<td>void</td>
</tr>
<tr>
<td><code>exportData(format)</code></td>
<td>Exports table data in the specified format</td>
<td>format: 'excel'|'csv'|'copy'</td>
<td>void</td>
</tr>
<tr>
<td><code>hideColumn(index)</code></td>
<td>Hides the specified column</td>
<td>index: number</td>
<td>void</td>
</tr>
<tr>
<td><code>showColumn(index)</code></td>
<td>Shows the specified column</td>
<td>index: number</td>
<td>void</td>
</tr>
<tr>
<td><code>clearAjaxCache()</code></td>
<td>Clears all cached AJAX responses</td>
<td>None</td>
<td>void</td>
</tr>
<tr>
<td><code>prefetchNextPage()</code></td>
<td>Manually fetches and caches the next page data</td>
<td>None</td>
<td>void</td>
</tr>
<tr>
<td><code>generateCacheKey()</code></td>
<td>Generates a unique cache key for current parameters</td>
<td>None</td>
<td>string</td>
</tr>
<tr>
<td><code>destroy()</code></td>
<td>Destroys the table instance and removes event listeners</td>
<td>None</td>
<td>void</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="smarttables-plugin-system" class="concept-section">
<h2 class="concept-title">Plugin System</h2>
<p>SmartTables supports plugins to extend its functionality:</p>
<div class="code-example">
<pre><code>// Define a plugin
var myPlugin = {
name: 'myPlugin',
init: function() {
console.log('Plugin initialized for table:', this.instance.table.id);
},
afterDraw: function() {
console.log('Table was redrawn');
}
};
// Initialize table with the plugin
var myTable = new SmartTables('tableId', {
plugins: [myPlugin]
});</code></pre>
</div>
</section>
<section id="smarttables-performance-tips" class="concept-section">
<h2 class="concept-title">Performance Tips</h2>
<ul class="list-spaced">
<li>For large datasets (1000+ rows), consider using server-side processing</li>
<li>Set appropriate <code>perPage</code> values to limit the number of rows rendered at once</li>
<li>Use the <code>loading.duration</code> option to show a loading indicator for long operations</li>
<li>Disable features you don't need (search, sort, pagination) for simpler tables</li>
</ul>
</section>
<section id="smarttables-framework-integrations" class="concept-section">
<h2 class="concept-title">Framework Integrations</h2>
<p>SmartTables.js can be seamlessly integrated with various modern frameworks and backend technologies. Here's how to implement it in different environments:</p>
<div class="accordion" id="frameworkAccordion">
<!-- Frontend Frameworks -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#reactIntegration">
React Integration
</button>
</h3>
<div id="reactIntegration" class="accordion-collapse collapse" data-bs-parent="#frameworkAccordion">
<div class="accordion-body">
<!-- React Integration -->
<div class="mb-4">
<h4>React Integration</h4>
<div class="code-example">
<pre><code>import { useEffect, useRef } from 'react';
import { SmartTables } from 'smarttables';
const DataTable = ({ data, columns }) => {
const tableRef = useRef(null);
const tableInstance = useRef(null);
useEffect(() => {
if (tableRef.current) {
// Initialize table
tableInstance.current = new SmartTables(tableRef.current, {
data: {
type: 'json',
source: data,
columns
},
hooks: {
afterDraw: () => {
console.log('Table redrawn');
}
}
});
// Cleanup on unmount
return () => {
if (tableInstance.current) {
tableInstance.current.destroy();
}
};
}
}, [data, columns]);
return (
'<table ref={tableRef} className="table">' +
'<thead>' +
'<tr>' +
'{columns.map(column => (' +
'<th key={column.data} data-priority={column.priority}>' +
'{column.title}' +
'</th>' +
'))}' +
'</tr>' +
'</thead>' +
'<tbody>' +
'{/* Data will be populated by SmartTables */}' +
'</tbody>' +
'</table>'
);
};
// Usage example
const App = () => {
const columns = [
{ data: 'id', title: 'ID', priority: 1 },
{ data: 'name', title: 'Name', priority: 2 },
{ data: 'position', title: 'Position', priority: 3 }
];
const data = [
{ id: 1, name: 'John Doe', position: 'Developer' },
{ id: 2, name: 'Jane Smith', position: 'Designer' }
];
return <DataTable data=%7bdata%7d.html columns={columns} />
};</code></pre>
</div>
<div class="info mt-3">
<strong>React-Specific Tips:</strong>
<ul>
<li>Use <code>useRef</code> to maintain a stable reference to both the table element and instance</li>
<li>Initialize the table in <code>useEffect</code> to ensure the DOM is ready</li>
<li>Clean up the table instance in the effect's cleanup function</li>
<li>Consider using React's Context API for global table state management</li>
<li>Use TypeScript for better type safety</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#vueIntegration">
Vue.js Integration
</button>
</h3>
<div id="vueIntegration" class="accordion-collapse collapse" data-bs-parent="#frameworkAccordion">
<div class="accordion-body">
<!-- Vue.js Integration -->
<div class="mb-4">
<h4>Vue.js Integration (Composition API)</h4>
<div class="code-example">
<pre><code>import { onMounted, onBeforeUnmount, ref } from 'vue';
import { SmartTables } from 'smarttables';
export default {
setup(props) {
const tableRef = ref(null);
const tableInstance = ref(null);
onMounted(() => {
if (tableRef.value) {
tableInstance.value = new SmartTables(tableRef.value, {
data: {
type: 'json',
source: props.data,
columns: props.columns
},
hooks: {
afterDraw: () => {
console.log('Table redrawn');
}
}
});
}
});
onBeforeUnmount(() => {
if (tableInstance.value) {
tableInstance.value.destroy();
}
});
return {
tableRef
};
}
}
// Usage example
const App = {
template: '<DataTable ref="tableRef" :data="tableData" :columns="columns" />',
setup() {
const columns = [
{ data: 'id', title: 'ID', priority: 1 },
{ data: 'name', title: 'Name', priority: 2 },
{ data: 'position', title: 'Position', priority: 3 }
];
const tableData = ref([
{ id: 1, name: 'John Doe', position: 'Developer' },
{ id: 2, name: 'Jane Smith', position: 'Designer' }
]);
return {
columns,
tableData
};
}
};</code></pre>
</div>
<div class="info mt-3">
<strong>Vue.js-Specific Tips:</strong>
<ul>
<li>Use <code>ref</code> for template references and instance management</li>
<li>Initialize in <code>onMounted</code> hook</li>
<li>Clean up in <code>onBeforeUnmount</code> hook</li>
<li>Consider using Pinia for state management</li>
<li>Use TypeScript for better type safety</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#angularIntegration">
Angular Integration
</button>
</h3>
<div id="angularIntegration" class="accordion-collapse collapse" data-bs-parent="#frameworkAccordion">
<div class="accordion-body">
<!-- Angular Integration -->
<div class="mb-4">
<h4>Angular Integration</h4>
<div class="code-example">
<pre><code>import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { SmartTables } from 'smarttables';
@Component({
selector: 'app-data-table',
template: '<table #dataTable class="table"><thead><tr><th *ngFor="let column of columns" [attr.data-priority]="column.priority">{{column.title}}</th></tr></thead><tbody><!-- Data will be populated by SmartTables --></tbody></table>'
})
export class DataTableComponent implements OnInit, OnDestroy {
@ViewChild('dataTable') tableRef: ElementRef;
private tableInstance: SmartTables;
columns = [
{ data: 'id', title: 'ID', priority: 1 },
{ data: 'name', title: 'Name', priority: 2 },
{ data: 'position', title: 'Position', priority: 3 }
];
tableData = [
{ id: 1, name: 'John Doe', position: 'Developer' },
{ id: 2, name: 'Jane Smith', position: 'Designer' }
];
ngOnInit() {
if (this.tableRef.nativeElement) {
this.tableInstance = new SmartTables(this.tableRef.nativeElement, {
data: {
type: 'json',
source: this.tableData,
columns: this.columns
},
hooks: {
afterDraw: () => {
console.log('Table redrawn');
}
}
});
}
}
ngOnDestroy() {
if (this.tableInstance) {
this.tableInstance.destroy();
}
}
}</code></pre>
</div>
<div class="info mt-3">
<strong>Angular-Specific Tips:</strong>
<ul>
<li>Use <code>@ViewChild</code> for template references</li>
<li>Initialize in <code>ngOnInit</code> lifecycle hook</li>
<li>Clean up in <code>ngOnDestroy</code> lifecycle hook</li>
<li>Consider using NgRx for state management</li>
<li>Use TypeScript for better type safety</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Backend Frameworks -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#aspNetCoreIntegration">
ASP.NET Core Integration
</button>
</h3>
<div id="aspNetCoreIntegration" class="accordion-collapse collapse" data-bs-parent="#frameworkAccordion">
<div class="accordion-body">
<!-- ASP.NET Core Integration -->
<div class="mb-4">
<h4>ASP.NET Core Integration</h4>
<div class="code-example">
<pre><code>// Controller
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly IDataService _service;
public DataController(IDataService service)
{
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetData([FromQuery] DataTableRequest request)
{
var data = await _service.GetDataAsync();
// Apply server-side processing
var result = data
.Skip(request.Start)
.Take(request.Length)
.ToList();
return Ok(new {
draw = request.Draw,
recordsTotal = data.Count,
recordsFiltered = data.Count,
data = result
});
}
}
// JavaScript/TypeScript
import { SmartTables } from 'smarttables';
const table = new SmartTables('myTable', {
data: {
type: 'ajax',
source: '/api/data',
serverSide: true,
method: 'GET',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
}
}
});</code></pre>
</div>
<div class="info mt-3">
<strong>ASP.NET Core-Specific Tips:</strong>
<ul>
<li>Use dependency injection for services</li>
<li>Implement proper error handling and logging</li>
<li>Consider using Entity Framework Core for data access</li>
<li>Use middleware for authentication and authorization</li>
<li>Consider using SignalR for real-time updates</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- General Integration Tips -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#generalTips">
General Integration Tips
</button>
</h3>
<div id="generalTips" class="accordion-collapse collapse" data-bs-parent="#frameworkAccordion">
<div class="accordion-body">
<div class="pro-tip">
<strong>Pro Tip:</strong> When integrating with any framework, always ensure proper cleanup by calling the <code>destroy()</code> method when the component is unmounted or the table is no longer needed. This prevents memory leaks and ensures proper event cleanup.
</div>
<div class="info">
<p>Framework-Specific Considerations:</p>
<ul class="list-spaced">
<li><strong>Modern JavaScript:</strong> Use ES6+ features like arrow functions, destructuring, and async/await for cleaner code.</li>
<li><strong>TypeScript:</strong> Leverage TypeScript for better type safety and developer experience.</li>
<li><strong>State Management:</strong> Consider using your framework's state management solution (Redux, Vuex, etc.) to handle table data and state.</li>
<li><strong>CSRF Protection:</strong> Include appropriate CSRF tokens in AJAX requests when required by your backend framework.</li>
<li><strong>Error Handling:</strong> Implement proper error handling both on the frontend and backend.</li>
<li><strong>Performance:</strong> Use appropriate caching strategies and optimize database queries.</li>
<li><strong>Security:</strong> Implement proper authentication and authorization mechanisms.</li>
<li><strong>Testing:</strong> Write unit tests for both frontend and backend components.</li>
<li><strong>Real-time Updates:</strong> Consider using WebSocket or similar technologies for real-time data updates.</li>
<li><strong>Accessibility:</strong> Ensure your table implementation follows WCAG guidelines for accessibility.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="smarttables-server-side" class="concept-section">
<h2 class="concept-title">Server-side Processing</h2>
<p>SmartTables provides robust server-side processing for handling large datasets efficiently. By enabling <code>data.serverSide: true</code>, data filtering, sorting, and pagination are offloaded to the server, ideal for datasets with thousands or millions of records. The server processes requests and returns only the necessary data for display, ensuring fast performance.</p>
<h4>Server-side Configuration</h4>
<p>Configure SmartTables to fetch data from a server endpoint using AJAX with server-side processing enabled:</p>
<div class="code-example">
<pre><code>import { SmartTables } from 'smarttables';
const serverTable = new SmartTables('serverTable', {
data: {
type: 'ajax',
source: '/api/employees',
serverSide: true,
method: 'GET',
prefetch: true,
cacheDuration: 300000, // 5 minutes cache duration
columns: [
{ data: 'id', title: 'ID' },
{ data: 'name', title: 'Name' },
{ data: 'department', title: 'Department' },
{ data: 'age', title: 'Age' },
{ data: 'salary', title: 'Salary' },
{ data: 'bonus', title: 'Bonus' }
]
}
});
</code></pre>
</div>
<h4>Server Response Format</h4>
<p>The server must return data in the following JSON format, compatible with SmartTables' expectations:</p>
<div class="code-example">
<pre><code>{
"data": [
{ "id": 1, "name": "John Doe", "department": "Finance", "age": 32, "salary": 85000, "bonus": 5000 },
{ "id": 2, "name": "Jane Smith", "department": "HR", "age": 28, "salary": 65000, "bonus": 3000 }
// ... more records
],
"total": 1000 // Total records after filtering
}
</code></pre>
</div>
<div class="warning">
<strong>Important:</strong> Unlike some table libraries, SmartTables expects <code>total</code> instead of <code>recordsTotal</code> or <code>recordsFiltered</code>. Ensure your server returns the correct field to avoid pagination issues.
</div>
<h4>Request Parameters</h4>
<p>SmartTables sends the following query parameters in GET requests to the server:</p>
<div class="table-responsive mb-4 mt-4">
<table class="table table-striped table-clean">
<thead>
<tr>
<th>Parameter</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>page</code></td>
<td>Current page number (1-based)</td>
<td><code>2</code></td>
</tr>
<tr>
<td><code>perPage</code></td>
<td>Number of records per page</td>
<td><code>10</code></td>
</tr>
<tr>
<td><code>search</code></td>
<td>Global search value</td>
<td><code>Finance</code></td>
</tr>
<tr>
<td><code>sortColumn</code></td>
<td>Name of the column being sorted</td>
<td><code>department</code></td>
</tr>
<tr>
<td><code>sortDirection</code></td>
<td>Direction of sort</td>
<td><code>asc</code> or <code>desc</code></td>
</tr>
</tbody>
</table>
</div>
<div class="pro-tip">
<strong>Pro Tip:</strong> Use the <code>data.params</code> option to customize parameter names if your server expects different ones (e.g., <code>limit</code> instead of <code>perPage</code>):
</div>
<pre><code>data: {
type: 'ajax',
source: '/api/employees',
serverSide: true,
params: {
limit: function() { return this.perPage; },
offset: function() { return (this.currentPage - 1) * this.perPage; },
query: 'search'
}
}</code></pre>
<h4 class="mt-4">Advanced Caching for Server-side Data</h4>
<p>SmartTables enhances performance with client-side caching and prefetching:</p>
<ul class="list-spaced">
<li><strong>Cache Keys:</strong> Unique keys are generated based on <code>page</code>, <code>search</code>, <code>sortColumn</code>, and <code>sortDirection</code>.</li>
<li><strong>Prefetching:</strong> Automatically loads the next page when <code>prefetch: true</code>, reducing wait times.</li>
<li><strong>Cache Invalidation:</strong> Clears cache on parameter changes (e.g., new search term).</li>
<li><strong>Cache Duration:</strong> Configurable via <code>cacheDuration</code> (default: 5 minutes).</li>
</ul>
<div class="code-example">
<pre><code>const serverTable = new SmartTables('serverTable', {
data: {
type: 'ajax',
source: '/api/employees',
serverSide: true,
prefetch: true,
cacheDuration: 600000, // 10 minutes
withCredentials: true,
headers: { 'X-API-Key': 'your-api-key' }
},
debug: true // Log cache operations
});</code></pre>
</div>
<h4 class="mt-4">Implementing a Server-side Endpoint</h4>
<p>Below are example implementations in multiple languages to demonstrate flexibility. Your server should handle pagination, filtering, and sorting based on the request parameters and return the expected response format.</p>
<div class="accordion" id="backendIntegrationsAccordion">
<!-- ASP.NET Core -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#aspNetCoreIntegration">
ASP.NET Core (C#)
</button>
</h3>
<div id="aspNetCoreIntegration" class="accordion-collapse collapse" data-bs-parent="#backendIntegrationsAccordion">
<div class="accordion-body">
<p>This example uses ASP.NET Core with Entity Framework Core for database access, implementing server-side processing with caching.</p>
<div class="code-example">
<pre><code>using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Linq;
using System.Threading.Tasks;
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public int Age { get; set; }
public decimal Salary { get; set; }
public decimal Bonus { get; set; }
}
public class PaginatedResponse&lt;T&gt;
{
public T[] Data { get; set; }
public int Total { get; set; }
}
[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
private readonly AppDbContext _context;
private readonly IMemoryCache _cache;
public EmployeesController(AppDbContext context, IMemoryCache cache)
{
_context = context;
_cache = cache;
}
[HttpGet]
public async Task&lt;IActionResult&gt; GetEmployees(
[FromQuery] int page = 1,
[FromQuery] int perPage = 10,
[FromQuery] string search = "",
[FromQuery] string sortColumn = "",
[FromQuery] string sortDirection = "asc")
{
try
{
// Generate cache key
string cacheKey = $"employees_{page}_{perPage}_{search}_{sortColumn}_{sortDirection}";
// Try to get from cache
if (!_cache.TryGetValue(cacheKey, out PaginatedResponse&lt;Employee&gt; response))
{
// Base query
var query = _context.Employees.AsQueryable();
// Apply search
if (!string.IsNullOrEmpty(search))
{
query = query.Where(e =&gt;
e.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
e.Department.Contains(search, StringComparison.OrdinalIgnoreCase) ||
e.Age.ToString().Contains(search) ||
e.Salary.ToString().Contains(search) ||
e.Bonus.ToString().Contains(search));
}
// Get total count
int total = await query.CountAsync();
// Apply sorting
if (!string.IsNullOrEmpty(sortColumn))
{
query = sortDirection.ToLower() == "asc"
? query.OrderBy(e =&gt; EF.Property&lt;object&gt;(e, sortColumn))
: query.OrderByDescending(e =&gt; EF.Property&lt;object&gt;(e, sortColumn));
}
// Apply pagination
query = query.Skip((page - 1) * perPage).Take(perPage);
// Execute query
var data = await query.ToArrayAsync();
response = new PaginatedResponse&lt;Employee&gt;
{
Data = data,
Total = total
};
// Cache the result
_cache.Set(cacheKey, response, TimeSpan.FromMinutes(5));
}
return Ok(response);
}
catch (Exception ex)
{
return StatusCode(500, new { data = Array.Empty&lt;Employee&gt;(), total = 0, error = "Internal server error" });
}
}
}
</code></pre>
</div>
<div class="info mt-3">
<strong>ASP.NET Core Tips:</strong>
<ul>
<li>Use <code>IMemoryCache</code> or <code>IDistributedCache</code> for caching responses.</li>
<li>Validate <code>sortColumn</code> to prevent SQL injection when using dynamic sorting.</li>
<li>Consider using dependency injection for database context and logging.</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Node.js -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#nodeJSIntegration">
Node.js (Express.js)
</button>
</h3>
<div id="nodeJSIntegration" class="accordion-collapse collapse" data-bs-parent="#backendIntegrationsAccordion">
<div class="accordion-body">
<p>A simple Express.js implementation with in-memory data, adaptable to any database.</p>
<div class="code-example">
<pre><code>const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Mock data (replace with database)
const employees = [
{ id: 1, name: 'John Doe', department: 'Engineering', age: 32, salary: 85000, bonus: 5000 },
{ id: 2, name: 'Jane Smith', department: 'Marketing', age: 28, salary: 65000, bonus: 3000 }
// ... more records
];
app.get('/api/employees', async (req, res) =&gt; {
try {
const { page = 1, perPage = 10, search = '', sortColumn = '', sortDirection = 'asc' } = req.query;
const pageNum = parseInt(page);
const perPageNum = parseInt(perPage);
// Filter data
let filteredData = employees;
if (search) {
const searchLower = search.toLowerCase();
filteredData = employees.filter(item =&gt;
Object.values(item).some(val =&gt;
String(val).toLowerCase().includes(searchLower)
)
);
}
// Get total
const total = filteredData.length;
// Sort data
if (sortColumn) {
filteredData.sort((a, b) =&gt; {
const aVal = a[sortColumn];
const bVal = b[sortColumn];
if (typeof aVal === 'number' &amp;&amp; typeof bVal === 'number') {
return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
}
const aStr = String(aVal).toLowerCase();
const bStr = String(bVal).toLowerCase();
return sortDirection === 'asc' ? aStr.localeCompare(bStr) : bStr.localeCompare(aStr);
});
}
// Paginate
const offset = (pageNum - 1) * perPageNum;
const paginatedData = filteredData.slice(offset, offset + perPageNum);
res.json({
data: paginatedData,
total
});
} catch (error) {
console.error(error);
res.status(500).json({ data: [], total: 0, error: 'Internal server error' });
}
});
app.listen(3000, () =&gt; console.log('Server running on port 3000'));
</code></pre>
</div>
<div class="info mt-3">
<strong>Node.js Tips:</strong>
<ul>
<li>Integrate with a database like MongoDB or PostgreSQL for production.</li>
<li>Use a caching layer like Redis for better performance.</li>
<li>Add middleware for authentication if needed.</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Python -->
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pythonIntegration">
Python (FastAPI)
</button>
</h3>
<div id="pythonIntegration" class="accordion-collapse collapse" data-bs-parent="#backendIntegrationsAccordion">
<div class="accordion-body">
<p>A FastAPI implementation using SQLAlchemy for database operations.</p>
<div class="code-example">
<pre><code>from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from sqlalchemy import or_, desc, asc
from typing import Optional
from pydantic import BaseModel
import models
from database import SessionLocal
app = FastAPI()
# Pydantic model for response
class PaginatedResponse(BaseModel):
data: list
total: int
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/api/employees", response_model=PaginatedResponse)
async def get_employees(
page: int = 1,
perPage: int = 10,
search: Optional[str] = None,
sortColumn: Optional[str] = None,
sortDirection: str = "asc",
db: Session = Depends(get_db)
):
try:
# Base query
query = db.query(models.Employee)
# Apply search
if search:
query = query.filter(
or_(
models.Employee.name.ilike(f"%{search}%"),
models.Employee.department.ilike(f"%{search}%"),
models.Employee.age.cast(str).ilike(f"%{search}%"),
models.Employee.salary.cast(str).ilike(f"%{search}%"),
models.Employee.bonus.cast(str).ilike(f"%{search}%")
)
)
# Get total
total = query.count()
# Apply sorting
if sortColumn:
column = getattr(models.Employee, sortColumn, None)
if column:
query = query.order_by(asc(column) if sortDirection == "asc" else desc(column))
# Apply pagination
offset = (page - 1) * perPage
query = query.offset(offset).limit(perPage)
# Execute
data = query.all()
return {
"data": data,
"total": total
}
except Exception as e:
return {"data": [], "total": 0, "error": str(e)}
</code></pre>
</div>
<div class="info mt-3">
<strong>Python Tips:</strong>
<ul>
<li>Use Pydantic models for response validation.</li>
<li>Implement caching with libraries like <code>cachetools</code>.</li>
<li>Secure endpoints with FastAPI's security utilities.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<h4 class="mt-4">Handling Pagination with Search</h4>
<p>Ensure consistent total counts across requests to prevent pagination issues:</p>
<div class="code-example">
<pre><code>const table = new SmartTables('myTable', {
data: {
serverSide: true,
source: '/api/employees',
method: 'GET'
},
hooks: {
afterDataLoad: function(response) {
console.log('Total records:', response.total);
}
}
});
</code></pre>
</div>
<div class="warning">
<strong>Important Considerations:</strong>
<ul class="mb-0">
<li>Calculate <code>total</code> after applying filters but before pagination.</li>
<li>Reset to page 1 when search changes to avoid invalid page requests.</li>
<li>Validate <code>sortColumn</code> to ensure it matches valid fields.</li>
<li>Use logging to debug inconsistencies in counts or data.</li>
</ul>
</div>
<h4>Real-time Debugging</h4>
<p>Enable <code>debug: true</code> to log AJAX requests, responses, and cache operations:</p>
<ul class="list-spaced">
<li>Request parameters sent to the server.</li>
<li>Cache hits, misses, and key generation.</li>
<li>Prefetching status and errors.</li>
<li>Total counts and pagination calculations.</li>
</ul>
<div class="pro-tip">
<strong>Pro Tip:</strong> Combine client-side debug logs with server-side logging to troubleshoot issues. Check that <code>total</code> remains consistent across pages for the same search query.
</div>
</section>
<section id="smarttables-troubleshooting" class="concept-section">
<h2 class="concept-title">Troubleshooting</h2>
<div class="warning">
<strong>Common Issues:</strong>
<ul class="mb-0">
<li><strong>Table not responsive?</strong> Make sure you've set <code>data-priority</code> attributes on your table headers.</li>
<li><strong>Search not working as expected?</strong> Check your <code>fuzzyMatch</code> settings and try adjusting the threshold.</li>
<li><strong>Export not working?</strong> Ensure you have the required dependencies for Excel export.</li>
<li><strong>Performance issues?</strong> Try reducing the number of rows per page or use server-side processing.</li>
</ul>
</div>
<h4>Server-side Processing Issues</h4>
<p>When working with server-side processing, you might encounter these common challenges:</p>
<div class="accordion" id="troubleshootingAccordion">
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#inconsistentCounts">
Inconsistent Row Counts in Pagination
</button>
</h3>
<div id="inconsistentCounts" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Symptoms:</strong> The table shows different total counts when navigating between pages. For example, page 1 shows "Showing 1 to 10 of 25 entries" but page 2 shows "Showing 11 to 20 of 13 entries".</p>
<p><strong>Solutions:</strong></p>
<ul>
<li><strong>Server-side:</strong> Ensure the search filter is applied consistently before calculating the total count. Apply the search once to the entire dataset before pagination.</li>
<li><strong>Client-side:</strong> Clear the cache when search parameters change using <code>clearAjaxCache()</code>, and ensure the total from the server response is stored consistently.</li>
<li><strong>Debug:</strong> Add logging on both server and client to track the search term, filtered count, and total count across requests.</li>
</ul>
<div class="code-example">
<pre><code>// Server-side fix (PHP example)
// Apply search filter once to get consistent count
$filteredData = [];
$totalRecords = count($data);
if (!empty($search)) {
$searchLower = strtolower($search);
foreach ($data as $item) {
$found = false;
foreach ($item as $key => $value) {
$strValue = strtolower((string)$value);
if (strpos($strValue, $searchLower) !== false) {
$found = true;
break;
}
}
if ($found) {
$filteredData[] = $item;
}
}
} else {
$filteredData = $data;
}
// Get TOTAL filtered count BEFORE pagination
$totalFiltered = count($filteredData);
// Now apply pagination
$offset = ($page - 1) * $perPage;
$paginatedData = array_slice($filteredData, $offset, $perPage);
// Return consistent counts
return [
'recordsTotal' => $totalRecords,
'recordsFiltered' => $totalFiltered,
'data' => $paginatedData
];</code></pre>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#cachingIssues">
Caching and Prefetch Issues
</button>
</h3>
<div id="cachingIssues" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Symptoms:</strong> Stale data appears after changing search parameters, or prefetching doesn't seem to work.</p>
<p><strong>Solutions:</strong></p>
<ul>
<li><strong>Cache keys:</strong> Ensure cache keys include all relevant parameters (search term, sort column, sort direction)</li>
<li><strong>Cache invalidation:</strong> Clear the cache when search or sort parameters change</li>
<li><strong>Cache debugging:</strong> Enable debug mode to see cache operations in the console</li>
</ul>
<div class="code-example">
<pre><code>// Client-side cache key generation
generateCacheKey() {
// Include ALL parameters that affect the results in the key
const params = {
url: this.options.data.url,
page: this.currentPage,
perPage: this.options.perPage,
search: this.searchQuery || '',
sort: this.currentSort
};
this.log('Generating cache key with params:', JSON.stringify(params));
return JSON.stringify(params);
}</code></pre>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sortingIssues">
Incorrect Sorting
</button>
</h3>
<div id="sortingIssues" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Symptoms:</strong> Columns sort incorrectly, especially numeric, date, or special format columns.</p>
<p><strong>Solutions:</strong></p>
<ul>
<li><strong>Column mapping:</strong> Implement direct column mapping between column indices and field names</li>
<li><strong>Data type detection:</strong> Sort different data types appropriately (numbers, dates, strings)</li>
<li><strong>Normalization:</strong> Normalize values before comparison for consistent sorting</li>
</ul>
<div class="code-example">
<pre><code>// Server-side sorting solution (PHP)
// Define column map for sorting
$columnMap = [
0 => 'id',
1 => 'name',
2 => 'position',
3 => 'office',
4 => 'age',
5 => 'startDate',
6 => 'salary'
];
// Identify data types for special handling
$numericFields = ['id', 'age', 'salary', 'bonus', 'progress'];
$dateFields = ['startDate', 'hireDate'];
// Apply sorting with data type awareness
if ($order) {
$columnIndex = $order['column'];
$columnKey = isset($columnMap[$columnIndex]) ? $columnMap[$columnIndex] : null;
$direction = $order['dir'];
if ($columnKey) {
usort($filteredData, function($a, $b) use ($columnKey, $direction, $numericFields, $dateFields) {
// Handle numeric fields
if (in_array($columnKey, $numericFields)) {
$aVal = (float)$a[$columnKey];
$bVal = (float)$b[$columnKey];
return $direction === 'asc' ? $aVal - $bVal : $bVal - $aVal;
}
// Handle date fields
else if (in_array($columnKey, $dateFields)) {
$aDate = strtotime($a[$columnKey]);
$bDate = strtotime($b[$columnKey]);
return $direction === 'asc' ? $aDate - $bDate : $bDate - $aDate;
}
// Default string comparison
else {
$aVal = strtolower($a[$columnKey]);
$bVal = strtolower($b[$columnKey]);
$result = strcmp($aVal, $bVal);
return $direction === 'asc' ? $result : -$result;
}
});
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#emptyResultsIssues">
Empty Results or Navigation Issues
</button>
</h3>
<div id="emptyResultsIssues" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Symptoms:</strong> Empty results when navigating to a page, or "No matching records found" errors when searching.</p>
<p><strong>Solutions:</strong></p>
<ul>
<li><strong>Max page calculation:</strong> Ensure the max page is calculated based on filtered results</li>
<li><strong>Page clamping:</strong> If requested page exceeds max page, clamp to max page</li>
<li><strong>Search resets:</strong> Reset to page 1 when search query changes</li>
</ul>
<div class="code-example">
<pre><code>// Server-side page validation (PHP)
// Calculate maximum page based on filtered count
$maxPage = ceil($totalFiltered / $perPage);
// Ensure page is valid
if ($page < 1) {
$page = 1;
} else if ($page > $maxPage && $maxPage > 0) {
$page = $maxPage;
}
// Calculate offset based on validated page
$offset = ($page - 1) * $perPage;
// Client-side search reset
handleSearch(value) {
this.searchQuery = value;
if (this.options.data.serverSide) {
// Reset the cache when search changes
this.clearAjaxCache();
this.currentPage = 1; // Reset to first page on new search
this.loadAjax(); // Fetch new data with search query
} else {
// Client-side search logic
}
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="smarttables-manage-records" class="concept-section">
<h2 class="concept-title">Managing Records with SmartTables</h2>
<p>SmartTables provides powerful functionality for managing records with full CRUD (Create, Read, Update, Delete) operations. This allows you to build interactive data management interfaces with minimal effort.</p>
<h4>Basic Setup for Record Management</h4>
<div class="code-example">
<pre><code>// Import the SmartTables module
import { SmartTables } from './optional/smartTables/smartTables.bundle.js';
document.addEventListener('DOMContentLoaded', () => {
// Initialize SmartTables with data management options
const clientTable = new SmartTables('clientTable', {
// Data configuration
data: {
type: 'json',
source: sampleData, // Your data array
idField: 'id', // Field that uniquely identifies each record
columns: [
{ data: 'id', title: 'ID', editable: false },
{ data: 'name', title: 'Name', required: true },
{ data: 'email', title: 'Email', type: 'email', required: true },
{ data: 'phone', title: 'Phone', type: 'tel' },
{ data: 'active', title: 'Active', type: 'boolean' },
{
data: 'actions',
title: 'Actions',
sortable: false,
editable: false,
render: (data, row) =>
'&lt;button type="button" class="btn btn-primary btn-xs edit-btn" data-id="' + row.id + '"&gt;Edit&lt;/button&gt; ' +
'&lt;button type="button" class="btn btn-danger btn-xs delete-btn" data-id="' + row.id + '"&gt;Del&lt;/button&gt;'
}
]
},
// Enable record management features
addRecord: true, // Shows an Add Record button
// Other options...
});
// Handle edit and delete button clicks with event delegation
clientTable.table.addEventListener('click', e => {
if (e.target.classList.contains('edit-btn')) {
const rowId = e.target.getAttribute('data-id');
clientTable.edit(rowId);
} else if (e.target.classList.contains('delete-btn')) {
const rowId = e.target.getAttribute('data-id');
clientTable.delete(rowId);
}
});
});</code></pre>
</div>
<h4>Advanced Record Management with Hooks</h4>
<p>SmartTables provides a comprehensive hooks system to customize the behavior of record management operations:</p>
<div class="code-example">
<pre><code>const table = new SmartTables('myTable', {
// ... other options
hooks: {
// Edit operation hooks
beforeEdit(rowId) {
// Called before edit mode is entered
console.log('Editing row: ' + rowId);
return true; // Return false to prevent editing
},
afterEdit(rowId, rowData, success) {
// Called after edit is completed
console.log('Edit completed: ' + success);
},
onEditModalCreated(modalHTML, rowId, rowData) {
// Customize the edit modal HTML
return modalHTML;
},
onEditModalBeforeShow(modalElement, rowId, rowData) {
// Access the modal DOM before it's shown
},
onEditDataCollected(updatedData, rowId, originalData) {
// Process and validate form data
return updatedData;
},
onEditSuccess(rowId, updatedRecord, submittedData) {
// Handle successful update
console.log('Record updated successfully');
},
onEditError(rowId, error, attemptedData) {
// Handle update errors
console.error('Update failed', error);
},
// Delete operation hooks
beforeDelete(rowId) {
// Called before deletion
return confirm('Are you sure you want to delete this record?');
},
afterDelete(rowId, data, success) {
// Called after deletion attempt
if (success) console.log('Deletion successful');
},
onDeleteModalCreated(modalHtml, rowId) {
// Customize deletion confirmation modal
return modalHtml;
},
onDeleteSuccess(rowId, deletedRecord) {
// Handle successful deletion
},
// Add record operation hooks
beforeAddRecord(initialData, options) {
// Called before add form is shown
return initialData; // Return modified initial data
},
afterAddRecord(newRecordData, success) {
// Called after add operation completes
if (success) console.log('Record added');
},
onAddModalCreated(modalHTML, initialData, options) {
// Customize add record modal
return modalHTML;
},
onAddModalBeforeShow(modalElement, initialData, options) {
// DOM access before modal shows
},
onAddDataCollected(newRecordData, options) {
// Process and validate new record data
return newRecordData;
},
onAddRecordSuccess(newRecord) {
// Handle successful addition
},
onAddRecordError(error, attemptedData) {
// Handle addition errors
},
onAddCancelled(options) {
// Called when add is cancelled
}
}
});</code></pre>
</div>
<h4>Column Type Configuration</h4>
<p>SmartTables supports various input types for different data fields:</p>
<div class="table-responsive mb-4 mt-4">
<table class="table table-striped table-clean">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
<th>Additional Options</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>text</code></td>
<td>Default text input</td>
<td>maxlength, pattern</td>
</tr>
<tr>
<td><code>number</code></td>
<td>Numeric input</td>
<td>min, max, step</td>
</tr>
<tr>
<td><code>email</code></td>
<td>Email input with validation</td>
<td>pattern</td>
</tr>
<tr>
<td><code>tel</code></td>
<td>Telephone input</td>
<td>format, placeholder</td>
</tr>
<tr>
<td><code>date</code></td>
<td>Date picker</td>
<td>min, max</td>
</tr>
<tr>
<td><code>select</code></td>
<td>Dropdown selection</td>
<td>options (array)</td>
</tr>
<tr>
<td><code>boolean</code></td>
<td>Checkbox</td>
<td>-</td>
</tr>
<tr>
<td><code>password</code></td>
<td>Password input</td>
<td>minlength</td>
</tr>
</tbody>
</table>
</div>
<div class="code-example">
<pre><code>// Column configuration examples for different types
columns: [
{
data: 'salary',
title: 'Salary',
type: 'number', // Numeric input
min: 0, // Minimum value
step: 500, // Increment by 500
render: data => '$' + data.toLocaleString() // Format with $ and commas
},
{
data: 'department',
title: 'Department',
type: 'select', // Dropdown select input
options: [ // Available options
'Engineering',
'Sales',
'Marketing',
'Human Resources'
]
},
{
data: 'joinDate',
title: 'Join Date',
type: 'date', // Date input
render: data => new Date(data).toLocaleDateString() // Format date
},
{
data: 'active',
title: 'Active',
type: 'boolean', // Boolean/checkbox
render: data => data ?
'&lt;span class="badge bg-success">Yes&lt;/span>' :
'&lt;span class="badge bg-danger">No&lt;/span>'
}
]</code></pre>
</div>
<h4>Visual Feedback for Row States</h4>
<p>Implement visual feedback for different row states during CRUD operations:</p>
<div class="code-example">
<pre><code>// Track row states
const rowStates = {
editing: null, // ID of the row being edited
saved: new Set(), // Set of recently saved row IDs
adding: false // Flag for adding operation
};
// Apply classes in hooks
hooks: {
beforeEdit(rowId) {
rowStates.editing = rowId;
const rowElement = this.table.querySelector('tbody tr[data-id="' + rowId + '"]');
if (rowElement) {
rowElement.classList.add('editing');
}
return true;
},
afterEdit(rowId, rowData, success) {
rowStates.editing = null;
if (success) {
rowStates.saved.add(rowId);
setTimeout(() => {
rowStates.saved.delete(rowId);
const row = this.table.querySelector('tbody tr[data-id="' + rowId + '"]');
if (row) row.classList.remove('saved');
}, 3000);
}
this.draw();
}
}</code></pre>
</div>
<div class="pro-tip">
<strong>Pro Tip:</strong> When implementing CRUD operations, consider using a custom modal system for consistency across the application. SmartTables hooks make it easy to integrate with custom UI components.
</div>
</section>
</div>
<div class="order-1 order-xl-2 col-lg-12 col-xl-3 position-relative">
<!-- Sidebar (Right Side) - Sticky Navigation -->
<h5 class="mb-3 ps-lg-3">On This Page</h5>
<ul class="list-unstyled ps-lg-3">
<li class="py-1"><a href="#smarttables">Introduction</a></li>
<li class="py-1"><a href="#smarttables-installation">Installation</a></li>
<li class="py-1"><a href="#smarttables-configuration">Configuration</a></li>
<li class="py-1"><a href="#smarttables-advanced-configuration">Advanced Configuration</a></li>
<li class="py-1"><a href="#smarttables-data-loading">Data Loading</a></li>
<li class="py-1"><a href="#smarttables-data-import">Data Import</a></li>
<li class="py-1"><a href="#smarttables-event-hooks">Event Hooks</a></li>
<li class="py-1"><a href="#smarttables-search-capabilities">Search Capabilities</a></li>
<li class="py-1"><a href="#smarttables-responsive-behavior">Responsive Behavior</a></li>
<li class="py-1"><a href="#smarttables-export-options">Export Options</a></li>
<li class="py-1"><a href="#smarttables-api-methods">API Methods</a></li>
<li class="py-1"><a href="#smarttables-plugin-system">Plugin System</a></li>
<li class="py-1"><a href="#smarttables-performance-tips">Performance Tips</a></li>
<li class="py-1"><a href="#smarttables-framework-integrations">Framework Integrations</a></li>
<li class="py-1"><a href="#smarttables-server-side">Server-side Processing</a></li>
<li class="py-1"><a href="#smarttables-troubleshooting">Troubleshooting</a></li>
<li class="py-1"><a href="#smarttables-manage-records">Managing Records</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<footer class="app-footer">
<div class="app-footer-content flex-grow-1">
SmartAdmin &copy;
<script>document.write(new Date().getFullYear());</script>.
<span class="hidden-mobile"> All rights reserved, </span>
<span class="fw-bold">Webora.</span>
<a href="#top" class="ms-auto hidden-mobile" aria-label="Back to top">
<svg class="sa-icon sa-thick sa-icon-primary">
<use href="icons/sprite.svg#arrow-up"></use>
</svg>
</a>
</div>
</footer>
</main>
<aside class="app-drawer js-app-drawer">
<button type="button" class="btn btn-system position-absolute top-0 end-0 z-2 overflow-hidden p-3" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-app-drawer" aria-label="Close">
<svg class="sa-icon sa-icon-2x">
<use href="icons/sprite.svg#x"></use>
</svg>
</button>
<div class="custom-scrollbar h-100 p-0">
<div class="d-flex flex-grow-1 p-0 w-100 h-100">
<!-- left sidebar -->
<div class="d-flex flex-column flex-grow-1 flex-0 overflow-x-auto h-100">
<div class="flex-wrap align-items-center flex-grow-1 position-relative bg-gray-50">
<div class="position-absolute top-0 bottom-0 w-100 overflow-hidden d-flex flex-column">
<!-- chat container -->
<div class="w-100 p-4 pb-0 px-lg-3 bg-subtlelight-fade custom-scroll flex-grow-1 d-flex flex-column">
<div class="mb-g">
<h4 class="fw-600">Hi Sunny,</h4>
<span class="text-muted">how can I help you today?</span>
</div>
<div class="mt-auto ms-auto mb-2 btn btn-outline-default px-2 py-1 fw-500 fs-xs text-gradient">
Analyze my data
</div>
<p class="ms-auto mb-2 btn btn-outline-default px-2 py-1 fs-xs fw-500 text-gradient">
Create a new report
</p>
<p class="ms-auto mb-2 btn btn-outline-default px-2 py-1 fs-xs fw-500 text-gradient">
Summarize my Calendar
</p>
</div>
<!-- chat input -->
<div class="bg-faded m-3 rounded height-auto">
<textarea rows="2" class="form-control px-2 rounded-top" placeholder="Ask me anything"></textarea>
<div class="d-flex align-items-center py-2 px-2 border border-top-0 rounded-bottom">
<div class="d-flex gap-1 flex-row align-items-center flex-wrap flex-shrink-0">
<button class="btn btn-icon fs-xl flex-shrink-0" aria-label="Attach Files or Photos" type="button" data-bs-toggle="tooltip" data-bs-original-title="Attach Files or Photos" data-bs-placement="top">
<svg class="sa-icon sa-bold sa-icon-subtlelight">
<use href="icons/sprite.svg#file"></use>
</svg>
</button>
<button class="btn btn-icon fs-xl width-1 flex-shrink-0" aria-label="Voice" type="button" data-bs-toggle="tooltip" data-bs-original-title="Voice" data-bs-placement="top">
<svg class="sa-icon sa-bold sa-icon-subtlelight">
<use href="icons/sprite.svg#mic"></use>
</svg>
</button>
</div>
<button class="btn btn-icon fs-xl width-1 flex-shrink-0 ms-auto" aria-label="Send" type="button" data-bs-toggle="tooltip" data-bs-original-title="Send" data-bs-placement="top">
<svg class="sa-icon sa-bold sa-icon-subtlelight sa-icon-2x">
<use href="icons/sprite.svg#arrow-up-circle"></use>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</aside>
<div class="backdrop" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-app-drawer"></div>
<aside class="app-drawer js-drawer-settings">
<div class="app-drawer-header">
<div class="h4 mb-0">
App Builder
</div>
<button type="button" class="btn btn-system ms-auto" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-drawer-settings" aria-label="Close">
<svg class="sa-icon sa-icon-2x">
<use href="icons/sprite.svg#x"></use>
</svg>
</button>
</div>
<div class="custom-scrollbar h-100">
<div class="info-container">
Unlock limitless design potential with 16+ layout combinations and extensive customization options—tailor your dashboard to fit your exact needs.
<button type="button" class="btn btn-sm btn-outline-secondary ms-1" data-action="playsound" data-soundpath="media/sound/" data-soundfile="settings-voice.mp3" aria-label="Play Settings Voice">
<svg class="sa-icon">
<use href="icons/sprite.svg#volume-2"></use>
</svg>
</button>
</div>
<div class="d-flex justify-content-spaced w-100 app-fob-showcase-text" data-prefix="Preview">
<div class="app-fob app-fob-lg app-fob-showcase">
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="mod-status primary-mod" data-prefix="Primary Settings">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="actionHeaderFixed" data-action="toggle" data-class="set-header-fixed">
<label class="form-check-label" for="actionHeaderFixed">
Header position fixed
</label>
</div>
<div class="form-check d-none d-lg-block d-xl-block d-xxl-block">
<input class="form-check-input" type="checkbox" id="actionNavFull" data-action="toggle" data-class="set-nav-full" data-codependence="set-nav-collapsed">
<label class="form-check-label" for="actionNavFull">
Navigation full height
</label>
</div>
</div>
<div class="mod-status d-none d-lg-block d-xl-block d-xxl-block" data-prefix="Addon Settings">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="actionNavFixed" data-action="toggle" data-class="set-nav-fixed">
<label class="form-check-label" for="actionNavFixed">
Navigation position fixed
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="actionNavCollapsed" data-action="toggle" data-class="set-nav-collapsed" data-dependency="set-nav-full">
<label class="form-check-label" for="actionNavCollapsed">
Navigation collapsed
</label>
</div>
</div>
<div class="mod-status" data-prefix="Misc Settings">
<div class="form-check d-none d-lg-block d-xl-block d-xxl-block">
<input class="form-check-input" type="checkbox" id="actionNavMinified" data-action="toggle" data-class="set-nav-minified">
<label class="form-check-label" for="actionNavMinified">
Navigation Minified
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="darkNavigation" data-action="toggle" data-class="set-nav-dark">
<label class="form-check-label" for="darkNavigation">
Dark Navigation
</label>
</div>
</div>
<div class="mod-status" data-prefix="Aria Settings">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="actionColorblindMode" data-action="toggle" data-class="set-colorblind-mode">
<label class="form-check-label" for="actionColorblindMode">
Colorblind Mode
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="actionHighContrastMode" data-action="toggle" data-class="set-high-contrast-mode">
<label class="form-check-label" for="actionHighContrastMode">
High Contrast Mode
</label>
</div>
</div>
<div class="mod-status app-themes" data-prefix="Themes">
<div class="clickable-boxes">
<!-- Default -->
<input type="radio" id="option0" name="options" data-action="theme-style" data-theme-style="" checked>
<label for="option0" data-prefix="Default" style="background: linear-gradient(135deg, #FF6A00, #F6A2D5, #4C91BF, #7A8B92, #AB7C9A);"></label>
<!-- Nebula -->
<input type="radio" id="option1" name="options" data-action="theme-style" data-theme-style="nebula">
<label for="option1" data-prefix="Nebula" style="background: linear-gradient(135deg, #2a7dbf, #2a9d8f, #766cbc, #f4a261, #e76f51);"></label>
<!-- Olive -->
<input type="radio" id="option2" name="options" data-action="theme-style" data-theme-style="olive">
<label for="option2" data-prefix="Olive" style="background: linear-gradient(135deg, #556B2F, #6B8E23, #8B9A3D, #A9B83E, #BDB76B);"></label>
<!-- Solar -->
<input type="radio" id="option3" name="options" data-action="theme-style" data-theme-style="solar">
<label for="option3" data-prefix="Solar" style="background: linear-gradient(135deg, #FF8C00, #FFD700, #FF4500, #F1C40F, #F39C12);"></label>
<!-- Lunar -->
<input type="radio" id="option4" name="options" data-action="theme-style" data-theme-style="lunar">
<label for="option4" data-prefix="Lunar" style="background: linear-gradient(135deg, #2C3E50, #34495E, #5F6368, #AAB7B8, #E6E6FA, #F1F3F4);"></label>
<!-- Night -->
<input type="radio" id="option5" name="options" data-action="theme-style" data-theme-style="night">
<label for="option5" data-prefix="Night" style="background: linear-gradient(135deg, #1e2a47, #2b3654, #363d6c, #4f5d79, #717b91, #b6c4d1);"></label>
<!-- Aurora -->
<input type="radio" id="option6" name="options" data-action="theme-style" data-theme-style="aurora">
<label for="option6" data-prefix="Aurora" style="background: linear-gradient(135deg, #337e7e, #527a4a, #63279b, #7FFF00, #87CEFA, #B0E0E6);"></label>
<!-- Earth -->
<input type="radio" id="option7" name="options" data-action="theme-style" data-theme-style="earth">
<label for="option7" data-prefix="Earth" style="background: linear-gradient(135deg, #2198f3, #3173a5, #3f6888, #618d48, #52bf11);"></label>
<!-- Flare -->
<input type="radio" id="option8" name="options" data-action="theme-style" data-theme-style="flare">
<label for="option8" data-prefix="Flare" style="background: linear-gradient(135deg, #FF4500, #FF6347, #F44336, #D32F2F, #B71C1C);"></label>
<!-- Storm -->
<input type="radio" id="option9" name="options" data-action="theme-style" data-theme-style="storm">
<label for="option9" data-prefix="Storm" style="background: linear-gradient(135deg, #2F4F4F, #3B5360, #4B6A6E, #5A7980, #A9A9A9, #FFD700);"></label>
</div>
</div>
<div class="d-flex" style="gap:10px">
<button type="button" onclick="appDOM.resetStyle();" class="btn reset-button btn-outline-danger flex-grow-1"> <i class="sa sa-reload align-base me-1"></i> Factory Reset</button>
<button type="button" onclick="resetPanelState();" class="btn btn-outline-secondary flex-grow-1"><i class="sa sa-reload align-base me-1"></i> Reset Panel</button>
</div>
</div>
</aside>
<div class="backdrop" data-action="toggle-swap" data-toggleclass="open" data-target="aside.js-drawer-settings"></div>
</div>
<!-- Core scripts -->
<script src="plugins/bootstrap/bootstrap.bundle.min.js"></script>
<script src="scripts/core/smartNavigation.js"></script>
<script src="scripts/core/smartFilter.js"></script>
<script src="scripts/core/smartSlimscroll.js"></script>
<!-- Dependable scripts -->
<script src="plugins/sortablejs/Sortable.min.js"></script>
<script src="plugins/waves/waves.min.js"></script>
<!-- App.js -->
<script src="scripts/core/smartApp.js"></script>
<!-- Code Highlightjs -->
<script src="plugins/highlightjs/highlight.min.js"></script>
<script src="plugins/highlightjs/go.min.js"></script>
<script src="scripts/pages/code-highlight.js"></script>
</body>
<!-- Mirrored from getwebora.com/smartadmin/demo/docs-smarttablesjs.html by HTTrack Website Copier/3.x [XR&CO'2014], Tue, 30 Jun 2026 04:28:02 GMT -->
</html>