feat: add server side pagination in list widget (#7128)

This commit is contained in:
Bhavin K 2021-09-22 14:16:51 +05:30 committed by GitHub
parent 2121050177
commit de20a2a52f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 20 deletions

View File

@ -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",
}

View File

@ -43,6 +43,8 @@ describe("EntityDefinitions", () => {
gridGap: "number",
items: "?",
listData: "?",
pageNo: "?",
pageSize: "?",
};
expect(listWidgetEntityDefinitions).toStrictEqual(output);

View File

@ -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.",

View File

@ -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;

View File

@ -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];

View File

@ -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);
});
});

View File

@ -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>
);
}

View File

@ -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"],
},
],
},
];