0

I need a Lightning UI component to collect a series of email addresses for the To, CC and Bcc fields of an email. The situation is we're composing an email for a Case. It is normal for us to have over ten emails associated with one case and we're writing to our customers, manufacturers and to other internal staff. So we have a set of email addresses we use often (to other staff and our manufacturer) and we have a set of email addresses that can be taken from the emails related to the Case. So, for this question assume you have 10+ email addresses in an array

The Lightning component needs to be used in any of the To, Ccc or Bcc field that will provide autocomplete service to the user. Once a user selects an address from the list we remove it and let the user select another. Each address is displayed as a pill. The user can change their mind and remove an address. The outer component needs to know the results so it can compose the email and later send it.

<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="emailAddresses" type="Set"  default="['customer1@customer.com'
                                                               , 'customer2@customer.com'
                                                               , 'staff@mycompany.com'
                                                               , 'orders@manufacter.com']"/>
    <c.b_InputWithAutocomplete listForAutocomplete="{!v.emailAddresses}" autoCompleteId="{!v.toFieldId}" />
    <c.b_InputWithAutocomplete listForAutocomplete="{!v.emailAddresses}" autoCompleteId="{!v.ccFieldId}" />
    <c.b_InputWithAutocomplete listForAutocomplete="{!v.emailAddresses}" autoCompleteId="{!v.bccFieldId}" />


</aura:component>

I searched for solutions and found http://sfdcmonkey.com/2017/01/07/custom-lookup-lightning-component/ provided a really good starting point. But his solution can only be used in one situation and is designed to select only one item from the list. We need to let the user select a number of items.

I've been composing a solution and will post it soon. Is that appropriate?

0

Demo showing how the component works

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);            
        }

    }
})
  • The above doesn't let users free type new email addresses. To fix that add an blur event handler and add a if keycode is 13 then call the helper.add entry method. – Bryan Jun 26 '18 at 13:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.