The Blog - Expert Thoughts & Opinions

Table Component for Flows in Salesforce Lightning - Part 2

Following on from ‘Table Component for Flows in Salesforce Lightning - Part 1’ take a look at further enhancing the current flow and adding more functionality to it. Using the same use case as the first version of this flow, we will show how to extend it further by allowing the user to update multiple quotes, use sorting, use pagination and use searching. 

With all these new enhanced functionalities, all the power remains with the system admin as they can configure the component/flow however they wish. This allows the end-user to use only the features set out by the admin. We have been able to add all these functionalities on the client-side. In doing this we keep performance as high as possible while limiting the amount of apex code needed. 

The “Update Multiple Quotes” Lightning Flow

From a sales exec point of view, this is what the flow looks like:

1. The sales exec clicks on an action called “Update Multiple Quotes” from the opportunity.

2. Similar to the ‘Select Primary Quote’ flow, the sales exec views a list of all the quotes under the opportunity.

From this list, the sales exec can select one or multiple quotes to update. Some added functionality is the sales exec can now filter the list of quotes by searching for a particular quote, the sales exec can also use sorting on the list of quotes simply by clicking on the column header. Pagination can also be used to break the full list of quotes into smaller bunches. All 3 of these functionalities can be enabled/disabled by the system admin depending on what the client is looking for.

3. Once again similar to the ‘Select Primary Quote’ flow, the sales exec now has the option to update the opportunity status, update the status of the multiple selected quotes and to also reject the other quotes that weren’t selected.

4. That’s exactly like the ‘Select Primary Quote’ flow. We have updated multiple quotes in a matter of clicks & just saved the sales executive a lot of time by using the flow.

We have taken the existing flow and further extended it adding more functionality, making it more valuable to clients & developers.

Let’s investigate the enhanced flow and see how we embed this Lightning Component into a flow in Salesforce:

The flow may look much different to the flow in the ‘Select Primary Quote’ but in reality, it follows a very similar order to the previous flow but just some steps added to accomplish the functionality we need for the client. 

We won’t go into detail on every single item in the flow but I will discuss what a few of the new items are. 

So following the Screen ‘Select Quote’ there is a Get Records function similar to the previous flow but instead of pulling the selected quote what I have done is used the Opportunity Id to pull all quotes related to the opportunity & we then loop over those related quotes.

In the loop, first, we have a decision function, which we use as a big if statement so to say, this decision checks the Id of the current quote against the ‘Selected Record Id’ string which can contain a single quote Id or multiple quote Ids separated by commas.

If the quote Id is in the ‘Selected Record Id’ then the quote will be added to the SelectedQuotes list variable & then if the quote Id isn’t in ‘Selected Record Id’ the quote will be added to the UnSelectedQuote List.

Once this loop is finished we move on to the next screen which is the Additional Options, this screen is the same as the previous flow as here we chose the Selected Quotes new status and also what we wish to update the opportunity status too & also we can still reject all unselected quotes here as well.

Then finally to update the multiple selected quotes we once again use a loop to do so, once all quotes have been updated we then drop out of the loop and update the opportunity. Also if the user selects the reject all other options we then use a loop as well to update those quotes to a status of rejected.

If you want to know more about this flow, both the flow and the Lightning component of the selection table are available by installing this Salesforce package in your Salesforce environment: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2p000001Brt9 

Configuration Options:

At a very high level with the new enhanced component, the system admin has certain configuration options available to him/her when configuring the flow. 

The system admin has three selection options for the end-user and they’re ‘No Selection’, ‘Single-Selection’ & ‘Multiple Selection’. These three options allow the system admin to decide whether he/she would like to allow the end-user to have no selection, allow the end-user single selection or allow the end-user multiple selections. The system admin should only enable any one of these options as functionally it would make no sense to have multiple selection options enabled at any one time. 

The system admin also has the possibility to enable or disable three separate functionalities and they’re ‘Pagination’, ‘Searching’ & ‘Sorting’. These three options allow the system admin to decide whether he/she would like to allow the end-user to have the ability to paginate when viewing the quotes, the ability to sort the quotes or the ability to search the quotes. The system admin can have any number of these functionalities enabled or disabled, it is completely at the discretion of the system admin and what they wish to allow the end-user the ability to do.

