Fix filters resetting user input on redraw

This commit is contained in:
Zaid Abdulla committed 2021-09-16 00:17:21 -07:00
1 parent 595f0eddf1
commit 53bb4d3552
10 files changed
+129 -96

No files matched your search

+17 -5
View File
@@ -51,11 +51,8 @@ export default class Autocomplete {
el.addEventListener("focus", () => { el.addEventListener("focus", () => {
this.element = el; this.element = el;
const domRect = el.getBoundingClientRect();
this.container.style.left = `${domRect.left + window.pageXOffset}px`;
this.container.style.width = `${domRect.width}px`;
this.container.style.top = `${domRect.bottom + window.pageYOffset}px`;
this.update(); this.update();
this.reposition();
}); });
el.addEventListener("blur", () => { el.addEventListener("blur", () => {
@@ -71,7 +68,8 @@ export default class Autocomplete {
} else if (e.key === "Enter") { } else if (e.key === "Enter") {
if (this.default != null) { if (this.default != null) {
el.value = this.default; el.value = this.default;
e.preventDefault(); e.stopImmediatePropagation();
el.dispatchEvent(new InputEvent("input"));
this.update(); this.update();
} }
} else if (e.key === "ArrowDown") { } else if (e.key === "ArrowDown") {
@@ -93,6 +91,19 @@ export default class Autocomplete {
}); });
} }
public reposition(): void {
if (!this.element) return;
const domRect = this.element.getBoundingClientRect();
if (!domRect.width) {
// Element has been removed
if (this.visible) this.hide();
return;
}
this.container.style.left = `${domRect.left + window.pageXOffset}px`;
this.container.style.width = `${domRect.width}px`;
this.container.style.top = `${domRect.bottom + window.pageYOffset}px`;
}
private hide(): void { private hide(): void {
this.container.style.opacity = "0"; this.container.style.opacity = "0";
this.visible = false; this.visible = false;
@@ -159,6 +170,7 @@ export default class Autocomplete {
e.addEventListener("mousedown", (ev) => { e.addEventListener("mousedown", (ev) => {
ev.preventDefault(); ev.preventDefault();
el.value = suggestion.value; el.value = suggestion.value;
el.dispatchEvent(new InputEvent("input"));
if (this.element === el) this.update(); if (this.element === el) this.update();
}); });
this.container.appendChild(e); this.container.appendChild(e);
+5 -4
View File
@@ -328,10 +328,11 @@ export const component: ClosureComponent = (): Component => {
if (window.authorizer.hasAccess("devices", 3)) if (window.authorizer.hasAccess("devices", 3))
attrs["actionsCallback"] = renderActions; attrs["actionsCallback"] = renderActions;
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "devices"; resource: "devices",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing devices"), m("h1", "Listing devices"),
+5 -4
View File
@@ -200,10 +200,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "faults"; resource: "faults",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing faults"), m("h1", "Listing faults"),
+5 -4
View File
@@ -316,10 +316,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "files"; resource: "files",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing files"), m("h1", "Listing files"),
+72 -59
View File
@@ -17,7 +17,7 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import m, { ClosureComponent, Component } from "mithril"; import m, { ClosureComponent } from "mithril";
import { parse, stringify, map } from "../lib/common/expression-parser"; import { parse, stringify, map } from "../lib/common/expression-parser";
import memoize from "../lib/common/memoize"; import memoize from "../lib/common/memoize";
import Autocomplete from "./autocomplete-compnent"; import Autocomplete from "./autocomplete-compnent";
@@ -82,9 +82,9 @@ function stringifyFilter(f: Expression): string {
return stringify(f); return stringify(f);
} }
const splitFilter = memoize((filter) => { function splitFilter(filter: string): string[] {
if (!filter) return [""]; if (!filter) return [""];
const list = []; const list: string[] = [];
const f = parse(filter); const f = parse(filter);
if (Array.isArray(f) && f[0] === "AND") if (Array.isArray(f) && f[0] === "AND")
for (const ff of f.slice(1)) list.push(stringifyFilter(ff)); for (const ff of f.slice(1)) list.push(stringifyFilter(ff));
@@ -92,69 +92,82 @@ const splitFilter = memoize((filter) => {
list.push(""); list.push("");
return list; return list;
}); }
interface Attrs {
resource: string;
filter: string;
onChange: (filter: string) => void;
}
const component: ClosureComponent<Attrs> = (initialVnode) => {
let filterList = splitFilter(initialVnode.attrs.filter);
let filterInvalid = 0;
let filterTouched = false;
let attrs: Attrs = initialVnode.attrs;
function onChange(): void {
filterTouched = false;
filterInvalid = 0;
filterList = filterList.filter((f) => f);
const list = filterList.map((f, idx) => {
try {
return parseFilter(attrs.resource, f);
} catch (err) {
filterInvalid |= 1 << idx;
}
return null;
});
filterList.push("");
if (filterInvalid) {
m.redraw();
return;
}
if (list.length === 0) attrs.onChange("");
else if (list.length > 1) attrs.onChange(stringify(["AND", ...list]));
else attrs.onChange(stringify(list[0]));
}
const component: ClosureComponent = (): Component => {
return { return {
onupdate: (vnode) => {
getAutocomplete(vnode.attrs.resource).reposition();
},
view: (vnode) => { view: (vnode) => {
if ( if (attrs.filter !== vnode.attrs.filter) {
!vnode.state["filterList"] || filterInvalid = 0;
vnode.attrs["filter"] !== vnode.state["filter"] filterList = splitFilter(vnode.attrs.filter);
) {
vnode.state["filterInvalid"] = 0;
vnode.state["filter"] = vnode.attrs["filter"];
vnode.state["filterList"] = splitFilter(vnode.attrs["filter"]);
} }
function onChange(): void { attrs = vnode.attrs;
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"]) { return m("div.filter", [
delete vnode.state["filter"]; m("b", "Filter"),
if (filter.length === 0) { ...filterList.map((fltr, idx) => {
vnode.attrs["onChange"](""); return m("input", {
} else { type: "text",
if (filter.length > 1) filter = ["AND"].concat(filter); class: `${(filterInvalid >> idx) & 1 ? "error" : ""}`,
else filter = filter[0]; value: fltr,
vnode.attrs["onChange"](stringify(filter)); oninput: (e) => {
} e.redraw = false;
} filterList[idx] = e.target.value;
} filterTouched = true;
},
oncreate: (vn) => {
const el = vn.dom as HTMLInputElement;
getAutocomplete(vnode.attrs.resource).attach(el);
return m( el.addEventListener("blur", () => {
"div.filter", if (filterTouched) onChange();
[m("b", "Filter")].concat( });
vnode.state["filterList"].map((fltr, idx) => {
return m("input", { el.addEventListener("keydown", (e) => {
type: "text", if (e.key === "Enter" && filterTouched) onChange();
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 as HTMLInputElement
);
},
});
})
)
);
}, },
}; };
}; };
+5 -4
View File
@@ -365,10 +365,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "permissions"; resource: "permissions",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing permissions"), m("h1", "Listing permissions"),
+5 -4
View File
@@ -427,10 +427,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "presets"; resource: "presets",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing presets"), m("h1", "Listing presets"),
+5 -4
View File
@@ -346,10 +346,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "provisions"; resource: "provisions",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing provisions"), m("h1", "Listing provisions"),
+5 -4
View File
@@ -401,10 +401,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "users"; resource: "users",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing users"), m("h1", "Listing users"),
+5 -4
View File
@@ -346,10 +346,11 @@ export const component: ClosureComponent = (): Component => {
}; };
} }
const filterAttrs = {}; const filterAttrs = {
filterAttrs["resource"] = "virtualParameters"; resource: "virtualParameters",
filterAttrs["filter"] = vnode.attrs["filter"]; filter: vnode.attrs["filter"],
filterAttrs["onChange"] = onFilterChanged; onChange: onFilterChanged,
};
return [ return [
m("h1", "Listing virtual parameters"), m("h1", "Listing virtual parameters"),