+ Start a Discussion
Barry Bakunowicz - DevBarry Bakunowicz - Dev 

Lightning Component Framework Specialist - Step 7 Event Error

Hello all,

I'm currently working through Step 7 of the new "Lightning Component Framework Specialist" superbadge, and I'm running into an error while checking the challenge:

"The AddBoatReview controller's onSave() function doesn't fire the BoatReviewAdded event."

The problem is that I'm testing the component right now, and my event is being received by the parent just fine, and my tabset switches to the "Reviews" section like it should, so know it's being handled correctly. I've tried using both a Component event and an Application event just to debug a bit (even though Component is the best option for this scenario), and that doesn't change anything. Any idea what could be triggering this error message?

Relevant code:

AddBoatReviewController.js

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 cmpEvent = component.getEvent("reviewAdded");
		       cmpEvent.fire();

               //Attempt at trying an Application event instead
		       //var cmpEvent = $A.get("e.c:BoatReviewAdded");
		       //cmpEvent.fire();


                var resultsToast = $A.get("e.force:showToast");
                //Toast code and other saveResults states below...
AddBoatReview.cmp
<aura:registerEvent name="reviewAdded" type="c:BoatReviewAdded"/>
     
    <!-- Display form -->
    <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" class="slds-align--absolute-center">
                <lightning:button iconName="utility:save" label="Submit" onclick="{!c.onSave}"/>
            </lightning:layoutItem>
    </lightning:layout>
BoatDetails.cmp
<aura:handler name="reviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/>

BoatDetailsController.js

onBoatReviewAdded : function(component, event, helper) {
	console.log("Event received");
	component.set("v.selTabId", "boatreviewtab");
}
Best Answer chosen by Barry Bakunowicz - Dev
Barry Bakunowicz - DevBarry Bakunowicz - Dev
Ah, *finally* got it working. Apparently, naming my component event "BoatReviewAdded" made that test pass:
<aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
So in addition to making it of type "c:BoatReviewAdded", it also has to be named "BoatReviewAdded" as well.

All Answers

Barry Bakunowicz - DevBarry Bakunowicz - Dev
Ah, *finally* got it working. Apparently, naming my component event "BoatReviewAdded" made that test pass:
<aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
So in addition to making it of type "c:BoatReviewAdded", it also has to be named "BoatReviewAdded" as well.
This was selected as the best answer
Abhishek-tandonAbhishek-tandon
Hi I am also geeting another error on this challenge, not sure what is wrong here i am getting an error message , however practically its working.

Challenge Not yet complete... here's what's wrong: 
The BoatDetails controller's onBoatReviewAdded function doesn't set the currently selected tab to "Reviews"
.

My BoatDetailsController.js is
onBoatReviewAdded : function(component, event, helper) {
		component.set("v.selTabId", "boatreviewtab");

    }
BoatDetails.cmp
<lightning:tabset variant="scoped" selectedTabId="{!v.selTabId}">
            <lightning:tab label="Details" id="details" >
                <c:BoatDetail boat="{!v.boat}"/>
            </lightning:tab>
            <lightning:tab label="Reviews" id="boatreviewtab" >
               
            </lightning:tab>
            <lightning:tab label="Add Review" id="addReview" >
                 <c:AddBoatReview boat="{!v.boat}"/>
            </lightning:tab>
        </lightning:tabset>


 
Abhishek-tandonAbhishek-tandon
Hi @Barry, any help here will be highly appreciated.
Barry Bakunowicz - DevBarry Bakunowicz - Dev

Try setting the selected tab directly from the "onBoatReviewAdded" function in your controller, similar to this:

 

BoatDetailsController.js

onBoatReviewAdded : function(component, event, helper) {
	console.log('Event received');
	component.find("details").set("v.selectedTabId", "boatreviewtab");
}

"details" is the aura:id I gave my TabSet component. You can probably use whatever name you'd like instead, since you already have that name for one of your tabs. Whatever's easiest!


I was getting the same error as you also while working on this step, and setting it this way fixed it. Both methods work in practice, but I think the Superbadge is specifically looking for the above. If you still get an error, try adding an aura:id of "boatreviewtab" to that Reviews tab as well, and see if that helps at all (it might be useless, but I know there have been some weird naming issues throughout this badge). Hopefully this works for you!

