////////////////////////////////////////////////////////////// // Copyright (c) 2017 Ben Jackman // All Rights Reserved // please contact ben@jackman.biz // for licensing inquiries // Created by bjackman @ 6/19/17 6:17 PM ////////////////////////////////////////////////////////////// import * as _ from "lodash"; import {QCol} from "./QTable"; export class HeaderFilters { filters: Array> = [] nonEmptyFilters: Array> = [] dataview: Slick.Data.DataView private refreshWhichFiltersAreNonEmpty() { this.nonEmptyFilters = this.filters.filter((h) => h.filterTxt.length > 0) } constructor(dataview: Slick.Data.DataView) { this.dataview = dataview } filterFn = (item: T, args: any): boolean => { let allows = true for (let i = 0; i < this.nonEmptyFilters.length && allows; i += 1) { const filter = this.nonEmptyFilters[i] allows = filter.allows(item) } return allows } setFilter(key: string, value: string | null) { const colFilter = this.filters.find(f => f.column.field == key) if (colFilter) { if (value == null) { colFilter.set("", true) } else { colFilter.set(value, true) } } } createAndAdd(column: QCol) : HeaderFilter { const f = new HeaderFilter(column, this) this.filters.push(f) return f } clearAll() { this.nonEmptyFilters.forEach(f => f.set("", true)) } clearByPredicate(p: (f: HeaderFilter) => boolean) { this.nonEmptyFilters.filter(p).forEach(f => f.set("", true)) } updated() { this.refreshWhichFiltersAreNonEmpty() this.dataview.refresh() } } type FilterFn = ["number" | "string", (v: string) => boolean] const compOps = { ">=": (a: any, b: any) => a >= b, ">": (a: any, b: any) => a > b, "<": (a: any, b: any) => a < b, "<=": (a: any, b: any) => a <= b, "=": (a: any, b: any) => a === b, "!=": (a: any, b: any) => a !== b, } function parseToFilterFn(ft: string): FilterFn | null { const setRegex = (): FilterFn | null => { let invert = false if (_.startsWith(ft, "!")) { invert = true ft = ft.substr(1) } function f(v: string) { const trimmed = ft.trim().toLowerCase() const m = v.match(trimmed) const res = m != null return invert ? !res : res } return ["string", f] } const re = /^(>=|<=|<|>|=|!=)(.*)/ const m = re.exec(ft) if (m != null) { // debugger const nraw = m[2] const n = _.toNumber(nraw) const op = (compOps as any)[m[1]] if (op != null) { if (isNaN(n)) { const ns = nraw.toLowerCase() return ["string", (v: string) => op(v.toLowerCase(), ns)] } else { return ["number", (v: string) => op(parseFloat(v), n)] } } else { //Should be unreachable return setRegex() } } else { return setRegex() } } export class HeaderFilter { column: QCol container: HeaderFilters filterTxt = "" filterFn: FilterFn | null = null el: HTMLInputElement constructor(column: QCol, container: HeaderFilters) { this.column = column this.container = container this.el = document.createElement("input") this.el.className = "slick-header-input-cell" this.el.addEventListener("keyup", () => { this.set(this.el.value) }) } allows = (item: T): boolean => { let vr: any if (this.column.getFilterValue) { vr = this.column.getFilterValue(item) } else { vr = (item as any)[this.column.field!] } let v: string if (_.isNumber(vr)) { v = "" + vr } else if (_.isString(vr)) { v = vr.toLowerCase() } else { v = "" } if (this.filterFn) { return this.filterFn[1](v) } else { return true } } set = (filterTxt: string, setEl: boolean = false) => { this.filterTxt = filterTxt if (setEl) { this.el.value = filterTxt } this.filterFn = parseToFilterFn(filterTxt.trim()) // if (_.startsWith(ft, ">=")) { // } else if (_.startsWith(ft, ">")) { // const n = parseFloat(ft.substring(1)) // this.filterFn = ["number", (v: string) => parseFloat(v) > n] // } else if (_.startsWith(ft, "<=")) { // const n = parseFloat(ft.substring(2)) // this.filterFn = ["number", (v: string) => parseFloat(v) <= n] // } else if (_.startsWith(ft, "<")) { // const n = parseFloat(ft.substring(1)) // this.filterFn = ["number", (v: string) => parseFloat(v) < n] // } else if (_.startsWith(ft, "=")) { // const n = parseFloat(ft.substring(1)) // this.filterFn = ["number", (v: string) => parseFloat(v) === n] // } else if (_.startsWith(ft, "!=")) { // const n = parseFloat(ft.substring(2)) // this.filterFn = ["number", (v: string) => parseFloat(v) !== n] // } else { // } this.container.updated() } }