These are the inputs this component accepts:

We use a lot of the same inputs as the previous flow; we just have a few more added to help with the extra functionality.

  • “Multiple Selection” – this is a true or false input. This will tell the component if we’re letting the user select multiple records. In our use case, we’ve passed “True”.
  • “No Selection” – this is a true or false input. This will tell the component if we’re only displaying records in a table or we’re also letting the user select a record. In our use case, we’ve passed “False”. I have used this input to replace the ‘Allow Selection’ input from the previous flow.
  • “Single Selection” – this is a true or false input. This will tell the component if we’re only letting the user select a single record. In our use case, we’ve passed “False”.
  • “Pagination Functionality” – this is a true or false input. This will tell the component if we’re allowing the user to have pagination on the list of quotes. In our use case, we’ve passed “True”.
  • “Sorting Functionality” – this is a true or false input. This will tell the component if we’re allowing the user to have sorting on the list of quotes. In our use case, we’ve passed “True”.
  • “Searching Functionality” – this is a true or false input. This will tell the component if we’re allowing the user to have searching on the list of quotes. In our use case, we’ve passed “True”.

The Lightning Component

In this part, we’re going to dive into the code of the Lightning component. You don’t have to understand the code in order to use the component yourself in flows. However, if you wish to build a similar component, enhance this one or if you’re curious how the component works, this section is for you.

The code of the component is available in our component: https://github.com/Pexlify/Selection-Table-Aura-Component-for-Salesforce-Flows

The FlowTableSelection component is quite standard, here we declare all the attributes we will need & how we wish to display the data retrieved from the apex controller and this all depends on the inputs read in from the flow.

The FlowTableSelection component is quite standard, here we declare all the attributes we will need & how we wish to display the data retrieved from the apex controller and this all depends on the inputs read in from the flow.

FlowTableSelection.cmp file:

