@@ -24,9 +24,9 @@ import { h, RemoteData } from '/js/src/index.js';
2424 * @param {object } config.filterMap - Map of the current filter values.
2525 * @param {string } [config.type='text'] - The type of the filter element (e.g., 'text', 'number').
2626 * @param {RemoteData } [config.options=RemoteData.notAsked()] - List of options for a dropdown selector (optional).
27- * @param {Function } config.onChangeCallback - Callback to be triggered on the change event of the filter.
28- * @param {Function } config.onInputCallback - Callback to be triggered on the input event.
29- * @param {Function } config.onEnterCallback - Callback to be triggered when the Enter key is pressed.
27+ * @param {onchange } config.onChangeCallback - Callback to be triggered on the change event of the filter.
28+ * @param {oninput } config.onInputCallback - Callback to be triggered on the input event.
29+ * @param {onkeydown } config.onEnterCallback - Callback to be triggered when the Enter key is pressed.
3030 * @param {string } [config.filterType=FilterType.DROPDOWN] - The type of filter to be used.
3131 * @param {string } [config.width='.'] - The CSS class that defines the width of the filter.
3232 * @returns {vnode } - A virtual node element representing the filter element (input or dropdown).
@@ -61,6 +61,81 @@ export const dynamicSelector = (config) => {
6161 } ) ;
6262} ;
6363
64+ /**
65+ * Represents options grouped for HTML <optgroup>.
66+ * Keys are group labels (for the <optgroup> label),
67+ * values are arrays of option values (for <option> elements).
68+ * @typedef {Record<string, string[]> } GroupedDropdownOptions
69+ */
70+
71+ /**
72+ * Builds a filter element. If options to show, selector filter element; otherwise, input element.
73+ * @param {object } config - Configuration object for building the filter element.
74+ * @param {string } config.queryLabel - The key used to query the storage with this parameter.
75+ * @param {string } config.placeholder - The placeholder text to be displayed in the input field.
76+ * @param {string } config.id - The unique identifier for the input field.
77+ * @param {object } config.filterMap - Map of the current filter values.
78+ * @param {string } [config.type='text'] - The type of the filter element (e.g., 'text', 'number').
79+ * @param {GroupedDropdownOptions } [config.options={}] - List of options for a grouped dropdown selector (optional).
80+ * @param {(filterId: string, value: string, setUrl: boolean) => void } config.onChangeCallback
81+ * - Callback to be triggered on the change event of the filter.
82+ * @param {(filterId: string, value: string, setUrl: boolean) => void } config.onInputCallback
83+ * - Callback to be triggered on the input event.
84+ * @param {(filterId: string, value: string, setUrl: boolean) => void } config.onEnterCallback
85+ * - Callback to be triggered when the Enter key is pressed.
86+ * @param {string } [config.width='.w-20'] - The CSS class that defines the width of the filter.
87+ * @returns {vnode } A virtual node element representing the filter element (input or grouped dropdown).
88+ */
89+ export const groupedDropdownComponent = ( {
90+ queryLabel,
91+ placeholder,
92+ id,
93+ filterMap,
94+ options = { } ,
95+ onChangeCallback,
96+ onInputCallback,
97+ onEnterCallback,
98+ type = 'text' ,
99+ width = '.w-20' ,
100+ } ) => {
101+ const groups = Object . keys ( options ) ;
102+ if ( ! groups . length ) {
103+ return filterInput ( { queryLabel, placeholder, id, filterMap, onInputCallback, onEnterCallback, type, width } ) ;
104+ }
105+
106+ const selectedOption = filterMap [ queryLabel ] ;
107+ const validValue = Object . values ( options ) . flat ( ) . some ( ( option ) => option === selectedOption ) ;
108+ if ( selectedOption && ! validValue ) {
109+ onChangeCallback ( queryLabel , '' , true ) ;
110+ }
111+
112+ const sortedGroupedOptions = groups
113+ . sort ( ( a , b ) => a . localeCompare ( b ) ) // sort group labels
114+ . reduce ( ( acc , key ) => {
115+ // sort option names and add to accumulator
116+ acc [ key ] = [ ...options [ key ] ] . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
117+ return acc ;
118+ } , { } ) ;
119+
120+ return h ( `${ width } ` , [
121+ h ( 'select.form-control' , {
122+ placeholder,
123+ id,
124+ name : id ,
125+ value : validValue ? selectedOption : '' ,
126+ onchange : ( event ) => onChangeCallback ( queryLabel , event . target . value , true ) ,
127+ } , [
128+ h ( 'option' , { value : '' } , placeholder ) ,
129+ h ( 'hr' ) ,
130+ ...Object . entries ( sortedGroupedOptions ) . map ( ( [ key , value ] ) => h (
131+ 'optgroup' ,
132+ { label : key } ,
133+ value . map ( ( option ) => h ( 'option' , { value : option } , option ) ) ,
134+ ) ) ,
135+ ] ) ,
136+ ] ) ;
137+ } ;
138+
64139/**
65140 * Builds a filter input element that allows the user to specify a parameter to be used when querying objects.
66141 * This function renders a text input element with event handling for input and Enter key press.
@@ -69,8 +144,8 @@ export const dynamicSelector = (config) => {
69144 * @param {string } config.placeholder - The placeholder text to be displayed in the input field.
70145 * @param {string } config.id - The unique identifier for the input field.
71146 * @param {object } config.filterMap - Map of the current filter values.
72- * @param {Function } config.onInputCallback - Callback to be triggered on the input event.
73- * @param {Function } config.onEnterCallback - Callback to be triggered when the Enter key is pressed.
147+ * @param {oninput } config.onInputCallback - Callback to be triggered on the input event.
148+ * @param {onkeydown } config.onEnterCallback - Callback to be triggered when the Enter key is pressed.
74149 * @param {string } [config.type='text'] - The type of the filter element (e.g., 'text', 'number').
75150 * @param {string } [config.width='.w-20'] - The CSS class that defines the width of the filter.
76151 * @returns {vnode } - A virtual node element representing the filter input.
@@ -105,7 +180,7 @@ export const filterInput = (config) => {
105180 * @param {string } config.id - The unique identifier for the select field.
106181 * @param {object } config.filterMap - Map of the current filter values.
107182 * @param {Array<string> } config.options - List of available options to be shown in the dropdown.
108- * @param {Function } config.onChangeCallback - Callback to be triggered on the change event of the selector.
183+ * @param {onchange } config.onChangeCallback - Callback to be triggered on the change event of the selector.
109184 * @param {string } [config.width='.w-20'] - The CSS class that defines the width of the dropdown.
110185 * @returns {vnode } - A virtual node element representing the dropdown selector.
111186 */
@@ -139,9 +214,9 @@ const dropdownSelector = (config) => {
139214 * @param {object } config - Selector config ({ id, placeholder, width }).
140215 * @param {object } filterMap - Current filters (RunNumber or empty).
141216 * @param {RemoteData } options - Available ongoing runs.
142- * @param {Function } onChangeCallback - To change the selection and update the filterMap
143- * @param {Function } onEnterCallback - To trigger the filter
144- * @param {Function } [onFocusCallback] - To retrieve ongoing runs
217+ * @param {onchange } onChangeCallback - To change the selection and update the filterMap
218+ * @param {onkeydown } onEnterCallback - To trigger the filter
219+ * @param {onfocus } [onFocusCallback] - To retrieve ongoing runs
145220 * @returns {object } Virtual DOM node (hyperscript element).
146221 */
147222export const ongoingRunsSelector = ( config , filterMap , options , onChangeCallback , onEnterCallback , onFocusCallback ) => {
0 commit comments