Lightning Component Framework Super Badge

Lightning Component Framework Specialist super badge step 2

Build the query-by-example form
Create a form displaying a drop-down that lists each boat type, along with Search and New buttons, using BoatSearchForm.cmp, BoatSearchResults.cmp, and 
BoatSearch.cmp, as described in the business requirements.

Add these components to a Lightning page named Friends with Boats, and activate the page as a new tab in Lightning Experience and the Salesforce App.
Lastly, create a Lightning application named FriendswithBoats.app that has a layout that is similar to the Lightning page.

BoatSearchForm.cmp

 <aura:component description="BoatSearchForm" controller="BoatSearchFormController" implements="flexipage:availableForAllPageTypes">
  
     <aura:registerEvent name="launchNewBoatForm" type="c:launchNewBoatForm"/>
 <!-- Handle component init in a client-side controller -->
 <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
 <aura:handler name="launchNewBoatForm" event="c:launchNewBoatForm" action="{!c.handleNewBoatForm}"/>

 <!-- dynamically load the BoatTypes -->
 <aura:attribute name="BoatTypes" type="BoatType__c[]" />
 <aura:attribute name="selectedType" type="String" default="foo"/>
 <aura:attribute name="standAlone" type="Boolean" default="true"/>
  <article class="slds-card slds-m-bottom_medium">
 <div class="slds-media__body">
 <div>

 <lightning:layout horizontalAlign="center" verticalAlign="center" >

 <lightning:layoutItem padding="horizontal-medium">
 <!-- Create a dropdown menu with options -->
 <lightning:select aura:id="boatTypes" label="" name="selectType"
 onchange="{!c.handleChange}">
 <option value="">All Types</option>
 <aura:iteration items="{!v.BoatTypes}" var="boatType">
 <option value="{!boatType.Id}">{!boatType.Name}</option>
 </aura:iteration>
 </lightning:select>
 </lightning:layoutItem>


 <lightning:layoutItem>

 <div class="slds-button-group" role="group">
 <lightning:button class="slds-button" variant="brand" label="Search" onclick="{!c.search}"/>
    <br></br>
 <aura:if isTrue="{!v.standAlone}">
 <lightning:button class="slds-button" variant="neutral" label="New" onclick="{!c.newBoat}"/>
 </aura:if>
 </div>
 </lightning:layoutItem>

 </lightning:layout>
 </div>
 </div>
 </article>

 </aura:component>

BoatSearchController.js
 ({
    doInit : function(component, event, helper){

        helper.loadBoatTypes(component);
    },

    handleChange : function(component, event, helper){
        console.log(component.find("boatTypes").get("v.value"));
        component.set("v.selectedType", component.find("boatTypes").get("v.value"));
    },

    search : function(component, event, helper){
        var selectedType = component.get("v.selectedType");
        console.log("Search button pressed " + selectedType)
    },

    newBoat : function(component, event, helper){
        var boatTypeId = component.get("v.selectedType");
        console.log("New button pressed " + boatTypeId);
        var requestNewBoat = component.getEvent("launchNewBoatForm");
        requestNewBoat.setParams({"boatTypeId": boatTypeId});
        requestNewBoat.fire();
    },

    handleNewBoatForm: function(component, event, helper){
        console.log("handleNewBoatForm handler called.")
        var boatTypeId = component.get("v.selectedType");

        console.log(boatTypeId);
        var createNewBoat = $A.get("e.force:createRecord");
        createNewBoat.setParams({
            "entityApiName": "Boat__c",
        })
        if(! boatTypeId==""){
            createNewBoat.setParams({
                "defaultFieldValues": {'BoatType__c': boatTypeId}
           })
        }
        createNewBoat.fire();
    },
    //more handlers here
})

BoatSearchHelper.js

({
    loadBoatTypes: function(component){
    //create the action
            console.log("Helper started");
            var action = component.get("c.getBoatTypes");

            //add the callback behavior for when the response is received
            action.setCallback(this,function(response){
            var state = response.getState();
            if (state === "SUCCESS"){
                component.set("v.BoatTypes", response.getReturnValue());
                console.log(response.getReturnValue());
                }
                else {
                console.log("Failed with state: " + state);
                }
            });

            //send action off to be executed
            $A.enqueueAction(action);
       },
})

Apex Controller:
public with sharing class BoatSearchFormController
{
   @AuraEnabled
    public static List<BoatType__c> getBoatTypes()
    {
        return [SELECT Id, Name from BoatType__c ORDER BY Name];
    }
    
}


FriendswithBoats.app
<aura:application extends="force:slds">
    <lightning:layout >
                 
                 <lightning:card title="Find a Boat" class="slds-m-top_10px" >
                          <c:BoatSearchForm />
                 </lightning:card>

    </lightning:layout>
</aura:application>

Event:
launchNewBoatForm.eve
<aura:event type="APPLICATION" description="Event template" >
   <aura:attribute name="boat" type="Boat__c"/>

</aura:event>

After that to create Lightning App Builder --- Create APP

Create a Lightning page named Friends with Boats that uses the Main Column and Right Sidebar Layout. Put the BoatSearch component in the main column. Activate the page as a new tab in Lightning Experience and the Salesforce App. 



Implement the BoatTile and BoatSearchResults components
Create a new BoatTile component and update your BoatSearchResults container to loop through all the results returned from an Apex controller BoatSearchResults to display an unfiltered list of every boat that HowWeRoll leases.

Define the method getBoats() in BoatSearchResults, to return search results as described in the business requirements. BoatSearchResults.cmp displays search results with a helper method, onSearch(), and displays each result as a BoatTile component. 

BoatSearchResults.cmp