<aura:component controller="FlowTableSelectionController" access="global" implements="lightning:availableForFlowScreens"> 
   <aura:attribute name="FieldLabels" type="List"/> 
      <aura:attribute name="TableRows" type="List"/> 
   <aura:attribute name="PaginationRows" type="List"/> 
   <aura:attribute name="FullListRows" type="List"/> 
      <aura:attribute name="ObjectAPIName" type="String"/> 
      <aura:attribute name="WhereClause" type="String"/> 
      <aura:attribute name="FieldSetName" type="String"/> 
      <aura:attribute name="SelectedRecordId" type="String"/> 
   <aura:attribute name="NoSelection" type="Boolean"/> 
      <aura:attribute name="AllowSingleSelection" type="Boolean"/> 
   <aura:attribute name="AllowMultiSelection" type="Boolean"/> 
      <aura:attribute name="NoRecords" type="Boolean" default="true"/> 
      <aura:attribute name="NoRecordsMessage" type="String"/> 
   <aura:attribute name="searchKey" type="String"/> 
   <aura:attribute name="IsSortAscending" type="Boolean" default="false"/> 
   <aura:attribute name="SelectedColumn" type="String"/> 
   <aura:attribute name="PageSize" type="Integer" default="5"/> 
   <aura:attribute name="TotalSize" type="Integer"/> 
   <aura:attribute name="Start" type="Integer" /> 
      <aura:attribute name="End" type="Integer"/> 
   <aura:attribute name="Pagination" type="Boolean"/> 
   <aura:attribute name="Searching" type="Boolean"/> 
   <aura:attribute name="Sorting" type="Boolean"/> 
      <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> 

   <aura:renderIf isTrue="{!not(v.NoRecords)}"> 
      <aura:if isTrue="{!v.Searching}" > 
        <div class="slds-clearfix" style="padding-bottom: 10px;"> 
          <div class="slds-float_right"> 
            <lightning:layoutItem size="12"> 
              <lightning:input name="searchbar" label="Search Quotes" value="{!v.searchKey}" placeholder="Search…" onkeyup="{!c.searchkey}"/> 
            </lightning:layoutItem> 
          </div> 
        </div> 
      </aura:if> 
         <table class="slds-table slds-table_bordered slds-table_striped slds-table_fixed-layout" style="border: 1px solid rgb(217, 219, 221);"> 
            <thead> 
      <aura:if isTrue="{!v.Sorting}" > 
         <tr class="slds-text-title_caps"> 
            <aura:iteration items="{!v.FieldLabels}" var="field_label" indexVar="i"> 
               <aura:if isTrue="{!v.SelectedColumn == field_label}"> 
                  <aura:if isTrue="{!v.IsSortAscending}"> 
                     <th> 
                       <div class="slds-truncate" data-index="{!i}" onclick="{!c.sortByFunction}">&#8593; {!field_label}</div> 
                     </th> 
                   <aura:set attribute="else"> 
                      <th> 
                         <div class="slds-truncate" data-index="{!i}" onclick="{!c.sortByFunction}">&#8595; {!field_label}</div> 
                      </th> 
                   </aura:set> 
                   </aura:if> 
                   <aura:set attribute="else"> 
                      <th> 
                         <div class="slds-truncate" data-index="{!i}" onclick="{!c.sortByFunction}">{!field_label}</div> 
                      </th> 
                   </aura:set> 
                 </aura:if> 
             </aura:iteration> 
         </tr> 
         <aura:set attribute="else"> 
           <tr class="slds-text-title_caps"> 
              <aura:iteration items="{!v.FieldLabels}" var="field_label"> 
              <th> 
              <div class="slds-truncate" >{!field_label}</div> 
              </th> 
              </aura:iteration> 
           </tr> 
        </aura:set> 
     </aura:if> 
             </thead> 
             <tbody> 
     <aura:if isTrue="{!v.Pagination}" > 
              <aura:iteration items="{!v.PaginationRows}" var="row"> 
                 <tr> 
                     <aura:iteration items="{!row.Fields}" var="field"> 
                        <td class="slds-truncate"> 
                           <lightning:layout > 
                              <aura:renderIf isTrue="{!and(v.AllowSingleSelection, field.FirstField)}"> 
                                 <lightning:layoutItem class="slds-m-right_x-small"> 
                                 <lightning:input type="radio" label="" name="SelectedRecord" value="{!row.RecordId}" variant="label-hidden" onchange="{!c.handleRadioClick}"/> 
                                 </lightning:layoutItem> 
                              </aura:renderIf> 
                     <aura:renderIf isTrue="{!and(v.AllowMultiSelection, field.FirstField)}"> 
                        <lightning:layoutItem class="slds-m-right_x-small"> 
                           <lightning:input type="checkbox" label="" name="SelectedRecord" value="{!row.RecordId}" variant="label-hidden" onchange="{!c.handleCheckBoxClick}" checked="{!row.ValueChecked}"/> 
                        </lightning:layoutItem> 
                           </aura:renderIf> 
                           <lightning:layoutItem class="slds-truncate"> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'BOOLEAN')}"> 
                                <lightning:input type="checkbox" disabled="true" label="" value="{!field.Value}" variant="label-hidden"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'CURRENCY')}"> 
                                 <lightning:formattedNumber value="{!field.Value}" style="currency" maximumFractionDigits="2"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'PERCENT')}"> 
                                 <lightning:formattedNumber value="{!field.Value}" style="percent" maximumFractionDigits="2"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'DOUBLE' || field.FieldType == 'LONG' || field.FieldType == 'INTEGER')}"> 
                                  <lightning:formattedNumber value="{!field.Value}" style="decimal" maximumFractionDigits="2"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'PHONE')}"> 
                                 <lightning:formattedPhone value="{!field.Value}"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'DATE' || field.FieldType == 'TIME' || field.FieldType == 'DATETIME' || field.FieldType == 'STRING' || field.FieldType == 'PICKLIST' ||  field.FieldType == 'MULTIPICKLIST' || field.FieldType == 'ADDRESS' || field.FieldType == 'ID' || field.FieldType == 'REFERENCE' || field.FieldType == 'EMAIL')}"> 
                                 {!field.Value} 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'TEXTAREA')}"> 
                                 <aura:unescapedHtml value="{!field.Value}"/> 
                              </aura:renderIf> 
                              <aura:renderIf isTrue="{!(field.FieldType == 'URL')}"> 
                              <lightning:formattedUrl value="{!field.Value}" /> 
                              </aura:renderIf> 
                            </lightning:layoutItem> 
                         </lightning:layout> 
                         </td> 
                    </aura:iteration> 
                </tr> 
              </aura:iteration> 
     <aura:set attribute="else"> 
        <aura:iteration items="{!v.TableRows}" var="row"> 
            <tr> 
                <aura:iteration items="{!row.Fields}" var="field"> 
                   <td class="slds-truncate"> 
                      <lightning:layout > 
                         <aura:renderIf isTrue="{!and(v.AllowSingleSelection, field.FirstField)}"> 
                           <lightning:layoutItem class="slds-m-right_x-small"> 
                             <lightning:input type="radio" label="" name="SelectedRecord" value="{!row.RecordId}" variant="label-hidden" onchange="{!c.handleRadioClick}"/> 
                           </lightning:layoutItem> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!and(v.AllowMultiSelection, field.FirstField)}"> 
                            <lightning:layoutItem class="slds-m-right_x-small"> 
                               <lightning:input type="checkbox" label="" name="SelectedRecord" value="{!row.RecordId}" variant="label-hidden" onchange="{!c.handleCheckBoxClick}"/> 
                            </lightning:layoutItem> 
                         </aura:renderIf> 
                         <lightning:layoutItem class="slds-truncate"> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'BOOLEAN')}"> 
                             <lightning:input type="checkbox" disabled="true" label="" value="{!field.Value}" variant="label-hidden"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'CURRENCY')}"> 
                            <lightning:formattedNumber value="{!field.Value}" style="currency" maximumFractionDigits="2"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'PERCENT')}"> 
                            <lightning:formattedNumber value="{!field.Value}" style="percent" maximumFractionDigits="2"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'DOUBLE' || field.FieldType == 'LONG' || field.FieldType == 'INTEGER')}"> 
                            <lightning:formattedNumber value="{!field.Value}" style="decimal" maximumFractionDigits="2"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'PHONE')}"> 
                            <lightning:formattedPhone value="{!field.Value}"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'DATE' || field.FieldType == 'TIME' || field.FieldType == 'DATETIME' || field.FieldType == 'STRING' || field.FieldType == 'PICKLIST' || field.FieldType == 'MULTIPICKLIST' || field.FieldType == 'ADDRESS' || field.FieldType == 'ID' || field.FieldType == 'REFERENCE' || field.FieldType == 'EMAIL')}"> 
                            {!field.Value} 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'TEXTAREA')}"> 
                            <aura:unescapedHtml value="{!field.Value}"/> 
                         </aura:renderIf> 
                         <aura:renderIf isTrue="{!(field.FieldType == 'URL')}"> 
                            <lightning:formattedUrl value="{!field.Value}" /> 
                         </aura:renderIf> 
                       </lightning:layoutItem> 
                     </lightning:layout> 
                  </td> 
               </aura:iteration> 
            </tr> 
         </aura:iteration> 
     </aura:set> 
   </aura:if> 
        </tbody> 
     </table> 
   <aura:if isTrue="{!v.Pagination}" > 
      <div class="slds-grid slds-gutters slds-p-top_medium"> 
         <div class="slds-col"> 
            <aura:If isTrue="{!greaterthan(v.TotalSize,v.End)}"> 
               <p class="slds-p-left_large">{! 'Showing ' + (v.Start + 1) + '-' + (v.End) + ' out of ' + v.TotalSize}</p> 
               <aura:set attribute="else"> 
                   <p class="slds-p-left_large">{! 'Showing ' + (v.Start + 1) + '-' + (v.TotalSize) + ' out of ' + v.TotalSize}</p> 
               </aura:set> 
             </aura:If> 
          </div> 
          <div class="slds-col"> 
             <lightning:buttonGroup class="slds-p-left_small"> 
                <lightning:button label="&lt;&lt; First" disabled="{!v.Start == 0}" onclick="{!c.first}"/> 
                <lightning:button label="&lt; Previous" disabled="{!v.Start == 0}" onclick="{!c.previous}"/> 
                <lightning:button label="Next &gt;" disabled="{!v.End >= v.TotalSize}" onclick="{!c.next}"/> 
                <lightning:button label="Last &gt;&gt;" disabled="{!v.End >= v.TotalSize}" onclick="{!c.last}"/> 
             </lightning:buttonGroup> 
           </div> 
      </div> 
   </aura:if> 
      <aura:set attribute="else"> 
          <div class="slds-align_absolute-center"> 
              {!v.NoRecordsMessage} 
          </div> 
      </aura:set> 
  </aura:renderIf> 
