Skip to content

Commit 0d20a94

Browse files
feat(grid): Support for binding to nested data source (#7777)
* test(grid): Add base tests for nested props * feat(grid): Support for binding to nested data. * fix(grid): Use the public prop of the grid * fix(grid): Group by expand states and column style bindings * refactor(grid): Move data mapper pipe * feat(tree-grid): Add support for tree grid * feat(grid): Nested props copy/paste behavior * feat(grid): Add hierarchical grid support Added support for summaries with nested data binding Tree grid supports updating with nested data binding. * refactor: Lodash imports and fixture clean-up * test(nested-props): Skip unit test for now * fix(grid): Aria described by escape invalid characters Removed the host binding from the cell component. Removed constructor definition in tree cell. * fix(grid): Summary cells aria attributes * test(tree-grid): Leave the test for later investigation * feat(grid): Grid exporter support for nested props * test(grid): Clean up test suite * chore(grid): Add demo sample for nested props * feat(grid): Apply styling pipes when updating cell values * chore(*): Add changelog entry. Add peer deps and cleanup mapper functions. * chore(*): 🤦‍♂️ * fix(*): mergeObjects is now lodash merge * fix(*): Row editing row data state Apply correct order of merges when partial nested objects are applied over the original row data * fix(*): Tree grid filtering pipe * fix(grid): Search cache resolving nested data * fix(grid): Various fixes Fixed edited transaction cell style in all grids. Moved the logic for checking applying said style to a pipe. Should improve performance a little bit. Cleaned some unused imports. * fix(grid): Row editing Correctly detect the number of changes when editing complex objects. * fix(grid): Apply transaction styles through API calls * fix(grid): Performance in hierarchical data * build(*): Move lodash merge into dependencies Co-authored-by: Desislava Dincheva <[email protected]>
1 parent f125387 commit 0d20a94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+650
-309
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ The following example shows how you can use the Indigo theme:
4949
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
5050
- Introduced `showSummaryOnCollapse` grid property which allows you to control whether the summary row stays visible when the groupBy / parent row is collapsed.
5151
- Added support for tooltips on data cells default template and summary cells.
52+
- Added support for binding columns to properties in nested data objects.
53+
Data operations (filtering/sorting/updating/etc) are supported for the nested properties.
54+
```html
55+
<igx-column field="foo.bar.baz"></igx-column>
56+
```
5257
- `IgxGridState` directive
5358
- Added support for expansion states, column selection and row pinning.
5459
- Added support for `IgxTreeGrid` and `IgxHierarchicalGrid` (including child grids)

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"hammerjs": "^2.0.8",
6262
"igniteui-trial-watermark": "^1.0.3",
6363
"jszip": "^3.4.0",
64+
"lodash.merge": "^4.6.2",
6465
"resize-observer-polyfill": "^1.5.1",
6566
"rxjs": "^6.5.4",
6667
"tslib": "^2.0.0",

projects/igniteui-angular/ng-package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"hammerjs",
1313
"jszip",
1414
"resize-observer-polyfill",
15-
"igniteui-trial-watermark"
15+
"igniteui-trial-watermark",
16+
"lodash.merge"
1617
]
1718
}

projects/igniteui-angular/ng-package.prod.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"hammerjs",
1515
"jszip",
1616
"resize-observer-polyfill",
17-
"igniteui-trial-watermark"
17+
"igniteui-trial-watermark",
18+
"lodash.merge"
1819
]
1920
}

projects/igniteui-angular/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"jszip": "^3.3.0",
7474
"tslib": "^2.0.0",
7575
"resize-observer-polyfill": "^1.5.1",
76-
"igniteui-trial-watermark": "^1.0.3"
76+
"igniteui-trial-watermark": "^1.0.3",
77+
"lodash.merge": "^4.6.2"
7778
},
7879
"peerDependencies": {
7980
"@angular/common": "^10.0.0",

projects/igniteui-angular/schematics/utils/dependency-handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const DEPENDENCIES_MAP: PackageEntry[] = [
3131
{ name: '@types/jszip', target: PackageTarget.DEV },
3232
{ name: 'igniteui-trial-watermark', target: PackageTarget.NONE },
3333
{ name: 'core-js-pure', target: PackageTarget.NONE },
34+
{ name: 'lodash.merge', target: PackageTarget.NONE },
3435
// peerDependencies
3536
{ name: '@angular/forms', target: PackageTarget.NONE },
3637
{ name: '@angular/common', target: PackageTarget.NONE },

projects/igniteui-angular/src/lib/core/utils.spec.ts

Lines changed: 0 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -171,146 +171,6 @@ describe('Utils', () => {
171171
});
172172
});
173173