Abhishek-tandonAbhishek-tandon
That Worked @Barry, thanks for the direction, it seems weird that how Trailhead wants thinngs to be done in hard & fast way that they like.

Also could you look into mt this request of step 9, again seems some markup issue

https://developer.salesforce.com/forums/ForumsMain#!/feedtype=SINGLE_QUESTION_DETAIL&dc=Developer_Forums&criteria=OPENQUESTIONS&id=9060G000000MUtyQAG
Barry Bakunowicz - DevBarry Bakunowicz - Dev
@Jeremiah What does your BoatSelect.evt look like? Make sure it's passing a String named "boatId," and that you select the best option for this step when choosing the type of event (COMPONENT or APPLICATION).
Abhishek-tandonAbhishek-tandon
Hi @Jeremiah, you need to define a attribute in your BoatSearchResults component like
<aura:attribute name="selectedBoatId" type="String" />

and pass it while iterating on list of boat and inserting tile for every boat
<aura:iteration items="{!v.boats}" var="boat">
                <lightning:layoutItem flexibility="grow"  class="slds-m-right_small" >   
                	<c:BoatTile boat="{!boat}" selected="{!boat.Id == v.selectedBoatId ? true:false}"/>
                </lightning:layoutItem>
            </aura:iteration

 
Medhanie Habte 37Medhanie Habte 37
Hi guys, I seem to be stuck with configuring a Toast message in my AddBoatReviewController.js file as evidenced by my code.
 
({
    onSave : function(component, event, helper){
        component.set("v.boatReview.Boat__c",component.get("v.boat.Id"));
       	 cmpEvent.fire();
		
        if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {

        var cmpEvent = component.getEvent("reviewAdded");

            var resultsToast = $A.get("e.force:showToast");}
	
    },
    doInit: function(cmp) {
        // Set the attribute value. 
        // You could also fire an event here instead.
        cmp.set("v.setMeOnInit", "controller init magic!");
    },
    
    
})

 
Abhishek-tandonAbhishek-tandon

var resultsToast = $A.get("e.force:showToast");
                  if ($A.util.isUndefined(resultsToast)){
                  	alert('Review Saved successfully.');
                  }else{
                      resultsToast.setParams({
                        "title": "Saved",
                        "message": "Review Saved successfully."
                    });
				resultsToast.fire();
                  }

Try This.
Vacheslav AronovVacheslav Aronov
Hi, I'm getting an error in step 5: Challenge Not yet complete... here's what's wrong: 
BoatSearchResults.cmp must handle the BoatSelect event by calling a controller function named onBoatSelect.

I defined <aura:handler name="boatSelect" event="c:BoatSelect" action="{!onBoatSelect}"/> in BoatSearchResult.cmp
What am I doing wrong?
PatMcClellan__cPatMcClellan__c
Vacheslav, please post your Step 5 issue on the Step 5 thread: https://developer.salesforce.com/forums/ForumsMain?id=9060G000000MUPF#!/feedtype=SINGLE_QUESTION_SEARCH_RESULT&id=9060G000000MUxMQAW

FYI, I think your issue is case sensitivity. Your aura:handler name should be "BoatSelect", not "boatSelect".
Vacheslav AronovVacheslav Aronov
No, it looks like not because of name. I'll post my question in another thread. Thanks.
Prasanth SalamkayalaPrasanth Salamkayala
Im facing the below error "Challenge Not yet complete... here's what's wrong: 
The BoatDetails controller's onBoatReviewAdded function doesn't set the currently selected tab to "Reviews".
I was able to navigate to Reviews tab on submission of add review.The only issue im having is the review record is not getting saved.i can see the resultstate to error and error string to "[{"fieldErrors":{},"pageErrors":[]}]" . can some one help me with the issue? i appreciate if someone can mail the Addboatreview component,controller,helper and boat details if they hold patience to connectprasanth@gmail.com. if someone is ready to help here i can copy paste my code as well here
PatMcClellan__cPatMcClellan__c
Prasanth, it's better to post your code here for people to comment/give you tips on. That way, others may benefit from your learning process.
Barry Bakunowicz - DevBarry Bakunowicz - Dev