</aura:component>

Between the FlowTableSelectionController.js & the FlowTableSelectionHelper.js is where all of the extra functionality has been added, in the controller we kick off all methods but then pass them to the helper to have clean & clear code.

FlowTableSelectionController.js:

({ doInit : function (component, event, helper) { helper.doInit(component, event, helper); }, handleRadioClick : function (component, event, helper) { let selected_record_id = event.getSource().get('v.value'); component.set('v.SelectedRecordId', selected_record_id); }, handleCheckBoxClick : function (component, event, helper) { let selected_record_id = event.getSource().get('v.value'); let current_selected_ids = component.get('v.SelectedRecordId'); if (helper.isListEmpty(current_selected_ids)) { component.set('v.SelectedRecordId', selected_record_id); } else if (helper.checkIfListContainSelectedId(current_selected_ids, selected_record_id)) { helper.handleListContainsSelectedId(component,current_selected_ids,selected_record_id); } else if (!current_selected_ids.includes(selected_record_id)) { component.set('v.SelectedRecordId', current_selected_ids + ',' + selected_record_id); } }, first : function(component, event, helper) { let pagination_list = component.get("v.TableRows"); let page_size = component.get("v.PageSize"); component.set('v.PaginationRows',pagination_list.slice(0,page_size)); component.set("v.Start",0); component.set("v.End",page_size); }, last : function(component, event, helper) { let pagination_list = component.get("v.TableRows"); let page_size = component.get("v.PageSize"); let total_size = component.get("v.TotalSize"); component.set('v.PaginationRows',pagination_list.slice(total_size-page_size,total_size)); component.set("v.Start",total_size-page_size); component.set("v.End",total_size); }, next : function(component, event, helper) { let pagination_list = component.get("v.TableRows"); let total_size = component.get("v.TotalSize"); let page_size = component.get("v.PageSize"); let end_of_current_list = component.get("v.End"); let new_end_value = end_of_current_list + page_size; component.set("v.Start",end_of_current_list); return (new_end_value > total_size) ? helper.handleEndValueGreaterThanTotal(component,total_size,pagination_list,end_of_current_list) : helper.handleEndValueLessThanTotal(component,new_end_value,pagination_list,end_of_current_list); }, previous : function(component, event, helper) { let pagination_list = component.get("v.TableRows"); let page_size = component.get("v.PageSize"); let start_of_current_list = component.get("v.Start"); let new_start_value = start_of_current_list - page_size; component.set("v.End",start_of_current_list); return (new_start_value < 0) ? helper.handleStartValueLessThanZero(component,pagination_list,start_of_current_list) : helper.handleStartValueGreaterThanZero(component,new_start_value,pagination_list,start_of_current_list); }, searchkey : function(component, event, helper) { helper.searchQuoteList(component, event, helper); }, sortByFunction : function(component, event, helper) { let selected_column = event.target.dataset.index; helper.sortList(component,selected_column); }, })

