An Exciting New Way to Celebrate Wins in Lightning

Confetti.gif

Salesforce got its start as the go-to tool for Leads, Contacts and Opportunities. So in today's post we are going to make winning Opportunities in the Lightning Experience awesome!

As you can see in the animation above, the component will rain down confetti on the screen and display a congratulatory message. This is a great way to have reps celebrate their big win. The celebration appears every time an Opportunity over a given dollar amount is Closed Won - but you could easily change the criteria to trigger a celebration when a rep closes a certain amount of business in a month or when they take the lead in a sales ranking.  

Let's get into how the component is built. The first thing we need to do is import the confetti animation script we got from Agezao's GitHub Project. We load the JS file as a Static Resource and then pull it in using the ltng:require component. Since we don't display the confetti until the stage is changed, we don't need to specify a function for the afterScriptsLoaded attribute. We leverage force:recordData to get the Amount and StageName of the opportunity. The nice thing about using force:recordData is that it doesn't require an Apex Controller to get the data and no matter how the Stage changes (Inline Editing or the Path Component) we are automatically notified of the change. 

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    
    <ltng:require scripts="{!$Resource.Confetti}"/>
    
    <aura:attribute name="record" type="Object"/>
    <aura:attribute name="simpleRecord" type="Object" description="A simplified view object to be displayed"/>
    <aura:attribute name="error" type="String" description="An error message bound to force:recordData"/>
    <aura:attribute name="displayCanvas" type="boolean" default="false" />
    <aura:attribute name="currentVal" type="string" />
    <aura:attribute name="threshold" type="integer" default="0" required="true"/>
    <aura:attribute name="winStage" type="string" default="Closed Won" required="true"/>
    
    <force:recordData aura:id="forceRecordCmp"
        recordId="{!v.recordId}"
        fields="Amount,StageName"
        mode="VIEW"
        targetRecord="{!v.record}"
        targetFields="{!v.simpleRecord}"
        targetError="{!v.error}" 
        recordUpdated="{!c.recordUpdated}"/>
    
    <canvas id="confetti" aura:id="confetti" class="{!v.displayCanvas?'':'slds-hide'}" />
 
</aura:component>

In the Lightning Controller all we need to do is handle when the record is changed. The function specified in the force:recordData component will fire when a the record is Loaded, Changed, Deleted, or when it has an Error. On load we set the current Stage so we know that the Stage changed later and when the record is updated we check to see if it was Closed Won.

({
    recordUpdated : function(component, event, helper) {
        
            var changeType = event.getParam('changeType');
        
            if(changeType==='LOADED'){
                component.set('v.currentVal',component.get('v.simpleRecord').StageName);
            }else if(changeType==='CHANGED'){ 
                helper.checkStage(component);
            }
    }
})

There is a lot more going on in the Lightning Helper. In the checkStage function we pull the threshold and winStage from aura:attributes which themselves are set in the App Builder (more on that later). In the stopConfetti function we are leveraging a JavaScript timeout to stop the confetti after 5 Seconds. Anytime you use a timeout you will need to wrap your function in $A.getCallback. This ensures that the framework rerenders the modified component and processes any enqueued actions. Finally, in the congrats function, we use $A.localizationService.formatCurrency which will format the Opportunity Amount to the users correct currency format.

({
    checkStage : function (component){
        
        var newStage = component.get('v.simpleRecord').StageName;
        var amount = component.get('v.simpleRecord').Amount;
        var oldStage = component.get('v.currentVal');
        var winStage = component.get('v.winStage');
        var threshold = component.get('v.threshold');
        
        if(oldStage != winStage && newStage == winStage){
            if(amount>threshold){
                this.startConfetti(component);
            }
        }
    },
    startConfetti : function(component){
        
        this.congrats(component);
        component.set('v.displayCanvas',true);
        
        var confettiSettings = { 
            target: 'confetti',
            max: 200,
            props: ['square','line']
        };
        
        var confetti = new window.ConfettiGenerator(confettiSettings);
        confetti.render();
        
        this.stopConfetti(confetti,component);
    },
    stopConfetti : function(confetti,component){
        window.setTimeout(
            $A.getCallback(function() {
                confetti.clear();
                component.set('v.displayCanvas',false);
            }), 5000
        );
    },
    congrats : function(component) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "type" : "success",  
            "message": "Congrats on your "+$A.localizationService.formatCurrency(component.get('v.simpleRecord.Amount'))+" Opportunity Win!"
        });
        toastEvent.fire();
    }
})

In Lightning Style all we need to do is make the canvas that holds the confetti be positioned absolute and on top of everything. 

.THIS#confetti{
  position: absolute;
  z-index : 9999;
}

In the Lightning Design Component we expose the threshold and winStage attributes so they are easily configurable when adding the component to the page.

<design:component>
    <design:attribute name="threshold" label="Win Threshold" description="The minimum amount the opportunity needs to be to display the confetti. If you always want to display it leave it at 0."/>
    <design:attribute name="winStage" label="Stage" description="The stage the opportunity should be at to display the confetti"/>
</design:component>

Finally, when you add the Component to the Lightning Page, add it to the very top to make sure it takes up the entire screen. Check out the full code on GitHub