#6372: Don't autopopulate collapsible API select data, improve advanced search

This commit is contained in:
checktheroads 2021-05-23 19:01:05 -07:00
parent 715fa13c12
commit ede5adfbbf
15 changed files with 84 additions and 16 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -18,7 +18,7 @@ function initMasonry(): void {
function initTooltips() { function initTooltips() {
for (const tooltip of getElements('[data-bs-toggle="tooltip"]')) { for (const tooltip of getElements('[data-bs-toggle="tooltip"]')) {
new Tooltip(tooltip, { container: 'body', boundary: 'window' }); new Tooltip(tooltip, { container: 'body' });
} }
} }

View File

@ -1,8 +1,15 @@
import SlimSelect from 'slim-select'; import SlimSelect from 'slim-select';
import queryString from 'query-string'; import queryString from 'query-string';
import { getApiData, isApiError, getElements, isTruthy, hasError } from '../util'; import {
getApiData,
isApiError,
getElements,
isTruthy,
hasError,
findFirstAdjacent,
} from '../util';
import { createToast } from '../bs'; import { createToast } from '../bs';
import { setOptionStyles, toggle, getDependencyIds } from './util'; import { setOptionStyles, toggle, getDependencyIds, initResetButton } from './util';
import type { Option } from 'slim-select/dist/data'; import type { Option } from 'slim-select/dist/data';
@ -318,18 +325,45 @@ export function initApiSelect() {
select.addEventListener(`netbox.select.onload.${dep}`, handleEvent); select.addEventListener(`netbox.select.onload.${dep}`, handleEvent);
} }
// Load data. /**
getOptions(url, select, disabledOptions) * Load this element's options from the NetBox API.
.then(options => instance.setData(options)) */
.catch(console.error) async function loadData(): Promise<void> {
.finally(() => { try {
// Set option styles, if the field calls for it (color selectors). const options = await getOptions(url, select, disabledOptions);
instance.setData(options);
} catch (err) {
console.error(err);
} finally {
setOptionStyles(instance); setOptionStyles(instance);
// Enable the element after data has loaded.
toggle('enable', instance); toggle('enable', instance);
// Inform any event listeners that data has updated.
select.dispatchEvent(event); select.dispatchEvent(event);
}); }
}
/**
* Delete this element's options.
*/
function clearData(): void {
return instance.setData([]);
}
// Determine if this element is part of collapsible element.
const collapse = findFirstAdjacent(select, '.collapse', '.content-container');
console.log('collapse', collapse);
if (collapse !== null) {
// If this element is part of a collapsible element, only load the data when the
// collapsible element is shown.
// See: https://getbootstrap.com/docs/5.0/components/collapse/#events
collapse.addEventListener('show.bs.collapse', loadData);
collapse.addEventListener('hide.bs.collapse', clearData);
} else {
// Otherwise, load the data on render.
Promise.all([loadData()]);
}
// Bind event listener to
initResetButton(select, instance);
// Set the underlying select element to the same size as the SlimSelect instance. // Set the underlying select element to the same size as the SlimSelect instance.
// This is primarily for built-in HTML form validation, which doesn't really work, // This is primarily for built-in HTML form validation, which doesn't really work,

View File

@ -1,4 +1,5 @@
import { readableColor } from 'color2k'; import { readableColor } from 'color2k';
import { findFirstAdjacent, getElements } from '../util';
import type SlimSelect from 'slim-select'; import type SlimSelect from 'slim-select';
@ -184,3 +185,23 @@ export function getDependencyIds<E extends HTMLElement>(element: Nullable<E>): s
const ids = new Set<string>(getAllDependencyIds(element)); const ids = new Set<string>(getAllDependencyIds(element));
return Array.from(ids).map(i => i.replaceAll('_id', '')); return Array.from(ids).map(i => i.replaceAll('_id', ''));
} }
/**
* Initialize any adjacent reset buttons so that when clicked, the instance's selected value is cleared.
*
* @param select Select Element
* @param instance SlimSelect Instance
*/
export function initResetButton(select: HTMLSelectElement, instance: SlimSelect) {
const resetButton = findFirstAdjacent<HTMLButtonElement>(select, 'button[data-reset-select');
if (resetButton !== null) {
resetButton.addEventListener('click', () => {
select.value = '';
if (select.multiple) {
instance.setSelected([]);
} else {
instance.setSelected('');
}
});
}
}

View File

@ -251,13 +251,23 @@ export function* getRowValues(table: HTMLTableRowElement): Generator<string> {
* *
* @param base Base Element * @param base Base Element
* @param query CSS Query * @param query CSS Query
* @param boundary Optionally specify a CSS Query which, when matched, recursion will cease.
*/ */
export function findFirstAdjacent<R extends HTMLElement, B extends Element = Element>( export function findFirstAdjacent<R extends HTMLElement, B extends Element = Element>(
base: B, base: B,
query: string, query: string,
boundary?: string,
): Nullable<R> { ): Nullable<R> {
function atBoundary<E extends Element | null>(element: E): boolean {
if (typeof boundary === 'string' && element !== null) {
if (element.matches(boundary)) {
return true;
}
}
return false;
}
function match<P extends Element | null>(parent: P): Nullable<R> { function match<P extends Element | null>(parent: P): Nullable<R> {
if (parent !== null && parent.parentElement !== null) { if (parent !== null && parent.parentElement !== null && !atBoundary(parent)) {
for (const child of parent.parentElement.querySelectorAll<R>(query)) { for (const child of parent.parentElement.querySelectorAll<R>(query)) {
if (child !== null) { if (child !== null) {
return child; return child;

View File

@ -50,9 +50,12 @@
{% endif %} {% endif %}
</div> </div>
<div class="card-footer text-end noprint border-0"> <div class="card-footer text-end noprint border-0">
<a href="." class="btn btn-sm btn-outline-dark m-1"> <button type="button" class="btn btn-sm btn-outline-dark m-1" data-bs-toggle="collapse" data-bs-target="#advanced-search-content">
<i class="mdi mdi-close"></i> Clear <i class="mdi mdi-close"></i> Close
</a> </button>
<button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
<i class="mdi mdi-backspace"></i> Reset
</button>
<button type="submit" class="btn btn-sm btn-primary m-1"> <button type="submit" class="btn btn-sm btn-primary m-1">
<i class="mdi mdi-magnify"></i> Search <i class="mdi mdi-magnify"></i> Search
</button> </button>