Refactor TomSelect implementation

This commit is contained in:
Jeremy Stretch 2024-01-29 16:36:34 -05:00
parent 36824d16e0
commit 04c6083ff6
16 changed files with 103 additions and 102 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,11 @@
import { getElements, isTruthy } from './util'; import { getElements, isTruthy } from './util';
import { initButtons } from './buttons'; import { initButtons } from './buttons';
import { initSelect } from './select'; import { initSelects } from './select';
import { initObjectSelector } from './objectSelector'; import { initObjectSelector } from './objectSelector';
import { initBootstrap } from './bs'; import { initBootstrap } from './bs';
function initDepedencies(): void { function initDepedencies(): void {
for (const init of [initButtons, initSelect, initObjectSelector, initBootstrap]) { for (const init of [initButtons, initSelects, initObjectSelector, initBootstrap]) {
init(); init();
} }
} }

View File

@ -1,8 +1,7 @@
import { initForms } from './forms'; import { initForms } from './forms';
import { initBootstrap } from './bs'; import { initBootstrap } from './bs';
import { initQuickSearch } from './search'; import { initQuickSearch } from './search';
// import { initSelect } from './select'; import { initSelects } from './select';
import { initSelects } from './tomSelect';
import { initButtons } from './buttons'; import { initButtons } from './buttons';
import { initColorMode } from './colorMode'; import { initColorMode } from './colorMode';
import { initMessages } from './messages'; import { initMessages } from './messages';
@ -23,7 +22,6 @@ function initDocument(): void {
initMessages, initMessages,
initForms, initForms,
initQuickSearch, initQuickSearch,
// initSelect,
initSelects, initSelects,
initDateSelector, initDateSelector,
initButtons, initButtons,

View File

@ -1,15 +1,15 @@
import { getElements } from './util'; import { RecursivePartial, TomInput, TomSettings } from 'tom-select/dist/types/types';
import { RecursivePartial, TomInput, TomOption, TomSettings } from 'tom-select/src/types';
import { addClasses } from 'tom-select/src/vanilla' import { addClasses } from 'tom-select/src/vanilla'
import queryString from 'query-string'; import queryString from 'query-string';
import TomSelect from 'tom-select'; import TomSelect from 'tom-select';
import type { Stringifiable } from 'query-string'; import type { Stringifiable } from 'query-string';
// Transitional // Transitional
import { QueryFilter } from './select/api/types' import { QueryFilter } from '../../select_old/api/types'
class DynamicTomSelect extends TomSelect { // Extends TomSelect to provide enhanced fetching of options via the REST API
export class DynamicTomSelect extends TomSelect {
/* /*
* Transitional code from APISelect * Transitional code from APISelect
@ -24,6 +24,7 @@ class DynamicTomSelect extends TomSelect {
constructor( input_arg: string|TomInput, user_settings: RecursivePartial<TomSettings> ) { constructor( input_arg: string|TomInput, user_settings: RecursivePartial<TomSettings> ) {
super(input_arg, user_settings); super(input_arg, user_settings);
// Glean the REST API endpoint URL from the <select> element
this.api_url = this.input.getAttribute('data-url') as string; this.api_url = this.input.getAttribute('data-url') as string;
// Populate static query parameters. // Populate static query parameters.
@ -35,6 +36,7 @@ class DynamicTomSelect extends TomSelect {
load(value: string) { load(value: string) {
const self = this; const self = this;
const url = self.getRequestUrl(value);
// Automatically clear any cached options. (Only options included // Automatically clear any cached options. (Only options included
// in the API response should be present.) // in the API response should be present.)
@ -45,16 +47,13 @@ class DynamicTomSelect extends TomSelect {
addClasses(self.wrapper, self.settings.loadingClass); addClasses(self.wrapper, self.settings.loadingClass);
self.loading++; self.loading++;
const callback = self.loadCallback.bind(self);
const url = self.getRequestUrl(value);
// Make the API request // Make the API request
fetch(url) fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(json => { .then(json => {
callback(json.results); self.loadCallback(json.results, []);
}).catch(()=>{ }).catch(()=>{
callback(); self.loadCallback([], []);
}); });
} }
@ -63,9 +62,7 @@ class DynamicTomSelect extends TomSelect {
* Custom methods * Custom methods
*/ */
/** // 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; const url = this.api_url;
@ -91,13 +88,9 @@ class DynamicTomSelect extends TomSelect {
* Transitional methods * Transitional methods
*/ */
/** // Determine if this instance's options should be filtered by static values passed from the
* Determine if this instance's options should be filtered by static values passed from the // server. Looks for the DOM attribute `data-static-params`, the value of which is a JSON
* server. // array of objects containing key/value pairs to add to `this.staticParams`.
*
* Looks for the DOM attribute `data-static-params`, the value of which is a JSON array of
* objects containing key/value pairs to add to `this.staticParams`.
*/
private getStaticParams(): void { private getStaticParams(): void {
const serialized = this.input.getAttribute('data-static-params'); const serialized = this.input.getAttribute('data-static-params');
@ -122,53 +115,3 @@ class DynamicTomSelect extends TomSelect {
} }
} }
// Initialize <select> elements with statically-defined options
function initStaticSelects(): void {
for (const select of getElements<HTMLSelectElement>('select:not(.api-select):not(.color-select)')) {
new TomSelect(select, {
plugins: ['clear_button']
});
}
}
// Initialize <select> elements which are populated via a REST API call
function initDynamicSelects(): void {
for (const select of getElements<HTMLSelectElement>('select.api-select')) {
new DynamicTomSelect(select, {
plugins: ['clear_button'],
valueField: 'id',
labelField: 'display',
searchField: [],
copyClassesToDropdown: false,
dropdownParent: 'body',
controlInput: '<input>',
preload: 'focus',
});
}
}
// Initialize color selection fields
function initColorSelects(): void {
for (const select of getElements<HTMLSelectElement>('select.color-select')) {
new TomSelect(select, {
render: {
option: function(item: TomOption, escape: Function) {
return `<div style="background-color: #${escape(item.value)}">${escape(item.text)}</div>`;
}
}
});
}
}
export function initSelects(): void {
initStaticSelects();
initDynamicSelects();
initColorSelects();
}

View File

@ -0,0 +1,21 @@
import { getElements } from '../util';
import { DynamicTomSelect } from './classes/dynamicTomSelect'
// Initialize <select> elements which are populated via a REST API call
export function initDynamicSelects(): void {
for (const select of getElements<HTMLSelectElement>('select.api-select')) {
new DynamicTomSelect(select, {
plugins: ['clear_button'],
valueField: 'id',
labelField: 'display',
searchField: [],
copyClassesToDropdown: false,
dropdownParent: 'body',
controlInput: '<input>',
preload: 'focus',
});
}
}

View File

@ -1,9 +1,9 @@
import { initApiSelect } from './api'; import { initColorSelects, initStaticSelects } from './static';
import { initColorSelect } from './color'; import { initDynamicSelects } from './dynamic';
import { initStaticSelect } from './static';
export function initSelect(): void {
for (const func of [initApiSelect, initColorSelect, initStaticSelect]) { export function initSelects(): void {
func(); initStaticSelects();
} initDynamicSelects();
initColorSelects();
} }

View File

@ -1,27 +1,30 @@
import SlimSelect from 'slim-select';
import { getElements } from '../util'; import { getElements } from '../util';
import { TomOption } from 'tom-select/src/types';
import TomSelect from 'tom-select';
export function initStaticSelect(): void {
for (const select of getElements<HTMLSelectElement>('.netbox-static-select:not([data-ssid])')) {
if (select !== null) {
const label = document.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
let placeholder; // Initialize <select> elements with statically-defined options
if (label !== null) { export function initStaticSelects(): void {
placeholder = `Select ${label.innerText.trim()}`;
}
const instance = new SlimSelect({ for (const select of getElements<HTMLSelectElement>('select:not(.api-select):not(.color-select)')) {
select, new TomSelect(select, {
allowDeselect: true, plugins: ['clear_button']
deselectLabel: `<i class="mdi mdi-close-circle"></i>`,
placeholder,
}); });
}
// Don't copy classes from select element to SlimSelect instance. }
for (const className of select.classList) {
instance.slim.container.classList.remove(className); // Initialize color selection fields
export function initColorSelects(): void {
for (const select of getElements<HTMLSelectElement>('select.color-select')) {
new TomSelect(select, {
render: {
option: function(item: TomOption, escape: Function) {
return `<div style="background-color: #${escape(item.value)}">${escape(item.text)}</div>`;
} }
} }
});
} }
} }

View File

@ -0,0 +1,9 @@
import { initApiSelect } from './api';
import { initColorSelect } from './color';
import { initStaticSelect } from './static';
export function initSelect(): void {
for (const func of [initApiSelect, initColorSelect, initStaticSelect]) {
func();
}
}

View File

@ -0,0 +1,27 @@
import SlimSelect from 'slim-select';
import { getElements } from '../util';
export function initStaticSelect(): void {
for (const select of getElements<HTMLSelectElement>('.netbox-static-select:not([data-ssid])')) {
if (select !== null) {
const label = document.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
let placeholder;
if (label !== null) {
placeholder = `Select ${label.innerText.trim()}`;
}
const instance = new SlimSelect({
select,
allowDeselect: true,
deselectLabel: `<i class="mdi mdi-close-circle"></i>`,
placeholder,
});
// Don't copy classes from select element to SlimSelect instance.
for (const className of select.classList) {
instance.slim.container.classList.remove(className);
}
}
}
}