Skip to content

Commit

Permalink
Merge pull request #113 from erqk/v5
Browse files Browse the repository at this point in the history
V5
  • Loading branch information
erqk authored Mar 11, 2024
2 parents 56e8394 + 5152157 commit a618bd9
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 13 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change log

## 5.2.16

### Feature

- Enable filter of array in object by key.

### Fix

- OptionsTrigger not working correctly when using dynamic params.

## 5.2.15

### Fix
Expand Down
2 changes: 1 addition & 1 deletion projects/ng-dynamic-json-form/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-dynamic-json-form",
"version": "5.2.15",
"version": "5.2.16",
"author": {
"name": "erqk",
"url": "https://github.com/erqk"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,21 @@ import { trimObjectByKeys } from '../utilities/trim-object-by-keys';

@Injectable()
export class OptionsDataService {
private readonly _http = inject(HttpClient);
private readonly _cancelAll$ = new Subject<void>();
private _http = inject(HttpClient);
private _cancelAll$ = new Subject<void>();
private _requests: { src: string; data: Subject<any>; params?: any }[] = [];

getOptions$(config: FormControlOptions): Observable<OptionItem[]> {
const { sourceList } = config;
if (!sourceList?.length) return EMPTY;

return from(sourceList).pipe(
concatMap((x) => this._fetchData$(x, false)),
concatMap((x) =>
this._fetchData$({
config: x,
useTrigger: false,
})
),
toArray(),
map((x) => x.flat()),
tap((x) => {
Expand Down Expand Up @@ -82,7 +87,7 @@ export class OptionsDataService {
}
}

return this._fetchData$(config, true).pipe(
return this._fetchData$({ config, useTrigger: true }).pipe(
switchMap((x) => valueChanges$(x)),
takeUntil(this._cancelAll$)
);
Expand All @@ -95,7 +100,9 @@ export class OptionsDataService {
return this._onTriggerControlChanges$(form, config).pipe(
switchMap((x) => {
const emptyValue = x === undefined || x === null || x === '';
return emptyValue ? of([]) : this._fetchData$(config, x);
return emptyValue
? of([])
: this._fetchData$({ config, useTrigger: true, controlValue: x });
}),
takeUntil(this._cancelAll$)
);
Expand All @@ -119,11 +126,15 @@ export class OptionsDataService {
this._cancelAll$.complete();
}

private _fetchData$(
config: OptionSource,
useTrigger: boolean | undefined,
controlValue?: any
): Observable<OptionItem[]> {
private _fetchData$({
config,
useTrigger,
controlValue,
}: {
config: OptionSource;
useTrigger: boolean | undefined;
controlValue?: any;
}): Observable<OptionItem[]> {
const {
data: { path, labelKey, valueKeys = [] },
slice,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getValueInObject } from './get-value-in-object';

const caseA = {
level_1_caseA: {
level_2_caseA: {
list: [
{
value: 0,
},
{
value: 1,
},
{
value: 2,
},
{
value: 3,
},
],
},
},
};

const caseB = {
level_1_caseB: {
level_2_caseB: ['A', 'B', 'C'],
},
};

describe('getItemKeyInArray', () => {
it('get 1', () => {
const index = getValueInObject(
caseA,
'level_1_caseA.level_2_caseA.list.["value", "===", 1].value'
);

expect(index).toEqual(1);
});

it('get "C"', () => {
const index = getValueInObject(caseB, 'level_1_caseB.level_2_caseB.[,"===", "C"]');
expect(index).toEqual('C');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FormControlIfCondition } from '../models';
import { getValueInObject } from './get-value-in-object';

/**
* Get the key of target item if the value is an array.
* Use syntax same with `FormControlIfCondition` to find the target item.
*
* @param array The array value
* @param path [path, operator, value]
* @returns key
*/
export function getItemKeyInArray(array: any[], path: string): string {
const validSyntax =
path.startsWith('[') && path.endsWith(']') && path.split(',').length === 3;

if (!validSyntax || !Array.isArray(array)) {
return path;
}

const [key, operator, value] = path
.replace('[', '')
.replace(']', '')
.trim()
.split(',')
.map((x) => x.trim()) as FormControlIfCondition;

const _key = key.replaceAll('"', '').replaceAll("'", '');
const _operator = operator.replaceAll('"', '').replaceAll("'", '');

const index = array.findIndex((item) => {
const left = !_key ? item : getValueInObject(item, _key);
const right = JSON.parse(value);

switch (_operator) {
case '!==':
return left !== right;

case '===':
return left === right;

case '<':
return left < right;

case '>':
return left > right;

case '<=':
return left <= right;

case '>=':
return left >= right;

case 'includes':
return left.includes(right);

case 'notIncludes':
return !left.includes(right);
}
});

return index < 0 ? '0' : index.toString();
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { getItemKeyInArray } from './get-item-key-in-array';

/**
* Get the value in an object located in a specific key.
*
* @param obj The object we want to get the value.
* @param path The path to the target value, with period delimiter
*/
export function getValueInObject(obj: any, path: string): any {
if (!path || !obj || typeof obj !== 'object') {
return obj;
}

let tempArray: any[] = [];

try {
return path
.split('.')
.map((x) => x.trim())
.reduce((acc, key) => acc[key], obj);
.reduce((acc, key) => {
const value = acc[key];
const getKeyByIndex =
key.startsWith('[') &&
key.endsWith(']') &&
key.split(',').length === 3;

if (Array.isArray(value)) {
tempArray = value;
}

return getKeyByIndex ? acc[getItemKeyInArray(tempArray, key)] : value;
}, obj);
} catch (error) {
throw error;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
import { getValueInObject } from './get-value-in-object';

/**
* Return the new object with only the keys provided.
* If any of the data is nested, the final object will be flatten out.
*
* @param obj The original object
* @param keys The keys to use as new object
* @returns New object contains only specific keys.
*
* The following example shows what happened if there is nested data.
* @example
* from:
* {
* A: {
* childA: ...
* },
* B: ...,
* C: ...
* }
*
* to:
* {
* childA: ...,
* B: ...,
* C: ...
* }
*/
export function trimObjectByKeys(obj: any, keys: string[]): any {
if (typeof obj !== 'object') {
return obj;
Expand All @@ -10,9 +36,13 @@ export function trimObjectByKeys(obj: any, keys: string[]): any {
}

return keys.reduce((acc, key) => {
// If any of the data is nested.
const _keys = key.split('.').map((x) => x.trim());

// We get the last key as new key if the data is nested.
const newKey = _keys.length > 1 ? _keys[_keys.length - 1] : key;

// Finally, we export the new object that's flatten.
acc[newKey] = getValueInObject(obj, key);
return acc;
}, {} as any);
Expand Down
2 changes: 1 addition & 1 deletion src/assets/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 5.2.15
## 5.2.16

- [English](./v5/index_en.md)
- [繁中](./v5/index_zh-TW.md)
Expand Down

0 comments on commit a618bd9

Please sign in to comment.