@stnekroman/tstools
Version:
Set of handy tools for TypeScript development
121 lines (105 loc) • 4.41 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, T[keyof 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, T[keyof T] & SortableType>) : Sorter<T> {
return new Sorter(extractor);
}
private constructor(extractorOrFieldname : Functions.MapFunction<T, T[keyof 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, T[keyof 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;
}
}