From 851ceff2bcf84099767280dc31f06619060bc7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Wittmann=20=E2=80=93=20Gestaltung=20=26=20Entwicklu?= =?UTF-8?q?ng?= Date: Sat, 11 Apr 2026 12:02:40 +0200 Subject: [PATCH] Add multiselect field type with SuperBoxSelect and configurable separator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new `clientconfig-multiselect` xtype backed by MODX's native SuperBoxSelect component (search, tag-pill UI, keyboard navigation) - Parse options string (label==value||…) into a local ArrayStore - Fix duplicate hidden inputs on save by reading getValue() per field - Add `clientconfig.multiselect_separator` system setting (default: ,) to control the separator stored in the database - Expose separator and admin flag to the JS context in home controller - Add expand-arrow CSS for the dropdown trigger button - Add translations for all supported languages (de, en, fi, fr, nl, pt, pt-br, ru, sv) --- _build/data/transport.settings.php | 1 + .../clientconfig/css/mgr/clientconfig.css | 31 ++++++++++++++- .../clientconfig/js/mgr/sections/home.js | 39 +++++++++++++++++++ .../clientconfig/js/mgr/widgets/combos.js | 1 + .../js/mgr/widgets/window.settings.js | 4 +- .../clientconfig/controllers/home.class.php | 4 +- .../clientconfig/docs/changelog.txt | 6 +++ .../clientconfig/lexicon/de/default.inc.php | 7 ++++ .../clientconfig/lexicon/en/default.inc.php | 4 ++ .../clientconfig/lexicon/fi/default.inc.php | 7 ++++ .../clientconfig/lexicon/fr/default.inc.php | 7 ++++ .../clientconfig/lexicon/nl/default.inc.php | 9 ++++- .../lexicon/pt-br/default.inc.php | 7 ++++ .../clientconfig/lexicon/pt/default.inc.php | 7 ++++ .../clientconfig/lexicon/ru/default.inc.php | 7 ++++ .../clientconfig/lexicon/sv/default.inc.php | 7 ++++ 16 files changed, 143 insertions(+), 5 deletions(-) diff --git a/_build/data/transport.settings.php b/_build/data/transport.settings.php index 90bd549..827b464 100644 --- a/_build/data/transport.settings.php +++ b/_build/data/transport.settings.php @@ -6,6 +6,7 @@ 'vertical_tabs' => false, 'context_aware' => false, 'google_fonts_api_key' => '', + 'multiselect_separator' => ',', ); $settings = array(); diff --git a/assets/components/clientconfig/css/mgr/clientconfig.css b/assets/components/clientconfig/css/mgr/clientconfig.css index afb1791..acfb30c 100644 --- a/assets/components/clientconfig/css/mgr/clientconfig.css +++ b/assets/components/clientconfig/css/mgr/clientconfig.css @@ -3,4 +3,33 @@ } .modx-tv-image-preview { margin-top: 7px; -} \ No newline at end of file +} + +/* Multiselect: expand (arrow-down) button */ +.x-superboxselect .x-superboxselect-btns .clientconfig-sbs-expand { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 30px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.x-superboxselect .x-superboxselect-btns .clientconfig-sbs-expand:before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + font-family: "Font Awesome 5 Free", "Font Awesome 5 Brands"; + font-weight: 900; + content: "\f078"; +} + +/* Shrink the btns container to only the expand button width */ +.x-superboxselect .x-superboxselect-btns:has(.clientconfig-sbs-expand) { + width: 30px; +} diff --git a/assets/components/clientconfig/js/mgr/sections/home.js b/assets/components/clientconfig/js/mgr/sections/home.js index eb9ab69..6d4409b 100755 --- a/assets/components/clientconfig/js/mgr/sections/home.js +++ b/assets/components/clientconfig/js/mgr/sections/home.js @@ -219,6 +219,37 @@ Ext.extend(ClientConfig.page.Home,MODx.Component,{ field.mode = 'local'; } + if (field.xtype === 'clientconfig-multiselect') { + var msOptions = value.options ? value.options.split('||') : []; + var sbsData = []; + Ext.each(msOptions, function(option) { + var parts = option.split('=='); + if (parts[1]) { + sbsData.push([parts[1].trim(), parts[0].trim()]); + } else { + sbsData.push([parts[0].trim(), parts[0].trim()]); + } + }); + field.xtype = 'superboxselect'; + field.store = new Ext.data.ArrayStore({ + mode : 'local', + fields: ['value', 'label'], + data : sbsData + }); + field.valueField = 'value'; + field.displayField = 'label'; + field.hiddenName = value.key; + field.mode = 'local'; + field.triggerAction = 'all'; + field.minChars = 0; + field.valueDelimiter = ClientConfig.multiselectSeparator || ','; + field.emptyText = _('clientconfig.multiselect.search'); + field.renderFieldBtns = true; + field.allowQueryAll = true; + field.clearBtnCls = 'x-hide-display'; + field.expandBtnCls = 'clientconfig-sbs-expand'; + } + // Remove all extra definitions for the line divider if (field.xtype === 'clientconfig-line') { field = { @@ -391,6 +422,14 @@ Ext.extend(ClientConfig.page.Home,MODx.Component,{ values[filePicker.name] = values['tv' + filePicker.name]; }, this); + // Fix multiselect: superboxselect creates one hidden input per item, + // which serialises as duplicate keys and breaks PHP. Overwrite with + // the single comma-separated string returned by getValue(). + var multiSelects = fp.find('xtype', 'superboxselect'); + Ext.each(multiSelects, function(ms) { + values[ms.hiddenName || ms.name] = ms.getValue(); + }, this); + fp.el.mask(_('saving')); MODx.Ajax.request({ url: ClientConfig.config.connectorUrl, diff --git a/assets/components/clientconfig/js/mgr/widgets/combos.js b/assets/components/clientconfig/js/mgr/widgets/combos.js index 5bd45ab..af7f7c8 100755 --- a/assets/components/clientconfig/js/mgr/widgets/combos.js +++ b/assets/components/clientconfig/js/mgr/widgets/combos.js @@ -48,6 +48,7 @@ ClientConfig.combo.FieldTypes = function(config) { ['timefield', _('clientconfig.xtype.timefield')], ['password', _('clientconfig.xtype.password')], ['modx-combo', _('clientconfig.xtype.combobox')], + ['clientconfig-multiselect', _('clientconfig.xtype.multiselect')], ['googlefontlist', _('clientconfig.xtype.googlefonts')], ['clientconfig-line', _('clientconfig.xtype.line')] ] diff --git a/assets/components/clientconfig/js/mgr/widgets/window.settings.js b/assets/components/clientconfig/js/mgr/widgets/window.settings.js index 48f4f7b..5b8d754 100755 --- a/assets/components/clientconfig/js/mgr/widgets/window.settings.js +++ b/assets/components/clientconfig/js/mgr/widgets/window.settings.js @@ -71,7 +71,7 @@ ClientConfig.window.Setting = function(config) { anchor: '100%', listeners: { select: {fn: function(field, record) { - if (record.data.xtype === 'modx-combo') { + if (['modx-combo', 'clientconfig-multiselect'].indexOf(record.data.xtype) !== -1) { Ext.getCmp(config.id + '-options').show(); Ext.getCmp(config.id + '-process_options').show(); } else { @@ -97,7 +97,7 @@ ClientConfig.window.Setting = function(config) { fieldLabel: _('clientconfig.options'), description: _('clientconfig.options.desc'), anchor: '100%', - hidden: (config.record && (config.record.xtype === 'modx-combo')) ? false : true + hidden: (config.record && (['modx-combo', 'clientconfig-multiselect'].indexOf(config.record.xtype) !== -1)) ? false : true },{ xtype: 'checkbox', id: config.id + '-process_options', diff --git a/core/components/clientconfig/controllers/home.class.php b/core/components/clientconfig/controllers/home.class.php index 3033e7c..d3f5067 100755 --- a/core/components/clientconfig/controllers/home.class.php +++ b/core/components/clientconfig/controllers/home.class.php @@ -42,7 +42,7 @@ public function process(array $scriptProperties = []) { $googleFontsApiKey = $this->modx->getOption('clientconfig.google_fonts_api_key', null, ''); $sa['xtype'] = empty($googleFontsApiKey) ? 'textfield' : $sa['xtype']; } - elseif ($sa['xtype'] === 'modx-combo' && $setting->get('process_options')) { + elseif (in_array($sa['xtype'], ['modx-combo', 'clientconfig-multiselect'], true) && $setting->get('process_options')) { $inputOpts = $setting->get('options'); $this->modx->getParser(); $this->modx->parser->processElementTags('', $inputOpts, true, true); @@ -94,11 +94,13 @@ public function loadCustomCssJs() { $this->addLastJavascript($this->clientconfig->config['jsUrl'].'mgr/sections/home.js'); $contextAware = $this->modx->getOption('clientconfig.context_aware') ? 'true' : 'false'; + $multiselectSeparator = $this->modx->getOption('clientconfig.multiselect_separator', null, ','); $this->addHtml(''); diff --git a/core/components/clientconfig/docs/changelog.txt b/core/components/clientconfig/docs/changelog.txt index fa5e4cd..3248267 100644 --- a/core/components/clientconfig/docs/changelog.txt +++ b/core/components/clientconfig/docs/changelog.txt @@ -1,3 +1,9 @@ +ClientConfig 2.6.0-rc1 +---------------------- +Released on 2026-04-10 + +- Add Multi-select field type using MODX native SuperBoxSelect component with search, translated into all supported languages + ClientConfig 2.5.0-pl ---------------------- Released on 2025-02-01 diff --git a/core/components/clientconfig/lexicon/de/default.inc.php b/core/components/clientconfig/lexicon/de/default.inc.php index bc7ee4e..3c7409b 100644 --- a/core/components/clientconfig/lexicon/de/default.inc.php +++ b/core/components/clientconfig/lexicon/de/default.inc.php @@ -108,3 +108,10 @@ $_lang['clientconfig.config_for_context'] = 'Konfiguration für [[+context]]'; $_lang['clientconfig.categories'] = 'Kategorien'; $_lang['clientconfig.process_options'] = 'MODX-Syntax in Optionen auswerten'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Mehrfachauswahl'; +$_lang['clientconfig.multiselect.search'] = 'Suchen…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Mehrfachauswahl-Trennzeichen'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Das Trennzeichen, das beim Speichern eines Mehrfachauswahl-Felds in der Datenbank verwendet wird. Standard: , (Komma)'; diff --git a/core/components/clientconfig/lexicon/en/default.inc.php b/core/components/clientconfig/lexicon/en/default.inc.php index d253686..3cd09ec 100755 --- a/core/components/clientconfig/lexicon/en/default.inc.php +++ b/core/components/clientconfig/lexicon/en/default.inc.php @@ -65,6 +65,10 @@ $_lang['clientconfig.xtype.datefield'] = 'Date'; $_lang['clientconfig.xtype.timefield'] = 'Time'; $_lang['clientconfig.xtype.combobox'] = 'Selectbox'; +$_lang['clientconfig.xtype.multiselect'] = 'Multi-select'; +$_lang['clientconfig.multiselect.search'] = 'Search…'; +$_lang['clientconfig.setting.multiselect_separator'] = 'Multi-select separator'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'The separator character used to join selected values when saving a multi-select field to the database. Default: , (comma)'; $_lang['clientconfig.xtype.image'] = 'Image'; $_lang['clientconfig.xtype.googlefonts'] = 'Google Font List'; $_lang['clientconfig.to_client_view'] = 'To Client View'; diff --git a/core/components/clientconfig/lexicon/fi/default.inc.php b/core/components/clientconfig/lexicon/fi/default.inc.php index 3c2233d..44cacfd 100644 --- a/core/components/clientconfig/lexicon/fi/default.inc.php +++ b/core/components/clientconfig/lexicon/fi/default.inc.php @@ -109,3 +109,10 @@ $_lang['clientconfig.config_for_context'] = 'Asetus [[+context]]:lle'; $_lang['clientconfig.categories'] = 'Kategoriat'; $_lang['clientconfig.process_options'] = 'Käsittele tagit optioissa'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Monivalinta'; +$_lang['clientconfig.multiselect.search'] = 'Hae…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Monivalinnan erotin'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Erotin, jota käytetään monivalintakentän arvojen tallentamiseen tietokantaan. Oletus: , (pilkku)'; diff --git a/core/components/clientconfig/lexicon/fr/default.inc.php b/core/components/clientconfig/lexicon/fr/default.inc.php index b5a4697..30ac984 100755 --- a/core/components/clientconfig/lexicon/fr/default.inc.php +++ b/core/components/clientconfig/lexicon/fr/default.inc.php @@ -97,3 +97,10 @@ $_lang['clientconfig.error.not_an_export'] = 'Le fichier téléchargé n\'est pas un fichier d\'export valide.'; $_lang['clientconfig.error.importing_row'] = 'Un incident lors de la sauvegarde d\'une donnée importée est apparu : '; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Sélection multiple'; +$_lang['clientconfig.multiselect.search'] = 'Rechercher…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Séparateur de sélection multiple'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Le caractère séparateur utilisé pour enregistrer les valeurs d\'un champ à sélection multiple en base de données. Défaut : , (virgule)'; diff --git a/core/components/clientconfig/lexicon/nl/default.inc.php b/core/components/clientconfig/lexicon/nl/default.inc.php index 0fb7829..dcaf255 100644 --- a/core/components/clientconfig/lexicon/nl/default.inc.php +++ b/core/components/clientconfig/lexicon/nl/default.inc.php @@ -109,4 +109,11 @@ $_lang['clientconfig.global_values'] = 'Globaal'; $_lang['clientconfig.config_for_context'] = 'Configuratie voor [[+context]]'; $_lang['clientconfig.categories'] = 'Categoriën'; -$_lang['clientconfig.process_options'] = 'Verwerk tags in de opties'; \ No newline at end of file +$_lang['clientconfig.process_options'] = 'Verwerk tags in de opties'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Meervoudige selectie'; +$_lang['clientconfig.multiselect.search'] = 'Zoeken…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Scheidingsteken voor meervoudige selectie'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Het scheidingsteken dat wordt gebruikt bij het opslaan van een meervoudige selectieveld in de database. Standaard: , (komma)'; diff --git a/core/components/clientconfig/lexicon/pt-br/default.inc.php b/core/components/clientconfig/lexicon/pt-br/default.inc.php index b4fe3ff..42aafa2 100644 --- a/core/components/clientconfig/lexicon/pt-br/default.inc.php +++ b/core/components/clientconfig/lexicon/pt-br/default.inc.php @@ -111,3 +111,10 @@ $_lang['clientconfig.config_for_context'] = 'Configuração para o contexto: [[+context]]'; $_lang['clientconfig.categories'] = 'Categorias'; $_lang['clientconfig.process_options'] = 'Processar Tags nas opções'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Seleção múltipla'; +$_lang['clientconfig.multiselect.search'] = 'Pesquisar…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Separador de seleção múltipla'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'O caractere separador usado para guardar os valores de um campo de seleção múltipla no banco de dados. Padrão: , (vírgula)'; diff --git a/core/components/clientconfig/lexicon/pt/default.inc.php b/core/components/clientconfig/lexicon/pt/default.inc.php index b4fe3ff..98aae82 100644 --- a/core/components/clientconfig/lexicon/pt/default.inc.php +++ b/core/components/clientconfig/lexicon/pt/default.inc.php @@ -111,3 +111,10 @@ $_lang['clientconfig.config_for_context'] = 'Configuração para o contexto: [[+context]]'; $_lang['clientconfig.categories'] = 'Categorias'; $_lang['clientconfig.process_options'] = 'Processar Tags nas opções'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Seleção múltipla'; +$_lang['clientconfig.multiselect.search'] = 'Pesquisar…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Separador de seleção múltipla'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'O caractere separador usado para guardar os valores de um campo de seleção múltipla na base de dados. Predefinição: , (vírgula)'; diff --git a/core/components/clientconfig/lexicon/ru/default.inc.php b/core/components/clientconfig/lexicon/ru/default.inc.php index cf5bfa0..8e42e45 100644 --- a/core/components/clientconfig/lexicon/ru/default.inc.php +++ b/core/components/clientconfig/lexicon/ru/default.inc.php @@ -112,3 +112,10 @@ $_lang['clientconfig.config_for_context'] = 'Конфигурация для [[+context]]'; $_lang['clientconfig.categories'] = 'Категории'; $_lang['clientconfig.process_options'] = 'Обрабатывать теги в параметрах'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Множественный выбор'; +$_lang['clientconfig.multiselect.search'] = 'Поиск…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Разделитель множественного выбора'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Символ-разделитель, используемый при сохранении значений поля множественного выбора в базе данных. По умолчанию: , (запятая)'; diff --git a/core/components/clientconfig/lexicon/sv/default.inc.php b/core/components/clientconfig/lexicon/sv/default.inc.php index 2c17155..313812c 100644 --- a/core/components/clientconfig/lexicon/sv/default.inc.php +++ b/core/components/clientconfig/lexicon/sv/default.inc.php @@ -69,3 +69,10 @@ $_lang['clientconfig.saved'] = 'Sparad'; $_lang['clientconfig.saved.text'] = 'Inställningarna har blivit sparade.'; $_lang['clientconfig.field_is_required'] = 'Detta alternativ kan inte lämnas tomt.'; + +// Multiselect field type +$_lang['clientconfig.xtype.multiselect'] = 'Flervalsruta'; +$_lang['clientconfig.multiselect.search'] = 'Sök…'; + +$_lang['clientconfig.setting.multiselect_separator'] = 'Avgränsare för flerval'; +$_lang['clientconfig.setting.multiselect_separator_desc'] = 'Tecknet som används för att sammanfoga valda värden när ett flervalsfält sparas i databasen. Standard: , (kommatecken)';