diff --git a/lib/public/components/Filters/RunsFilter/runDefinitionFilter.js b/lib/public/components/Filters/RunsFilter/runDefinitionFilter.js index d53ba62428..3ce13c83cf 100644 --- a/lib/public/components/Filters/RunsFilter/runDefinitionFilter.js +++ b/lib/public/components/Filters/RunsFilter/runDefinitionFilter.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -import { checkboxes } from '../common/filters/checkboxFilter.js'; +import { expandedSelectionFilter } from '../common/filters/checkboxFilter.js'; /** * Renders a list of checkboxes that lets the user look for runs with specific definition @@ -19,7 +19,7 @@ import { checkboxes } from '../common/filters/checkboxFilter.js'; * @param {RunDefinitionFilterModel} runDefinitionFilterModel run definition filter model * @return {Component} the filter */ -export const runDefinitionFilter = (runDefinitionFilterModel) => checkboxes( +export const runDefinitionFilter = (runDefinitionFilterModel) => expandedSelectionFilter( runDefinitionFilterModel.selectionModel, { selector: 'run-definition' }, ); diff --git a/lib/public/components/Filters/RunsFilter/runQualitiesFilter.js b/lib/public/components/Filters/RunsFilter/runQualitiesFilter.js index 8b9c7b3ba1..5f8ab2ae21 100644 --- a/lib/public/components/Filters/RunsFilter/runQualitiesFilter.js +++ b/lib/public/components/Filters/RunsFilter/runQualitiesFilter.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -import { checkboxes } from '../common/filters/checkboxFilter.js'; +import { expandedSelectionFilter } from '../common/filters/checkboxFilter.js'; /** * Renders a list of checkboxes that lets the user look for runs with specific quality @@ -19,4 +19,4 @@ import { checkboxes } from '../common/filters/checkboxFilter.js'; * @param {SelectionFilterModel} filterModel run quality filter model * @return {Component} the filter component */ -export const runQualitiesFilter = (filterModel) => checkboxes(filterModel.selectionModel); +export const runQualitiesFilter = (filterModel) => expandedSelectionFilter(filterModel.selectionModel); diff --git a/lib/public/components/Filters/common/filters/checkboxFilter.js b/lib/public/components/Filters/common/filters/checkboxFilter.js index dcfcb4a95b..592cc3a688 100644 --- a/lib/public/components/Filters/common/filters/checkboxFilter.js +++ b/lib/public/components/Filters/common/filters/checkboxFilter.js @@ -13,6 +13,7 @@ */ import { h } from '/js/src/index.js'; +import { selectionOptions } from '../../../common/selection/selectionOptions.js'; /** * A general component for generating checkboxes. @@ -47,19 +48,7 @@ export const checkboxFilter = (name, values, isChecked, onChange, additionalProp * @param {string} [additionalProperties.selector] input identifiers prefix * @return {Component} filter component */ -export const checkboxes = (selectionModel, additionalProperties = {}) => { - const { selector = 'checkboxes' } = additionalProperties; - - return h('.flex-row.flex-wrap', selectionModel.options.map((option) => h('.form-check.flex-grow', [ - h('input.form-check-input', { - id: `${selector}-checkbox-${option.value}`, - type: 'checkbox', - checked: selectionModel.isSelected(option), - onchange: () => selectionModel.isSelected(option) ? selectionModel.deselect(option) : selectionModel.select(option), - ...additionalProperties, - }), - h('label.form-check-label', { - for: `${selector}-checkbox-${option.value}`, - }, option.label || option.value), - ]))); -}; +export const expandedSelectionFilter = (selectionModel, additionalProperties = {}) => h( + '.flex-row.flex-wrap.gc3', + selectionOptions(selectionModel, { selectorPrefix: additionalProperties.selector }), +); diff --git a/lib/public/components/common/selection/dropdown/selectionDropdown.js b/lib/public/components/common/selection/dropdown/selectionDropdown.js index 7d595f2a25..e4d311894f 100644 --- a/lib/public/components/common/selection/dropdown/selectionDropdown.js +++ b/lib/public/components/common/selection/dropdown/selectionDropdown.js @@ -15,64 +15,7 @@ import { h, Observable } from '/js/src/index.js'; import spinner from '../../spinner.js'; import { cleanPrefix } from '../../../../utilities/cleanPrefix.js'; import { dropdown } from '../../popover/dropdown.js'; -import { filterSelectionOptions } from '../filterSelectionOptions.js'; - -/** - * Display the dropdown options component containing the search input ahd the available options - * - * @param {SelectionModel} selectionModel the selection model - * @param {string} filter eventual filter used to show only a subset of options - * @param {string} selectorPrefix the prefix used to generate DOM selectors - * @return {Component} the dropdown options component - */ -const filteredDropdownOptions = (selectionModel, filter, selectorPrefix) => { - /** - * Display a given option - * - * @param {SelectionOption} option the option to display - * @param {string} [selectorPrefix] prefix used to generate DOM selectors for the component - * @return {Component} the option's view - */ - const displayOption = (option, selectorPrefix) => { - const selector = option.selector ?? option.value; - - return h( - 'label.dropdown-option.form-check-label.flex-row.g2.ph2.pv1', - { key: selector }, - [ - h( - `input#${selectorPrefix}dropdown-option-${selector}`, - { - type: selectionModel.multiple || selectionModel.allowEmpty ? 'checkbox' : 'radio', - name: `${selectorPrefix}dropdown-option-${selectionModel.multiple ? selector : 'group'}`, - checked: selectionModel.isSelected(option), - onchange: (e) => e.target.checked ? selectionModel.select(option) : selectionModel.deselect(option), - }, - ), - option.label || option.value, - ], - ); - }; - - /** - * Displays the list of available options - * - * @param {SelectionOption[]} availableOptions the list of all the available options - * @return {Component} the options list - */ - const optionsList = (availableOptions) => { - if (availableOptions.length === 0) { - return h('.ph2.pv1', h('em', 'No options')); - } - - return availableOptions.map((option) => displayOption( - option, - selectorPrefix, - )); - }; - - return h('.dropdown-options', optionsList(filterSelectionOptions(selectionModel.options, filter))); -}; +import { selectionOptions } from '../selectionOptions.js'; /** * Display a selection component composed of a view of current selection plus a dropdown displaying available options @@ -151,7 +94,18 @@ export const selectionDropdown = (selectionDropdownModel, configuration) => { selectionDropdownModel.selectionModel.match({ NotAsked: () => null, Loading: () => spinner({ size: 2, absolute: false }), - Success: (selectionModel) => filteredDropdownOptions(selectionModel, selectionDropdownModel.searchInputContent, selectorPrefix), + Success: (selectionModel) => h( + '.dropdown-options', + selectionOptions( + selectionModel, + { + filter: selectionDropdownModel.searchInputContent, + placeholder: h('.ph2.pv1', h('em', 'No options')), + selectorPrefix, + labelClasses: ['dropdown-option', 'ph2', 'pv1'], + }, + ), + ), Failure: () => null, }), ], diff --git a/lib/public/components/common/selection/selectionOptions.js b/lib/public/components/common/selection/selectionOptions.js new file mode 100644 index 0000000000..f8c4724d69 --- /dev/null +++ b/lib/public/components/common/selection/selectionOptions.js @@ -0,0 +1,65 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { cleanPrefix } from '../../../utilities/cleanPrefix.js'; +import { filterSelectionOptions } from './filterSelectionOptions.js'; +import { h } from '/js/src/index.js'; + +/** + * Display the options of the given selection model + * + * @param {SelectionModel} selectionModel the selection model + * @param {object} [configuration] eventual configuration + * @param {string} [configuration.filter] if specified, only options with label (or value if no label) including this filter will be displayed + * @param {Component} [configuration.placeholder] if specified, this component will be returned if there is no option to display + * @param {string} [configuration.selectorPrefix] prefix to be used to construct elements selectors + * @param {string[]} [configuration.labelClasses] additional classes applied to label + * @return {Component} filter component + */ +export const selectionOptions = (selectionModel, configuration) => { + const { filter, placeholder = null, selectorPrefix, labelClasses: additionalLabelClasses = [] } = configuration || {}; + + const options = filterSelectionOptions(selectionModel.options, filter); + + if (options.length === 0) { + return placeholder; + } + + return options.map((option) => { + const selector = option.selector ?? option.value; + + const uniqueSelector = `${cleanPrefix(selectorPrefix)}option-${selector}`; + + const labelClasses = ['form-check-label', 'flex-row', 'g2', ...additionalLabelClasses]; + + return h( + `label.${labelClasses.join('.')}`, + { key: uniqueSelector }, + [ + h( + `input#${uniqueSelector}`, + { + id: uniqueSelector, + type: selectionModel.multiple || selectionModel.allowEmpty ? 'checkbox' : 'radio', + name: selectionModel.multiple || selectionModel.allowEmpty + ? uniqueSelector + : `${cleanPrefix(selectorPrefix)}option-group`, + checked: selectionModel.isSelected(option), + onchange: () => selectionModel.isSelected(option) ? selectionModel.deselect(option) : selectionModel.select(option), + }, + ), + option.label || option.value, + ], + ); + }); +}; diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index 7f4ae8aa69..3b412a0058 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -14,7 +14,7 @@ import { h } from '/js/src/index.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; -import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js'; +import { expandedSelectionFilter } from '../../../components/Filters/common/filters/checkboxFilter.js'; import { qcFlagTypeColoredBadge } from '../../../components/qcFlags/qcFlagTypeColoredBadge.js'; /** @@ -54,7 +54,7 @@ export const qcFlagTypesActiveColumns = { name: 'Bad', visible: true, sortable: true, - filter: ({ isBadFilterModel }) => checkboxes( + filter: ({ isBadFilterModel }) => expandedSelectionFilter( isBadFilterModel, { class: 'w-75 mt1', selector: 'qc-flag-type-bad-filter' }, ),