One of the biggest feature announcement of Spring ’18 is Flow Components. World of Lightning Components have reached lightning flow.We can directly drag the Lightning component written by someone else to our flow screen, and thus it dramatically improves the range of things we could do using flow the screen. We just need to implement the interface in our lightning component to use it into flow screen.
Configure Components for Flow Screens
- Make your custom Lightning components available to flow screens in the Cloud Flow Designer by implementing the lightning:availableForFlowScreens interface.
- Next, add a design resource to your component bundle.
A design resource describes the design-time behavior of a Lightning component—information that visual tools need to allow adding the component to a page or app.
It contains attributes that are available for admins to edit in the Cloud Flow Designer. - Header and Footer is optional. If we want custom header or footer we can uncheck the check box.
lightning:availableForFlowScreens
This interface is supported only in Lightning run time.
This interface is a marker interface. A marker interface signals the component’s container to add the interface’s behavior to the component.
You don’t need to implement methods or attributes in your component. Instead, you just add the interface name to the component’s implements attribute.Few other interface like “flexipage:availableForAllPageTypes, flexipage:availableForRecordHome” won’t work when we use our component as a flow screen.
Let’s dive into a demo and see our components in action
- In this demo we have two components, first to create Contact and second to create Opportunity. Flow component is added to Account record detail page. To use these components in the flow screen first we need to include lightning:availableForFlowScreens interface. In this demo, both the components are in separate screen as shown in the figure below.
- We pass the record Id of Account from the flow into our design attribute of lightning component by using an Input/Output variable named “recordId”. By naming a variable as recordId, it automatically stores the Id of the record in which the flow component is kept.
- Contact form has standard footer to navigate to the Opportunity form while Opportunity form has customized footer.
In this demo, record is created using powerful functionality of Lightning Data Service(LDS) which reduces our task to make any apex call or use standard record create screen of the flow for creating new record. The controller method is called using button on the customized footer of the opportunity flow screen.
The details filled in the form can be retrieved by setting a local Input/Output flow variable to our design attribute in Lightning component. Setting up the design attribute is shown below.
Lightning Component: CreateContact.cmp
[sourcecode language=”html”]
<aura:component implements=”lightning:availableForFlowScreens, force:appHostable, force:hasRecordId, force:lightningQuickAction” access=”global”>
<aura:attribute name=”con” type=”Contact” default=”{‘LastName’:”,’Phone’:”,’Email’:”}” />
<aura:attribute name=”recordError” type=”String” />
<aura:attribute name=”newrecord” type=”Object” />
<aura:attribute name=”targetR” type=”String” />
<aura:handler name=”change” value=”{!v.con}” action=”{!c.handleChange}” />
<aura:attribute name=”recordId” type=”String” />
<aura:handler name=”init” value=”{!this}” action=”{!c.doInit}” />
<force:recordData aura:id=”ContactDetail” layoutType=”FULL” targetRecord=”{!v.newrecord}” targetFields=”{!v.con}” targetError=”{!v.recordError}” />
<div class=”slds-text-align–left”>
<h3 class=”slds-section-title–divider”>Contact Form</h3><br/>
</div>
<form class=”slds-form–stacked”>
<lightning:input aura:id=”Contactform” label=”Name” name=”lastname” value=”{!v.con.LastName}” />
<lightning:input type=”email” aura:id=”Contactform” label=”Email” name=”contactemail” value=”{!v.con.Email}” />
<lightning:input type=”Tel” aura:id=”Contactform” label=”Phone” name=”phone” value=”{!v.con.Phone}” pattern=”[0-9]{3}-[0-9]{3}-[0-9]{4}” />
</form>
</aura:component>
[/sourcecode]
JavaScript Controller: CreateContactController.js
[sourcecode language=”java”]
({
doInit: function(component, event, helper) {
// Prepare a new record from template
component.find(“ContactDetail”).getNewRecord(
“Contact”, // sObject type (objectApiName)
null, // recordTypeId
false, // skip cache?
$A.getCallback(function() {
var rec = component.get(“v.newrecord”);
var error = component.get(“v.recordError”);
if(error || (rec === null)) {
return;
}
})
);
},
handleChange:function(component,event){
component.set(“v.con.AccountId”, component.get(“v.recordId”));
component.set(“v.targetR”,JSON.stringify(component.get(“v.newrecord”)));
},
})
[/sourcecode]
Lightning Design:Â CreateContact.design
[sourcecode language=”html”]
<design:component >
<design:attribute name=”con” label=”Contact Record”/>
<design:attribute name=”recordId” label=”Account RecordId”/>
<design:attribute name=”targetR” label=”Target Record”/>
</design:component>
[/sourcecode]
Lightning Component:Â CreateOpportunity.cmp
[sourcecode language=”html”]
<aura:component implements=”lightning:availableForFlowScreens, force:appHostable, force:hasRecordId, force:lightningQuickAction” access=”global”>
<aura:attribute name=”opp” type=”Opportunity” default=”{‘sobjectType’ :’Opportunity’,’Name’:”,’Amount’:”,’StageName’:’Needs Analysis’,’CurrencyIsoCode’:’INR’}” />
<aura:attribute name=”opprecord” type=”Object” />
<aura:attribute name=”newrecord” type=”Object” />
<aura:attribute name=”showOpp” type=”Boolean” />
<aura:attribute name=”getTarget” type=”String” />
<aura:attribute name=”recordError” type=”String” />
<aura:attribute name=”con” type=”Contact” default=”{‘LastName’:”,’Phone’:”,’Email’:”}” />
<aura:attribute name=”cont” type=”Contact” default=”{‘LastName’:”,’Phone’:”,’Email’:”}” />
<aura:attribute name=”recordId” type=”String” />
<aura:handler name=”init” value=”{!this}” action=”{!c.doInit}” />
<force:recordData aura:id=”OppDetail” layoutType=”FULL” targetRecord=”{!v.opprecord}” targetFields=”{!v.opp}” targetError=”{!v.recordError}” />
<force:recordData aura:id=”ContactDetail” layoutType=”FULL” targetRecord=”{!v.newrecord}” targetFields=”{!v.cont}” targetError=”{!v.recordError}” />
<div class=”slds-text-align–left”>
<h3 class=”slds-section-title–divider”>Opportunity Form</h3><br/>
</div>
<form class=”slds-form–stacked”>
<lightning:input aura:id=”Opptform” label=”Name” name=”name” value=”{!v.opp.Name}” />
<label class=”slds-form-element__label” for=”select-01″>Currency</label>
<ui:inputSelect class=”single” aura:id=”InputSelectCurrency” change=”{!c.onSingleSelectCurrency}”>
<ui:inputSelectOption text=”USD” />
<ui:inputSelectOption text=”INR” value=”true” />
</ui:inputSelect>
<ui:inputCurrency aura:id=”Opptform” label=”Amount” value=”{!v.opp.Amount}” />
<label class=”slds-form-element__label” for=”single”>Stage</label>
<ui:inputSelect class=”single” aura:id=”InputSelectSingle” change=”{!c.onSingleSelectChange}”>
<ui:inputSelectOption text=”Any” />
<ui:inputSelectOption text=”Open” value=”true” />
<ui:inputSelectOption text=”Closed” />
<ui:inputSelectOption text=”Closed Won” />
<ui:inputSelectOption text=”Prospecting” />
<ui:inputSelectOption text=”Qualification” />
<ui:inputSelectOption text=”Needs Analysis” />
<ui:inputSelectOption text=”Closed Lost” />
</ui:inputSelect>
<ui:inputDate aura:id=”Opptform” label=”Closing Date” value=”{!v.opp.CloseDate}” displayDatePicker=”true” /><br/>
</form>
<div class=”slds-modal__footer”>
<lightning:button label=”Previous” onclick=”{!c.cancel}” class=”slds-button_brand” />
<lightning:button label=”Finish” onclick=”{!c.handleSaveContact}” class=”slds-button_brand” />
</div>
</aura:component>
[/sourcecode]
Javascript Controller:Â CreateOpportunityController.js
[sourcecode language=”java”]
({
doInit: function(component, event, helper) {
component.find(“OppDetail”).getNewRecord(
“Opportunity”, // sObject type (objectApiName)
null, // recordTypeId
false, // skip cache?
$A.getCallback(function() {
var rec = component.get(“v.opprecord”);
var error = component.get(“v.recordError”);
if (error || (rec === null)) {
return;
}
})
);
component.set(“v.newrecord”, JSON.parse(component.get(“v.getTarget”)));
component.find(“ContactDetail”).getNewRecord(
“Contact”, // sObject type (objectApiName)
null, // recordTypeId
false, // skip cache?
$A.getCallback(function() {
var rec = component.get(“v.newrecord”);
var error = component.get(“v.recordError”);
if (error || (rec === null)) {
return;
}
})
);
component.set(“v.cont”, component.get(“v.con”));
component.set(“v.newrecord.AccountId.value”, component.get(“v.recordId”));
},
onSingleSelectChange: function(component, event) {
var a = component.find(“InputSelectSingle”).get(“v.value”);
component.set(“v.opp.StageName”, a);
},
onSingleSelectCurrency: function(component, event) {
var a = component.find(“InputSelectCurrency”).get(“v.value”);
component.set(“v.opp.CurrencyIsoCode”, a);
},
handleSaveContact: function(component, event, helper) {
var flag = false;
component.set(“v.newrecord.AccountId.value”, component.get(“v.recordId”));
component.set(“v.newrecord”, JSON.parse(component.get(“v.getTarget”))); //
component.set(“v.cont”, component.get(“v.con”));
component.set(“v.cont.AccountId”, component.get(“v.recordId”));
component.set(“v.opp.AccountId”, component.get(“v.recordId”));
component.find(“ContactDetail”).saveRecord(function(saveResult) {
alert(JSON.stringify(saveResult.state));
if (saveResult.state === “SUCCESS” || saveResult.state === “DRAFT”) {
flag = true;
} else if (saveResult.state === “INCOMPLETE”) {
// handle the incomplete state
} else if (saveResult.state === “ERROR”) {
// handle the error state
} else {}
});
component.find(“OppDetail”).saveRecord(function(saveOpResult) {
if ((saveOpResult.state === “SUCCESS” || saveOpResult.state === “DRAFT”) && flag == true) {
// record is saved successfully
var resultsToast = $A.get(“e.force:showToast”);
resultsToast.setParams({
“title”: “Saved”,
“message”: “New Contact and Opportunity is created.”
});
resultsToast.fire();
$A.get(“e.force:refreshView”).fire();
var navEvt = $A.get(“e.force:navigateToSObject”);
navEvt.setParams({
“recordId”: component.get(“v.recordId”)
});
navEvt.fire();
} else if (saveOpResult.state === “INCOMPLETE”) {
// handle the incomplete state
} else if (saveOpResult.state === “ERROR”) {
// handle the error state
} else {}
});
},
cancel: function(component, event) {
$A.get(“e.force:refreshView”).fire();
var navEvt = $A.get(“e.force:navigateToSObject”);
navEvt.setParams({
“recordId”: component.get(“v.recordId”)
});
navEvt.fire();
}
})
[/sourcecode]
Lightning Design:Â CreateOpportunity.design
[sourcecode language=”html”]
<design:component >
<design:attribute name=”con” label=”Contact Record”/>
<design:attribute name=”opp” label=”Opportunity Record”/>
<design:attribute name=”recordId” label=”Account RecordId”/>
<design:attribute name=”getTarget” label=”Target Record”/>
</design:component>
[/sourcecode]
Note: This demo is just to make us familiar with the new release in the flow components. The above functionality could have been achieved using “Record Create” screen in flow or using lightning forms instead of using Lightning Data Service.