UNPKG

@codenameryuu/adonis-datatable

Version:
320 lines (319 loc) 11.1 kB
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(); } }