@codenameryuu/adonis-datatable
Version:
Server side datatables package for Adonis JS
283 lines (282 loc) • 10.6 kB
JavaScript
import collect, { Collection } from "collect.js";
import lodash from "lodash";
import { DataTableAbstract } from "../datatable_abstract.js";
import Helper from "../utils/helper.js";
export default class ObjectDataTable extends DataTableAbstract {
items;
$offset = 0;
constructor(items) {
super();
this.items = items;
this.$columns = collect(this.items).keys();
}
static canCreate(source) {
return typeof source === "object" || source instanceof Collection;
}
resolveCallback() {
return this;
}
defaultOrdering() {
const self = this;
const orderable = this.request.orderableColumns();
if (orderable.length) {
this.items = collect(this.items)
.map((data) => Helper.dot(data))
.sort((a, b) => {
for (const value of Object.values(orderable)) {
const column = self.getColumnName(value["column"]);
const direction = value["direction"];
let first;
let second;
let cmp = 0;
if (direction === "desc") {
first = b;
second = a;
}
else {
first = a;
second = b;
}
if (Number.isInteger(first[column] ?? null) && Number.isInteger(second[column] ?? null)) {
if (first[column] < second[column]) {
cmp = -1;
}
else if (first[column] > second[column]) {
cmp = 1;
}
}
else if (self.config.isCaseInsensitive()) {
const collator = new Intl.Collator(undefined, { sensitivity: "base", numeric: true });
cmp = collator.compare(first[column], second[column]);
}
else {
const collator = new Intl.Collator(undefined, {
sensitivity: "variant",
numeric: true,
});
cmp = collator.compare(first[column], second[column]);
}
return cmp;
}
return 0;
})
.map((data) => {
for (const [index, value] of Object.entries(data)) {
lodash.unset(data, index);
lodash.set(data, index, value);
}
return data;
})
.all();
}
}
revertIndexColumn() {
if (this.$columnDef["index"]) {
const indexColumn = this.config.get("datatables.index_column", "DT_RowIndex");
const index = this.$dataObject ? indexColumn : 0;
let start = this.request.start();
collect(this.items).transform((data) => {
data[index] = ++start;
return data;
});
}
}
async count() {
return collect(this.items).count();
}
async results() {
try {
this.prepareContext();
this.totalRecords = await this.totalCount();
if (this.totalRecords) {
const results = this.dataResults();
const processed = this.processResults(results);
const output = lodash.transform(processed, (result, value, key) => {
if (value) {
result[key] = value;
}
});
this.items = collect(output).all();
this.ordering();
await this.filterRecords();
this.paginate();
this.revertIndexColumn();
}
return this.render(collect(this.items).all());
}
catch (error) {
return this.errorResponse(error);
}
}
dataResults() {
return collect(this.items).all();
}
setOffset(offset) {
this.$offset = offset;
return this;
}
columnSearch() {
const self = this;
for (let i = 0, c = this.request.columns().length; i < c; i++) {
const column = this.getColumnName(i);
if (column === null) {
continue;
}
if (this.isBlacklisted(column)) {
continue;
}
if (this.request.hasColumnControl(i) && this.request.isColumnSearchable(i, false)) {
this.applyColumnControlSearch(i, column);
continue;
}
if (!this.request.isColumnSearchable(i)) {
continue;
}
const regex = this.request.isRegex(i);
const keyword = this.request.columnKeyword(i);
this.items = collect(this.items)
.filter((row) => {
const value = lodash.get(row, column);
if (self.config.isCaseInsensitive()) {
if (regex) {
return new RegExp(keyword, "i").test(value);
}
return Helper.contains(value.toLowerCase(), keyword.toLowerCase());
}
if (regex) {
return new RegExp(keyword).test(value);
}
return Helper.contains(value, keyword);
})
.all();
}
}
/**
* Apply the search conditions sent by the DataTables ColumnControl extension.
*
* @see https://datatables.net/extensions/columncontrol/server-side
*/
applyColumnControlSearch(index, column) {
const self = this;
if (this.request.hasColumnControlList(index)) {
const list = this.request.columnControlList(index).map((value) => self.normalizeColumnControl(value));
this.items = collect(this.items)
.filter((row) => list.includes(self.normalizeColumnControl(lodash.get(row, column))))
.all();
}
const search = this.request.columnControlSearch(index);
if (search && (search.value !== "" || search.logic === "empty" || search.logic === "notEmpty")) {
this.items = collect(this.items)
.filter((row) => self.matchColumnControl(lodash.get(row, column), search))
.all();
}
}
normalizeColumnControl(value) {
const text = value === null || value === undefined ? "" : String(value);
return this.config.isCaseInsensitive() ? text.toLowerCase() : text;
}
matchColumnControl(raw, search) {
const logic = search.logic;
const isEmpty = raw === null || raw === undefined || String(raw) === "";
if (logic === "empty") {
return isEmpty;
}
if (logic === "notEmpty") {
return !isEmpty;
}
if (search.type === "num") {
const left = Number(raw);
const right = Number(search.value);
if (Number.isNaN(left) || Number.isNaN(right)) {
return false;
}
switch (logic) {
case "notEqual":
return left !== right;
case "greater":
return left > right;
case "greaterOrEqual":
return left >= right;
case "less":
return left < right;
case "lessOrEqual":
return left <= right;
case "equal":
default:
return left === right;
}
}
if (search.type === "date") {
const dateOnly = !!search.mask && !/[Hhms]/.test(search.mask);
const left = this.toTimestamp(raw, dateOnly);
const right = this.toTimestamp(search.value, dateOnly);
if (Number.isNaN(left) || Number.isNaN(right)) {
return false;
}
switch (logic) {
case "notEqual":
return left !== right;
case "greater":
return left > right;
case "less":
return left < right;
case "equal":
default:
return left === right;
}
}
const haystack = this.normalizeColumnControl(raw);
const needle = this.normalizeColumnControl(search.value);
switch (logic) {
case "equal":
return haystack === needle;
case "notEqual":
return haystack !== needle;
case "starts":
return haystack.startsWith(needle);
case "ends":
return haystack.endsWith(needle);
case "notContains":
return !haystack.includes(needle);
case "contains":
default:
return haystack.includes(needle);
}
}
toTimestamp(value, dateOnly) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return Number.NaN;
}
if (dateOnly) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
}
return date.getTime();
}
paging() {
const offset = this.request.start() - this.$offset;
const length = this.request.length() > 0 ? this.request.length() : 10;
this.items = collect(this.items).slice(offset, length).all();
}
globalSearch(keyword) {
keyword = this.config.isCaseInsensitive() ? keyword.toLowerCase() : keyword;
this.items = collect(this.items)
.filter((row) => {
for (const index of Object.values(this.request.searchableColumnIndex())) {
const column = this.getColumnName(index);
let value = lodash.get(row, column);
if (typeof value !== "string") {
continue;
}
else {
value = this.config.isCaseInsensitive() ? value.toLowerCase() : value;
}
if (Helper.contains(value, keyword)) {
return true;
}
}
return false;
})
.all();
}
}