Skip to content

Commit 890c526

Browse files
authored
refactor: move the array data provider logic to a separate module (#1976)
1 parent 72059f6 commit 890c526

File tree

7 files changed

+243
-161
lines changed

7 files changed

+243
-161
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Returns a sub-property of an object
3+
*
4+
* @param {string} path dot-separated path to the sub property
5+
* @param {*} object
6+
* @returns {*}
7+
*/
8+
function get(path, object) {
9+
return path.split('.').reduce((obj, property) => obj[property], object);
10+
}
11+
12+
/**
13+
* Check array of filters/sorters for paths validity, console.warn invalid items
14+
* @param {!Array<!GridFilter | !GridSorter>} arrayToCheck The array of filters/sorters to check
15+
* @param {string} action The name of action to include in warning (filtering, sorting)
16+
* @param {!Array<!GridItem>} items
17+
*/
18+
function checkPaths(arrayToCheck, action, items) {
19+
if (items.length === 0) {
20+
return false;
21+
}
22+
23+
let result = true;
24+
25+
for (let i in arrayToCheck) {
26+
const path = arrayToCheck[i].path;
27+
28+
// skip simple paths
29+
if (!path || path.indexOf('.') === -1) {
30+
continue;
31+
}
32+
33+
const parentProperty = path.replace(/\.[^.]*$/, ''); // a.b.c -> a.b
34+
if (get(parentProperty, items[0]) === undefined) {
35+
console.warn(`Path "${path}" used for ${action} does not exist in all of the items, ${action} is disabled.`);
36+
result = false;
37+
}
38+
}
39+
40+
return result;
41+
}
42+
43+
/**
44+
* Sorts the given array of items based on the sorting rules and returns the result.
45+
*
46+
* @param {Array<any>} items
47+
* @param {Array<GridSorter>} items
48+
* @return {Array<any>}
49+
*/
50+
function multiSort(items, sortOrders) {
51+
return items.sort((a, b) => {
52+
return sortOrders
53+
.map((sortOrder) => {
54+
if (sortOrder.direction === 'asc') {
55+
return compare(get(sortOrder.path, a), get(sortOrder.path, b));
56+
} else if (sortOrder.direction === 'desc') {
57+
return compare(get(sortOrder.path, b), get(sortOrder.path, a));
58+
}
59+
return 0;
60+
})
61+
.reduce((p, n) => {
62+
return p !== 0 ? p : n;
63+
}, 0);
64+
});
65+
}
66+
67+
/**
68+
* @param {unknown} value
69+
* @return {string}
70+
*/
71+
function normalizeEmptyValue(value) {
72+
if ([undefined, null].indexOf(value) >= 0) {
73+
return '';
74+
} else if (isNaN(value)) {
75+
return value.toString();
76+
} else {
77+
return value;
78+
}
79+
}
80+
81+
/**
82+
* @param {unknown} a
83+
* @param {unknown} b
84+
* @return {number}
85+
*/
86+
function compare(a, b) {
87+
a = normalizeEmptyValue(a);
88+
b = normalizeEmptyValue(b);
89+
90+
if (a < b) {
91+
return -1;
92+
}
93+
if (a > b) {
94+
return 1;
95+
}
96+
return 0;
97+
}
98+
99+
/**
100+
* @param {!Array<!GridItem>} items
101+
* @return {!Array<!GridItem>}
102+
*/
103+
function filter(items, filters) {
104+
return items.filter((item) => {
105+
return filters.every((filter) => {
106+
const value = normalizeEmptyValue(get(filter.path, item));
107+
const filterValueLowercase = normalizeEmptyValue(filter.value).toString().toLowerCase();
108+
return value.toString().toLowerCase().includes(filterValueLowercase);
109+
});
110+
});
111+
}
112+
113+
/**
114+
* WARNING: This API is still intended for internal purposes only and
115+
* may change any time.
116+
*
117+
* Creates a new grid compatible data provider that serves the items
118+
* from the given array as data when requested by the grid.
119+
*
120+
* @param {Array<any>} items
121+
* @return {GridDataProvider<any>}
122+
*/
123+
export const createArrayDataProvider = (allItems) => {
124+
return (params, callback) => {
125+
let items = allItems ? [...allItems] : [];
126+
127+
if (params.filters && checkPaths(params.filters, 'filtering', items)) {
128+
items = filter(items, params.filters);
129+
}
130+
131+
if (
132+
Array.isArray(params.sortOrders) &&
133+
params.sortOrders.length &&
134+
checkPaths(params.sortOrders, 'sorting', items)
135+
) {
136+
items = multiSort(items, params.sortOrders);
137+
}
138+
139+
const count = Math.min(items.length, params.pageSize);
140+
const start = params.page * count;
141+
const end = start + count;
142+
const slice = items.slice(start, end);
143+
callback(slice, items.length);
144+
};
145+
};
Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GridDataProviderCallback, GridDataProviderParams, GridFilter, GridSorter } from './interfaces';
1+
import { GridDataProviderCallback, GridDataProviderParams } from './interfaces';
22