@Prasanth - I did get that error during my work on this step as well. Here's what my Lightning Data Service definition is when it passed:

<aura:attribute access="private" name="boatReview" type="BoatReview__c"/>
<force:recordData aura:id="service"
	    fields="Id, Name, Comment__c, Boat__c, Rating__c"
	    targetFields ="{!v.boatReview}"
	    targetError="{!v.recordError}"
	    recordUpdated="{!c.onRecordUpdated}"
	    />

This form looks a bit different than what the documentation for LDS has, but I had to play around with it a bit before the Superbadge liked what I had.
 

And my controller:
 

//Not sure if I needed this next line, but I was having trouble getting the Badge to recognize the boat //id.
component.set("v.boatReview.Boat__c",component.get("v.boat.Id"));
console.log("Boat Id: "+component.get("v.boat.Id"));

component.find("service").saveRecord(function(saveResult) {

//Callback code here....

I got the record successfully created with the above. If you're still having issues; make sure the "doInit" function in your helper is working properly.
 
Prasanth SalamkayalaPrasanth Salamkayala
I'm not able to get through this challenge.here is my code. 

Add Boat Review Component Code
<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" 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>

Add Boat Review Controller Code
({
    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');
                }
        }
    }
})


Add Boat Review Helper

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

Boat Details Controller
<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: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" >
               
            </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

({
    onBoatSelected : function(component, event, helper) {
        var boatSelected=event.getParam("boat");
        component.set("v.id",boatSelected.Id);
        component.find("service").reloadRecord() ;
    },
    onRecordUpdated : function(component, event, helper) {
    },
    onBoatReviewAdded : function(component, event, helper) {
    component.set("v.selTabId", "boatreviewtab");
}
})
 
David Cohen 15David Cohen 15
Hiya folks, hoping someone could gimme a hand here. I passed the challenge, but annoyingly, my code still has a bug I'm trying to resolve.

It happens when the onSave() method in the controller calls the helper's onInit() method at the end. As per the instructions: "​Lastly, it invokes helper.onInit() to reset the component to enable the user to add another review."

When the code returns to the onInit() for that second time, var rec = component.get("v.boatReview"); returns undefined, so naturally, it crashes when I try to assign a value to rec.Boat__c. I get around the error by only assigning if rec != undefined, but I'm sure I'm missing something...

Any thoughts?

Thanks very much!

Here's the controller's onSave():
  onSave: function(component, event, helper) {
    component.find("service").saveRecord(function(saveResult) {
      if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
        // record is saved successfully
        var resultsToast = $A.get("e.force:showToast");
        if (resultsToast) {
          resultsToast.setParams({
            "title": "Submitted",
            "message": "The review was saved."
          });
          resultsToast.fire();
        } else {
          alert('The review was saved.');
        }
        helper.onInit(component, event, helper);

        var boatReviewAddedEvent = component.getEvent("boatReviewAdded");
        boatReviewAddedEvent.fire();
      } else if (saveResult.state === "INCOMPLETE") {
        // handle the incomplete state
        console.log("User is offline, device doesn't support drafts.");
      } else if (saveResult.state === "ERROR") {
        // handle the error state
        console.log('Problem saving contact, error: ' +
          JSON.stringify(saveResult.error));
      } else {
        console.log('Unknown problem, state: ' + saveResult.state +
          ', error: ' + JSON.stringify(saveResult.error));
      }
    });
  },
And here's the helper's onInit():
  onInit : function(component, event, helper) {
    // Prepare a new record from template
    component.find("service").getNewRecord(
      "BoatReview__c",
      null,
      false,
      $A.getCallback(function() {
        var rec = component.get("v.boatReview");
        var error = component.get("v.recordError");
        console.log('hey', JSON.stringify(rec));

        if (error || (rec === null)) {
          console.log("Error initializing record template: " + error);
        } else {
          rec.Boat__c = component.get("v.boat").Id;
          component.set("v.boatReview", rec);
          console.log("Record template initialized: " + rec.sobjectType);
        }
      })
    );
  }

 
