-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add list support for auto grid
- Loading branch information
Showing
20 changed files
with
1,708 additions
and
43 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
packages/java/endpoint/src/main/java/dev/hilla/crud/CrudRepositoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
packages/java/endpoint/src/main/java/dev/hilla/crud/CrudService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
} |
2 changes: 2 additions & 0 deletions
2
packages/java/endpoint/src/main/java/dev/hilla/crud/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
@org.springframework.lang.NonNullApi | ||
package dev.hilla.crud; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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], | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]>; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
enum Direction { | ||
ASC = "ASC", | ||
DESC = "DESC" | ||
} | ||
export default Direction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.