@azrico/reactgrid 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,79 +1,106 @@
1
- import React from 'react';
2
- import { AzGridProps, DataGridWrapperProps, GridType } from '../types/types';
3
- import debounce from 'lodash.debounce';
4
- import { array_intersection, fn_eval } from '@azrico/object';
5
-
6
- export function useGridSearch(grid: GridType, props: DataGridWrapperProps) {
7
- const [search, set_search] = React.useState('');
8
- const [filteredData, set_filteredData] = React.useState<any>([]);
9
- const currentSearch = React.useRef('');
10
- const currentDS = React.useRef<any[]>([]);
11
-
12
- const try_search = React.useCallback(
13
- debounce(() => {
14
- if (!Array.isArray(currentDS.current)) {
15
- return;
16
- }
17
- let newData: any[] = [];
18
- if (currentSearch.current) {
19
- const visibleColumns = grid.visibleColumns;
20
- const columns_to_search: string[] = array_intersection(
21
- props.advancedFilterFields || [],
22
- visibleColumns.map((r) => r.id)
23
- );
24
- const lowerSearchText = currentSearch.current.toLowerCase().trim();
25
- if (!lowerSearchText || columns_to_search.length === 0)
26
- newData = currentDS.current;
27
- else {
28
- newData = currentDS.current.filter((p) => {
29
- for (let index = 0; index < columns_to_search.length; index++) {
30
- const col = columns_to_search[index];
31
- const v = (p[col] + '').toLowerCase(); //value of column
32
- if (v && v.indexOf(lowerSearchText) != -1) {
33
- //if one column is matching then the row is a match
34
- return true;
35
- }
36
- }
37
- return false;
38
- });
39
- }
40
- } else newData = currentDS.current;
41
- set_filteredData(newData);
42
- }, 1000),
43
- [grid]
44
- );
45
- React.useEffect(() => {
46
- fn_eval(props.dataSource).then((r) => {
47
- currentDS.current = typeof r === 'object' ? r.data || r : r;
48
- searchIfNeeded();
49
- });
50
- }, [props.dataSource]);
51
-
52
- React.useEffect(() => {
53
- currentSearch.current = search;
54
- searchIfNeeded();
55
- }, [search]);
56
-
57
- function searchIfNeeded() {
58
- if (!search) {
59
- set_filteredData(props.dataSource);
60
- } else try_search();
61
- }
62
-
63
- const element = (
64
- <>
65
- <form className={'azgrid-searcharea'}>
66
- <label>
67
- Search
68
- <input
69
- type="text"
70
- name="name"
71
- value={search}
72
- onChange={(e) => set_search(e.target.value)}
73
- />
74
- </label>
75
- </form>
76
- </>
77
- );
78
- return { filteredData, element };
79
- }
1
+ import React from 'react';
2
+ import { DataGridWrapperProps } from '../types/types';
3
+ import debounce from 'lodash.debounce';
4
+ import { array_intersection, fn_eval } from '@azrico/object';
5
+
6
+ const SEARCH_DELAY = 300;
7
+
8
+ function to_rows(result: any): any[] {
9
+ return result && typeof result === 'object' ? result.data || result : result;
10
+ }
11
+ //false when the result is only a page of the data (remote pagination)
12
+ function is_complete(result: any) {
13
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return true;
14
+ const loaded = Array.isArray(result.data) ? result.data.length : 0;
15
+ return !(result.count > loaded);
16
+ }
17
+
18
+ export function useGridSearch(props: DataGridWrapperProps) {
19
+ const [search, set_search] = React.useState('');
20
+ // null shows the datasource itself, an array shows search results
21
+ const [searchResult, set_searchResult] = React.useState<any[] | null>(null);
22
+ const latestProps = React.useRef(props);
23
+ latestProps.current = props;
24
+ // last result the grid loaded, reused by search instead of loading the data again
25
+ const loaded = React.useRef<{ ds: any; result: any } | null>(null);
26
+ const searchId = React.useRef(0);
27
+
28
+ const trackedDataSource = React.useCallback(
29
+ async (args: any) => {
30
+ const result = await fn_eval(props.dataSource, args);
31
+ loaded.current = { ds: props.dataSource, result };
32
+ return result;
33
+ },
34
+ [props.dataSource]
35
+ );
36
+
37
+ const get_search_rows = React.useCallback(async () => {
38
+ const ds = latestProps.current.dataSource;
39
+ const last = loaded.current;
40
+ if (last && last.ds === ds && is_complete(last.result)) return to_rows(last.result);
41
+ return to_rows(await fn_eval(ds));
42
+ }, []);
43
+
44
+ const filter_rows = React.useCallback((rows: any[], text: string) => {
45
+ const { gridRef, advancedFilterFields } = latestProps.current;
46
+ const visibleColumns = gridRef.current?.visibleColumns;
47
+ const columns_to_search: string[] = visibleColumns
48
+ ? array_intersection(
49
+ advancedFilterFields || [],
50
+ visibleColumns.map((r) => r.id)
51
+ )
52
+ : advancedFilterFields || [];
53
+ const lowerSearchText = text.toLowerCase().trim();
54
+ if (!lowerSearchText || columns_to_search.length === 0) return rows;
55
+ return rows.filter((p) => {
56
+ for (let index = 0; index < columns_to_search.length; index++) {
57
+ const col = columns_to_search[index];
58
+ const v = (p[col] + '').toLowerCase(); //value of column
59
+ if (v && v.indexOf(lowerSearchText) != -1) {
60
+ //if one column is matching then the row is a match
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ });
66
+ }, []);
67
+
68
+ const run_search = React.useMemo(
69
+ () =>
70
+ debounce(async (text: string) => {
71
+ const id = ++searchId.current;
72
+ const rows = await get_search_rows();
73
+ if (id !== searchId.current || !Array.isArray(rows)) return; //a newer search started
74
+ set_searchResult(filter_rows(rows, text));
75
+ }, SEARCH_DELAY),
76
+ []
77
+ );
78
+ React.useEffect(() => () => run_search.cancel(), [run_search]);
79
+
80
+ React.useEffect(() => {
81
+ if (search) {
82
+ run_search(search);
83
+ return;
84
+ }
85
+ run_search.cancel();
86
+ searchId.current++;
87
+ set_searchResult(null);
88
+ }, [search, props.dataSource]);
89
+
90
+ const element = (
91
+ <>
92
+ <form className={'azgrid-searcharea'}>
93
+ <label>
94
+ Search
95
+ <input
96
+ type="text"
97
+ name="name"
98
+ value={search}
99
+ onChange={(e) => set_search(e.target.value)}
100
+ />
101
+ </label>
102
+ </form>
103
+ </>
104
+ );
105
+ return { filteredData: searchResult ?? trackedDataSource, element };
106
+ }
@@ -13,6 +13,8 @@ import {
13
13
  TypeRowProps,
14
14
  TypeRowSelection,
15
15
  TypeComputedProps,
16
+ TypeSortInfo,
17
+ TypeGroupBy,
16
18
  } from '@inovua/reactdatagrid-community/types';
17
19
 
18
20
  import { TypeOnSelectionChangeArg } from '@inovua/reactdatagrid-community/types/TypeDataGridProps';
@@ -58,10 +60,22 @@ export type AzGridOptions = Partial<TypeDataGridProps> & {
58
60
  /** extra DOM props for the header row */
59
61
  headerProps?: { [key: string]: any };
60
62
  };
63
+ /** arguments passed to a dataSource function */
64
+ export type AzDataSourceParams = {
65
+ skip?: number;
66
+ limit?: number;
67
+ /** current header filters, filter.value is an array of selected ids for a multi-select SelectFilter */
68
+ filterValue?: TypeFilterValue;
69
+ sortInfo?: TypeSortInfo;
70
+ groupBy?: TypeGroupBy;
71
+ cached_data: React.MutableRefObject<any>;
72
+ last_cache_info: React.MutableRefObject<any>;
73
+ };
74
+ export type AzDataSourceFunc = (args: AzDataSourceParams) => any[] | Promise<any[] | any>;
61
75
  /* -------------------------------------------------------------------------- */
62
76
  export type AzGridProps = {
63
- dataSource?: any[] | ((args: any) => any[] | Promise<any[] | any>);
64
- details_dataSource?: any[] | ((args: any) => any[] | Promise<any[] | any>); //details is rendered under each row
77
+ dataSource?: any[] | AzDataSourceFunc;
78
+ details_dataSource?: any[] | AzDataSourceFunc; //details is rendered under each row
65
79
  details_columns?: LooseColumnDefinition[];
66
80
  details_filter?: FilterValues;
67
81