@codenameryuu/adonis-datatable
Version:
Server side datatables package for Adonis JS
368 lines (367 loc) • 10.9 kB
JavaScript
import Request from "./request.js";
import { HttpContext } from "@adonisjs/core/http";
import collect from "collect.js";
import Config from "./config.js";
import DataProcessor from "./processor.js";
import Helper from "./utils/helper.js";
import app from "@adonisjs/core/services/app";
import emitter from "@adonisjs/core/services/emitter";
export class DataTableAbstract {
ctx;
config;
request;
$columns = {};
$columnDef = {
index: false,
append: [],
edit: [],
filter: {},
order: {},
only: [],
};
$extraColumns = [];
totalRecords = 0;
filteredRecords = 0;
$skipTotalRecords = false;
autoFilter = true;
filterCallback = null;
$templates = {
DT_RowId: "",
DT_RowClass: "",
DT_RowData: {},
DT_RowAttr: {},
};
orderCallback = null;
$skipPaging = false;
appends = {};
$editOnlySelectedColumns = false;
$dataObject = true;
$queryLogging = [];
constructor() {
this.config = new Config(app.config.get("datatables"));
emitter.on("db:query", (query) => {
this.$queryLogging.push({
query: query.sql,
bindings: query.bindings,
time: `${query.duration}ms`,
});
});
}
static canCreate(_source) {
return false;
}
static create(source) {
return new this(source);
}
prepareContext() {
if (this.ctx) {
return;
}
this.ctx = this.ctx ? this.ctx : HttpContext.getOrFail();
this.request = new Request(this.ctx.request);
}
async totalCount() {
const total = await this.count();
return this.totalRecords ? this.totalRecords : total;
}
isBlacklisted(column) {
const columnDef = this.getColumnsDefinition();
if (columnDef["blacklist"].includes(column)) {
return true;
}
if (columnDef["whitelist"] === "*" || (columnDef["whitelist"] && columnDef["whitelist"].includes(column))) {
return false;
}
return true;
}
async filterRecords() {
if (this.autoFilter && this.request.isSearchable()) {
this.filtering();
}
if (typeof this.filterCallback === "function") {
this.filterCallback(this.resolveCallback());
}
this.columnSearch();
await this.filteredCount();
}
smartGlobalSearch(keyword) {
const self = this;
collect(keyword.split(" "))
.reject((text) => text.trim() === "")
.each((text) => self.globalSearch(text));
}
async filteredCount() {
const total = await this.count();
return (this.filteredRecords = this.filteredRecords ? this.filteredRecords : total);
}
paginate() {
if (this.request.isPaginationable() && !this.$skipPaging) {
this.paging();
}
}
processResults(results) {
const processor = new DataProcessor(results, this.getColumnsDefinition(), this.$templates, this.request.start(), this.config);
return processor.process(this.$dataObject);
}
render(data) {
let result = this.attachAppends({
draw: this.request.draw(),
recordsTotal: this.totalRecords,
recordsFiltered: this.filteredRecords ?? 0,
data: data,
});
if (this.config.isDebugging()) {
result = this.showDebugger(result);
}
return result;
}
attachAppends(data) {
return { ...data, ...this.appends };
}
showDebugger(output) {
output["queries"] = this.$queryLogging;
output["input"] = this.request.all();
return output;
}
errorResponse(exception) {
emitter.clearListeners("db:query");
if (!this.ctx) {
return;
}
const stack = exception instanceof Error ? exception.stack : String(exception);
const result = {
draw: this.request.draw(),
recordsTotal: this.totalRecords,
recordsFiltered: 0,
data: [],
error: `Exception Message: ${stack}`,
};
return result;
}
setupKeyword(value) {
if (this.config.isSmartSearch()) {
let keyword = `%${value}%}`;
if (this.config.isWildcard()) {
keyword = Helper.wildcardString(value, "%");
}
return keyword.replace("\\", "%");
}
return value;
}
getColumnName(index, wantsAlias = false) {
let column = this.request.columnName(index);
if (column === null || column === undefined) {
return null;
}
if (Number.isInteger(column)) {
column = this.getColumnNameByIndex(index);
}
if (Helper.contains(column.toUpperCase(), " AS ")) {
column = Helper.extractColumnName(column, wantsAlias);
}
return column;
}
getColumnNameByIndex(index) {
const name = this.$columns[index] !== undefined && this.$columns[index] !== "*" ? this.$columns[index] : this.getPrimaryKeyName();
return this.$extraColumns.includes(name) ? this.getPrimaryKeyName() : name;
}
getPrimaryKeyName() {
return "id";
}
setContext(ctx) {
this.ctx = ctx;
this.request = new Request(this.ctx.request);
return this;
}
addColumn(name, content, order = false) {
this.$extraColumns.push(name);
this.$columnDef["append"].push({
name: name,
content: content,
order: order,
});
return this;
}
addIndexColumn() {
this.$columnDef["index"] = true;
return this;
}
editColumn(name, content) {
if (this.$editOnlySelectedColumns) {
if (!this.request.columns().length ||
this.request
.columns()
.map((column) => column.name)
.includes(name)) {
this.$columnDef["edit"].push({
name: name,
content: content,
});
}
}
else {
this.$columnDef["edit"].push({
name: name,
content: content,
});
}
return this;
}
removeColumn(...names) {
this.$columnDef.excess = [...this.getColumnsDefinition()["access"], ...names];
return this;
}
getColumnsDefinition() {
const datatableColumns = this.config.get("columns");
const allowed = ["excess", "escape", "raw", "blacklist", "whitelist"];
return Helper.objectReplaceRecursive(Helper.objectIntersectKey(datatableColumns, allowed), this.$columnDef);
}
only(columns = []) {
this.$columnDef["only"] = columns;
return this;
}
escapeColumns(columns = "*") {
this.$columnDef["escape"] = columns;
return this;
}
rawColumns(columns, merge = false) {
if (merge) {
const datatableColumns = this.config.get("columns");
this.$columnDef["raw"] = [...datatableColumns.raw, ...columns];
}
else {
this.$columnDef["raw"] = columns;
}
return this;
}
setRowClass(content) {
this.$templates["DT_RowClass"] = content;
return this;
}
setRowId(content) {
this.$templates["DT_RowId"] = content;
return this;
}
setRowData(data) {
this.$templates["DT_RowData"] = data;
return this;
}
addRowData(key, value) {
this.$templates["DT_RowData"][key] = value;
return this;
}
setRowAttr(data) {
this.$templates["DT_RowAttr"] = data;
return this;
}
addRowAttr(key, value) {
this.$templates["DT_RowAttr"][key] = value;
return this;
}
with(key, value = "") {
if (Array.isArray(key)) {
this.appends = key;
}
else {
this.appends[key] = Helper.value(value);
}
return this;
}
withQuery(key, callback) {
this.appends[key] = callback;
return this;
}
/**
* Send a list of options for the DataTables ColumnControl `searchList` content type.
* The `columnDataSrc` should match the column name, data source or index used client-side.
*
* @see https://datatables.net/extensions/columncontrol/server-side
*/
columnControl(columnDataSrc, options) {
if (!this.appends["columnControl"] || typeof this.appends["columnControl"] !== "object") {
this.appends["columnControl"] = {};
}
this.appends["columnControl"][columnDataSrc] = options;
return this;
}
order(callback) {
this.orderCallback = callback;
return this;
}
blacklist(blacklist) {
this.$columnDef["blacklist"] = blacklist;
return this;
}
whitelist(whitelist = "*") {
this.$columnDef["whitelist"] = whitelist;
return this;
}
smart(state = true) {
this.config.set("datatables.search.smart", state);
return this;
}
startsWithSearch(state = true) {
this.config.set("datatables.search.starts_with", state);
return this;
}
setTotalRecords(total) {
this.totalRecords = total;
return this;
}
skipTotalRecords() {
this.totalRecords = 0;
this.$skipTotalRecords = true;
return this;
}
setFilteredRecords(total) {
this.filteredRecords = total;
return this;
}
skipPaging() {
this.$skipPaging = true;
return this;
}
pushToBlacklist(column) {
if (!this.isBlacklisted(column)) {
if (this.$columnDef["blacklist"] === undefined) {
this.$columnDef["blacklist"] = [];
}
this.$columnDef["blacklist"].push(column);
}
return this;
}
filtering() {
const keyword = this.request.keyword();
if (this.config.isMultiTerm()) {
this.smartGlobalSearch(keyword);
return;
}
this.globalSearch(keyword);
}
editOnlySelectedColumns() {
this.$editOnlySelectedColumns = true;
return this;
}
ordering() {
if (typeof this.orderCallback === "function") {
this.orderCallback(this.resolveCallback());
return;
}
else {
this.defaultOrdering();
}
}
filter(callback, globalSearch = false) {
this.filterCallback = callback;
this.autoFilter = globalSearch;
return this;
}
asJson() {
this.$dataObject = true;
return this;
}
asArray() {
this.$dataObject = false;
return this;
}
}