diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index 3b81abf41f..7898400343 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -9,7 +9,7 @@ import {LionSelect} from "@lion/select"; -import {css, html, PropertyValues, render, repeat, TemplateResult} from "@lion/core"; +import {css, html, PropertyValues, TemplateResult} from "@lion/core"; import {cssImage} from "../Et2Widget/Et2Widget"; import {StaticOptions} from "./StaticOptions"; import {Et2widgetWithSelectMixin} from "./Et2WidgetWithSelectMixin"; @@ -73,37 +73,35 @@ export class Et2Select extends Et2WidgetWithSelect this.querySelector('select').append(...this.querySelectorAll('option')); // if _inputNode was not available by the time set_value() got called - if (this.getValue() !== this.modelValue) + if(this.getValue() !== this.modelValue) { this.set_value(this.modelValue); } } + /** + * Get the node where we're putting the selection options + * + * @returns {HTMLElement} + */ + get _optionTargetNode() : HTMLElement + { + return this._inputNode; + } + /** @param {import('@lion/core').PropertyValues } changedProperties */ updated(changedProperties : PropertyValues) { super.updated(changedProperties); - if(changedProperties.has('select_options')) - { - // Add in actual options as children to select - if(this._inputNode) - { - // We use this.get_select_options() instead of this.select_options so children can override - // This is how sub-types get their options in - render(html`${this._emptyLabelTemplate()} - ${repeat(this.get_select_options(), (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`, - this._inputNode - ); - } - } - if (changedProperties.has('select_options') || changedProperties.has("value") || changedProperties.has('empty_label')) + + if(changedProperties.has('select_options') || changedProperties.has("value") || changedProperties.has('empty_label')) { // 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) => option.value == this.modelValue).length === 0 && - !(typeof this.empty_label !== 'undefined' && (this.modelValue||"") === "")) + if(this.get_select_options().length > 0 && this.get_select_options().filter((option) => option.value == this.modelValue).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.get_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._inputNode.value = this.modelValue || ""; diff --git a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts index 68130fef72..22e7a8b172 100644 --- a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts +++ b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts @@ -9,7 +9,7 @@ import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {StaticOptions} from "./StaticOptions"; -import {dedupeMixin, html, PropertyValues, TemplateResult} from "@lion/core"; +import {dedupeMixin, html, PropertyValues, render, repeat, TemplateResult} from "@lion/core"; import {et2_readAttrWithDefault} from "../et2_core_xml"; import {find_select_options, SelectOption} from "./FindSelectOptions"; @@ -17,6 +17,37 @@ import {find_select_options, SelectOption} from "./FindSelectOptions"; * Base class for things that do selectbox type behaviour, to avoid putting too much or copying into read-only * selectboxes, also for common handling of properties for more special selectboxes. * + * As with most other widgets that extend Lion components, do not override render(). + * To extend this mixin, override: + * - _optionTargetNode(): Return the HTMLElement where the "options" go. + * - _optionTemplate(option:SelectOption): Renders the option. To use a special widget, use its tag in render. + * Select option: + * ```js + * return html` + * `; + * ``` + * + * + * or pass it off to a different WebComponent: + * + * ```js + * _optionTemplate(option:SelectOption) : TemplateResult + * { + * return html` + * `; + * } + * ``` + * + * Optionally, you can override: + * - _emptyLabelTemplate(): How to render the empty label + * - slots(): Most Lion components have an input slot where the tag is created. + * You can specify something else, or return {} to do your own thing. This is a little more complicated. You should + * also override _inputGroupInputTemplate() to do what you normally would in render(). + * + * + * Technical note: * LionSelect (and any other LionField) use slots to wrap a real DOM node. ET2 doesn't expect this, * so we have to create the input node (via slots()) and respect that it is _external_ to the Web Component. * This complicates things like adding the options, since we can't just override _inputGroupInputTemplate() @@ -65,6 +96,21 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) => { this.set_select_options(find_select_options(this)); } + + // Add in actual option tags to the DOM based on the new select_options + if(changedProperties.has('select_options')) + { + // 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))}`, + this._optionTargetNode + ); + } + } } /** @@ -121,19 +167,55 @@ export const Et2widgetWithSelectMixin = dedupeMixin((superclass) => return this.select_options; } + /** + * Get the node where we're putting the options + * + * If this were a normal selectbox, this would be just the