@codenameryuu/adonis-datatable
Version:
Package server side datatables on AdonisJS 6
357 lines (356 loc) • 10.3 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 result = {
draw: this.request.draw(),
recordsTotal: this.totalRecords,
recordsFiltered: 0,
data: [],
error: `Exception Message: ${exception.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;
}
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;
}
}