
Here is a fairly comprehensive solution that supports the typical needs of a form to compose emails. Comments welcome.
Two events:
<!-- bevt_AutocompleteList -->
<aura:event type="APPLICATION" description="Autocomplete compenent has a new list of selections">
<aura:attribute name="autoCompleteId" type="String"/>
<aura:attribute name="list" type="String[]"/>
</aura:event>
Inner component to handle one entry on a drop down list.
<!-- b_LookupResult.cmp one entry in a auto-complete drop-down-->
<aura:component >
<!-- autoCompleteId: Id of the auto complete component. This is returned with the entry if user clicks on me. -->
<aura:attribute name="autoCompleteId" type="String"/>
<aura:attribute name="entry" type="String" />
<aura:registerEvent name="bevt_AutocompleteSelect" type="c:bevt_AutocompleteSelect"/>
<li role="presentation">
<span class="slds-lookup__item-action slds-media slds-media--center" role="option">
<div class="slds-media__body">
<div class="slds-lookup__result-text"><a onclick="{!c.selectEntry}">{!v.entry}</a></div>
</div>
</span>
</li>
</aura:component>
({
selectEntry : function(component, event, helper){
var autoCompleteId = component.get("v.autoCompleteId");
var entry = component.get("v.entry");
var compEvent = component.getEvent("bevt_AutocompleteSelect");
compEvent.setParams({"autoCompleteId" : autoCompleteId, "entry" : entry });
compEvent.fire();
},
})
The input component. It handles one of the To, Cc, or Bcc inputs.
<!-- b_InputWithAutocomplete.cmp
Usage:
c.b_InputWithAutocomplete listForAutocomplete="{!v.listForThisInput}" autoCompleteId="{!v.someUniqueId}" />
Users of this component may need to use the component several times in the same form. So,
we need a unique ID for each declaration so the bevt_AutocompleteSelect event can
identify which input's autocomplete was just resolved along with the selected value.
-->
<aura:component>
<!--declare input attributes-->
<aura:attribute name="autoCompleteId" type="String" />
<aura:attribute name="label" type="String" />
<aura:attribute name="searchLabel" type="String" />
<aura:attribute name="originalList" type="String[]" />
<aura:attribute name="activeList" type="String[]" />
<aura:method name="setLists" action="{!c.setLists}"
description="Outer component can use this method to setup or change the originalList">
<aura:attribute name="newList" type="String[]"/>
<aura:attribute name="prePopList" type="String[]"/>
</aura:method>
<!--declare working attributes-->
<aura:attribute name="filteredList" type="String[]" />
<aura:attribute name="searchTerm" type="String" />
<!--declare output attributes-->
<aura:attribute name="selectedList" type="String[]" default="[]" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<aura:handler name="bevt_AutocompleteSelect"
event="c:bevt_AutocompleteSelect"
action="{!c.bevt_AutocompleteSelect}" />
<aura:registerEvent name="bevt_AutocompleteList" type="c:bevt_AutocompleteList" />
<div aura:id="searchRes" class="slds-form-element slds-lookup" data-select="single">
<div class="slds-grid b_wrap_flex b_input_with_pills uiPillContainer--default">
<!--This part is for display search bar for lookup-->
<aura:iteration items="{!v.selectedList}" indexVar="inx" var="pill">
<lightning:layoutItem class="slds-p-around_xx-small">
<span class="slds-pill b_input_pill pill uiPill--default uiPill" role="option" tabindex="0">
<span class="slds-pill__label" title="{!pill}">{!pill} </span>
<button class="slds-button slds-button--icon slds-pill__remove"
data-value="{!pill}" onclick="{!c.clear}">
<!--c:svg close'}" /-->
x <span class="slds-assistive-text">Remove</span>
</button>
</span>
</lightning:layoutItem>
</aura:iteration>
<lightning:layoutItem>
<div>
<div>
<ui:inputText aura:id="searchInput"
updateOn="keyup" keyup="{!c.keyPressController}"
class="slds-lookup__search-input slds-input b_input"
value="{!v.searchTerm}" placeholder="{!v.searchLabel}" />
</div>
<!--This part is for Display typehead lookup result List-->
<!-- div aura:id="searchRes" class="slds-form-element slds-lookup slds-is-close" data-select="single"-->
<div aura:id="searchRes" id="{!'searchRes'+ v.autoCompleteId}"
class="slds-lookup__menu slds slds-hide">
<ul class="slds-lookup__list" role="listbox">
<aura:iteration items="{!v.filteredList}" var="entry">
<c:b_LookupResult entry="{!entry}"
autoCompleteId="{!v.autoCompleteId}" />
</aura:iteration>
</ul>
</div>
</div>
</lightning:layoutItem>
</div>
</div>
</aura:component>
Controller
({
doInit : function(component, event, helper) {
var list = component.get("v.originalList");
console.log("Init has list ", list);
},
keyPressController : function(component, event, helper) {
var searchTerm = component.get("v.searchTerm");
var searchBox = helper.findSearchBox(component);
if( searchTerm.length > 0 ){
console.log("keyPressController open with ", searchTerm);
helper.show(searchBox);
helper.searchHelper(component,event,searchTerm);
}
else{
console.log("keyPressController close");
helper.hide(searchBox);
}
},
setLists : function(component,event,helper) {
var params = event.getParam('arguments');
if (params) {
var list = params.newList;
list.sort();
component.set("v.originalList", list);
component.set("v.activeList", list);
console.log("setList with new list ", list);
var prePopList = params.prePopList;
if(prePopList && prePopList.length>0){
prePopList.forEach(function(entry){
helper.addEntry(component,helper,entry);
})
}
}
},
// function for clear the selection
clear :function(component,event,helper){
var ctarget = event.currentTarget;
var entry = ctarget.dataset.value;
helper.removeEntry(component,helper,entry);
},
// Called when the user selects an iter from the list.
bevt_AutocompleteSelect : function(component, event, helper) {
// My parent component may have a form with many copies of this component.
// So only act if the event is for me.
var thisAutoCompleteId = component.get("v.autoCompleteId");
var autoCompleteId = event.getParam("autoCompleteId");
if( thisAutoCompleteId === autoCompleteId ) {
var entry = event.getParam("entry");
helper.addEntry(component, helper, entry);
// clear the search field and hide the search drop down
component.set("v.searchTerm","");
var searchBox = helper.findSearchBox(component);
helper.hide(searchBox);
}
},
})
Input Component Helper:
({
searchHelper : function(component,event,searchTerm) {
// to do search for searchTerm in a list of entries
var list = component.get("v.activeList");
var selectedList = list.filter(function(elem) {
console.log("test includes ", elem);
return elem.includes(searchTerm);
});
console.log("searchHelper has list ", list, " searchTerm", searchTerm, " selectedList", selectedList);
component.set("v.filteredList", selectedList);
},
hide : function(element) {
console.log("hide element ", element);
$A.util.addClass(element, 'slds-hide');
$A.util.removeClass(element, 'slds-show');
},
show : function(element) {
console.log("show element ", element);
$A.util.addClass(element, 'slds-show');
$A.util.removeClass(element, 'slds-hide');
},
open : function(element) {
$A.util.addClass(element, 'slds-is-open');
$A.util.removeClass(element, 'slds-is-close');
},
close : function(element) {
$A.util.addClass(element, 'slds-is-close');
$A.util.removeClass(element, 'slds-is-open');
},
findSearchBox : function(component){
var id = 'searchRes' + component.get("v.autoCompleteId");
//var searchBox = component.find(id); // returns more than one
var searchBox = document.getElementById (id);
return searchBox;
},
fireNewListEvent : function(component,helper){
var autoCompleteId = component.get("v.autoCompleteId");
var bevt_AutocompleteList = $A.get("e.c:bevt_AutocompleteList");
var list = component.get("v.selectedList");
bevt_AutocompleteList.setParams({"autoCompleteId": autoCompleteId, "list": list});
bevt_AutocompleteList.fire();
},
addEntry : function (component,helper, entry){
console.log("add entry ", entry);
// create a pill with the selected entry
var pills = component.get("v.selectedList");
pills.push(entry);
component.set("v.selectedList" , pills);
// remove the entry from the active list (can only add entry once)
var activeList = component.get("v.activeList");
var filtered = activeList.filter(function(d) {
return d !== entry;
})
component.set("v.activeList", filtered);
helper.fireNewListEvent(component,helper);
},
removeEntry : function(component,helper,entry){
console.log("clear entry ", entry);
var pills = component.get("v.selectedList");
var newPills = pills.filter(function(p) {
return p !== entry;
})
component.set("v.selectedList" , newPills);
// add the entry back to the active list
var activeList = component.get("v.activeList");
activeList.push(entry);
activeList.sort();
component.set("v.activeList", activeList);
helper.fireNewListEvent(component,helper);
}
})
Input component styling. Note the need for flex wrap to allow users to enter several email addresses
.THIS .b_wrap_flex {
flex-wrap: wrap;
}
.THIS .b_input {
border: none;
}
.THIS .b_input_pill {
color:blue;
}
That's it. Next is a component that uses and demonstrates the above.
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global">
<aura:attribute name="recordId" type="Id" />
<aura:attribute name="toFieldId" type="String" default="toFieldId" />
<aura:attribute name="ccFieldId" type="String" default="ccFieldId" />
<aura:attribute name="bccFieldId" type="String" default="bccFieldId" />
<aura:attribute name="toList" type="String[]" />
<aura:attribute name="ccList" type="String[]" />
<aura:attribute name="bccList" type="String[]" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<aura:handler event="c.bevt_AutocompleteList" action="{!c.bevt_AutocompleteList}"/>
<div class="edit_panel">
<article class="slds-card">
<div class="slds-card__body">
<article class="slds-tile">
<div class="slds-tile__detail eep-panel">
<dl class="slds-list_horizontal slds-wrap">
<dt class="slds-item_label" title="To">To:</dt>
<dd class="slds-item_detail" title="To Value">
<c.b_InputWithAutocomplete aura:id="toInput" autoCompleteId="{!v.toFieldId}" />
</dd>
<dt class="slds-item_label" title="Cc">Cc:</dt>
<dd class="slds-item_detail" title="Cc Value">
<c.b_InputWithAutocomplete aura:id="ccInput" autoCompleteId="{!v.ccFieldId}" />
</dd>
<dt class="slds-item_label" title="Bcc">Bcc:</dt>
<dd class="slds-item_detail" title="Bcc Value">
<c.b_InputWithAutocomplete aura:id="bccInput" autoCompleteId="{!v.bccFieldId}" />
</dd>
</dl>
</div>
</article>
</div>
</article>
</div>
<div class="slds-pill_container">
<ul class="slds-listbox slds-listbox_horizontal" role="listbox" aria-label="Selected Options:" aria-orientation="horizontal">
<aura:iteration items="{!v.toList}" indexVar="inx" var="pill">
<li class="slds-listbox-item" role="presentation">
<span class="slds-pill__label" title="{!pill}">{!pill} </span>;
</li>
</aura:iteration>
</ul>
</div>
<div class="slds-pill_container">
<ul class="slds-listbox slds-listbox_horizontal" role="listbox" aria-label="Selected Options:" aria-orientation="horizontal">
<aura:iteration items="{!v.ccList}" indexVar="inx" var="pill">
<li class="slds-listbox-item" role="presentation">
<span class="slds-pill__label" title="{!pill}">{!pill} </span>;
</li>
</aura:iteration>
</ul>
</div>
<div class="slds-pill_container">
<ul class="slds-listbox slds-listbox_horizontal" role="listbox" aria-label="Selected Options:" aria-orientation="horizontal">
<aura:iteration items="{!v.bccList}" indexVar="inx" var="pill">
<li class="slds-listbox-item" role="presentation">
<span class="slds-pill__label" title="{!pill}">{!pill} </span>;
</li>
</aura:iteration>
</ul>
</div>
b_auto61
</aura:component>
Demo Controller
({
doInit : function(component, event, helper) {
var emailAddresses = [];
for(var i = 1; i< 5; i++) {
emailAddresses.push('customer'+i+'@customer.com');
}
for(var i = 1; i< 5; i++) {
emailAddresses.push('orderDesk'+i+'@manufacter.com');
}
for(var i = 1; i< 5; i++) {
emailAddresses.push('staff'+i+'@mycompany.com');
}
var prePopList = [];
prePopList.push(emailAddresses[1]);
prePopList.push(emailAddresses[7]);
var toInput = component.find("toInput");
var ccInput = component.find("ccInput");
var bccInput = component.find("bccInput");
toInput.setLists(emailAddresses,prePopList);
ccInput.setLists(emailAddresses);
bccInput.setLists(emailAddresses);
},
bevt_AutocompleteList : function(component,event,helper) {
var autoCompleteId = event.getParam("autoCompleteId");
var list = event.getParam("list");
if(component.get("v.toFieldId") === autoCompleteId) {
component.set("v.toList",list);
}
if(component.get("v.ccFieldId") === autoCompleteId) {
component.set("v.ccList",list);
}
if(component.get("v.bccFieldId") === autoCompleteId) {
component.set("v.bccList",list);
}
}
})