Et2SelectEmail: Add button on hover to add a new contact with the email

(multiple)
This commit is contained in:
nathan 2022-06-24 12:10:10 -06:00
parent 9ca78de12f
commit d11be10fa1
4 changed files with 211 additions and 1 deletions

View File

@ -24,6 +24,37 @@ export class Et2WidgetWithSelect extends Et2widgetWithSelectMixin(SlSelect)
{ {
}; };
/**
* Select widget
*
* At its most basic, you can select one option from a list provided. The list can be passed from the server in
* the sel_options array or options can be added as children in the template. Some extending classes provide specific
* options, such as Et2SelectPercent or Et2SelectCountry. All provided options will be mixed together and used.
*
* To allow selecting more than one option, use the attribute multiple="true". This will take & return an array
* as value instead of just a string.
*
* SearchMixin adds additional abilities to ALL select boxes
* @see Et2WithSearchMixin
*
* Override for extending widgets:
* # Custom display of selected value
* When selecting a single value (!multiple) you can override doLabelChange() to customise the displayed label
* @see Et2SelectCategory, which adds in the category icon
*
* # Custom option rows
* Options can have 'class' and 'icon' properties that will be used for the option
* The easiest way for further customisation to use CSS in an external file (like etemplate2.css) and ::part().
* @see Et2SelectCountry which displays flags via CSS instead of using SelectOption.icon
*
* # Custom tags
* When multiple is set, instead of a single value each selected value is shown in a tag. While it's possible to
* use CSS to some degree, we can also use a custom tag class that extends Et2Tag.
* 1. Create the extending class
* 2. Make sure it's loaded (add to etemplate2.ts)
* 3. In your extending Et2Select, override get tagTag() to return the custom tag name
*
*/
// @ts-ignore SlSelect styles is a single CSSResult, not an array, so TS complains // @ts-ignore SlSelect styles is a single CSSResult, not an array, so TS complains
export class Et2Select extends Et2WithSearchMixin(Et2InvokerMixin(Et2WidgetWithSelect)) export class Et2Select extends Et2WithSearchMixin(Et2InvokerMixin(Et2WidgetWithSelect))
{ {

View File

@ -141,6 +141,16 @@ export class Et2SelectEmail extends Et2Select
super.processRemoteResults(results); super.processRemoteResults(results);
} }
/**
* Use a custom tag for when multiple=true
*
* @returns {string}
*/
get tagTag() : string
{
return "et2-email-tag";
}
/** /**
* override tag creation in order to add DND functionality * override tag creation in order to add DND functionality
* @param item * @param item
@ -149,7 +159,7 @@ export class Et2SelectEmail extends Et2Select
protected _createTagNode(item) protected _createTagNode(item)
{ {
let tag = super._createTagNode(item); let tag = super._createTagNode(item);
if (!this.readonly && this.allowFreeEntries && this.allowDragAndDrop) if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop)
{ {
let dragTranslate = {x:0,y:0}; let dragTranslate = {x:0,y:0};
tag.class = item.classList.value + " et2-select-draggable"; tag.class = item.classList.value + " et2-select-draggable";

View File

@ -0,0 +1,168 @@
/**
* EGroupware eTemplate2 - Email Tag 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 shoelace from "../../Styles/shoelace";
import {Et2Tag} from "./Et2Tag";
import {cssImage} from "../../Et2Widget/Et2Widget";
/**
* Display a single email address
* On hover, queries the server to see if
* Tag is usually used in a Et2EmailSelect with multiple=true, but there's no reason it can't go anywhere
*/
export class Et2EmailTag extends Et2Tag
{
private static email_cache : string[] = [];
static get styles()
{
return [
super.styles,
shoelace, css`
.tag {
position: relative;
}
.tag__prefix {
display: none;
height: 16px;
background-color: white;
background-repeat: no-repeat;
background-size: contain;
}
.contact_plus .tag__prefix {
display: block;
order: 2;
}
.tag__prefix.loading {
width: 16px;
background-image: ${cssImage("loading")};
}
.tag__prefix.contact_plus_add {
width: 16px;
background-image: ${cssImage("add")};
cursor: pointer;
}
`];
}
static get properties()
{
return {
...super.properties,
/**
* Check if the email is associated with an existing contact, and if it is not show a button to create
* a new contact with this email address.
*/
contact_plus: {
type: Boolean,
reflect: true,
}
}
}
constructor(...args : [])
{
super(...args);
this.contact_plus = true;
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleClick = this.handleClick.bind(this);
}
connectedCallback()
{
super.connectedCallback();
if(this.contact_plus && this.egw().app('addressbook'))
{
this.addEventListener("mouseenter", this.handleMouseEnter);
this.addEventListener("mouseleave", this.handleMouseLeave);
}
}
disconnectedCallback()
{
super.disconnectedCallback();
this.removeEventListener("mouseenter", this.handleMouseEnter);
this.removeEventListener("mouseleave", this.handleMouseLeave);
}
public checkContact(email : string) : Promise<boolean | number>
{
if(typeof Et2EmailTag.email_cache[email] !== "undefined")
{
return Promise.resolve(Et2EmailTag.email_cache[email]);
}
return this.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Url::ajax_contact', [email]).then(
(result) =>
{
Et2EmailTag.email_cache[email] = result;
return result;
});
}
handleMouseEnter(e : MouseEvent)
{
this.shadowRoot.querySelector(".tag").classList.add("contact_plus");
this._contactPlusNode.classList.add("loading");
this._contactPlusNode.style.right = getComputedStyle(this).left;
this.checkContact(this.value).then((result) =>
{
this._contactPlusNode.classList.remove("loading");
this.handleContactResponse(result);
})
}
handleMouseLeave(e : MouseEvent)
{
this.shadowRoot.querySelector(".tag").classList.remove("contact_plus");
}
/**
* We either have a contact ID, or false. If false, show the add button.
* @param {boolean | number} data
*/
handleContactResponse(data : boolean | number)
{
if(data)
{
return;
}
this._contactPlusNode.classList.add("contact_plus_add");
this._contactPlusNode.addEventListener("click", this.handleClick);
}
handleClick(e : MouseEvent)
{
e.stopPropagation();
let extra = {
'presets[email]': this.value
};
this.egw().open('', 'addressbook', 'add', extra);
}
/**
* Get the node that is shown & clicked on to add email as contact
*
* @returns {Element}
*/
get _contactPlusNode() : HTMLElement
{
return this.shadowRoot.querySelector(".tag__prefix");
}
}
customElements.define("et2-email-tag", Et2EmailTag);

View File

@ -58,6 +58,7 @@ import './Et2Select/Et2SelectReadonly';
import './Et2Select/Et2SelectThumbnail' import './Et2Select/Et2SelectThumbnail'
import './Et2Select/Tag/Et2Tag'; import './Et2Select/Tag/Et2Tag';
import './Et2Select/Tag/Et2CategoryTag'; import './Et2Select/Tag/Et2CategoryTag';
import './Et2Select/Tag/Et2EmailTag';
import './Et2Select/Tag/Et2ThumbnailTag'; import './Et2Select/Tag/Et2ThumbnailTag';
import './Et2Textarea/Et2Textarea'; import './Et2Textarea/Et2Textarea';
import './Et2Textbox/Et2Textbox'; import './Et2Textbox/Et2Textbox';