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>

39 comments:

  1. For Step2 challenge. I am getting below error:
    No EVENT named markup://c:launchNewBoatForm found : [markup://c:BoatSearchForm]: Source
    Is there a styling that needs to be created "launchnewboatform".

    Can you please help me.

    ReplyDelete
    Replies
    1. I am getting this error in Step 3:

      Challenge Not yet complete... here's what's wrong:
      The getBoats() method isn't working properly. Define getBoats() In the BoatSearchResults Apex controller to accept an optional boatTypeId and return all boats if no boatTypeId is passed, and a filtered list of boats if a boatTypeId is passed.

      Delete
  2. I am getting this error in Step 3:

    Challenge Not yet complete... here's what's wrong:
    The getBoats() method isn't working properly. Define getBoats() In the BoatSearchResults Apex controller to accept an optional boatTypeId and return all boats if no boatTypeId is passed, and a filtered list of boats if a boatTypeId is passed.

    ReplyDelete
  3. Hi Naren,

    I am also getting same issue. Did you sort out the problem, please let me know

    Krishna.

    ReplyDelete
  4. In Step 4 i am getting error
    This page has an error. You might just need to refresh it.
    Action failed: c:BoatSearchForm$controller$onFormSubmit [Cannot read property 'setParams' of null]
    Failing descriptor: {c:BoatSearchForm$controller$onFormSubmit}

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. In Step 10 :

    I am facing following error please help me out for that.

    Challenge Not yet complete... here's what's wrong:
    We couldn't find a design resource in the Map bundle.

    ReplyDelete
    Replies
    1. I have the same issue, did you solve it yet?

      Delete
    2. Is this issue resolved ?
      Please reply the solution for step 10 , i am getting the same error.

      Delete
    3. cLick on design in MAP lightning component and create design module





      and add Map component in app

      Delete
    4. Hi Amar,

      Thanks for your reply , i follow the same step , but i am getting the different error.


      Challenge Not yet complete... here's what's wrong:
      The BoatTile component doesn't register the event of type PlotMapMarker with name PlotMapMarker.

      Please let me know answer of the above question.

      Delete
  7. Hello

    I'm getting the fallowing error in Step 7.

    Challenge Not yet complete... here's what's wrong:
    The BoatReviewAdded event isn't configured correctly. There is something missing or improperly configured in the BoatReviewAdded.evt file.

    So, I created an event like below





    still getting please help me.

    ReplyDelete
    Replies
    1. Please post a solution for this error ASAP. I tried all ways that I know but no use.
      Please help

      Delete
    2. Change Event Type for boatReviewAdded from "APPLICATION" to "COMPONENT"

      Delete
  8. In Step3, I am getting below error:
    Challenge Not yet complete... here's what's wrong:
    The getBoats() method isn't working properly. Define getBoats() In the BoatSearchResults Apex controller to accept an optional boatTypeId and return all boats if no boatTypeId is passed, and a filtered list of boats if a boatTypeId is passed.

    Please someone tell what I should do.

    Thanks - Naresh

    ReplyDelete
  9. Static resource in fivestar has be below as it is zip file

    <ltng:require scripts="{!$Resource.fivestar + '/rating.js'}"

    ReplyDelete
  10. where is BSRcmp?

    ReplyDelete
  11. Lightning Component Framework Specialist - Step 4-Super badge

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

    from where "BSRcmp" is coming: i am confused

    ReplyDelete
  12. Failed to save BoatDetails.cmp: A aura:handler that specifies an event="" attribute must handle an application event. Either change the aura:event to have type="APPLICATION" or alternately change the aura:handler to specify a name="" attribute.: Source


    I am getting this error how to resolve it help me frnds.

    ReplyDelete
  13. I´m having this problem in Challeng n°5
    We couldn't find the appropriate CSS for the BoatTile component. Be sure to include it in its own file, rather than inline.

    ReplyDelete
    Replies
    1. Use This--
      .THIS.selected {
      border: 3px solid rgb(0, 112, 210);
      }
      it should resolve your problem

      Delete
    2. Thank you for this tip!!

      Delete
  14. I am getting below error in step 8 :

    The BoatReviews Apex controller's getAll() function doesn't return the right list of the fields from the BoatReview custom object.

    ReplyDelete
  15. It is fine, nonetheless evaluate the information and facts around this correct. Mercruiser Alpha One outdrive replacement

    ReplyDelete
  16. Hi Guys, I am facing the below issue please help me

    hallenge Not yet complete... here's what's wrong:
    Map.design doesn't enable a business user to set the width and height of the map.

    ReplyDelete
    Replies
    1. Add the below lines to the Map.svg file


      Delete
    2. Hi which below line.Can u plz tell me.Even I am facing the same issue.

      Delete
  17. Step 10: add below code in boattile.cmp:

    ReplyDelete
    Replies
    1. aura:registerEvent type="c:PlotMapMarker" name="PlotMapMarker"/

      Delete
  18. Guys anyone got this weird error(mentioned below) for Step 10? I have put the component on the page using app builder and I can see the component on the page and yet for some reason it keeps showing me this error. I tried everything since 2 days but nothing is helping. Anyone familiar with this?

    Challenge Not yet complete... here's what's wrong:
    The Map component isn't available in Lightning App Builder.

    ReplyDelete
  19. Hi Manju is it solved ? I already spent 1 day and still not working .

    ReplyDelete
  20. in challenge 6 facing error reply soon
    Challenge Not yet complete... here's what's wrong:
    The BoatDetails component's tabs shouldn't display if the component’s boat attribute is undefined. Either use the empty function or check for undefined.

    ReplyDelete
  21. Hi Team,
    I got struck in 10 th module.Getting below error.

    Map.design doesn't enable a business user to set the width and height of the map.


    Can anyone help me.

    Thankyou.

    ReplyDelete
  22. Getting below error when checking for Challenge 10

    MapController.js must have an event handler named onPlotMapMarker that uses the latitude and longitude that were passed through the event to update the boat’s location. Make sure the component was created according to the requirements, including the zoom level, map markers, titles, and labels.

    Any help is appreciated and Thankyou

    ReplyDelete
  23. Challenge Not yet complete... here's what's wrong:
    The getBoats() method isn't working properly. Define getBoats() In the BoatSearchResults Apex controller to accept an optional boatTypeId and return all boats if no boatTypeId is passed, and a filtered list of boats if a boatTypeId is passed.

    ReplyDelete