From b9cca9c5ff90564109b2d6307f461b830ff07cd5 Mon Sep 17 00:00:00 2001
From: ralf <rb@egroupware.org>
Date: Wed, 16 Mar 2022 22:36:23 +0200
Subject: [PATCH] using set/get select_option plus a set_selection_options
 marked as deprecated - get_select_options seems to be nowhere in use, so I
 did not implement it - Et2Select* widgets with static options assign them in
 their constructor (like the r/o ones) - removed a jQuery.proxy call, which we
 dont want in new code

---
 api/js/etemplate/Et2Select/Et2Select.ts       | 95 ++++++++++++-------
 .../etemplate/Et2Select/Et2SelectAccount.ts   | 15 ++-
 .../etemplate/Et2Select/Et2SelectReadonly.ts  | 32 +++++--
 .../Et2Select/Et2WidgetWithSelectMixin.ts     | 41 +++++---
 api/js/etemplate/Et2Select/StaticOptions.ts   | 42 ++++----
 5 files changed, 147 insertions(+), 78 deletions(-)

diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts
index 0400e953ee..417b25de6a 100644
--- a/api/js/etemplate/Et2Select/Et2Select.ts
+++ b/api/js/etemplate/Et2Select/Et2Select.ts
@@ -134,7 +134,7 @@ export class Et2Select extends Et2InvokerMixin(Et2WidgetWithSelect)
 	 */
 	get value() : string|string[]
 	{
-		if (!this._inputNode || !this.select_options?.length)
+		if (!this._inputNode || !this.select_options.length)
 		{
 			return this.__value || '';
 		}
@@ -155,7 +155,7 @@ export class Et2Select extends Et2InvokerMixin(Et2WidgetWithSelect)
 	set value(value: string|string[])
 	{
 		// if not yet connected to dom can't change the value
-		if (this._inputNode && this.select_options?.length)
+		if (this._inputNode && this.select_options.length)
 		{
 			// split multiple comma-separated values for multiple or expand_multiple_rows
 			if (typeof value === 'string' && (this.multiple || this.expand_multiple_rows) && value.indexOf(',') !== -1)
@@ -252,11 +252,11 @@ export class Et2Select extends Et2InvokerMixin(Et2WidgetWithSelect)
 		{
 			const modelValueArr = Array.isArray(this.modelValue) ? this.modelValue : this.modelValue.split(',');
 			// value not in options AND NOT (having an empty label and value)
-			if(this.get_select_options().length > 0 && this.get_select_options().filter((option) => modelValueArr.find(val => val == option.value)).length === 0 &&
+			if(this.select_options.length > 0 && this.select_options.filter((option) => modelValueArr.find(val => val == option.value)).length === 0 &&
 				!(typeof this.empty_label !== 'undefined' && (this.modelValue || "") === ""))
 			{
 				// --> use first option
-				this.modelValue = "" + this.get_select_options()[0]?.value;	// ""+ to cast value of 0 to "0", to not replace with ""
+				this.modelValue = "" + this.select_options[0]?.value;	// ""+ to cast value of 0 to "0", to not replace with ""
 			}
 			// Re-set value, the option for it may have just shown up
 			this.value = this.modelValue || "";
@@ -304,9 +304,11 @@ customElements.define("et2-select", Et2Select);
 
 export class Et2SelectApp extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.app(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.app(this, {other: this.other || []});
 	}
 }
 
@@ -319,7 +321,7 @@ export class Et2SelectBitwise extends Et2Select
 	{
 		let oldValue = this._value;
 		let expanded_value = [];
-		let options = this.get_select_options();
+		let options = this.select_options;
 		for(let index in options)
 		{
 			let right = parseInt(options[index].value);
@@ -339,11 +341,12 @@ customElements.define("et2-select-bitwise", Et2SelectBitwise);
 
 export class Et2SelectBool extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.bool(this);
-	}
+		super();
 
+		this.select_options = so.bool(this);
+	}
 }
 
 // @ts-ignore TypeScript is not recognizing that this widget is a LitElement
@@ -351,9 +354,11 @@ customElements.define("et2-select-bool", Et2SelectBool);
 
 export class Et2SelectCategory extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.cat(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.cat(this, {other: this.other || []});
 	}
 }
 
@@ -362,9 +367,11 @@ customElements.define("et2-select-cat", Et2SelectCategory);
 
 export class Et2SelectPercent extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.percent(this, {});
+		super();
+
+		this.select_options = so.percent(this, {});
 	}
 }
 
