mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#7123: Handle empty_option on API Select
This commit is contained in:
14
netbox/project-static/dist/netbox.js
vendored
14
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
2
netbox/project-static/dist/netbox.js.map
vendored
2
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@ -22,13 +22,6 @@ import type { Stringifiable } from 'query-string';
|
|||||||
import type { Option } from 'slim-select/dist/data';
|
import type { Option } from 'slim-select/dist/data';
|
||||||
import type { Trigger, PathFilter, ApplyMethod, QueryFilter } from './types';
|
import type { Trigger, PathFilter, ApplyMethod, QueryFilter } from './types';
|
||||||
|
|
||||||
// Empty placeholder option.
|
|
||||||
const PLACEHOLDER = {
|
|
||||||
value: '',
|
|
||||||
text: '',
|
|
||||||
placeholder: true,
|
|
||||||
} as Option;
|
|
||||||
|
|
||||||
// Attributes which if truthy should render the option disabled.
|
// Attributes which if truthy should render the option disabled.
|
||||||
const DISABLED_ATTRIBUTES = ['occupied'] as string[];
|
const DISABLED_ATTRIBUTES = ['occupied'] as string[];
|
||||||
|
|
||||||
@ -52,6 +45,8 @@ export class APISelect {
|
|||||||
*/
|
*/
|
||||||
public readonly placeholder: string;
|
public readonly placeholder: string;
|
||||||
|
|
||||||
|
public readonly emptyOption: Nullable<Option> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event that will initiate the API call to NetBox to load option data. By default, the trigger
|
* Event that will initiate the API call to NetBox to load option data. By default, the trigger
|
||||||
* is `'load'`, so data will be fetched when the element renders on the page.
|
* is `'load'`, so data will be fetched when the element renders on the page.
|
||||||
@ -147,7 +142,7 @@ export class APISelect {
|
|||||||
/**
|
/**
|
||||||
* This instance's available options.
|
* This instance's available options.
|
||||||
*/
|
*/
|
||||||
private _options: Option[] = [PLACEHOLDER];
|
private _options: Option[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of options values which should be considered disabled or static.
|
* Array of options values which should be considered disabled or static.
|
||||||
@ -168,6 +163,14 @@ export class APISelect {
|
|||||||
this.preSorted = true;
|
this.preSorted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyOption = base.getAttribute('data-empty-option');
|
||||||
|
if (isTruthy(emptyOption)) {
|
||||||
|
this.emptyOption = {
|
||||||
|
text: emptyOption,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (hasUrl(base)) {
|
if (hasUrl(base)) {
|
||||||
const url = base.getAttribute('data-url') as string;
|
const url = base.getAttribute('data-url') as string;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@ -285,17 +288,16 @@ export class APISelect {
|
|||||||
// Get the placeholder index (note: if there is no placeholder, the index will be `-1`).
|
// Get the placeholder index (note: if there is no placeholder, the index will be `-1`).
|
||||||
const placeholderIdx = deduplicated.findIndex(o => o.value === '');
|
const placeholderIdx = deduplicated.findIndex(o => o.value === '');
|
||||||
|
|
||||||
if (hasPlaceholder && placeholderIdx < 0) {
|
if (hasPlaceholder && placeholderIdx < 0 && this.emptyOption !== null) {
|
||||||
// If there is a placeholder but it is not the first element (due to sorting or other merge
|
// If there is a placeholder but it is not the first element (due to sorting or other merge
|
||||||
// issues), remove it from the options array and place it in front.
|
// issues), remove it from the options array and place it in front.
|
||||||
deduplicated.splice(placeholderIdx);
|
deduplicated.splice(placeholderIdx);
|
||||||
deduplicated = [PLACEHOLDER, ...deduplicated];
|
deduplicated = [this.emptyOption, ...deduplicated];
|
||||||
}
|
}
|
||||||
if (!hasPlaceholder) {
|
if (!hasPlaceholder && this.emptyOption !== null) {
|
||||||
// If there is no placeholder, add one to the front of the array.
|
// If there is no placeholder, add one to the front of the array.
|
||||||
deduplicated = [PLACEHOLDER, ...deduplicated];
|
deduplicated = [this.emptyOption, ...deduplicated];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._options = deduplicated;
|
this._options = deduplicated;
|
||||||
this.slim.setData(deduplicated);
|
this.slim.setData(deduplicated);
|
||||||
}
|
}
|
||||||
@ -304,7 +306,11 @@ export class APISelect {
|
|||||||
* Remove all options and reset back to the generic placeholder.
|
* Remove all options and reset back to the generic placeholder.
|
||||||
*/
|
*/
|
||||||
private resetOptions(): void {
|
private resetOptions(): void {
|
||||||
this.options = [PLACEHOLDER];
|
if (this.emptyOption !== null) {
|
||||||
|
this.options = [this.emptyOption];
|
||||||
|
} else {
|
||||||
|
this.options = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -534,7 +540,7 @@ export class APISelect {
|
|||||||
*/
|
*/
|
||||||
private async getOptions(action: ApplyMethod = 'merge'): Promise<void> {
|
private async getOptions(action: ApplyMethod = 'merge'): Promise<void> {
|
||||||
if (this.queryUrl.includes(`{{`)) {
|
if (this.queryUrl.includes(`{{`)) {
|
||||||
this.options = [PLACEHOLDER];
|
this.resetOptions();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.fetchOptions(this.queryUrl, action);
|
await this.fetchOptions(this.queryUrl, action);
|
||||||
|
@ -376,7 +376,7 @@ class DynamicModelChoiceMixin:
|
|||||||
widget = widgets.APISelect
|
widget = widgets.APISelect
|
||||||
|
|
||||||
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, fetch_trigger=None,
|
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, fetch_trigger=None,
|
||||||
*args, **kwargs):
|
empty_label=None, *args, **kwargs):
|
||||||
self.query_params = query_params or {}
|
self.query_params = query_params or {}
|
||||||
self.initial_params = initial_params or {}
|
self.initial_params = initial_params or {}
|
||||||
self.null_option = null_option
|
self.null_option = null_option
|
||||||
@ -386,11 +386,14 @@ class DynamicModelChoiceMixin:
|
|||||||
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
|
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
|
||||||
# by widget_attrs()
|
# by widget_attrs()
|
||||||
self.to_field_name = kwargs.get('to_field_name')
|
self.to_field_name = kwargs.get('to_field_name')
|
||||||
|
self.empty_option = empty_label or ""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def widget_attrs(self, widget):
|
def widget_attrs(self, widget):
|
||||||
attrs = {}
|
attrs = {
|
||||||
|
'data-empty-option': self.empty_option
|
||||||
|
}
|
||||||
|
|
||||||
# Set value-field attribute if the field specifies to_field_name
|
# Set value-field attribute if the field specifies to_field_name
|
||||||
if self.to_field_name:
|
if self.to_field_name:
|
||||||
|
Reference in New Issue
Block a user