DataTables-based dropdown
This commit is contained in:
parent
0c860456a6
commit
38cf3e49c0
7 changed files with 332 additions and 105 deletions
|
@ -19,8 +19,6 @@
|
||||||
"axios": "^0.16.2",
|
"axios": "^0.16.2",
|
||||||
"datatables.net": "^1.10.15",
|
"datatables.net": "^1.10.15",
|
||||||
"datatables.net-bs": "^1.10.15",
|
"datatables.net-bs": "^1.10.15",
|
||||||
"datatables.net-select": "^1.2.2",
|
|
||||||
"datatables.net-select-bs": "^1.2.2",
|
|
||||||
"i18next": "^8.4.3",
|
"i18next": "^8.4.3",
|
||||||
"i18next-xhr-backend": "^1.4.2",
|
"i18next-xhr-backend": "^1.4.2",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { withPageHelpers } from './page'
|
||||||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||||
import { TreeTable, TreeSelectMode } from './tree';
|
import { TreeTable, TreeSelectMode } from './tree';
|
||||||
import { Table, TableSelectMode } from './table';
|
import { Table, TableSelectMode } from './table';
|
||||||
|
import { Button as ActionButton } from "./bootstrap-components";
|
||||||
|
|
||||||
import brace from 'brace';
|
import brace from 'brace';
|
||||||
import AceEditor from 'react-ace';
|
import AceEditor from 'react-ace';
|
||||||
|
@ -385,14 +386,25 @@ class TreeTableSelect extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@translate()
|
||||||
class TableSelect extends Component {
|
class TableSelect extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedLabel: '',
|
||||||
|
open: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dataUrl: PropTypes.string,
|
dataUrl: PropTypes.string,
|
||||||
data: PropTypes.array,
|
|
||||||
columns: PropTypes.array,
|
columns: PropTypes.array,
|
||||||
selectionKeyIndex: PropTypes.number,
|
selectionKeyIndex: PropTypes.number,
|
||||||
|
selectionLabelIndex: PropTypes.number,
|
||||||
selectMode: PropTypes.number,
|
selectMode: PropTypes.number,
|
||||||
withHeader: PropTypes.bool,
|
withHeader: PropTypes.bool,
|
||||||
|
dropdown: PropTypes.bool,
|
||||||
|
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
|
@ -400,27 +412,69 @@ class TableSelect extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
selectMode: TableSelectMode.SINGLE
|
selectMode: TableSelectMode.SINGLE,
|
||||||
|
selectionLabelIndex: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
formStateOwner: PropTypes.object.isRequired
|
formStateOwner: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSelectionChangedAsync(sel) {
|
async onSelectionChangedAsync(sel, data) {
|
||||||
|
if (this.props.selectMode === TableSelectMode.SINGLE && this.props.dropdown) {
|
||||||
|
this.setState({
|
||||||
|
open: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const owner = this.context.formStateOwner;
|
const owner = this.context.formStateOwner;
|
||||||
owner.updateFormValue(this.props.id, sel);
|
owner.updateFormValue(this.props.id, sel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onSelectionDataAsync(sel, data) {
|
||||||
|
if (this.props.selectMode === TableSelectMode.SINGLE && this.props.dropdown) {
|
||||||
|
this.setState({
|
||||||
|
selectedLabel: data ? data[this.props.selectionLabelIndex] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleOpen() {
|
||||||
|
this.setState({
|
||||||
|
open: !this.state.open
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
const owner = this.context.formStateOwner;
|
const owner = this.context.formStateOwner;
|
||||||
const id = this.props.id;
|
const id = this.props.id;
|
||||||
const htmlId = 'form_' + id;
|
const htmlId = 'form_' + id;
|
||||||
|
const t = props.t;
|
||||||
|
|
||||||
return wrapInput(id, htmlId, owner, props.label, props.help,
|
if (props.dropdown) {
|
||||||
<Table data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
return wrapInput(id, htmlId, owner, props.label, props.help,
|
||||||
);
|
<div>
|
||||||
|
<div className="input-group mt-tableselect-dropdown">
|
||||||
|
<input type="text" className="form-control" value={this.state.selectedLabel} readOnly onClick={::this.toggleOpen}/>
|
||||||
|
<span className="input-group-btn">
|
||||||
|
<ActionButton label={t('Select')} className="btn-default" onClickAsync={::this.toggleOpen}/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-tableselect-table' + (this.state.open ? '' : ' mt-tableselect-table-hidden')}>
|
||||||
|
<Table dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return wrapInput(id, htmlId, owner, props.label, props.help,
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Table dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,16 @@
|
||||||
.ace_editor {
|
.ace_editor {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-tableselect-dropdown {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-tableselect-table.mt-tableselect-table-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-tableselect-dropdown input[readonly] {
|
||||||
|
background-color: white;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
table.dataTable tbody > tr.selected {
|
||||||
|
background-color: rgb(218, 231, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.table-hover.dataTable tbody > tr.selected {
|
||||||
|
background-color: rgb(205, 212, 226);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.table-hover.dataTable tbody {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dataTables_wrapper .form-control {
|
||||||
|
border-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dataTables_wrapper .table-bordered {
|
||||||
|
border-radius: 3px;
|
||||||
|
border-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-error div.dataTables_wrapper .table-bordered {
|
||||||
|
border-color: #b94a48;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
div.dataTables_wrapper div.dataTable_selection_info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import '../../public/jquery/jquery-ui-1.12.1.min.js';
|
||||||
import 'datatables.net';
|
import 'datatables.net';
|
||||||
import 'datatables.net-bs';
|
import 'datatables.net-bs';
|
||||||
import 'datatables.net-bs/css/dataTables.bootstrap.css';
|
import 'datatables.net-bs/css/dataTables.bootstrap.css';
|
||||||
import 'datatables.net-select';
|
|
||||||
import 'datatables.net-select-bs/css/select.bootstrap.css';
|
|
||||||
|
|
||||||
import './table.css';
|
import './table.css';
|
||||||
import axios from './axios';
|
import axios from './axios';
|
||||||
|
@ -37,13 +35,7 @@ const TableSelectMode = {
|
||||||
class Table extends Component {
|
class Table extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.selectionMap = this.getSelectionMap(props);
|
||||||
// Select Mode simply cannot be changed later. This is just to make sure we avoid inconsistencies if someone changes it anyway.
|
|
||||||
this.selectMode = this.props.selectMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
selectMode: TableSelectMode.NONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -54,20 +46,121 @@ class Table extends Component {
|
||||||
selection: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
|
selection: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
|
||||||
selectionKeyIndex: PropTypes.number,
|
selectionKeyIndex: PropTypes.number,
|
||||||
onSelectionChangedAsync: PropTypes.func,
|
onSelectionChangedAsync: PropTypes.func,
|
||||||
|
onSelectionDataAsync: PropTypes.func,
|
||||||
actionLinks: PropTypes.array,
|
actionLinks: PropTypes.array,
|
||||||
withHeader: PropTypes.bool
|
withHeader: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
return this.props.selection !== nextProps.selection || this.props.data != nextProps.data || this.props.dataUrl != nextProps.dataUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
selectMode: TableSelectMode.NONE,
|
||||||
selectionKeyIndex: 0
|
selectionKeyIndex: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
getSelectionMap(props) {
|
||||||
|
let selArray = [];
|
||||||
|
if (props.selectMode === TableSelectMode.SINGLE) {
|
||||||
|
if (props.selection !== null && props.selection !== undefined) {
|
||||||
|
selArray = [props.selection];
|
||||||
|
} else {
|
||||||
|
selArray = [];
|
||||||
|
}
|
||||||
|
} else if (props.selectMode === TableSelectMode.MULTI) {
|
||||||
|
selArray = props.selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selMap = new Map();
|
||||||
|
|
||||||
|
for (const elem of selArray) {
|
||||||
|
selMap.set(elem, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.table) {
|
||||||
|
const self = this;
|
||||||
|
this.table.rows().every(function() {
|
||||||
|
const data = this.data();
|
||||||
|
const key = data[self.props.selectionKeyIndex];
|
||||||
|
if (selMap.has(key)) {
|
||||||
|
selMap.set(key, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return selMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectInfo() {
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
|
const count = this.selectionMap.size;
|
||||||
|
if (this.selectionMap.size > 0) {
|
||||||
|
const jqInfo = jQuery('<span>' + t('{{ count }} entries selected.', { count }) + ' </span>');
|
||||||
|
const jqDeselectLink = jQuery('<a href="">Deselect all.</a>').on('click', ::this.deselectAll);
|
||||||
|
|
||||||
|
this.jqSelectInfo.empty().append(jqInfo).append(jqDeselectLink);
|
||||||
|
} else {
|
||||||
|
this.jqSelectInfo.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@withAsyncErrorHandler
|
||||||
|
async fetchData(data, callback) {
|
||||||
|
// This custom ajax fetch function allows us to properly handle the case when the user is not authenticated.
|
||||||
|
const response = await axios.post(this.props.dataUrl, data);
|
||||||
|
callback(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@withAsyncErrorHandler
|
||||||
|
async fetchSelectionData() {
|
||||||
|
if (this.props.onSelectionDataAsync) {
|
||||||
|
const keysToFetch = [];
|
||||||
|
for (const pair of this.selectionMap.entries()) {
|
||||||
|
if (!pair[1]) {
|
||||||
|
keysToFetch.push(pair[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keysToFetch.length > 0) {
|
||||||
|
const response = await axios.post(this.props.dataUrl, {
|
||||||
|
operation: 'getBy',
|
||||||
|
column: this.props.selectionKeyIndex,
|
||||||
|
values: keysToFetch
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(response.data);
|
||||||
|
|
||||||
|
for (const row of response.data) {
|
||||||
|
const key = row[this.props.selectionKeyIndex];
|
||||||
|
if (this.selectionMap.has(key)) {
|
||||||
|
this.selectionMap.set(key, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifySelection(this.props.onSelectionDataAsync, this.selectionMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
const nextSelectionMap = this.getSelectionMap(nextProps);
|
||||||
|
|
||||||
|
let updateDueToSelectionChange = false;
|
||||||
|
if (nextSelectionMap.size !== this.selectionMap.size) {
|
||||||
|
updateDueToSelectionChange = true;
|
||||||
|
} else {
|
||||||
|
for (const key of this.selectionMap.keys()) {
|
||||||
|
if (!nextSelectionMap.has(key)) {
|
||||||
|
updateDueToSelectionChange = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectionMap = nextSelectionMap;
|
||||||
|
|
||||||
|
return updateDueToSelectionChange || this.props.data != nextProps.data || this.props.dataUrl != nextProps.dataUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
const columns = this.props.columns.slice();
|
const columns = this.props.columns.slice();
|
||||||
|
|
||||||
if (this.props.actionLinks) {
|
if (this.props.actionLinks) {
|
||||||
|
@ -99,15 +192,43 @@ class Table extends Component {
|
||||||
columns
|
columns
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.selectMode === TableSelectMode.MULTI) {
|
const self = this;
|
||||||
dtOptions.select = {
|
dtOptions.createdRow = function(row, data) {
|
||||||
style: 'os'
|
const rowKey = data[self.props.selectionKeyIndex];
|
||||||
|
|
||||||
|
if (self.selectionMap.has(rowKey)) {
|
||||||
|
jQuery(row).addClass('selected');
|
||||||
}
|
}
|
||||||
} else if (this.selectMode === TableSelectMode.SINGLE) {
|
|
||||||
dtOptions.select = {
|
jQuery(row).on('click', () => {
|
||||||
style: 'single'
|
const selectionMap = self.selectionMap;
|
||||||
}
|
|
||||||
}
|
if (self.props.selectMode === TableSelectMode.SINGLE) {
|
||||||
|
if (selectionMap.size !== 1 || !selectionMap.has(rowKey)) {
|
||||||
|
self.notifySelection(self.props.onSelectionChangedAsync, new Map([[rowKey, data]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (self.props.selectMode === TableSelectMode.MULTI) {
|
||||||
|
const newSelMap = new Map(selectionMap);
|
||||||
|
|
||||||
|
if (selectionMap.has(rowKey)) {
|
||||||
|
newSelMap.delete(rowKey);
|
||||||
|
} else {
|
||||||
|
newSelMap.set(rowKey, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.notifySelection(self.props.onSelectionChangedAsync, newSelMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
dtOptions.initComplete = function() {
|
||||||
|
self.jqSelectInfo = jQuery('<div class="dataTable_selection_info"/>');
|
||||||
|
const jqWrapper = jQuery(self.domTable).parents('.dataTables_wrapper');
|
||||||
|
jQuery('.dataTables_info', jqWrapper).after(self.jqSelectInfo);
|
||||||
|
|
||||||
|
self.updateSelectInfo();
|
||||||
|
};
|
||||||
|
|
||||||
if (this.props.data) {
|
if (this.props.data) {
|
||||||
dtOptions.data = this.props.data;
|
dtOptions.data = this.props.data;
|
||||||
|
@ -118,70 +239,70 @@ class Table extends Component {
|
||||||
|
|
||||||
this.table = jQuery(this.domTable).DataTable(dtOptions);
|
this.table = jQuery(this.domTable).DataTable(dtOptions);
|
||||||
|
|
||||||
this.table.on('select.dt', ::this.onSelect);
|
this.fetchSelectionData();
|
||||||
this.table.on('deselect.dt', ::this.onSelect);
|
|
||||||
|
|
||||||
this.updateSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
|
||||||
async fetchData(data, callback) {
|
|
||||||
// This custom ajax fetch function allows us to properly handle the case when the user is not authenticated.
|
|
||||||
const response = await axios.post(this.props.dataUrl, data);
|
|
||||||
callback(response.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.data) {
|
if (this.props.data) {
|
||||||
this.table.clear();
|
this.table.clear();
|
||||||
this.table.rows.add(this.props.data);
|
this.table.rows.add(this.props.data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const self = this;
|
||||||
|
this.table.rows().every(function() {
|
||||||
|
const key = this.data()[self.props.selectionKeyIndex];
|
||||||
|
if (self.selectionMap.has(key)) {
|
||||||
|
jQuery(this.node()).addClass('selected');
|
||||||
|
} else {
|
||||||
|
jQuery(this.node()).removeClass('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSelection();
|
this.updateSelectInfo();
|
||||||
|
this.fetchSelectionData();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelection() {
|
async notifySelection(eventCallback, newSelectionMap) {
|
||||||
let selArray = [];
|
if (eventCallback) {
|
||||||
if (this.selectMode === TableSelectMode.SINGLE) {
|
const selPairs = Array.from(newSelectionMap).sort((l, r) => l[0] - r[0]);
|
||||||
selArray = [this.props.selection];
|
|
||||||
} else if (this.selectMode === TableSelectMode.MULTI) {
|
|
||||||
selArray = this.props.selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selSet = new Set(selArray);
|
let data = selPairs.map(entry => entry[1]);
|
||||||
|
let sel = selPairs.map(entry => entry[0]);
|
||||||
|
|
||||||
const selectionKeyIndex = this.props.selectionKeyIndex;
|
if (this.props.selectMode === TableSelectMode.SINGLE) {
|
||||||
|
if (sel.length) {
|
||||||
this.table.rows({ selected: true }).every(function() {
|
sel = sel[0];
|
||||||
const key = this.data()[selectionKeyIndex];
|
data = data[0];
|
||||||
if (!selSet.has(key)) {
|
} else {
|
||||||
this.deselect();
|
sel = null;
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selSet.delete(key);
|
await eventCallback(sel, data);
|
||||||
});
|
}
|
||||||
|
|
||||||
this.table.rows((idx, data, node) => selSet.has(data[selectionKeyIndex])).select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSelect(event, data) {
|
async deselectAll(evt) {
|
||||||
let sel = this.table.rows( { selected: true } ).data().toArray().map(item => item[this.props.selectionKeyIndex]);
|
evt.preventDefault();
|
||||||
|
this.notifySelection(this.props.onSelectionChangedAsync, new Map());
|
||||||
if (this.selectMode === TableSelectMode.SINGLE) {
|
|
||||||
sel = sel.length ? sel[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.onSelectionChangedAsync) {
|
|
||||||
await this.props.onSelectionChangedAsync(sel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
|
let className = 'table table-striped table-bordered';
|
||||||
|
|
||||||
|
if (this.props.selectMode !== TableSelectMode.NONE) {
|
||||||
|
className += ' table-hover';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table ref={(domElem) => { this.domTable = domElem; }} className="table table-striped table-bordered" cellSpacing="0" width="100%" />
|
<div>
|
||||||
|
<table ref={(domElem) => { this.domTable = domElem; }} className={className} cellSpacing="0" width="100%" />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ export default class CUD extends Component {
|
||||||
<InputField id="name" label={t('Name')}/>
|
<InputField id="name" label={t('Name')}/>
|
||||||
<TextArea id="description" label={t('Description')} help={t('HTML is allowed')}/>
|
<TextArea id="description" label={t('Description')} help={t('HTML is allowed')}/>
|
||||||
|
|
||||||
<TableSelect id="report_template" label={t('Report Template')} withHeader dataUrl="/rest/report-templates-table" columns={columns} />
|
<TableSelect id="report_template" label={t('Report Template')} withHeader dropdown dataUrl="/rest/report-templates-table" columns={columns} selectionLabelIndex={1} />
|
||||||
|
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
|
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
|
||||||
|
|
|
@ -6,46 +6,56 @@ async function ajaxList(params, queryFun, columns) {
|
||||||
return await knex.transaction(async (tx) => {
|
return await knex.transaction(async (tx) => {
|
||||||
const query = queryFun(tx);
|
const query = queryFun(tx);
|
||||||
|
|
||||||
const recordsTotalQuery = query.clone().count('* as recordsTotal').first();
|
if (params.operation === 'getBy') {
|
||||||
const recordsTotal = (await recordsTotalQuery).recordsTotal;
|
query.whereIn(columns[parseInt(params.column)], params.values);
|
||||||
|
query.select(columns);
|
||||||
|
|
||||||
query.where(function() {
|
const rows = await query;
|
||||||
let searchVal = '%' + params.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
|
const rowsOfArray = rows.map(row => Object.keys(row).map(key => row[key]));
|
||||||
for (let colIdx = 0; colIdx < params.columns.length; colIdx++) {
|
return rowsOfArray;
|
||||||
const col = params.columns[colIdx];
|
|
||||||
if (col.searchable === 'true') {
|
} else {
|
||||||
this.orWhere(columns[parseInt(col.data)], 'like', searchVal);
|
const recordsTotalQuery = query.clone().count('* as recordsTotal').first();
|
||||||
|
const recordsTotal = (await recordsTotalQuery).recordsTotal;
|
||||||
|
|
||||||
|
query.where(function() {
|
||||||
|
let searchVal = '%' + params.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
|
||||||
|
for (let colIdx = 0; colIdx < params.columns.length; colIdx++) {
|
||||||
|
const col = params.columns[colIdx];
|
||||||
|
if (col.searchable) {
|
||||||
|
this.orWhere(columns[parseInt(col.data)], 'like', searchVal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const recordsFilteredQuery = query.clone().count('* as recordsFiltered').first();
|
||||||
|
const recordsFiltered = (await recordsFilteredQuery).recordsFiltered;
|
||||||
|
|
||||||
|
query.offset(parseInt(params.start));
|
||||||
|
|
||||||
|
const limit = parseInt(params.length);
|
||||||
|
if (limit >= 0) {
|
||||||
|
query.limit(limit);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const recordsFilteredQuery = query.clone().count('* as recordsFiltered').first();
|
query.select(columns);
|
||||||
const recordsFiltered = (await recordsFilteredQuery).recordsFiltered;
|
|
||||||
|
|
||||||
query.offset(parseInt(params.start));
|
for (const order of params.order) {
|
||||||
|
query.orderBy(columns[params.columns[order.column].data], order.dir);
|
||||||
|
}
|
||||||
|
|
||||||
const limit = parseInt(params.length);
|
const rows = await query;
|
||||||
if (limit >= 0) {
|
const rowsOfArray = rows.map(row => Object.keys(row).map(field => row[field]));
|
||||||
query.limit(limit);
|
|
||||||
|
const result = {
|
||||||
|
draw: params.draw,
|
||||||
|
recordsTotal,
|
||||||
|
recordsFiltered,
|
||||||
|
data: rowsOfArray
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
query.select(columns);
|
|
||||||
|
|
||||||
for (const order of params.order) {
|
|
||||||
query.orderBy(columns[params.columns[order.column].data], order.dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await query;
|
|
||||||
const rowsOfArray = rows.map(row => Object.keys(row).map(key => row[key]));
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
draw: params.draw,
|
|
||||||
recordsTotal,
|
|
||||||
recordsFiltered,
|
|
||||||
data: rowsOfArray
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue