KnockoutJS & jQuery Datatable custom binding (jQueryUI)
a custom knockout binding works on vanilla 3.4 knockout
by KANDR101
HTML
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/1.10.19/css/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/responsive/2.2.3/css/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css" rel="stylesheet">
<!-- <script src="https://code.jquery.com/jquery-3.3.1.js"></script> -->
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.2.3/js/dataTables.responsive.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.2.3/js/responsive.bootstrap4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.5/knockout.mapping.js"></script>
<div class="container">
<table class="table table-striped table-bordered nowrap small" id="vehicleSearchGrid" style="width:100%">
<thead>
<tr>
<th class="d-none">Select</th>
<th>Full Name</th>
<th>Position</th>
<th>Office</th>
<th>Start Date</th>
<th>Salary</th>
<th></th>
</tr>
</thead>
<tbody data-bind="
dataTablesForEach : {
data: employees,
options: {
...
CSS
/* Ensure that the demo table scrolls */
td.control.control-right::before,
th.control.control-right::before,
td.control.control-left::before,
th.control.control-left::before {
top: 49% !important;
left: 49% !important;
height: 15px !important;
width: 15px !important;
line-height: 15px !important;
}
.table>tbody>tr.text-vcenter>td,
.table>tbody>tr.text-vcenter>th,
.table>tbody>tr>td.text-vcenter,
.table>tbody>tr>th.text-vcenter {
vertical-align: middle !important;
}
.table>tbody>tr>td.action-btn .dropdown-btn {
position: relative !important;
cursor: pointer;
padding: 0 0.75rem !important;
}
.table>tbody>tr>td.action-btn .dropdown-btn i {
font-weight: 900;
font-family: "Font Awesome 5 Free";
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 3;
}
.table>tbody>tr>td.action-btn .dropdown-btn i::before {
content: "\f141";
top: 50%;
left: 50%;
height: 23px;
width: 25px;
margin-top: -12px;
margin-left: -12px;
display: block;
position: absolute;
color: white;
/* border: 2px solid white; */
border-radius: 4px;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
line-height: 23px;
}
td a.cmp-status {
font-size: 1.33333em;
line-height: .75em;
vertical-align: -.0667em;
font-size: 1.33333em;
line-height: .75em;
vertical-align: -.0667em;
font-weight: 900;
font-family: "Font Awesome 5 Free";
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
text-decoration: none;
cursor: default;
}
td a.cmp-status.text-success {
text-decoration: underline !important;
cursor: pointer !important;
}
td a.cmp-status>b {
display: none;
}
JavaScript
ko.bindingHandlers.dataTablesForEach = {
page: 0,
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var binding = ko.utils.unwrapObservable(valueAccessor());
ko.unwrap(binding.data);
if (binding.options.paging) {
binding.data.subscribe(function(changes) {
var table = $(element).closest('table').DataTable();
ko.bindingHandlers.dataTablesForEach.page = table.page();
table.destroy();
}, null, 'arrayChange');
}
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function(node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var binding = ko.utils.unwrapObservable(valueAccessor()),
key = 'DataTablesForEach_Initialized';
ko.unwrap(binding.data);
var table;
if (!binding.options.paging) {
table = $(element).closest('table').DataTable();
table.destroy();
}
ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);
table = $(element).closest('table').DataTable(binding.options);
if (binding.options.paging) {
if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page === 0) {
table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false);
} else {
table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false);
}
}
if (!ko.utils.domData.get(element, key) && (binding.data || binding.length)) {
ko.utils.domData.set(element, key, true);
}
return {
controlsDescendantBindings: true
};
}
};
var ViewModel = function() {
this.employees = ko.observableArray([{
"name": "Airi Satou",
...