Prasanth SalamkayalaPrasanth Salamkayala
Hey david!! Thanks for posting your issue.I was able to resolve mine. Coming to your issue please replace the line 16 helper class of code component.set("v.boatReview", rec);​ with "component.set("v.boatReview.Boat__c",component.get("v.boat").Id);" and it will resolve your issue.Please acknowlege if your able to resolve your issue.I can help you on this
Prasanth SalamkayalaPrasanth Salamkayala
im still facing the issue "Challenge Not yet complete... here's what's wrong: 
The BoatDetails controller's onBoatReviewAdded function doesn't set the currently selected tab to "Reviews".I still dont understand whats the problem with my code.I have posted my code above.can anyone help me on this??
Prasanth SalamkayalaPrasanth Salamkayala
Add boat review component code
<aura:registerEvent name="boatReviewAdded" type="c:BoatReviewAdded"/>

Add boat review Controller On save method

onSave: function(component, event, helper) {
  component.find("service").saveRecord(function(saveResult) {
    if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
      // record is saved successfully
      var resultsToast = $A.get("e.force:showToast");
      if (resultsToast) {
        resultsToast.setParams({
          "title": "Submitted",
          "message": "The review was saved."
        });
        resultsToast.fire();
      } else {
        alert('The review was saved.');
      }
      helper.onInit(component, event, helper);

      var boatReviewAddedEvent = component.getEvent("boatReviewAdded");
      boatReviewAddedEvent.fire();
    } else if (saveResult.state === "INCOMPLETE") {
      // handle the incomplete state
      console.log("User is offline, device doesn't support drafts.");
    } else if (saveResult.state === "ERROR") {
      // handle the error state
      console.log('Problem saving contact, error: ' +
        JSON.stringify(saveResult.error));
    } else {
      console.log('Unknown problem, state: ' + saveResult.state +
        ', error: ' + JSON.stringify(saveResult.error));
    }
  });
},

Boat details component code
<aura:handler name="boatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/>
<lightning:tab label="Reviews" id="boatreviewtab" >

Boat Details Controller code
onBoatReviewAdded : function(component, event, helper) {
	component.set("v.selTabId", "boatreviewtab");

Can anyone here look into my issue  "Challenge Not yet complete... here's what's wrong: 
The BoatDetails controller's onBoatReviewAdded function doesn't set the currently selected tab to "Reviews".
David Cohen 15David Cohen 15
Prasanth,

Replace your line 44 with: component.find("tabs").set("v.selectedTabId", 'boatreviewtab');
where tabs is the aura:id of the tabset and boatreviewtab is the id of the tab.

That said, thank you for resolving my problem. But I don't understand why that works!

WHY is component.get("v.boatReview") undefined the second time onInit is called?
And if it returns undefined, why am I doing this???: component.set("v.boatReview", rec);
And if's undefined, how am I able to set the Boat__c field on it with this???: component.set("v.boatReview.Boat__c", component.get("v.boat").Id);

Doesn't make much sense to me.
Prasanth SalamkayalaPrasanth Salamkayala
Thanks @David Cohen 15 for resolving my problem . Im able to pass the challenge. Im also confused about your issue and i felt setting javascript object to attribute and retrieving the attribute makes it undefined.Hopefully Javascript folks might understand this issue
David Cohen 15David Cohen 15
Prasanth,

I figured it out. In the controller's onSave(), the component (and event and helper) is not in scope inside the saveRecord callback function. Debugging the component inside the callback returns undefined. Which is why it is undefined in the onInit().

I moved the helper.onInit(component, event, helper); line to the very last line of the onSave(), outside the callback. Then everything works as desired and I returned my onInit code to the original, as I pasted earlier.. and that code makes much more sense to me.
Eric SchragerEric Schrager
I'm getting a different error:

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.

What am I missing?

User-added image
Drew Kennedy 7Drew Kennedy 7
So for me I had to name the attribute "selectedTabId" for this to actually work instead of "selTabId" like what's being used by others above, and I have no idea why this would even matter.

Code:

AddBoatReview
 
<aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded" />

AddBoatReviewController.js
 
onSave : function(component, event, helper) {
        var toast = $A.get("e.force:showToast");
        component.find("service").saveRecord(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                if (!$A.util.isUndefinedOrNull(toast)) {
                    toast
                    .setParams({"title":"Saved",
                                "message":"The record was saved."})
                    .fire();
                } else {
                    alert("Successfully saved, question mark?");
                }
            } else {
                console.log('Error: ' + JSON.stringify(saveResult.error));
            }
        });
        component.getEvent("BoatReviewAdded").fire();
        helper.onInit(component, event, helper);
}

