xenos
Version:
Xenos is a data grid built upon angular2 and bootstrap.
198 lines (165 loc) • 6.27 kB
text/typescript
import { SortDescriptor } from "./sort-descriptor";
import { SortDirection } from "./sort-direction";
import { FilterDescriptor } from "./filter-descriptor";
import { ObservableArray } from "./observable-array";
import { Subject } from "rxjs/Subject";
import { async } from "rxjs/scheduler/async";
import { Observable } from "rxjs/Observable";
import "rxjs/add/Observable/from";
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import "rxjs/add/operator/observeOn";
export interface ItemsPreview {
items?: any[];
processedItems?: any[];
viewItems?: any[];
}
export class ViewSource {
private static empty: any[] = [];
private itemsSourceInternal: any[];
private pageSizeInternal: number;
private pageIndexInternal: number;
private pageCountInternal: number = 1;
private itemsPreviewer: Subject<ItemsPreview> = new Subject<ItemsPreview>();
private itemsLoader: Subject<any[]> = new Subject<any[]>();
constructor() {
this.pageIndex = 0;
this.pageSize = Number.MAX_SAFE_INTEGER;
this.itemsViewChanging = this.itemsPreviewer
.asObservable();
this.itemsViewChanged = this.itemsLoader
.asObservable()
.observeOn(async)
.do(x => this.working = true)
.map(x => {
let container: ItemsPreview = {
items: x
};
return container;
})
.map(x => {
x.processedItems = this.filterItems(x.items);
return x;
})
.do(x => this.calculatePageCount(x.processedItems))
.map(x => {
x.processedItems = this.sortItems(x.processedItems);
return x;
})
.map(x => {
x.viewItems = this.applyPaging(x.processedItems.slice(0));
return x;
})
.map(x => {
this.itemsPreviewer.next(x);
return x.viewItems;
})
.do(x => this.working = false);
}
public working: boolean = false;
public itemsViewChanging: Observable<ItemsPreview>;
public itemsViewChanged: Observable<any[]>;
public sortDescriptors: ObservableArray<SortDescriptor> = new ObservableArray<SortDescriptor>();
public filterDescriptors: ObservableArray<FilterDescriptor> = new ObservableArray<FilterDescriptor>();
private calculatePageCount(items: any[]): void {
this.pageCountInternal = Math.ceil(items.length / this.pageSize)
}
public set pageIndex(value: number) {
if (value > this.pageCount - 1) {
throw new RangeError("Page index must be larger than -1 and smaller than the page count plus 1.");
}
this.pageIndexInternal = value;
}
public get pageIndex(): number {
return this.pageIndexInternal;
}
public set itemsSource(value: any[]) {
this.itemsSourceInternal = value;
}
public get itemsSource(): any[] {
return this.itemsSourceInternal;
}
private applyPaging(items: any[]): any[] {
var page: any;
if (this.pageSize > items.length) {
page = items;
} else {
page = this.getPage(items);
}
return page;
}
private groupFilterDescriptors(descriptors: FilterDescriptor[]): Map<any, FilterDescriptor[]> {
var placeboId = 0;
var groups = new Map<any, FilterDescriptor[]>();
descriptors.forEach(x => {
let groupId = x.groupId;
if (groupId == null) {
groupId = placeboId++;
}
if (!groups.has(groupId)) {
groups.set(groupId, [x]);
} else {
groups.get(groupId).push(x);
}
});
return groups;
}
private filterItems(items: any[]): any[] {
return items.filter((x, i) => {
if (this.filterDescriptors.length === 0) {
return true;
}
// We construct a CNF formula using the provided filter predicates.
// All filters with the same groupId are concatenated using the "OR" operator
// while all of those groups are then combined using an "AND" -- (A||B)&&(C||D)
let expression = true;
var groups = this.groupFilterDescriptors(this.filterDescriptors);
groups.forEach((v, i) => {
let group = v.map(y => y.predicate(x)).reduce((a, b) => a || b, false);
expression = expression && group;
});
return expression;
});
}
private sortItems(items: any[]): any[] {
return items.sort((a, b) => {
for (let i = 0; i < this.sortDescriptors.length; i++) {
let descriptor = this.sortDescriptors[i];
let v1 = descriptor.valueAccessor(a);
let v2 = descriptor.valueAccessor(b);
if (descriptor.direction === SortDirection.descending) {
let v = v1;
v1 = v2;
v2 = v
}
let c = v1 === v2 ? 0 : v1 < v2 ? -1 : 1;
if (c != 0) {
return c;
}
}
return 0;
});
}
public set pageSize(value: number) {
if (value < 1) {
throw new RangeError("Page size must be larger than 0.");
}
this.pageSizeInternal = value;
}
public get pageSize(): number {
return this.pageSizeInternal;
}
public get pageCount(): number {
return this.pageCountInternal;
}
private getPage(items: any[]): any[] {
let index = 0 + this.pageIndex * this.pageSize;
return items.slice(index, index + this.pageSize);
}
public refresh(): void {
if (this.itemsSourceInternal == null) {
return;
}
this.itemsLoader.next(this.itemsSourceInternal);
}
}