<aura:component controller="BoatSearchResults">

    <aura:handler name="init" action="{!c.doSearch}" value="{!this}"/>
 <aura:attribute name="boats" type="Boat__c[]" />

 <lightning:layout horizontalAlign="center" verticalAlign="center" multipleRows='true'>
            <lightning:layoutItem flexibility="grow"  class="slds-m-right_small" >
     <aura:iteration items="{!v.boats}" var="boatVar">
                 <c:BoatTile boat="{!boatVar}"/>
                </aura:iteration>
      <aura:if isTrue="{!v.boats.length > 0}">
                <aura:iteration items="{!v.boats}" var="bot">
                    <lightning:layoutItem flexibility="grow" class="slds-m-around_small">
                        <c:BoatTile boat="{!bot}" />
                    </lightning:layoutItem>
                </aura:iteration>
                <aura:set attribute="else">
                    <lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">
                        <ui:outputText value="No boats found" />
                    </lightning:layoutItem>
                </aura:set>
            </aura:if>


     </lightning:layoutItem>
    </lightning:layout>
</aura:component>

BoatSearchResultsController.js

({
 doSearch : function(component, event, helper) {
  alert(component.get("v.boatTypeId")); //<---here I am getting undefined
        helper.onSearch(component); //calling helper method
 },
     search: function(component, event, helper){
        var params = event.getParam('arguments');
        alert(params.boatTypeId); //<---getting proper value
        alert(component.set("v.boatTypeId", params.boatTypeId)); //<---here I am getting undefined
        var a = component.get('c.doSearch');
        $A.enqueueAction(a);
    }
})

BoatSearchResultsHelper.js
({
    onSearch : function(component, event) {
        var boatTypId = component.get("v.boatTypeId");
        alert(boatTypId); //<---here I am getting undefined
        console.log("boatTypId--> " + boatTypId);
        var action = component.get("c.getBoats");
        action.setParams({boatTypeId:boatTypId});

        //add the callback behavior for when the response is received
        action.setCallback(this, function(response){
            var state = response.getState();
            console.log("state " + state);
            if(state === "SUCCESS"){
                var res = response.getReturnValue();
                component.set("v.boats", res);
                //console.log("v.boats ->"+ JSON.stringify(res));
            }
            else{
                console.log("Failed with state: " + state);
            }
        });

        //send action off to be executed
        $A.enqueueAction(action);
    },
})



BoatTile.cmp


<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
    <aura:attribute name="boat" type="Boat__c" />
        <lightning:button class="tile">
            <!-- Image -->
            <div style="{!'background-image: url(\'' + v.boat.Picture__c + '\')'}" class="innertile">
              <div class="lower-third">
               <h1 class="slds-truncate">{!v.boat.Contact__r.Name}</h1>
              </div>
            </div>
        </lightning:button>

</aura:component>

BoatTileHelper.js

.THIS.tile {
    position:relative;
    display: inline-block;
    width: 100%;
    height: 220px;
    padding: 1px !important;
}

.THIS .innertile {
    position: relative;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 100%;
    height: 100%;
}

.THIS .lower-third {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    color: #FFFFFF;
    background-color: rgba(0, 0, 0, .4);
    padding: 6px 8px;
}

.THIS.selected {
    border: 3px solid rgb(0, 112, 210);

}

BoatSearchResults.apex
public with sharing class BoatSearchResults  { 
@AuraEnabled    
public static List <Boat__c> getBoats(String boatTypeId) {    
if(boatTypeId != '')  {         
return [SELECT id, BoatType__c, picture__c, name,contact__r.Name FROM Boat__c WHERE BoatType__c =:boatTypeId];    
} else {       
return [SELECT id, BoatType__c, picture__c, name,contact__r.Name FROM Boat__c];    
}      
}    

}


Implement the search filter
Create a FormSubmit event to allow your BoatSearchFormto pass the selected boat type to the BoatSearchResultscomponent, which queries Apex and stores the results.

Handle FormSubmit with a controller action, onFormSubmit, and pass formData.boatTypeId from the controller to search, a public method on the BoatSearchResults component. The search function uses a helper function, onSearch(), and controller function, doSearch(), to get the list of boats. 


1. BoatSearchResults.cmp
    <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" 
                access="global" controller="BoatSearchResults">
    <aura:handler name="init" action="{!c.doSearch}" value="{!this}"/>
    <aura:attribute name="boatTypeId" type="String" />
    <aura:attribute name="boats" type="Boat__c[]" />  
    <!--<aura:method name="search" description="Sample method with parameter">
        <aura:attribute name="boatTypeId" type="String"  />
    </aura:method>-->
    <!-- set up the aura:method for search -->
    <aura:method name="search" description="accepts boatTypeId
            and executes search that refreshes the boats attribute">
        <aura:attribute name="boatTypeId" type="Id"/>
    </aura:method>
    <!-- Display errors, if any -->
    <!--
    <aura:if isTrue="{!not(empty(v.errorString))}">
        <div class="recordError">
            <ui:message title="Error" severity="error" closable="true">
                {!v.errorString}
            </ui:message>
        </div>
    </aura:if>-->
    <aura:if isTrue="{!not(empty(v.boats))}">
        <lightning:layout multipleRows="true" horizontalAlign="center">
                <aura:iteration items="{!v.boats}" var="boatVar">
                    <lightning:layoutItem flexibility="grow"  class="slds-m-right_small" >   
                        <c:BoatTile boat="{!boatVar}"/>
                    </lightning:layoutItem>
                </aura:iteration>
        </lightning:layout>
         <aura:set attribute="else">
            <div class="slds-align_absolute-center">No boats found</div>
        </aura:set>
    </aura:if>
</aura:component>

2. BoatSearchForm.cmp

    <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" controller="BoatSearchFormController" >
    <aura:handler name="formsubmit"
                  event="c:FormSubmit"
                  action="{!c.onFormSubmit}"
                  phase="capture"/>
    
    <aura:attribute name="searchOptions" type='String[]' default='All'/>
    <aura:attribute name='searchOptionToIdMap' type='Map' default="{All:''}" />
    <aura:attribute name='showNewButton' type='Boolean' default='false'/>

        
    <lightning:layout horizontalAlign="center"   >

       <lightning:layoutItem class="slds-grid_vertical-align-center" >

           <lightning:select aura:id='typeSelect' name='selectItem' label='' onchange=''>
             <aura:iteration items='{!v.searchOptions}' var='option'>
                 <option value='{!option}' text='{!option}'></option>
             </aura:iteration>
         </lightning:select>
       </lightning:layoutItem>

       <lightning:layoutItem class="slds-grid_vertical-align-center" >
         <lightning:button label="Search" variant="brand" onclick='{!c.onFormSubmit}' />
         <aura:if isTrue='{!v.showNewButton}'>
            <lightning:button variant='neutral' label='New' onclick='{!c.createBoat}'/>
        </aura:if>
       </lightning:layoutItem>

    </lightning:layout>

