<aura:attribute name="recordId" type="String" />
This behavior is different than you might expect for an interface in a programming language. This difference is because force:hasRecordId is a marker interface. A marker interface is a signal to the component’s container to add the interface’s behavior to the component.
The recordId attribute is set only when you place or invoke the component in a context of a record. For example, when you place the component on a record page, or invoke it as an action from a record page or object home. In all other cases, such as when you create this component programmatically inside another component, recordId isn’t set, and your component shouldn’t depend on it.
This extended example shows a component designed to be invoked as a custom action
from the detail page of an account record. After creating the component, you need to
create the custom action on the account object, and then add the action to an
account page layout. When opened using an action, the component appears in an action
panel that looks like this:
The component definition begins by implementing both the force:lightningQuickActionWithoutHeader and the force:hasRecordId interfaces. The first makes it available for use as an action and prevents the standard controls from displaying. The second adds the interface’s automatic record ID attribute and value assignment behavior, when the component is invoked in a record context.
<aura:component controller="QuickContactController" implements="force:lightningQuickActionWithoutHeader,force:hasRecordId"> <aura:attribute name="account" type="Account" /> <aura:attribute name="newContact" type="Contact" default="{ 'sobjectType': 'Contact' }" /> <!-- default to empty record --> <aura:handler name="init" value="{!this}" action="{!c.doInit}" /> <!-- Display a header with details about the account --> <div class="slds-page-header" role="banner"> <p class="slds-text-heading--label">{!v.account.Name}</p> <h1 class="slds-page-header__title slds-m-right--small slds-truncate slds-align-left">Create New Contact</h1> </div> <!-- Display the new contact form --> <lightning:input aura:id="contactField" name="firstName" label="First Name" value="{!v.newContact.FirstName}" required="true"/> <lightning:input aura:id="contactField" name="lastname" label="Last Name" value="{!v.newContact.LastName}" required="true"/> <lightning:input aura:id="contactField" name="title" label="Title" value="{!v.newContact.Title}" /> <lightning:input aura:id="contactField" type="phone" name="phone" label="Phone Number" pattern="^(1?(-?\d{3})-?)?(\d{3})(-?\d{4})$" messageWhenPatternMismatch="The phone number must contain 7, 10, or 11 digits. Hyphens are optional." value="{!v.newContact.Phone}" required="true"/> <lightning:input aura:id="contactField" type="email" name="email" label="Email" value="{!v.newContact.Email}" /> <lightning:button label="Cancel" onclick="{!c.handleCancel}" class="slds-m-top--medium" /> <lightning:button label="Save Contact" onclick="{!c.handleSaveContact}" variant="brand" class="slds-m-top--medium"/> </aura:component>
The component’s controller has all of the interesting code, in three action handlers.
({ doInit : function(component, event, helper) { // Prepare the action to load account record var action = component.get("c.getAccount"); action.setParams({"accountId": component.get("v.recordId")}); // Configure response handler action.setCallback(this, function(response) { var state = response.getState(); if(state === "SUCCESS") { component.set("v.account", response.getReturnValue()); } else { console.log('Problem getting account, response state: ' + state); } }); $A.enqueueAction(action); }, handleSaveContact: function(component, event, helper) { if(helper.validateContactForm(component)) { // Prepare the action to create the new contact var saveContactAction = component.get("c.saveContactWithAccount"); saveContactAction.setParams({ "contact": component.get("v.newContact"), "accountId": component.get("v.recordId") }); // Configure the response handler for the action saveContactAction.setCallback(this, function(response) { var state = response.getState(); if(state === "SUCCESS") { // Prepare a toast UI message var resultsToast = $A.get("e.force:showToast"); resultsToast.setParams({ "title": "Contact Saved", "message": "The new contact was created." }); // Update the UI: close panel, show toast, refresh account page $A.get("e.force:closeQuickAction").fire(); resultsToast.fire(); $A.get("e.force:refreshView").fire(); } else if (state === "ERROR") { console.log('Problem saving contact, response state: ' + state); } else { console.log('Unknown problem, response state: ' + state); } }); // Send the request to create the new contact $A.enqueueAction(saveContactAction); } }, handleCancel: function(component, event, helper) { $A.get("e.force:closeQuickAction").fire(); } })
The first action handler, doInit, is an init handler. Its job is to use the record ID that’s provided via the force:hasRecordId interface and load the full account record. Note that there’s nothing to stop this component from being used in an action on another object, like a lead, opportunity, or custom object. In that case, doInit will fail to load a record, but the form will still display.
The handleCancel action handler closes the action panel by firing the force:closeQuickAction event.
The component helper provided here is minimal, sufficient to illustrate its use. You’ll likely have more work to do in any production quality form validation code.
({ validateContactForm: function(component) { var validContact = true; // Show error messages if required fields are blank var allValid = component.find('contactField').reduce(function (validFields, inputCmp) { inputCmp.showHelpMessageIfInvalid(); return validFields && inputCmp.get('v.validity').valid; }, true); if (allValid) { // Verify we have an account to attach it to var account = component.get("v.account"); if($A.util.isEmpty(account)) { validContact = false; console.log("Quick action context doesn't have a valid account."); } return(validContact); } } })
Finally, the Apex class used as the server-side controller for this component is deliberately simple to the point of being obvious.
public with sharing class QuickContactController { @AuraEnabled public static Account getAccount(Id accountId) { // Perform isAccessible() checks here return [SELECT Name, BillingCity, BillingState FROM Account WHERE Id = :accountId]; } @AuraEnabled public static Contact saveContactWithAccount(Contact contact, Id accountId) { // Perform isAccessible() and isUpdateable() checks here contact.AccountId = accountId; upsert contact; return contact; } }