UNPKG

@stnekroman/tstools

Version:

Set of handy tools for TypeScript development

124 lines (110 loc) 3.88 kB
import { Functions } from './Functions'; import { Objects } from './Objects'; import { Types } from './Types'; export type SortableType = string | number | Date | boolean; export class Sorter<T> { public static readonly COMPARATORS = { get number() { return (a: number, b: number) => a - b; }, get string() { return (a: string, b: string, stringCompareOptions?: Intl.CollatorOptions) => a.localeCompare(b, undefined, stringCompareOptions); }, get boolean() { return (a: boolean, b: boolean) => { if (!a && b) { return 1; } else if (a && !b) { return -1; } else { return 0; } }; }, get Date() { return (a: Date, b: Date) => { return a.getTime() - b.getTime(); }; }, get null() { return (a: SortableType, b: SortableType) => { if (!Objects.isNotNullOrUndefined(a) && Objects.isNotNullOrUndefined(b)) { return 1; } else if (Objects.isNotNullOrUndefined(a) && !Objects.isNotNullOrUndefined) { return -1; } else { return 0; } }; }, }; private _nullsLast: boolean = true; private _stringCollatorOptions?: Intl.CollatorOptions; private _inverse: boolean = false; private readonly extractor: Functions.MapFunction<T, SortableType>; public static byField<T>(fieldname: keyof T & Types.KeysWithValsOfType<T, SortableType>): Sorter<T> { return new Sorter<T>(fieldname); } public static byExtractor<T>(extractor: Functions.MapFunction<T, SortableType>): Sorter<T> { return new Sorter(extractor); } private constructor( extractorOrFieldname: Functions.MapFunction<T, SortableType> | (keyof T & Types.KeysWithValsOfType<T, SortableType>) ) { if (Objects.isFunction(extractorOrFieldname)) { this.extractor = extractorOrFieldname; } else { this.extractor = Functions.extractor(extractorOrFieldname) as Functions.MapFunction<T, SortableType>; } } public nullsLast(value: boolean): this { this._nullsLast = value; return this; } public inverse(): this { this._inverse = !this._inverse; return this; } public stringCollatorOptions(value: Intl.CollatorOptions): this { this._stringCollatorOptions = value; return this; } public build(nextSort?: Functions.Comparator<T>): Functions.Comparator<T> { return (a: T, b: T): number => { const avalue: SortableType = this.extractor(a); const bvalue: SortableType = this.extractor(b); const result = this.compareValues(avalue, bvalue); if (result === 0 && Objects.isNotNullOrUndefined(nextSort)) { return nextSort(a, b); } return result; }; } private compareValues(avalue: SortableType, bvalue: SortableType): number { const nullCheck: number = this._nullsLast ? Sorter.COMPARATORS.null(avalue, bvalue) : -1 * Sorter.COMPARATORS.null(avalue, bvalue); if (nullCheck !== 0) { return nullCheck; } let result: number; if (Objects.isString(avalue) && Objects.isString(bvalue)) { result = Sorter.COMPARATORS.string(avalue, bvalue, this._stringCollatorOptions); } else if (Objects.isNumeric(avalue) && Objects.isNumeric(bvalue)) { result = Sorter.COMPARATORS.number(avalue, bvalue); } else if (Objects.isBoolean(avalue) && Objects.isBoolean(bvalue)) { result = Sorter.COMPARATORS.boolean(avalue, bvalue); } else if (avalue instanceof Date && bvalue instanceof Date) { result = Sorter.COMPARATORS.Date(avalue, bvalue); } else if (!Objects.isNotNullOrUndefined && !Objects.isNotNullOrUndefined(bvalue)) { result = nullCheck; } else { throw new Error('Not supported combination of types'); } if (this._inverse) { result = -1 * result; } return result; } }