Build the Geolocation App Using Salesforce CLI

Learning Objectives

After completing this unit, you’ll be able to:
  • Describe how you use the CLI to create an Apex class.
  • Describe how you use the CLI command to create an Aura component.

What Are We Building?

We’ll build our feature—our geolocation app—by writing code locally, and then synchronizing it to our scratch org, where we can test it. We’ll work our way through Apex and several Aura components.

To keep our focus on what’s new here (Salesforce CLI and scratch orgs), we’re going to reuse code from a project right here on Trailhead, Develop an Account Geolocation App with Aura Components. We’ll repeat many of the steps from that project, but use the CLI at the appropriate moments. We provide all the code in this module so you don’t have to jump back and forth.

This diagram shows a high-level overview of the component interactions:Flow control diagram when user performs a search.

This design makes your app flexible and easier to maintain. As your app and business requirements evolve, you can replace the component that displays the data without reworking the component that queries the data. This design also allows you to reuse these components independently in other applications.

  1. The Account Search component calls a server-side action to search for accounts.
  2. The Apex method returns the SOSL search results.
  3. The Account Search component fires an event notifying other components of the data.
  4. Components that handle the event display the data to the user.

Create the Account Search Apex Controller

In this step, you create an Apex controller that lets your Aura components retrieve a list of accounts with their location information. Apex classes are stored in a folder called classes in force-app/main/default. You can use the CLI to quickly scaffold a new Apex class.

  1. From within the geolocation project directory, run this command from the root of your project.
    sfdx force:apex:class:create -n AccountSearchController -d force-app/main/default/classes
  2. Open AccountSearchController.cls and replace the scaffold code with this code, then save the file.
    public with sharing class AccountSearchController {
        @AuraEnabled
        public static List<Account> searchAccounts( String searchTerm ) {
            List<Account> accounts = new List<Account>();
            if ( String.isNotBlank( searchTerm ) ) {
                List<List<SObject>> searchResults = [
                    FIND :searchTerm
                    RETURNING Account(
                        Id, Name, Phone, Website,
                        BillingStreet, BillingCity,
                        BillingState, BillingPostalCode
                        ORDER BY Name
                        LIMIT 10
                    )
                ];
                accounts = searchResults[0];
            }
            return accounts;
        }
    }
  3. Now, push (synchronize) your new code to the scratch org.
    sfdx force:source:push
    The output of the force:source:push command looks something like this:
    === Pushed Source
    STATE  FULL NAME               TYPE        PROJECT PATH
    ─────  ─────────────────       ──────────  ───────────────────────────────────────
    Add    AccountSearchController ApexClass   force-app/.../AccountSearchController.cls
    Add    AccountSearchController ApexClass   force-app/.../AccountSearchController.cls-meta.xml

    Your scratch org is now updated with the AccountSearchController.

Create the Accounts Loaded Aura Event

Using the single responsibility design principle, we delegate the display of the search results by firing an event that is handled by the Account Map and Account List components you create in later steps.

  1. Create the event in the aura folder.
    sfdx force:lightning:event:create -n AccountsLoaded -d force-app/main/default/aura
  2. Open AccountsLoaded.evt and replace the contents with this code, then save the file.
    <aura:event type="APPLICATION">
        <aura:attribute name="accounts" type="Account[]"/>
    </aura:event>
    
  3. Push your new code to the scratch org.
    sfdx force:source:push

    The output of the force:source:push command looks something like this:

    === Pushed Source
    STATE FULL NAME                         TYPE                 PROJECT PATH
    ───── ───────────────────────────────── ──────────────────── ──────────────────────────────────────────────────────
    Add   AccountsLoaded/AccountsLoaded.evt AuraDefinitionBundle force-app/.../AccountsLoaded/AccountsLoaded.evt
    Add   AccountsLoaded/AccountsLoaded.evt AuraDefinitionBundle force-app/.../AccountsLoaded/AccountsLoaded.evt-meta.xml

Create the Account Search Aura Component

