feat: add server side pagination in list widget (#7128)
This commit is contained in:
parent
2121050177
commit
de20a2a52f
|
|
@ -81,6 +81,7 @@ export enum EventType {
|
|||
ON_SNIPPET_EXECUTE = "ON_SNIPPET_EXECUTE",
|
||||
ON_SORT = "ON_SORT",
|
||||
ON_CHECKBOX_GROUP_SELECTION_CHANGE = "ON_CHECKBOX_GROUP_SELECTION_CHANGE",
|
||||
ON_LIST_PAGE_CHANGE = "ON_LIST_PAGE_CHANGE",
|
||||
ON_RECORDING_START = "ON_RECORDING_START",
|
||||
ON_RECORDING_COMPLETE = "ON_RECORDING_COMPLETE",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ describe("EntityDefinitions", () => {
|
|||
gridGap: "number",
|
||||
items: "?",
|
||||
listData: "?",
|
||||
pageNo: "?",
|
||||
pageSize: "?",
|
||||
};
|
||||
|
||||
expect(listWidgetEntityDefinitions).toStrictEqual(output);
|
||||
|
|
|
|||
|
|
@ -280,6 +280,8 @@ export const entityDefinitions: Record<string, unknown> = {
|
|||
selectedItem: generateTypeDef(widget.selectedItem),
|
||||
items: generateTypeDef(widget.items),
|
||||
listData: generateTypeDef(widget.listData),
|
||||
pageNo: generateTypeDef(widget.pageNo),
|
||||
pageSize: generateTypeDef(widget.pageSize),
|
||||
}),
|
||||
RATE_WIDGET: {
|
||||
"!doc": "Rating widget is used to display ratings in your app.",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import Pagination from "rc-pagination";
|
||||
import styled from "styled-components";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const locale = {
|
||||
// Options.jsx
|
||||
|
|
@ -17,9 +17,7 @@ const locale = {
|
|||
next_3: "Next 3 Pages",
|
||||
};
|
||||
|
||||
const StyledPagination = styled(Pagination)<{
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
const paginatorCss = css`
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
|
|
@ -30,9 +28,6 @@ const StyledPagination = styled(Pagination)<{
|
|||
left: 0;
|
||||
right: 0;
|
||||
z-index: 3;
|
||||
pointer-events: ${(props) => (props.disabled ? "none" : "all")};
|
||||
opacity: ${(props) => (props.disabled ? "0.4" : "1")};
|
||||
|
||||
.rc-pagination::after {
|
||||
display: block;
|
||||
clear: both;
|
||||
|
|
@ -303,6 +298,14 @@ const StyledPagination = styled(Pagination)<{
|
|||
}
|
||||
`;
|
||||
|
||||
const StyledPagination = styled(Pagination)<{
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
${paginatorCss}
|
||||
pointer-events: ${(props) => (props.disabled ? "none" : "all")};
|
||||
opacity: ${(props) => (props.disabled ? "0.4" : "1")};
|
||||
`;
|
||||
|
||||
interface ListPaginationProps {
|
||||
current: number;
|
||||
total: number;
|
||||
|
|
@ -324,4 +327,50 @@ function ListPagination(props: ListPaginationProps) {
|
|||
);
|
||||
}
|
||||
|
||||
const PaginationWrapper = styled.ul`
|
||||
${paginatorCss}
|
||||
pointer-events: "all";
|
||||
opacity: "1";
|
||||
`;
|
||||
|
||||
export function ServerSideListPagination(props: any) {
|
||||
return (
|
||||
<PaginationWrapper>
|
||||
<li
|
||||
className={`t--list-widget-prev-page rc-pagination-prev ${props.pageNo ===
|
||||
1 && "rc-pagination-disabled"}`}
|
||||
title="Previous Page"
|
||||
>
|
||||
<button
|
||||
area-label="prev page"
|
||||
className="rc-pagination-item-link"
|
||||
onClick={() => {
|
||||
if (props.pageNo > 1) props.prevPageClick();
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
className="rc-pagination-item rc-pagination-item-0 rc-pagination-item-active"
|
||||
title={props.pageNo}
|
||||
>
|
||||
<a rel="nofollow">{props.pageNo}</a>
|
||||
</li>
|
||||
<li
|
||||
className="t--list-widget-next-page rc-pagination-next"
|
||||
title="Next Page"
|
||||
>
|
||||
<button
|
||||
area-label="next page"
|
||||
className="rc-pagination-item-link"
|
||||
onClick={() => {
|
||||
props.nextPageClick();
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
</li>
|
||||
</PaginationWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListPagination;
|
||||
|
|
|
|||
|
|
@ -111,6 +111,32 @@ export default {
|
|||
|
||||
return updatedItems;
|
||||
},
|
||||
//
|
||||
getPageSize: (props, moment, _) => {
|
||||
const LIST_WIDGET_PAGINATION_HEIGHT = 36;
|
||||
const DEFAULT_GRID_ROW_HEIGHT = 10;
|
||||
const WIDGET_PADDING = DEFAULT_GRID_ROW_HEIGHT * 0.4;
|
||||
|
||||
const templateBottomRow = props.templateBottomRow;
|
||||
|
||||
const templateHeight = templateBottomRow * DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
const componentHeight =
|
||||
(props.bottomRow - props.topRow) * props.parentRowSpace;
|
||||
|
||||
const totalSpaceAvailable =
|
||||
componentHeight - (LIST_WIDGET_PAGINATION_HEIGHT + WIDGET_PADDING * 2);
|
||||
const spaceTakenByOneContainer = templateHeight + (props.gridGap * 3) / 4;
|
||||
|
||||
const perPage = totalSpaceAvailable / spaceTakenByOneContainer;
|
||||
|
||||
if (_.isNaN(perPage)) {
|
||||
return 0;
|
||||
} else {
|
||||
return _.floor(perPage);
|
||||
}
|
||||
},
|
||||
//
|
||||
// this is just a patch for #7520
|
||||
getChildAutoComplete: (props, moment, _) => {
|
||||
const data = [...props.listData];
|
||||
|
|
|
|||
|
|
@ -50,4 +50,30 @@ describe("Validates Derived Properties", () => {
|
|||
let result = getItems(input, moment, _);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("validates pageSize property", () => {
|
||||
const { getPageSize } = derivedProperty;
|
||||
const input1 = {
|
||||
bottomRow: 86,
|
||||
children: [{ children: [{ bottomRow: 16 }] }],
|
||||
templateBottomRow: 16,
|
||||
gridGap: 0,
|
||||
parentRowSpace: 10,
|
||||
topRow: 9,
|
||||
};
|
||||
// resize ListWidget so bottomRow changes
|
||||
const input2 = {
|
||||
...input1,
|
||||
bottomRow: 56,
|
||||
};
|
||||
|
||||
const expected1 = 4;
|
||||
const expected2 = 2;
|
||||
|
||||
let result1 = getPageSize(input1, moment, _);
|
||||
let result2 = getPageSize(input2, moment, _);
|
||||
|
||||
expect(result1).toStrictEqual(expected1);
|
||||
expect(result2).toStrictEqual(expected2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import ListComponent, {
|
|||
import propertyPaneConfig from "./propertyConfig";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
||||
import ListPagination from "../component/ListPagination";
|
||||
import ListPagination, {
|
||||
ServerSideListPagination,
|
||||
} from "../component/ListPagination";
|
||||
import { GridDefaults, WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import derivedProperties from "./parseDerivedProperties";
|
||||
|
|
@ -49,6 +51,7 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
|
||||
static getDerivedPropertiesMap() {
|
||||
return {
|
||||
pageSize: `{{(()=>{${derivedProperties.getPageSize}})()}}`,
|
||||
selectedItem: `{{(()=>{${derivedProperties.getSelectedItem}})()}}`,
|
||||
items: `{{(() => {${derivedProperties.getItems}})()}}`,
|
||||
childAutoComplete: `{{(() => {${derivedProperties.getChildAutoComplete}})()}}`,
|
||||
|
|
@ -56,6 +59,14 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.serverSidePaginationEnabled && !this.props.pageNo) {
|
||||
this.props.updateWidgetMetaProperty("pageNo", 1);
|
||||
}
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"templateBottomRow",
|
||||
get(this.props.children, "0.children.0.bottomRow"),
|
||||
);
|
||||
|
||||
// generate childMetaPropertyMap
|
||||
this.generateChildrenDefaultPropertiesMap(this.props);
|
||||
this.generateChildrenMetaPropertiesMap(this.props);
|
||||
|
|
@ -169,16 +180,78 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
this.generateChildrenMetaPropertiesMap(this.props);
|
||||
this.generateChildrenEntityDefinitions(this.props);
|
||||
}
|
||||
|
||||
if (this.props.serverSidePaginationEnabled) {
|
||||
if (!this.props.pageNo) this.props.updateWidgetMetaProperty("pageNo", 1);
|
||||
// run onPageSizeChange if user resize widgets
|
||||
if (
|
||||
this.props.onPageSizeChange &&
|
||||
this.props.pageSize !== prevProps.pageSize
|
||||
) {
|
||||
super.executeAction({
|
||||
triggerPropertyName: "onPageSizeChange",
|
||||
dynamicString: this.props.onPageSizeChange,
|
||||
event: {
|
||||
type: EventType.ON_PAGE_SIZE_CHANGE,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.serverSidePaginationEnabled) {
|
||||
if (
|
||||
this.props.serverSidePaginationEnabled === true &&
|
||||
prevProps.serverSidePaginationEnabled === false
|
||||
) {
|
||||
super.executeAction({
|
||||
triggerPropertyName: "onPageSizeChange",
|
||||
dynamicString: this.props.onPageSizeChange,
|
||||
event: {
|
||||
type: EventType.ON_PAGE_SIZE_CHANGE,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
get(this.props.children, "0.children.0.bottomRow") !==
|
||||
get(prevProps.children, "0.children.0.bottomRow")
|
||||
) {
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"templateBottomRow",
|
||||
get(this.props.children, "0.children.0.bottomRow"),
|
||||
{
|
||||
triggerPropertyName: "onPageSizeChange",
|
||||
dynamicString: this.props.onPageSizeChange,
|
||||
event: {
|
||||
type: EventType.ON_PAGE_SIZE_CHANGE,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static getDefaultPropertiesMap(): Record<string, string> {
|
||||
return {};
|
||||
}
|
||||
|
||||
static getMetaPropertiesMap(): Record<string, string> {
|
||||
return {};
|
||||
static getMetaPropertiesMap(): Record<string, any> {
|
||||
return {
|
||||
pageNo: 1,
|
||||
templateBottomRow: 16,
|
||||
};
|
||||
}
|
||||
|
||||
onPageChange = (page: number) => {
|
||||
this.props.updateWidgetMetaProperty("pageNo", page, {
|
||||
triggerPropertyName: "onPageChange",
|
||||
dynamicString: this.props.onPageChange,
|
||||
event: {
|
||||
type: EventType.ON_LIST_PAGE_CHANGE,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* on click item action
|
||||
*
|
||||
|
|
@ -545,6 +618,8 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
* @param children
|
||||
*/
|
||||
paginateItems = (children: DSLWidget[]) => {
|
||||
// return all children if serverside pagination
|
||||
if (this.props.serverSidePaginationEnabled) return children;
|
||||
const { page } = this.state;
|
||||
const { perPage, shouldPaginate } = this.shouldPaginate();
|
||||
|
||||
|
|
@ -596,7 +671,12 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
*/
|
||||
shouldPaginate = () => {
|
||||
let { gridGap } = this.props;
|
||||
const { children, listData } = this.props;
|
||||
const { children, listData, serverSidePaginationEnabled } = this.props;
|
||||
|
||||
if (serverSidePaginationEnabled) {
|
||||
return { shouldPaginate: true, perPage: this.props.pageSize };
|
||||
}
|
||||
|
||||
if (!listData?.length) {
|
||||
return { shouldPaginate: false, perPage: 0 };
|
||||
}
|
||||
|
|
@ -636,6 +716,7 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
getPageView() {
|
||||
const children = this.renderChildren();
|
||||
const { componentHeight } = this.getComponentDimensions();
|
||||
const { pageNo, serverSidePaginationEnabled } = this.props;
|
||||
const { perPage, shouldPaginate } = this.shouldPaginate();
|
||||
const templateBottomRow = get(
|
||||
this.props.children,
|
||||
|
|
@ -697,15 +778,22 @@ class ListWidget extends BaseWidget<ListWidgetProps<WidgetProps>, WidgetState> {
|
|||
>
|
||||
{children}
|
||||
|
||||
{shouldPaginate && (
|
||||
<ListPagination
|
||||
current={this.state.page}
|
||||
disabled={false && this.props.renderMode === RenderModes.CANVAS}
|
||||
onChange={(page: number) => this.setState({ page })}
|
||||
perPage={perPage}
|
||||
total={(this.props.listData || []).length}
|
||||
/>
|
||||
)}
|
||||
{shouldPaginate &&
|
||||
(serverSidePaginationEnabled ? (
|
||||
<ServerSideListPagination
|
||||
nextPageClick={() => this.onPageChange(pageNo + 1)}
|
||||
pageNo={this.props.pageNo}
|
||||
prevPageClick={() => this.onPageChange(pageNo - 1)}
|
||||
/>
|
||||
) : (
|
||||
<ListPagination
|
||||
current={this.state.page}
|
||||
disabled={false && this.props.renderMode === RenderModes.CANVAS}
|
||||
onChange={(page: number) => this.setState({ page })}
|
||||
perPage={perPage}
|
||||
total={(this.props.listData || []).length}
|
||||
/>
|
||||
))}
|
||||
</ListComponent>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,15 @@ const PropertyPaneConfig = [
|
|||
inputType: "INTEGER",
|
||||
validation: { type: ValidationTypes.NUMBER, params: { min: 0 } },
|
||||
},
|
||||
{
|
||||
helpText:
|
||||
"Bind the List.pageNo property in your API and call it onPageChange",
|
||||
propertyName: "serverSidePaginationEnabled",
|
||||
label: "Server Side Pagination",
|
||||
controlType: "SWITCH",
|
||||
isBindProperty: false,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "isVisible",
|
||||
label: "Visible",
|
||||
|
|
@ -117,6 +126,30 @@ const PropertyPaneConfig = [
|
|||
},
|
||||
dependencies: ["listData"],
|
||||
},
|
||||
{
|
||||
helpText: "Triggers an action when a list page is changed",
|
||||
propertyName: "onPageChange",
|
||||
label: "onPageChange",
|
||||
controlType: "ACTION_SELECTOR",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: true,
|
||||
hidden: (props: ListWidgetProps<WidgetProps>) =>
|
||||
!props.serverSidePaginationEnabled,
|
||||
dependencies: ["serverSidePaginationEnabled"],
|
||||
},
|
||||
{
|
||||
helpText: "Triggers an action when a list page size is changed",
|
||||
propertyName: "onPageSizeChange",
|
||||
label: "onPageSizeChange",
|
||||
controlType: "ACTION_SELECTOR",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: true,
|
||||
hidden: (props: ListWidgetProps<WidgetProps>) =>
|
||||
!props.serverSidePaginationEnabled,
|
||||
dependencies: ["serverSidePaginationEnabled"],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user