mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-21 04:42:22 -06:00
* Initial work on #13283 * Enable passing TomSelect HTML template attibutes on DynamicModelChoiceField * Merge disabled_indicator into option_attrs * Add support for annotating a numeric count on dropdown options * Annotate parent object on relevant fields * Improve rendering of color options * Improve rendering of color options * Rename option_attrs to context * Expose option context on ObjectVar for custom scripts * Document dropdown context variables
This commit is contained in:
8
netbox/project-static/dist/netbox.js
vendored
8
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
@@ -31,6 +31,15 @@ export class DynamicTomSelect extends TomSelect {
|
||||
// Glean the REST API endpoint URL from the <select> element
|
||||
this.api_url = this.input.getAttribute('data-url') as string;
|
||||
|
||||
// Override any field names set as widget attributes
|
||||
this.valueField = this.input.getAttribute('ts-value-field') || this.settings.valueField;
|
||||
this.labelField = this.input.getAttribute('ts-label-field') || this.settings.labelField;
|
||||
this.disabledField = this.input.getAttribute('ts-disabled-field') || this.settings.disabledField;
|
||||
this.descriptionField = this.input.getAttribute('ts-description-field') || 'description';
|
||||
this.depthField = this.input.getAttribute('ts-depth-field') || '_depth';
|
||||
this.parentField = this.input.getAttribute('ts-parent-field') || null;
|
||||
this.countField = this.input.getAttribute('ts-count-field') || null;
|
||||
|
||||
// Set the null option (if any)
|
||||
const nullOption = this.input.getAttribute('data-null-option');
|
||||
if (nullOption) {
|
||||
@@ -82,10 +91,20 @@ export class DynamicTomSelect extends TomSelect {
|
||||
// Make the API request
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
self.loadCallback(json.results, []);
|
||||
.then(apiData => {
|
||||
const results: Dict[] = apiData.results;
|
||||
let options: Dict[] = []
|
||||
for (let result of results) {
|
||||
const option = self.getOptionFromData(result);
|
||||
options.push(option);
|
||||
}
|
||||
return options;
|
||||
})
|
||||
// Pass the options to the callback function
|
||||
.then(options => {
|
||||
self.loadCallback(options, []);
|
||||
}).catch(()=>{
|
||||
self.loadCallback([], []);
|
||||
self.loadCallback([], []);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -126,6 +145,27 @@ export class DynamicTomSelect extends TomSelect {
|
||||
return queryString.stringifyUrl({ url, query });
|
||||
}
|
||||
|
||||
// Compile TomOption data from an API result
|
||||
getOptionFromData(data: Dict) {
|
||||
let option: Dict = {
|
||||
id: data[this.valueField],
|
||||
display: data[this.labelField],
|
||||
depth: data[this.depthField] || null,
|
||||
description: data[this.descriptionField] || null,
|
||||
};
|
||||
if (data[this.parentField]) {
|
||||
let parent: Dict = data[this.parentField] as Dict;
|
||||
option['parent'] = parent[this.labelField];
|
||||
}
|
||||
if (data[this.countField]) {
|
||||
option['count'] = data[this.countField];
|
||||
}
|
||||
if (data[this.disabledField]) {
|
||||
option['disabled'] = data[this.disabledField];
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitional methods
|
||||
*/
|
||||
|
||||
@@ -10,12 +10,34 @@ const MAX_OPTIONS = 100;
|
||||
|
||||
// Render the HTML for a dropdown option
|
||||
function renderOption(data: TomOption, escape: typeof escape_html) {
|
||||
// If the option has a `_depth` property, indent its label
|
||||
if (typeof data._depth === 'number' && data._depth > 0) {
|
||||
return `<div>${'─'.repeat(data._depth)} ${escape(data[LABEL_FIELD])}</div>`;
|
||||
let html = '<div>';
|
||||
|
||||
// If the option has a `depth` property, indent its label
|
||||
if (typeof data.depth === 'number' && data.depth > 0) {
|
||||
html = `${html}${'─'.repeat(data.depth)} `;
|
||||
}
|
||||
|
||||
return `<div>${escape(data[LABEL_FIELD])}</div>`;
|
||||
html = `${html}${escape(data[LABEL_FIELD])}`;
|
||||
if (data['parent']) {
|
||||
html = `${html} <span class="text-secondary">${escape(data['parent'])}</span>`;
|
||||
}
|
||||
if (data['count']) {
|
||||
html = `${html} <span class="badge">${escape(data['count'])}</span>`;
|
||||
}
|
||||
if (data['description']) {
|
||||
html = `${html}<br /><small class="text-secondary">${escape(data['description'])}</small>`;
|
||||
}
|
||||
html = `${html}</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Render the HTML for a selected item
|
||||
function renderItem(data: TomOption, escape: typeof escape_html) {
|
||||
if (data['parent']) {
|
||||
return `<div>${escape(data['parent'])} > ${escape(data[LABEL_FIELD])}</div>`;
|
||||
}
|
||||
return `<div>${escape(data[LABEL_FIELD])}<div>`;
|
||||
}
|
||||
|
||||
// Initialize <select> elements which are populated via a REST API call
|
||||
@@ -30,16 +52,13 @@ export function initDynamicSelects(): void {
|
||||
// Disable local search (search is performed on the backend)
|
||||
searchField: [],
|
||||
|
||||
// Reference the disabled-indicator attr on the <select> element to determine
|
||||
// the name of the attribute which indicates whether an option should be disabled
|
||||
disabledField: select.getAttribute('disabled-indicator') || undefined,
|
||||
|
||||
// Load options from API immediately on focus
|
||||
preload: 'focus',
|
||||
|
||||
// Define custom rendering functions
|
||||
render: {
|
||||
option: renderOption,
|
||||
item: renderItem,
|
||||
},
|
||||
|
||||
// By default, load() will be called only if query.length > 0
|
||||
|
||||
@@ -17,13 +17,18 @@ export function initStaticSelects(): void {
|
||||
|
||||
// Initialize color selection fields
|
||||
export function initColorSelects(): void {
|
||||
function renderColor(item: TomOption, escape: typeof escape_html) {
|
||||
return `<div><span class="dropdown-item-indicator color-label" style="background-color: #${escape(
|
||||
item.value,
|
||||
)}"></span> ${escape(item.text)}</div>`;
|
||||
}
|
||||
|
||||
for (const select of getElements<HTMLSelectElement>('select.color-select')) {
|
||||
new TomSelect(select, {
|
||||
...config,
|
||||
render: {
|
||||
option: function (item: TomOption, escape: typeof escape_html) {
|
||||
return `<div style="background-color: #${escape(item.value)}">${escape(item.text)}</div>`;
|
||||
},
|
||||
option: renderColor,
|
||||
item: renderColor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user