UNPKG

@codenameryuu/adonis-datatable

Version:
368 lines (367 loc) 10.9 kB
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; } }