Fix filters resetting user input on redraw
This commit is contained in:
1 parent
595f0eddf1
commit
53bb4d3552
10 files changed
+117
-84
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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"),
|
||||||
|
|||||||
+60
-47
@@ -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;
|
||||||
});
|
}
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
interface Attrs {
|
||||||
return {
|
resource: string;
|
||||||
view: (vnode) => {
|
filter: string;
|
||||||
if (
|
onChange: (filter: string) => void;
|
||||||
!vnode.state["filterList"] ||
|
}
|
||||||
vnode.attrs["filter"] !== vnode.state["filter"]
|
|
||||||
) {
|
const component: ClosureComponent<Attrs> = (initialVnode) => {
|
||||||
vnode.state["filterInvalid"] = 0;
|
let filterList = splitFilter(initialVnode.attrs.filter);
|
||||||
vnode.state["filter"] = vnode.attrs["filter"];
|
let filterInvalid = 0;
|
||||||
vnode.state["filterList"] = splitFilter(vnode.attrs["filter"]);
|
let filterTouched = false;
|
||||||
}
|
let attrs: Attrs = initialVnode.attrs;
|
||||||
|
|
||||||
function onChange(): void {
|
function onChange(): void {
|
||||||
vnode.state["filterInvalid"] = 0;
|
filterTouched = false;
|
||||||
vnode.state["filterList"] = vnode.state["filterList"].filter((f) => f);
|
filterInvalid = 0;
|
||||||
let filter = vnode.state["filterList"].map((f, idx) => {
|
filterList = filterList.filter((f) => f);
|
||||||
|
const list = filterList.map((f, idx) => {
|
||||||
try {
|
try {
|
||||||
return parseFilter(vnode.attrs["resource"], f);
|
return parseFilter(attrs.resource, f);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vnode.state["filterInvalid"] |= 1 << idx;
|
filterInvalid |= 1 << idx;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
vnode.state["filterList"].push("");
|
filterList.push("");
|
||||||
|
|
||||||
if (!vnode.state["filterInvalid"]) {
|
if (filterInvalid) {
|
||||||
delete vnode.state["filter"];
|
m.redraw();
|
||||||
if (filter.length === 0) {
|
return;
|
||||||
vnode.attrs["onChange"]("");
|
|
||||||
} else {
|
|
||||||
if (filter.length > 1) filter = ["AND"].concat(filter);
|
|
||||||
else filter = filter[0];
|
|
||||||
vnode.attrs["onChange"](stringify(filter));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (list.length === 0) attrs.onChange("");
|
||||||
|
else if (list.length > 1) attrs.onChange(stringify(["AND", ...list]));
|
||||||
|
else attrs.onChange(stringify(list[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return m(
|
return {
|
||||||
"div.filter",
|
onupdate: (vnode) => {
|
||||||
[m("b", "Filter")].concat(
|
getAutocomplete(vnode.attrs.resource).reposition();
|
||||||
vnode.state["filterList"].map((fltr, idx) => {
|
},
|
||||||
|
view: (vnode) => {
|
||||||
|
if (attrs.filter !== vnode.attrs.filter) {
|
||||||
|
filterInvalid = 0;
|
||||||
|
filterList = splitFilter(vnode.attrs.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = vnode.attrs;
|
||||||
|
|
||||||
|
return m("div.filter", [
|
||||||
|
m("b", "Filter"),
|
||||||
|
...filterList.map((fltr, idx) => {
|
||||||
return m("input", {
|
return m("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
class: `${
|
class: `${(filterInvalid >> idx) & 1 ? "error" : ""}`,
|
||||||
(vnode.state["filterInvalid"] >> idx) & 1 ? "error" : ""
|
|
||||||
}`,
|
|
||||||
value: fltr,
|
value: fltr,
|
||||||
onchange: (e) => {
|
oninput: (e) => {
|
||||||
vnode.state["filterList"] = vnode.state["filterList"].slice();
|
e.redraw = false;
|
||||||
vnode.state["filterList"][idx] = e.target.value.trim();
|
filterList[idx] = e.target.value;
|
||||||
onChange();
|
filterTouched = true;
|
||||||
},
|
},
|
||||||
oncreate: (vn) => {
|
oncreate: (vn) => {
|
||||||
getAutocomplete(vnode.attrs["resource"]).attach(
|
const el = vn.dom as HTMLInputElement;
|
||||||
vn.dom as HTMLInputElement
|
getAutocomplete(vnode.attrs.resource).attach(el);
|
||||||
);
|
|
||||||
|
el.addEventListener("blur", () => {
|
||||||
|
if (filterTouched) onChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" && filterTouched) onChange();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
}),
|
||||||
)
|
]);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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"),
|
||||||
|
|||||||
@@ -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
@@ -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"),
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
Reference in new issue
Block a user