UNPKG

@rdkmaster/jigsaw-labs

Version:

Jigsaw, the next generation component set for RDK

1,047 lines (921 loc) 36.6 kB
import {EventEmitter} from "@angular/core"; import {HttpClient} from "@angular/common/http"; import {Subject} from "rxjs/Subject"; import "rxjs/add/operator/map"; import 'rxjs/add/operator/debounceTime'; import {Subscription} from "rxjs/Subscription"; import { ComponentDataHelper, DataFilterInfo, DataReviser, DataSortInfo, HttpClientOptions, IAjaxComponentData, IEmittable, IFilterable, IPageable, IServerSidePageable, ISortable, PagingInfo, PreparedHttpClientOptions, SortAs, SortOrder, serializeFilterFunction } from "./component-data"; import {TableData} from "./table-data"; import {CallbackRemoval, CommonUtils} from "../utils/common-utils"; /** * we have to implement the `Array<T>` interface due to this breaking change: * <https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work> * <https://github.com/Microsoft/TypeScript/issues/14869> */ export class JigsawArray<T> implements Array<T> { private _agent: T[] = []; /** * 将位置`index`处的数据更新为`value`。`JigsawArray`不支持采用方括号表达式设置一个值,因此必须通过这个方法来替代。 * * ``` * const a = new ArrayCollection<any>(); * a[0] = 123; // compile error! * a.set(0, 123); // everything is fine. * ``` * * @param {number} index * @param {T} value */ public set(index: number, value: T): void { this._length = this._length > index ? this._length : index + 1; const thiz: any = this; thiz[index] = value; } /** * 获取`index`位置处的数据,和数组的方括号表达式的作用一样。 * * ``` * const a = new ArrayCollection<any>([{}]); * a.get(0) === a[0] // true * ``` * * @param {number} index * @returns {T} */ public get(index: number): T { return this[index]; } private _length: number = 0; /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/length> * @return {number} */ public get length(): number { return this._length; } public set length(value: number) { this._length = value; } readonly [n: number]: T; /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes> * * @param {T} searchElement * @param {number} fromIndex * @return {boolean} */ public includes(searchElement: T, fromIndex?: number): boolean { return this._agent.includes.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/toString> * @return {string} */ public toString(): string { return this._agent.toString.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/toLocaleString> * @return {string} */ public toLocaleString(): string { return this._agent.toLocaleString.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/push> * @param {T} items * @return {number} */ public push(...items: T[]): number { return this._agent.push.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/pop> * @return {T} */ public pop(): T { return this._agent.pop.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/concat> * @param items * @return {any} */ public concat(...items: any[]): any { return this._agent.concat.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/join> * @param {string} separator * @return {string} */ public join(separator?: string): string { return this._agent.join.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse> * @return {T[]} */ public reverse(): T[] { return this._agent.reverse.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/shift> * @return {T} */ public shift(): T { return this._agent.shift.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice> * @param {number} start * @param {number} end * @return {T[]} */ public slice(start?: number, end?: number): T[] { return this._agent.slice.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort> * @param {(a: T, b: T) => number} compareFn * @return {any} */ public sort(compareFn?: (a: T, b: T) => number): any { return this._agent.sort.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice> * @param start * @param deleteCount * @param rest * @return {T[]} */ public splice(start: any, deleteCount?: any, ...rest: any[]): T[] { return this._agent.splice.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift> * @param {T} items * @return {number} */ public unshift(...items: T[]): number { return this._agent.unshift.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf> * @param {T} searchElement * @param {number} fromIndex * @return {number} */ public indexOf(searchElement: T, fromIndex?: number): number { return this._agent.indexOf.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf> * @param {T} searchElement * @param {number} fromIndex * @return {number} */ public lastIndexOf(searchElement: T, fromIndex?: number): number { return this._agent.lastIndexOf.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/every> * @param {(value: T, index: number, array: T[]) => boolean} callbackfn * @param thisArg * @return {boolean} */ public every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean { return this._agent.every.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/some> * @param {(value: T, index: number, array: T[]) => boolean} callbackfn * @param thisArg * @return {boolean} */ public some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean { return this._agent.some.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach> * @param {(value: T, index: number, array: T[]) => void} callbackfn * @param thisArg */ public forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void { return this._agent.forEach.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map> * @param callbackfn * @param thisArg * @return {[any , any , any , any , any]} */ public map(callbackfn: any, thisArg?: any): [any, any, any, any, any] { return this._agent.map.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter> * @param {(value: T, index: number, array: T[]) => any} callbackfn * @param thisArg * @return {T[]} */ public filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[] { return this._agent.filter.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce> * @param callbackfn * @param initialValue * @return {T} */ public reduce(callbackfn: any, initialValue?: any): T { return this._agent.reduce.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight> * @param callbackfn * @param initialValue * @return {T} */ public reduceRight(callbackfn: any, initialValue?: any): T { return this._agent.reduceRight.apply(this, arguments); } /** * @internal */ [Symbol.unscopables](): { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; } { const iterator = this._agent[Symbol.unscopables]; return iterator.apply(this); } /** * @internal */ [Symbol.iterator](): IterableIterator<T> { const iterator = this._agent[Symbol.iterator]; return iterator.apply(this); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/entries> * @return {IterableIterator<[number , T]>} */ public entries(): IterableIterator<[number, T]> { return this._agent.entries.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/keys> * @return {IterableIterator<number>} */ public keys(): IterableIterator<number> { return this._agent.keys.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/values> * @return {IterableIterator<T>} */ public values(): IterableIterator<T> { return this._agent.values.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/find> * @param {(value: T, index: number, obj: T[]) => boolean} predicate * @param thisArg * @return {T} */ public find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T { return this._agent.find.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex> * @param {(value: T, index: number, obj: T[]) => boolean} predicate * @param thisArg * @return {number} */ public findIndex(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): number { return this._agent.findIndex.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/fill> * @param {T} value * @param {number} start * @param {number} end * @return {any} */ public fill(value: T, start?: number, end?: number): any { return this._agent.fill.apply(this, arguments); } /** * 参考这里 <https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin> * @param {number} target * @param {number} start * @param {number} end * @return {any} */ public copyWithin(target: number, start: number, end?: number): any { return this._agent.copyWithin.apply(this, arguments); } } /** * 这是Jigsaw数据体系中两大分支之一:数组类型的基类。 * * 关于Jigsaw数据体系详细介绍,请参考`IComponentData`的说明 */ export class ArrayCollection<T> extends JigsawArray<T> implements IAjaxComponentData, IEmittable { /** * 用于发起网络请求,在调用`fromAjax()`之前必须设置好此值。 */ public http: HttpClient; public dataReviser: DataReviser; public concat(...items: any[]): ArrayCollection<T> { const acArr = []; for (let i = 0; i < this.length; i++) { acArr.push(this[i]) } let itemArr = []; items.forEach(item => { itemArr = itemArr.concat(item); }); return new ArrayCollection<T>(acArr.concat(itemArr)); } public slice(start?: number, end?: number): ArrayCollection<T> { const acArr = []; for (let i = 0; i < this.length; i++) { acArr.push(this[i]) } return new ArrayCollection<T>(acArr.slice(start, end)); } constructor(source?: T[]) { super(); this._fromArray(source); } protected _busy: boolean = false; get busy(): boolean { return this._busy; } /** * 调用在`onAjaxStart`里注册的所有回调函数。 */ protected ajaxStartHandler(): void { this._busy = true; this.componentDataHelper.invokeAjaxStartCallback(); } /** * 调用在`onAjaxSuccess`里注册的所有回调函数。 */ protected ajaxSuccessHandler(data: T[]): void { console.log('get data from paging server success!!'); if (data instanceof Array) { this.fromArray(data); } else { console.error('invalid data type: ' + typeof(data) + ', need Array.'); this.fromArray([]); } this._busy = false; this.componentDataHelper.invokeAjaxSuccessCallback(data); } /** * 调用在`onAjaxError`里注册的所有回调函数。 */ protected ajaxErrorHandler(error: Response): void { if (!error) { const reason = 'the array collection is busy now!'; console.error('get data from paging server error!! detail: ' + reason); error = new Response(reason, {status: 409, statusText: reason}); } else { console.error('get data from paging server error!! detail: ' + error['message']); this.fromArray([]); this._busy = false; } this.componentDataHelper.invokeAjaxErrorCallback(error); } /** * 调用在`onAjaxComplete`里注册的所有回调函数。 */ protected ajaxCompleteHandler(): void { console.log('get data from paging server complete!!'); this._busy = false; this.componentDataHelper.invokeAjaxCompleteCallback(); } /** * 安全地调用`dataReviser`函数。 * * @param originData * @return {any} */ protected reviseData(originData: any): any { if (!this.dataReviser) { return originData; } try { const revisedData = this.dataReviser(originData); if (revisedData == undefined) { console.error('a dataReviser function should NOT return undefined,' + 'use null is you do not have any valid value!' + 'Jigsaw is ignoring this result and using the original value.'); return originData; } else { return revisedData; } } catch (e) { console.error('revise data error: ' + e); console.error(e.stack); return originData; } } public fromAjax(url?: string): void; public fromAjax(options?: HttpClientOptions): void; /** * @internal */ public fromAjax(optionsOrUrl?: HttpClientOptions | string): void { if (!this.http) { console.error('set a valid Http instance to ArrayCollection.http before invoking ArrayCollection.fromAjax()!'); return; } if (this._busy) { this.ajaxErrorHandler(null); return; } this.ajaxStartHandler(); const op = HttpClientOptions.prepare(optionsOrUrl); this.http.request(op.method, op.url, op) .map(res => this.reviseData(res) as T[]) .subscribe( data => this.ajaxSuccessHandler(data), error => this.ajaxErrorHandler(error), () => this.ajaxCompleteHandler() ); } /** * 将一个普通数组对象`source`的所有元素浅拷贝到当前数据对象中。 * * ``` * const ac = new ArrayCollection<number>(); * ac.fromArray([1, 2, 3]); * console.log(ac); // [1, 2, 3] * ``` * * @param {T[]} source 源数据 * @returns {ArrayCollection<T>} 返回当前数据对象的引用 */ public fromArray(source: T[]): ArrayCollection<T> { if (this._fromArray(source)) { this.refresh(); } return this; } private _fromArray(source: T[]): boolean { source = source instanceof Array || source instanceof ArrayCollection ? source : CommonUtils.isDefined(source) ? [source] : []; let needRefresh = this.length > 0; this.splice(0, this.length); if (source.length > 0) { needRefresh = needRefresh || source.length > 0; source.forEach(item => this.push(item)); } return needRefresh; } protected componentDataHelper: ComponentDataHelper = new ComponentDataHelper(); public refresh(): void { this.componentDataHelper.invokeRefreshCallback(); } public onRefresh(callback: (thisData: ArrayCollection<T>) => void, context?: any): CallbackRemoval { return this.componentDataHelper.getRefreshRemoval({fn: callback, context: context}); } public onAjaxStart(callback: () => void, context?: any): CallbackRemoval { return this.componentDataHelper.getAjaxStartRemoval({fn: callback, context: context}); } public onAjaxSuccess(callback: (data: any) => void, context?: any): CallbackRemoval { return this.componentDataHelper.getAjaxSuccessRemoval({fn: callback, context: context}); } public onAjaxError(callback: (error: Response) => void, context?: any): CallbackRemoval { return this.componentDataHelper.getAjaxErrorRemoval({fn: callback, context: context}); } public onAjaxComplete(callback: () => void, context?: any): CallbackRemoval { return this.componentDataHelper.getAjaxCompleteRemoval({fn: callback, context: context}); } public destroy(): void { console.log('destroying ArrayCollection....'); this.splice(0, this.length); this.componentDataHelper && this.componentDataHelper.clearCallbacks(); this.componentDataHelper = null; this.dataReviser = null; this._emitter && this._emitter.unsubscribe(); this._emitter = null; } private _emitter = new EventEmitter<any>(); public emit(value?: any): void { this._emitter.emit(value); } public subscribe(callback?: (value: any) => void): Subscription { return this._emitter.subscribe(callback); } public unsubscribe() { this._emitter.unsubscribe(); } } /** * 这是实际使用时最常用的数组对象,具备服务端分页、服务端排序、服务端过滤能力。 * 注意:需要有一个统一的具备服务端分页、服务端排序、服务端过滤能力的REST服务配合使用, * 更多信息请参考`PagingInfo.pagingServerUrl` * * 实际用法请参考[这个demo]($demo=data-encapsulation/array-ssp) * * 关于Jigsaw数据体系详细介绍,请参考`IComponentData`的说明 */ export class PageableArray extends ArrayCollection<any> implements IServerSidePageable, ISortable, IFilterable { public pagingInfo: PagingInfo; public filterInfo: DataFilterInfo; public sortInfo: DataSortInfo; /** * 参考`PageableTableData.sourceRequestOptions`的说明 */ public sourceRequestOptions: HttpClientOptions; private _filterSubject = new Subject<DataFilterInfo>(); private _sortSubject = new Subject<DataSortInfo>(); constructor(public http: HttpClient, requestOptionsOrUrl: HttpClientOptions | string) { super(); if (!http) { throw new Error('invalid http!'); } this.pagingInfo = new PagingInfo(); this.pagingInfo.subscribe(() => { this._ajax(); }); this.sourceRequestOptions = typeof requestOptionsOrUrl === 'string' ? {url: requestOptionsOrUrl} : requestOptionsOrUrl; this._initSubjects(); } private _initSubjects(): void { this._filterSubject.debounceTime(300).subscribe(filter => { this.filterInfo = filter; this._ajax(); }); this._sortSubject.debounceTime(300).subscribe(sort => { this.sortInfo = sort; this._ajax(); }); } public updateDataSource(options: HttpClientOptions): void public updateDataSource(url: string): void; /** * @internal */ public updateDataSource(optionsOrUrl: HttpClientOptions | string): void { this.sourceRequestOptions = typeof optionsOrUrl === 'string' ? {url: optionsOrUrl} : optionsOrUrl; this.pagingInfo.currentPage = 1; this.pagingInfo.totalRecord = 0; this.filterInfo = null; this.sortInfo = null; } public fromAjax(url?: string): void; public fromAjax(options?: HttpClientOptions): void; /** * @internal */ public fromAjax(optionsOrUrl?: HttpClientOptions | string): void { if (optionsOrUrl instanceof HttpClientOptions) { this.updateDataSource(<HttpClientOptions>optionsOrUrl); } else if (!!optionsOrUrl) { this.updateDataSource(<string>optionsOrUrl); } this._ajax(); } private _ajax(): void { if (this._busy) { this.ajaxErrorHandler(null); return; } const options = HttpClientOptions.prepare(this.sourceRequestOptions); if (!options) { console.error('invalid source request options, use updateDataSource() to reset the option.'); return; } this._busy = true; this.ajaxStartHandler(); const method = this.sourceRequestOptions.method ? this.sourceRequestOptions.method.toLowerCase() : 'get'; const paramProperty = method == 'get' ? 'params' : 'body'; let originParams = this.sourceRequestOptions[paramProperty]; delete options.params; delete options.body; options[paramProperty] = { service: options.url, paging: this.pagingInfo.valueOf() }; if (CommonUtils.isDefined(originParams)) { options[paramProperty].peerParam = originParams; } if (CommonUtils.isDefined(this.filterInfo)) { options[paramProperty].filter = this.filterInfo; } if (CommonUtils.isDefined(this.sortInfo)) { options[paramProperty].sortInfo = this.sortInfo; } if (CommonUtils.isDefined(this.sortInfo)) { options[paramProperty].sort = this.sortInfo; } if (paramProperty == 'params') { options.params = PreparedHttpClientOptions.prepareParams(options.params) } this.http.request(options.method, PagingInfo.pagingServerUrl, options) .map(res => this.reviseData(res)) .map(data => { this._updatePagingInfo(data); const tableData: TableData = new TableData(); if (TableData.isTableData(data)) { tableData.fromObject(data); } else { console.error('invalid data format, need a TableData object.'); } return tableData; }) .subscribe( tableData => this.ajaxSuccessHandler(tableData), error => this.ajaxErrorHandler(error), () => this.ajaxCompleteHandler() ); } private _updatePagingInfo(data: any): void { if (!data.hasOwnProperty('paging')) { return; } const paging = data.paging; this.pagingInfo.totalRecord = paging.hasOwnProperty('totalRecord') ? paging.totalRecord : this.pagingInfo.totalRecord; } protected ajaxSuccessHandler(data: any): void { console.log('get data from paging server success!!'); this.fromArray(data.toArray()); this.componentDataHelper.invokeAjaxSuccessCallback(data); } public filter(callbackfn: (value: any, index: number, array: any[]) => any, thisArg?: any): any; public filter(term: string, fields?: string[] | number[]): void; public filter(term: DataFilterInfo): void; /** * @internal */ public filter(term: string | DataFilterInfo | Function, fields?: string[] | number[]): void { let pfi: DataFilterInfo; if (term instanceof DataFilterInfo) { pfi = term; } else if (term instanceof Function) { // 这里的fields相当于thisArg,即函数执行的上下文对象 pfi = new DataFilterInfo(undefined, undefined, serializeFilterFunction(term), fields); } else { pfi = new DataFilterInfo(term, fields); } this._filterSubject.next(pfi); } public sort(compareFn?: (a: any, b: any) => number): any; public sort(as: SortAs, order: SortOrder, field: string | number): void; public sort(sort: DataSortInfo): void; /** * @internal */ public sort(as, order?: SortOrder, field?: string | number): void { if (as instanceof Function) { throw 'compare function is NOT accepted by this class!'; } const psi = as instanceof DataSortInfo ? as : new DataSortInfo(as, order, field); this._sortSubject.next(psi); } public changePage(currentPage: number, pageSize?: number): void; public changePage(info: PagingInfo): void; /** * @internal */ public changePage(currentPage, pageSize?: number): void { pageSize = isNaN(+pageSize) ? this.pagingInfo.pageSize : pageSize; const pi: PagingInfo = currentPage instanceof PagingInfo ? currentPage : new PagingInfo(currentPage, +pageSize); let needRefresh: boolean = false; if (pi.currentPage >= 1 && pi.currentPage <= this.pagingInfo.totalPage) { this.pagingInfo.currentPage = pi.currentPage; needRefresh = true; } else { console.error(`invalid currentPage[${pi.currentPage}], it should be between in [1, ${this.pagingInfo.totalPage}]`); } if (pi.pageSize > 0) { this.pagingInfo.pageSize = pi.pageSize; needRefresh = true; } else { console.error(`invalid pageSize[${pi.pageSize}], it should be greater than 0`); } if (needRefresh) { this.fromAjax(); } } public firstPage(): void { this.changePage(1); } public previousPage(): void { this.changePage(this.pagingInfo.currentPage - 1); } public nextPage(): void { this.changePage(this.pagingInfo.currentPage + 1); } public lastPage(): void { this.changePage(this.pagingInfo.pageSize); } public destroy(): void { super.destroy(); this.http = null; this.sourceRequestOptions = null; this.pagingInfo && this.pagingInfo.unsubscribe(); this.pagingInfo = null; this.filterInfo = null; this.sortInfo = null; this._filterSubject && this._filterSubject.unsubscribe(); this._filterSubject = null; this._sortSubject && this._sortSubject.unsubscribe(); this._sortSubject = null; } } /** * 如果你没有统一的服务端分页、过滤、排序服务,则需要使用这个数据对象,并且请求的提供数据的服务需要自行处理分页、过滤、排序等。 * * Jigsaw暂未实现此功能,如有需要,请给我们[提issue](https://github.com/rdkmaster/jigsaw/issues/new)。 * * 关于Jigsaw数据体系详细介绍,请参考`IComponentData`的说明 */ export class DirectPageableArray extends PageableArray { constructor(public http: HttpClient, public sourceRequestOptions: HttpClientOptions) { super(http, sourceRequestOptions); console.error("unsupported yet!"); } } /** * 在本地分页、排序、过滤的数组。 * * 关于Jigsaw数据体系详细介绍,请参考`IComponentData`的说明 */ export class LocalPageableArray<T> extends ArrayCollection<T> implements IPageable { public pagingInfo: PagingInfo; private _bakData: T[] = []; private _filterSubject = new Subject<DataFilterInfo>(); private _sortSubject = new Subject<DataSortInfo>(); private _filteredData: T[]; public get filteredData(): T[] { return this._filteredData; } public set filteredData(value: T[]) { this._filteredData = value; if (this._filteredData instanceof Array || this._filteredData instanceof ArrayCollection) { this.pagingInfo.totalRecord = this._filteredData.length; } } constructor(source?: T[]) { super(source); this._bakData = source; this.pagingInfo = new PagingInfo(); this.pagingInfo.subscribe(() => { if (!this.filteredData) { return; } this._setDataByPageInfo(); this.refresh(); }); this._initSubjects(); } public fromArray(source: T[]): ArrayCollection<T> { this._bakData = source; this.filteredData = source; this.firstPage(); return this; } /** * @internal * @param item * @param {string} keyword * @param {any[]} fields * @returns {boolean} */ public static filterItemByKeyword(item: any, keyword: string, fields: any[]): boolean { if (typeof item == 'string') { return item.toLowerCase().includes(keyword.toLowerCase()) } else if (fields) { return fields.find(field => { const value: string = !item || item[field] === undefined || item[field] === null ? '' : item[field].toString(); return value.toLowerCase().includes(keyword.toLowerCase()) }) } else { return false } } private _initSubjects(): void { this._filterSubject.debounceTime(300).subscribe(filter => { this.filteredData = this._bakData.filter(item => LocalPageableArray.filterItemByKeyword(item, filter.key, filter.field)); this.firstPage(); }); this._sortSubject.debounceTime(300).subscribe((sortInfo: DataSortInfo) => { const orderFlag = sortInfo.order == SortOrder.asc ? 1 : -1; if (sortInfo.as == SortAs.number) { this.filteredData.sort((a, b) => orderFlag * (Number(sortInfo.field ? a[sortInfo.field] : a) - Number(sortInfo.field ? b[sortInfo.field] : b))); } else { this.filteredData.sort((a, b) => orderFlag * String(sortInfo.field ? a[sortInfo.field] : a).localeCompare(String(sortInfo.field ? b[sortInfo.field] : b))); } this.firstPage(); }) } public filter(callbackfn: (value: any, index: number, array: any[]) => any, context?: any): any; public filter(term: string, fields?: string[] | number[]): void; public filter(term: DataFilterInfo): void; /** * @internal */ public filter(term, fields?: string[] | number[]): void { if(!this._bakData) return; if (term instanceof Function) { this.filteredData = this._bakData.filter(term.bind(fields)); this.firstPage(); } else { const pfi = term instanceof DataFilterInfo ? term : new DataFilterInfo(term, fields); this._filterSubject.next(pfi); } } public sort(compareFn?: (a: any, b: any) => number): any; public sort(as: SortAs, order: SortOrder, field?: string | number): void; public sort(sort: DataSortInfo): void; /** * @internal */ public sort(as, order?: SortOrder, field?: string | number): void { if (!this.filteredData) return; if (as instanceof Function) { this.filteredData.sort(as); this.firstPage(); } const psi = as instanceof DataSortInfo ? as : new DataSortInfo(as, order, field); this._sortSubject.next(psi); } public changePage(currentPage: number, pageSize?: number): void; public changePage(info: PagingInfo): void; /** * @internal */ public changePage(currentPage, pageSize?: number): void { if (!this.filteredData) { return; } if (!isNaN(pageSize) && +pageSize > 0) { this.pagingInfo.pageSize = pageSize; } let cp: number = 0; if (currentPage instanceof PagingInfo) { this.pagingInfo.pageSize = currentPage.pageSize; cp = currentPage.currentPage; } else if (!isNaN(+currentPage)) { cp = +currentPage; } if (cp >= 1 && cp <= this.pagingInfo.totalPage) { this.pagingInfo.currentPage = cp; } else { console.error(`invalid currentPage[${cp}], it should be between in [1, ${this.pagingInfo.totalPage}]`); } } private _setDataByPageInfo() { if (this.pagingInfo.pageSize == Infinity) { super.fromArray(this.filteredData); } else { const begin = (this.pagingInfo.currentPage - 1) * this.pagingInfo.pageSize; const end = this.pagingInfo.currentPage * this.pagingInfo.pageSize < this.pagingInfo.totalRecord ? this.pagingInfo.currentPage * this.pagingInfo.pageSize : this.pagingInfo.totalRecord; super.fromArray(this.filteredData.slice(begin, end)); } } public firstPage(): void { this.changePage(1); } public previousPage(): void { this.changePage(this.pagingInfo.currentPage - 1); } public nextPage(): void { this.changePage(this.pagingInfo.currentPage + 1); } public lastPage(): void { this.changePage(this.pagingInfo.totalPage); } public destroy() { super.destroy(); this._filterSubject && this._filterSubject.unsubscribe(); this._sortSubject && this._sortSubject.unsubscribe(); this.pagingInfo && this.pagingInfo.unsubscribe(); this._bakData = null; this.filteredData = null; this.pagingInfo = null; this._filterSubject = null; this._sortSubject = null; } }