</aura:component>

3. BoatSearch.cmp

    <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" access="global" >
<lightning:card title="Find a Boat" class="slds-m-top_10px" >
     <c:BoatSearchForm />
</lightning:card>    
<lightning:card title="Matching Boats" >
     <c:BoatSearchResults /> 
</lightning:card>
<aura:handler name="formsubmit"
                  event="c:FormSubmit"
                  action="{!c.onFormSubmit}"
                  phase="capture"/>

</aura:component>

4. BoatSearchFormController.js

({    createBoat: function (component,event,helper) {
        var createRecordEvent = $A.get('e.force:createRecord');
        if (createRecordEvent) {
            var typeName = component.find('typeSelect').get('v.value');
            var typeMap = component.get('v.searchOptionToIdMap');
            var typeId = null;
            if (typeName && typeMap && typeMap[typeName]) {
                typeId = typeMap[typeName];
            }
            createRecordEvent.setParams({
                'entityApiName': 'Boat__c',
                'defaultFieldValues': {
                    'BoatType__c': typeId
                }
            });
            createRecordEvent.fire();
        }
    },
    
    onFormSubmit : function(component, event, helper){
        var boatTypeId = component.get("v.selectedType");
        console.log("Search button pressed " + boatTypeId);
        var formSubmit = component.getEvent("FormSubmit");
        formSubmit.setParams({"formData":
                            {"boatTypeId" : boatTypeId}
        });
        formSubmit.fire();
    },

    
})


5. BoatSearchResults.apxc

public with sharing class BoatSearchResults  {
    @AuraEnabled
     public static List <Boat__c> getBoats(String boatTypeId) {
      if(boatTypeId != '')  {
             return [SELECT id, BoatType__c, picture__c, name,contact__r.Name
                    FROM Boat__c
                    WHERE BoatType__c =:boatTypeId];
      } else {
          return [SELECT id, BoatType__c, picture__c, name,contact__r.Name
                    FROM Boat__c];
      }
         }
         }

6. BoatSearchResultsHelper.js

({
    onSearch : function(component) {
        var action = component.get("c.getBoats");
        action.setParam({"boatTypeId":""});
        action.setCallback(this, function(response){
        var status = response.getState();
            if(status === "SUCCESS"){
             if(! $A.util.isEmpty(response.getReturnValue())){
                    component.set("v.boatTypeId",response.getReturnValue()); 
                } else {
                     component.set("v.recordError","No boats found");
                }
            }
        });
        $A.enqueueAction(action);
    }
})

7. Event FormSubmit.evt

<aura:event type="COMPONENT" description="Boat Event">
    <aura:attribute name="formData" type="Object"/>
    
</aura:event>

8. BoatSearchController.js
({
    onFormSubmit : function(component, event, helper){
        console.log("event received by BoatSearchController.js");
        var formData = event.getParam("formData");
        var boatTypeId = formData.boatTypeId;
        var BSRcmp = component.find("BSRcmp");
        var auraMethodResult = BSRcmp.search(boatTypeId);
        console.log("auraMethodResult: " + auraMethodResult);
    }
})


Highlight the selected boat
Fire a new BoatSelect event when a BoatTile is clicked, which sets the selectedBoatId on BoatSearchResults and in turn toggles the selected attribute on the right BoatTile, triggering the addition of a CSS class that shows a dark blue border around the selected boat as shown in the requirements. 

Do this by defining a click handler on the BoatTile’s lightning:button that invokes controller function onBoatClick, and raises the BoatSelect event, as laid out in the business requirements.



1.BoatTile.cmp



<<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" access="global" >

              <aura:attribute name="boat" type="Boat__c" />

    <aura:attribute name="selected" type="Boolean" default="false" />



    <aura:registerEvent name="BoatSelect" type="c:BoatSelect"/>

   

    <lightning:button name="{!v.boat.Id}" class="{!v.selected? 'tile selected' : 'tile'}"
                       onclick="{!c.onBoatClick}" >
        <div style="{! 'background-image:url(\'' + v.boat.Picture__c + '\'); '}" class="innertile">
          <div class="lower-third">
           <h1 class="slds-truncate">{!v.boat.Contact__r.Name}</h1>
          </div>
        </div>
    </lightning:button>
</aura:component>

2.BoatTile.css

.THIS.tile {
    position:relative;
    display: inline-block;
    width: 100%;
    height: 220px;
    padding: 1px !important;
}

.THIS .innertile {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 100%;
    height: 100%;
}

.THIS .lower-third {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    color: #FFFFFF;
    background-color: rgba(0, 0, 0, .4);
    padding: 6px 8px;
}

.THIS.selected {
    border-color: rgb(0, 112, 210);
    border-style: solid;
    border-width: 5px;
}

3.BoatSearchResults.aspx
public with sharing class BoatSearchResults  {
    @AuraEnabled
     public static List <Boat__c> getBoats(String boatTypeId) {
      if(boatTypeId != '')  {
             return [SELECT id, BoatType__c, picture__c, name,contact__r.Name
                    FROM Boat__c
                    WHERE BoatType__c =:boatTypeId];
      } else {
          return [SELECT id, BoatType__c, picture__c, name,contact__r.Name
                    FROM Boat__c];
      }
         }
         }

4.BoatSearchResults.cmp
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes"
                access="global" controller="BoatSearchResults">
    <aura:handler name="init" action="{!c.doSearch}" value="{!this}"/>
    <aura:attribute name="boatTypeId" type="String" />
    <aura:attribute name="boats" type="Boat__c[]" />
    <aura:handler name="BoatSelect" event="c:BoatSelect" action="{!c.onBoatSelect}"/>
     <aura:attribute name="selectedBoatId" type="Id"/>

    <lightning:layout multipleRows="true" >

        <aura:iteration items="{!v.boats}" var="boat">
            <lightning:layoutItem  padding="around-small">
                <c:BoatTile boat="{!boat}"
                            selected="{!boat.Id == v.selectedBoatId ? 'true' : 'false' }"/>
            </lightning:layoutItem>
        </aura:iteration>

        <aura:if isTrue="{!v.boats.length==0}">
            <lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">
                <ui:outputText value="No boats found" />
            </lightning:layoutItem>
        </aura:if>

    </lightning:layout>
