diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index ec838fbc5e..5569302307 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -689,7 +689,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) { return html`${repeat(this.select_options // Filter out empty values if we have empty label to avoid duplicates - .filter(o => this.emptyLabel ? o.value !== '' : o), this._groupTemplate.bind(this)) + .filter(o => this.emptyLabel ? o.value !== '' : o), o => o.value, this._groupTemplate.bind(this)) }`; } diff --git a/api/js/etemplate/Et2Select/SearchMixin.ts b/api/js/etemplate/Et2Select/SearchMixin.ts index c05125892d..96b59e70b6 100644 --- a/api/js/etemplate/Et2Select/SearchMixin.ts +++ b/api/js/etemplate/Et2Select/SearchMixin.ts @@ -15,6 +15,7 @@ import {StaticOptions} from "./StaticOptions"; import {dedupeMixin} from "@open-wc/dedupe-mixin"; import {SlOption} from "@shoelace-style/shoelace"; import {Et2Textbox} from "../Et2Textbox/Et2Textbox"; +import {until} from "lit/directives/until.js"; // Otherwise import gets stripped let keep_import : Et2Tag; @@ -428,18 +429,20 @@ export const Et2WithSearchMixin = dedupeMixin( return html` ${this._searchInputTemplate()} - ${this._moreResultsTemplate()} + ${until(this._moreResultsTemplate(), nothing)} ${this._noResultsTemplate()} `; } - protected _moreResultsTemplate() + protected async _moreResultsTemplate() { - if(this._total_result_count == 0 || this._total_result_count - this._remote_options.length == 0) + await this.updateComplete; + const moreCount = this._total_result_count - this.select?.querySelectorAll("sl-option.match").length; + if(this._total_result_count == 0 || moreCount == 0 || !this.select) { return nothing; } - const more = this.egw().lang("%1 more...", this._total_result_count - this._remote_options.length); + const more = this.egw().lang("%1 more...", moreCount); return html`${more}`; } @@ -1107,6 +1110,8 @@ export const Et2WithSearchMixin = dedupeMixin( this._remote_options = []; + this._total_result_count = 0; + // Not searching anymore, clear flag this.select_options.map((o) => o.isMatch = null); this.requestUpdate("select_options"); @@ -1179,7 +1184,7 @@ export const Et2WithSearchMixin = dedupeMixin( return option.label.toLowerCase().includes(lower_search) || option.value.includes(search) }); // Limit results - this._total_result_count = filtered.length; + this._total_result_count += filtered.length; if(filtered.length > Et2WidgetWithSearch.RESULT_LIMIT) { filtered.splice(Et2WidgetWithSearch.RESULT_LIMIT); @@ -1223,12 +1228,15 @@ export const Et2WithSearchMixin = dedupeMixin( { // If results have a total included, pull it out. // It will cause errors if left in the results - this._total_result_count = results.length; if(typeof results.total !== "undefined") { - this._total_result_count = results.total; + this._total_result_count += results.total; delete results.total; } + else + { + this._total_result_count += results.length; + } let entries = cleanSelectOptions(results); this.processRemoteResults(entries); return entries; diff --git a/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts b/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts index 6a77e3d55f..cfacefb6f0 100644 --- a/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts +++ b/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts @@ -53,19 +53,28 @@ export class Et2SelectAccount extends SelectAccountMixin(Et2StaticSelectMixin(Et // Start fetch of select_options const type = this.egw().preference('account_selection', 'common'); let fetch = []; + let process = (options) => + { + // Shallow copy to avoid re-using the same object. + // Uses more memory, but otherwise multiple selectboxes get "tied" together + let cleaned = cleanSelectOptions(options) + // slice to avoid problems with lots of accounts + .slice(0, /* Et2WidgetWithSearch.RESULT_LIMIT */ 100); + this.account_options = this.account_options.concat(cleaned); + }; // for primary_group we only display owngroups == own memberships, not other groups if(type === 'primary_group' && this.accountType !== 'accounts') { if(this.accountType === 'both') { - fetch.push(this.egw().accounts('accounts').then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))})); + fetch.push(this.egw().accounts('accounts').then(process)); } - fetch.push(this.egw().accounts('owngroups').then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))})); + fetch.push(this.egw().accounts('owngroups').then(process)); } - else + else if(["primary_group", "groupmembers"].includes(type)) { - fetch.push(this.egw().accounts(this.accountType).then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))})); + fetch.push(this.egw().accounts(this.accountType).then(process)); } this.fetchComplete = Promise.all(fetch); } diff --git a/api/js/etemplate/Et2Select/SelectAccountMixin.ts b/api/js/etemplate/Et2Select/SelectAccountMixin.ts index 1c35111b59..f83a6f62ea 100644 --- a/api/js/etemplate/Et2Select/SelectAccountMixin.ts +++ b/api/js/etemplate/Et2Select/SelectAccountMixin.ts @@ -110,7 +110,8 @@ export const SelectAccountMixin = >(superclass get select_options() { - return [...(this.account_options || []), ...super.select_options]; + return [...new Map([...this.account_options, ...(super.select_options || [])].map(item => + [item.value, item])).values()]; } set select_options(value : SelectOption[]) diff --git a/api/js/jsapi/egw_user.js b/api/js/jsapi/egw_user.js index b1e7c3303e..cf49b6b924 100644 --- a/api/js/jsapi/egw_user.js +++ b/api/js/jsapi/egw_user.js @@ -117,7 +117,7 @@ egw.extend('user', egw.MODULE_GLOBAL, function() { if (typeof data[t] === "object") { - accountStore[t] = jQuery.extend(true, [], data[t] || []); + accountStore[t] = (Array.isArray(data[t]) ? data[t]:Object.values(data[t]) ?? []).map(a => {a.value = ""+a.value; return a}); } } }