174-
describe('Utils - mergeObjects() unit tests', () => {
175-
it('Should correctly merge objects', () => {
176-
const obj1 = {
177-
Numeric: 1,
178-
String: 'Some test string',
179-
Boolean: true,
180-
Date: new Date(0),
181-
Object: {
182-
Numeric: 10,
183-
String: 'Some inner test string',
184-
Boolean: false,
185-
Date: new Date(1000 * 60 * 60 * 24 * 10),
186-
}
187-
};
188-
189-
const obj2 = {
190-
Numeric: 100,
191-
String: 'Some changed test string',
192-
Boolean: false,
193-
Date: new Date(1000 * 60 * 60 * 24 * 100),
194-
Object: {
195-
Numeric: Infinity,
196-
}
197-
};
198-
199-
const result = mergeObjects(obj1, obj2);
200-
expect(result.Numeric).toBe(obj2.Numeric);
201-
expect(result.String).toBe(obj2.String);
202-
expect(result.Boolean).toBe(obj2.Boolean);
203-
expect(result.Date).toEqual(obj2.Date);
204-
expect(result.Date).not.toBe(obj2.Date);
205-
expect(result.Date.getTime()).toBe(obj2.Date.getTime());
206-
207-
expect(result.Object).toEqual(obj2.Object);
208-
expect(result.Object).not.toBe(obj2.Object);
209-
expect(result.Object.Numeric).toEqual(obj2.Object.Numeric);
210-
});
211-
212-
it('Should correctly merge into empty object', () => {
213-
const obj1 = {};
214-
const obj2 = {
215-
Test: 'Test',
216-
Date: new Date(0)
217-
};
218-
219-
const result = mergeObjects(obj1, obj2);
220-
expect(result).toEqual(obj2);
221-
expect(result).not.toBe(obj2);
222-
});
223-
224-
it('Should correctly merge from empty object', () => {
225-
const obj1 = {
226-
Test: 'Test',
227-
Date: new Date(0)
228-
};
229-
const obj2 = {};
230-
231-
const result = mergeObjects(obj1, obj2);
232-
expect(result).toEqual(obj1);
233-
expect(result).toBe(obj1);
234-
});
235-
236-
it('Should throw when try to merge into null object', () => {
237-
const obj1 = null;
238-
const obj2 = {
239-
Test: 'Test',
240-
Date: new Date(0)
241-
};
242-
243-
const errorFunction = function () { mergeObjects(obj1, obj2); };
244-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
245-
});
246-
247-
it('Should correctly merge from null object', () => {
248-
const obj1 = {
249-
Test: 'Test',
250-
Date: new Date(0)
251-
};
252-
const obj2 = null;
253-
254-
const result = mergeObjects(obj1, obj2);
255-
expect(result).toEqual(obj1);
256-
expect(result).toBe(obj1);
257-
});
258-
259-
it('Should throw when try to merge into undefined object', () => {
260-
const obj1 = undefined;
261-
const obj2 = {
262-
Test: 'Test',
263-
Date: new Date(0)
264-
};
265-
266-
const errorFunction = function () { mergeObjects(obj1, obj2); };
267-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
268-
});
269-
270-
it('Should correctly merge from undefined object', () => {
271-
const obj1 = {
272-
Test: 'Test',
273-
Date: new Date(0)
274-
};
275-
const obj2 = undefined;
276-
277-
const result = mergeObjects(obj1, obj2);
278-
expect(result).toEqual(obj1);
279-
expect(result).toBe(obj1);
280-
});
281-
282-
it('Should throw error when try to merge into non object type', () => {
283-
let obj1: any = 'Some string';
284-
const obj2 = {};
285-
const errorFunction = function () { mergeObjects(obj1, obj2); };
286-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
287-
288-
obj1 = 100;
289-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
290-
291-
obj1 = true;
292-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
293-
294-
obj1 = new Date(0);
295-
expect(errorFunction).toThrowError(`Cannot merge into ${obj1}. First param must be an object.`);
296-
});
297-
298-
it('Should return first object when try to merge from non object type', () => {
299-
const obj1 = {};
300-
let obj2: any = 'Some string';
301-
expect(mergeObjects(obj1, obj2)).toBe(obj1);
302-
303-
obj2 = 100;
304-
expect(mergeObjects(obj1, obj2)).toBe(obj1);
305-
306-
obj2 = true;
307-
expect(mergeObjects(obj1, obj2)).toBe(obj1);
308-
309-
obj2 = new Date(0);
310-
expect(mergeObjects(obj1, obj2)).toBe(obj1);
311-
});
312-
});
313-
314174
describe('Utils - isObject() unit tests', () => {
315175
it('Should correctly determine if variable is Object', () => {
316176
let variable: any = {};

projects/igniteui-angular/src/lib/core/utils.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isPlatformBrowser } from '@angular/common';
33
import { Observable } from 'rxjs';
44
import ResizeObserver from 'resize-observer-polyfill';
55
import { setImmediate } from 'core-js-pure';
6+
import merge from 'lodash.merge';
67

78
/**
89
* @hidden
@@ -47,19 +48,7 @@ export function cloneHierarchicalArray(array: any[], childDataKey: any): any[] {
4748
* @hidden
4849
*/
4950
export function mergeObjects(obj1: {}, obj2: {}): any {
50-
if (!isObject(obj1)) {
51-
throw new Error(`Cannot merge into ${obj1}. First param must be an object.`);
52-
}
53-
54-
if (!isObject(obj2)) {
55-
return obj1;
56-
}
57-
58-
for (const key of Object.keys(obj2)) {
59-
obj1[key] = cloneValue(obj2[key]);
60-
}
61-
62-
return obj1;
51+
return merge(obj1, obj2);
6352
}
6453

6554
/**
@@ -385,6 +374,66 @@ export function compareMaps(map1: Map<any, any>, map2: Map<any, any>): boolean {
385374
return match;
386375
}
387376

377+
/**
378+
*
379+
* Given a property access path in the format `x.y.z` resolves and returns
380+
* the value of the `z` property in the passed object.
381+
*
382+
* @hidden
383+
* @internal
384+
*/
385+
export function resolveNestedPath(obj: any, path: string) {
386+
const parts = path?.split('.') ?? [];
387+
let current = obj[parts.shift()];
388+
389+
parts.forEach(prop => {
390+
if (current) {
391+
current = current[prop];
392+
}
393+
});
394+
395+
return current;
396+
}
397+
398+
399+
/**
400+
*
401+
* Given a property access path in the format `x.y.z` and a value
402+
* this functions builds and returns an object following the access path.
403+
*
404+
* @example
405+
* ```typescript
406+
* console.log('x.y.z.', 42);
407+
* >> { x: { y: { z: 42 } } }
408+
* ```
409+
*
410+
* @hidden
411+
* @internal
412+
*/
413+
export function reverseMapper(path: string, value: any) {
414+
const obj = {};
415+
const parts = path?.split('.') ?? [];
416+
417+
let _prop = parts.shift();
418+
let mapping: any;
419+
420+
// Initial binding for first level bindings
421+
obj[_prop] = value;
422+
mapping = obj;
423+
424+
parts.forEach(prop => {
425+
// Start building the hierarchy
426+
mapping[_prop] = {};
427+
// Go down a level
428+
mapping = mapping[_prop];
429+
// Bind the value and move the key
430+
mapping[prop] = value;
431+
_prop = prop;
432+
});
433+
434+
return obj;
435+
}
436+
388437
export function yieldingLoop(count: number, chunkSize: number, callback: (index: number) => void, done: () => void) {
389438
let i = 0;
390439
const chunk = () => {

projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FilteringLogic, IFilteringExpression } from './filtering-expression.interface';
22
import { FilteringExpressionsTree, IFilteringExpressionsTree } from './filtering-expressions-tree';
3+
import { resolveNestedPath } from '../core/utils';
34

45
export interface IFilteringStrategy {
56
filter(data: any[], expressionsTree: IFilteringExpressionsTree, advancedExpressionsTree?: IFilteringExpressionsTree): any[];
@@ -95,6 +96,6 @@ export class FilteringStrategy extends BaseFilteringStrategy {
9596
}
9697

9798
protected getFieldValue(rec: object, fieldName: string): any {
98-
return rec[fieldName];
99+
return resolveNestedPath(rec, fieldName);
99100
}
100101
}

0 commit comments

Comments
 (0)