BoatDetails
<!-- here it needed to be named "BoatReviewAdded" -->
<aura:handler name="BoatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}" />
    
    <aura:if isTrue="{!not(empty(v.boat))}">
        <lightning:tabset aura:id="details" variant="scoped" selectedTabId="{!v.selectedTabId}">
            <lightning:tab label="DETAILS">
                <c:BoatDetail boat="{!v.boat}" />
            </lightning:tab>
            <lightning:tab label="REVIEWS" id="boatreviewtab">
            </lightning:tab>
            <lightning:tab label="ADD REVIEW">
                <c:AddBoatReview boat="{!v.boat}" />
            </lightning:tab>
        </lightning:tabset>
    </aura:if>

BoatDetailsController.js
onBoatReviewAdded : function(component) {
    component.find("details").set("v.selectedTabId", "boatreviewtab");
}

Hopefully this saves someone the same pain of wasting two hours of your life on such a nothing issue.

Good luck!
venkatesh Raaye 12venkatesh Raaye 12
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.

BoatDetails
<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" >
               
            </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
({
    onBoatSelected : function(component, event, helper) {
        var boatSelected=event.getParam("boat");
        component.set("v.id",boatSelected.Id);
        component.find("service").reloadRecord() ;
    },
    onRecordUpdated : function(component, event, helper) {
    },
    onBoatReviewAdded : function(component, event, helper) {
        console.log("Event received");
        component.set("v.selTabId", "boatreviewtab");
    }
})
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" 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) {
        var toast = $A.get("e.force:showToast");
        component.find("service").saveRecord(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                if (!$A.util.isUndefinedOrNull(toast)) {
                    toast
                    .setParams({"title":"Saved",
                                "message":"The record was saved."})
                    .fire();
                } else {
                    alert("Successfully saved, question mark?");
                }
            } else {
                console.log('Error: ' + JSON.stringify(saveResult.error));
            }
        });
        component.getEvent("BoatReviewAdded").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');
            }
        }
    }
})
AddBoatRiewHelper.js
({
    onInit : function(component, event, helper) {
        // Prepare a new record from template
        component.find("service").getNewRecord(
            "BoatReview__c",
            null,
            false,
            $A.getCallback(function() {
                var rec = component.get("v.boatReview");
                var error = component.get("v.recordError");
                console.log('hey', JSON.stringify(rec));
                
                if (error || (rec === null)) {
                    console.log("Error initializing record template: " + error);
                } else {
                    rec.Boat__c = component.get("v.boat").Id;
                    component.set("v.boatReview.Boat__c",component.get("v.boat").Id);
                    console.log("Record template initialized: " + rec.sobjectType);
                }
            })
        );
    }
})
BoatReivewAdded.evt
 
<aura:event type="APPLICATION" description="Event template">
    <aura:attribute name="BoatReviewAdded" type="Boat__c"/>
</aura:event>






 
VishnuYVishnuY
Did you figure out the error venkatesh Raaye 12?
venkatesh Raaye 12venkatesh Raaye 12
Yes @VishnuY
Emilien Guichard 40Emilien Guichard 40
@Raaye

I am facing the same error, what do you do to fix that ?

 
Amitkumar KatreAmitkumar Katre
Hello Emilien,

I think you should change event type to component insted of Application.

<aura:event type="Component" description="Event template">
    <aura:attribute name="BoatReviewAdded" type="Boat__c"/>
</aura:event>

Hope this helps.

Thanks,
Amit
Emilien Guichard 40Emilien Guichard 40
Hi Amitkumar,

Thanks a lot, I managed to pass the challenge !
shashi kumar 58shashi kumar 58
Hi @venkatesh Raaye 12,

Please follow the below code to clear this challenge:-

1. BoatDetails
<aura:component description="BoatDetails"
                implements="flexipage:availableForAllPageTypes">

<aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
  <aura:handler name="reviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/>
    <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}" />

    <lightning:tabset variant="scoped" selectedTabId="{!v.selTabId}">
            <lightning:tab label="Details" id="details" >
                <c:BoatDetail boat="{!v.boat}"/>
            </lightning:tab>
            <lightning:tab label="Reviews" id="boatreviewtab" >
               
            </lightning:tab>
            <lightning:tab label="Add Review" id="addReview" >
                 <c:AddBoatReview boat="{!v.boat}"/>
            </lightning:tab>
        </lightning:tabset>
       
    <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>

2. 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.find("details").set("v.selectedTabId", "boatreviewtab");
}
    
})

3. 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" 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>

4.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');
                }
        }
    }
})

5. AddBoatRiewHelper.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);
                }
            })
        );
    }
})

6. BoatReivewAdded.evt
<aura:event type="Component" description="Event template">
    <aura:attribute name="BoatReviewAdded" type="Boat__c"/>
</aura:event>
If you have any doubt or any question let me know.

Regards,
Shashi Kumar
​​​​​
shashi kumar 58shashi kumar 58
@If any assist or help please ping me for Challenge  no#8
Lightning Component Framework Specialist
Sudipta Ghosh 9Sudipta Ghosh 9
Hi Shashi,
Need help with step 8.
I need the code for BoatReviews.cmp and controller js. I am geeting the below error. Actually would be great if you please share the all components code

Challenge Not yet complete... here's what's wrong: 
The BoatDetails component doesn't instantiate the BoatReviews component, passing in the selected boat information.

 
sai sekhar 1sai sekhar 1
@shashi kumar 58 

I am getting the following error.
User-added image
shashi kumar 58shashi kumar 58
Hi @Sudipta Ghosh 9,sai sekhar 1​
Please markit as best ans if you clear your challenge.

or if you need any help let me know.
BoatSelect.evt
<aura:event type="COMPONENT" description="Boat Event">
    <aura:attribute name="boatId" type="Id"/>
</aura:event>
 
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>
 
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){
        var boat = component.get("v.boat");
        console.log("onRecordUpdated called | boat: " + boat.Id);

        //invoke a refresh on the reviews tab, calling public method refresh
        var BoatReviews = component.find("BoatReviews");
        console.log("BoatReviews: " + BoatReviews);
        if(typeof BoatReviews != 'undefined'){
            BoatReviews.refresh();
        }
    },

   /* Earlier dated on 8 Jan2018
    *  onBoatReviewAdded : function(component, event, helper) {
	console.log('Event received');
	component.find("details").set("v.selectedTabId", "boatreviewtab");
}*/
    
    
    onBoatReviewAdded : function(component, event, helper){
        console.log("BDCjs: onBoatReviewAdded")
        component.find("tabs").set("v.selectedTabId", "boatreviewtab");

        //invoke a refresh on the reviews tab, calling public method refresh
        var BoatReviews = component.find("BoatReviews");
        //BRcmp is the aura:id for the component when invoked in BoatDetails.cmp
        BoatReviews.refresh();

    },
    
    
    
    
    
    
})
 
BoatDetails.cmp
 
 <aura:component description="BoatDetails"
                implements="flexipage:availableForAllPageTypes">

<aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
  <aura:handler name="reviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/>
    <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}"/>
    <lightning:tab label="Reviews" id="boatreviewtab">
          <c:BoatReviews boat="{!v.boat}" aura:id="BRcmp"/>
 </lightning:tab>
   

    <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}" />

    <lightning:tabset variant="scoped" selectedTabId="{!v.selTabId}">
            <lightning:tab label="Details" id="details" >
                <c:BoatDetail boat="{!v.boat}"/>
            </lightning:tab>
            <lightning:tab label="Reviews" id="boatreviewtab" >
               
            </lightning:tab>
            <lightning:tab label="Add Review" id="addReview" >
                 <c:AddBoatReview boat="{!v.boat}"/>
            </lightning:tab>
        </lightning:tabset>
       
    <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>
 
oatTileController.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();
	}
})
 
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>

2. 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');
                }
        }
    }
})

3.AddBoatReviewHelper.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);
                }
            })
        );
    }
})



Here i attached the BoatReviews.cmp and controller js

if you need any help let me know.

BoatReviews.cmp
BoatReviews.cmp