FlowTableSelectionHelper.js:

({ doInit : function (component, event, helper) { let action = component.get('c.getRecordsToDisplayInTable'); action.setParams({ sobject_name: component.get('v.ObjectAPIName'), field_set_name: component.get('v.FieldSetName'), where_clause: component.get('v.WhereClause') }); action.setCallback(this, function(response){ let state = response.getState(); if(state === 'SUCCESS'){ let apex_method_result = response.getReturnValue(); if (apex_method_result.Success) { if (apex_method_result.TableRows.length != 0) { component.set("v.NoRecords", false); component.set("v.FieldLabels", apex_method_result.FieldLabels); component.set("v.TableRows", apex_method_result.TableRows); component.set("v.FullListRows", apex_method_result.TableRows); component.set("v.TotalSize",component.get("v.TableRows").length); component.set("v.SelectedColumn", apex_method_result.FieldLabels[0]); component.set("v.Start",0); component.set("v.End",component.get("v.PageSize")); if (component.get("v.Pagination")) { component.set('v.PaginationRows', apex_method_result.TableRows.slice(0,component.get("v.PageSize"))); } if (component.get("v.Sorting")) { this.sortList(component,0); } } } else { helper.handleError(component, apex_method_result.ErrorMessage); } } else if (state === 'ERROR'){ helper.handleError(component, response.getError()); } }); $A.enqueueAction(action); }, handleError : function (component,error) { component.set("v.NoRecordsMessage", error); }, searchQuoteList : function (component, event, helper) { let self = this; let search_key = component.get("v.searchKey"); let full_rowlist = component.get('v.TableRows'); let filtered_list = []; if (search_key != "") { for (var i = 0; full_rowlist.length; i++) { let quote_name = full_rowlist.Fields[0].Value; if (quote_name.toLowerCase().indexOf(search_key.toLowerCase()) > -1) { filtered_list.push(full_rowlist); } if (i == full_rowlist.length -1) { self.setSearchResults(component,filtered_list); } } } else { self.resetSearchList(component,full_rowlist); } }, sortList : function (component,selected_column) { let self = this; let column_headers = component.get("v.FieldLabels"); let full_list_rows = component.get("v.TableRows"); let sort_order = component.get("v.IsSortAscending"); full_list_rows.sort(function(a, b) { if (a.Fields[selected_column].FieldType == 'DATE') { let formatted_dateA = self.formatDateMethod(a.Fields[selected_column].Value); let date_value1 = new Date(formatted_dateA); let formatted_dateB = self.formatDateMethod(b.Fields[selected_column].Value); let date_value2 = new Date(formatted_dateB); return (sort_order == false) ? date_value1 - date_value2 : date_value2 - date_value1; } else if (a.Fields[selected_column].FieldType == 'CURRENCY') { let currencyA = a.Fields[selected_column].Value; let currencyB = b.Fields[selected_column].Value; return (sort_order == false) ? parseFloat(currencyA) - parseFloat(currencyB) : parseFloat(currencyB) - parseFloat(currencyA); } else { let textA = a.Fields[selected_column].Value.toLowerCase().replace(' ', ''); let textB = b.Fields[selected_column].Value.toLowerCase().replace(' ', ''); return (sort_order == false) ? (textA < textB) ? -1 : (textA > textB) ? 1 : 0 : (textA > textB) ? -1 : (textA < textB) ? 1 : 0; } }); self.setSortResults(component,sort_order,full_list_rows,column_headers,selected_column); }, formatDateMethod : function (currentDate) { let year = currentDate.substring(6, 10); let month = currentDate.substring(3, 5); let day = currentDate.substring(0, 2); let formatted_date = year+'-'+month+'-'+day; return formatted_date; }, isListEmpty : function (current_selected_ids) { if (current_selected_ids === undefined || current_selected_ids === '') { return true; } else { return false; } }, checkIfListContainSelectedId : function (current_selected_ids,selected_record_id) { if (current_selected_ids.includes(selected_record_id)) { return true; } else { return false; } }, handleListContainsSelectedId : function (component,current_selected_ids,selected_record_id) { if (current_selected_ids == selected_record_id) { component.set('v.SelectedRecordId', ''); } else { this.handleListHasMultipleIds(component,current_selected_ids,selected_record_id); } }, handleListHasMultipleIds : function (component,current_selected_ids,selected_record_id) { let new_selected_id = current_selected_ids.replace(selected_record_id,''); if (this.isStartOfListInvalid(new_selected_id)) { component.set('v.SelectedRecordId', new_selected_id.slice(1)); } else { this.checkRemainingList(component,new_selected_id); } }, checkRemainingList : function(component,new_selected_id) { if (this.isEndOfListInvalid(new_selected_id)) { component.set('v.SelectedRecordId', new_selected_id.slice(0,-1)); } else if (new_selected_id.includes(',,')){ component.set('v.SelectedRecordId', new_selected_id.replace(',,',',')); } else { component.set('v.SelectedRecordId', new_selected_id); } }, isStartOfListInvalid : function(new_selected_id) { if (new_selected_id.charAt(0) == ',') { return true; } else { return false; } }, isEndOfListInvalid : function(new_selected_id) { if (new_selected_id[new_selected_id.length -1] == ',') { return true; } else { return false; } }, handleEndValueGreaterThanTotal : function(component,total_size,pagination_list,end_of_current_list) { component.set("v.End",total_size); component.set('v.PaginationRows',pagination_list.slice(end_of_current_list,total_size)); }, handleEndValueLessThanTotal : function(component,new_end_value,pagination_list,end_of_current_list) { component.set("v.End",new_end_value); component.set('v.PaginationRows',pagination_list.slice(end_of_current_list,new_end_value)); }, handleStartValueLessThanZero : function (component,pagination_list,start_of_current_list) { component.set("v.Start",0); component.set('v.PaginationRows',pagination_list.slice(0,start_of_current_list)); }, handleStartValueGreaterThanZero : function (component,new_start_value,pagination_list,start_of_current_list) { component.set("v.Start",new_start_value); component.set('v.PaginationRows',pagination_list.slice(new_start_value,start_of_current_list)); }, setSearchResults : function (component,filtered_list) { component.set("v.Start",0); component.set("v.End",component.get("v.PageSize")); if (component.get("v.Pagination")) { component.set('v.PaginationRows', filtered_list); component.set("v.TotalSize",component.get("v.PaginationRows").length); } else { component.set('v.TableRows', filtered_list); component.set("v.TotalSize",component.get("v.TableRows").length); } }, resetSearchList : function (component,full_rowlist) { component.set("v.TotalSize",component.get("v.TableRows").length); component.set("v.Start",0); component.set("v.End",component.get("v.PageSize")); if (component.get("v.Pagination")) { component.set("v.PaginationRows", full_rowlist.slice(0,component.get("v.PageSize"))); } else { component.set('v.TableRows', component.get('v.FullListRows')); } }, setSortResults : function (component,sort_order,full_list_rows,column_headers,selected_column) { if (sort_order == false) { component.set("v.IsSortAscending", true); } else { component.set("v.IsSortAscending", false); } component.set("v.TotalSize", full_list_rows.length); component.set("v.Start",0); component.set("v.End",component.get("v.PageSize")); component.set("v.SelectedColumn", column_headers[selected_column]); if (component.get("v.Pagination")) { component.set('v.PaginationRows', full_list_rows.slice(0,component.get("v.PageSize"))); } else { component.set('v.TableRows', full_list_rows); } } })

