Implement context mechanism to pass default attributes to descendant components.
119 lines
3.3 KiB
JavaScript
119 lines
3.3 KiB
JavaScript
"use strict";
|
|
|
|
import m from "mithril";
|
|
import { parse, stringify, map } from "../lib/common/expression-parser";
|
|
import memoize from "../lib/common/memoize";
|
|
import Autocomplete from "./autocomplete-compnent";
|
|
import * as smartQuery from "./smart-query";
|
|
import { filterToMongoQuery } from "../lib/ui/mongodb-functions";
|
|
|
|
const getAutocomplete = memoize(resource => {
|
|
const labels = smartQuery.getLabels(resource);
|
|
const autocomplete = new Autocomplete("autocomplete", (txt, cb) => {
|
|
txt = txt.toLowerCase();
|
|
cb(labels.filter(s => s.toLowerCase().includes(txt)).map(s => `${s}: `));
|
|
});
|
|
return autocomplete;
|
|
});
|
|
|
|
function parseFilter(resource, f) {
|
|
let exp;
|
|
if (/^[\s0-9a-zA-Z]+:/.test(f)) {
|
|
const k = f.split(":", 1)[0];
|
|
const v = f.slice(k.length + 1).trim();
|
|
exp = ["FUNC", "Q", k.trim(), v];
|
|
} else {
|
|
exp = parse(f);
|
|
}
|
|
|
|
// Throw exception if invalid Mongo query
|
|
filterToMongoQuery(
|
|
map(exp, e => {
|
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "Q")
|
|
return smartQuery.unpack(resource, e[2], e[3]);
|
|
return e;
|
|
})
|
|
);
|
|
|
|
return exp;
|
|
}
|
|
|
|
function stringifyFilter(f) {
|
|
if (Array.isArray(f) && f[0] === "FUNC" && f[1] === "Q")
|
|
return `${f[2]}: ${f[3]}`;
|
|
return stringify(f);
|
|
}
|
|
|
|
const splitFilter = memoize(filter => {
|
|
if (!filter) return [""];
|
|
const list = [];
|
|
const f = parse(filter);
|
|
if (Array.isArray(f) && f[0] === "AND")
|
|
for (const ff of f.slice(1)) list.push(stringifyFilter(ff));
|
|
else list.push(stringifyFilter(f));
|
|
|
|
list.push("");
|
|
return list;
|
|
});
|
|
|
|
export default function component() {
|
|
return {
|
|
view: vnode => {
|
|
if (
|
|
!vnode.state.filterList ||
|
|
vnode.attrs.filter !== vnode.state.filter
|
|
) {
|
|
vnode.state.filterInvalid = 0;
|
|
vnode.state.filter = vnode.attrs.filter;
|
|
vnode.state.filterList = splitFilter(vnode.attrs.filter);
|
|
}
|
|
|
|
function onChange() {
|
|
vnode.state.filterInvalid = 0;
|
|
vnode.state.filterList = vnode.state.filterList.filter(f => f);
|
|
let filter = vnode.state.filterList.map((f, idx) => {
|
|
try {
|
|
return parseFilter(vnode.attrs.resource, f);
|
|
} catch (err) {
|
|
vnode.state.filterInvalid |= 1 << idx;
|
|
}
|
|
return null;
|
|
});
|
|
vnode.state.filterList.push("");
|
|
|
|
if (!vnode.state.filterInvalid) {
|
|
delete vnode.state.filter;
|
|
if (filter.length === 0) {
|
|
vnode.attrs.onChange("");
|
|
} else {
|
|
if (filter.length > 1) filter = ["AND"].concat(filter);
|
|
else filter = filter[0];
|
|
vnode.attrs.onChange(stringify(filter));
|
|
}
|
|
}
|
|
}
|
|
|
|
return m(
|
|
"div.filter",
|
|
[m("b", "Filter")].concat(
|
|
vnode.state.filterList.map((fltr, idx) => {
|
|
return m("input", {
|
|
type: "text",
|
|
class: `${(vnode.state.filterInvalid >> idx) & 1 ? "error" : ""}`,
|
|
value: fltr,
|
|
onchange: e => {
|
|
vnode.state.filterList = vnode.state.filterList.slice();
|
|
vnode.state.filterList[idx] = e.target.value.trim();
|
|
onChange();
|
|
},
|
|
oncreate: vn => {
|
|
getAutocomplete(vnode.attrs.resource).attach(vn.dom);
|
|
}
|
|
});
|
|
})
|
|
)
|
|
);
|
|
}
|
|
};
|
|
}
|