</aura:component>

5.BoatTileController.js
({
    onBoatClick : function(component, event, helper) {
        var cmpEvent = component.getEvent("BoatSelect");
        var boatId = event.getSource().get("v.name");
        cmpEvent.setParams({
            "boatId" : boatId
        });
        cmpEvent.fire();
    }
})

6.BoatSearchResults.js
({
              doSearch : function(component, event, helper) {
        component.get("v.boatTypeId");
        helper.onSearch(component, event, helper);
              },
   
    search: function(component, event, helper){
        var params = event.getParam('arguments');
        console.log("boatTypeId extracted: " + params.boatTypeId);
        component.set("v.boatTypeId", params.boatTypeId);
        helper.onSearch(component);
        return "search complete.";
    },
    onBoatSelect : function(component, event, helper) {
              var boatId = event.getParam("boatId");
        console.log(boatId);
        component.set("v.selectedBoatId",boatId);
    }
   
})
7.BoatSearchResultsHelper.js
({
    onSearch : function(component) {
        var action = component.get("c.getBoats");
        action.setParam({"boatTypeId":""});
        action.setCallback(this, function(response){
        var status = response.getState();
            if(status === "SUCCESS"){
             if(! $A.util.isEmpty(response.getReturnValue())){
                    component.set("v.boatTypeId",response.getReturnValue());
                } else {
                     component.set("v.recordError","No boats found");
                }
            }
        });
        $A.enqueueAction(action);
    }
})





8.BoatSelect.evt

<aura:event type="COMPONENT" description="fires when a user clicks a boat on BoatSearchResults.cmp">
    <aura:attribute name="BoatId" type="Id"/>
</aura:event>


Display boat details
Create two new components—BoatDetails and BoatDetail—as well as a new event BoatSelected. Raise the new event from BoatTile, and leverage Lightning Data Service to output boat details. Deploy the BoatDetails component in the top right corner of the Lightning page.


1. BoatDetail.cmp

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >

    <aura:attribute name="boat" type="Boat__c"/>

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <aura:attribute name="showButton" type="Boolean" default="false"/> 

     <lightning:button label="Full Details" onclick="{!c.onFullDetails }" />

    <lightning:card iconName="utility:anchor">     

        <aura:set attribute="title">

            {!v.boat.Contact__r.Name}'s boat

        </aura:set>

        <aura:set attribute="actions">

            <aura:if isTrue='{!v.showButton}'>

            <lightning:button label="Full Details" onclick="{!c.onFullDetails}"/>

            </aura:if>

        </aura:set>

        <p class="slds-p-horizontal_small">

            <lightning:layout >             

                <lightning:layoutItem flexibility="grow" size="6" mediumDeviceSize="6" largeDeviceSize="6">

                    <div class="slds-p-horizontal--small">

                        <div class="boatproperty">

                            <span class="label">Boat Name:</span>

                            <span><ui:outputText value="{!v.boat.Name}"/></span>

                        </div>

                        <div class="boatproperty">

                            <span class="label">Type:</span>

                            <span><ui:outputText value="{!v.boat.BoatType__r.Name}"/></span>

                        </div>

                        <div class="boatproperty">

                            <span class="label">Length:</span>

                            <span><ui:outputText value="{!v.boat.Length__c}"/> ft</span>

                        </div>

                        <div class="boatproperty">

                            <span class="label">Est. Price:</span>

                            <span><lightning:formattedNumber value="{!v.boat.Price__c}" currencyCode="USD" style="currency" currencyDisplayAs="symbol"/></span>

                        </div>

                        <div class="boatproperty">

                            <span class="label">Description:</span>

                           <span><ui:outputRichText class="slds-text-longform" value="{!v.boat.Description__c}"/></span>

                        </div>

                    </div>

                </lightning:layoutItem>

                <lightning:layoutItem flexibility="grow" size="6" mediumDeviceSize="6" largeDeviceSize="6">

                    <div class="imageview" style="{!'background-image:url(\'' + v.boat.Picture__c + '\')'}"/>

                </lightning:layoutItem>

            </lightning:layout>

        </p>

    </lightning:card>

</aura:component>
2. BoatDetailController.js

({

     onFullDetails: function(component, event, helper) {

        var navEvt = $A.get("e.force:navigateToSObject");

        navEvt.setParams({

            "recordId": component.get("v.boat.Id")

        });

        navEvt.fire();

    }

})

3. BoatDetails.cmp

<aura:component description="BoatDetails"

                implements="flexipage:availableForAllPageTypes">






    <aura:attribute name="boat" type="Boat__c"/>

    <aura:attribute name="recordError" type="String"/>

    <aura:attribute name="id" type="Id" default="" access="public"/>

    <aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}"/>



    <force:recordData aura:id="service"

                      recordId="{!v.id}"

                      mode="VIEW"

                      fields=  "Id,

                                Name,

                                Description__c,

                                Price__c, Length__c,

                                Contact__r.Name,
                                Contact__r.Email,

                                Contact__r.HomePhone,

                                BoatType__r.Name,

                                Picture__c"



                      targetFields="{!v.boat}"

                      targetError="{!v.recordError}"

                      recordUpdated="{!c.onRecordUpdated}" />



    <aura:if isTrue="{! !empty(v.boat)}">

        <article class="slds-card">

                <lightning:tabset>

                    <lightning:tab label="Details" id="details">

                     <c:BoatDetail boat="{!v.boat}"/>

                    </lightning:tab>

                    <lightning:tab label="Reviews" id="boatreviewtab">

                        Sample review

                    </lightning:tab>

                    <lightning:tab label="Add Review" id="addReview">

                        Sample add review

                    </lightning:tab>

                </lightning:tabset>

        </article>

    </aura:if>

</aura:component>

4. BoatDetailsController.js

