From 5a8f7c3c709ad8d854583be8c4fa15eec939a709 Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 22 Sep 2022 11:15:25 -0600 Subject: [PATCH] More fixing of missing search options / tags Fixes new free entries don't show up after removing a free entry --- .../Et2Select/Et2WidgetWithSelectMixin.ts | 38 +++++++++++-- api/js/etemplate/Et2Select/SearchMixin.ts | 55 ++++++++++++++++--- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts index b4c687065c..424e67aae4 100644 --- a/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts +++ b/api/js/etemplate/Et2Select/Et2WidgetWithSelectMixin.ts @@ -8,7 +8,7 @@ */ import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget"; -import {html, LitElement, PropertyValues, render, repeat, TemplateResult} from "@lion/core"; +import {html, LitElement, PropertyValues, render, TemplateResult} from "@lion/core"; import {et2_readAttrWithDefault} from "../et2_core_xml"; import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions"; import {SearchMixinInterface} from "./SearchMixin"; @@ -134,13 +134,39 @@ export const Et2widgetWithSelectMixin = >(supe protected _renderOptions() { // Add in options as children to the target node - if(this._optionTargetNode) + if(!this._optionTargetNode) { - render(html`${this._emptyLabelTemplate()} - ${repeat(this.select_options, (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`, - this._optionTargetNode - ); + return Promise.resolve(); } + /** + * Doing all this garbage to get the options to always show up. + * If we just do `render(options, target)`, they only show up in the DOM the first time. If the + * same option comes back in a subsequent search, map() does not put it into the DOM. + * If we render into a new target, the options get rendered, but we have to wait for them to be + * rendered before we can do anything else with them. + */ + let temp_target = document.createElement("div"); + + let options = html`${this._emptyLabelTemplate()}${this.select_options.map(this._optionTemplate.bind(this))}`; + + render(options, temp_target); + return Promise.all(([...temp_target.querySelectorAll(":scope > *")].map(item => item.render))) + .then(() => + { + temp_target.querySelectorAll(":scope > *").forEach((item) => + { + // Avoid duplicate error + if(!this._optionTargetNode.querySelector("[value='" + item.value + "']")) + { + this._optionTargetNode.appendChild(item); + } + }); + if(typeof this.handleMenuSlotChange == "function") + { + this.handleMenuSlotChange(); + } + }); + } /** diff --git a/api/js/etemplate/Et2Select/SearchMixin.ts b/api/js/etemplate/Et2Select/SearchMixin.ts index 564d5aeb8b..f0eb8b86db 100644 --- a/api/js/etemplate/Et2Select/SearchMixin.ts +++ b/api/js/etemplate/Et2Select/SearchMixin.ts @@ -437,11 +437,46 @@ export const Et2WithSearchMixin = >(superclass return this.querySelectorAll(this.optionTag + ":not(.remote)"); } + /** + * Only remote options from search results + * @returns {NodeList} + * @protected + */ protected get remoteItems() : NodeList { return this.querySelectorAll(this.optionTag + ".remote"); } + /** + * Only free entries + * @returns {NodeList} + * @protected + */ + protected get freeEntries() : NodeList + { + return this.querySelectorAll(this.optionTag + ".freeEntry"); + } + + get search_options() : SelectOption[] + { + let options = []; + + if(this.allowFreeEntries) + { + this.freeEntries.forEach((item) => + { + options.push({value: item.value, label: item.textContent, class: item.classList.toString()}); + }) + } + // Any provided options + options = options.concat(this.__search_options); + + // Any kept remote options + options = options.concat(this._selected_remote); + + return options; + } + get value() { return super.value; @@ -897,6 +932,11 @@ export const Et2WithSearchMixin = >(superclass // Remove "no suggestions" target.querySelector(".no-results")?.remove(); + // Remove any previously selected remote options that aren't used anymore + this._selected_remote = this._selected_remote.filter((option) => + { + return this.value.indexOf(option.value) != -1; + }); // Remove remote options that aren't used let keepers = this._selected_remote.reduce((prev, current) => { @@ -1005,9 +1045,8 @@ export const Et2WithSearchMixin = >(superclass let target = this._optionTargetNode || this; if(target) { - // Keep local options first, add in remote options - // Include already selected remote entries, or they will be removed and we lose icon/class - this.select_options.concat(this._selected_remote).filter(function(item) + // Add in remote options, avoiding duplicates + this.select_options.filter(function(item) { let i = entries.findIndex(x => (x.value == item.value)); if(i <= -1) @@ -1020,11 +1059,8 @@ export const Et2WithSearchMixin = >(superclass let options = html`${entries.map(this._optionTemplate.bind(this))}`; /** - * Doing all this garbage to get the options to always show up. - * If we just do `render(options, target)`, they only show up in the DOM the first time. If the - * same option comes back in a subsequent search, it map() does not put it into the DOM. - * If we render into a new target, the options get rendered, but we have to wait for them to be - * rendered before we can do anything else with them. + * Add in new options. + * Rendering directly into target will remove existing options, which we don't need to do */ let temp_target = document.createElement("div"); @@ -1087,7 +1123,8 @@ export const Et2WithSearchMixin = >(superclass { this.__select_options.push({ value: text, - label: text + label: text, + class: "freeEntry" }); this.requestUpdate('select_options'); }