Add support for API path variables

This commit is contained in:
Jeremy Stretch 2024-02-06 11:58:38 -05:00
parent dfc58bb7e0
commit 3874c8de9b
4 changed files with 62 additions and 11 deletions

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,8 @@ import type { Stringifiable } from 'query-string';
import { DynamicParamsMap } from './dynamicParamsMap'; import { DynamicParamsMap } from './dynamicParamsMap';
// Transitional // Transitional
import { QueryFilter } from '../../select_old/api/types' import { QueryFilter, PathFilter } from '../../select_old/api/types'
import { getElement, replaceAll } from '../../util';
// Extends TomSelect to provide enhanced fetching of options via the REST API // Extends TomSelect to provide enhanced fetching of options via the REST API
@ -18,6 +19,7 @@ export class DynamicTomSelect extends TomSelect {
private readonly queryParams: QueryFilter = new Map(); private readonly queryParams: QueryFilter = new Map();
private readonly staticParams: QueryFilter = new Map(); private readonly staticParams: QueryFilter = new Map();
private readonly dynamicParams: DynamicParamsMap = new DynamicParamsMap(); private readonly dynamicParams: DynamicParamsMap = new DynamicParamsMap();
private readonly pathValues: PathFilter = new Map();
/** /**
* Overrides * Overrides
@ -41,6 +43,12 @@ export class DynamicTomSelect extends TomSelect {
this.updateQueryParams(filter); this.updateQueryParams(filter);
} }
// Path values
this.getPathKeys();
for (const filter of this.pathValues.keys()) {
this.updatePathValues(filter);
}
// Add dependency event listeners. // Add dependency event listeners.
this.addEventListeners(); this.addEventListeners();
} }
@ -73,7 +81,7 @@ export class DynamicTomSelect extends TomSelect {
// Formulate and return the complete URL for an API request, including any query parameters. // Formulate and return the complete URL for an API request, including any query parameters.
getRequestUrl(search: string): string { getRequestUrl(search: string): string {
const url = this.api_url; let url = this.api_url;
// Create new URL query parameters based on the current state of `queryParams` and create an // Create new URL query parameters based on the current state of `queryParams` and create an
// updated API query URL. // updated API query URL.
@ -82,6 +90,15 @@ export class DynamicTomSelect extends TomSelect {
query[key] = value; query[key] = value;
} }
// Replace any variables in the URL with values from `pathValues` if set.
for (const [key, value] of this.pathValues.entries()) {
for (const result of this.api_url.matchAll(new RegExp(`({{${key}}})`, 'g'))) {
if (value) {
url = replaceAll(url, result[1], value.toString());
}
}
}
// Append the search query, if any // Append the search query, if any
if (search) { if (search) {
query['q'] = [search]; query['q'] = [search];
@ -138,6 +155,16 @@ export class DynamicTomSelect extends TomSelect {
} }
} }
// Parse the `data-url` attribute to add any variables to `pathValues` as keys with empty
// values. As those keys' corresponding form fields' values change, `pathValues` will be
// updated to reflect the new value.
private getPathKeys() {
for (const result of this.api_url.matchAll(new RegExp(`{{(.+)}}`, 'g'))) {
this.pathValues.set(result[1], '');
}
}
// Update an element's API URL based on the value of another element on which this element // Update an element's API URL based on the value of another element on which this element
// relies. // relies.
private updateQueryParams(fieldName: string): void { private updateQueryParams(fieldName: string): void {
@ -198,16 +225,39 @@ export class DynamicTomSelect extends TomSelect {
} }
} }
/** // Update `pathValues` based on the form value of another element.
* Add event listeners to this element and its dependencies so that when dependencies change private updatePathValues(id: string): void {
* this element's options are updated. const key = replaceAll(id, /^id_/i, '');
*/ const element = getElement<HTMLSelectElement>(`id_${key}`);
private addEventListeners(): void { if (element !== null) {
// If this element's URL contains variable tags ({{), replace the tag with the dependency's
// value. For example, if the dependency is the `rack` field, and the `rack` field's value
// is `1`, this element's URL would change from `/dcim/racks/{{rack}}/` to `/dcim/racks/1/`.
const hasReplacement =
this.api_url.includes(`{{`) && Boolean(this.api_url.match(new RegExp(`({{(${id})}})`, 'g')));
if (hasReplacement) {
if (element.value) {
// If the field has a value, add it to the map.
this.pathValues.set(id, element.value);
} else {
// Otherwise, reset the value.
this.pathValues.set(id, '');
}
}
}
}
/**
* Events
*/
// Add event listeners to this element and its dependencies so that when dependencies change
//this element's options are updated.
private addEventListeners(): void {
// Create a unique iterator of all possible form fields which, when changed, should cause this // Create a unique iterator of all possible form fields which, when changed, should cause this
// element to update its API query. // element to update its API query.
// const dependencies = new Set([...this.dynamicParams.keys(), ...this.pathValues.keys()]); const dependencies = new Set([...this.dynamicParams.keys(), ...this.pathValues.keys()]);
const dependencies = this.dynamicParams.keys();
for (const dep of dependencies) { for (const dep of dependencies) {
const filterElement = document.querySelector(`[name="${dep}"]`); const filterElement = document.querySelector(`[name="${dep}"]`);
@ -225,10 +275,10 @@ export class DynamicTomSelect extends TomSelect {
// `tenant_group` and update the query parameters and API query URL for the `tenant` field. // `tenant_group` and update the query parameters and API query URL for the `tenant` field.
private handleEvent(event: Event): void { private handleEvent(event: Event): void {
const target = event.target as HTMLSelectElement; const target = event.target as HTMLSelectElement;
// Update the element's URL after any changes to a dependency. // Update the element's URL after any changes to a dependency.
this.updateQueryParams(target.name); this.updateQueryParams(target.name);
// this.updatePathValues(target.name); this.updatePathValues(target.name);
// this.updateQueryUrl();
// Load new data. // Load new data.
this.load(this.lastValue); this.load(this.lastValue);

View File

@ -16,6 +16,7 @@ export function initDynamicSelects(): void {
dropdownParent: 'body', dropdownParent: 'body',
controlInput: '<input>', controlInput: '<input>',
preload: 'focus', preload: 'focus',
maxOptions: 100,
}); });
} }