({

    init: function(component, event, helper) {

        component.set("v.enableFullDetails", $A.get("e.force:navigateToSObject"));

    },

    onFullDetails: function(component, event, helper) {

        var navEvt = $A.get("e.force:navigateToSObject");

        navEvt.setParams({

            "recordId": component.get("v.boat.Id")

        });

        navEvt.fire();

    }, 

    onBoatSelected : function(component, event, helper) {

        var boatSelected = event.getParam("boat");

        component.set("v.id",boatSelected.Id);

        var service = component.find("service");

        service.reloadRecord() ;

    },

    onRecordUpdated : function(component, event, helper) {

    },

    onBoatReviewAdded : function(component, event, helper) {

        console.log("Event received");

        component.set("v.selTabId", "boatreviewtab");

    }

})

5. BoatTile.cmp

<aura:component >



    <aura:attribute name="boat" type="Boat__c" />

    <aura:attribute name="selected" type="boolean" default="false"/>

    <aura:registerEvent name="boatselected" type="c:BoatSelected"/>

    <aura:attribute name="selectedBoatId" type="Id"/>

    <aura:registerEvent name="BoatSelect" type="c:BoatSelect"/>



        <lightning:button onclick="{!c.onBoatClick}"

                          class="{! v.selected ? 'tile selected' : 'tile' }">

            <div style="{!'background-image:url(\'' + v.boat.Picture__c + '\')'}"

                 class="innertile">

                <div class="lower-third">

                    <h1 class="slds-truncate">{! v.boat.Contact__r.Name}</h1>

                </div>

            </div>

        </lightning:button>



</aura:component>

6. BoatTileController.js

({

    onBoatClick : function(component, event, helper) {

        var BoatSelectEvent = component.getEvent('BoatSelect');

        var boat = component.get('v.boat');

        BoatSelectEvent.setParams({

            "boatId" : boat.Id

        });

        BoatSelectEvent.fire();

       

        //var BoatSelectedEvt = component.getEvent('boatselected');

        var BoatSelectedEvt = $A.get('e.c:BoatSelected');

        BoatSelectedEvt.setParams({

            "boat" : boat

        });     

        BoatSelectedEvt.fire();

    }

})
7. BoatTile.css


.THIS.selected {

    border: 3px solid rgb(0, 112, 210);

}



.THIS.tile {

    position:relative;

    display: inline-block;

    width: 100%;

    height: 220px;

    padding: 1px !important;

}



.THIS .innertile {

    background-size: cover;

    background-position: center;

    background-repeat: no-repeat;

    width: 100%;

    height: 100%;

}



.THIS .lower-third {

    position: absolute;

    bottom: 0;

    left: 0;

    right: 0;

    color: #FFFFFF;

    background-color: rgba(0, 0, 0, .4);

    padding: 6px 8px;

}



.THIS.selected {

    border-color: rgb(0, 112, 210);

    border-style: solid;

    border-width: 5px;

}

8. BoatSearch.cmp

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" access="global" >

<lightning:card title="Find a Boat" class="slds-m-top_10px" >

     <c:BoatSearchForm />

</lightning:card> 

<lightning:card title="Matching Boats" >

     <c:BoatSearchResults />

</lightning:card>

<aura:handler name="formsubmit"

                  event="c:FormSubmit"

                  action="{!c.onFormSubmit}"

                  phase="capture"/>



</aura:component>

9. BoatSearchController.js

({

    onFormSubmit : function(component, event, helper){

        console.log("event received by BoatSearchController.js");

        var formData = event.getParam("formData");

        var boatTypeId = formData.boatTypeId;

        var BSRcmp = component.find("BSRcmp");

        var auraMethodResult = BSRcmp.search(boatTypeId);

        console.log("auraMethodResult: " + auraMethodResult);

    }

})

10. BoatSearchResults.apxc

public with sharing class BoatSearchResults  {

    @AuraEnabled

     public static List <Boat__c> getBoats(String boatTypeId) {

      if(boatTypeId != '')  {

             return [SELECT id, BoatType__c, picture__c, name,contact__r.Name

                    FROM Boat__c

                    WHERE BoatType__c =:boatTypeId];

      } else {

          return [SELECT id, BoatType__c, picture__c, name,contact__r.Name

                    FROM Boat__c];

      }

         }

         }

11. BoatSearchForm.cmp

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" controller="BoatSearchFormController" >

    <aura:handler name="formsubmit"

                  event="c:FormSubmit"

                  action="{!c.onFormSubmit}"

                  phase="capture"/>

   

    <aura:attribute name="searchOptions" type='String[]' default='All'/>

    <aura:attribute name='searchOptionToIdMap' type='Map' default="{All:''}" />

    <aura:attribute name='showNewButton' type='Boolean' default='false'/>



       

    <lightning:layout horizontalAlign="center"   >



       <lightning:layoutItem class="slds-grid_vertical-align-center" >



           <lightning:select aura:id='typeSelect' name='selectItem' label='' onchange=''>

             <aura:iteration items='{!v.searchOptions}' var='option'>

                 <option value='{!option}' text='{!option}'></option>

             </aura:iteration>

         </lightning:select>

       </lightning:layoutItem>



       <lightning:layoutItem class="slds-grid_vertical-align-center" >

         <lightning:button label="Search" variant="brand" onclick='{!c.onFormSubmit}' />

         <aura:if isTrue='{!v.showNewButton}'>

            <lightning:button variant='neutral' label='New' onclick='{!c.createBoat}'/>

        </aura:if>

       </lightning:layoutItem>



    </lightning:layout>



</aura:component>

12. BoatSearchFormController.js

({    createBoat: function (component,event,helper) {

        var createRecordEvent = $A.get('e.force:createRecord');

        if (createRecordEvent) {

            var typeName = component.find('typeSelect').get('v.value');

            var typeMap = component.get('v.searchOptionToIdMap');

            var typeId = null;

            if (typeName && typeMap && typeMap[typeName]) {

                typeId = typeMap[typeName];

            }

            createRecordEvent.setParams({

                'entityApiName': 'Boat__c',

                'defaultFieldValues': {

                    'BoatType__c': typeId

                }

            });

            createRecordEvent.fire();

        }

    },

   

    onFormSubmit : function(component, event, helper){

        var boatTypeId = component.get("v.selectedType");

        console.log("Search button pressed " + boatTypeId);

        var formSubmit = component.getEvent("FormSubmit");

        formSubmit.setParams({"formData":

                            {"boatTypeId" : boatTypeId}

        });

        formSubmit.fire();

    },



   

})

