mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-03 04:29:28 +01:00
Implement local search in SearchMixin & for Et2TreeDropdown
Add ability for SearchResult to have children
This commit is contained in:
parent
5e32896ccd
commit
a9a26ffe39
@ -1,3 +1,5 @@
|
||||
import {SearchResult} from "../Et2Widget/SearchMixin";
|
||||
|
||||
/**
|
||||
* Interface for select options
|
||||
*
|
||||
@ -5,22 +7,8 @@
|
||||
* For option groups, value is the list of sub-options.
|
||||
*
|
||||
*/
|
||||
export interface SelectOption
|
||||
export interface SelectOption extends SearchResult
|
||||
{
|
||||
value : string | SelectOption[];
|
||||
label : string;
|
||||
// Hover help text
|
||||
title? : string;
|
||||
// Related image or icon
|
||||
icon? : string;
|
||||
// Class applied to node
|
||||
class? : string;
|
||||
// Show the option, but it is not selectable.
|
||||
// If multiple=true and the option is in the value, it is not removable.
|
||||
disabled? : boolean;
|
||||
// If a search is in progress, does this option match.
|
||||
// Automatically changed.
|
||||
isMatch? : boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {SlTreeItem} from "@shoelace-style/shoelace";
|
||||
import {egw} from "../../jsapi/egw_global";
|
||||
import {find_select_options} from "../Et2Select/FindSelectOptions";
|
||||
import {find_select_options, SelectOption} from "../Et2Select/FindSelectOptions";
|
||||
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
|
||||
import {css, html, LitElement, nothing, PropertyValues, TemplateResult} from "lit";
|
||||
import {repeat} from "lit/directives/repeat.js";
|
||||
@ -14,7 +14,7 @@ import {EgwActionObject} from "../../egw_action/EgwActionObject";
|
||||
import {EgwAction} from "../../egw_action/EgwAction";
|
||||
import {EgwDragDropShoelaceTree} from "../../egw_action/EgwDragDropShoelaceTree";
|
||||
|
||||
export type TreeItemData = {
|
||||
export type TreeItemData = SelectOption & {
|
||||
focused?: boolean;
|
||||
// Has children, but they may not be provided in item
|
||||
child: Boolean | 1,
|
||||
@ -652,7 +652,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
||||
/*
|
||||
if collapsed .. opended? leaf?
|
||||
*/
|
||||
let img: String = selectOption.im0 ?? selectOption.im1 ?? selectOption.im2;
|
||||
let img : String = selectOption.icon ?? selectOption.im0 ?? selectOption.im1 ?? selectOption.im2;
|
||||
if (img)
|
||||
{
|
||||
//sl-icon images need to be svgs if there is a png try to find the corresponding svg
|
||||
@ -696,8 +696,8 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
||||
>
|
||||
<sl-icon src="${img ?? nothing}"></sl-icon>
|
||||
|
||||
<span class="tree-item__label">${selectOption.text}</span>
|
||||
${selectOption.item ? repeat(selectOption.item, this._optionTemplate) : nothing}
|
||||
<span class="tree-item__label">${selectOption.label ?? selectOption.text}</span>
|
||||
${selectOption.children ? repeat(selectOption.children, this._optionTemplate) : (selectOption.item ? repeat(selectOption.item, this._optionTemplate) : nothing)}
|
||||
</sl-tree-item>`
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {LitElement, nothing} from "lit";
|
||||
import {html, literal, StaticValue} from "lit/static-html.js";
|
||||
import {Et2Tree, TreeItemData} from "./Et2Tree";
|
||||
import {Et2Tree, TreeItemData, TreeSearchResult} from "./Et2Tree";
|
||||
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {classMap} from "lit/directives/class-map.js";
|
||||
@ -15,7 +15,7 @@ import {SearchMixin, SearchResult, SearchResultsInterface} from "../Et2Widget/Se
|
||||
import {Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
|
||||
|
||||
|
||||
interface TreeSearchResult extends SearchResultsInterface<SearchResult>
|
||||
interface TreeSearchResults extends SearchResultsInterface<TreeSearchResult>
|
||||
{
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ type Constructor<T = {}> = new (...args : any[]) => T;
|
||||
* @csspart form-control - The form control that wraps the label, input, and help text.
|
||||
*/
|
||||
|
||||
export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidgetInterface & typeof LitElement, SearchResult, TreeSearchResult>(Et2WidgetWithSelectMixin(LitElement))
|
||||
export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidgetInterface & typeof LitElement, TreeSearchResult, TreeSearchResults>(Et2WidgetWithSelectMixin(LitElement))
|
||||
{
|
||||
|
||||
static get styles()
|
||||
@ -89,6 +89,11 @@ export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidg
|
||||
this.__value = [];
|
||||
}
|
||||
|
||||
updated()
|
||||
{
|
||||
// @ts-ignore Popup sometimes loses the anchor which breaks the sizing
|
||||
this._popup.handleAnchorChange();
|
||||
}
|
||||
/** Selected tree leaves */
|
||||
@property()
|
||||
set value(new_value : string | string[])
|
||||
@ -202,6 +207,22 @@ export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidg
|
||||
this.treeOrSearch = "search";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If you have a local list of options, you can search through them on the client and include them in the results.
|
||||
*
|
||||
* This is done independently from the server-side search, and the results are merged.
|
||||
*
|
||||
* @param {string} search
|
||||
* @param {object} options
|
||||
* @returns {Promise<any[]>}
|
||||
* @protected
|
||||
*/
|
||||
protected localSearch<DataType extends SearchResult>(search : string, searchOptions : object, localOptions : DataType[] = []) : Promise<DataType[]>
|
||||
{
|
||||
return super.localSearch(search, searchOptions, this.select_options);
|
||||
}
|
||||
|
||||
protected searchResultSelected()
|
||||
{
|
||||
super.searchResultSelected();
|
||||
@ -219,6 +240,9 @@ export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidg
|
||||
|
||||
// Done with search, show the tree
|
||||
this.treeOrSearch = "tree";
|
||||
// Close the dropdown
|
||||
this.hide();
|
||||
this.requestUpdate("value");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -448,7 +472,7 @@ export class Et2TreeDropdown extends SearchMixin<Constructor<any> & Et2InputWidg
|
||||
return html`${map(value, (value, index) =>
|
||||
{
|
||||
// Deal with value that is not in options
|
||||
const option = this.optionSearch(value, this.select_options, 'value', 'item');
|
||||
const option = this.optionSearch(value, this.select_options, 'value', 'children');
|
||||
return option ? this.tagTemplate(option) : nothing;
|
||||
})}`;
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ export type SearchResult = {
|
||||
// If a search is in progress, does this option match.
|
||||
// Automatically changed.
|
||||
isMatch? : boolean;
|
||||
|
||||
// The item has children (option group)
|
||||
hasChildren? : boolean,
|
||||
// The item's children
|
||||
children? : SearchResult[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -191,7 +196,7 @@ export const SearchMixin = <T extends Constructor<Et2InputWidgetInterface &
|
||||
// The component has the focus
|
||||
@state() hasFocus = false;
|
||||
// For keyboard navigation of search results
|
||||
@state() currentResult : HTMLElement & SearchResultElement = null;
|
||||
@state() currentResult : LitElement & SearchResultElement = null;
|
||||
// Search result nodes marked as "selected"
|
||||
@state() selectedResults : (HTMLElement & SearchResultElement)[] = [];
|
||||
|
||||
@ -209,7 +214,7 @@ export const SearchMixin = <T extends Constructor<Et2InputWidgetInterface &
|
||||
// Element where we render the search results
|
||||
protected get _listNode() : HTMLElement { return this.shadowRoot.querySelector("#listbox");}
|
||||
|
||||
protected get _resultNodes() : (HTMLElement & SearchResultElement)[] { return this._listNode ? Array.from(this._listNode.querySelectorAll("*:not(div)")) : [];}
|
||||
protected get _resultNodes() : (LitElement & SearchResultElement)[] { return this._listNode ? Array.from(this._listNode.querySelectorAll("*:not(div)")) : [];}
|
||||
|
||||
constructor(...args : any[])
|
||||
{
|
||||
@ -278,17 +283,38 @@ export const SearchMixin = <T extends Constructor<Et2InputWidgetInterface &
|
||||
* This is done independently from the server-side search, and the results are merged.
|
||||
*
|
||||
* @param {string} search
|
||||
* @param {object} options
|
||||
* @param {object} searchOptions
|
||||
* @returns {Promise<any[]>}
|
||||
* @protected
|
||||
*/
|
||||
protected localSearch<DataType>(search : string, options : object) : Promise<DataType[]>
|
||||
protected localSearch<DataType extends SearchResult>(search : string, searchOptions : object, localOptions : DataType[] = []) : Promise<DataType[]>
|
||||
{
|
||||
const nullResults : Results = <Results><unknown>{
|
||||
const local : Results = <Results><unknown>{
|
||||
results: <DataType[]>[],
|
||||
total: 0
|
||||
}
|
||||
return Promise.resolve(this.processLocalResults(nullResults));
|
||||
let doSearch = function <DataType extends SearchResult>(options : DataType[], value : string)
|
||||
{
|
||||
options.forEach((option) =>
|
||||
{
|
||||
if(typeof option !== "object")
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(option.label?.includes(value) || option.value?.includes(value))
|
||||
{
|
||||
local.results.push(option);
|
||||
local.total++;
|
||||
}
|
||||
if(typeof option.children != "undefined" && Array.isArray(option.children))
|
||||
{
|
||||
return doSearch(option.children, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
doSearch(localOptions, search);
|
||||
|
||||
return Promise.resolve(this.processLocalResults(local));
|
||||
}
|
||||
|
||||
protected remoteSearch<DataType>(search : string, options : object) : Promise<DataType[]>
|
||||
@ -375,7 +401,7 @@ export const SearchMixin = <T extends Constructor<Et2InputWidgetInterface &
|
||||
* Sets the current search result, which is the one the user is currently interacting with (e.g. via keyboard).
|
||||
* Only one result may be "current" at a time.
|
||||
*/
|
||||
private setCurrentResult(result : HTMLElement & SearchResultElement | null)
|
||||
private setCurrentResult(result : LitElement & SearchResultElement | null)
|
||||
{
|
||||
// Clear selection
|
||||
this._resultNodes.forEach((el) =>
|
||||
|
@ -523,9 +523,8 @@ class Tree extends Etemplate\Widget
|
||||
$cat_id_list[] = $cat['id'];
|
||||
if(!empty($cat['children']))
|
||||
{
|
||||
$category['item'] = [];
|
||||
unset($cat['children']);
|
||||
static::processCategory($cat['id'], $category['item'], $categories, $globals, $cat_id_list);
|
||||
unset($category['children']);
|
||||
static::processCategory($cat['id'], $category['children'], $categories, $globals, $cat_id_list);
|
||||
}
|
||||
$options[] = $category;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user