diff --git a/addressbook/templates/default/app.css b/addressbook/templates/default/app.css
index 9c691fa4d9..3553e7a4f6 100644
--- a/addressbook/templates/default/app.css
+++ b/addressbook/templates/default/app.css
@@ -108,10 +108,10 @@ div.city_state_postcode #addressbook-edit_adr_one_postalcode {margin-right: 5px
* adjust width of select-boxes in nextmatch
*/
#addressbook-index .filtersContainer {
- position: absolute;
- top: 0; /* Required for Chrome 76+ on Windows */
- left: 342px;
- right: 215px;
+ position: absolute;
+ top: 0; /* Required for Chrome 76+ on Windows */
+ left: 350px;
+ right: 233px;
}
#addressbook-index .filtersContainer select {
width: 31.5%;
diff --git a/addressbook/templates/pixelegg/app.css b/addressbook/templates/pixelegg/app.css
index 76c247c440..d564748fb1 100755
--- a/addressbook/templates/pixelegg/app.css
+++ b/addressbook/templates/pixelegg/app.css
@@ -126,8 +126,8 @@ div.city_state_postcode #addressbook-edit_adr_one_postalcode {
position: absolute;
top: 0;
/* Required for Chrome 76+ on Windows */
- left: 342px;
- right: 215px;
+ left: 350px;
+ right: 233px;
}
#addressbook-index .filtersContainer select {
width: 31.5%;
diff --git a/api/etemplate.php b/api/etemplate.php
index 6753f04adc..861c1243f6 100644
--- a/api/etemplate.php
+++ b/api/etemplate.php
@@ -82,23 +82,27 @@ function send_template()
list($matches[1], $matches[2]) = explode('-', $type[1], 2);
if (!empty($matches[2])) $matches[2] = '-'.$matches[2];
}
- static $legacy_options = array( // use "ignore" to ignore further comma-sep. values, otherwise they are all in last attribute
- 'select' => 'empty_label,ignore',
- 'select-account' => 'empty_label,account_type,ignore',
- 'select-number' => 'empty_label,min,max,interval,suffix',
- 'box' => ',cellpadding,cellspacing,keep',
- 'hbox' => 'cellpadding,cellspacing,keep',
- 'vbox' => 'cellpadding,cellspacing,keep',
- 'groupbox' => 'cellpadding,cellspacing,keep',
- 'checkbox' => 'selected_value,unselected_value,ro_true,ro_false',
- 'radio' => 'set_value,ro_true,ro_false',
- 'customfields' => 'sub-type,use-private,field-names',
- 'date' => 'data_format,ignore',
+ static $legacy_options = array(
+ // use "ignore" to ignore further comma-sep. values, otherwise they are all in last attribute
+ 'select' => 'empty_label,ignore',
+ 'select-account' => 'empty_label,account_type,ignore',
+ 'select-number' => 'empty_label,min,max,interval,suffix',
+ 'box' => ',cellpadding,cellspacing,keep',
+ 'hbox' => 'cellpadding,cellspacing,keep',
+ 'vbox' => 'cellpadding,cellspacing,keep',
+ 'groupbox' => 'cellpadding,cellspacing,keep',
+ 'checkbox' => 'selected_value,unselected_value,ro_true,ro_false',
+ 'radio' => 'set_value,ro_true,ro_false',
+ 'customfields' => 'sub-type,use-private,field-names',
+ 'date' => 'data_format,ignore',
// Legacy option "mode" was never implemented in et2
- 'description' => 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title',
- 'button' => 'image,ro_image',
- 'buttononly' => 'image,ro_image',
- 'link-entry' => 'only_app,application_list',
+ 'description' => 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title',
+ 'button' => 'image,ro_image',
+ 'buttononly' => 'image,ro_image',
+ 'link-entry' => 'only_app,application_list',
+ 'nextmatch-filterheader' => 'empty_label',
+ 'nextmatch-customfilter' => 'widget_type,widget_options',
+ 'nextmatch-accountfilter' => 'empty_label,account_type,ignore',
);
// prefer more specific type-subtype over just type
$names = $legacy_options[$matches[1] . $matches[2]] ?? $legacy_options[$matches[1]] ?? null;
@@ -229,6 +233,33 @@ function send_template()
return $replace;
}, $str);
+ // nextmatch headers
+ $str = preg_replace_callback('#<(nextmatch-)([^ ]+)(header|filter) ([^>]+?)/>#s', static function (array $matches)
+ {
+ preg_match_all('/(^|\s)([a-z0-9_-]+)="([^"]*)"/i', $matches[4], $attrs, PREG_PATTERN_ORDER);
+ $attrs = array_combine($attrs[2], $attrs[3]);
+
+ if(!$matches[2] || in_array($matches[2], ['sort']))
+ {
+ return $matches[0];
+ }
+ // No longer needed & type causes problems
+ unset($attrs['type'], $attrs['tags']);
+
+ if($matches[2] == 'taglist')
+ {
+ $matches[2] = "filter";
+ }
+
+ $replace = '';
+ return $replace;
+ }, $str);
+
// ^^^^^^^^^^^^^^^^ above widgets get transformed independent of legacy="true" set in overlay ^^^^^^^^^^^^^^^^^^
// eTemplate marked as legacy --> replace only some widgets (eg. requiring jQueryUI) with web-components
diff --git a/api/js/etemplate/Et2Nextmatch/Headers/AccountFilterHeader.ts b/api/js/etemplate/Et2Nextmatch/Headers/AccountFilterHeader.ts
new file mode 100644
index 0000000000..3f6bfc3080
--- /dev/null
+++ b/api/js/etemplate/Et2Nextmatch/Headers/AccountFilterHeader.ts
@@ -0,0 +1,19 @@
+import {Et2SelectAccount} from "../../Et2Select/Et2SelectAccount";
+import {et2_INextmatchHeader} from "../../et2_extension_nextmatch";
+import {FilterMixin} from "./FilterMixin";
+
+/**
+ * Filter by account
+ */
+export class Et2AccountFilterHeader extends FilterMixin(Et2SelectAccount) implements et2_INextmatchHeader
+{
+ constructor(...args : any[])
+ {
+ super();
+ this.hoist = true;
+ this.clearable = true;
+ }
+
+}
+
+customElements.define("et2-nextmatch-header-account", Et2AccountFilterHeader);
\ No newline at end of file
diff --git a/api/js/etemplate/Et2Nextmatch/Headers/CustomFilterHeader.ts b/api/js/etemplate/Et2Nextmatch/Headers/CustomFilterHeader.ts
new file mode 100644
index 0000000000..0c5cb8cf54
--- /dev/null
+++ b/api/js/etemplate/Et2Nextmatch/Headers/CustomFilterHeader.ts
@@ -0,0 +1,92 @@
+import {loadWebComponent} from "../../Et2Widget/Et2Widget";
+import {Et2Select} from "../../Et2Select/Et2Select";
+import {Et2InputWidget, Et2InputWidgetInterface} from "../../Et2InputWidget/Et2InputWidget";
+import {FilterMixin} from "./FilterMixin";
+import {html, LitElement} from "@lion/core";
+
+/**
+ * Filter by some other type of widget
+ * Acts as a wrapper around the other widget, but handles all the nm stuff here
+ * Any attributes set are passed to the filter widget
+ */
+export class Et2CustomFilterHeader extends FilterMixin(Et2InputWidget(LitElement))
+{
+ private widget_type : string;
+ private widget_options : {};
+ private filter_node : Et2InputWidgetInterface & LitElement;
+
+ static get properties()
+ {
+ return {
+ ...super.properties,
+
+ /**
+ * tag of widget we want to use to filter
+ */
+ widget_type: {type: String},
+
+ /**
+ * Attributes / properties used for the filter widget
+ */
+ widget_options: {type: Object}
+ };
+ }
+
+ constructor(...args : any[])
+ {
+ super();
+ this.widget_type = "et2-description";
+ this.widget_options = {};
+ }
+
+ transformAttributes(attrs)
+ {
+ super.transformAttributes(attrs);
+
+ switch(attrs.widget_type)
+ {
+ case "link-entry":
+ this.widget_type = 'et2-nextmatch-header-entry';
+ break;
+ default:
+ this.widget_type = attrs.widget_type;
+ // Prefer webcomponent, if legacy type was sent
+ if(window.customElements.get("et2-" + this.widget_type))
+ {
+ this.widget_type = "et2-" + this.widget_type;
+ }
+ }
+ // @ts-ignore TS doesn't know about this.getParent()
+ this.filter_node = loadWebComponent(this.widget_type, {...attrs, ...this.widget_options}, this);
+ if(this.filter_node instanceof Et2Select)
+ {
+ this.filter_node.hoist = true;
+ this.filter_node.clearable = true;
+ }
+ }
+
+ connectedCallback()
+ {
+ super.connectedCallback();
+ if(this.filter_node)
+ {
+ this.filter_node.updateComplete.then(() =>
+ {
+ this.filter_node.addEventListener("change", this.handleChange);
+ })
+ }
+ }
+
+ render()
+ {
+ return html`
+ `;
+ }
+
+ get value() { return this.filter_node?.value || undefined;}
+
+ set value(new_value) { this.filter_node.value = new_value;}
+
+}
+
+customElements.define("et2-nextmatch-header-custom", Et2CustomFilterHeader);
\ No newline at end of file
diff --git a/api/js/etemplate/Et2Nextmatch/Headers/EntryHeader.ts b/api/js/etemplate/Et2Nextmatch/Headers/EntryHeader.ts
new file mode 100644
index 0000000000..b88ee067e5
--- /dev/null
+++ b/api/js/etemplate/Et2Nextmatch/Headers/EntryHeader.ts
@@ -0,0 +1,43 @@
+import {et2_INextmatchHeader} from "../../et2_extension_nextmatch";
+import {FilterMixin} from "./FilterMixin";
+import {Et2LinkEntry} from "../../Et2Link/Et2LinkEntry";
+
+/**
+ * Filter using a selected entry
+ */
+export class Et2EntryFilterHeader extends FilterMixin(Et2LinkEntry) implements et2_INextmatchHeader
+{
+
+ /**
+ * Override to always return a string appname:id (or just id) for simple (one real selection)
+ * cases, parent returns an object. If multiple are selected, or anything other than app and
+ * id, the original parent value is returned.
+ */
+ get value()
+ {
+ let value = super.value;
+ if(typeof value == "object" && value != null)
+ {
+ if(!value.app || !value.id)
+ {
+ return null;
+ }
+
+ // If simple value, format it legacy string style, otherwise
+ // we return full value
+ if(typeof value.id == 'string')
+ {
+ value = value.app + ":" + value.id;
+ }
+ }
+ return value;
+ }
+
+ set value(new_value)
+ {
+ super.value = new_value;
+ }
+
+}
+
+customElements.define("et2-nextmatch-header-entry", Et2EntryFilterHeader);
diff --git a/api/js/etemplate/Et2Nextmatch/Headers/FilterHeader.ts b/api/js/etemplate/Et2Nextmatch/Headers/FilterHeader.ts
new file mode 100644
index 0000000000..c8bc14dee2
--- /dev/null
+++ b/api/js/etemplate/Et2Nextmatch/Headers/FilterHeader.ts
@@ -0,0 +1,18 @@
+import {et2_INextmatchHeader} from "../../et2_extension_nextmatch";
+import {Et2Select} from "../../Et2Select/Et2Select";
+import {FilterMixin} from "./FilterMixin";
+
+/**
+ * Filter from a provided list of options
+ */
+export class Et2FilterHeader extends FilterMixin(Et2Select) implements et2_INextmatchHeader
+{
+ constructor(...args : any[])
+ {
+ super(...args);
+ this.hoist = true;
+ this.clearable = true;
+ }
+}
+
+customElements.define("et2-nextmatch-header-filter", Et2FilterHeader);
diff --git a/api/js/etemplate/Et2Nextmatch/Headers/FilterMixin.ts b/api/js/etemplate/Et2Nextmatch/Headers/FilterMixin.ts
new file mode 100644
index 0000000000..21888e5e39
--- /dev/null
+++ b/api/js/etemplate/Et2Nextmatch/Headers/FilterMixin.ts
@@ -0,0 +1,76 @@
+import {egw} from "../../../jsapi/egw_global";
+import {et2_INextmatchHeader, et2_nextmatch} from "../../et2_extension_nextmatch";
+import {LitElement} from "@lion/core";
+
+// Export the Interface for TypeScript
+type Constructor = new (...args : any[]) => T;
+
+/**
+ * Base class for things that do filter type behaviour in nextmatch header
+ * Separated to keep things a little simpler.
+ *
+ * Currently I assume we're extending an Et2Select, so changes may need to be made for better abstraction
+ */
+export const FilterMixin = (superclass : T) => class extends superclass implements et2_INextmatchHeader
+{
+ private nextmatch : et2_nextmatch;
+
+ /**
+ * Override to add change handler
+ *
+ */
+ connectedCallback()
+ {
+ super.connectedCallback();
+
+ // Make sure there's an option for all
+ if(!this.empty_label && Array.isArray(this.select_options) && !this.select_options.find(o => o.value == ""))
+ {
+ this.empty_label = this.label ? this.label : egw.lang("All");
+ }
+
+ this.handleChange = this.handleChange.bind(this);
+
+ // Bind late, maybe that helps early change triggers?
+ this.updateComplete.then(() =>
+ {
+ this.addEventListener("change", this.handleChange);
+ });
+ }
+
+ disconnectedCallback()
+ {
+ super.disconnectedCallback();
+ this.removeEventListener("change", this.handleChange);
+ }
+
+ handleChange(event)
+ {
+ if(typeof this.nextmatch == 'undefined')
+ {
+ // Not fully set up yet
+ return;
+ }
+ let col_filter = {};
+ col_filter[this.id] = this.value;
+
+ this.nextmatch.applyFilters({col_filter: col_filter});
+ }
+
+ /**
+ * Set nextmatch is the function which has to be implemented for the
+ * et2_INextmatchHeader interface.
+ *
+ * @param {et2_nextmatch} _nextmatch
+ */
+ setNextmatch(_nextmatch : et2_nextmatch)
+ {
+ this.nextmatch = _nextmatch;
+
+ // Set current filter value from nextmatch settings
+ if(this.nextmatch.activeFilters.col_filter && this.nextmatch.activeFilters.col_filter[this.id])
+ {
+ this.set_value(this.nextmatch.activeFilters.col_filter[this.id]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts
index 9c7086445a..eb18f8def3 100644
--- a/api/js/etemplate/Et2Select/Et2Select.ts
+++ b/api/js/etemplate/Et2Select/Et2Select.ts
@@ -66,6 +66,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
css`
:host {
display: block;
+ flex: 1 0 auto;
--icon-width: 20px;
}
diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts
index 6305f675f5..8ba880d5c8 100644
--- a/api/js/etemplate/et2_extension_nextmatch.ts
+++ b/api/js/etemplate/et2_extension_nextmatch.ts
@@ -59,11 +59,9 @@ import {et2_nextmatch_controller} from "./et2_extension_nextmatch_controller";
import {et2_dataview} from "./et2_dataview";
import {et2_dataview_column} from "./et2_dataview_model_columns";
import {et2_customfields_list} from "./et2_extension_customfields";
-import {et2_link_entry, et2_link_to} from "./et2_widget_link";
+import {et2_link_to} from "./et2_widget_link";
import {et2_grid} from "./et2_widget_grid";
import {et2_dataview_grid} from "./et2_dataview_view_grid";
-import {et2_taglist} from "./et2_widget_taglist";
-import {et2_selectAccount} from "./et2_widget_selectAccount";
import {et2_dynheight} from "./et2_widget_dynheight";
import {et2_arrayMgr} from "./et2_core_arrayMgr";
import {et2_button} from "./et2_widget_button";
@@ -77,8 +75,11 @@ 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 {et2_selectAccount} from "./et2_widget_SelectAccount";
+let keep_import : Et2AccountFilterHeader
/**
* Interface all special nextmatch header elements have to implement.
@@ -1260,7 +1261,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
_widget.iterateOver(function(_widget)
{
- const label = self.egw().lang(_widget.options.label || _widget.options.empty_label || '');
+ const label = self.egw().lang(_widget.label || _widget.empty_label || _widget.options.label || _widget.options.empty_label || '');
if(!label) return; // skip empty, undefined or null labels
if(!result)
{
@@ -1999,18 +2000,18 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
autoRefresh = loadWebComponent("et2-select", {
empty_label: "Refresh",
id: "nm_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"
- },
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", {
@@ -2181,7 +2182,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
// Add autorefresh
if(autoRefresh)
{
- $footerWrap.append(autoRefresh.getSurroundings().getDOMNode(autoRefresh.getDOMNode()));
+ $footerWrap.append(autoRefresh);
}
// Add default checkbox for admins
@@ -2606,7 +2607,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
}
else if(bool)
{
- filter = this.header._build_select(filter_name, 'select',
+ filter = this.header._build_select(filter_name, 'et2-select',
this.settings[filter_name], this.settings[filter_name + '_no_lang']);
}
}
@@ -3354,9 +3355,9 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
private action_header : JQuery;
private search_box : JQuery;
- private category : any;
- private filter : et2_selectbox;
- private filter2 : et2_selectbox;
+ private category : Et2Select | Et2SelectCategory;
+ private filter : Et2Select;
+ private filter2 : Et2Select;
private right_div : JQuery;
private count : JQuery;
private count_total : JQuery;
@@ -3520,7 +3521,7 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
{
if(typeof settings.cat_id_label == 'undefined') settings.cat_id_label = '';
this.category = this._build_select('cat_id', settings.cat_is_select ?
- 'select' : 'select-cat', settings.cat_id, settings.cat_is_select !== true, {
+ 'et2-select' : 'et2-select-cat', settings.cat_id, settings.cat_is_select !== true, {
multiple: false,
tags: true,
class: "select-cat",
@@ -3531,13 +3532,13 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
// Filter 1
if(!settings.no_filter)
{
- this.filter = this._build_select('filter', 'select', settings.filter, settings.filter_no_lang);
+ this.filter = this._build_select('filter', 'et2-select', settings.filter, settings.filter_no_lang);
}
// Filter 2
if(!settings.no_filter2)
{
- this.filter2 = this._build_select('filter2', 'select', settings.filter2,
+ this.filter2 = this._build_select('filter2', 'et2-select', settings.filter2,
settings.filter2_no_lang, {
multiple: false,
tags: settings.filter2_tags,
@@ -3704,7 +3705,7 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
* @param {string} lang
* @param {object} extra
*/
- _build_select(name : string, type : string, value : string, lang : string | boolean, extra? : object) : et2_selectbox
+ _build_select(name : string, type : string, value : string, lang : string | boolean, extra? : object) : Et2Select
{
const widget_options = jQuery.extend({
"id": name,
@@ -3716,13 +3717,9 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
// Set select options
// Check in content for options-
const mgr = this.nextmatch.getArrayMgr("content");
- let options = mgr.getEntry("options-" + name);
- // Look in sel_options
- if(!options) options = this.nextmatch.getArrayMgr("sel_options").getEntry(name);
- // Check parent sel_options, because those are usually global and don't get passed down
- if(!options) options = this.nextmatch.getArrayMgr("sel_options").getParentMgr()?.getEntry(name);
+ let options = false
// Sometimes legacy stuff puts it in here
- if(!options) options = mgr.getEntry('rows[sel_options][' + name + ']');
+ options = mgr.getEntry('rows[sel_options][' + name + ']');
// Maybe in a row, and options got stuck in ${row} instead of top level
const row_stuck = ['${row}', '{$row}'];
@@ -3753,21 +3750,21 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
}
// Create widget
- const select = et2_createWidget(type, widget_options, this);
+ const select = loadWebComponent(type, widget_options, this);
- if(options) select.set_select_options(options);
+ if(options)
+ {
+ select.select_options = options;
+ }
// Set value
select.set_value(value);
// Set activeFilters to current value
- this.nextmatch.activeFilters[select.id] = select.get_value();
-
- // Set onChange
- const input = select.input;
+ this.nextmatch.activeFilters[select.id] = select.value;
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
- select.attributes.select_options.ignore = true;
+ //select.attributes.select_options.ignore = true;
if(this.nextmatch.options.settings[name + "_onchange"])
{
@@ -3782,18 +3779,23 @@ export class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INext
}
// Connect it to the onchange event of the input element - may submit
- select.change = et2_compileLegacyJS(onchange, this.nextmatch, select.getInputNode());
+ select.onchange = et2_compileLegacyJS(onchange, this.nextmatch, select.getInputNode());
this._bindHeaderInput(select);
}
else // default request changed rows with new filters, previous this.form.submit()
{
- input.change(this.nextmatch, function(event)
+ select.addEventListener("change", () =>
{
const set = {};
- set[name] = select.getValue();
- event.data.applyFilters(set);
+ set[select.id] = select.getValue();
+ this.nextmatch.applyFilters(set);
});
}
+ select.updateComplete.then(async() =>
+ {
+ await select.updateComplete;
+ //select.syncValueFromItems();
+ })
return select;
}
@@ -4236,8 +4238,8 @@ export class et2_nextmatch_customfields extends et2_customfields_list implements
{
delete (field.values['']);
}
- widget = et2_createWidget(
- field.type == 'select-account' ? 'nextmatch-accountfilter' : "nextmatch-filterheader",
+ widget = loadWebComponent(
+ field.type == 'select-account' ? 'et2-nextmatch-header-accountfilter' : "et2-nextmatch-header-filter",
{
id: cf_id,
empty_label: field.label,
@@ -4248,10 +4250,10 @@ export class et2_nextmatch_customfields extends et2_customfields_list implements
}
else if(apps[field.type])
{
- widget = et2_createWidget("nextmatch-entryheader", {
+ widget = loadWebComponent("et2-nextmatch-header-entry", {
id: cf_id,
only_app: field.type,
- blur: field.label
+ placeholder: field.label
}, this);
}
else
@@ -4263,7 +4265,7 @@ export class et2_nextmatch_customfields extends et2_customfields_list implements
}
// If this is already attached, widget needs to be finished explicitly
- if(this.isAttached() && !widget.isAttached())
+ if(this.isAttached() && typeof widget.isAttached == "function" && !widget.isAttached())
{
widget.loadingFinished();
}
@@ -4435,395 +4437,4 @@ export class et2_nextmatch_sortheader extends et2_nextmatch_header implements et
}
-et2_register_widget(et2_nextmatch_sortheader, ['nextmatch-sortheader']);
-
-/**
- * Filter from a provided list of options
- */
-export class et2_nextmatch_filterheader extends et2_selectbox implements et2_INextmatchHeader, et2_IResizeable
-{
- private nextmatch : et2_nextmatch;
-
- /**
- * Override to add change handler
- */
- createInputWidget()
- {
- // Make sure there's an option for all
- if(!this.options.empty_label && (!this.options.select_options || !this.options.select_options[""]))
- {
- this.options.empty_label = this.options.label ? this.options.label : egw.lang("All");
- }
- super.createInputWidget();
-
- jQuery(this.getInputNode()).change(this, function(event)
- {
- if(typeof event.data.nextmatch == 'undefined')
- {
- // Not fully set up yet
- return;
- }
- const col_filter = {};
- col_filter[event.data.id] = event.data.input.val();
- // Set value so it's there for response (otherwise it gets cleared if options are updated)
- event.data.set_value(event.data.input.val());
-
- event.data.nextmatch.applyFilters({col_filter: col_filter});
- });
-
- }
-
- /**
- * Set nextmatch is the function which has to be implemented for the
- * et2_INextmatchHeader interface.
- *
- * @param {et2_nextmatch} _nextmatch
- */
- setNextmatch(_nextmatch)
- {
- this.nextmatch = _nextmatch;
-
- // Set current filter value from nextmatch settings
- if(this.nextmatch.activeFilters.col_filter && typeof this.nextmatch.activeFilters.col_filter[this.id] != "undefined")
- {
- this.set_value(this.nextmatch.activeFilters.col_filter[this.id]);
-
- // Make sure it's set in the nextmatch
- _nextmatch.activeFilters.col_filter[this.id] = this.getValue();
- }
- }
-
- // Make sure selectbox is not longer than the column
- resize()
- {
- this.input.css("max-width", jQuery(this.parentNode).innerWidth() + "px");
- }
-
-}
-
-et2_register_widget(et2_nextmatch_filterheader, ['nextmatch-filterheader']);
-
-/**
- * Filter by account
- */
-export class et2_nextmatch_accountfilterheader extends et2_selectAccount implements et2_INextmatchHeader, et2_IResizeable
-{
- /**
- * Override to add change handler
- *
- */
- createInputWidget()
- {
- // Make sure there's an option for all
- if(!this.options.empty_label && !this.options.select_options[""])
- {
- this.options.empty_label = this.options.label ? this.options.label : egw.lang("All");
- }
- super.createInputWidget();
-
- this.input.change(this, function(event)
- {
- if(typeof event.data.nextmatch == 'undefined')
- {
- // Not fully set up yet
- return;
- }
- var col_filter = {};
- col_filter[event.data.id] = event.data.getValue();
- event.data.nextmatch.applyFilters({col_filter: col_filter});
- });
-
- }
-
- /**
- * Set nextmatch is the function which has to be implemented for the
- * et2_INextmatchHeader interface.
- *
- * @param {et2_nextmatch} _nextmatch
- */
- setNextmatch(_nextmatch)
- {
- this.nextmatch = _nextmatch;
-
- // Set current filter value from nextmatch settings
- if(this.nextmatch.activeFilters.col_filter && this.nextmatch.activeFilters.col_filter[this.id])
- {
- this.set_value(this.nextmatch.activeFilters.col_filter[this.id]);
- }
- }
-
- // Make sure selectbox is not longer than the column
- resize()
- {
- var max = jQuery(this.parentNode).innerWidth() - 4;
- var surroundings = this.getSurroundings()._widgetSurroundings;
- for(var i = 0; i < surroundings.length; i++)
- {
- max -= jQuery(surroundings[i]).outerWidth();
- }
- this.input.css("max-width", max + "px");
- }
-
-}
-
-et2_register_widget(et2_nextmatch_accountfilterheader, ['nextmatch-accountfilter']);
-
-/**
- * Filter allowing multiple values to be selected, base on a taglist instead
- * of a regular selectbox
- *
- * @augments et2_taglist
- */
-export class et2_nextmatch_taglistheader extends et2_taglist implements et2_INextmatchHeader, et2_IResizeable
-{
- static readonly _attributes : any = {
- autocomplete_url: {default: ''},
- multiple: {default: 'toggle'},
- onchange: {
- // @ts-ignore
- default: function(event)
- {
- if(typeof this.nextmatch === 'undefined')
- {
- // Not fully set up yet
- return;
- }
- var col_filter = {};
- col_filter[this.id] = this.getValue();
- // Set value so it's there for response (otherwise it gets cleared if options are updated)
- //event.data.set_value(event.data.input.val());
-
- this.nextmatch.applyFilters({col_filter: col_filter});
- }
- },
- rows: {default: 2},
- class: {default: 'nm_filterheader_taglist'}
- };
- private nextmatch : et2_nextmatch;
-
- /**
- * Override to add change handler
- *
- * @memberOf et2_nextmatch_filterheader
- */
- createInputWidget()
- {
- // Make sure there's an option for all
- if(!this.options.empty_label && (!this.options.select_options || !this.options.select_options[""]))
- {
- this.options.empty_label = this.options.label ? this.options.label : egw.lang("All");
- }
- super.createInputWidget();
- }
-
- /**
- * Disable toggle if there are 2 or less options
- * @param {Object[]} options
- */
- set_select_options(options)
- {
- if(options && options.length <= 2 && this.options.multiple == 'toggle')
- {
- this.set_multiple(false);
- }
- super.set_select_options(options)
- }
-
- /**
- * Set nextmatch is the function which has to be implemented for the
- * et2_INextmatchHeader interface.
- *
- * @param {et2_nextmatch} _nextmatch
- */
- setNextmatch(_nextmatch)
- {
- this.nextmatch = _nextmatch;
-
- // Set current filter value from nextmatch settings
- if(this.nextmatch.activeFilters.col_filter && typeof this.nextmatch.activeFilters.col_filter[this.id] != "undefined")
- {
- this.set_value(this.nextmatch.activeFilters.col_filter[this.id]);
-
- // Make sure it's set in the nextmatch
- _nextmatch.activeFilters.col_filter[this.id] = this.getValue();
- }
- }
-
- // Make sure selectbox is not longer than the column
- resize()
- {
- this.div.css("height", '');
- this.div.css("max-width", jQuery(this.parentNode).innerWidth() + "px");
- super.resize();
- }
-
-}
-
-et2_register_widget(et2_nextmatch_taglistheader, ['nextmatch-taglistheader']);
-
-/**
- * Nextmatch filter that can filter for a selected entry
- */
-export class et2_nextmatch_entryheader extends et2_link_entry implements et2_INextmatchHeader
-{
- /**
- * Override to add change handler
- *
- * @memberOf et2_nextmatch_entryheader
- * @param {object} event
- * @param {object} selected
- */
- onchange(event, selected)
- {
- const col_filter = {};
- col_filter[this.id] = this.get_value();
- this.nextmatch.applyFilters.call(this.nextmatch, {col_filter: col_filter});
- }
-
- /**
- * Override to always return a string appname:id (or just id) for simple (one real selection)
- * cases, parent returns an object. If multiple are selected, or anything other than app and
- * id, the original parent value is returned.
- */
- getValue()
- {
- let value = super.getValue();
- if(typeof value == "object" && value != null)
- {
- if(!value.app || !value.id) return null;
-
- // If array with just one value, use a string instead for legacy server handling
- if(typeof value.id == 'object' && value.id.shift && value.id.length == 1)
- {
- value.id = value.id.shift();
- }
- // If simple value, format it legacy string style, otherwise
- // we return full value
- if(typeof value.id == 'string')
- {
- value = value.app + ":" + value.id;
- }
- }
- return value;
- }
-
- /**
- * Set nextmatch is the function which has to be implemented for the
- * et2_INextmatchHeader interface.
- *
- * @param {et2_nextmatch} _nextmatch
- */
- setNextmatch(_nextmatch)
- {
- this.nextmatch = _nextmatch;
-
- // Set current filter value from nextmatch settings
- if(this.nextmatch.options.settings.col_filter && this.nextmatch.options.settings.col_filter[this.id])
- {
- this.set_value(this.nextmatch.options.settings.col_filter[this.id]);
-
- if(this.getValue() != this.nextmatch.activeFilters.col_filter[this.id])
- {
- this.nextmatch.activeFilters.col_filter[this.id] = this.getValue();
- }
-
- // Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
- this.attributes.value.ignore = true;
- //this.attributes.select_options.ignore = true;
- }
- // Fire on lost focus, clear filter if user emptied box
- }
-}
-
-et2_register_widget(et2_nextmatch_entryheader, ['nextmatch-entryheader']);
-
-/**
- * @augments et2_nextmatch_filterheader
- */
-export class et2_nextmatch_customfilter extends et2_nextmatch_filterheader
-{
- static readonly _attributes : any = {
- "widget_type": {
- "name": "Actual type",
- "type": "string",
- "description": "The actual type of widget you should use",
- "no_lang": 1
- },
- "widget_options": {
- "name": "Actual options",
- "type": "any",
- "description": "The options for the actual widget",
- "no_lang": 1,
- "default": {}
- }
- };
- public static readonly legacyOptions : ["widget_type", "widget_options"];
-
- real_node : et2_selectbox;
-
- /**
- * Constructor
- *
- * @param _parent
- * @param _attrs
- * @param _child
- * @memberOf et2_nextmatch_customfilter
- */
- constructor(_parent? : et2_widget, _attrs? : WidgetConfig, _child? : object)
- {
- super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {}));
-
- switch(_attrs.widget_type)
- {
- case "link-entry":
- _attrs.type = 'nextmatch-entryheader';
- break;
- default:
- if(_attrs.widget_type.indexOf('select') === 0)
- {
- _attrs.type = 'nextmatch-filterheader';
- }
- else
- {
- _attrs.type = _attrs.widget_type;
- }
- }
- jQuery.extend(_attrs.widget_options, {id: this.id});
-
- _attrs.id = '';
- super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {}));
-
- this.real_node = et2_createWidget(_attrs.type, _attrs.widget_options, this.getParent());
- const select_options = [];
- const correct_type = _attrs.type;
- this.real_node['type'] = _attrs.widget_type;
- et2_selectbox.find_select_options(this.real_node, select_options, _attrs);
- this.real_node["_type"] = correct_type;
- if(typeof this.real_node.set_select_options === 'function')
- {
- this.real_node.set_select_options(select_options);
- }
- }
-
- // Just pass the real DOM node through, in case anybody asks
- getDOMNode(_sender)
- {
- return this.real_node ? this.real_node.getDOMNode(_sender) : null;
- }
-
- // Also need to pass through real children
- getChildren()
- {
- return this.real_node.getChildren() || [];
- }
-
- setNextmatch(_nextmatch : et2_nextmatch)
- {
- if(this.real_node && this.real_node.instanceOf(et2_INextmatchHeader))
- {
- return (this.real_node).setNextmatch(_nextmatch);
- }
- }
-}
-
-et2_register_widget(et2_nextmatch_customfilter, ['nextmatch-customfilter']);
\ No newline at end of file
+et2_register_widget(et2_nextmatch_sortheader, ['nextmatch-sortheader']);
\ No newline at end of file
diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts
index 1d940595ef..11cc4a426a 100644
--- a/api/js/etemplate/etemplate2.ts
+++ b/api/js/etemplate/etemplate2.ts
@@ -49,6 +49,10 @@ import './Et2Link/Et2LinkList';
import './Et2Link/Et2LinkSearch';
import './Et2Link/Et2LinkString';
import './Et2Link/Et2LinkTo';
+import './Et2Nextmatch/Headers/AccountFilterHeader';
+import './Et2Nextmatch/Headers/CustomFilterHeader';
+import './Et2Nextmatch/Headers/EntryHeader';
+import './Et2Nextmatch/Headers/FilterHeader';
import './Et2Select/Et2Select';
import './Et2Select/Et2SelectAccount';
import './Et2Select/Et2SelectCategory';
diff --git a/api/src/Etemplate/Widget/Nextmatch/Customfilter.php b/api/src/Etemplate/Widget/Nextmatch/Customfilter.php
index b48e4fb462..ddc3837363 100644
--- a/api/src/Etemplate/Widget/Nextmatch/Customfilter.php
+++ b/api/src/Etemplate/Widget/Nextmatch/Customfilter.php
@@ -34,7 +34,7 @@ class Customfilter extends Widget\Transformer
switch($this->attrs['type'])
{
case "link-entry":
- self::$transformation['type'] = $this->attrs['type'] = 'nextmatch-entryheader';
+ self::$transformation['type'] = $this->attrs['type'] = 'et2-nextmatch-header-entry';
break;
default:
list($type) = explode('-',$this->attrs['type']);
@@ -44,7 +44,7 @@ class Customfilter extends Widget\Transformer
{
$widget_type = $this->attrs['type'];
}
- $this->attrs['type'] = 'nextmatch-filterheader';
+ $this->attrs['type'] = 'et2-nextmatch-header-custom';
}
self::$transformation['type'] = $this->attrs['type'];
}
diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css
index 4d3086da40..ee9370a153 100644
--- a/api/templates/default/etemplate2.css
+++ b/api/templates/default/etemplate2.css
@@ -2188,7 +2188,9 @@ div.message.floating, lion-validation-feedback[type] {
}
.et2_nextmatch .nextmatch_header_row > div {
- display: inline-block;
+ display: inline-flex;
+ flex-direction: row;
+ gap: 1ex;
}
/* Firefox only search clear button */