@stnekroman/tstools
Version:
Set of handy tools for TypeScript development
124 lines (110 loc) • 3.88 kB
text/typescript
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;
}
}