@@ -373,9 +380,11 @@ customElements.define("et2-select-percent", Et2SelectPercent);
 
 export class Et2SelectCountry extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.country(this, {});
+		super();
+
+		this.select_options = so.country(this, {});
 	}
 }
 
@@ -384,9 +393,11 @@ customElements.define("et2-select-country", Et2SelectCountry);
 
 export class Et2SelectDay extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.day(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.day(this, {other: this.other || []});
 	}
 }
 
@@ -395,9 +406,11 @@ customElements.define("et2-select-day", Et2SelectDay);
 
 export class Et2SelectDayOfWeek extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.dow(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.dow(this, {other: this.other || []});
 	}
 }
 
@@ -406,9 +419,11 @@ customElements.define("et2-select-dow", Et2SelectDayOfWeek);
 
 export class Et2SelectHour extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.hour(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.hour(this, {other: this.other || []});
 	}
 }
 
@@ -417,9 +432,11 @@ customElements.define("et2-select-hour", Et2SelectHour);
 
 export class Et2SelectMonth extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.month(this);
+		super();
+
+		this.select_options = so.month(this);
 	}
 }
 
@@ -428,9 +445,11 @@ customElements.define("et2-select-month", Et2SelectMonth);
 
 export class Et2SelectNumber extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.number(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.number(this, {other: this.other || []});
 	}
 }
 
@@ -439,9 +458,11 @@ customElements.define("et2-select-number", Et2SelectNumber);
 
 export class Et2SelectPriority extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.priority(this);
+		super();
+
+		this.select_options = so.priority(this);
 	}
 }
 
@@ -450,9 +471,11 @@ customElements.define("et2-select-priority", Et2SelectPriority);
 
 export class Et2SelectState extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.state(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.state(this, {other: this.other || []});
 	}
 }
 
@@ -461,9 +484,11 @@ customElements.define("et2-select-state", Et2SelectState);
 
 export class Et2SelectTimezone extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.timezone(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.timezone(this, {other: this.other || []});
 	}
 }
 
@@ -472,9 +497,11 @@ customElements.define("et2-select-timezone", Et2SelectTimezone);
 
 export class Et2SelectYear extends Et2Select
 {
-	get_select_options() : SelectOption[]
+	constructor()
 	{
-		return so.year(this, {other: this.other || []});
+		super();
+
+		this.select_options = so.year(this, {other: this.other || []});
 	}
 }
 
diff --git a/api/js/etemplate/Et2Select/Et2SelectAccount.ts b/api/js/etemplate/Et2Select/Et2SelectAccount.ts
index 61095ede01..acc916c642 100644
--- a/api/js/etemplate/Et2Select/Et2SelectAccount.ts
+++ b/api/js/etemplate/Et2Select/Et2SelectAccount.ts
@@ -32,20 +32,25 @@ export class Et2SelectAccount extends Et2Select
 	{
 		super();
 
-		this.account_type = 'accounts';
+		this.__account_type = 'accounts';
 	}
 
-	set_account_type(type : AccountType)
+	set account_type(type : AccountType)
 	{
-		this.account_type = type;
+		this.__account_type = type;
 
-		this.set_select_options(this.get_select_options());
+		super.select_options = this.select_options;
+	}
+
+	get account_type() : AccountType
+	{
+		return this.__account_type;
 	}
 
 	/**
 	 * Get account info for select options from common client-side account cache
 	 */
