diff --git a/packages/datalib/src/TableFormViewDisplay.ts b/packages/datalib/src/TableFormViewDisplay.ts
index 0554fab06..9b7f90ec7 100644
--- a/packages/datalib/src/TableFormViewDisplay.ts
+++ b/packages/datalib/src/TableFormViewDisplay.ts
@@ -3,9 +3,18 @@ import _ from 'lodash';
import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import { GridConfig, GridCache, createGridCache } from './GridConfig';
-import { Expression, Select, treeToSql, dumpSqlSelect, mergeConditions, Condition } from 'dbgate-sqltree';
+import {
+ Expression,
+ Select,
+ treeToSql,
+ dumpSqlSelect,
+ mergeConditions,
+ Condition,
+ OrderByExpression,
+} from 'dbgate-sqltree';
import { filterName } from './filterName';
import { TableGridDisplay } from './TableGridDisplay';
+import stableStringify from 'json-stable-stringify';
export class TableFormViewDisplay extends FormViewDisplay {
public table: TableInfo;
@@ -28,7 +37,7 @@ export class TableFormViewDisplay extends FormViewDisplay {
this.columns = this.gridDisplay.columns;
}
- getPrimaryKeyCondition(): Condition {
+ getPrimaryKeyEqualCondition(): Condition {
if (!this.config.formViewKey) return null;
return {
conditionType: 'and',
@@ -50,12 +59,130 @@ export class TableFormViewDisplay extends FormViewDisplay {
};
}
- getCurrentRowQuery() {
+ getPrimaryKeyOperatorCondition(operator): Condition {
+ if (!this.config.formViewKey) return null;
+ const conditions = [];
+
+ const { primaryKey } = this.gridDisplay.baseTable;
+ for (let index = 0; index < primaryKey.columns.length; index++) {
+ conditions.push({
+ conditionType: 'and',
+ conditions: [
+ ...primaryKey.columns.slice(0, index).map(({ columnName }) => ({
+ conditionType: 'binary',
+ operator: '=',
+ left: {
+ exprType: 'column',
+ columnName,
+ source: {
+ alias: 'basetbl',
+ },
+ },
+ right: {
+ exprType: 'value',
+ value: this.config.formViewKey[columnName],
+ },
+ })),
+ ...primaryKey.columns.slice(index).map(({ columnName }) => ({
+ conditionType: 'binary',
+ operator: operator,
+ left: {
+ exprType: 'column',
+ columnName,
+ source: {
+ alias: 'basetbl',
+ },
+ },
+ right: {
+ exprType: 'value',
+ value: this.config.formViewKey[columnName],
+ },
+ })),
+ ],
+ });
+ }
+
+ if (conditions.length == 1) {
+ return conditions[0];
+ }
+
+ return {
+ conditionType: 'or',
+ conditions,
+ };
+ }
+
+ getSelect() {
if (!this.driver) return null;
const select = this.gridDisplay.createSelect();
if (!select) return null;
select.topRecords = 1;
- select.where = mergeConditions(select.where, this.getPrimaryKeyCondition());
+ return select;
+ }
+
+ getCurrentRowQuery() {
+ const select = this.getSelect();
+ if (!select) return null;
+
+ select.where = mergeConditions(select.where, this.getPrimaryKeyEqualCondition());
+ const sql = treeToSql(this.driver, select, dumpSqlSelect);
+ return sql;
+ }
+
+ extractKey(row) {
+ const formViewKey = _.pick(
+ row,
+ this.gridDisplay.baseTable.primaryKey.columns.map((x) => x.columnName)
+ );
+ return formViewKey;
+ }
+
+ navigate(row) {
+ const formViewKey = this.extractKey(row);
+ this.setConfig((cfg) => ({
+ ...cfg,
+ formViewKey,
+ }));
+ }
+
+ isLoadedCurrentRow(row) {
+ if (!row) return false;
+ const formViewKey = this.extractKey(row);
+ return stableStringify(formViewKey) == stableStringify(this.config.formViewKey);
+ }
+
+ navigateRowQuery(commmand: 'begin' | 'previous' | 'next' | 'end') {
+ if (!this.driver) return null;
+ const select = this.gridDisplay.createSelect();
+ if (!select) return null;
+ const { primaryKey } = this.gridDisplay.baseTable;
+
+ function getOrderBy(direction): OrderByExpression[] {
+ return primaryKey.columns.map(({ columnName }) => ({
+ exprType: 'column',
+ columnName,
+ direction,
+ }));
+ }
+
+ select.topRecords = 1;
+ switch (commmand) {
+ case 'begin':
+ select.orderBy = getOrderBy('ASC');
+ break;
+ case 'end':
+ select.orderBy = getOrderBy('DESC');
+ break;
+ case 'previous':
+ select.orderBy = getOrderBy('DESC');
+ select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('<'));
+ break;
+ case 'next':
+ select.orderBy = getOrderBy('ASC');
+ select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('>'));
+ break;
+ }
+
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
diff --git a/packages/web/src/formview/FormView.js b/packages/web/src/formview/FormView.js
index 158e74d14..48d3dfa00 100644
--- a/packages/web/src/formview/FormView.js
+++ b/packages/web/src/formview/FormView.js
@@ -62,7 +62,7 @@ const NullSpan = styled.span`
`;
export default function FormView(props) {
- const { rowData, toolbarPortalRef, tabVisible, config, setConfig } = props;
+ const { rowData, toolbarPortalRef, tabVisible, config, setConfig, onNavigate } = props;
/** @type {import('dbgate-datalib').FormViewDisplay} */
const formDisplay = props.formDisplay;
const theme = useTheme();
@@ -81,7 +81,10 @@ export default function FormView(props) {
toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
- ReactDOM.createPortal(, toolbarPortalRef.current);
+ ReactDOM.createPortal(
+ ,
+ toolbarPortalRef.current
+ );
// console.log('display', display);
diff --git a/packages/web/src/formview/FormViewToolbar.js b/packages/web/src/formview/FormViewToolbar.js
index 280c0124a..a0d070f0b 100644
--- a/packages/web/src/formview/FormViewToolbar.js
+++ b/packages/web/src/formview/FormViewToolbar.js
@@ -1,12 +1,24 @@
import React from 'react';
import ToolbarButton from '../widgets/ToolbarButton';
-export default function FormViewToolbar({ switchToTable }) {
+export default function FormViewToolbar({ switchToTable, onNavigate }) {
return (
<>
Table view
+ onNavigate('begin')} icon="icon arrow-begin">
+ First
+
+ onNavigate('previous')} icon="icon arrow-left">
+ Previous
+
+ onNavigate('next')} icon="icon arrow-right">
+ Next
+
+ onNavigate('end')} icon="icon arrow-end">
+ Last
+
>
);
}
diff --git a/packages/web/src/formview/SqlFormView.js b/packages/web/src/formview/SqlFormView.js
index e8ca69b2c..9f252e2ed 100644
--- a/packages/web/src/formview/SqlFormView.js
+++ b/packages/web/src/formview/SqlFormView.js
@@ -6,11 +6,10 @@ import useExtensions from '../utility/useExtensions';
import FormView from './FormView';
import axios from '../utility/axios';
-async function loadCurrentRow(props) {
- const { formDisplay, conid, database } = props;
+async function loadRow(props, sql) {
+ const { conid, database } = props;
/** @type {import('dbgate-datalib').TableFormViewDisplay} */
-
- const sql = formDisplay.getCurrentRowQuery();
+ const formDisplay = props.formDisplay;
const response = await axios.request({
url: 'database-connections/query-data',
@@ -31,12 +30,22 @@ export default function SqlFormView(props) {
const [rowData, setRowData] = React.useState(null);
const handleLoadCurrentRow = async () => {
- const row = await loadCurrentRow(props);
+ const row = await loadRow(props, formDisplay.getCurrentRowQuery());
if (row) setRowData(row);
};
+ const handleNavigate = async (command) => {
+ const row = await loadRow(props, formDisplay.navigateRowQuery(command));
+ if (row) {
+ setRowData(row);
+ formDisplay.navigate(row);
+ }
+ };
+
React.useEffect(() => {
- handleLoadCurrentRow();
+ if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) {
+ handleLoadCurrentRow();
+ }
}, [formDisplay]);
// const { config, setConfig, cache, setCache, schemaName, pureName, conid, database } = props;
@@ -67,5 +76,5 @@ export default function SqlFormView(props) {
// setDisplay(newDisplay);
// }, [config, cache, conid, database, schemaName, pureName, dbinfo, extensions]);
- return ;
+ return ;
}
diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js
index 357bde17b..369ce50e8 100644
--- a/packages/web/src/icons.js
+++ b/packages/web/src/icons.js
@@ -39,6 +39,8 @@ const iconNames = {
'icon arrow-up': 'mdi mdi-arrow-up',
'icon arrow-down': 'mdi mdi-arrow-down',
'icon arrow-left': 'mdi mdi-arrow-left',
+ 'icon arrow-begin': 'mdi mdi-arrow-collapse-left',
+ 'icon arrow-end': 'mdi mdi-arrow-collapse-right',
'icon arrow-right': 'mdi mdi-arrow-right',
'icon format-code': 'mdi mdi-code-tags-check',
'icon show-wizard': 'mdi mdi-comment-edit',