Skip to content

Commit

Permalink
feat: Add list support for auto grid
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur- committed Sep 11, 2023
1 parent fadb95c commit 1ed260e
Show file tree
Hide file tree
Showing 20 changed files with 1,708 additions and 43 deletions.
1,245 changes: 1,239 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

41 changes: 29 additions & 12 deletions packages/java/endpoint/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,40 @@
<artifactId>spring-beans</artifactId>
</dependency>

<!-- vaadin-fusion generator-->
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-symbol-solver-core</artifactId>
<version>${github.javaparser.core.version}</version>
</dependency>

<!-- Fusion Testing dependencies -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>aspectjrt</artifactId>
<groupId>org.aspectj</groupId>
</exclusion>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>dev.hilla</groupId>
<artifactId>parser-jvm-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.hilla</groupId>
<artifactId>runtime-plugin-transfertypes</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Testing dependencies -->

<!-- TODO: re-enable tests mocking final classes in the connect
package by using the org.mockito.plugins.MockMaker plugin with
Expand Down Expand Up @@ -238,16 +264,7 @@
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.hilla</groupId>
<artifactId>parser-jvm-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.hilla</groupId>
<artifactId>runtime-plugin-transfertypes</artifactId>
<version>${project.version}</version>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.hilla.crud;

import java.util.List;