12.BoatSearchResults.cmp

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes"

                access="global" controller="BoatSearchResults">

    <aura:handler name="init" action="{!c.doSearch}" value="{!this}"/>

    <aura:attribute name="boatTypeId" type="String" />

    <aura:attribute name="boats" type="Boat__c[]" />

    <aura:handler name="BoatSelect" event="c:BoatSelect" action="{!c.onBoatSelect}"/>

     <aura:attribute name="selectedBoatId" type="Id"/>



    <lightning:layout multipleRows="true" >



        <aura:iteration items="{!v.boats}" var="boat">

            <lightning:layoutItem padding="around-small">

                <c:BoatTile boat="{!boat}"

                            selected="{!boat.Id == v.selectedBoatId ? 'true' : 'false' }"/>

            </lightning:layoutItem>

        </aura:iteration>



        <aura:if isTrue="{!v.boats.length==0}">

            <lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">

                <ui:outputText value="No boats found" />

            </lightning:layoutItem>

        </aura:if>



    </lightning:layout>

</aura:component>
13. BoatSearchResultsController.js

({ search: function(component, event, helper)

  {

      var params = event.getParam('arguments');

   component.set("v.boatTypeId", params.boatTypeId);

      component.get("c,doSearch"); },

 doSearch : function (component, event, helper)

  { component.get("v.boatTypeId"); helper.onSearch(component); },



 onBoatSelect : function(component, event, helper) {

        var boatId = event.getParam("boatId");

        console.log(boatId);

        component.set("v.selectedBoatId",boatId);

    }

 })


finally add those component in your Lighting page Ex :- Friends with Boats
Add boat reviews
Instantiate an AddBoatReview component inside the Add Review tab and display the form. When a user clicks Submit, save the record using Lightning Data Service and fire a BoatReviewAdded event that the BoatDetails parent component listens for so that it can switch the active tab to Reviews. Don’t worry about displaying the reviews yet.

