diff --git a/src/webcomponents/commons/catalog-browser-grid-config.js b/src/webcomponents/commons/catalog-browser-grid-config.js index 0fe96d27b1..aadee51598 100644 --- a/src/webcomponents/commons/catalog-browser-grid-config.js +++ b/src/webcomponents/commons/catalog-browser-grid-config.js @@ -228,9 +228,11 @@ export default class CatalogBrowserGridConfig extends LitElement { `; diff --git a/src/webcomponents/commons/filters/disease-panel-filter.js b/src/webcomponents/commons/filters/disease-panel-filter.js index e3d539c9ad..7d80e3e7f8 100644 --- a/src/webcomponents/commons/filters/disease-panel-filter.js +++ b/src/webcomponents/commons/filters/disease-panel-filter.js @@ -17,6 +17,7 @@ import {LitElement, html, nothing} from "lit"; import LitUtils from "../utils/lit-utils.js"; import "../forms/select-field-filter.js"; +import "../forms/select-field-filter2.js"; import "../forms/toggle-switch.js"; import "../forms/toggle-radio.js"; @@ -210,16 +211,17 @@ export default class DiseasePanelFilter extends LitElement { ` : nothing } - 5} - .multiple="${this.multiple || false}" .classes="${this.classes}" - .disabled="${this.disabled || false}" - separator="\n" + .config="${{ + tags: false, + multiple: this.multiple, + separator: "\n" + }}" @filterChange="${e => this.filterChange(e, "panel")}"> - + ${this.showSelectedPanels && this.panel?.length > 0 ? html` diff --git a/src/webcomponents/commons/forms/select-field-filter2.js b/src/webcomponents/commons/forms/select-field-filter2.js index cffa1bed30..b396704520 100644 --- a/src/webcomponents/commons/forms/select-field-filter2.js +++ b/src/webcomponents/commons/forms/select-field-filter2.js @@ -37,63 +37,34 @@ export default class SelectFieldFilter2 extends LitElement { value: { type: String }, - title: { - type: String - }, - placeholder: { - type: String - }, - multiple: { - type: Boolean - }, - all: { - type: Boolean - }, - disabled: { - type: Boolean - }, - required: { - type: Boolean - }, - maxOptions: { - type: Number - }, - liveSearch: { - type: Boolean - }, - forceSelection: { - type: Boolean - }, - classes: { - type: String - }, - size: { - type: Number, - }, - separator: { - type: String, - }, // the expected format is either an array of string or an array of objects {id, name} data: { type: Object + }, + config: { + type: Object } }; } _init() { this._prefix = UtilsNew.randomString(8); - // $.fn.selectpicker.Constructor.BootstrapVersion = "5"; - this.multiple = false; - this.all = false; this.data = []; this.classes = ""; - this.elm = this._prefix + "selectpicker"; - this.size = 20; // Default size - this.separator = ","; // Default separator } firstUpdated() { this.select = $("#" + this._prefix); + if (!this._config?.tags) { + this.customAdapter(); + } + } + + update(changedProperties) { + if (changedProperties.has("config")) { + this._config = {...this.getDefaultConfig(), ...this.config}; + } + super.update(changedProperties); } updated(changedProperties) { @@ -101,7 +72,8 @@ export default class SelectFieldFilter2 extends LitElement { this.loadData(); } - if (changedProperties.has("value") || changedProperties.has("disabled")) { + if (changedProperties.has("value")) { + // TODO: Figure out why this does not execute when config.tags are false. this.loadValueSelected(); } } @@ -124,42 +96,167 @@ export default class SelectFieldFilter2 extends LitElement { }); } }); - this.select.select2({ + const selectConfig = { theme: "bootstrap-5", + dropdownParent: document.querySelector(`#${this._prefix}`).parentElement, selectionCssClass: "select2--small", - tags: this._config?.freeTag ?? false, - multiple: this.multiple, - separator: this.separator, - placeholder: this.placeholder || "Select option(s)", - disabled: this.disabled, + multiple: this._config?.multiple, + placeholder: this._config?.placeholder, + disabled: false, width: "80%", data: options, - tokenSeparator: this.tokenSeparator, - selectOnClose: false, + tokenSeparator: this._config?.separator, + closeOnSelect: false, templateResult: e => this.optionsFormatter(e), - }) - .on("select2:select", e => this.filterChange(e)) - .on("select2:unselect", e => this.filterChange(e)); + ...this._config, + }; + + if (!this._config?.tags) { + const searchBox = {dropdownAdapter: $.fn.select2.amd.require("CustomDropdownAdapter")}; + const selectAdapter = { + templateSelection: data => { + return `Selected ${data.selected.length} out of ${data.all.length}`; + }, + // Make selection-box similar to single select + selectionAdapter: $.fn.select2.amd.require("CustomSelectionAdapter"), + ...this._config.liveSearch ? searchBox : {} + }; + + this.select.select2({...selectConfig, ...selectAdapter}) + .on("select2:select", e => this.filterChange(e)) + .on("select2:unselect", e => this.filterChange(e)); + + if (this.value) { + // temporal solution for now to load selected values + this.loadValueSelected(); + } + + } else { + this.select.select2({...selectConfig}) + .on("select2:select", e => this.filterChange(e)) + .on("select2:unselect", e => this.filterChange(e)); + + } - this.querySelector("span.select2-search.select2-search--inline").style = "display: none"; + // This hides the search field for basic select2 tags. + if (!this.config?.liveSearch && !this.config?.tags) { + this.querySelector("span.select2-search select2-search--dropdown").style.display = "none"; + } } } + customAdapter() { + + $.fn.select2.amd.define("CustomSelectionAdapter", [ + "select2/utils", + "select2/selection/multiple", + "select2/selection/placeholder", + "select2/selection/eventRelay", + "select2/selection/single", + ], (Utils, MultipleSelection, Placeholder, EventRelay, SingleSelection) => { + + // Decorates MultipleSelection with Placeholder + let adapter = Utils.Decorate(MultipleSelection, Placeholder); + // Decorates adapter with EventRelay - ensures events will continue to fire + // e.g. selected, changed + adapter = Utils.Decorate(adapter, EventRelay); + + adapter.prototype.render = function () { + // Use selection-box from SingleSelection adapter + // This implementation overrides the default implementation + const $selection = SingleSelection.prototype.render.call(this); + return $selection; + }; + + adapter.prototype.update = function (data) { + // copy and modify SingleSelection adapter + this.clear(); + + const $rendered = this.$selection.find(".select2-selection__rendered"); + const noItemsSelected = data.length === 0; + let formatted = ""; + + if (noItemsSelected) { + formatted = this.options.get("placeholder") || ""; + } else { + const itemsData = { + selected: data || [], + all: this.$element.find("option") || [] + }; + // Pass selected and all items to display method + // which calls templateSelection + formatted = this.display(itemsData, $rendered); + } + + $rendered.empty().append(formatted); + $rendered.prop("title", formatted); + }; + + return adapter; + }); + + $.fn.select2.amd.define("CustomDropdownAdapter", [ + "select2/utils", + "select2/dropdown", + "select2/dropdown/attachBody", + "select2/dropdown/attachContainer", + "select2/dropdown/search", + "select2/dropdown/minimumResultsForSearch", + "select2/dropdown/closeOnSelect", + ], (Utils, Dropdown, AttachBody, AttachContainer, Search, MinimumResultsForSearch, CloseOnSelect) => { + + // Decorate Dropdown with Search functionalities + const dropdownWithSearch = Utils.Decorate(Dropdown, Search); + dropdownWithSearch.prototype.render = function () { + // Copy and modify default search render method + const $rendered = Dropdown.prototype.render.call(this); + // Add ability for a placeholder in the search box + const placeholder = this.options.get("placeholderForSearch") || ""; + const $search = $( + ` + + ` + ); + + this.$searchContainer = $search; + this.$search = $search.find("input"); + + $rendered.prepend($search); + return $rendered; + }; + + // Decorate the dropdown+search with necessary containers + let adapter = Utils.Decorate(dropdownWithSearch, AttachContainer); + adapter = Utils.Decorate(adapter, AttachBody); + + return adapter; + }); + + } + optionsFormatter(item) { // optgroup elements if (typeof item.children != "undefined") { return $(` - ${item.text} +
+ + ${item.text} + `); } return $(` - ${item.text} +
+ ${item.text} + +
`); } loadValueSelected() { let val = ""; - if (this.value && this.multiple) { + if (this.value && this._config?.multiple) { val = Array.isArray(this.value) ? this.value : this.value.split(","); } else { val = UtilsNew.isNotUndefinedOrNull(this.value) ? this.value : ""; @@ -192,7 +289,7 @@ export default class SelectFieldFilter2 extends LitElement { let val = ""; if (selection && selection.length) { - if (this.multiple) { + if (this._config?.multiple) { val = selection.join(","); } } @@ -229,13 +326,34 @@ export default class SelectFieldFilter2 extends LitElement { `; } + renderStyle() { + return html` + + `; + } + render() { return html` -
+ ${this.renderStyle()} +
${this.all ? this.renderShowSelectAll() : nothing} @@ -244,7 +362,21 @@ export default class SelectFieldFilter2 extends LitElement { getDefaultConfig() { return { - + title: "", + separator: [","], + multiple: true, + all: false, + required: false, + liveSearch: true, + classes: "", + showSelectAll: false, + limit: 10, + disablePagination: false, + minimumInputLength: 0, + maxItems: 0, // maxOptions + disabled: false, + placeholder: "Select option(s)", + freeTag: false }; } diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-grid-config.js b/src/webcomponents/variant/interpretation/variant-interpreter-grid-config.js index 357315d168..70ae4d0674 100644 --- a/src/webcomponents/variant/interpretation/variant-interpreter-grid-config.js +++ b/src/webcomponents/variant/interpretation/variant-interpreter-grid-config.js @@ -246,9 +246,11 @@ export default class VariantInterpreterGridConfig extends LitElement { `;