33
declare function ArrayDataProviderMixin<TItem, T extends new (...args: any[]) => {}>(
44
base: T
@@ -15,22 +15,6 @@ declare interface ArrayDataProviderMixin<TItem> {
1515
items: TItem[] | null | undefined;
1616

1717
_arrayDataProvider(opts: GridDataProviderParams<TItem> | null, cb: GridDataProviderCallback<TItem> | null): void;
18-
19-
/**
20-
* Check array of filters/sorters for paths validity, console.warn invalid items
21-
*
22-
* @param arrayToCheck The array of filters/sorters to check
23-
* @param action The name of action to include in warning (filtering, sorting)
24-
*/
25-
_checkPaths(arrayToCheck: Array<GridFilter | GridSorter>, action: string, items: TItem[]): any;
26-
27-
_multiSort(a: unknown | null, b: unknown | null): number;
28-
29-
_normalizeEmptyValue(value: unknown | null): string;
30-
31-
_compare(a: unknown | null, b: unknown | null): number;
32-
33-
_filter(items: TItem[]): TItem[];
3418
}
3519

3620
export { ArrayDataProviderMixin, ArrayDataProviderMixinConstructor };

packages/vaadin-grid/src/vaadin-grid-array-data-provider-mixin.js

Lines changed: 41 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2021 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { Base } from '@polymer/polymer/polymer-legacy.js';
6+
import { createArrayDataProvider } from './array-data-provider.js';
77

88
/**
99
* @polymerMixin
@@ -22,152 +22,54 @@ export const ArrayDataProviderMixin = (superClass) =>
2222
}
2323

2424
static get observers() {
25-
return ['_itemsChanged(items, items.*, isAttached)'];
25+
return ['__dataProviderOrItemsChanged(dataProvider, items, isAttached, items.*, _filters, _sorters)'];
2626
}
2727

2828
/** @private */
29-
_itemsChanged(items, splices, isAttached) {
30-
if (!isAttached) {
31-
return;
32-
}
33-
if (!Array.isArray(items)) {
34-
if (this.dataProvider === this._arrayDataProvider) {
35-
this.dataProvider = undefined;
36-
this.size = 0;
37-
}
38-
} else {
39-
this.size = items.length;
40-
this.dataProvider = this.dataProvider || this._arrayDataProvider;
41-
this.clearCache();
42-
this._ensureFirstPageLoaded();
43-
}
44-
}
45-
46-
/**
47-
* @param {GridDataProviderParams} opts
48-
* @param {GridDataProviderCallback} cb
49-
* @protected
50-
*/
51-
_arrayDataProvider(opts, cb) {
52-
let items = (Array.isArray(this.items) ? this.items : []).slice(0);
53-
54-
if (this._filters && this._checkPaths(this._filters, 'filtering', items)) {
55-
items = this._filter(items);
56-
}
57-
58-
this.size = items.length;
59-
60-
if (opts.sortOrders.length && this._checkPaths(this._sorters, 'sorting', items)) {
61-
items = items.sort(this._multiSort.bind(this));
62-
}
63-
64-
const start = opts.page * opts.pageSize;
65-
const end = start + opts.pageSize;
66-
const slice = items.slice(start, end);
67-
cb(slice, items.length);
29+
__setArrayDataProvider(items) {
30+
const arrayDataProvider = createArrayDataProvider(this.items, {});
31+
arrayDataProvider.__items = items;
32+
this.setProperties({
33+
_arrayDataProvider: arrayDataProvider,
34+
size: items.length,
35+
dataProvider: arrayDataProvider
36+
});
6837
}
6938

70-
/**
71-
* Check array of filters/sorters for paths validity, console.warn invalid items
72-
* @param {!Array<!GridFilter | !GridSorter>} arrayToCheck The array of filters/sorters to check
73-
* @param {string} action The name of action to include in warning (filtering, sorting)
74-
* @param {!Array<!GridItem>} items
75-
* @protected
76-
*/
77-
_checkPaths(arrayToCheck, action, items) {
78-
if (!items.length) {
79-
return false;
39+
/** @private */
40+
__dataProviderOrItemsChanged(dataProvider, items, isAttached) {
41+
if (!isAttached) {
42+
return;
8043
}
8144

82-
let result = true;
83-
84-
for (let i in arrayToCheck) {
85-
const path = arrayToCheck[i].path;
86-
87-
// skip simple paths
88-
if (!path || path.indexOf('.') === -1) {
89-
continue;
90-
}
91-
92-
const parentProperty = path.replace(/\.[^.]*$/, ''); // a.b.c -> a.b
93-
if (Base.get(parentProperty, items[0]) === undefined) {
94-
console.warn(`Path "${path}" used for ${action} does not exist in all of the items, ${action} is disabled.`);
95-
result = false;
45+
if (this._arrayDataProvider) {
46+
// Has an items array data provider beforehand
47+
48+
if (dataProvider !== this._arrayDataProvider) {
49+
// A custom data provider was set externally
50+
this.setProperties({
51+
_arrayDataProvider: undefined,
52+
items: undefined
53+
});
54+
} else if (!items) {
55+
// The items array was unset
56+
this.setProperties({
57+
_arrayDataProvider: undefined,
58+
dataProvider: undefined,
59+
size: 0
60+
});
61+
this.clearCache();
62+
} else if (this._arrayDataProvider.__items === items) {
63+
// The items array was modified
64+
this.clearCache();
65+
this.size = this._effectiveSize;
66+
} else {
67+
// The items array was replaced
68+
this.__setArrayDataProvider(items);
9669
}
70+
} else if (items) {
71+
// There was no array data provider before items was set
72+
this.__setArrayDataProvider(items);
9773
}
98-
99-
return result;
100-
}
101-
102-
/**
103-
* @param {unknown} a
104-
* @param {unknown} b
105-
* @return {number}
106-
* @protected
107-
*/
108-
_multiSort(a, b) {
109-
return this._sorters
110-
.map((sort) => {
111-
if (sort.direction === 'asc') {
112-
return this._compare(Base.get(sort.path, a), Base.get(sort.path, b));
113-
} else if (sort.direction === 'desc') {
114-
return this._compare(Base.get(sort.path, b), Base.get(sort.path, a));
115-
}
116-
return 0;
117-
})
118-
.reduce((p, n) => {
119-
return p ? p : n;
120-
}, 0);
121-
}
122-
123-
/**
124-
* @param {unknown} value
125-
* @return {string}
126-
* @protected
127-
*/
128-
_normalizeEmptyValue(value) {
129-
if ([undefined, null].indexOf(value) >= 0) {
130-
return '';
131-
} else if (isNaN(value)) {
132-
return value.toString();
133-
} else {
134-
return value;
135-
}
136-
}
137-
138-
/**
139-
* @param {unknown} a
140-
* @param {unknown} b
141-
* @return {number}
142-
* @protected
143-
*/
144-
_compare(a, b) {
145-
a = this._normalizeEmptyValue(a);
146-
b = this._normalizeEmptyValue(b);
147-
148-
if (a < b) {
149-
return -1;
150-
}
151-
if (a > b) {
152-
return 1;
153-
}
154-
return 0;
155-
}
156-
157-
/**
158-
* @param {!Array<!GridItem>} items
159-
* @return {!Array<!GridItem>}
160-
* @protected
161-
*/
162-
_filter(items) {
163-
return items.filter((item) => {
164-
return (
165-
this._filters.filter((filter) => {
166-
const value = this._normalizeEmptyValue(Base.get(filter.path, item));
167-
const filterValueLowercase = this._normalizeEmptyValue(filter.value).toString().toLowerCase();
168-
return value.toString().toLowerCase().indexOf(filterValueLowercase) === -1;
169-
}).length === 0
170-
);
171-
});
17274
}
17375
};

packages/vaadin-grid/src/vaadin-grid-data-provider-mixin.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ export const DataProviderMixin = (superClass) =>
203203
type: Object,
204204
notify: true,
205205
value: () => []
206+
},
207+
208+
/**
209+
* @private
210+
*/
211+
__expandedKeys: {
212+
type: Object,
213+
value: () => new Set()
206214
}
207215
};
208216
}

0 commit comments

Comments
 (0)