BoatDetails.cmp
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="selTabId" type="Id"/> <aura:attribute name="boat" type="Boat__c"/> <aura:attribute name="id" type="Id" /> <aura:attribute name="recordError" type="String"/> <aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}" /> <aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/> <aura:handler name="BoatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/> <force:recordData aura:id="service" layoutType="FULL" recordId="{!v.id}" fields="Id,Name,Description__c,Price__c,Length__c,Contact__r.Name, Contact__r.Email,Contact__r.HomePhone,BoatType__r.Name,Picture__c" targetError="{!v.recordError}" targetFields="{!v.boat}" mode="EDIT" recordUpdated="{!c.onRecordUpdated}" /> <aura:if isTrue="{!not(empty(v.id))}"> <lightning:tabset variant="scoped" selectedTabId="{!v.selTabId}" aura:id="details"> <lightning:tab label="Details" id="details" > <c:BoatDetail boat="{!v.boat}"/> </lightning:tab> <lightning:tab label="Reviews" id="boatreviewtab" > <c:BoatReviews boat="{!v.boat}" aura:id="BRcmp"/> </lightning:tab> <lightning:tab label="Add Review" id="addReview" > <c:AddBoatReview boat="{!v.boat}"/> </lightning:tab> </lightning:tabset> </aura:if> <aura:if isTrue="{!not(empty(v.recordError))}"> <div class="recordError"> <ui:message title="Error" severity="error" closable="true"> {!v.recordError} </ui:message> </div> </aura:if> </aura:component>
BoatDetailscontroller.js
({ init: function(component, event, helper) { component.set("v.enableFullDetails", $A.get("e.force:navigateToSObject")); }, onFullDetails: function(component, event, helper) { var navEvt = $A.get("e.force:navigateToSObject"); navEvt.setParams({ "recordId": component.get("v.boat.Id") }); navEvt.fire(); }, onBoatSelected : function(component, event, helper) { var boatSelected = event.getParam("boat"); component.set("v.id",boatSelected.Id); var service = component.find("service"); service.reloadRecord() ; }, onRecordUpdated : function(component, event, helper){ //invoke a refresh on the reviews tab, calling public method refresh //BRcmp is the aura:id for the component when invoked in BoatDetails.cmp var boat = component.get("v.boat"); console.log("onRecordUpdated called | boat: " + boat.Id); var BRcmp = component.find("BRcmp"); console.log(BRcmp); var auraMethodResult = BRcmp.refresh(); console.log("auraMethodResult: " + auraMethodResult); }, onBoatReviewAdded : function(component, event, helper) { console.log('Event received'); component.find("details").set("v.selectedTabId", "boatreviewtab"); } })
BoatReviews.cmp
<aura:component controller="BoatReviews"> <aura:attribute name="boat" type="Boat__c" access="public"/> <aura:attribute name="boatReviews" type="BoatReview__c[]" access="private"/> <aura:handler name="init" action="{!c.doInit}" value="{!this}"/> <aura:handler name="change" value="{!v.boat}" action="{!c.doInit}"/> <aura:method name="refresh" action="{!c.doInit}"> </aura:method> <aura:if isTrue="{!v.boatReviews.length==0}"> <lightning:layoutItem class="slds-align_absolute-center" flexibility="auto"
padding="around-small"> <ui:outputText value="No reviews available" /> </lightning:layoutItem> </aura:if> <div class="slds-feed"> <ul class="slds-feed__list"> <ui:scrollerWrapper class="scrollerSize"> <aura:iteration items="{!v.boatReviews}" var="review"> <li class="slds-feed__item slds-scrollable_y"> <article class="slds-post"> <header class="slds-post__header slds-media"> <div class="slds-media__figure"> <a href="javascript:void(0);" class="slds-avatar slds-avatar_circle slds-avatar_large"> <img alt="{!review.CreatedBy.Name}" src="{!review.CreatedBy.SmallPhotoUrl}" title="{!review.CreatedBy.Name}" /> </a> </div> <div class="slds-media__body"> <div class="slds-grid slds-grid_align-spread slds-has-flexi-truncate"> <p><a data-userid="{!review.CreatedBy.Id}" href="javascript:void(0);"
title="{!review.CreatedBy.Name}" onclick="{!c.onUserInfoClick}"> {!review.CreatedBy.Name} </a> — {!review.CreatedBy.CompanyName} </p> </div> <p class="slds-text-body_small"> <lightning:formattedDateTime value="{!review.LastModifiedDate}" year="numeric" month="short" day="numeric" hour="2-digit" minute="2-digit" second="2-digit" /> </p> </div> </header> <div class="slds-post__content slds-text-longform"> <p class="slds-text-title_caps">{!review.Name}</p> <p class="slds-text-body_small"><lightning:formattedRichText
value="{!review.Comment__c}"/> </p> </div> <footer class="slds-post__footer"> <ul class="slds-post__footer-actions-list slds-list_horizontal"> <li class="slds-col slds-item slds-m-right_medium"> <c:FiveStarRating aura:id="FiveStarRating"
value="{!BoatReview.Rating__c}" readonly="true"/> </li> </ul> </footer> </article> </li> </aura:iteration> </ui:scrollerWrapper> </ul> </div> </aura:component>
BoatReviewscontroller.js
({ onUserInfoClick : function(component,event,helper){ var userId = event.currentTarget.getAttribute("data-userid"); var navEvt = $A.get("e.force:navigateToSObject"); navEvt.setParams({ "recordId" : userId, }); navEvt.fire() }, doInit : function(component,event,helper){ console.log("BRCjs: doInit"); helper.onInit(component, event); }, refresh : function(component,event,helper){ console.log("refresh called") this.doInit; } })
BoarReviewsHelper.js
({ onInit : function(component, event){ var boat = component.get("v.boat"); console.log("BRHjs:onInit started: boatId is " + boat.Id); var action = component.get("c.getAll"); action.setParams({"boatId" : boat.Id}); console.log("boatId: " + boat.Id); //add the callback behavior for when the response is received action.setCallback(this,function(response){ var state = response.getState(); if (state === "SUCCESS"){ component.set("v.boatReviews", response.getReturnValue()); console.log("APEX success"); } else { console.log("Failed with state: " + state); } }); //send action off to be executed in APEX $A.enqueueAction(action); }, })
FiveStarRating.cmp
<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
    <aura:attribute name="value" type="Integer" default="0" />
    <aura:attribute name="readonly" type="Boolean" default="false" />
              <aura:handler name="change" value="{!v.value}" action="{!c.onValueChange}"/>
    <ltng:require styles="{!$Resource.fivestar.rating.css}" />
              <ltng:require scripts="{!$Resource.fivestar.rating.js}"
                             afterScriptsLoaded="{!c.afterScriptsLoaded}" />   
              <ul aura:id="ratingarea" class="{!v.readonly ? 'readonly c-rating' : 'c-rating'}"></ul>
</aura:component>
FiveStarRatingcontroller.js
({
    afterScriptsLoaded : function(component, event, helper) {
        var domEl = component.find("ratingarea").getElement();
        var currentRating = component.get('v.value');
        var readOnly = component.get('v.readonly');
        var maxRating = 5;
        var callback = function(rating) {
            component.set('v.value',rating);
        }
        component.ratingObj = rating(domEl,currentRating,maxRating,callback,readOnly);
    },
   
    onValueChange: function(component,event,helper) {
        if (component.ratingObj) {
            var value = component.get('v.value');
            component.ratingObj.setRating(value,false);
        }
    }
})
BoatReviews.apex
public with sharing class BoatReviews
{
    @AuraEnabled
    public static List<BoatReview__c>  getAll(String boatTypeId)
    {
       
 return [SELECT Id, Comment__c,Rating__c,CreatedBy.Id,CreatedBy.Name,CreatedBy.SmallPhotoUrl,CreatedBy.CompanyName,LastModifiedDate,CreatedDate from BoatReview__c where Boat__c =: boatTypeId];
    }
}
AddBoatreview.cmp
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    <aura:attribute name="boatReview" type="BoatReview__c" access="public"/>
    <aura:attribute name="boatReviewRecord" type="Object" access="public"/>
    <aura:attribute name="boat" type="Boat__c"/>
    <aura:attribute name="recordError" type="String" access="private"/>
    <aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <force:recordData aura:id="service"
                      targetError="{!v.recordError}"
                      targetRecord="{!v.boatReviewRecord}"
                      targetFields="{!v.boatReview}"
                      fields="Id,Name,Comment__c,Boat__c"
                      recordUpdated="{!c.onRecordUpdated}"
                      />
    <lightning:layout multipleRows="true">
        <lightning:layoutItem size="12" padding="around-small">
            <lightning:input name="title" label="Title" value="{!v.boatReview.Name}"/>
        </lightning:layoutItem>
       
        <lightning:layoutItem size="12" padding="around-small">
            <label class="slds-form-element__label" for="input-id-01">Description</label>
            <lightning:inputRichText value="{!v.boatReview.Comment__c}" disabledCategories="FORMAT_FONT"/>
        </lightning:layoutItem>
       
        <lightning:layoutItem size="12" padding="around-small">
            <label class="slds-form-element__label" for="input-id-01">Description</label>
            <c:FiveStarRating value="{!v.boatReview.Rating__c}" readonly="false"/>
        </lightning:layoutItem>
       
       
        <lightning:layoutItem size="12" class="slds-align--absolute-center">
            <lightning:button iconName="utility:save" label="Submit" onclick="{!c.onSave}"/>
        </lightning:layoutItem>
    </lightning:layout>
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            <ui:message title="Error" severity="error" closable="true">
                {!v.recordError}
            </ui:message>
        </div>
    </aura:if>
</aura:component>
AddBoatReviewcontroller.js
({
    doInit : function(component, event, helper) {
        helper.onInit(component,event);
    },
    onSave : function(component, event, helper) {
        component.set("v.boatReview.Boat__c",component.get("v.boat.Id"));
        component.find("service").saveRecord(function(saveResult){
            if(saveResult.state==="SUCCESS" || saveResult.state === "DRAFT")
            {
               
               var resultsToast = $A.get("e.force:showToast");
                if(resultsToast)
                {
                    resultsToast.setParams({
                        "title": "Saved",
                        "message": "Boat Review Created"
                    });
                    resultsToast.fire();
                }
                else
                {
                    alert('Boat Review Created');
                }
            }
            else if (saveResult.state === "ERROR") {
                var errMsg='';
                console.log('Problem saving record, error: ' + JSON.stringify(saveResult.error));
                for (var i = 0; i < saveResult.error.length; i++) {
                    errMsg += saveResult.error[i].message + "\n";
                }
                component.set("v.recordError", errMsg);
            }
            else
            {
                console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
            }
            var boatReviewAddedEvnt=component.getEvent("boatReviewAdded");
                boatReviewAddedEvnt.fire();
                 helper.onInit(component,event,helper);
          
        });
      
    },
    onRecordUpdated : function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
            var changedFields = eventParams.changedFields;
            var saveResultsToast = $A.get("e.force:showToast");
                if(saveResultsToast!='undefined')
                {
                    saveResultsToast.setParams({
                        "title": "Saved",
                        "message": "Boat Review Saved"
                    });
                    saveResultsToast.fire();
                }
                else
                {
                    alert('Boat Review Saved');
                }
        }
    }
})
AddBoatReviewHealper.js
({
    onInit : function(component,event) {
        component.find("service").getNewRecord(
            "BoatReview__c", // sObject type (entityAPIName)
            null,      // recordTypeId
            false,     // skip cache?
            $A.getCallback(function() {
                var rec = component.get("v.boatReview");
                var error = component.get("v.recordError");
                var boat=component.get("v.boat");
                if(error || (rec === null)) {
                    console.log("Error initializing record template: " + error);
                }
                else {
                    component.set("v.boatReviewRecord.Boat__c",boat.Id);
                    component.set("v.boatReview.Boat__c",boat.Id);
                }
            })
        );
    }
})
BoatTitle.cmp
<aura:component >
    <aura:attribute name="boat" type="Boat__c" />
    <aura:attribute name="selected" type="boolean" default="false"/>
    <aura:registerEvent name="boatselected" type="c:BoatSelected"/>
    <aura:attribute name="selectedBoatId" type="Id"/>
    <aura:registerEvent name="BoatSelect" type="c:BoatSelect"/>
    <aura:registerEvent type="c:PlotMapMarker" name="PlotMapMarker"/>
        <lightning:button onclick="{!c.onBoatClick}"
                          class="{! v.selected ? 'tile selected' : 'tile' }">
            <div style="{!'background-image:url(\'' + v.boat.Picture__c + '\')'}"
                 class="innertile">
                <div class="lower-third">
                    <h1 class="slds-truncate">{! v.boat.Contact__r.Name}</h1>
                </div>
            </div>
        </lightning:button>
</aura:component>
BoatTitlecontroller.jc
({
              onBoatClick : function(component, event, helper) {
        var boatId = event.getSource().get("v.name");
        console.log('Selected boat Id',boatId);
        var formSubmit = component.getEvent("BoatSelect");
        formSubmit.setParams({"boatId" : boatId});
        formSubmit.fire();
        console.log('Selectedboad in boattile component!!',component.get("v.boat"));
        // Fire Additional Apllication event step #6 BoatSelected
        var appEvent = $A.get("e.c:BoatSelected");
        appEvent.setParams({
            "boat": component.get("v.boat")
        });
        appEvent.fire(); 
        var boat = component.get('v.boat');
        //send geolocation to map.cmp through the PlotMapMarker Application event
        var lat = boat.Geolocation__Latitude__s;
        var long = boat.Geolocation__Longitude__s;
        var label = boat.Name;
        var sObjectId;
        var plotMapMarkerAppEvent = $A.get("e.c:PlotMapMarker");
        plotMapMarkerAppEvent.setParams({
            "lat"   : lat,
            "long"  : long,
            "label" : label,
            "SObjectId" : boat.Id});
        plotMapMarkerAppEvent.fire();
        console.log('lat ',lat);
       
    }
})
Map.cmp
<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
   
    <aura:attribute name="width"  type="String" default="100%" />
    <aura:attribute name="height" type="String" default="200px" />
    <aura:attribute name="location" type="SObject"/>
    <aura:attribute name="jsLoaded" type="boolean" default="false"/>
    <aura:handler event="c:PlotMapMarker" action="{!c.onPlotMapMarker}" />
    <aura:registerEvent type="c:PlotMapMarker" name="PlotMapMarker"/>
    <ltng:require styles="{!$Resource.Leaflet + '/leaflet.css'}"
                  scripts="{!$Resource.Leaflet + '/leaflet-src.js'}"
                  afterScriptsLoaded="{!c.jsLoaded}" />
   
              <lightning:card title="Current Boat Location" >
                  <div aura:id="map" style="{!'width: ' + v.width + '; height: ' + v.height + '; border:none;'}">
                      <div style="width:100%; height:100%" class="slds-align_absolute-center">Please make a selection</div>
                  </div>
    </lightning:card>
   
</aura:component>
Mapcontroller.js
({
    jsLoaded: function(component) {
        console.debug('here map jsLoaded');
        component.set("v.jsLoaded", true);
    },
   onPlotMapMarker: function(component, event, helper) {
        var id = event.getParam('sObjectId');
        var latitude = event.getParam('lat');
        var longitude = event.getParam('long');
        var label = event.getParam('label');
        //component.set("v.location", {'latitude' : latitude, 'longitude' : longitude});
        component.set('v.location', {
            'lat' : latitude,
            'long' : longitude
        });
       
        console.log('latitude !!',latitude);
       
       
    }
})
PlotMapMarker.event
<aura:event type="APPLICATION" description="PlotMapMarker test">
    <aura:attribute name="sObjectId" type="String"/>
    <aura:attribute name="lat" type="String"/>
    <aura:attribute name="long" type="String"/>
    <aura:attribute name="label" type="String"/>
</aura:event>