web/elements: only render form once instance is loaded (#5049)

* web/elements: only render form once instance is loaded

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use radio for transport

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only wait for instance to be loaded if set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add hook to load additional data in form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make send an abstract function instead of attribute

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* ensure form is updated after data is loaded

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove until for select and multi-selects in forms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't use until for file uploads

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove last until from form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove deprecated import

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* prevent form double load, add error handling for PreventFormSubmit

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix double creation of inner element in proxy form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make PreventFormSubmit work correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-03-23 14:05:14 +01:00
committed by GitHub
parent 20522558fe
commit 14f0034a0a
29 changed files with 900 additions and 995 deletions

View File

@ -7,7 +7,7 @@ import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { customElement, property, state } from "lit/decorators.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -22,7 +22,7 @@ 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) {}
constructor(public message: string, public element?: HorizontalFormElement) {}
}
export class APIError extends Error {
@ -36,16 +36,15 @@ export interface KeyUnknown {
}
@customElement("ak-form")
export class Form<T> extends AKElement {
export abstract class Form<T> extends AKElement {
abstract send(data: T): Promise<unknown>;
viewportCheck = true;
@property()
successMessage = "";
@property()
send!: (data: T) => Promise<unknown>;
@property({ attribute: false })
@state()
nonFieldErrors?: string[];
static get styles(): CSSResult[] {
@ -177,17 +176,15 @@ export class Form<T> extends AKElement {
json[element.name] = inputElement.checked;
} else if (inputElement.tagName.toLowerCase() === "ak-search-select") {
const select = inputElement as unknown as SearchSelect<unknown>;
let value: unknown;
try {
value = select.toForm();
} catch {
console.debug("authentik/form: SearchSelect.value error");
return;
const value = select.toForm();
json[element.name] = value;
} catch (exc) {
if (exc instanceof PreventFormSubmit) {
throw new PreventFormSubmit(exc.message, element);
}
throw exc;
}
if (value instanceof PreventFormSubmit) {
throw new Error(value.message);
}
json[element.name] = value;
} else {
this.serializeFieldRecursive(inputElement, inputElement.value, json);
}
@ -215,30 +212,27 @@ export class Form<T> extends AKElement {
parent[nameElements[nameElements.length - 1]] = value;
}
submit(ev: Event): Promise<unknown> | undefined {
async submit(ev: Event): Promise<unknown | undefined> {
ev.preventDefault();
const data = this.serializeForm();
if (!data) {
return;
}
return this.send(data)
.then((r) => {
showMessage({
level: MessageLevel.success,
message: this.getSuccessMessage(),
});
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
return r;
})
.catch(async (ex: Error | ResponseError) => {
if (!(ex instanceof ResponseError)) {
throw ex;
}
try {
const data = this.serializeForm();
if (!data) {
return;
}
const response = await this.send(data);
showMessage({
level: MessageLevel.success,
message: this.getSuccessMessage(),
});
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
return response;
} catch (ex) {
if (ex instanceof ResponseError) {
let msg = ex.response.statusText;
if (ex.response.status > 399 && ex.response.status < 500) {
const errorMessage: ValidationError = await ex.response.json();
@ -277,9 +271,14 @@ export class Form<T> extends AKElement {
message: msg,
level: MessageLevel.error,
});
// rethrow the error so the form doesn't close
throw ex;
});
}
if (ex instanceof PreventFormSubmit && ex.element) {
ex.element.errorMessages = [ex.message];
ex.element.invalid = true;
}
// rethrow the error so the form doesn't close
throw ex;
}
}
renderForm(): TemplateResult {