diff --git a/api/js/etemplate/Et2Nextmatch/ColumnSelection.ts b/api/js/etemplate/Et2Nextmatch/ColumnSelection.ts new file mode 100644 index 0000000000..970e20287a --- /dev/null +++ b/api/js/etemplate/Et2Nextmatch/ColumnSelection.ts @@ -0,0 +1,297 @@ +/** + * Column selector for nextmatch + */ +import {classMap, css, html, LitElement, PropertyValues, repeat, TemplateResult} from "@lion/core"; +import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; +import {et2_nextmatch_customfields} from "../et2_extension_nextmatch"; +import shoelace from "../Styles/shoelace"; +import {et2_dataview_column} from "../et2_dataview_model_columns"; +import {et2_customfields_list} from "../et2_extension_customfields"; +import Sortable from "sortablejs/modular/sortable.complete.esm"; +import {cssImage} from "../Et2Widget/Et2Widget"; +import {SlMenuItem} from "@shoelace-style/shoelace"; +import {Et2Select} from "../Et2Select/Et2Select"; + +export class Et2ColumnSelection extends Et2InputWidget(LitElement) +{ + static get styles() + { + return [ + // Parent (SlSelect) returns a single cssResult, not an array + super.styles, + shoelace, + css` + :host { + height: 100%; + display: flex; + flex-direction: column; + flex: 1 0 auto; + --icon-width: 20px; + } + .title { + font-size: var(--sl-font-size-large); + color: var(--sl-color-neutral-0); + background-color: var(--sl-color-primary-600); + } + .title sl-icon { + vertical-align: middle; + cursor: pointer; + } + sl-menu { + flex: 1 1 auto; + overflow-y: auto; + } + /* Drag handle on columns (not individual custom fields or search letter) */ + sl-menu > .select_row::part(base) { + padding-left: 10px; + } + sl-menu > .column::part(base) { + background-image: ${cssImage("splitter_vert")}; + background-position: 3px 1.5ex; + background-repeat: no-repeat; + cursor: grab; + } + /* Change vertical alignment of CF checkbox line to up with title, not middle */ + .custom_fields::part(base) { + align-items: baseline; + } + ` + ] + } + + static get properties() + { + return { + /** + * List of currently selected columns + */ + value: {type: Object}, + + autoRefresh: {type: Number} + } + } + + private __columns = []; + private __autoRefresh : number | false = false; + private sort : Sortable; + + constructor(...args : any[]) + { + super(...args); + + this.columnClickHandler = this.columnClickHandler.bind(this); + this.handleSelectAll = this.handleSelectAll.bind(this); + } + + connectedCallback() + { + super.connectedCallback(); + + this.updateComplete.then(() => + { + this.sort = Sortable.create(this.shadowRoot.querySelector('sl-menu'), { + ghostClass: 'ui-fav-sortable-placeholder', + draggable: 'sl-menu-item.column', + dataIdAttr: 'value', + direction: 'vertical', + delay: 25 + }); + }); + } + + protected firstUpdated(_changedProperties : PropertyValues) + { + super.firstUpdated(_changedProperties); + + if(this._autoRefreshNode) + { + this._autoRefreshNode.select_options = { + // Cause [unknown] problems with mail + 30: "30 seconds", + //60: "1 Minute", + 180: "3 Minutes", + 300: "5 Minutes", + 900: "15 Minutes", + 1800: "30 Minutes" + }; + } + + if(this._preferenceNode) + { + this._preferenceNode.select_options = [ + {value: 'default', label: 'Default', title: 'Set these columns as the default'}, + {value: 'reset', label: 'Reset', title: "Reset all user's column preferences"}, + {value: 'force', label: 'Force', title: 'Force column preference so users cannot change it'} + ]; + } + } + + protected render() : TemplateResult + { + return html`${this.headerTemplate()} + + ${repeat(this.__columns, (column) => column.id, (column) => this.rowTemplate(column))} + + + + ${this.footerTemplate()} + `; + } + + protected headerTemplate() + { + return html` +
+ + ${this.egw().lang("Select columns")} +
+ `; + } + + protected footerTemplate() + { + let autoRefresh = html` + + + `; + // Add default checkbox for admins + const apps = this.egw().user('apps'); + + return html` + ${this.__autoRefresh !== false ? autoRefresh : ''} + ${!apps['admin'] ? '' : html` + + ` + } + `; + } + + /** + * Template for each individual column + * + * @param column + * @returns {TemplateResult} + * @protected + */ + protected rowTemplate(column) : TemplateResult + { + let isCustom = column.widget?.instanceOf(et2_nextmatch_customfields) || false; + /* ?disabled=${column.visibility == et2_dataview_column.ET2_COL_VISIBILITY_DISABLED} */ + return html` + + ${column.caption} + + ${isCustom ? this.customFieldsTemplate(column) : ''} + `; + } + + /** + * Template for all custom fields + * Does not include "Custom fields", it's done as a regular column + * + * @param column + * @returns {TemplateResult} + * @protected + */ + protected customFieldsTemplate(column) : TemplateResult + { + // Custom fields get listed separately + let widget = column.widget; + if(jQuery.isEmptyObject((widget).customfields)) + { + // No customfields defined, don't show column + return html``; + } + return html` + + ${repeat(Object.values(widget.customfields), (field) => field.name, (field) => + { + return this.rowTemplate({ + id: et2_customfields_list.PREFIX + field.name, + caption: field.label, + visibility: (widget.fields[field.name] ? et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : false) + + }); + })} + `; + } + + columnClickHandler(event) + { + const item = event.detail.item; + + // Toggle checked state + item.checked = !item.checked; + } + + handleSelectAll(event) + { + let checked = (this.shadowRoot.querySelector("sl-menu-item")).checked || false; + this.shadowRoot.querySelectorAll('sl-menu-item').forEach((item) => {item.checked = !checked}); + } + + set columns(new_columns) + { + this.__columns = new_columns; + this.requestUpdate(); + } + + get value() + { + let value = []; + + this.sort.toArray().forEach((val) => + { + let column = this.__columns.find((col) => col.id == val); + let menuItem = this.shadowRoot.querySelector("[value='" + val + "']"); + if(column && menuItem) + { + if(menuItem.checked) + { + value.push(val); + } + if(column.widget?.customfields) + { + menuItem.querySelectorAll("[value][checked]").forEach((cf : SlMenuItem) => + { + value.push(cf.value); + }) + } + } + }); + return value; + } + + private get _autoRefreshNode() : Et2Select + { + return (this.shadowRoot?.querySelector("#nm_autorefresh")); + } + + private get _preferenceNode() : Et2Select + { + return (this.shadowRoot.querySelector("#default_preference")) + } + + get autoRefresh() : number + { + return parseInt(this._autoRefreshNode?.value.toString()) || 0; + } + + set autoRefresh(new_value : number) + { + this.__autoRefresh = new_value; + this.requestUpdate("autoRefresh"); + } +} + +customElements.define("et2-nextmatch-columnselection", Et2ColumnSelection); \ No newline at end of file diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index 61889f5431..d8921d3b1a 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -70,13 +70,13 @@ import {et2_template} from "./et2_widget_template"; import {egw} from "../jsapi/egw_global"; import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; import {egwIsMobile} from "../egw_action/egw_action_common.js"; -import Sortable from 'sortablejs/modular/sortable.complete.esm.js'; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; import {Et2Select} from "./Et2Select/Et2Select"; import {Et2Button} from "./Et2Button/Et2Button"; import {loadWebComponent} from "./Et2Widget/Et2Widget"; import {Et2AccountFilterHeader} from "./Nextmatch/Headers/AccountFilterHeader"; import {Et2SelectCategory} from "./Et2Select/Et2SelectCategory"; +import {Et2ColumnSelection} from "./Et2Nextmatch/ColumnSelection"; //import {et2_selectAccount} from "./et2_widget_SelectAccount"; let keep_import : Et2AccountFilterHeader @@ -1938,111 +1938,47 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 { var col = columnMgr.columns[i]; const widget = this.columns[i].widget; - - if(col.visibility == et2_dataview_column.ET2_COL_VISIBILITY_DISABLED || - col.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) - { - continue; - } - if(col.caption) - { - columns.push({value: col.id, label: col.caption}); - if(col.visibility == et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE) columns_selected.push(col.id); - } - // Custom fields get listed separately - if(widget.instanceOf(et2_nextmatch_customfields)) - { - if(jQuery.isEmptyObject((widget).customfields)) - { - // No customfields defined, don't show column - columns.pop(); - continue; - } - for(var field_name in (widget).customfields) - { - columns.push({ - value: et2_nextmatch_customfields.PREFIX + field_name, label: " - " + - (widget).customfields[field_name].label - }); - if(widget.options.fields[field_name]) - { - columns_selected.push(et2_customfields_list.PREFIX + field_name); - } - } - } + columns.push({...col, widget: widget}); } // Letter search if(this.options.settings.lettersearch) { - columns.push({value: LETTERS, label: egw.lang('Search letter')}); - if(this.header.lettersearch.is(':visible')) columns_selected.push(LETTERS); + columns.push({ + id: LETTERS, + caption: this.egw().lang('Search letter'), + visibility: (this.header.lettersearch.is(':visible') ? et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) + }); } // Build the popup - if(!this.selectPopup) + let selectPopup = new Et2ColumnSelection(); + selectPopup.setParent(this); + selectPopup.columns = columns; + if(!this.options.disable_autorefresh) { - const select = loadWebComponent("et2-select", { - id: "columns", - multiple: true, - rows: 8, - empty_label: this.egw().lang("select columns"), - selected_first: false - }, this); - // Don't let options run through the loading stuff - select.select_options = columns; - select.value = columns_selected; + selectPopup.autoRefresh = parseInt(this._get_autorefresh()); + } - let autoRefresh; - let sort; - if(!this.options.disable_autorefresh) + const okButton = loadWebComponent("et2-button", { + image: "check", + label: this.egw().lang("ok"), + slot: "buttons" + }, this); + okButton.onclick = function() + { + // Update visibility + const visibility = {}; + for(var i = 0; i < columnMgr.columns.length; i++) { - autoRefresh = loadWebComponent("et2-select", { - empty_label: "Refresh", - id: "nm_autorefresh", - statustext: egw.lang("Automatically refresh list"), - value: this._get_autorefresh() - }, this); - autoRefresh.select_options = { - // Cause [unknown] problems with mail - 30: "30 seconds", - //60: "1 Minute", - 180: "3 Minutes", - 300: "5 Minutes", - 900: "15 Minutes", - 1800: "30 Minutes" - }; - } - - const defaultCheck = loadWebComponent("et2-select", { - id: "nm_col_preference", - empty_label: "Preference", - value: this.options.settings.columns_forced ? 'force' : '' - }, this); - defaultCheck.select_options = [ - {value: 'default', label: 'Default', title: 'Set these columns as the default'}, - {value: 'reset', label: 'Reset', title: "Reset all user's column preferences"}, - {value: 'force', label: 'Force', title: 'Force column preference so users cannot change it'} - ]; - - const okButton = loadWebComponent("et2-button", { - image: "check", - label: this.egw().lang("ok") - }, this); - okButton.onclick = function() - { - // Update visibility - const visibility = {}; - for(var i = 0; i < columnMgr.columns.length; i++) + const col = columnMgr.columns[i]; + if(col.caption && col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT && + col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) { - const col = columnMgr.columns[i]; - if(col.caption && col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT && - col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) - { visibility[col.id] = {visible: false}; } } - const value = select.value; + const value = selectPopup.value; // Update & remove letter filter if(self.header.lettersearch) @@ -2058,7 +1994,6 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } self._set_lettersearch(show_letters); } - let column = 0; for(var i = 0; i < value.length; i++) { @@ -2097,105 +2032,42 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } columnMgr.setColumnVisibilitySet(visibility); - this.sortedColumnsList = []; - if(sort) - { - sort.toArray().forEach((data_id) => - { - const value = select.getValue(); - if(data_id.match(/^col_/) && value.indexOf(data_id) != -1) - { - const col_id = data_id.replace('col_', ''); - const col_widget = self.columns[col_id].widget; - if(col_widget.customfields) - { - self.sortedColumnsList.push(col_widget.id); - for(let field_name in col_widget.customfields) - { - if(jQuery.isEmptyObject(col_widget.options.fields) || col_widget.options.fields[field_name] == true) - { - self.sortedColumnsList.push(et2_customfields_list.PREFIX + field_name); - } - } - } - else - { - self.sortedColumnsList.push(self._getColumnName(col_widget)); - } - } - }); - } - // Hide popup self.selectPopup.toggle(); self.dataview.updateColumns(); // Auto refresh - self._set_autorefresh(autoRefresh ? autoRefresh.get_value() : 0); + self._set_autorefresh(selectPopup.autoRefresh); - // Set default or clear forced - if(show_letters) - { - self.activeFilters.selectcols.push('lettersearch'); - } - self.getInstanceManager().submit(); - - self.selectPopup = null; - }; - - const cancelButton = loadWebComponent("et2-button", { - label: this.egw().lang("cancel"), - image: "cancel" - }, this); - cancelButton.onclick = function() + // Set default or clear forced + if(show_letters) { - self.selectPopup.toggle(); - self.selectPopup = null; - }; - - select.updateComplete.then(() => - { - window.setTimeout(() => - { - sort = Sortable.create(select.shadowRoot.querySelector('.select__tags'), { - ghostClass: 'ui-fav-sortable-placeholder', - draggable: 'et2-tag', - dataIdAttr: 'value', - direction: 'vertical', - delay: 25, - - }); - }, 100); - }); - - const $footerWrap = jQuery(document.createElement("div")) - .addClass('dialogFooterToolbar') - .append(okButton) - .append(cancelButton); - this.selectPopup = jQuery(document.createElement("div")) - .addClass("colselection ui-dialog ui-widget-content") - .append(select) - .append($footerWrap) - .appendTo(this.innerDiv); - - // Add autorefresh - if(autoRefresh) - { - $footerWrap.append(autoRefresh); + self.activeFilters.selectcols.push('lettersearch'); } + self.getInstanceManager().submit(); - // Add default checkbox for admins - const apps = this.egw().user('apps'); - if(apps['admin']) - { - $footerWrap.append(defaultCheck); - } - } - else + self.selectPopup = null; + }; + + const cancelButton = loadWebComponent("et2-button", { + label: this.egw().lang("cancel"), + image: "cancel", + slot: "buttons" + }, this); + cancelButton.onclick = function() { - this.selectPopup.toggle(); - } + self.selectPopup.toggle(); + }; + + selectPopup.append(okButton); + selectPopup.append(cancelButton); + + this.selectPopup = jQuery(document.createElement("div")) + .addClass("colselection ui-dialog ui-widget-content") + .append(selectPopup) + .appendTo(this.innerDiv); + const t_position = jQuery(e.target).position(); const s_position = this.div.position(); const max_height = this.getDOMNode().getElementsByClassName('egwGridView_outer')[0]['tBodies'][0].clientHeight - @@ -3791,10 +3663,12 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext this.nextmatch.applyFilters(set); }); } + // Sometimes the filter does not display the current value + // Call sync to try to get it to display select.updateComplete.then(async() => { await select.updateComplete; - //select.syncValueFromItems(); + select.syncItemsFromValue(); }) return select; } diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css index 2c23367612..284b078d0f 100644 --- a/api/templates/default/etemplate2.css +++ b/api/templates/default/etemplate2.css @@ -4003,10 +4003,6 @@ tr.disableIfNoEPL { border: 2px dashed silver; } -.colselection [id*="columns"]::part(tags) { - max-height: 10em; -} - .colselection .dialogFooterToolbar { display: flex; flex-direction: row;