Work on LinkEntry

This commit is contained in:
nathan 2022-05-31 13:40:31 -06:00
parent c572fa637d
commit 9b0e1b9206
5 changed files with 279 additions and 19 deletions

View File

@ -13,6 +13,7 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
:host {
--icon-width: 20px;
display: inline-block;
min-width: 64px;
}
:host([app_icons]) {
max-width: 75px;
@ -130,6 +131,22 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
}
}
set application_list(app_list : string[])
{
let oldValue = this.__application_list;
if(typeof app_list == "string")
{
app_list = (<string>app_list).split(",");
}
this.__application_list = app_list;
this.requestUpdate("application_list", oldValue);
}
get application_list() : string[]
{
return this.__application_list;
}
private _handleChange(e)
{
// Set icon

View File

@ -0,0 +1,142 @@
import {css, html, LitElement, SlotMixin} from "@lion/core";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import {Et2LinkSearch} from "./Et2LinkSearch";
/**
* EGroupware eTemplate2 - Search & select link entry WebComponent
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(ValidateMixin(SlotMixin(LitElement))))
{
static get styles()
{
return [
...super.styles,
css`
:host {
display: block;
}
`
];
}
static get properties()
{
return {
...super.properties,
value: {type: Object},
/**
* Limit to just this application - hides app selection
*/
only_app: {type: String},
/**
* Limit to the listed applications (comma seperated)
*/
application_list: {type: String},
/**
* Show just application icons instead of names
*/
app_icons: {type: Boolean},
/**
* Callback before query to server.
* It will be passed the request & et2_link_entry objects. Must return true, or false to abort query.
*/
query: {type: Function},
/**
* Callback when user selects an option. Must return true, or false to abort normal action.
*/
select: {type: Function}
}
}
get slots()
{
return {
...super.slots,
app: () =>
{
const app = document.createElement("et2-link-apps")
return app;
},
select: () =>
{
const select = <Et2LinkSearch><unknown>document.createElement("et2-link-search");
return select;
}
}
}
constructor()
{
super();
}
connectedCallback()
{
super.connectedCallback();
this._handleAppChange = this._handleAppChange.bind(this);
}
set only_app(app)
{
this._appNode.only_app = app;
this._searchNode.app = app;
}
get only_app()
{
return this._appNode.only_app;
}
get _appNode() : Et2LinkAppSelect
{
return this.querySelector("[slot='app']");
}
get _searchNode() : Et2LinkSearch
{
return this.querySelector("[slot='select']");
}
protected _bindListeners()
{
this._appNode.addEventListener("change", this._handleAppChange);
}
protected _unbindListeners()
{
this._appNode.removeEventListener("change", this._handleAppChange);
}
protected _handleAppChange(event)
{
this._searchNode.app = this._appNode.value;
}
/**
* @return {TemplateResult}
* @protected
*/
// eslint-disable-next-line class-methods-use-this
_inputGroupInputTemplate()
{
return html`
<div class="input-group__input">
<slot name="app"></slot>
<slot name="select"></slot>
</div>
`;
}
}
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
customElements.define("et2-link-entry", Et2LinkEntry);

View File

