Just get it working WIP

- Fix category tree structure
- Switch on tree multiple
probably lots of bugs still, looks like we may have to do click on tree = add / remove and not show the value after all
This commit is contained in:
nathan 2024-02-13 17:15:23 -07:00
parent 6fa102dfc5
commit 62d9c222b6
6 changed files with 116 additions and 63 deletions

View File

@ -148,7 +148,7 @@
</columns> </columns>
<rows> <rows>
<row valign="top"> <row valign="top">
<tree-cat id="cat_id_tree" options="13,,width:99%"/> <et2-tree-cat id="cat_id_tree" multiple="true" placeholder="Category"/>
<et2-select-cat id="cat_id" width="100%" height="195" multiple="true" placeholder="Category"></et2-select-cat> <et2-select-cat id="cat_id" width="100%" height="195" multiple="true" placeholder="Category"></et2-select-cat>
<et2-description></et2-description> <et2-description></et2-description>
<grid width="100%"> <grid width="100%">

View File

@ -58,7 +58,6 @@ export class Et2MultiselectTree extends Et2Tree {
} }
_optionTemplate(selectOption: TreeItemData): TemplateResult<1> { _optionTemplate(selectOption: TreeItemData): TemplateResult<1> {
this._currentOption = selectOption
let img: String = selectOption.im0 ?? selectOption.im1 ?? selectOption.im2; let img: String = selectOption.im0 ?? selectOption.im1 ?? selectOption.im2;
if (img) { if (img) {
//sl-icon images need to be svgs if there is a png try to find the corresponding svg //sl-icon images need to be svgs if there is a png try to find the corresponding svg
@ -70,7 +69,8 @@ export class Et2MultiselectTree extends Et2Tree {
<sl-tree-item <sl-tree-item
id=${selectOption.id} id=${selectOption.id}
?lazy=${this._currentOption.item?.length === 0 && this._currentOption.child} ?selected=${this.value.includes(selectOption.id)}
?lazy=${selectOption.item?.length === 0 && selectOption.child}
@sl-lazy-load=${(event) => { @sl-lazy-load=${(event) => {
this.handleLazyLoading(selectOption).then((result) => { this.handleLazyLoading(selectOption).then((result) => {
@ -82,8 +82,8 @@ export class Et2MultiselectTree extends Et2Tree {
> >
<sl-icon src="${img ?? nothing}"></sl-icon> <sl-icon src="${img ?? nothing}"></sl-icon>
${this._currentOption.text} ${selectOption.text}
${repeat(this._currentOption.item, this._optionTemplate.bind(this))} ${(selectOption.item) ? html`${repeat(selectOption.item, this._optionTemplate.bind(this))}` : nothing}
</sl-tree-item>` </sl-tree-item>`
} }
@ -100,8 +100,10 @@ export class Et2MultiselectTree extends Et2Tree {
} }
this.selectedNodes = event.detail.selection; this.selectedNodes = event.detail.selection;
//TODO look at what signature is expected here //TODO look at what signature is expected here
this.onchange(event,this) if(typeof this.onclick == "function")
{
this.onclick(event.detail.selection[0].id, this, event.detail.previous)
}
} }
} }

View File

@ -16,12 +16,14 @@ import {EgwDragDropShoelaceTree} from "../../egw_action/EgwDragDropShoelaceTree"
export type TreeItemData = { export type TreeItemData = {
focused?: boolean; focused?: boolean;
// Has children, but they may not be provided in item
child: Boolean | 1, child: Boolean | 1,
data?: Object,//{sieve:true,...} or {acl:true} or other data?: Object,//{sieve:true,...} or {acl:true} or other
id: string, id: string,
im0: String, im0: String,
im1: String, im1: String,
im2: String, im2: String,
// Child items
item: TreeItemData[], item: TreeItemData[],
checked?: Boolean, checked?: Boolean,
nocheckbox: number | Boolean, nocheckbox: number | Boolean,
@ -98,6 +100,8 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
{ {
super(); super();
this._selectOptions = []; this._selectOptions = [];
this._optionTemplate = this._optionTemplate.bind(this);
} }
//Sl-Trees handle their own onClick events //Sl-Trees handle their own onClick events
@ -592,7 +596,11 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
return html` return html`
<sl-tree-item <sl-tree-item
part="item"
exportparts="checkbox"
id=${selectOption.id} id=${selectOption.id}
title=${selectOption.tooltip || nothing}
?selected=${this.value.includes(selectOption.id)}
?expanded=${(this.calculateExpandState(selectOption))} ?expanded=${(this.calculateExpandState(selectOption))}
?lazy=${selectOption.item?.length === 0 && selectOption.child} ?lazy=${selectOption.item?.length === 0 && selectOption.child}
?focused=${selectOption.focused || nothing} ?focused=${selectOption.focused || nothing}
@ -607,7 +615,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
<sl-icon src="${img ?? nothing}"></sl-icon> <sl-icon src="${img ?? nothing}"></sl-icon>
${selectOption.text} ${selectOption.text}
${selectOption.item ? repeat(selectOption.item, this._optionTemplate.bind(this)) : ""} ${selectOption.item ? repeat(selectOption.item, this._optionTemplate) : nothing}
</sl-tree-item>` </sl-tree-item>`
} }
@ -616,6 +624,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
{ {
return html` return html`
<sl-tree <sl-tree
.selection=${this.multiple ? "multiple" : "single"}
@sl-selection-change=${ @sl-selection-change=${
(event: any) => { (event: any) => {
this._previousOption = this._currentOption this._previousOption = this._currentOption
@ -646,7 +655,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
} }
> >
${repeat(this._selectOptions, this._optionTemplate.bind(this))} ${repeat(this._selectOptions, this._optionTemplate)}
</sl-tree> </sl-tree>
`; `;
} }
@ -750,7 +759,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
protected updated(_changedProperties: PropertyValues) protected updated(_changedProperties: PropertyValues)
{ {
this._link_actions(this.actions) // this._link_actions(this.actions)
super.updated(_changedProperties); super.updated(_changedProperties);
} }
@ -772,15 +781,12 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
} }
private calculateExpandState = (selectOption: TreeItemData) => { private calculateExpandState = (selectOption: TreeItemData) => {
if (selectOption.id.endsWith("INBOX") || selectOption.id == window.egw.preference("ActiveProfileID", "mail"))
{
return true
}
if (selectOption.open) if (selectOption.open)
{ {
return true return true
} }
if ( if(this._selectOptions.length > 1 &&
this._selectOptions[0] == selectOption && this._selectOptions[0] == selectOption &&
(this._selectOptions.find((selectOption) => { (this._selectOptions.find((selectOption) => {
return selectOption.open return selectOption.open
@ -790,7 +796,10 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
{ {
return true //open the first item, if no item is opened return true //open the first item, if no item is opened
} }
if(selectOption.id && (selectOption.id.endsWith("INBOX") || selectOption.id == window.egw.preference("ActiveProfileID", "mail")))
{
return true
}
return false return false
; ;
} }
@ -855,8 +864,5 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
} }
customElements.define("et2-tree", Et2Tree); customElements.define("et2-tree", Et2Tree);
customElements.define("et2-tree-cat", class extends Et2Tree
{
});

View File

@ -44,15 +44,12 @@ export default css`
flex-wrap: nowrap; flex-wrap: nowrap;
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 0.1rem 0.5rem;
background-color: var(--sl-input-background-color); background-color: var(--sl-input-background-color);
border: solid var(--sl-input-border-width) var(--sl-input-border-color); border: solid var(--sl-input-border-width) var(--sl-input-border-color);
border-radius: var(--sl-input-border-radius-medium); border-radius: var(--sl-input-border-radius-medium);
font-size: var(--sl-input-font-size-medium); font-size: var(--sl-input-font-size-medium);
min-height: var(--sl-input-height-medium);
max-height: calc(var(--height, 5) * var(--sl-input-height-medium));
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding-block: 0; padding-block: 0;
@ -107,6 +104,9 @@ export default css`
.tree-dropdown__tags { .tree-dropdown__tags {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.1rem 0.5rem;
min-height: var(--sl-input-height-medium);
max-height: calc(var(--height, 5) * var(--sl-input-height-medium));
} }
/* End tags */ /* End tags */
@ -157,14 +157,17 @@ export default css`
border-radius: var(--sl-border-radius-medium); border-radius: var(--sl-border-radius-medium);
padding-block: var(--sl-spacing-x-small); padding-block: var(--sl-spacing-x-small);
padding-inline: 0; padding-inline: 0;
overflow: auto; overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: none; overscroll-behavior: none;
z-index: var(--sl-z-index-dropdown); z-index: var(--sl-z-index-dropdown);
/* Make sure it adheres to the popup's auto size */ /* Make sure it adheres to the popup's auto size */
height: auto;
max-width: var(--auto-size-available-width); max-width: var(--auto-size-available-width);
}
/* This doesn't work for some reason, it's overwritten somewhere */ et2-tree::part(checkbox) {
--size: 1.8em; display: none;
} }
`; `;

View File

@ -90,7 +90,8 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
new_value = new_value.split(",") new_value = new_value.split(",")
} }
const oldValue = this.__value; const oldValue = this.__value;
this.__value = <string[]>new_value; // Filter to make sure there are no trailing commas
this.__value = <string[]>new_value.filter(v => v);
this.requestUpdate("value", oldValue); this.requestUpdate("value", oldValue);
} }
@ -316,19 +317,41 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
{ {
// Find the tag value and remove it from current value // Find the tag value and remove it from current value
let valueArray = this.getValueAsArray(); let valueArray = this.getValueAsArray();
const oldValue = valueArray.slice(0);
const index = valueArray.indexOf(value); const index = valueArray.indexOf(value);
valueArray.splice(index, 1); valueArray.splice(index, 1);
this.value = valueArray; this.value = valueArray;
this.requestUpdate("value"); this.requestUpdate("value", oldValue);
this.dispatchEvent(new Event("change", {bubbles: true})); // TODO: Clean up this scope violation
// sl-tree-item is not getting its selected attribute updated
Array.from(this._tree._tree.querySelectorAll('sl-tree-item')).forEach(e =>
{
if(this.value.includes(e.id))
{
e.setAttribute("selected", "");
}
else
{
e.removeAttribute("selected");
}
});
this._tree.requestUpdate();
this.updateComplete.then(() =>
{
this.dispatchEvent(new Event("change", {bubbles: true}));
});
} }
handleTreeChange(event) handleTreeChange(event)
{ {
const oldValue = this.value; const oldValue = this.value;
this.value = this._tree.getValue(); this.value = event?.detail?.selection?.map(i => i.id) ?? [];
this.requestUpdate("value", oldValue); this.requestUpdate("value", oldValue);
this.updateComplete.then(() =>
{
this.dispatchEvent(new Event("change", {bubbles: true}));
});
if(!this.multiple) if(!this.multiple)
{ {
this.hide(); this.hide();
@ -394,14 +417,21 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
tagsTemplate() tagsTemplate()
{ {
const value = this.getValueAsArray(); const value = this.getValueAsArray();
return html`${keyed(this._valueUID, map(value, (value, index) => this.tagTemplate(this.optionSearch(value, this.select_options))))}`; return html`${keyed(this._valueUID, map(value, (value, index) =>
{
// Deal with value that is not in options
const option = this.optionSearch(value, this.select_options);
return option ? this.tagTemplate(option) : nothing;
}))}`;
} }
tagTemplate(option : TreeItemData) tagTemplate(option : TreeItemData)
{ {
const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled); const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled);
const isEditable = false && !readonly; const isEditable = false && !readonly;
const image = this.iconTemplate(option?.option ?? option); const image = option ? this.iconTemplate(option?.option ?? option) : null;
const isValid = true;
return html` return html`
<et2-tag <et2-tag
part="tag" part="tag"
@ -414,7 +444,7 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
" "
class=${"tree_tag " + option.class ?? ""} class=${"tree_tag " + option.class ?? ""}
tabindex="-1" tabindex="-1"
?pill=${this.pill} variant=${isValid ? nothing : "danger"}
size=${this.size || "medium"} size=${this.size || "medium"}
?removable=${!readonly} ?removable=${!readonly}
?readonly=${readonly} ?readonly=${readonly}
@ -468,7 +498,6 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
'tree-dropdown--focused': this.hasFocus, 'tree-dropdown--focused': this.hasFocus,
'tree-dropdown--placeholder-visible': isPlaceholderVisible, 'tree-dropdown--placeholder-visible': isPlaceholderVisible,
})} })}
strategy="fixed"
flip flip
shift shift
sync="width" sync="width"
@ -504,7 +533,7 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
multiple=${this.multiple} multiple=${this.multiple}
?readonly=${this.readonly} ?readonly=${this.readonly}
?disabled=${this.disabled} ?disabled=${this.disabled}
.value=${this.value} value=${this.value}
._selectOptions=${this.select_options} ._selectOptions=${this.select_options}
@sl-selection-change=${this.handleTreeChange} @sl-selection-change=${this.handleTreeChange}
@ -517,3 +546,7 @@ export class Et2TreeDropdown extends Et2WidgetWithSelectMixin(LitElement)
} }
customElements.define("et2-tree-dropdown", Et2TreeDropdown); customElements.define("et2-tree-dropdown", Et2TreeDropdown);
customElements.define("et2-tree-cat", class extends Et2TreeDropdown
{
});

View File

@ -473,34 +473,8 @@ class Tree extends Etemplate\Widget
$categories = new Api\Categories('',$type3); $categories = new Api\Categories('',$type3);
} }
$cat2path=array(); $cat2path=array();
foreach((array)$categories->return_sorted_array(0,False,'','','',!$type,0,true) as $cat)
{
$s = stripslashes($cat['name']);
if ($cat['app_name'] == 'phpgw' || $cat['owner'] == '-1') static::processCategory(0, $options, $categories, !$type);
{
$s .= ' &#9830;';
}
$cat2path[$cat['id']] = $path = ($cat['parent'] ? $cat2path[$cat['parent']].'/' : '').(string)$cat['id'];
// 1D array
$options[] = $cat + array(
'text' => $s,
'path' => $path,
/*
These ones to play nice when a user puts a tree & a selectbox with the same
ID on the form (addressbook edit):
if tree overwrites selectbox options, selectbox will still work
*/
'value' => $cat['id'],
'label' => $s,
'title' => $cat['description']
);
// Tree in array
//$options[$cat['parent']][] = $cat;
}
// change cat-ids to pathes and preserv unavailible cats (eg. private user-cats) // change cat-ids to pathes and preserv unavailible cats (eg. private user-cats)
if ($value) if ($value)
{ {
@ -533,4 +507,39 @@ class Tree extends Etemplate\Widget
//error_log(__METHOD__."('$widget_type', '$legacy_options', no_lang=".array2string($no_lang).', readonly='.array2string($readonly).", value=$value) returning ".array2string($options)); //error_log(__METHOD__."('$widget_type', '$legacy_options', no_lang=".array2string($no_lang).', readonly='.array2string($readonly).", value=$value) returning ".array2string($options));
return $options; return $options;
} }
protected static function processCategory($cat_id, &$options, &$categories, $globals)
{
foreach((array)$categories->return_array($cat_id ? 'subs' : 'mains', 0, false, '', 'ASC', '', $globals, $cat_id) as $cat)
{
$s = stripslashes($cat['name']);
if($cat['app_name'] == 'phpgw' || $cat['owner'] == '-1')
{
$s .= ' &#9830;';
}
// 1D array
$category = $cat + array(
'text' => $s,
/*
These ones to play nice when a user puts a tree & a selectbox with the same
ID on the form (addressbook edit):
if tree overwrites selectbox options, selectbox will still work
*/
'value' => $cat['id'],
'label' => $s,
'title' => $cat['description']
);
if(!empty($cat['children']))
{
$category['item'] = [];
unset($cat['children']);
static::processCategory($cat['id'], $category['item'], $categories, $globals);
}
$options[] = $category;
}
}
} }