diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 6f379af8d..17c8eec41 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 5a97f7adc..65d52a848 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/src/select/classes/dynamicTomSelect.ts b/netbox/project-static/src/select/classes/dynamicTomSelect.ts index c99185e0d..8715e8bc8 100644 --- a/netbox/project-static/src/select/classes/dynamicTomSelect.ts +++ b/netbox/project-static/src/select/classes/dynamicTomSelect.ts @@ -6,7 +6,8 @@ import type { Stringifiable } from 'query-string'; import { DynamicParamsMap } from './dynamicParamsMap'; // 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 @@ -18,6 +19,7 @@ export class DynamicTomSelect extends TomSelect { private readonly queryParams: QueryFilter = new Map(); private readonly staticParams: QueryFilter = new Map(); private readonly dynamicParams: DynamicParamsMap = new DynamicParamsMap(); + private readonly pathValues: PathFilter = new Map(); /** * Overrides @@ -41,6 +43,12 @@ export class DynamicTomSelect extends TomSelect { this.updateQueryParams(filter); } + // Path values + this.getPathKeys(); + for (const filter of this.pathValues.keys()) { + this.updatePathValues(filter); + } + // Add dependency event listeners. 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. 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 // updated API query URL. @@ -82,6 +90,15 @@ export class DynamicTomSelect extends TomSelect { 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 if (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 // relies. private updateQueryParams(fieldName: string): void { @@ -198,16 +225,39 @@ export class DynamicTomSelect extends TomSelect { } } - /** - * Add event listeners to this element and its dependencies so that when dependencies change - * this element's options are updated. - */ - private addEventListeners(): void { + // Update `pathValues` based on the form value of another element. + private updatePathValues(id: string): void { + const key = replaceAll(id, /^id_/i, ''); + const element = getElement(`id_${key}`); + 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 // element to update its API query. - // const dependencies = new Set([...this.dynamicParams.keys(), ...this.pathValues.keys()]); - const dependencies = this.dynamicParams.keys(); + const dependencies = new Set([...this.dynamicParams.keys(), ...this.pathValues.keys()]); for (const dep of dependencies) { 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. private handleEvent(event: Event): void { const target = event.target as HTMLSelectElement; + // Update the element's URL after any changes to a dependency. this.updateQueryParams(target.name); - // this.updatePathValues(target.name); - // this.updateQueryUrl(); + this.updatePathValues(target.name); // Load new data. this.load(this.lastValue); diff --git a/netbox/project-static/src/select/dynamic.ts b/netbox/project-static/src/select/dynamic.ts index 8794ffcc6..60cdfa9e4 100644 --- a/netbox/project-static/src/select/dynamic.ts +++ b/netbox/project-static/src/select/dynamic.ts @@ -16,6 +16,7 @@ export function initDynamicSelects(): void { dropdownParent: 'body', controlInput: '', preload: 'focus', + maxOptions: 100, }); }