FlowTableSelection.design:

<design:component> 
   <design:attribute name="ObjectAPIName" label="Object API Name" /> 
   <design:attribute name="WhereClause" label="Where Clause" /> 
   <design:attribute name="FieldSetName" label="Field Set Name" /> 
   <design:attribute name="SelectedRecordId" label="Selected Record Id" /> 
   <design:attribute name="Pagination" label="Pagination functionality" default="false"/> 
   <design:attribute name="Searching" label="Searching functionality" default="false"/> 
   <design:attribute name="Sorting" label="Sorting functionality" default="false"/> 
   <design:attribute name="NoSelection" label="No Selection" default="false"/> 
   <design:attribute name="AllowSingleSelection" label="Single Selection" default="false"/> 
   <design:attribute name="AllowMultiSelection" label="Multiple Selection" default="false"/> 
   <design:attribute name="NoRecordsMessage" label="No Records Message" default="There are no records to display"/> 
</design:component>

The full Apex controller :

public class FlowTableSelectionController { @AuraEnabled public static ApexMethodResult getRecordsToDisplayInTable(String sobject_name, String field_set_name, String where_clause){ ApexMethodResult apex_method_result = new ApexMethodResult(); try { Schema.FieldSet field_set = getFieldSetForObject(sobject_name, field_set_name); apex_method_result.setFieldLabels(field_set); String query = getQueryForObjectFieldSetAndWhereClause(sobject_name, field_set, where_clause); List<sObject> table_records = Database.query(query); for(sObject table_record : table_records){ TableRow table_row = new TableRow(table_record, field_set); apex_method_result.TableRows.add(table_row); } } catch(Exception e){ apex_method_result.handleException(e); } return apex_method_result; } private static Schema.FieldSet getFieldSetForObject(String sobject_name, String field_set_name){ Map<String, Schema.SObjectType> global_describe = Schema.getGlobalDescribe(); if(!global_describe.containsKey(sobject_name)){ throw new FlowTableSelectionException('Bad object specified ' + sobject_name); } Schema.SObjectType sobject_type = global_describe.get(sobject_name); Schema.DescribeSObjectResult sobject_type_describe = sobject_type.getDescribe(); if(!sobject_type_describe.FieldSets.getMap().containsKey(field_set_name)){ throw new FlowTableSelectionException('Can\'t find fieldset ' + field_set_name); } return sobject_type_describe.FieldSets.getMap().get(field_set_name); } private static String getQueryForObjectFieldSetAndWhereClause(String sobject_name, Schema.FieldSet field_set, String where_clause){ List<String> fields_api_name = new List<String>(); for(Schema.FieldSetMember fieldset_member : field_set.getFields()){ fields_api_name.add(fieldset_member.getFieldPath()); } String query = 'SELECT ' + String.join(fields_api_name, ', ') + ' FROM ' + sobject_name; if(!String.isBlank(where_clause)){ query += ' WHERE ' + where_clause; } return query; } @TestVisible private class ApexMethodResult { @AuraEnabled public List<TableRow> TableRows; @AuraEnabled public List<String> FieldLabels; @AuraEnabled public Boolean Success; @AuraEnabled public String ErrorMessage; public ApexMethodResult(){ this.Success = true; this.TableRows = new List<TableRow>(); this.FieldLabels = new List<String>(); } public void handleException(Exception e){ this.Success = false; this.ErrorMessage = e.getMessage(); } public void setFieldLabels(Schema.FieldSet field_set){ for(Schema.FieldSetMember fieldset_member : field_set.getFields()){ this.FieldLabels.add(fieldset_member.getLabel()); } } } @TestVisible private class TableRow { @AuraEnabled public List<Field> Fields; @AuraEnabled public String RecordId; @AuraEnabled public Boolean ValueChecked; public TableRow(sObject record, Schema.FieldSet field_set){ this.RecordId = record.Id; this.ValueChecked = false; this.Fields = new List<Field>(); for(Schema.FieldSetMember fieldset_member : field_set.getFields()){ Field table_row_field = new Field(record, fieldset_member, this.Fields.isEmpty()); this.Fields.add(table_row_field); } } } @TestVisible private class Field { @AuraEnabled public String Value; @AuraEnabled public String FieldType; @AuraEnabled public Boolean FirstField; public Field(sObject record, Schema.FieldSetMember fieldset_member, Boolean first_field){ this.FirstField = first_field; String field_api_name = fieldset_member.getFieldPath(); this.FieldType = String.valueOf(fieldset_member.getType()); if(record.get(field_api_name) != null){ if(this.FieldType == 'DATE'){ this.Value = ((Date)record.get(field_api_name)).format(); } else if (this.FieldType == 'DATETIME'){ this.Value = ((DateTime)record.get(field_api_name)).format(); } else if (this.FieldType == 'PERCENT'){ this.Value = String.valueOf((Decimal)record.get(field_api_name) / 100.0); } else { this.Value = String.valueOf(record.get(field_api_name)); } } } } @TestVisible private class FlowTableSelectionException extends Exception {} }

 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Today's post is by one of our Salesforce Developers, Aidan Dunne.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

As always, thanks for reading, if you enjoyed this post please feel free to share it and tag us @Pexlify

Pexlify are a Salesforce Platinum Partner in the UK & Ireland

If you’re interested in Salesforce Solutions, contact us today to set up a hassle-free consultation.