How a Salesforce Architect Plans His Baby’s Gender Reveal Party

How does a Salesforce architect announce to his family and friends the gender of the baby he and his wife are expecting? Using Salesforce and a Lightning Web Component, of course! A homemade confetti cannon also played a part :)

I’m Jason, one of the co-founders of Daydream. When my wife and I found out we were expecting, I wanted to design something special to help with the gender reveal. I adapted the Confetti Aura Component we told you about in a previous post - giving it a few new features:

  • I converted it from an Aura Component to a Lightning Web Component (using yesterday’s standards for such a forward-looking announcement didn’t seem quite right)

  • I added a color parameter to give the confetti a gender-appropriate look

  • I wrote an Apex controller to query for a single contact in the org which held a boy/girl value in a Gender custom field (Derek, the other co-founder of Daydream, set the gender value right before the big reveal)

  • I added some UI code to display a “Reveal” button that, when clicked, reveals the phrase “It’s a…” along with a dynamic tag to populate the gender, plus the confetti of course

  • Everything was wrapped in an Aura application that could be displayed full-screen with the computer connected to a TV so everyone could see

After a countdown, myself, my wife, family, and friends were delighted to be greeted by this:

Screen Shot 2019-10-26 at 9.02.29 PM.png

As the screen lit up with the confetti and message, Derek triggered the real-life confetti cannon and the room was filled with actual blue confetti too.

If you’re interested, you can find the source code for the Reveal LWC on GitHub.

Why Did My Lightning Web Component Disappear?

A snowy Sunday morning in Colorado seemed like the perfect time to dive into Lightning Web Components. All was going well with my simple Hello World component until I tried to fetch an Account’s name using the wire service. My component simply vanished from my Lightning Record Page.

Here’s what my component’s HTML file looked like:

<template>
    <lightning-card>
        Hello {name}!
    </lightning-card>
</template>

And here’s the JavaScript file:

import { LightningElement, wire} from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';

export default class Example extends LightningElement {

    @wire(getRecord, { recordId: '0015600000BZoDoAAL', fields: [ACCOUNT_NAME_FIELD] })
    account

    get name() {
        return this.account.data.fields.Name.value;
    }
}

And… here’s how it rendered in Salesforce:

Where’d my component go?

Where’d my component go?

In an attempt to get to the bottom of this mystery, I created a function that would run when ‘account’ was populated with data:

    @wire(getRecord, { recordId: '0015600000BZoDoAAL', fields: [ACCOUNT_NAME_FIELD] })
    account({ error, data }) {
        if (data) {
            console.log('got data');
            console.log('data -> ' + JSON.stringify(data));
        } else if (error) {
            console.log('got error');
            console.log('error -> ' + JSON.stringify(error));
        }
    }

In my browser console, I got exactly what I hoped for:

[Log] got data
[Log] data -> {"apiName":"Account","childRelationships":{},"fields":{"Name":{"displayValue":null,"value":"Daydream Developers"}},"id":"0015600000BZoDoAAL","lastModifiedById":"00556000001KZXnAAO","lastModifiedDate":"2019-01-13T15:32:01.000Z","recordTypeInfo":null,"systemModstamp":"2019-01-13T15:32:01.000Z"}

Next I added a similar debug statement to the name() function:

    get name() {
        console.log('fetching name');
        console.log('data -> ' + JSON.stringify(this.account));
        return this.account.data.fields.Name.value;
    }

Here’s what I found in the console log:

[Log] fetching name
[Log] data -> undefined

The component was attempting to fetch Name before it had a value. Apparently, doing that caused the component to fail silently in such a way that it refused to even render on the page.

The solution was to add a conditional tag, <template if:true={account.data}> so that the name property is only referenced if account.data is not null.

<template>
    <lightning-card>
        <template if:true={account.data}>
            <div class="slds-m-around_medium">
                Hello {name}!
            </div>
        </template>
    </lightning-card>
</template>
Two hours later, and I can now call off the search for my missing Lightning web component!

Two hours later, and I can now call off the search for my missing Lightning web component!

The key lesson learned here - if your Lightning web component disappears from the page silently - start your investigation by checking to see if you’re referencing a null value. If you are, then use conditional logic to check for null before trying to reference the value.


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

Customizing Lightning Components Using the Ternary Operator

When building with Lightning Components, rarely will you need to build a component completely from scratch. You can typically find standard components, provided by Salesforce, that you can leverage to meet your requirements with just a few lines of code.

In this post, we'll demonstrate how you can turn the lightning:buttonGroup component into a toggle.

We dynamically assign a value to the Variant attribute on the component using a ternary operator. The Variant attribute is either neutral (white) or brand (blue) depending on the value of isActive

ToggleButton.gif

Lightning Component

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    
    <aura:attribute name="isActive" type="boolean" />
    
    <lightning:buttonGroup>
        
        <lightning:button label="On" variant="{! v.isActive?'brand':'neutral' }" onclick="{!c.activate}"/>
        <lightning:button label="Off" variant="{! v.isActive?'neutral':'brand' }" onclick="{!c.deactivate}"/>
    
    </lightning:buttonGroup>
    
</aura:component>

Lightning Component Controller

({
    activate : function(component, event, helper) {
        component.set('v.isActive',true);
    },
    deactivate : function(component, event, helper) {
        component.set('v.isActive',false);
    }
})

One key to remember when using Ternary operators is that the entire expression should be enclosed in curly brackets.

Wrong: variant="{!v.isActive} ? brand : neutral"

Right: variant="{!v.isActive ? 'brand' : 'neutral'}"

Ternary operators are great way to toggle between different values in Lightning. You can toggle the label, class, or almost any other attribute on a component.