Next, we’re going to repeat much of this process to create an AccountSearch component. Aura component files are grouped together in bundles, which are stored in folders inside the force-app/main/default/aura folder. Similar to Apex classes, you can scaffold an Aura component from the command line.

  1. Create the component in the aura folder:
    sfdx force:lightning:component:create -n AccountSearch -d force-app/main/default/aura

    As you can see, all the required files are created.

    AccountSearch folder expanded to show its files in Visual Studio Code
  2. Open AccountSearch.cmp and replace its contents with the following code, then save the file.
    <aura:component controller="AccountSearchController">
        <aura:registerEvent name="accountsLoaded" type="c:AccountsLoaded"/>
        <aura:handler name="init" value="{!this}" action="{!c.onInit}"/>
        <aura:attribute name="searchTerm" type="String" default="San Francisco"/>
        <lightning:card title="Account Search" iconName="standard:search">
            <div class="slds-form slds-p-around_x-small">
                <lightning:input
                    label="Search"
                    variant="label-hidden"
                    value="{!v.searchTerm}"
                    placeholder="Search by name, phone, website, or address"
                    onchange="{!c.onSearchTermChange}"/>
            </div>
        </lightning:card>
    </aura:component>

    This component has an input field for the user to enter search terms, such as account name or address, and registers event handlers when the component is initialized or the search term changes.

  3. Open AccountSearchController.js and replace its contents with the following code, then save the file.
    ({
        onInit: function( component, event, helper ) {
            // proactively search on component initialization
            var searchTerm = component.get( "v.searchTerm" );
            helper.handleSearch( component, searchTerm );
        },
    
        onSearchTermChange: function( component, event, helper ) {
            // search anytime the term changes in the input field
            var searchTerm = component.get( "v.searchTerm" );
            helper.handleSearch( component, searchTerm );
        }
    })

    The client-side controller handles the component initialization event and when the search term changes. It calls the helper file to perform the search based on the user’s input.

  4. Open AccountSearchHelper.js and replace its contents with the following code, then save the file.
    ({
        // code in the helper is reusable by both
        // the controller.js and helper.js files
        handleSearch: function( component, searchTerm ) {
            var action = component.get( "c.searchAccounts" );
            action.setParams({
                searchTerm: searchTerm
            });
            action.setCallback( this, function( response ) {
                var event = $A.get( "e.c:AccountsLoaded" );
                event.setParams({
                    "accounts": response.getReturnValue()
                });
                event.fire();
            });
            $A.enqueueAction( action );
        }
    })
  5. Push the new source to the scratch org.
    sfdx force:source:push
You see the definition bundle pushed to the scratch org:
=== Pushed Source
STATE FULL NAME                                TYPE                 PROJECT PATH
───── ──────────────────────────────────────── ──────────────────── ──────────────────────────────────────────────────────
Add   AccountSearch/AccountSearch.auradoc      AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.auradoc
Add   AccountSearch/AccountSearch.cmp          AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.cmp
Add   AccountSearch/AccountSearch.cmp          AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.cmp-meta.xml
Add   AccountSearch/AccountSearch.css          AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.css
Add   AccountSearch/AccountSearch.design       AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.design
Add   AccountSearch/AccountSearch.svg          AuraDefinitionBundle force-app/.../AccountSearch/AccountSearch.svg
Add   AccountSearch/AccountSearchController.js AuraDefinitionBundle force-app/.../AccountSearch/AccountSearchController.js
Add   AccountSearch/AccountSearchHelper.js     AuraDefinitionBundle force-app/.../AccountSearch/AccountSearchHelper.js
Add   AccountSearch/AccountSearchRenderer.js   AuraDefinitionBundle force-app/.../AccountSearch/AccountSearchRenderer.js

Create the Account List Aura Component

Next, we create an Aura component that displays accounts in a data table. To know what data to show, it subscribes to the c:AccountsLoaded event that you created in a previous step.

  1. Create the component in the aura folder.
    sfdx force:lightning:component:create -n AccountList -d force-app/main/default/aura
  2. Open AccountList.cmp and replace its contents with the following code, then save the file.
    <aura:component>
        <aura:handler event="c:AccountsLoaded" action="{!c.onAccountsLoaded}"/>
        <lightning:navigation aura:id="navigation"/>
        <aura:attribute name="rows" type="Map[]"/>
        <aura:attribute name="cols" type="Map[]"/>
        <lightning:card title="Account List" iconName="standard:account">
            <lightning:datatable
                data="{!v.rows}"
                columns="{!v.cols}"
                keyField="Id"
                hideCheckboxColumn="true"
                showRowNumberColumn="true"
                onrowaction="{!c.onRowAction}"/>
        </lightning:card>
    </aura:component>

    This component listens for the AccountsLoaded event and displays the event data in a table. As the user searches and finds different results, the list updates accordingly.

  3. Open AccountListController.js and replace its contents with the following code, then save the file.
    ({
        onAccountsLoaded: function( component, event, helper ) {
            var cols = [
                {
                    'label': 'Name',
                    'fieldName': 'Name',
                    'type': 'text'
                },
                {
                    'label': 'Phone',
                    'fieldName': 'Phone',
                    'type': 'phone'
                },
                {
                    'label': 'Website',
                    'fieldName': 'Website',
                    'type': 'url'
                },
                {
                    'label': 'Action',
                    'type': 'button',
                    'typeAttributes': {
                        'label': 'View details',
                        'name': 'view_details'
                    }
                }
            ];
            component.set( 'v.cols', cols );
            component.set( 'v.rows', event.getParam( 'accounts' ) );
        },
    
        onRowAction: function( component, event, helper ) {
            var action = event.getParam( 'action' );
            var row = event.getParam( 'row' );
            if ( action.name == 'view_details' ) {
                var navigation = component.find( 'navigation' );
                navigation.navigate({
                    'type': 'standard__recordPage',
                    'attributes': {
                        'objectApiName': 'Account',
                        'recordId': row.Id,
                        'actionName': 'view'
                    }
                });
            }
        }
    })

    The client-side controller’s onAccountsLoaded function transforms the event data into the format expected by the <lightning:datatable> component. The onRowAction function navigates to the account record of the row the user interacted with.

  4. Push the new code to the scratch org.
    sfdx force:source:push
retargeting