Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
import { ContainerProvider } from "brandi-react";
import { observer } from "mobx-react-lite";
import { ReactElement, ReactNode, useCallback, useMemo } from "react";
import { ReactElement, useCallback, useMemo } from "react";
import { DatagridContainerProps } from "../typings/DatagridProps";
import { Cell } from "./components/Cell";
import { Widget } from "./components/Widget";
Expand Down Expand Up @@ -84,11 +84,6 @@ const DatagridRoot = observer((props: DatagridContainerProps): ReactElement => {
columnsResizable={props.columnsResizable}
columnsSortable={props.columnsSortable}
data={items}
emptyPlaceholderRenderer={useCallback(
(renderWrapper: (children: ReactNode) => ReactElement) =>
props.showEmptyPlaceholder === "custom" ? renderWrapper(props.emptyPlaceholder) : <div />,
[props.emptyPlaceholder, props.showEmptyPlaceholder]
)}
filterRenderer={useCallback(
(renderWrapper, columnIndex) => {
const columnFilter = columnsStore.columnFilters[columnIndex];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { RefreshIndicator } from "@mendix/widget-plugin-component-kit/RefreshIndicator";
import { Pagination } from "@mendix/widget-plugin-grid/components/Pagination";
import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController";
import classNames from "classnames";
import { ListActionValue, ObjectItem } from "mendix";
import { observer } from "mobx-react-lite";
import { CSSProperties, Fragment, ReactElement, ReactNode } from "react";
Expand All @@ -12,6 +11,7 @@ import {
ShowPagingButtonsEnum
} from "../../typings/DatagridProps";

import { EmptyPlaceholder } from "../features/empty-message/EmptyPlaceholder";
import { SelectAllBar } from "../features/select-all/SelectAllBar";
import { SelectionProgressDialog } from "../features/select-all/SelectionProgressDialog";
import { SelectActionHelper } from "../helpers/SelectActionHelper";
Expand All @@ -38,7 +38,6 @@ export interface WidgetProps<C extends GridColumn, T extends ObjectItem = Object
columnsResizable: boolean;
columnsSortable: boolean;
data: T[];
emptyPlaceholderRenderer?: (renderWrapper: (children: ReactNode) => ReactElement) => ReactElement;
exporting: boolean;
filterRenderer: (renderWrapper: (children: ReactNode) => ReactElement, columnIndex: number) => ReactElement;
hasMoreItems: boolean;
Expand Down Expand Up @@ -117,7 +116,6 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
CellComponent,
columnsHidable,
data: rows,
emptyPlaceholderRenderer,
hasMoreItems,
headerContent,
headerTitle,
Expand All @@ -128,7 +126,6 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
paginationType,
paging,
pagingPosition,
preview,
showRefreshIndicator,
selectActionHelper,
setPage,
Expand Down Expand Up @@ -216,23 +213,7 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
eventsController={props.cellEventsController}
pageSize={props.pageSize}
/>
{(rows.length === 0 || preview) &&
emptyPlaceholderRenderer &&
emptyPlaceholderRenderer(children => (
<div
key="row-footer"
className={classNames("td", { "td-borders": !preview })}
style={{
gridColumn: `span ${
visibleColumns.length +
(columnsHidable ? 1 : 0) +
(selectActionHelper.showCheckboxColumn ? 1 : 0)
}`
}}
>
<div className="empty-placeholder">{children}</div>
</div>
))}
<EmptyPlaceholder />
</GridBody>
</Grid>
</WidgetContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import classNames from "classnames";
import { observer } from "mobx-react-lite";
import { ReactNode } from "react";
import { useEmptyPlaceholderVM } from "./injection-hooks";

export const EmptyPlaceholder = observer(function EmptyPlaceholder(): ReactNode {
const vm = useEmptyPlaceholderVM();

if (!vm.content) return null;

return (
<div className={classNames("td", "td-borders")} style={vm.style}>
<div className="empty-placeholder">{vm.content}</div>
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
import { makeAutoObservable } from "mobx";
import { CSSProperties, ReactNode } from "react";

export class EmptyPlaceholderViewModel {
constructor(
private widgets: ComputedAtom<ReactNode>,
private visibleColumnsCount: ComputedAtom<number>,
private config: { checkboxColumnEnabled: boolean; selectorColumnEnabled: boolean }
) {
makeAutoObservable(this);
}

get content(): ReactNode {
return this.widgets.get();
}

get span(): number {
let span = this.visibleColumnsCount.get();
if (this.config.checkboxColumnEnabled) {
span += 1;
}
if (this.config.selectorColumnEnabled) {
span += 1;
}
return Math.max(span, 1);
}

get style(): CSSProperties {
return { gridColumn: `span ${this.span}` };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { computed, observable } from "mobx";
import { ReactNode } from "react";
import { EmptyPlaceholderViewModel } from "../EmptyPlaceholder.viewModel";

describe("EmptyPlaceholderViewModel", () => {
describe("style getter", () => {
it("reacts to changes in visible columns count", () => {
const mockWidgets = computed(() => "Empty message" as ReactNode);
const columnCount = observable.box(3);
const config = { checkboxColumnEnabled: false, selectorColumnEnabled: false };

const viewModel = new EmptyPlaceholderViewModel(mockWidgets, columnCount, config);

expect(viewModel.style).toEqual({ gridColumn: "span 3" });

columnCount.set(5);
expect(viewModel.style).toEqual({ gridColumn: "span 5" });

columnCount.set(0);
expect(viewModel.style).toEqual({ gridColumn: "span 1" });
});

it("reacts to changes in visible columns count with config flags enabled", () => {
const mockWidgets = computed(() => "Empty message" as ReactNode);
const columnCount = observable.box(3);
const config = { checkboxColumnEnabled: true, selectorColumnEnabled: true };

const viewModel = new EmptyPlaceholderViewModel(mockWidgets, columnCount, config);

expect(viewModel.style).toEqual({ gridColumn: "span 5" });

columnCount.set(5);
expect(viewModel.style).toEqual({ gridColumn: "span 7" });

columnCount.set(0);
expect(viewModel.style).toEqual({ gridColumn: "span 2" });
});
});

describe("content getter", () => {
it("returns widgets from atom", () => {
const message = "Empty message";
const atom = computed(() => message);
const columnCount = observable.box(3);
const config = { checkboxColumnEnabled: false, selectorColumnEnabled: false };

const viewModel = new EmptyPlaceholderViewModel(atom, columnCount, config);

expect(viewModel.content).toBe(message);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createInjectionHooks } from "brandi-react";
import { DG_TOKENS as DG } from "../../model/tokens";

export const [useEmptyPlaceholderVM] = createInjectionHooks(DG.emptyPlaceholderVM);
Original file line number Diff line number Diff line change
@@ -1,160 +1,54 @@
import { DerivedPropsGate, SetupComponent, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
import { DynamicValue, ListValue, SelectionMultiValue, SelectionSingleValue } from "mendix";
import { action, makeAutoObservable, reaction } from "mobx";

type DynamicProps = {
datasource: ListValue;
selectAllTemplate?: DynamicValue<string>;
selectAllText?: DynamicValue<string>;
itemSelection?: SelectionSingleValue | SelectionMultiValue;
allSelectedText?: DynamicValue<string>;
};

interface SelectService {
selectAllPages(): Promise<{ success: boolean }> | { success: boolean };
clearSelection(): void;
}

interface CounterService {
selectedCount: number;
selectedCountText: string;
clearButtonLabel: string;
}
import { SelectAllEvents } from "@mendix/widget-plugin-grid/select-all/select-all.model";
import { Emitter } from "@mendix/widget-plugin-mobx-kit/main";
import { makeAutoObservable } from "mobx";

/** @injectable */
export class SelectAllBarViewModel implements SetupComponent {
private barVisible = false;
private clearVisible = false;

pending = false;

export class SelectAllBarViewModel {
constructor(
host: SetupComponentHost,
private readonly gate: DerivedPropsGate<DynamicProps>,
private readonly selectService: SelectService,
private readonly count: CounterService,
private readonly enableSelectAll: boolean
private emitter: Emitter<SelectAllEvents>,
private state: { pending: boolean; visible: boolean; clearBtnVisible: boolean },
private selectionTexts: {
clearSelectionButtonLabel: string;
selectedCountText: string;
},
private selectAllTexts: {
selectAllLabel: string;
selectionStatus: string;
},
private enableSelectAll: boolean
) {
host.add(this);
type PrivateMembers = "setClearVisible" | "setPending" | "hideBar" | "showBar";
makeAutoObservable<this, PrivateMembers>(this, {
setClearVisible: action,
setPending: action,
hideBar: action,
showBar: action
});
}

private get props(): DynamicProps {
return this.gate.props;
}

private setClearVisible(value: boolean): void {
this.clearVisible = value;
}

private setPending(value: boolean): void {
this.pending = value;
}

private hideBar(): void {
this.barVisible = false;
this.clearVisible = false;
}

private showBar(): void {
this.barVisible = true;
}

private get total(): number {
return this.props.datasource.totalCount ?? 0;
}

private get selectAllFormat(): string {
return this.props.selectAllTemplate?.value ?? "Select all %d rows in the data source";
}

private get selectAllText(): string {
return this.props.selectAllText?.value ?? "Select all rows in the data source";
}

private get allSelectedText(): string {
const str = this.props.allSelectedText?.value ?? "All %d rows selected.";
return str.replace("%d", `${this.count.selectedCount}`);
}

private get isCurrentPageSelected(): boolean {
const selection = this.props.itemSelection;

if (!selection || selection.type === "Single") return false;

const pageIds = new Set(this.props.datasource.items?.map(item => item.id) ?? []);
const selectionSubArray = selection.selection.filter(item => pageIds.has(item.id));
return selectionSubArray.length === pageIds.size && pageIds.size > 0;
}

private get isAllItemsSelected(): boolean {
if (this.total > 0) return this.total === this.count.selectedCount;

const { offset, limit, items = [], hasMoreItems } = this.gate.props.datasource;
const noMoreItems = typeof hasMoreItems === "boolean" && hasMoreItems === false;
const fullyLoaded = offset === 0 && limit >= items.length;

return fullyLoaded && noMoreItems && items.length === this.count.selectedCount;
makeAutoObservable(this);
}

get selectAllLabel(): string {
if (this.total > 0) return this.selectAllFormat.replace("%d", `${this.total}`);
return this.selectAllText;
return this.selectAllTexts.selectAllLabel;
}

get clearSelectionLabel(): string {
return this.count.clearButtonLabel;
return this.selectionTexts.clearSelectionButtonLabel;
}

get selectionStatus(): string {
if (this.isAllItemsSelected) return this.allSelectedText;
return this.count.selectedCountText;
return this.selectAllTexts.selectionStatus;
}

get isBarVisible(): boolean {
return this.enableSelectAll && this.barVisible;
return this.enableSelectAll && this.state.visible;
}

get isClearVisible(): boolean {
return this.clearVisible;
return this.state.clearBtnVisible;
}

get isSelectAllDisabled(): boolean {
return this.pending;
}

setup(): (() => void) | void {
if (!this.enableSelectAll) return;

return reaction(
() => this.isCurrentPageSelected,
isCurrentPageSelected => {
if (isCurrentPageSelected === false) {
this.hideBar();
} else if (this.isAllItemsSelected === false) {
this.showBar();
}
}
);
return this.state.pending;
}

onClear(): void {
this.selectService.clearSelection();
this.emitter.emit("clear");
}

async onSelectAll(): Promise<void> {
this.setPending(true);
try {
const { success } = await this.selectService.selectAllPages();
this.setClearVisible(success);
} finally {
this.setPending(false);
}
onSelectAll(): void {
this.emitter.emit("startSelecting");
}
}

This file was deleted.

Loading
Loading