Et2Search: Fix some UI bugs

- Fix missing loading spinner while searching
Clear button was in the way, now hiding clear button when displaying the spinner

- Fix searching for the same thing twice displays no results the second time
repeat() and map() directives were not putting the DOM nodes back in, worked around by using a temp div to render into.  There were some lifecycle mistakes as well leading to collisions & re-doing.
This commit is contained in:
nathan 2022-09-14 15:46:59 -06:00
parent f9ced2c834
commit 56eb4d64ac
2 changed files with 80 additions and 46 deletions

View File

@ -118,6 +118,8 @@ export const Et2widgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
{ {
// Add in options as children to the target node // Add in options as children to the target node
this._renderOptions(); this._renderOptions();
// This is needed to display initial load value in some cases, like infolog nm header filters
if(this.handleMenuSlotChange) if(this.handleMenuSlotChange)
{ {
this.handleMenuSlotChange(); this.handleMenuSlotChange();

View File

@ -8,7 +8,7 @@
*/ */
import {css, html, LitElement, render, repeat, SlotMixin} from "@lion/core"; import {css, html, LitElement, render, SlotMixin} from "@lion/core";
import {cleanSelectOptions, SelectOption} from "./FindSelectOptions"; import {cleanSelectOptions, SelectOption} from "./FindSelectOptions";
import {Validator} from "@lion/form-core"; import {Validator} from "@lion/form-core";
import {Et2Tag} from "./Tag/Et2Tag"; import {Et2Tag} from "./Tag/Et2Tag";
@ -268,7 +268,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
this.defaultValidators = []; this.defaultValidators = [];
this._handleSelect = this._handleSelect.bind(this); this.handleMenuSelect = this.handleMenuSelect.bind(this);
this._handleChange = this._handleChange.bind(this); this._handleChange = this._handleChange.bind(this);
this._handleSearchBlur = this._handleSearchBlur.bind(this); this._handleSearchBlur = this._handleSearchBlur.bind(this);
this._handleClear = this._handleClear.bind(this); this._handleClear = this._handleClear.bind(this);
@ -450,16 +450,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
this.createFreeEntry(this.value); this.createFreeEntry(this.value);
} }
else if(this.multiple)
{
this.value.forEach((e) =>
{
if(!this._menuItems.find(o => o.value == e && !o.classList.contains('remote')))
{
this.createFreeEntry(e);
}
});
}
} }
protected fix_bad_value() protected fix_bad_value()
@ -483,7 +473,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
protected _bindListeners() protected _bindListeners()
{ {
this.addEventListener("sl-select", this._handleSelect);
this.addEventListener("sl-clear", this._handleClear) this.addEventListener("sl-clear", this._handleClear)
// Need our own change to catch the change event from search input // Need our own change to catch the change event from search input
@ -606,41 +595,45 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
/** /**
* An option was selected * An option was selected
*/ */
_handleSelect(event) handleMenuSelect(event)
{ {
// Need to keep the remote option - only if selected // Need to keep the remote option - only if selected
if(event.detail.item.classList.contains("remote") && !this._selected_remote.find(o => o.value == event.detail.item.value)) if(event.detail.item.classList.contains("remote") && !this._selected_remote.find(o => o.value == event.detail.item.value))
{ {
this._selected_remote.push({...event.detail.item.option}); this._selected_remote.push({...event.detail.item.option});
} }
super.handleMenuSelect(event);
// If they just chose one from the list, re-focus the search this.updateComplete.then(() =>
if(this.multiple && this.searchEnabled)
{ {
this._searchInputNode.focus(); // If they just chose one from the list, re-focus the search
this._searchInputNode.select(); if(this.multiple && this.searchEnabled)
// If we were overlapping, reset
if(this._activeControls.classList.contains("novalue"))
{ {
this.handleMenuShow(); this._searchInputNode.focus();
} this._searchInputNode.select();
// Scroll the new tag into view // If we were overlapping, reset
if(event.detail && event.detail.item) if(this._activeControls.classList.contains("novalue"))
{
this.updateComplete.then(() =>
{ {
this.shadowRoot.querySelector("et2-tag[value='" + event.detail.item.value + "']")?.scrollIntoView(); this.handleMenuShow();
}); }
// Scroll the new tag into view
if(event.detail && event.detail.item)
{
this.updateComplete.then(() =>
{
this.shadowRoot.querySelector("et2-tag[value='" + event.detail.item.value + "']")?.scrollIntoView();
});
}
} }
} else if(!this.multiple && this.searchEnabled)
else if(!this.multiple && this.searchEnabled) {
{ // Stop all the search stuff when they select an option
// Stop all the search stuff when they select an option // this shows all non-matching options again
// this shows all non-matching options again this._handleSearchAbort(event);
this._handleSearchAbort(event); }
} });
} }
/** /**
@ -675,7 +668,10 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
if(event.relatedTarget && this !== (<Element>event.relatedTarget).parentElement) if(event.relatedTarget && this !== (<Element>event.relatedTarget).parentElement)
{ {
await this.dropdown.hide(); await this.dropdown.hide();
event.relatedTarget.focus(); if(event.relatedTarget)
{
event.relatedTarget.focus();
}
} }
} }
@ -764,6 +760,13 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
spinner.slot = "suffix"; spinner.slot = "suffix";
this.appendChild(spinner); this.appendChild(spinner);
// Hide clear button
let clear_button = <HTMLElement>this._searchInputNode.shadowRoot.querySelector(".input__clear")
if(clear_button)
{
clear_button.style.display = "none";
}
// Start the searches // Start the searches
Promise.all([ Promise.all([
this.localSearch(this._searchInputNode.value, this.searchOptions), this.localSearch(this._searchInputNode.value, this.searchOptions),
@ -771,6 +774,11 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
]).then(() => ]).then(() =>
{ {
spinner.remove(); spinner.remove();
// Restore clear button
if(clear_button)
{
clear_button.style.display = "";
}
}); });
} }
@ -791,6 +799,13 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
return ":not([value='" + current.value + "'])"; return ":not([value='" + current.value + "'])";
}, ""); }, "");
target.querySelectorAll(".remote" + keepers).forEach(o => o.remove()); target.querySelectorAll(".remote" + keepers).forEach(o => o.remove());
target.childNodes.forEach((n) =>
{
if(n.nodeType == Node.COMMENT_NODE)
{
n.remove();
}
})
// Reset remaining options. It might be faster to re-create instead. // Reset remaining options. It might be faster to re-create instead.
this._menuItems.forEach((item) => this._menuItems.forEach((item) =>
@ -899,10 +914,32 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
return null; return null;
}); });
render(html`${repeat(<SelectOption[]>entries, (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`, let options = html`${entries.map(this._optionTemplate.bind(this))}`;
target
); /**
this.handleMenuSlotChange(); * 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.
*/
let temp_target = document.createElement("div");
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(!target.querySelector("[value='" + item.value + "']"))
{
target.appendChild(item);
}
})
this.handleMenuSlotChange();
});
} }
} }
@ -962,11 +999,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this.value = text; this.value = text;
} }
// Once added to options, add to value / tags
this.updateComplete.then(() =>
{
this.handleMenuSlotChange();
});
// If we were overlapping edit inputbox with the value display, reset // If we were overlapping edit inputbox with the value display, reset
if(!this.readonly && this._activeControls?.classList.contains("novalue")) if(!this.readonly && this._activeControls?.classList.contains("novalue"))
{ {