@codenameryuu/adonis-datatable
Version:
Package server side datatables on AdonisJS 6
320 lines (319 loc) • 11.1 kB
JavaScript
import { DataTableAbstract } from '../datatable_abstract.js';
import { DatabaseQueryBuilder } from '@adonisjs/lucid/database';
import Helper from '../utils/helper.js';
import lodash from 'lodash';
import collect from 'collect.js';
import { sprintf } from 'sprintf-js';
export default class DatabaseDataTable extends DataTableAbstract {
query;
nullsLast = false;
prepared = false;
keepSelectBindings = false;
disableUserOrdering = false;
constructor(query) {
super();
this.query = query;
this.$columns = query.columns;
}
static canCreate(source) {
return source instanceof DatabaseQueryBuilder;
}
getConnection() {
return this.query.knexQuery.client;
}
defaultOrdering() {
const self = this;
collect(this.request.orderableColumns())
.map((orderable) => {
orderable['name'] = self.getColumnName(orderable['column'], true);
return orderable;
})
.reject((orderable) => self.isBlacklisted(orderable['name']) && !self.hasOrderColumn(orderable['name']))
.each((orderable) => {
const column = self.resolveRelationColumn(orderable['name']);
if (self.hasOrderColumn(orderable['name'])) {
self.applyOrderColumn(orderable['name'], orderable);
}
else if (self.hasOrderColumn(column)) {
self.applyOrderColumn(column, orderable);
}
else {
const nullsLastSql = self.getNullsLastSql(column, orderable['direction']);
const normalSql = self.wrapColumn(column) + ' ' + orderable['direction'];
const sql = self.nullsLast ? nullsLastSql : normalSql;
self.query.orderByRaw(sql);
}
});
}
hasOrderColumn(column) {
return this.$columnDef['order'][column] !== undefined;
}
applyOrderColumn(column, orderable) {
let sql = this.$columnDef['order'][column]['sql'];
if (sql === false) {
return;
}
if (typeof sql === 'function') {
sql(this.query, orderable['direction']);
}
else {
sql = sql.replace('$1', orderable['direction']);
const bindings = this.$columnDef['order'][column]['bindings'];
this.query.orderByRaw(sql, bindings);
}
}
getNullsLastSql(column, direction) {
const sql = this.config.get('datatables.nulls_last_sql', '%s %s NULLS LAST');
return sprintf(sql, column, direction)
.replace(':column', column)
.replace(':direction', direction);
}
attachAppends(data) {
const appends = {};
for (const [key, value] of Object.entries(this.appends)) {
if (typeof value === 'function') {
appends[key] = Helper.value(value(this.getFilteredQuery()));
}
else {
appends[key] = value;
}
}
appends['disableOrdering'] = this.disableUserOrdering;
return { ...data, ...appends };
}
getColumnSearchKeyword(i, raw = false) {
const keyword = this.request.columnKeyword(i);
if (raw || this.request.isRegex(i)) {
return keyword;
}
return this.setupKeyword(keyword);
}
applyFilterColumn(query, columnName, keyword) {
query = this.getBaseQueryBuilder(query);
const callback = this.$columnDef['filter'][columnName]['method'];
callback(query, keyword);
}
resolveRelationColumn(column) {
return column;
}
compileColumnSearch(i, column, keyword) {
if (this.request.isRegex(i)) {
this.regexColumnSearch(column, keyword);
}
else {
this.compileQuerySearch(this.query, column, keyword, '');
}
}
regexColumnSearch(column, keyword) {
column = this.wrapColumn(column);
let sql = '';
switch (this.getConnection().driverName) {
case 'oracle':
sql = !this.config.isCaseInsensitive()
? 'REGEXP_LIKE( ' + column + ' , ? )'
: 'REGEXP_LIKE( LOWER(' + column + ") , ?, 'i' )";
break;
case 'pgsql':
column = this.castColumn(column);
sql = !this.config.isCaseInsensitive() ? column + ' ~ ?' : column + ' ~* ? ';
break;
default:
sql = !this.config.isCaseInsensitive()
? column + ' REGEXP ?'
: 'LOWER(' + column + ') REGEXP ?';
keyword = keyword.toLowerCase();
}
this.query.whereRaw(sql, [keyword]);
}
compileQuerySearch(query, columnName, keyword, boolean = 'or') {
let column = this.addTablePrefix(query, columnName);
column = this.castColumn(column);
let sql = column + ' LIKE ?';
if (this.config.isCaseInsensitive()) {
sql = 'LOWER(' + column + ') LIKE ?';
}
const method = lodash.lowerFirst(`${boolean}WhereRaw`);
query[method](sql, [this.prepareKeyword(keyword)]);
}
prepareKeyword(keyword) {
if (this.config.isCaseInsensitive()) {
keyword = keyword.toLowerCase();
}
if (this.config.isStartsWithSearch()) {
return `${keyword}%`;
}
if (this.config.isWildcard()) {
keyword = Helper.wildcardString(keyword, '%');
}
if (this.config.isSmartSearch()) {
keyword = `%${keyword}%`;
}
return keyword;
}
async prepareQuery() {
if (!this.prepared) {
this.totalRecords = await this.totalCount();
await this.filterRecords();
this.ordering();
this.paginate();
}
this.prepared = true;
return this;
}
async filterRecords() {
const initialQuery = this.query.clone();
if (this.autoFilter && this.request.isSearchable()) {
this.filtering();
}
if (typeof this.filterCallback === 'function') {
this.filterCallback(this.query);
}
this.columnSearch();
if (!this.$skipTotalRecords && this.query === initialQuery) {
this.filteredRecords ??= this.totalRecords;
}
else {
await this.filteredCount();
if (this.$skipTotalRecords) {
this.totalRecords = this.filteredRecords;
}
}
}
hasFilterColumn(columnName) {
return this.$columnDef['filter'][columnName] !== undefined;
}
wrapColumn(column) {
return Helper.wrapColumn(column, true);
}
getBaseQueryBuilder(instance = undefined) {
if (!instance) {
instance = this.query;
}
return instance;
}
castColumn(column) {
const driverName = this.getConnection().driverName;
switch (driverName) {
case 'pgsql':
return `CAST(${column} AS TEXT)`;
case 'firebird':
return `CAST(${column} AS VARCHAR(255))`;
default:
return column;
}
}
addTablePrefix(query, column) {
if (!column.includes('.')) {
const sql = this.getBaseQueryBuilder(query).toSQL().sql;
const tableName = sql.match(/from\s+`?(\w+)`?/i)?.[1] || '';
let from = tableName;
if (typeof from === 'string') {
if (from.includes(' as ')) {
from = from.split(' as ')[1];
}
column = `${from}.${column}`;
}
}
return this.wrapColumn(column);
}
getFilteredQuery() {
this.prepareQuery();
return this.query;
}
resolveCallback() {
return this.query;
}
async results() {
try {
this.prepareContext();
const query = await this.prepareQuery();
const results = await query.dataResults();
const processed = this.processResults(results);
return this.render(processed);
}
catch (error) {
return this.errorResponse(error);
}
}
async dataResults() {
return await this.query;
}
async count() {
const builder = this.query.clone();
const result = await builder.exec();
return result.length;
}
columnSearch() {
const columns = this.request.columns();
for (let index = 0; index < columns.length; index++) {
let column = this.getColumnName(index);
if (column === null) {
continue;
}
if (!this.request.isColumnSearchable(index) ||
(this.isBlacklisted(column) && !this.hasFilterColumn(column))) {
continue;
}
if (this.hasFilterColumn(column)) {
const keyword = this.getColumnSearchKeyword(index, true);
this.applyFilterColumn(this.getBaseQueryBuilder(), column, keyword);
}
else {
column = this.resolveRelationColumn(column);
const keyword = this.getColumnSearchKeyword(index);
this.compileColumnSearch(index, column, keyword);
}
}
}
filterColumn(column, callback) {
this.$columnDef['filter'][column] = { method: callback };
return this;
}
orderColumns(columns, sql, bindings = []) {
for (const column of Object.values(columns)) {
this.orderColumn(column, sql.replace(':column', column), bindings);
}
return this;
}
orderColumn(column, sql, bindings = []) {
this.$columnDef['order'][column] = { sql: sql, bindings: bindings };
return this;
}
orderByNullsLast() {
this.nullsLast = true;
return this;
}
paging() {
const start = this.request.start();
const length = this.request.length();
const limit = length > 0 ? length : 10;
this.query.offset(start).limit(limit);
}
addColumn(name, content, order = false) {
this.pushToBlacklist(name);
return super.addColumn(name, content, order);
}
globalSearch(keyword) {
const self = this;
this.query.where((query) => {
collect(self.request.searchableColumnIndex())
.map((index) => super.getColumnName(index))
.filter(() => true)
.reject((column) => self.isBlacklisted(column) && !self.hasFilterColumn(column))
.each((column) => {
if (self.hasFilterColumn(column)) {
self.applyFilterColumn(query, column, keyword);
}
else {
self.compileQuerySearch(query, column, keyword);
}
});
});
}
ordering() {
if (this.disableUserOrdering) {
return;
}
super.ordering();
}
}