diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index 59e56f1776..a74d334e5c 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -550,6 +550,55 @@ export class Et2SelectApp extends Et2StaticSelectMixin(Et2Select) // @ts-ignore TypeScript is not recognizing that this widget is a LitElement customElements.define("et2-select-app", Et2SelectApp); +export class Et2SelectTab extends Et2SelectApp +{ + constructor() + { + super(); + + this.allowFreeEntries = true; + } + + set value(new_value) + { + const values = Array.isArray(new_value) ? new_value : [new_value]; + const options = this.select_options; + values.forEach(value => { + if (!options.filter(option => option.value == value).length) + { + const matches = value.match(/^([a-z0-9]+)\-/i); + let option : SelectOption = {value: value, label: value}; + if (matches) + { + option = options.filter(option => option.value == matches[1])[0]; + option.value = value; + option.label += ' '+egw.lang('Tab'); + } + try { + const app = opener?.framework.getApplicationByName(value); + if (app && app.displayName) + { + option.label = app.displayName; + } + } + catch (e) { + // ignore security exception, if opener is not accessible + } + this.select_options.concat(option); + } + }) + super.value = new_value; + } + + get value() + { + return super.value; + } +} + +// @ts-ignore TypeScript is not recognizing that this widget is a LitElement +customElements.define("et2-select-tab", Et2SelectTab); + export class Et2SelectBitwise extends Et2StaticSelectMixin(Et2Select) { set value(new_value) diff --git a/api/js/etemplate/Et2Select/StaticOptions.ts b/api/js/etemplate/Et2Select/StaticOptions.ts index 907f71dfb9..fdcdb57ea6 100644 --- a/api/js/etemplate/Et2Select/StaticOptions.ts +++ b/api/js/etemplate/Et2Select/StaticOptions.ts @@ -44,7 +44,20 @@ export const Et2StaticSelectMixin = > get select_options() : SelectOption[] { // @ts-ignore - return [...super.select_options, ...this.static_options]; + const options = super.select_options || []; + // make sure result is unique + if (options.length && this.static_options.length) + { + const union = [].concat(options); + this.static_options.forEach(option => { + if (!union.filter(opt => opt.value == option.value).length) + { + union.concat(option); + } + }); + return union; + } + return [...options, ...(this.static_options || [])]; } set select_options(new_options) diff --git a/api/js/etemplate/Et2Widget/Et2Widget.ts b/api/js/etemplate/Et2Widget/Et2Widget.ts index 26ae2b9d30..540d765680 100644 --- a/api/js/etemplate/Et2Widget/Et2Widget.ts +++ b/api/js/etemplate/Et2Widget/Et2Widget.ts @@ -1313,7 +1313,7 @@ export function loadWebComponent(_nodeName : string, _template_node : Element|{[ let widget = document.createElement(_nodeName); widget.textContent = _template_node.textContent; - if (parent) widget.setParent(parent); + if (parent && typeof widget.setParent === 'function') widget.setParent(parent); // Set read-only. Doesn't really matter if it's a ro widget, but otherwise it needs set widget.readonly = readonly; diff --git a/api/src/Etemplate/Widget/Select.php b/api/src/Etemplate/Widget/Select.php index 3a7843f97b..84cf7340ad 100644 --- a/api/src/Etemplate/Widget/Select.php +++ b/api/src/Etemplate/Widget/Select.php @@ -143,7 +143,11 @@ class Select extends Etemplate\Widget public function validate($cname, array $expand, array $content, &$validated=array()) { $form_name = self::form_name($cname, $this->id, $expand); - $widget_type = $this->attrs['type'] ? $this->attrs['type'] : $this->type; + $widget_type = $this->attrs['type'] ?? $this->type; + if (substr($widget_type, 0, 4) === 'et2-') + { + $widget_type = substr($widget_type, 4); + } $multiple = $this->attrs['multiple'] || $this->getElementAttribute($form_name, 'multiple') || $this->getElementAttribute($form_name, 'rows') > 1; $ok = true; @@ -208,6 +212,17 @@ class Select extends Etemplate\Widget } break; + case 'select-app': + case 'select-tab': + if (!in_array($val, $allowed) && + !($widget_type === 'select-tab' && preg_match('/^[a-z0-9]+\-[a-z0-9]+$/i', $val, $matches) && in_array($matches[1], $allowed))) + { + self::set_validation_error($form_name, lang("'%1' is NOT a valid app-name ('%2')!", $val, implode("', '",$allowed)),''); + $value = ''; + break 2; + } + break; + default: if(!in_array($val, $allowed)) { diff --git a/preferences/inc/class.preferences_settings.inc.php b/preferences/inc/class.preferences_settings.inc.php index 60d6454be8..e377773fa8 100644 --- a/preferences/inc/class.preferences_settings.inc.php +++ b/preferences/inc/class.preferences_settings.inc.php @@ -458,6 +458,12 @@ class preferences_settings $setting['type'] = 'et2-select'; $tpl->setElementAttribute($tab . '[' . $setting['name'] . ']', 'multiple', true); break; + case 'select-tab': + case 'select-tabs': + $setting['type'] = 'et2-select-tab'; + $tpl->setElementAttribute($tab . '[' . $setting['name'] . ']', 'allowFreeEntries', true); + $tpl->setElementAttribute($tab . '[' . $setting['name'] . ']', 'multiple', $old_type === 'select-tabs'); + break; case 'color': $setting['type'] = 'et2-colorpicker'; break; @@ -715,4 +721,4 @@ class preferences_settings } return True; } -} +} \ No newline at end of file