Saturday 24 October 2015

LDS Activity Timeline, Lightning Components and Visualforce

LDS Activity Timeline, Lightning Components and Visualforce

Overview

One of the aspects of the Lightning Design System (LDS) that I particularly like is the example components, which means that I don't have to find a standard page with the feature that I want and scrape the HTML to replicate it.

A useful component for a variety of purposes is the Activity Timeline, which provides a visual overview of what has happened to a record, customer, anything you can think of really, and when. This is in use in a number of places in the new Lightning Experience UI to show activities, both future and historic.

In this post I'll show how to create an activity timeline component that displays the opportunities that have been closed won for a particular account record. I'll also be using the new Winter 16 feature that allows Lightning Components to be embedded inside a Visualforce page, which saves me having to write boilerplate JavaScript to pull the account Id parameter from the URL.

Remember that Winter 16 requires My Domain before Lightning Components can be displayed.

Update 31/10/2015 - @JennyJBennett pointed me at the Winter 16 release notes, which say:

— snip ---

Finally, you don’t need to enable My Domain to use Lightning components in the following contexts.

  • Lightning components with Communities in Community Builder
  • Lightning Components for Visualforce

— snip ---

Which means that you don’t need to enable My Domain to use the code in this blog post (though you will if you want to use the other components in my unmanaged package). This makes perfect sense as this is a security requirement, and Visualforce is already blocked from taking over other parts of the page/app via the browser’s same origin policy.

Show me the Code!

I didn't want to create a timeline that was tightly coupled to the opportunity sObject, instead I was looking for a more generic solution that could display any kind of data. I also didn't want the component to have to know too much about the information that it was displaying - components are much more re-usable if they just traverse a data structure and output what they find.

To this end, the timeline is modelled as a single Apex class, BBTimeline, with an inner class to represent an entry in the timeline. Note that the class fields are all annotated @AuraEnabled, to make them available for use the lightning component that renders them:

public class BB_LTG_Timeline {

    @AuraEnabled
    public String name {get; set;}
        
    @AuraEnabled
    public List<Entry> entries {get; set;}
        
    public BB_LTG_Timeline()
    {
        entries=new List<Entry>();
    }

    public class Entry
    {
        @AuraEnabled
        public Date theDate {get; set;}

        @AuraEnabled
        public String description {get; set;}
    }

}

There's then a custom Apex controller that builds the timeline object, in this case by retrieving the closed won opportunities from the database. It’s the responsibility of the method constructing the Timeline to add the entries in the desired order:

public class BB_LTG_AccountOppTimelineCtrl
{
    @AuraEnabled
    public static BB_LTG_Timeline GetTimeline(String accIdStr)
    {
        BB_LTG_Timeline result=new BB_LTG_Timeline();
        try
        {
            Id accId=(Id) accIdStr;
            System.debug('Account id = ' + accId);
            Account acc=[select id, Name from Account where id=:accId];
            result.name=acc.Name + ' closed deals';
            List<Opportunity> opps=[select CloseDate, Amount, Type
                                    from Opportunity
                                    where AccountId=:accId
                                      and StageName='Closed Won'
                                    order by CloseDate desc];

            for (Opportunity opp : opps)
            {
                BB_LTG_Timeline.Entry entry=new BB_LTG_Timeline.Entry();
                entry.theDate=opp.CloseDate;
                entry.description=opp.type + ' opportunity closed for ' + opp.amount;
                result.entries.add(entry);
            }
        }
        catch (Exception e)
        {
           System.debug('Exception - ' + e);
        }
        
        return result;
    }
}

Next there's the lightning component that outputs the timeline - BBAccountOppTimeline. This is pretty much lifted from the example in the Lightning Design System documentation. 

