From 9564894eda10b8ba9b1106edcd705c7df6b8126c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 2 Jan 2023 10:26:52 +0100 Subject: [PATCH] web/elements: trigger search select data update on connected callback Signed-off-by: Jens Langhammer --- .../identification/IdentificationStageForm.ts | 31 +++++++------ web/src/elements/SearchSelect.ts | 45 +++++++++++++------ web/src/elements/charts/Chart.ts | 39 +++++++++++----- web/src/elements/forms/Form.ts | 13 +++++- 4 files changed, 89 insertions(+), 39 deletions(-) diff --git a/web/src/admin/stages/identification/IdentificationStageForm.ts b/web/src/admin/stages/identification/IdentificationStageForm.ts index 737212d96..3103332ea 100644 --- a/web/src/admin/stages/identification/IdentificationStageForm.ts +++ b/web/src/admin/stages/identification/IdentificationStageForm.ts @@ -154,6 +154,24 @@ export class IdentificationStageForm extends ModelForm + +
+ + +
+

+ ${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`} +

+
+ + + + ${t`Source settings`} +
- -
- - -
-

- ${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`} -

-
diff --git a/web/src/elements/SearchSelect.ts b/web/src/elements/SearchSelect.ts index 728378694..dc0f4fb80 100644 --- a/web/src/elements/SearchSelect.ts +++ b/web/src/elements/SearchSelect.ts @@ -1,5 +1,7 @@ +import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { groupBy } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; +import { PreventFormSubmit } from "@goauthentik/elements/forms/Form"; import { t } from "@lingui/macro"; @@ -18,7 +20,7 @@ export class SearchSelect extends AKElement { query?: string; @property({ attribute: false }) - objects: T[] = []; + objects?: T[]; @property({ attribute: false }) selectedObject?: T; @@ -54,17 +56,6 @@ export class SearchSelect extends AKElement { @property({ attribute: false }) selected?: (element: T, elements: T[]) => boolean; - firstUpdated(): void { - this.fetchObjects(this.query).then((objects) => { - this.objects = objects; - this.objects.forEach((obj) => { - if (this.selected && this.selected(obj, this.objects)) { - this.selectedObject = obj; - } - }); - }); - } - @property() emptyOption = "---------"; @@ -82,15 +73,40 @@ export class SearchSelect extends AKElement { this.dropdownContainer = document.createElement("div"); } + toForm(): unknown { + if (!this.objects) { + return new PreventFormSubmit(t`Loading options...`); + } + return this.value(this.selectedObject) || ""; + } + + firstUpdated(): void { + this.updateData(); + } + + updateData(): void { + this.fetchObjects(this.query).then((objects) => { + this.objects = objects; + this.objects.forEach((obj) => { + if (this.selected && this.selected(obj, this.objects || [])) { + this.selectedObject = obj; + } + }); + }); + } + connectedCallback(): void { super.connectedCallback(); this.dropdownContainer = document.createElement("div"); this.dropdownContainer.dataset["managedBy"] = "ak-search-select"; document.body.append(this.dropdownContainer); + this.updateData(); + this.addEventListener(EVENT_REFRESH, this.updateData); } disconnectedCallback(): void { super.disconnectedCallback(); + this.removeEventListener(EVENT_REFRESH, this.updateData); this.dropdownContainer.remove(); } @@ -106,6 +122,9 @@ export class SearchSelect extends AKElement { * the pf-c-dropdown CSS needs to be loaded on the body. */ renderMenu(): void { + if (!this.objects) { + return; + } const pos = this.getBoundingClientRect(); let groupedItems = this.groupBy(this.objects); let shouldRenderGroups = true; @@ -209,7 +228,7 @@ export class SearchSelect extends AKElement { placeholder=${this.placeholder} @input=${(ev: InputEvent) => { this.query = (ev.target as HTMLInputElement).value; - this.firstUpdated(); + this.updateData(); }} @focus=${() => { this.open = true; diff --git a/web/src/elements/charts/Chart.ts b/web/src/elements/charts/Chart.ts index f1c9c42b6..4f2884fa0 100644 --- a/web/src/elements/charts/Chart.ts +++ b/web/src/elements/charts/Chart.ts @@ -78,18 +78,6 @@ export abstract class AKChart extends AKElement { constructor() { super(); - window.addEventListener("resize", () => { - if (this.chart) { - this.chart.resize(); - } - }); - window.addEventListener(EVENT_REFRESH, () => { - this.apiRequest().then((r: T) => { - if (!this.chart) return; - this.chart.data = this.getChartData(r); - this.chart.update(); - }); - }); const matcher = window.matchMedia("(prefers-color-scheme: light)"); const handler = (ev?: MediaQueryListEvent) => { if (ev?.matches || matcher.matches) { @@ -103,6 +91,33 @@ export abstract class AKChart extends AKElement { handler(); } + connectedCallback(): void { + super.connectedCallback(); + window.addEventListener("resize", this.resizeHandler); + this.addEventListener(EVENT_REFRESH, this.refreshHandler); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + window.removeEventListener("resize", this.resizeHandler); + this.removeEventListener(EVENT_REFRESH, this.refreshHandler); + } + + refreshHandler(): void { + this.apiRequest().then((r: T) => { + if (!this.chart) return; + this.chart.data = this.getChartData(r); + this.chart.update(); + }); + } + + resizeHandler(): void { + if (!this.chart) { + return; + } + this.chart.resize(); + } + firstUpdated(): void { this.apiRequest().then((r) => { const canvas = this.shadowRoot?.querySelector("canvas"); diff --git a/web/src/elements/forms/Form.ts b/web/src/elements/forms/Form.ts index e078c08b4..67bafdfd1 100644 --- a/web/src/elements/forms/Form.ts +++ b/web/src/elements/forms/Form.ts @@ -23,6 +23,11 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { ResponseError, ValidationError } from "@goauthentik/api"; +export class PreventFormSubmit { + // Stub class which can be returned by form elements to prevent the form from submitting + constructor(public message: string) {} +} + export class APIError extends Error { constructor(public response: ValidationError) { super(); @@ -162,11 +167,17 @@ export class Form extends AKElement { json[element.name] = element.checked; } else if (element.tagName.toLowerCase() === "ak-search-select") { const select = element as unknown as SearchSelect; + let value: unknown; try { - json[element.name] = select.value(select.selectedObject) || ""; + value = select.toForm(); } catch { console.debug("authentik/form: SearchSelect.value error"); + return; } + if (value instanceof PreventFormSubmit) { + throw new Error(value.message); + } + json[element.name] = value; } else { for (let v = 0; v < values.length; v++) { this.serializeFieldRecursive(element, values[v], json);