-	get_select_options() : Array<SelectOption>
+	get select_options() : Array<SelectOption>
 	{
 		const type = this.egw().preference('account_selection', 'common');
 		if (type === 'none' && typeof egw.user('apps').admin === 'undefined')
diff --git a/api/js/etemplate/Et2Select/Et2SelectReadonly.ts b/api/js/etemplate/Et2Select/Et2SelectReadonly.ts
index 4fd5ff930c..902a1d2a89 100644
--- a/api/js/etemplate/Et2Select/Et2SelectReadonly.ts
+++ b/api/js/etemplate/Et2Select/Et2SelectReadonly.ts
@@ -55,7 +55,7 @@ li {
 	{
 		super();
 		this.type = "";
-		this.select_options = [];
+		this.__select_options = <SelectOption[]>[];
 	}
 
 	protected find_select_options(_attrs)
@@ -63,7 +63,7 @@ li {
 		let sel_options = find_select_options(this, _attrs['select_options']);
 		if(sel_options.length > 0)
 		{
-			this.set_select_options(sel_options);
+			this.select_options = sel_options;
 		}
 	}
 
@@ -86,6 +86,10 @@ li {
 		this.find_select_options(_attrs)
 	}
 
+	/**
+	 * @deprecated assign to value
+	 * @param value
+	 */
 	set_value(value)
 	{
 		this.value = value;
@@ -117,7 +121,7 @@ li {
 	 *
 	 * @param {SelectOption[]} new_options
 	 */
-	set_select_options(new_options : SelectOption[] | { [key : string] : string })
+	set select_options(new_options : SelectOption[] | { [key : string] : string })
 	{
 		if(!Array.isArray(new_options))
 		{
@@ -130,9 +134,25 @@ li {
 			this.select_options = fixed_options;
 			return;
 		}
+		this.__select_options = new_options;
+	}
+
+	/**
+	 * Set the select options
+	 *
+	 * @deprecated assign to select_options
+	 * @param new_options
+	 */
+	set_select_options(new_options : SelectOption[] | { [key : string] : string })
+	{
 		this.select_options = new_options;
 	}
 
+	get select_options() : SelectOption[] | { [key : string] : string }
+	{
+		return this.__select_options;
+	}
+
 	render()
 	{
 		if(!this.value)
@@ -144,7 +164,7 @@ li {
             <ul>
                 ${repeat(this.value, (val : string) => val, (val) =>
                 {
-                    let option = this.select_options.find(option => option.value == val);
+                    let option = (<SelectOption[]>this.select_options).find(option => option.value == val);
                     if(!option)
                     {
                         return "";
@@ -204,7 +224,7 @@ export class Et2SelectAccountReadonly extends Et2SelectReadonly
 		{
 			let account_name = null;
 			let option = <SelectOption>{value: id, label: id + " ..."};
-			this.select_options.push(option);
+			this.select_options = [].concat(this.select_options, option);
 			if(new_value && (account_name = this.egw().link_title('api-accounts', id)))
 			{
 				option.label = account_name;
@@ -264,7 +284,7 @@ export class Et2SelectBitwiseReadonly extends Et2SelectReadonly
             <ul>
                 ${repeat(new_value, (val : string) => val, (val) =>
                 {
-                    let option = this.select_options.find(option => option.value == val);
+                    let option = (<SelectOption[]>this.select_options).find(option => option.value == val);
                     if(!option)
                     {
                         return "";
diff --git a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts
index 22e7a8b172..9f9138db1f 100644
--- a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts
+++ b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts
@@ -73,7 +73,7 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 				 * Select box options
 				 *
 				 * Will be found automatically based on ID and type, or can be set explicitly in the template using
-				 * <option/> children, or using widget.set_select_options(SelectOption[])
+				 * <option/> children, or using widget.select_options = SelectOption[]
 				 */
 				select_options: Object,
 			}
@@ -83,7 +83,7 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 		{
 			super();
 
-			this.select_options = <StaticOptions[]>[];
+			this.__select_options = <StaticOptions[]>[];
 		}
 
 		/** @param {import('@lion/core').PropertyValues } changedProperties */
@@ -94,7 +94,8 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 			// If the ID changed (or was just set) find the select options
 			if(changedProperties.has("id"))
 			{
-				this.set_select_options(find_select_options(this));
+				const options = find_select_options(this);
+				if (options.length) this.select_options = options;
 			}
 
 			// Add in actual option tags to the DOM based on the new select_options
@@ -103,10 +104,8 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 				// Add in options as children to the target node
 				if(this._optionTargetNode)
 				{
-					// We use this.get_select_options() instead of this.select_options so children can override
-					// This is how the sub-types with static options (day of week, month, etc.) get their options in
 					render(html`${this._emptyLabelTemplate()}
-                            ${repeat(this.get_select_options(), (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`,
+                            ${repeat(<SelectOption[]>this.select_options, (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`,
 						this._optionTargetNode
 					);
 				}
@@ -143,10 +142,11 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 		/**
 		 * Set the select options
 		 *
-		 * @param {SelectOption[]} new_options
+		 * @param new_options
 		 */
-		set_select_options(new_options : SelectOption[] | { [key : string] : string })
+		set select_options(new_options : SelectOption[] | { [key : string] : string })
 		{
+			const old_options = this.__select_options;
 			if(!Array.isArray(new_options))
 			{
 				let fixed_options = [];
@@ -154,17 +154,29 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 				{
 					fixed_options.push({value: key, label: new_options[key]});
 				}
-				this.select_options = fixed_options;
+				this.__select_options = fixed_options;
 			}
 			else
 			{
-				this.select_options = new_options;
+				this.__select_options = new_options;
 			}
+			this.requestUpdate("select_options", old_options);
 		}
 
-		get_select_options()
+		/**
+		 * Set select options
+		 *
+		 * @deprecated assign to select_options
+		 * @param new_options
+		 */
+		set_select_options(new_options : SelectOption[] | { [key : string] : string })
 		{
-			return this.select_options;
+			this.select_options = new_options;
+		}
+
+		get select_options()
+		{
+			return this.__select_options;
 		}
 
 		/**
@@ -245,7 +257,10 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
 			{
 				new_options = find_select_options(this, {}, new_options);
 			}
-			this.set_select_options(new_options);
+			if (new_options.length)
+			{
+				this.select_options = new_options;
+			}
 		}
 	}
 	return Et2WidgetWithSelect;
diff --git a/api/js/etemplate/Et2Select/StaticOptions.ts b/api/js/etemplate/Et2Select/StaticOptions.ts
index 641cb2533f..f3e17c4554 100644
--- a/api/js/etemplate/Et2Select/StaticOptions.ts
+++ b/api/js/etemplate/Et2Select/StaticOptions.ts
@@ -10,7 +10,9 @@
 import {sprintf} from "../../egw_action/egw_action_common";
 import {Et2SelectReadonly} from "./Et2SelectReadonly";
 import {find_select_options, SelectOption} from "./FindSelectOptions";
-import {Et2WidgetWithSelect} from "./Et2Select";
+import {Et2Select, Et2WidgetWithSelect} from "./Et2Select";
+
+export type Et2SelectWidgets = Et2Select | Et2WidgetWithSelect | Et2SelectReadonly;
 
 /**
  * Some options change, or are too complicated to have twice, so we get the
@@ -25,7 +27,7 @@ import {Et2WidgetWithSelect} from "./Et2Select";
  */
 export class StaticOptions
 {
-	cached_server_side(widget : Et2WidgetWithSelect | Et2SelectReadonly, type : string, options_string) : SelectOption[]
+	cached_server_side(widget : Et2SelectWidgets, type : string, options_string) : SelectOption[]
 	{
 		// normalize options by removing trailing commas
 		options_string = options_string.replace(/,+$/, '');
@@ -93,14 +95,14 @@ export class StaticOptions
 				{
 					if(widget.value && widget && widget.get_value() !== widget.value)
 					{
-						egw.window.setTimeout(jQuery.proxy(function()
+						egw.window.setTimeout(function()
 						{
 							// Avoid errors if widget is destroyed before the timeout
 							if(this.widget && typeof this.widget.id !== 'undefined')
 							{
 								this.widget.set_value(this.widget.options.value);
 							}
-						}, {widget: widget}), 1);
+						}.bind({widget: widget}), 1);
 					}
 				}
 			}
@@ -108,7 +110,7 @@ export class StaticOptions
 		}
 	}
 
-	priority(widget : Et2WidgetWithSelect | Et2SelectReadonly) : SelectOption[]
+	priority(widget : Et2SelectWidgets) : SelectOption[]
 	{
 		return [
 			{value: "1", label: 'low'},
@@ -118,7 +120,7 @@ export class StaticOptions
 		];
 	}
 
-	bool(widget : Et2WidgetWithSelect | Et2SelectReadonly) : SelectOption[]
+	bool(widget : Et2SelectWidgets) : SelectOption[]
 	{
 		return [
 			{value: "0", label: 'no'},
@@ -126,7 +128,7 @@ export class StaticOptions
 		];
 	}
 
-	month(widget : Et2WidgetWithSelect | Et2SelectReadonly) : SelectOption[]
+	month(widget : Et2SelectWidgets) : SelectOption[]
 	{
 		return [
 			{value: "1", label: 'January'},
@@ -144,7 +146,7 @@ export class StaticOptions
 		];
 	}
 
-	number(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	number(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		if(typeof attrs.other != 'object')
 		{
@@ -180,7 +182,7 @@ export class StaticOptions
 		return options;
 	}
 
-	percent(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	percent(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		if(typeof attrs.other != 'object')
 		{
@@ -193,7 +195,7 @@ export class StaticOptions
 		return this.number(widget, attrs);
 	}
 
-	year(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	year(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		if(typeof attrs.other != 'object')
 		{
@@ -206,13 +208,13 @@ export class StaticOptions
 		return this.number(widget, attrs);
 	}
 
-	day(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	day(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		attrs.other = [1, 31, 1];
 		return this.number(widget, attrs);
 	}
 
-	hour(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	hour(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = [];
 		var timeformat = widget.egw().preference('common', 'timeformat');
@@ -228,13 +230,13 @@ export class StaticOptions
 		return options;
 	}
 
-	app(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	app(widget : Et2SelectWidgets | Et2Select, attrs) : SelectOption[]
 	{
 		var options = ',' + (attrs.other || []).join(',');
 		return this.cached_server_side(widget, 'select-app', options);
 	}
 
-	cat(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	cat(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		// Add in application, if not there
 		if(typeof attrs.other == 'undefined')
@@ -252,33 +254,33 @@ export class StaticOptions
 		return this.cached_server_side(widget, 'select-cat', options);
 	}
 
-	country(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	country(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = ',';
 		return this.cached_server_side(widget, 'select-country', options);
 	}
 
-	state(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	state(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = attrs.country_code ? attrs.country_code : 'de';
 		return this.cached_server_side(widget, 'select-state', options);
 	}
 
-	dow(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	dow(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = ',' + (attrs.other || []).join(',');
 		return this.cached_server_side(widget, 'select-dow', options);
 	}
 
-	lang(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	lang(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = ',' + (attrs.other || []).join(',');
 		return this.cached_server_side(widget, 'select-lang', options);
 	}
 
-	timezone(widget : Et2WidgetWithSelect | Et2SelectReadonly, attrs) : SelectOption[]
+	timezone(widget : Et2SelectWidgets, attrs) : SelectOption[]
 	{
 		var options = ',' + (attrs.other || []).join(',');
 		return this.cached_server_side(widget, 'select-timezone', options);
 	}
-}
+}
\ No newline at end of file