Browse Source

Filter state finalized.

pull/353/head
Sebastian Stehle 7 years ago
parent
commit
316a8b1d0b
  1. 6
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  2. 6
      src/Squidex/app/features/content/shared/contents-selector.component.html
  3. 24
      src/Squidex/app/shared/services/schemas.types.ts
  4. 150
      src/Squidex/app/shared/state/filter.state.spec.ts
  5. 26
      src/Squidex/app/shared/state/filter.state.ts

6
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -54,13 +54,15 @@
<input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" /> <input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th> </th>
<th class="cell-auto" *ngFor="let field of schema.listFields"> <th class="cell-auto" *ngFor="let field of schema.listFields">
<sqx-table-header [text]="field.displayName" sortable="true" <sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[sorting]="filter.sortMode(field) | async" [sorting]="filter.sortMode(field) | async"
(sortingChange)="sort(field, $event)"> (sortingChange)="sort(field, $event)">
</sqx-table-header> </sqx-table-header>
</th> </th>
<th class="cell-time"> <th class="cell-time">
<sqx-table-header text="Updated" sortable="true" <sqx-table-header text="Updated"
[sortable]="true"
[sorting]="filter.sortMode('lastModified') | async" [sorting]="filter.sortMode('lastModified') | async"
(sortingChange)="sort('lastModified', $event)"> (sortingChange)="sort('lastModified', $event)">
</sqx-table-header> </sqx-table-header>

6
src/Squidex/app/features/content/shared/contents-selector.component.html

@ -31,13 +31,15 @@
<input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" /> <input type="checkbox" class="form-check" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th> </th>
<th class="cell-auto" *ngFor="let field of schema.listFields"> <th class="cell-auto" *ngFor="let field of schema.listFields">
<sqx-table-header [text]="field.displayName" sortable="true" <sqx-table-header [text]="field.displayName"
[sortable]="field.properties.isSortable"
[sorting]="filter.sortMode(field) | async" [sorting]="filter.sortMode(field) | async"
(sortingChange)="sort(field, $event)"> (sortingChange)="sort(field, $event)">
</sqx-table-header> </sqx-table-header>
</th> </th>
<th class="cell-time"> <th class="cell-time">
<sqx-table-header text="Updated" sortable="true" <sqx-table-header text="Updated"
[sortable]="true"
[sorting]="filter.sortMode('lastModified') | async" [sorting]="filter.sortMode('lastModified') | async"
(sortingChange)="sort('lastModified', $event)"> (sortingChange)="sort('lastModified', $event)">
</sqx-table-header> </sqx-table-header>

24
src/Squidex/app/shared/services/schemas.types.ts

