web: provide a search for the dual list multiselect
**This commit** - Includes a new widget that represents the basic, Patternfly-designed search bar. It just emits events of search request updates. - Changes the definition of a data provider to take an optional search string. - Changes the handler in the *independent* layer so that it catches search requests and those requests work on the "selected" collection. - Changes the handler of the `authentik` interface layer so that it catches search requests and those requests are sent to the data provider. - Provides a debounce function for the `authentik` interface layer to not hammer the Django instance too much. - Updates the data providers in the example for `OutpostForm` to handle search requests. - Provides a property in the `authentik` interface layer so that the debounce can be tuned.
This commit is contained in:
parent
51e89ce916
commit
10999630e5
|
@ -39,11 +39,11 @@ interface ProviderData {
|
|||
}
|
||||
|
||||
const api = () => new ProvidersApi(DEFAULT_CONFIG);
|
||||
const providerListArgs = (page: number) => ({
|
||||
const providerListArgs = (page: number, search = "") => ({
|
||||
ordering: "name",
|
||||
applicationIsnull: false,
|
||||
pageSize: 20,
|
||||
search: "",
|
||||
search: search,
|
||||
page,
|
||||
});
|
||||
|
||||
|
@ -64,17 +64,17 @@ const provisionMaker = (results: ProviderData) => ({
|
|||
options: results.results.map(dualSelectPairMaker),
|
||||
});
|
||||
|
||||
const proxyListFetch = async (page: number) =>
|
||||
provisionMaker(await api().providersProxyList(providerListArgs(page)));
|
||||
const proxyListFetch = async (page: number, search = "") =>
|
||||
provisionMaker(await api().providersProxyList(providerListArgs(page, search)));
|
||||
|
||||
const ldapListFetch = async (page: number) =>
|
||||
provisionMaker(await api().providersLdapList(providerListArgs(page)));
|
||||
const ldapListFetch = async (page: number, search = "") =>
|
||||
provisionMaker(await api().providersLdapList(providerListArgs(page, search)));
|
||||
|
||||
const radiusListFetch = async (page: number) =>
|
||||
provisionMaker(await api().providersRadiusList(providerListArgs(page)));
|
||||
const radiusListFetch = async (page: number, search = "") =>
|
||||
provisionMaker(await api().providersRadiusList(providerListArgs(page, search)));
|
||||
|
||||
const racListProvider = async (page: number) =>
|
||||
provisionMaker(await api().providersRacList(providerListArgs(page)));
|
||||
const racListProvider = async (page: number, search = "") =>
|
||||
provisionMaker(await api().providersRacList(providerListArgs(page, search)));
|
||||
|
||||
function providerProvider(type: OutpostTypeEnum): DataProvider {
|
||||
switch (type) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import { PropertyValues, html } from "lit";
|
|||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import type { Ref } from "lit/directives/ref.js";
|
||||
import { debounce } from "@goauthentik/elements/utils/debounce";
|
||||
|
||||
import type { Pagination } from "@goauthentik/api";
|
||||
|
||||
|
@ -26,8 +27,9 @@ import type { DataProvider, DualSelectPair } from "./types";
|
|||
|
||||
@customElement("ak-dual-select-provider")
|
||||
export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
|
||||
// A function that takes a page and returns the DualSelectPair[] collection with which to update
|
||||
// the "Available" pane.
|
||||
/** A function that takes a page and returns the DualSelectPair[] collection with which to update
|
||||
* the "Available" pane.
|
||||
*/
|
||||
@property({ type: Object })
|
||||
provider!: DataProvider;
|
||||
|
||||
|
@ -40,6 +42,10 @@ export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
|
|||
@property({ attribute: "selected-label" })
|
||||
selectedLabel = msg("Selected options");
|
||||
|
||||
/** The remote lists are debounced by definition. This is the interval for the debounce. */
|
||||
@property({ attribute: "search-delay", type: Number })
|
||||
searchDelay = 250;
|
||||
|
||||
@state()
|
||||
private options: DualSelectPair[] = [];
|
||||
|
||||
|
@ -58,12 +64,14 @@ export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
|
|||
constructor() {
|
||||
super();
|
||||
setTimeout(() => this.fetch(1), 0);
|
||||
this.onNav = this.onNav.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
// Notify AkForElementHorizontal how to handle this thing.
|
||||
this.dataset.akControl = "true";
|
||||
this.onNav = this.onNav.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onSearch = this.onSearch.bind(this);
|
||||
this.addCustomListener("ak-pagination-nav-to", this.onNav);
|
||||
this.addCustomListener("ak-dual-select-change", this.onChange);
|
||||
this.addCustomListener("ak-dual-select-search", this.onSearch);
|
||||
}
|
||||
|
||||
onNav(event: Event) {
|
||||
|
@ -80,7 +88,23 @@ export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
|
|||
this.selected = event.detail.value;
|
||||
}
|
||||
|
||||
doSearch(search: string) {
|
||||
this.pagination = undefined;
|
||||
this.fetch(undefined, search);
|
||||
}
|
||||
|
||||
onSearch(event: Event) {
|
||||
if (!(event instanceof CustomEvent)) {
|
||||
throw new Error(`Expecting a CustomEvent for change, received ${event} instead`);
|
||||
}
|
||||
this.doSearch(event.detail);
|
||||
}
|
||||
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("searchDelay")) {
|
||||
this.doSearch = debounce(this.doSearch.bind(this), this.searchDelay);
|
||||
}
|
||||
|
||||
if (changedProperties.has("provider")) {
|
||||
this.pagination = undefined;
|
||||
if (changedProperties.get("provider")) {
|
||||
|
@ -91,13 +115,13 @@ export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
|
|||
}
|
||||
}
|
||||
|
||||
async fetch(page?: number) {
|
||||
async fetch(page?: number, search = "") {
|
||||
if (this.isLoading) {
|
||||
return;
|
||||
}
|
||||
this.isLoading = true;
|
||||
const goto = page ?? this.pagination?.current ?? 1;
|
||||
const data = await this.provider(goto);
|
||||
const data = await this.provider(goto, search);
|
||||
this.pagination = data.pagination;
|
||||
this.options = data.options;
|
||||
this.isLoading = false;
|
||||
|
|
|
@ -234,11 +234,11 @@ export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKE
|
|||
case "ak-dual-list-selected-search":
|
||||
return this.handleSelectedSearch(event.detail.value);
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
handleAvailbleSearch(value: string) {
|
||||
console.log(value);
|
||||
handleAvailableSearch(value: string) {
|
||||
this.dispatchCustomEvent("ak-dual-select-search", value);
|
||||
}
|
||||
|
||||
handleSelectedSearch(value: string) {
|
||||
|
|
|
@ -16,7 +16,7 @@ export type DataProvision = {
|
|||
options: DualSelectPair[];
|
||||
};
|
||||
|
||||
export type DataProvider = (page: number) => Promise<DataProvision>;
|
||||
export type DataProvider = (page: number, search?: string) => Promise<DataProvision>;
|
||||
|
||||
export interface SearchbarEvent extends CustomEvent {
|
||||
detail: {
|
||||
|
|
Reference in a new issue