/// import utils = require('./Utils'); import context = require('./Context'); export class List { protected context:context.Context; protected utils:utils.Utils; constructor(context:context.Context) { this.context = context; this.utils = utils.$get(context); this.numberComparator = this.numberComparator.bind(this); this.stringComparator = this.stringComparator.bind(this); } /** * TODO Use utils getProperty * @param {string} property * @returns {function(object)} */ propertyExtractor(property) { return (item, options) => { return property ? ((item && item[property]) || null) : item; }; } /** * Non-string types are converted to string * Non-string types are extracted as an empty string if they could be converted to false * If no options.sortBy given the item itself is extracted * Compares strings: * - if (a is less than b) return -1; * - if (a is greater than b) return 1; * - else (a must be equal to b) return 0; * Exceptions in will be suppressed, if any - a is assumed to be less than b */ stringComparator(a:string, b:string, options?:IListComparatorOptions):number { return this.utils.parseString(a).localeCompare(this.utils.parseString(b)); } /** * Non-numeric types are extracted as 0 if they could be converted to false * Objects that could not be converted to number are extracted as 0 * If no options.sortBy given the item itself is extracted * See parseFloat for more info * Compares numbers: * - if (a is less than b) return -1; * - if (a is greater than b) return 1; * - else (a must be equal to b) return 0; * Function does not check types * Exceptions in will be suppressed, if any - a is assumed to be less than b */ numberComparator(a:any, b:any, options?:IListComparatorOptions):number { return (this.utils.parseNumber(a) - this.utils.parseNumber(b)); } /** * Function extracts (using _extractFn_ option) values of a property (_sortBy_ option) and compares them using * compare function (_compareFn_ option, by default Helper.stringComparator) * Merged options are provided to _extractFn_ and _compareFn_ * TODO Check memory leaks for all that options links */ comparator(options?:IListComparatorOptions):(item1:any, item2:any) => number { options = this.utils.extend({ extractFn: this.propertyExtractor((options && options.sortBy) || null).bind(this), compareFn: this.stringComparator.bind(this) }, options); return (item1:any, item2:any):number => { return options.compareFn(options.extractFn(item1, options), options.extractFn(item2, options), options); }; } equalsFilter(obj:any, options?:IListFilterOptions):boolean { return (options.condition === obj); } /** * @param {string} obj * @param {IListFilterOptions} options * @returns {boolean} */ containsFilter(obj:any, options?:IListFilterOptions):boolean { return (obj && obj.toString().indexOf(options.condition) > -1); } regexpFilter(obj, options?:IListFilterOptions):boolean { if (!(options.condition instanceof RegExp)) throw new Error('Condition must be an instance of RegExp'); return (options.condition.test(obj)); } /** * Function extracts (using `extractFn` option) values of a property (`filterBy` option) and filters them using * compare function (`filterFn` option, by default Helper.equalsFilter) * Merged options are provided to `extractFn` and `compareFn` * Set `filterBy` to null to force `propertyExtractor` to return object itself * TODO Check memory leaks for all that options links */ filter(filterConfigs:IListFilterOptions[]):(item:any) => boolean { var self = this; filterConfigs = (filterConfigs || []).map((opt) => { return this.utils.extend({ condition: '', extractFn: self.propertyExtractor((opt && opt.filterBy) || null).bind(this), filterFn: self.equalsFilter.bind(this) }, opt); }); return (item):boolean => { return filterConfigs.reduce((pass, opt) => { if (!pass || !opt.condition) return pass; return opt.filterFn(opt.extractFn(item, opt), opt); }, true); }; } } export function $get(context:context.Context):List { return context.createSingleton('List', ()=> { return new List(context); }); } export interface IListComparatorOptions { sortBy?:string; extractFn?:(object:any, options?:IListComparatorOptions|any)=>any; compareFn?:(a:any, b:any, options?:IListComparatorOptions|any)=>number; } export interface IListFilterOptions { filterBy?:string; condition?:any|string|RegExp; extractFn?:(object:any, options?:IListFilterOptions|any)=>any; filterFn?:(object:any, options?:IListFilterOptions|any)=>any; }