@ -0,0 +1,72 @@
/**
* EGroupware eTemplate2 - Search & select link entry WebComponent
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css} from "@lion/core";
import {Et2Select} from "../Et2Select/Et2Select";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
export class Et2LinkSearch extends Et2Select
{
static get styles()
{
return [
...super.styles,
css`
:host {
display: block;
flex: 1 1 auto;
min-width: 200px;
}
::part(icon), .select__icon {
display: none;
}
`
];
}
static get properties()
{
return {
...super.properties,
app: {type: String, reflect: true}
}
}
constructor()
{
super();
this.search = true;
this.searchUrl = "EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_search";
}
get _appNode() : Et2LinkAppSelect
{
return this.parentNode.querySelector("et2-link-apps");
}
protected remoteQuery(search : string, options : object)
{
let request = this.egw().json(this.searchUrl, [this._appNode.value, '', search, options]);
if(this.query && typeof this.query == "function")
{
if(!this.query(request, this))
{
return;
}
}
request.sendRequest().then((result) =>
{
this.processRemoteResults(result);
});
}
}
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
customElements.define("et2-link-search", Et2LinkSearch);

View File

@ -31,6 +31,12 @@ export declare class SearchMixinInterface
*/
allowFreeEntries : boolean;
/**
* Additional search options passed to the search functions
*
* @type {object}
*/
searchOptions : object;
/**
* Start the search process
@ -40,18 +46,18 @@ export declare class SearchMixinInterface
/**
* Search local options
*/
localSearch(search : string) : Promise<void>
localSearch(search : string, options : object) : Promise<void>
/**
* Search remote options.
* If searchUrl is not set, it will return very quickly with no results
*/
remoteSearch(search : string) : Promise<void>
remoteSearch(search : string, options : object) : Promise<void>
/**
* Check a [local] item to see if it matches
*/
searchMatch(search : string, item : LitElement) : boolean
searchMatch(search : string, options : object, item : LitElement) : boolean
}
/**
@ -72,7 +78,9 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
searchUrl: {type: String},
allowFreeEntries: {type: Boolean}
allowFreeEntries: {type: Boolean},
searchOptions: {type: Object}
}
}
@ -110,7 +118,7 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
width: 100%;
}
::slotted(.search_input.active) {
display: block;
display: flex;
}
::slotted(.no-match) {
display: none;
@ -120,13 +128,17 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
}
private _searchTimeout : number;
protected static SEARCH_DELAY = 200;
protected static SEARCH_TIMEOUT = 500;
protected static MIN_CHARS = 2;
constructor(...args : any[])
{
super(...args);
this.search = false;
this.searchUrl = "";
this.searchOptions = {};
this._handleSearchButtonClick = this._handleSearchButtonClick.bind(this);
this._handleSearchAbort = this._handleSearchAbort.bind(this);
this._handleSearchKeyDown = this._handleSearchKeyDown.bind(this);
@ -176,6 +188,16 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
}
/**
* Do we have the needed properties set, so we can actually do searching
*
* @returns {boolean}
*/
public get searchEnabled() : boolean
{
return this.search || this.searchUrl.length > 0;
}
protected get _searchButtonNode()
{
return this.querySelector("sl-icon[slot='suffix']");
@ -232,15 +254,21 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
{
super.handleMenuShow();
this._activeControls.classList.add("active");
if(this.searchEnabled)
{
this._activeControls?.classList.add("active");
this._searchInputNode.focus();
this._searchInputNode.select();
}
}
handleMenuHide()
{
super.handleMenuHide();
this._activeControls.classList.remove("active");
if(this.searchEnabled)
{
this._activeControls?.classList.remove("active");
}
}
/**
@ -250,7 +278,7 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
*/
protected _handleSearchKeyDown(event : KeyboardEvent)
{
this._activeControls.classList.add("active");
this._activeControls?.classList.add("active");
this.dropdown.show();
// Pass off some keys to select
@ -278,7 +306,7 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
clearTimeout(this._searchTimeout);
if(this._searchInputNode.value.length >= Et2WidgetWithSearch.MIN_CHARS)
{
this._searchTimeout = window.setTimeout(() => {this.startSearch()}, Et2WidgetWithSearch.SEARCH_DELAY);
this._searchTimeout = window.setTimeout(() => {this.startSearch()}, Et2WidgetWithSearch.SEARCH_TIMEOUT);
}
}
@ -298,8 +326,8 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
// Start the searches
Promise.all([
this.localSearch(this._searchInputNode.value),
this.remoteSearch(this._searchInputNode.value)
this.localSearch(this._searchInputNode.value, this.searchOptions),
this.remoteSearch(this._searchInputNode.value, this.searchOptions)
]).then(() =>
{
spinner.remove();
@ -312,7 +340,7 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
* @param {string} search
* @protected
*/
protected localSearch(search : string) : Promise<void>
protected localSearch(search : string, options : object) : Promise<void>
{
return new Promise((resolve) =>
{
@ -333,7 +361,7 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
* @param {string} search
* @protected
*/
protected remoteSearch(search : string)
protected remoteSearch(search : string, options : object)
{
// Remove existing remote items
this.remoteItems.forEach(i => i.remove());
@ -344,16 +372,15 @@ export const Et2WithSearchMixin = dedupeMixin((superclass) =>
}
// Fire off the query
let promise = this.remoteQuery(search);
let promise = this.remoteQuery(search, options);
return promise;
}
protected remoteQuery(search : string)
protected remoteQuery(search : string, options : object)
{
return this.egw().json(this.searchUrl, [search]).sendRequest().then((result) =>
{
debugger;
this.processRemoteResults(result);
});
}

View File

@ -44,7 +44,9 @@ import './Et2Favorites/Et2Favorites';
import './Et2Image/Et2Image';
import './Et2Link/Et2Link';
import './Et2Link/Et2LinkAppSelect';
import './Et2Link/Et2LinkEntry';
import './Et2Link/Et2LinkList';
import './Et2Link/Et2LinkSearch';
import './Et2Link/Et2LinkString';
import './Et2Select/Et2Select';
import './Et2Select/Et2SelectAccount';