forked from extern/egroupware
Move some code out of Et2Select into parent mixin for better re-use
Any child that has selectbox type functionality (extends Et2WidgetWithSelectMixin) needs to implement: - _optionTargetNode() : HTMLElement - _optionTemplate(option : SelectOption) : TemplateResult
This commit is contained in:
parent
f0422b91ca
commit
aff71549a2
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import {LionSelect} from "@lion/select";
|
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 {cssImage} from "../Et2Widget/Et2Widget";
|
||||||
import {StaticOptions} from "./StaticOptions";
|
import {StaticOptions} from "./StaticOptions";
|
||||||
import {Et2widgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
|
import {Et2widgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
|
||||||
@ -73,37 +73,35 @@ export class Et2Select extends Et2WidgetWithSelect
|
|||||||
this.querySelector('select').append(...this.querySelectorAll('option'));
|
this.querySelector('select').append(...this.querySelectorAll('option'));
|
||||||
|
|
||||||
// if _inputNode was not available by the time set_value() got called
|
// 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);
|
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 */
|
/** @param {import('@lion/core').PropertyValues } changedProperties */
|
||||||
updated(changedProperties : PropertyValues)
|
updated(changedProperties : PropertyValues)
|
||||||
{
|
{
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if(changedProperties.has('select_options'))
|
|
||||||
{
|
if(changedProperties.has('select_options') || changedProperties.has("value") || changedProperties.has('empty_label'))
|
||||||
// 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'))
|
|
||||||
{
|
{
|
||||||
// value not in options AND NOT (having an empty label and value)
|
// 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 &&
|
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||"") === ""))
|
!(typeof this.empty_label !== 'undefined' && (this.modelValue || "") === ""))
|
||||||
{
|
{
|
||||||
// --> use first option
|
// --> 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
|
// Re-set value, the option for it may have just shown up
|
||||||
this._inputNode.value = this.modelValue || "";
|
this._inputNode.value = this.modelValue || "";
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
||||||
import {StaticOptions} from "./StaticOptions";
|
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 {et2_readAttrWithDefault} from "../et2_core_xml";
|
||||||
import {find_select_options, SelectOption} from "./FindSelectOptions";
|
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
|
* 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.
|
* 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`
|
||||||
|
* <option value="${option.value}" title="${option.title}" ?selected=${option.value == this.modelValue}>
|
||||||
|
* ${option.label}
|
||||||
|
* </option>`;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* or pass it off to a different WebComponent:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* _optionTemplate(option:SelectOption) : TemplateResult
|
||||||
|
* {
|
||||||
|
* return html`
|
||||||
|
* <special-option-tag .value=${option}></special-option-tag>`;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Optionally, you can override:
|
||||||
|
* - _emptyLabelTemplate(): How to render the empty label
|
||||||
|
* - slots(): Most Lion components have an input slot where the <input> 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,
|
* 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.
|
* 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()
|
* 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));
|
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;
|
return this.select_options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the node where we're putting the options
|
||||||
|
*
|
||||||
|
* If this were a normal selectbox, this would be just the <select> tag (this._inputNode) but in a more
|
||||||
|
* complicated widget, this could be anything.
|
||||||
|
*
|
||||||
|
* @overridable
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
get _optionTargetNode() : HTMLElement
|
||||||
|
{
|
||||||
|
return <HTMLElement><unknown>this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the "empty label", used when the selectbox does not currently have a value
|
* Render the "empty label", used when the selectbox does not currently have a value
|
||||||
*
|
*
|
||||||
* @returns {}
|
* @overridable
|
||||||
|
* @returns {TemplateResult}
|
||||||
*/
|
*/
|
||||||
_emptyLabelTemplate() : TemplateResult
|
_emptyLabelTemplate() : TemplateResult
|
||||||
{
|
{
|
||||||
return html`${this.empty_label}`;
|
return html`${this.empty_label}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single option
|
||||||
|
*
|
||||||
|
* Override this method to specify how to render each option.
|
||||||
|
* In a normal selectbox, this would be something like:
|
||||||
|
*```
|
||||||
|
* <option value="${option.value}" title="${option.title}" ?selected=${option.value == this.modelValue}>
|
||||||
|
* ${option.label}
|
||||||
|
* </option>`;
|
||||||
|
* ```
|
||||||
|
* but you can do whatever you need. To use a different WebComponent, just use its tag instead of "option".
|
||||||
|
* We should even be able to pass the whole SelectOption across
|
||||||
|
* ```
|
||||||
|
* <special-option .value=${option}></special-option>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @overridable
|
||||||
|
* @param {SelectOption} option
|
||||||
|
* @returns {TemplateResult}
|
||||||
|
*/
|
||||||
_optionTemplate(option : SelectOption) : TemplateResult
|
_optionTemplate(option : SelectOption) : TemplateResult
|
||||||
{
|
{
|
||||||
return html``;
|
return html`
|
||||||
|
<span>Override _optionTemplate(). ${option.value} => ${option.label}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user