#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 429d995270
commit e8c91ea3a1
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() {
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 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 { setOptionStyles, toggle, getDependencyIds } from './util';
import { setOptionStyles, toggle, getDependencyIds, initResetButton } from './util';
import type { Option } from 'slim-select/dist/data';
@ -318,18 +325,45 @@ export function initApiSelect() {
select.addEventListener(`netbox.select.onload.${dep}`, handleEvent);
}
// Load data.
getOptions(url, select, disabledOptions)
.then(options => instance.setData(options))
.catch(console.error)
.finally(() => {
// Set option styles, if the field calls for it (color selectors).
/**
* Load this element's options from the NetBox API.
*/
async function loadData(): Promise<void> {
try {
const options = await getOptions(url, select, disabledOptions);
instance.setData(options);
} catch (err) {
console.error(err);
} finally {
setOptionStyles(instance);
// Enable the element after data has loaded.
toggle('enable', instance);
// Inform any event listeners that data has updated.
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.
// 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 { findFirstAdjacent, getElements } from '../util';
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));
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 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>(
base: B,
query: string,
boundary?: string,
): 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> {
if (parent !== null && parent.parentElement !== null) {
if (parent !== null && parent.parentElement !== null && !atBoundary(parent)) {
for (const child of parent.parentElement.querySelectorAll<R>(query)) {
if (child !== null) {
return child;

View File

@ -50,9 +50,12 @@
{% endif %}
</div>
<div class="card-footer text-end noprint border-0">
<a href="." class="btn btn-sm btn-outline-dark m-1">
<i class="mdi mdi-close"></i> Clear
</a>
<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> Close
</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">
<i class="mdi mdi-magnify"></i> Search
</button>