@ -130,6 +130,10 @@ export abstract class FieldPropertiesDto {
return true; return true;
} }
public get isSortable() {
return true;
}
public abstract accept<T>(visitor: FieldPropertiesVisitor<T>): T; public abstract accept<T>(visitor: FieldPropertiesVisitor<T>): T;
} }
@ -166,6 +170,10 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
public readonly aspectWidth?: number; public readonly aspectWidth?: number;
public readonly aspectHeight?: number; public readonly aspectHeight?: number;
public get isSortable() {
return false;
}
constructor( constructor(
props?: Partial<AssetsFieldPropertiesDto> props?: Partial<AssetsFieldPropertiesDto>
) { ) {
@ -224,6 +232,10 @@ export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
export class GeolocationFieldPropertiesDto extends FieldPropertiesDto { export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'Geolocation'; public readonly fieldType = 'Geolocation';
public get isSortable() {
return false;
}
constructor( constructor(
props?: Partial<GeolocationFieldPropertiesDto> props?: Partial<GeolocationFieldPropertiesDto>
) { ) {
@ -238,6 +250,10 @@ export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
export class JsonFieldPropertiesDto extends FieldPropertiesDto { export class JsonFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'Json'; public readonly fieldType = 'Json';
public get isSortable() {
return false;
}
constructor( constructor(
props?: Partial<JsonFieldPropertiesDto> props?: Partial<JsonFieldPropertiesDto>
) { ) {
@ -281,6 +297,10 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
public readonly maxItems?: number; public readonly maxItems?: number;
public readonly schemaId?: string; public readonly schemaId?: string;
public get isSortable() {
return false;
}
constructor( constructor(
props?: Partial<ReferencesFieldPropertiesDto> props?: Partial<ReferencesFieldPropertiesDto>
) { ) {
@ -330,6 +350,10 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto {
return false; return false;
} }
public get isSortable() {
return false;
}
constructor(editor: string, constructor(editor: string,
props?: Partial<TagsFieldPropertiesDto> props?: Partial<TagsFieldPropertiesDto>
) { ) {

150
src/Squidex/app/shared/state/filter.state.spec.ts

@ -0,0 +1,150 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import {
FilterState,
LanguageDto,
Sorting
} from './../';
describe('FilterState', () => {
let filterState: FilterState;
let query: string | undefined;
let filter: string | undefined;
let fullText: string | undefined;
let order: string | undefined;
beforeEach(() => {
filterState = new FilterState();
filterState.setLanguage(new LanguageDto('de', 'German'));
filterState.query.subscribe(value => {
query = value;
});
filterState.filter.subscribe(value => {
filter = value;
});
filterState.fullText.subscribe(value => {
fullText = value;
});
filterState.order.subscribe(value => {
order = value;
});
});
it('should parse elements from query', () => {
const newQuery = '$filter=MY_FILTER&$orderby=MY_FIELD desc&$search=MY_TEXT';
filterState.setQuery(newQuery);
expect(order).toBe('MY_FIELD desc');
expect(query).toBe(newQuery);
expect(filter).toBe('MY_FILTER');
expect(fullText).toBe('MY_TEXT');
});
it('should set full text and order and calculate query', () => {
filterState.setFullText('Hello World');
filterState.setOrder('data/name/iv asc');
expect(query).toBe('$search=Hello World&$orderby=data/name/iv asc');
expect(fullText).toBe('Hello World');
});
it('should set full text only and calculate query', () => {
filterState.setFullText('Hello World');
expect(query).toBe('Hello World');
expect(fullText).toBe('Hello World');
});
it('should set filter and calculate query', () => {
filterState.setFilter('data/name/iv eq "Squidex"');
expect(query).toBe('$filter=data/name/iv eq "Squidex"');
expect(filter).toBe('data/name/iv eq "Squidex"');
});
it('should set order and calculate query', () => {
filterState.setOrder('data/name/iv asc');
expect(query).toBe('$orderby=data/name/iv asc');
expect(order).toBe('data/name/iv asc');
});
it('should set field name and calculate query', () => {
filterState.setOrderField('field', 'Descending');
expect(query).toBe('$orderby=field desc');
expect(order).toBe('field desc');
});
it('should set field and calculate query', () => {
filterState.setOrderField(<any>{ name: 'first-name', isLocalizable: false }, 'Ascending');
expect(query).toBe('$orderby=data/first_name/iv asc');
expect(order).toBe('data/first_name/iv asc');
});
it('should set localizable field and calculate query', () => {
filterState.setOrderField(<any>{ name: 'first-name', isLocalizable: true }, 'Ascending');
expect(query).toBe('$orderby=data/first_name/de asc');
expect(order).toBe('data/first_name/de asc');
});
it('should update field ordering for path', () => {
let sorting: Sorting;
filterState.sortMode('field').subscribe(value => {
sorting = value;
});
filterState.setQuery('$orderby=field desc');
expect(sorting!).toBe('Descending');
});
it('should update field ordering for field', () => {
let sorting: Sorting;
filterState.sortMode(<any>{ name: 'first-name', fieldId: 1, isLocalizable: false }).subscribe(value => {
sorting = value;
});
filterState.setQuery('$orderby=data/first_name/iv asc');
expect(sorting!).toBe('Ascending');
});
it('should update field ordering for localizable field', () => {
let sorting: Sorting;
filterState.sortMode(<any>{ name: 'first-name', fieldId: 1, isLocalizable: true }).subscribe(value => {
sorting = value;
});
filterState.setQuery('$orderby=data/first_name/de asc');
expect(sorting!).toBe('Ascending');
});
it('should update field ordering for localizable field and other language', () => {
let sorting: Sorting;
filterState.sortMode(<any>{ name: 'first-name', fieldId: 1, isLocalizable: true }).subscribe(value => {
sorting = value;
});
filterState.setQuery('$orderby=data/first_name/en asc');
expect(sorting!).toBe('None');
});
});

26
src/Squidex/app/shared/state/filter.state.ts

@ -8,11 +8,7 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/internal/operators'; import { distinctUntilChanged, map } from 'rxjs/internal/operators';
import { import { State, Types } from '@app/framework';
State,
StringHelper,
Types
} from '@app/framework';
import { LanguageDto } from '../services/languages.service'; import { LanguageDto } from '../services/languages.service';
@ -43,6 +39,8 @@ interface Snapshot {
export type Sorting = 'Ascending' | 'Descending' | 'None'; export type Sorting = 'Ascending' | 'Descending' | 'None';
type Field = string | RootFieldDto;
export class FilterState extends State<Snapshot> { export class FilterState extends State<Snapshot> {
private readonly sortModes: { [key: string]: Observable<Sorting> } = {}; private readonly sortModes: { [key: string]: Observable<Sorting> } = {};
@ -70,7 +68,7 @@ export class FilterState extends State<Snapshot> {
super({}); super({});
} }
public sortMode(field: RootFieldDto | string) { public sortMode(field: Field) {
const key = Types.isString(field) ? field : field.fieldId.toString(); const key = Types.isString(field) ? field : field.fieldId.toString();
let result = this.sortModes[key]; let result = this.sortModes[key];
@ -104,12 +102,12 @@ export class FilterState extends State<Snapshot> {
this.next(s => ({ ...s, language })); this.next(s => ({ ...s, language }));
} }
public setOrderField(field: RootFieldDto | string, sorting: Sorting) { public setOrderField(field: Field, sorting: Sorting) {
this.setOrder(getFieldSorting(this.snapshot, field, sorting)); this.setOrder(getFieldSorting(this.snapshot, field, sorting));
} }
} }
function sortMode(snapshot: Snapshot, field: RootFieldDto | string): Sorting { function sortMode(snapshot: Snapshot, field: Field): Sorting {
let path = getFieldPath(snapshot, field); let path = getFieldPath(snapshot, field);
if (snapshot.orderField === path) { if (snapshot.orderField === path) {
@ -123,7 +121,11 @@ function sortMode(snapshot: Snapshot, field: RootFieldDto | string): Sorting {
return 'None'; return 'None';
} }
function getFieldSorting(snapshot: Snapshot, field: RootFieldDto | string, sorting: Sorting) { function escapeField(value: string) {
return value.replace('-', '_');
}
function getFieldSorting(snapshot: Snapshot, field: Field, sorting: Sorting) {
if (sorting === 'Ascending') { if (sorting === 'Ascending') {
return `${getFieldPath(snapshot, field)} asc`; return `${getFieldPath(snapshot, field)} asc`;
} else { } else {
@ -131,16 +133,16 @@ function getFieldSorting(snapshot: Snapshot, field: RootFieldDto | string, sorti
} }
} }
function getFieldPath(snapshot: Snapshot, field?: RootFieldDto | string) { function getFieldPath(snapshot: Snapshot, field?: Field) {
let path: string | undefined = undefined; let path: string | undefined = undefined;
if (field) { if (field) {
if (Types.isString(field)) { if (Types.isString(field)) {
path = field; path = field;
} else if (field.isLocalizable && snapshot.language) { } else if (field.isLocalizable && snapshot.language) {
path = `data/${StringHelper.toCamelCase(field.name)}/${snapshot.language.iso2Code}`; path = `data/${escapeField(field.name)}/${snapshot.language.iso2Code}`;
} else { } else {
path = `data/${StringHelper.toCamelCase(field.name)}/iv`; path = `data/${escapeField(field.name)}/iv`;
} }
} }

Loading…
Cancel
Save