import dev.hilla.EndpointExposed;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* A browser service that delegates crud operations to a JPA repository.
*/
@EndpointExposed
public class CrudRepositoryService<T, ID> implements CrudService<T> {

private JpaRepository<T, ID> repository;

/**
* Creates the service using the given repository.
*
* @param repository
* the JPA repository
*/
public CrudRepositoryService(JpaRepository<T, ID> repository) {
this.repository = repository;
}

@Override
public List<T> list(Pageable pageable) {
return repository.findAll(pageable).getContent();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.hilla.crud;

import java.util.List;

import org.springframework.data.domain.Pageable;

/**
* A browser service that can create, read and update a given type of object.
* <p>
* Note! Not yet fully implemented but limited to read operations
*/
public interface CrudService<T> {

/**
* Lists objects of the given type using the paging and sorting options
* provided in the parameter.
*
* @param pageable
* contains information about paging and sorting
* @return a list of objects or an empty list if no objects were found
*/
List<T> list(Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@org.springframework.lang.NonNullApi
package dev.hilla.crud;
1 change: 1 addition & 0 deletions packages/ts/react-grid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"access": "public"
},
"dependencies": {
"@hilla/react-components": "^2.1.7"
},
"peerDependencies": {
"react": "^18"
Expand Down
3 changes: 0 additions & 3 deletions packages/ts/react-grid/src/autogrid.ts

This file was deleted.

77 changes: 77 additions & 0 deletions packages/ts/react-grid/src/autogrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { ModelConstructor } from '@hilla/form';
import type { GridDataProviderCallback, GridDataProviderParams, GridElement } from '@hilla/react-components/Grid.js';
import { GridSortColumn } from '@hilla/react-components/GridSortColumn.js';
import { useEffect, useRef } from 'react';
import type { CrudEndpoint } from './crud';
import { getProperties } from './modelutil.js';

// import Sort from "Frontend/generated/dev/hilla/mappedtypes/Sort";
// import Direction from "Frontend/generated/org/springframework/data/domain/Sort/Direction";
type Sort = any;
enum Direction {
ASC = 'ASC',
DESC = 'DESC',
}

export const useAutoGrid = <T,>(endpoint: CrudEndpoint<T>, itemType: ModelConstructor<T, any>) => {
const listMethod = endpoint.list;
const ref = useRef(null);

useEffect(() => {
const grid = ref.current as any as GridElement<T>;

let first = true;

grid.dataProvider = async (params: GridDataProviderParams<T>, callback: GridDataProviderCallback<T>) => {
const sort: Sort = {
orders: params.sortOrders.map((order) => ({
property: order.path,
direction: order.direction == 'asc' ? Direction.ASC : Direction.DESC,
ignoreCase: false,
})),
};

const pageNumber = params.page;
const pageSize = params.pageSize;
const req = {
pageNumber,
pageSize,
sort,
};

const items = await listMethod(req);
let size;
if (items.length === pageSize) {
size = (pageNumber + 1) * pageSize + 1;
if (size < (grid as any)._cache.size) {
// Only allow size to grow here to avoid shrinking the size when scrolled down and sorting
size = undefined;
}
} else {
size = pageNumber * pageSize + items.length;
}
callback(items, size);
if (first) {
// Workaround for https://github.com/vaadin/react-components/issues/129
first = false;
setTimeout(() => grid.recalculateColumnWidths(), 0);
}
};
}, []);

const properties = getProperties(itemType);
const children = properties.map((p) => {
let customProps: any = { autoWidth: true };

let column = (
<GridSortColumn path={p.name} header={p.humanReadableName} key={p.name} {...customProps}></GridSortColumn>
);

return column;
});

return {
ref,
children: [...children],
};
};
7 changes: 7 additions & 0 deletions packages/ts/react-grid/src/crud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Pageable from './types/Pageable';

export interface CrudEndpoint<T> {
list: {
(request: Pageable): Promise<T[]>;
};
}
29 changes: 29 additions & 0 deletions packages/ts/react-grid/src/modelutil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ModelConstructor } from '@hilla/form';

export interface PropertyInfo {
name: string;
humanReadableName: string;
}

export const getProperties = (model: ModelConstructor<any, any>): PropertyInfo[] => {
const properties = Object.keys(Object.getOwnPropertyDescriptors(model.prototype)).filter((p) => p !== 'constructor');

return properties.map((name) => {
const humanReadableName = _generateHeader(name);

return {
name,
humanReadableName,
};
});
};

// This is from vaadin-grid-column.js, should be used from there maybe. At least we must be 100% sure to match grid and fields
function _generateHeader(path: string) {
return path
.substr(path.lastIndexOf('.') + 1)
.replace(/([A-Z])/gu, '-$1')
.toLowerCase()
.replace(/-/gu, ' ')
.replace(/^./u, (match) => match.toUpperCase());
}
5 changes: 5 additions & 0 deletions packages/ts/react-grid/src/types/Direction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum Direction {
ASC = "ASC",
DESC = "DESC"
}
export default Direction;
6 changes: 6 additions & 0 deletions packages/ts/react-grid/src/types/NullHandling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum NullHandling {
NATIVE = "NATIVE",
NULLS_FIRST = "NULLS_FIRST",
NULLS_LAST = "NULLS_LAST"
}
export default NullHandling;
9 changes: 9 additions & 0 deletions packages/ts/react-grid/src/types/Order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type Direction_1 from './Direction.js';
import type NullHandling_1 from './NullHandling.js';
interface Order {
direction: Direction_1;
ignoreCase: boolean;
nullHandling?: NullHandling_1;
property: string;
}
export default Order;
7 changes: 7 additions & 0 deletions packages/ts/react-grid/src/types/Pageable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type Sort_1 from "./Sort.js";
interface Pageable {
pageNumber: number;
pageSize: number;
sort: Sort_1;
}
export default Pageable;
5 changes: 5 additions & 0 deletions packages/ts/react-grid/src/types/Sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type Order_1 from "./Order.js";
interface Sort {
orders: Array<Order_1 | undefined>;
}
export default Sort;
17 changes: 17 additions & 0 deletions packages/ts/react-grid/test/TestModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ObjectModel, StringModel, _getPropertyModel } from '@hilla/form';

export interface Person {
firstName: string;
lastName: string;
}

export class PersonModel<T extends Person = Person> extends ObjectModel<T> {
declare static createEmptyValue: () => Person;

get firstName(): StringModel {
return this[_getPropertyModel]('firstName', StringModel, [false]);
}
get lastName(): StringModel {
return this[_getPropertyModel]('firstName', StringModel, [false]);
}
}
77 changes: 77 additions & 0 deletions packages/ts/react-grid/test/autogrid.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect, use } from '@esm-bundle/chai';
import { Grid, GridElement } from '@hilla/react-components/Grid.js';
import { render } from '@testing-library/react';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { useAutoGrid as _useAutoGrid } from '../src/autogrid.js';
import type { CrudEndpoint } from '../src/crud.js';
import Pageable from '../src/types/Pageable.js';
import { Person, PersonModel } from './TestModels.js';
//@ts-ignore
import { getRowBodyCells, getBodyCellContent, getRows, getCellContent } from './grid-test-utils.js';
use(sinonChai);

const fakeEndpoint: CrudEndpoint<Person> = {
list: async (request: Pageable): Promise<Person[]> => {
const data: Person[] = [
{ firstName: 'John', lastName: 'Dove' },
{ firstName: 'Jane', lastName: 'Love' },
];
if (request.pageNumber === 0) {
return data;
}

return [];
},
};

async function sleep(ms: number) {
return new Promise((resolve) =>
setTimeout(() => {
resolve(undefined);
}, 1),
);
}
describe('@hilla/react-grid', () => {
type UseAutoGridSpy = sinon.SinonSpy<Parameters<typeof _useAutoGrid>, ReturnType<typeof _useAutoGrid>>;
const useAutoGrid = sinon.spy(_useAutoGrid) as typeof _useAutoGrid;

beforeEach(() => {
(useAutoGrid as UseAutoGridSpy).resetHistory();
});

function AutoGrid() {
const autoGrid = useAutoGrid(fakeEndpoint, PersonModel);
return <Grid {...autoGrid}></Grid>;
}
describe('useAutoGrid', () => {
it('creates columns based on model', async () => {
const result = render(<AutoGrid />);
const columns = result.container.querySelectorAll('vaadin-grid-sort-column');
expect(columns.length).to.equal(2);
expect(columns[0].path).to.equal('firstName');
expect(columns[0].header).to.equal('First name');
expect(columns[1].path).to.equal('lastName');
expect(columns[1].header).to.equal('Last name');
});
it('sets a data provider', async () => {
const result = render(<AutoGrid />);
const grid = result.container.querySelector('vaadin-grid');
expect(grid?.dataProvider).to.not.be.undefined;
});
it('data provider provides data', async () => {
const result = render(<AutoGrid />);
const grid: GridElement = result.container.querySelector('vaadin-grid')!;
grid.requestContentUpdate();
await sleep(1);
expect((grid as any)._cache.size).to.equal(2);
expect(getBodyCellContent(grid, 0, 0).innerText).to.equal('John');
expect(getBodyCellContent(grid, 0, 1).innerText).to.equal('Dove');
expect(getBodyCellContent(grid, 1, 0).innerText).to.equal('Jane');
expect(getBodyCellContent(grid, 1, 1).innerText).to.equal('Love');
});
});
});
function getBodyCellText(grid: GridElement, row: number, col: number): any {
return getCellContent(getRowBodyCells(getRows(grid.shadowRoot)[row])[col]).innerText;
}
Loading

0 comments on commit 1ed260e

Please sign in to comment.