<aura:component controller="BB_LTG_AccountOppTimelineCtrl">
    <aura:attribute name="recordId" type="String" />
    <aura:attribute name="timeline" type="BB_LTG_Timeline" />
    
    <ltng:require styles="/resource/BB_SLDS091/assets/styles/salesforce-lightning-design-system-ltng.css"
    afterScriptsLoaded="{!c.doInit}" />
    
    <div class="slds">
        <c:BBAccountOppTimelineHeader />
        <ul class="slds-timeline">
            <p class="slds-m-around--medium"><a href="#">{!v.timeline.name}</a></p>
            <aura:iteration items="{!v.timeline.entries}" var="entry">
                <li class="slds-timeline__item">
                    <span class="slds-assistive-text">Event</span>
                    <div class="slds-media slds-media--reverse">
                        <div class="slds-media__figure">
                            <div class="slds-timeline__actions">
                                <button class="slds-button slds-button--icon-border-filled">
                                    <c:BBsvg class="slds-icon slds-icon-standard-event slds-timeline__icon" xlinkHref="/resource/BB_SLDS091/assets/icons/standard-sprite/svg/symbols.svg#event" />
                                    <span class="slds-assistive-text">Opportunity</span>
                                </button>
                                <p class="slds-timeline__date"><ui:outputDate value="{!entry.theDate}" /></p>
                            </div>
                        </div>
                        <div class="slds-media__body">
                            <div class="slds-media slds-media--timeline slds-timeline__media--event">
                                <div class="slds-media__figure">
                                    <c:BBsvg class="slds-icon slds-icon-standard-opportunity slds-timeline__icon" xlinkHref="/resource/BB_SLDS091/assets/icons/standard-sprite/svg/symbols.svg#opportunity" />
                                </div>
                                <div class="slds-media__body">
                                    <ul class="slds-list--vertical slds-text-body--small">
                                        <li class="slds-list__item slds-m-right--large">
                                            <dl class="slds-dl--inline">
                                                <dt class="slds-dl--inline__label">Description:</dt>
                                                <dd class="slds-dl--inline__detail"><a href="#">{!entry.description}</a></dd>
                                            </dl>
                                        </li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </li>
            </aura:iteration>
        </ul>
    </div>
</aura:component>

The JavaScript controller and helper, plus the supporting BBTimelineHeader component can be found in the unmanaged package or github repository via the links at the end of this post.

In order to display a Lightning Component in a Visualforce page, you need to construct a simple Lightning Application that is used as the bridge between the two technologies. This needs to have a dependency on the Lightning Component that will display the content - BBAccountOppTimeline in this case: 

<aura:application access="GLOBAL" extends="ltng:outApp">
    <aura:dependency resource="c:BBAccountOppTimeline" />
</aura:application>

Once the app is in place, there's a small amount of markup in the Visualforce page to tie things together - note that the Lightning Component is constructed dynamically via JavaScript, rather than being included in the page markup server side:

<apex:page sidebar="false" showHeader="false" standardStylesheets="false">
    <apex:includeScript value="/lightning/lightning.out.js" />
    <div id="lightning"/>

    <script>
        $Lightning.use("c:BBAccountOppTimelineApp", function() {
            $Lightning.createComponent("c:BBAccountOppTimeline",
                  { "recordId" : "{!$CurrentPage.parameters.id}" },
                  "lightning",
                  function(cmp) {
                    // any further setup goes here
              });
        });
    </script>
</apex:page>

The Results

Once all this scaffolding is in place, accessing the Visualforce page with the id of an account with at least one closed won opportunity displays a timeline of these opportunities, with the most recent at the top:

Screen Shot 2015 10 24 at 12 14 46

 

Where Can I Get It

As usual, I've added this into my BBLDS samples project available on github at :

https://github.com/keirbowden/BBLDS

Are there Test Classes?

Yes - this is available as an unmanaged package (there’s a link in the Github README), and you have to have test coverage to upload a package. Caveat emptor - these are purely focused on coverage!

Related Posts

 

No comments:

Post a Comment