Compare commits

..

2 Commits

Author SHA1 Message Date
Arthur
0e8023899f #20383 clear rack face if no rack on edit 2026-01-15 09:38:00 -08:00
Arthur
601a7092e0 #20383 clear rack face if no rack on edit 2026-01-15 09:20:38 -08:00
6 changed files with 55 additions and 40 deletions

View File

@@ -722,6 +722,9 @@ class DeviceForm(TenancyForm, PrimaryModelForm):
if position: if position:
self.fields['position'].widget.choices = [(position, f'U{position}')] self.fields['position'].widget.choices = [(position, f'U{position}')]
# Clear face field when rack is cleared
self.fields['face'].widget.attrs['ts-clear-field'] = 'rack'
class ModuleForm(ModuleCommonForm, PrimaryModelForm): class ModuleForm(ModuleCommonForm, PrimaryModelForm):
device = DynamicModelChoiceField( device = DynamicModelChoiceField(
@@ -733,10 +736,9 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
) )
module_bay = DynamicModelChoiceField( module_bay = DynamicModelChoiceField(
label=_('Module bay'), label=_('Module bay'),
queryset=ModuleBay.objects.order_by('name'), queryset=ModuleBay.objects.all(),
query_params={ query_params={
'device_id': '$device', 'device_id': '$device'
'ordering': 'name',
}, },
context={ context={
'disabled': 'installed_module', 'disabled': 'installed_module',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
import TomSelect from 'tom-select';
import { getElements } from '../util'; import { getElements } from '../util';
function handleFormSubmit(): void { function handleFormSubmit(): void {
@@ -8,6 +9,37 @@ function handleFormSubmit(): void {
} }
} }
/**
* Initialize clear-field dependencies.
* When a field with ts-clear-field attribute's parent field is cleared, this field will also be cleared.
*/
function initClearFieldDependencies(): void {
// Find all fields with ts-clear-field attribute
for (const field of getElements<HTMLSelectElement>('[ts-clear-field]')) {
const parentFieldName = field.getAttribute('ts-clear-field');
if (!parentFieldName) continue;
// Find the parent field
const parentField = document.querySelector<HTMLSelectElement>(`[name="${parentFieldName}"]`);
if (!parentField) continue;
// Listen for changes on the parent field
parentField.addEventListener('change', () => {
// If parent field is cleared, also clear this dependent field
if (!parentField.value || parentField.value === '') {
// Check if this field uses TomSelect
const tomselect = (field as HTMLSelectElement & { tomselect?: TomSelect }).tomselect;
if (tomselect) {
tomselect.clear();
} else {
// Regular select field
field.value = '';
}
}
});
}
}
/** /**
* Attach event listeners to each form's submit/reset buttons. * Attach event listeners to each form's submit/reset buttons.
*/ */
@@ -28,4 +60,7 @@ export function initFormElements(): void {
}); });
} }
} }
// Initialize clear-field dependencies
initClearFieldDependencies();
} }

View File

@@ -75,16 +75,10 @@ export class DynamicTomSelect extends TomSelect {
load(value: string) { load(value: string) {
const self = this; const self = this;
// Save current selection before clearing
const currentValue = self.getValue();
// 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.)
self.clearOptions(); self.clearOptions();
// Clear user_options to prevent the pre-selected option from being treated specially
(self as any).user_options = {};
// Populate the null option (if any) if not searching // Populate the null option (if any) if not searching
if (self.nullOption && !value) { if (self.nullOption && !value) {
self.addOption(self.nullOption); self.addOption(self.nullOption);
@@ -104,29 +98,16 @@ export class DynamicTomSelect extends TomSelect {
.then(response => response.json()) .then(response => response.json())
.then(apiData => { .then(apiData => {
const results: Dict[] = apiData.results; const results: Dict[] = apiData.results;
const options: Dict[] = [];
// Add options and manually set $order to ensure correct sorting for (const result of results) {
results.forEach((result, index) => {
const option = self.getOptionFromData(result); const option = self.getOptionFromData(result);
self.addOption(option); options.push(option);
// Set $order after addOption() to override any special handling of pre-selected items
const key = option[self.settings.valueField as string] as string;
if (self.options[key]) {
(self.options[key] as any).$order = index;
} }
}); return options;
})
self.loading--; // Pass the options to the callback function
if (self.loading === 0) { .then(options => {
self.wrapper.classList.remove(self.settings.loadingClass as string); self.loadCallback(options, []);
}
// Restore the current selection
if (currentValue && !self.items.includes(currentValue as string)) {
self.items.push(currentValue as string);
}
self.refreshOptions(false);
}) })
.catch(() => { .catch(() => {
self.loadCallback([], []); self.loadCallback([], []);

View File

@@ -49,9 +49,6 @@ export function initDynamicSelects(): void {
labelField: LABEL_FIELD, labelField: LABEL_FIELD,
maxOptions: MAX_OPTIONS, maxOptions: MAX_OPTIONS,
// Preserve API response order
sortField: '$order',
// Disable local search (search is performed on the backend) // Disable local search (search is performed on the backend)
searchField: [], searchField: [],