<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" 
                controller="BoatReviews">
    
     <aura:attribute name="boat" type="Boat__c" access="public"/>
    <aura:handler name="change" value="{!v.boat}" action="{!c.refresh}"/>
    <aura:attribute name="boatReviews" type="BoatReview__c[]" access="private"/>
    <!-- set up the aura:method for refresh -->
    <aura:method name="refresh" action="{!c.doInit}" access="public"
                 description="BoatDetailsController.js invokes refresh whenever boat is updated">
    </aura:method>
     
    <ui:scrollerWrapper class="scrollerSize">
        <!--Scrollable content here -->
        <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">
                <aura:iteration items="{!v.boatReviews}" var="boatReview">
                    <li class="slds-feed__item">
                        <div class="slds-media__body">
                       <div class="slds-grid slds-has-flexi-truncate">
                            <a href="javascript:void(0)" onclick="{!c.onUserInfoClick}"
          data-userid="{!boatReview.CreatedBy.Id}">
          {!boatReview.CreatedBy.Name}
      </a>
                        &nbsp; &mdash; &nbsp; {!boatReview.CreatedBy.CompanyName}
   </div>
                         <p><lightning:formattedDateTime value="{!boatReview.CreatedDate}" 
                                   year="numeric" month="short" day="numeric"  
                                   hour="2-digit" minute="2-digit" hour12="true"/></p>
                        </div>
                    </li>
                </aura:iteration>
            </ul>
        </div>
    </ui:scrollerWrapper>
    
    
</aura:component>
 
BoatReviewsController.js
({
	refresh : function(component,event,helper){
        console.log("refresh called")
        this.doInit;
    },
    doInit : function(component,event,helper){
        console.log("BRCjs: doInit");
        helper.onInit(component, event);
    },
    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()

    },
})
 
BoatReviewsHelper.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);
    },
})
 
BoatSelected.evt
<aura:event type="APPLICATION" description="BoatSelected fired from BoatTileController's onBoatClick handler">
    <aura:attribute name="boat" type="Boat__c"/>
</aura:event>

 
sudheerasudheera
User-added image
sudheerasudheera
Hi  @Sudipta Ghosh 9,sai sekhar 1​ ,Barry Bakunowicz - Dev

 I am stucked at step 7 . Please help on this.
shashi kumar 58shashi kumar 58
Hi sudheera katta
you need to add the tab for the above error let me know if you need more help.


Regards,
Shashi Kumar
sudheerasudheera
@shashi kumar 58
I mean , is it in code 
shashi kumar 58shashi kumar 58
No it should be in Front end i thought you remember where you create one app name as Friends and Boat there only you need to create
Faiza Naz 10Faiza Naz 10
Hi,
I am facing below error in step 7.
Challenge Not yet complete... here's what's wrong: 
The BoatDetails controller's onBoatReviewAdded function doesn't set the currently selected tab to "Reviews".

My code:
BoatDetails:
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    
    <aura:attribute name="id" type="Id" access="public"/>
    <aura:attribute name="boat" type="Boat__c" access="public"/>
    <aura:attribute name="selTabId" type="String" />
    <aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}"/>
    <aura:handler name="BoatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}" />
    
    <force:recordData aura:id="service"
                      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"
                      targetFields="{!v.boat}"
                      recordUpdated="{!c.onRecordUpdated}"/>
                      
    
    <lightning:tabset variant="scoped" selectedTabId="{!v.selTabId}" aura:id="tabsDetail">
        <lightning:tab label="Details">
            <aura:if isTrue="{!not(empty(v.boat))}">
                <c:BoatDetail boat="{!v.boat}"/>
            </aura:if>
        </lightning:tab>
        
        <lightning:tab label="Reviews" id="boatReviewTab">
        </lightning:tab>
        
        <lightning:tab label="Add Review" id="addReview">
            <c:AddBoatReview boat="{!v.boat}"/>
        </lightning:tab>
        
    </lightning:tabset>
</aura:component>

Controller function:
onBoatReviewAdded : function(component, event, helper) {
        console.log("Event started");
        //component.set("v.selTabId", "boatReviewTab");
        component.find("tabsDetail").set("v.selTabId", "boatReviewTab");

    },