Added sorting, started on paging #3

Merged
ff00ff merged 1 commits from dev into main 2024-09-10 22:04:09 -04:00
18 changed files with 558 additions and 177 deletions
Showing only changes of commit 7a525930db - Show all commits

View File

@@ -20,8 +20,8 @@ export class MetadataComponent {
constructor(metaService: MetaService, private queryService: QueryService) { constructor(metaService: MetaService, private queryService: QueryService) {
combineLatest({ combineLatest({
meta: metaService.Data, meta: metaService.Data$,
query: queryService.Query, query: queryService.Query$,
}).subscribe((d: { meta: Partial<TreeNode>[]; query: Query }) => { }).subscribe((d: { meta: Partial<TreeNode>[]; query: Query }) => {
const inuse = d.query.fields; const inuse = d.query.fields;
const expanded = this.getExpanded(this.node); const expanded = this.getExpanded(this.node);

View File

@@ -8,9 +8,14 @@ import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
faRemove, faRemove,
faSort,
faSortAsc,
faSortDesc,
faSortDown,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { ExecuteService } from '../../services/execute.service'; import { ExecuteService } from '../../services/execute.service';
import { Header } from '../../models/header'; import { Header } from '../../models/header';
import { SORT } from '../../enums/sort';
@Component({ @Component({
selector: 'fbi-query', selector: 'fbi-query',
@@ -26,54 +31,34 @@ export class QueryComponent {
private queryService: QueryService, private queryService: QueryService,
executeService: ExecuteService executeService: ExecuteService
) { ) {
queryService.Query.subscribe((query: Query) => { queryService.Query$.subscribe((query: Query) => {
executeService.headers(query).subscribe((headers: Header[]) => { const headers = executeService.headers(query);
this.node = new TreeNode({ const fields = this.getFields(query, headers);
hidden: true, const filters = this.getFilters(query, headers);
expanded: true, const sort = this.getSorts(query, headers);
children: [ this.node = new TreeNode({
{ hidden: true,
label: 'Fields', expanded: true,
leaf: false, children: [
expanded: true, {
children: (query?.fields ?? []).map( label: 'Fields',
(field: string, index: number, array: string[]) => { leaf: false,
const actions = []; expanded: true,
if (index !== 0) { children: fields,
actions.push({ },
label: 'Move Up', {
icon: faArrowUp, label: 'Filters',
data: { cmd: ACTIONS.UP, data: index - 1 }, expanded: true,
}); leaf: false,
} children: filters,
if (index !== array.length - 1) { },
actions.push({ {
label: 'Move Down', label: 'Sort',
icon: faArrowDown, expanded: true,
data: { cmd: ACTIONS.DOWN, data: index + 1 }, leaf: false,
}); children: sort,
} },
actions.push({ ] as TreeNode[],
label: 'Remove',
icon: faRemove,
data: { cmd: ACTIONS.REMOVE },
});
const label =
headers.find((header: Header) => header.source === field)
?.label ?? 'unknown';
return {
label: label,
data: field,
actions: actions,
};
}
),
},
{ label: 'Filters', expanded: true, leaf: false },
] as TreeNode[],
});
}); });
}); });
} }
@@ -85,19 +70,139 @@ export class QueryComponent {
this.queryService.remove(event.node.data); this.queryService.remove(event.node.data);
break; break;
case ACTIONS.UP: case ACTIONS.UP:
this.queryService.add(event.node.data, data.data);
break;
case ACTIONS.DOWN: case ACTIONS.DOWN:
this.queryService.add(event.node.data, data.data); this.queryService.add(event.node.data, data.data);
break; break;
case ACTIONS.SORTASC:
case ACTIONS.SORTDSC:
case ACTIONS.SORTDEL:
this.queryService.sort(data.data);
break;
} }
} }
private getFields(query: Query, headers: Header[]): Partial<TreeNode>[] {
return (query?.fields ?? []).map(
(field: string, index: number, array: string[]) => {
const sort = query?.sort?.field ?? '';
const sdir = query?.sort?.dir ?? SORT.NONE;
const actions = [];
// move
if (index !== 0) {
actions.push({
label: 'Move Up',
icon: faArrowUp,
data: { cmd: ACTIONS.UP, data: index - 1 },
});
}
if (index !== array.length - 1) {
actions.push({
label: 'Move Down',
icon: faArrowDown,
data: { cmd: ACTIONS.DOWN, data: index + 1 },
});
}
// sort
if (sort === field) {
switch (sdir) {
case SORT.ASC:
actions.push({
label: 'Sort Dsc',
icon: faSortDown,
data: { cmd: ACTIONS.SORTDSC, data: field },
});
break;
case SORT.DSC:
actions.push({
label: 'Clear Sort',
icon: faSort,
data: { cmd: ACTIONS.SORTDEL, data: field },
});
break;
default:
actions.push({
label: 'Sort Asc',
icon: faSortAsc,
data: { cmd: ACTIONS.SORTASC, data: field },
});
break;
}
} else {
actions.push({
label: 'Sort Asc',
icon: faSortAsc,
data: { cmd: ACTIONS.SORTASC, data: field },
});
}
// remove
actions.push({
label: 'Remove',
icon: faRemove,
data: { cmd: ACTIONS.REMOVE },
});
const label =
headers.find((header: Header) => header.source === field)?.label ??
'unknown';
return {
label: label,
data: field,
actions: actions,
};
}
);
}
private getFilters(query: Query, headers: Header[]): Partial<TreeNode>[] {
return [];
}
private getSorts(query: Query, headers: Header[]): Partial<TreeNode>[] {
const result: Partial<TreeNode>[] = [];
if (query?.sort?.isValid?.()) {
const s = query.sort;
const label =
headers.find((header: Header) => header.source === s.field)?.label ??
'unknown';
const actions = [];
if (s.dir === SORT.ASC) {
actions.push({
label: 'Sort Dsc',
icon: faSortDesc,
data: { cmd: ACTIONS.SORTDSC, data: s.field },
});
} else if (s.dir === SORT.DSC) {
actions.push({
label: 'Clear Sort',
icon: faSort,
data: { cmd: ACTIONS.SORTDEL, data: s.field },
});
}
result.push({
label: label,
data: s.field,
actions: actions,
});
}
return result;
}
} }
enum ACTIONS { enum ACTIONS {
REMOVE = 'del', REMOVE = 'del',
UP = 'up', UP = 'up',
DOWN = 'down', DOWN = 'down',
SORTDSC = 'dsc',
SORTASC = 'asc',
SORTDEL = 'nne',
} }
type ActionData = { type ActionData = {

View File

@@ -1 +1,5 @@
<fbi-table [headers]="headers" [rows]="rows"></fbi-table> <fbi-table
[data]="data"
(page)="onPage($event)"
(sort)="onSort($event)"
></fbi-table>

View File

@@ -4,9 +4,10 @@ import { TreeComponent } from '../tree/tree.component';
import { QueryService } from '../../services/query.service'; import { QueryService } from '../../services/query.service';
import { Query } from '../../models/query'; import { Query } from '../../models/query';
import { ExecuteService } from '../../services/execute.service'; import { ExecuteService } from '../../services/execute.service';
import { forkJoin } from 'rxjs';
import { Header } from '../../models/header';
import { TableComponent } from '../table/table.component'; import { TableComponent } from '../table/table.component';
import { Result } from '../../models/result';
import { Header } from '../../models/header';
import { Page } from '../../models/page';
@Component({ @Component({
selector: 'fbi-result', selector: 'fbi-result',
@@ -16,34 +17,43 @@ import { TableComponent } from '../table/table.component';
styleUrl: './result.component.scss', styleUrl: './result.component.scss',
}) })
export class ResultComponent { export class ResultComponent {
headers: Header[] = []; data: Result = new Result({});
rows: Record<string, any>[] = [];
private query: Query = new Query({});
private page: Page = new Page({});
constructor( constructor(
queryService: QueryService, private queryService: QueryService,
private executeService: ExecuteService private executeService: ExecuteService
) { ) {
let last: string = ''; let last: string = '';
queryService.Query.subscribe((query: Query) => { queryService.Query$.subscribe((query: Query) => {
if (query.isValid()) { if (query.isValid()) {
const current = query.toString(); const current = query.toString();
if (last !== current) { if (last !== current) {
this.load(query); this.query = query;
this.page = new Page({});
this.load();
last = current; last = current;
} }
} }
}); });
} }
private load(query: Query): void { private load(): void {
forkJoin({ const query = new Query(this.query);
headers: this.executeService.headers(query), query.page = new Page(this.page);
data: this.executeService.data(query), this.executeService.data(query).subscribe((result: Result) => {
}).subscribe( this.data = result;
(result: { headers: Header[]; data: Record<string, any>[] }) => { });
this.headers = result.headers; }
this.rows = result.data;
} onPage(page: Page): void {
); this.page = new Page(page);
this.load();
}
onSort(header: Header): void {
this.queryService.sort(header.source);
} }
} }

View File

@@ -1,14 +1,59 @@
@if (data.headers.length > 0) {
<table> <table>
<tr> <thead>
<th></th> <tr>
<th *ngFor="let h of headers"> <th class="spacer"></th>
{{ h.label }} @for (h of data.headers; track h) {
</th> <th [title]="h.label">
</tr> {{ h.label }}
<tr *ngFor="let row of rows; index as i"> <span
<td>{{ i }}</td> class="clickable"
<td *ngFor="let h of headers"> [class.button]="h.sort !== SORT.NONE"
{{ row[h.source] }} [class.unused]="h.sort === SORT.NONE"
</td> (click)="onSort($event, h)"
</tr> >
@switch (h.sort) { @case (SORT.ASC) {
<fa-icon [icon]="faSortUp"></fa-icon>
} @case(SORT.DSC) {
<fa-icon [icon]="faSortDown"></fa-icon>
} @default {
<fa-icon [icon]="faSort"></fa-icon>
} }
</span>
</th>
}
</tr>
</thead>
<tbody>
@for (row of data.data; track row; let i = $index) {
<tr>
<td>
@if (data.page) {
{{ (data.page.page - 1) * data.page.size + i + 1 }}
} @else {
{{ i + 1 }}
}
</td>
@for (h of data.headers; track h) {
<td>
{{ row[h.source] }}
</td>
}
</tr>
}
</tbody>
@if (data.page) {
<tfoot>
<tr>
<td [attr.colspan]="data.headers.length + 1">
<span>
Showing {{ (data.page.page - 1) * data.page.size + 1 }} to
{{ data.page.page * data.page.size }} of {{ data.total }}
</span>
<span> </span>
</td>
</tr>
</tfoot>
}
</table> </table>
}

View File

@@ -0,0 +1,61 @@
table {
border-collapse: collapse;
}
thead {
th {
border: 1px solid #ddd;
background-color: #eee;
text-align: left;
vertical-align: top;
margin: 2px;
padding: 0px 2px;
}
th.highlight {
background-color: #cdf;
}
.spacer {
border: none;
background-color: inherit;
}
}
tbody {
td {
border: 1px solid #ddd;
text-align: right;
padding: 2px;
}
td.highlight {
background-color: #def;
}
td:hover {
background-color: #eff;
}
}
tfoot {
td {
text-align: center;
}
}
.left {
text-align: left;
}
.unused {
color: #ddd;
}
.button {
color: #777;
}
.clickable {
cursor: pointer;
}

View File

@@ -1,15 +1,41 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Header } from '../../models/header'; import { Header } from '../../models/header';
import {
faSort,
faSortDown,
faSortUp,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { SORT } from '../../enums/sort';
import { Result } from '../../models/result';
import { Page } from '../../models/page';
@Component({ @Component({
selector: 'fbi-table', selector: 'fbi-table',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule, FontAwesomeModule],
templateUrl: './table.component.html', templateUrl: './table.component.html',
styleUrl: './table.component.scss', styleUrl: './table.component.scss',
}) })
export class TableComponent { export class TableComponent {
@Input() headers!: Header[]; @Input() data!: Result;
@Input() rows!: Record<string, any>[];
@Output() page = new EventEmitter<Page>();
@Output() sort = new EventEmitter<Header>();
faSortDown = faSortDown;
faSortUp = faSortUp;
faSort = faSort;
SORT = SORT;
onPage(event: MouseEvent): void {
event.stopPropagation();
this.page.emit();
}
onSort(event: MouseEvent, header: Header): void {
event.stopPropagation();
this.sort.emit(header);
}
} }

View File

@@ -2,7 +2,9 @@ export enum Milestone {
Cohort = 'cohort', Cohort = 'cohort',
Year = 'year', Year = 'year',
County = 'mcode', County = 'mcode',
CountyLabel = 'county',
School = 'lcode', School = 'lcode',
SchoolLabel = 'school',
Grade = 'grade', Grade = 'grade',
ELACount = 'ecount', ELACount = 'ecount',
ELAMean = 'emean', ELAMean = 'emean',

5
src/enums/sort.ts Normal file
View File

@@ -0,0 +1,5 @@
export enum SORT {
NONE = 'none',
ASC = 'asc',
DSC = 'dsc',
}

View File

@@ -1,9 +1,13 @@
import { SORT } from '../enums/sort';
export class Header { export class Header {
label: string; label: string;
source: string; source: string;
sort: SORT;
constructor(data: Partial<Header>) { constructor(data: Partial<Header>) {
this.label = data?.label ?? ''; this.label = data?.label ?? '';
this.source = data?.source ?? ''; this.source = data?.source ?? '';
this.sort = data?.sort ?? SORT.NONE;
} }
} }

View File

@@ -1,13 +1,13 @@
export class Page { export class Page {
start: number; page: number;
limit: number; size: number;
constructor(data: Partial<Page>) { constructor(data: Partial<Page>) {
this.start = data?.start ?? 1; this.page = data?.page ?? 1;
this.limit = data?.limit ?? 20; this.size = data?.size ?? 20;
} }
toString(): string { toString(): string {
return `(${this.start}:${this.limit})`; return `${this.page}:${this.size}`;
} }
} }

View File

@@ -1,14 +1,17 @@
import { Page } from './page'; import { Page } from './page';
import { Sort } from './sort';
export class Query { export class Query {
fields: string[]; fields: string[];
filter: string[]; filter: string[];
page: Page; sort: Sort;
page?: Page;
constructor(data: Partial<Query>) { constructor(data: Partial<Query>) {
this.fields = data?.fields ?? []; this.fields = data?.fields ?? [];
this.filter = data?.filter ?? []; this.filter = data?.filter ?? [];
this.page = new Page(data?.page ?? {}); this.sort = new Sort(data?.sort ?? {});
if (data?.page) this.page = new Page(data?.page);
} }
isValid(): boolean { isValid(): boolean {
@@ -16,8 +19,11 @@ export class Query {
} }
toString(): string { toString(): string {
const fields = (this.fields ?? []).join(', '); return [
const filters = ''; this.fields.join(','),
return `${fields}${filters}`; '',
this.page?.toString(),
this.sort.toString(),
].join(';');
} }
} }

16
src/models/result.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Header } from './header';
import { Page } from './page';
export class Result {
headers: Header[];
data: Record<string, any>[];
total: number;
page?: Page;
constructor(data: Partial<Result>) {
this.headers = data?.headers ?? [];
this.data = data?.data ?? [];
this.total = data?.total ?? 0;
if (data?.page) this.page = new Page(data.page);
}
}

19
src/models/sort.ts Normal file
View File

@@ -0,0 +1,19 @@
import { SORT } from '../enums/sort';
export class Sort {
field: string;
dir: SORT;
constructor(data: Partial<Sort>) {
this.field = data?.field ?? '';
this.dir = data?.dir ?? SORT.NONE;
}
isValid(): boolean {
return this.field !== '' && this.dir !== SORT.NONE;
}
toString(): string {
return `${this.field}:${this.dir}`;
}
}

View File

@@ -68,16 +68,29 @@ export class DataService {
const data: Record<string, any>[] = []; const data: Record<string, any>[] = [];
(results?.data ?? []).forEach((record: Record<string, any>) => { (results?.data ?? []).forEach((record: Record<string, any>) => {
let code = fn(Csv.County_Code, record); const code = fn(Csv.County_Code, record);
// skip non-data rows on the csv // skip non-data rows on the csv
if (isNaN(code)) return; if (isNaN(code)) return;
const county = fs(Csv.County_Code, record);
const county_label = fs(Csv.County_Label, record);
const school_label = fs(Csv.School_Label, record);
if (county_label === '' || school_label === '') return;
if (!this._counties.has(county)) {
this._counties.set(county, county_label);
}
const school = fs(Csv.School_Code, record);
if (!this._schools.has(school)) {
this._schools.set(school, school_label);
}
const ms = {} as Record<string, any>; const ms = {} as Record<string, any>;
ms[Milestone.Year] = year; ms[Milestone.Year] = year;
ms[Milestone.Grade] = grade; ms[Milestone.Grade] = grade;
ms[Milestone.Cohort] = cohort; ms[Milestone.Cohort] = cohort;
ms[Milestone.County] = fs(Csv.County_Code, record); ms[Milestone.County] = this._counties.get(county); //fs(Csv.County_Code, record);
ms[Milestone.School] = fs(Csv.School_Code, record); ms[Milestone.School] = this._schools.get(school); //fs(Csv.School_Code, record);
if (year !== 2020) { if (year !== 2020) {
ms[Milestone.ELACount] = fn(Csv.ELA_Count, record); ms[Milestone.ELACount] = fn(Csv.ELA_Count, record);
ms[Milestone.ELAMean] = fn(Csv.ELA_Mean, record); ms[Milestone.ELAMean] = fn(Csv.ELA_Mean, record);
@@ -106,12 +119,14 @@ export class DataService {
} }
data.push(ms); data.push(ms);
const county = fs(Csv.County_Code, record); // this._counties.set(
this._counties.set(county, fs(Csv.County_Label, record)); // fs(Csv.County_Code, record),
this._schools.set( // fs(Csv.County_Label, record)
`${county}-${fs(Csv.School_Code, record)}`, // );
fs(Csv.School_Label, record) // this._schools.set(
); // fs(Csv.School_Code, record),
// fs(Csv.School_Label, record)
// );
}); });
if (year !== 2020) { if (year !== 2020) {

View File

@@ -4,6 +4,8 @@ import { Query } from '../models/query';
import { Observable, of, map, take } from 'rxjs'; import { Observable, of, map, take } from 'rxjs';
import { Header } from '../models/header'; import { Header } from '../models/header';
import { MetaService } from './meta.service'; import { MetaService } from './meta.service';
import { SORT } from '../enums/sort';
import { Result } from '../models/result';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ExecuteService { export class ExecuteService {
@@ -12,27 +14,36 @@ export class ExecuteService {
private metaService: MetaService private metaService: MetaService
) {} ) {}
headers(query: Query): Observable<Header[]> { headers(query: Query): Header[] {
if (!query?.isValid()) return of([]); if (!query?.isValid?.()) return [];
const fields = query.fields; const fields = [...query.fields];
return this.metaService.Flat.pipe( const sort = query.sort;
map((items: Header[]) =>
items return this.metaService
.filter((item: Header) => fields.includes(item.source)) .Flat()
.sort( .filter((item: Header) => fields.includes(item.source))
(a: Header, b: Header) => .sort(
fields.indexOf(a.source) - fields.indexOf(b.source) (a: Header, b: Header) =>
) fields.indexOf(a.source) - fields.indexOf(b.source)
), )
take(1) .map((item: Header) => {
); if (item.source === sort.field) {
item.sort = sort.dir;
}
return item;
});
} }
data(query: Query): Observable<Record<string, any>[]> { data(query: Query): Observable<Result> {
if (!query?.isValid()) return of([]); if (!query?.isValid()) return of(new Result({}));
console.log(query.toString());
const fields = query.fields; const fields = [...query.fields];
const page = query.page;
const sort = query.sort;
const headers = this.headers(query);
return this.dataService.Data$.pipe( return this.dataService.Data$.pipe(
// apply filter // apply filter
@@ -43,37 +54,47 @@ export class ExecuteService {
return true; return true;
}); });
}), }),
// apply fields // apply fields, make unique
map((data: Record<string, any>[]) => map((data: Record<string, any>[]) => {
data.map((i: Record<string, any>) => { let unique = new Map<string, Record<string, any>>();
data.forEach((i: Record<string, any>) => {
const r = {} as Record<string, any>; const r = {} as Record<string, any>;
fields.forEach((field: string) => (r[field] = i[field])); fields.forEach((field: string) => (r[field] = i[field]));
return r; const key = fields.map((field: string) => i[field]).join(':');
}) unique.set(key, r);
),
// make the data unique and apply paging
map((data: Record<string, any>[]) => {
let i = 0;
const result: Record<string, any>[] = [];
const page = query.page;
data.some((d: Record<string, any>) => {
const idx = result.findIndex((r: Record<string, any>) =>
fields
.map((field: string) => d[field] === r[field])
.reduce((acc, cur) => acc && cur, true)
);
// if this element has not been seen before, add it to the results we care about.
if (idx < 0) {
result.push(d);
++i;
}
return i >= page.start + page.limit;
}); });
return [...unique].map(([name, value]) => value);
return result.slice(page.start, page.start + page.limit);
}), }),
// sort
map((data: Record<string, any>[]) => {
if (!sort.isValid()) return data;
const field = sort.field;
if (!fields.includes(field)) {
} else if (sort.dir === SORT.ASC) {
return data.sort((a: Record<string, any>, b: Record<string, any>) =>
a[field] > b[field] ? 1 : -1
);
} else if (sort.dir === SORT.DSC) {
return data.sort((a: Record<string, any>, b: Record<string, any>) =>
a[field] < b[field] ? 1 : -1
);
}
return data;
}),
// paging
map(
(data: Record<string, any>[]) =>
new Result({
headers: headers,
data: page
? data.slice((page.page - 1) * page.size, page.page * page.size)
: data,
page: page,
total: data.length,
})
),
// force it to close // force it to close
take(1) take(1)
); );

View File

@@ -76,35 +76,54 @@ export class MetaService {
{ data: Milestone.Soc3, label: 'Advanced Learner %' }, { data: Milestone.Soc3, label: 'Advanced Learner %' },
], ],
}, },
]; ] as Partial<TreeNode>[];
private subject = new ReplaySubject<Partial<TreeNode>[]>(1);
readonly Data = this.subject.asObservable();
readonly Flat = this.subject.asObservable().pipe(
map((data: Partial<TreeNode>[]) => {
const recurse = (path: string, item: Partial<TreeNode>): Header[] => {
const result = [];
const children = item?.children ?? [];
if (children.length > 0) {
children.forEach((child: Partial<TreeNode>) => {
result.push(...recurse(`${path}${item.label}`, child));
});
} else {
result.push(
new Header({ source: item.data, label: `${path} - ${item.label}` })
);
}
return result;
};
const result: Header[] = []; private subject = new ReplaySubject<TreeNode[]>(1);
data.forEach((item: Partial<TreeNode>) => readonly Data$ = this.subject
result.push(...recurse('', item)) .asObservable()
); .pipe(
return result; map((items: TreeNode[]) =>
}) items.map((item: TreeNode) => new TreeNode(item))
); )
);
// readonly Flat$ = this.subject.asObservable().pipe(
// map((data: TreeNode[]) => {
// const result: Header[] = [];
// data.forEach((item: TreeNode) => result.push(...this.flatten('', item)));
// return result;
// })
// );
// readonly Data = () =>
// this.data.map((item: Partial<TreeNode>) => new TreeNode(item));
readonly Flat = () => {
const result: Header[] = [];
this.data.forEach((item: Partial<TreeNode>) =>
result.push(...this.flatten('', item))
);
return result;
};
constructor() { constructor() {
this.subject.next(this.data as Partial<TreeNode>[]); this.subject.next(
this.data.map((item: Partial<TreeNode>) => new TreeNode(item))
);
}
private flatten(path: string, item: Partial<TreeNode>): Header[] {
const result: Header[] = [];
const children = item?.children ?? [];
if (children.length > 0) {
children.forEach((child: Partial<TreeNode>) =>
result.push(...this.flatten(`${path}${item.label}`, child))
);
} else {
result.push(
new Header({ source: item.data, label: `${path} - ${item.label}` })
);
}
return result;
} }
} }

View File

@@ -1,11 +1,13 @@
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { Query } from '../models/query'; import { Query } from '../models/query';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Sort } from '../models/sort';
import { SORT } from '../enums/sort';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class QueryService { export class QueryService {
private querySubject = new BehaviorSubject<Query>(new Query({})); private querySubject = new BehaviorSubject<Query>(new Query({}));
readonly Query = this.querySubject.asObservable(); readonly Query$ = this.querySubject.asObservable();
constructor() {} constructor() {}
@@ -21,4 +23,25 @@ export class QueryService {
q.fields = q.fields.filter((f: string) => f !== field); q.fields = q.fields.filter((f: string) => f !== field);
this.querySubject.next(q); this.querySubject.next(q);
} }
sort(field: string): void {
const q = new Query(this.querySubject.value);
const s = q.sort;
if (s.field === field) {
switch (s.dir) {
case SORT.ASC:
q.sort = new Sort({ field: field, dir: SORT.DSC });
break;
case SORT.DSC:
q.sort = new Sort({});
break;
default:
q.sort = new Sort({ field: field, dir: SORT.ASC });
break;
}
} else {
q.sort = new Sort({ field: field, dir: SORT.ASC });
}
this.querySubject.next(q);
}
} }