Pages

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

 

Saturday, 10 October 2015

Responsive Design with the Lightning Design System

Responsive Design with the Lightning Design System

Screen Shot 2015 10 10 at 17 44 36

Introduction

The Lightning Design System was launched by Salesforce in August 2015 and, as I previously blogged, is the first time that Salesforce have made styles available to the developer community that allow us to build pages that match the standard look and feel perfectly (the standard look and feel for the Lightning Experience that is, Salesforce classic would still require style scraping).

The Lightning Design System offers more than just styling - it also provides a Responsive Grid to allow building of user interfaces that react to the device being used, to provide an appropriate experience tailored to the amount of screen real estate available.

Responsive Design

Overview

Wikipedia has a great definition of Responsive Design:

"Provide an optimal viewing experience – easy reading and navigation with a minimum of resizing, panning and scrolling – across a wide range of devices”.

From a technical perspective its a mechanism for building web pages so that they respond to the device that is displaying them. The page changes its content and layout based on the viewport size and orientation of the device. Ethan Marcotte first coined the phrase in his article on A List Apart, which is well worth a read.

An important point about responsive design is that its not about removing content for smaller devices - more and more of the web is accessed via mobile devices these days and you don’t want to punish people for accessing your content via a phone by only serving them part of what you have to offer.

Responsive Grid

A responsive grid breaks your page up into a collection of rows, each of which decomposes into a common number of columns. On a large device the row will contain all columns, while on an extra-small device such as a phone, the grid can reflow to display one column per row, stacking the columns vertically instead of laying them out horizontally.

This is achieved via CSS media queries - a media query is essentially CSS that limits its scope based on attributes of the device. For example, consider the following media query:

.sidebar {
    display: none;
}

@media (min-width: 1024px)  {
    .sidebar {
         display: block;
    }
}

 the initial CSS rule mandates that anything with a class of sidebar will be hidden by setting the display attribute to none. The next rule is constrained by a media query and only applies if the minimum width of the device is 1024 pixels. This rule overrides the sidebar style to make it visible by setting the display attribute to block. Thus any user accessing the page on a small device will not see content with the sidebar class, while those accessing from a large device will.

Note that in keeping with the principle that responsive design is not about removing content, if i were using this media query on a real page I would still allow users accessing from a mobile device to see the content, just not as part of a sidebar.

Responsive Images

Displaying the right images for a device form factor is an important part of responsive design, and I’ve written about this before in the following blog post

Lightning Design System

The Lightning Design System responsive grid is created by specifying a component with the style class slds-grid - I pretty much always use a div as that way I don’t get any additional behaviour outside of the container. Note that you also want to specify the style class slds-wrap, otherwise your grid will continue to layout columns horizontally and, if you are anything like me, you’ll waste a couple of hours digging through the HTML and CSS trying to figure out what is going on.

Inside the grid, elements with the style class slds-col define the column data. Additional style classes can be applied to dictate how the columns will flow based on the device size. For example, given the following layout for extra small and large devices:

Screen Shot 2015 09 28 at 09 16 59

In this scenario, on a small device I want each component to span the full width of the device, while on a large device I want the search/about component to appear in a smaller sidebar on the right hand side.

Using the responsive grid, I can keep the same content, but define different column spans based on the device itself. 

<div class="slds-grid slds-wrap">
    <div class="slds-col slds-size--1-of-1 slds-medium-size--3-of-4">
        ...
    </div>
    <div class="slds-col slds-size--1-of-1 slds-medium-size--1-of-4">
        ...
    </div>
</div>

As the grid styles are mobile first, whatever I specify as the default (slds-size—1-of-1) will apply from extra small upwards. My override for medium (slds-slds-medium-size—3-of-4) will apply to that device size and upwards, so covering medium and large devices.

Blog Posts App

Overview

The sample blog post application conforms to the layout shown above - the main content of the page is a list of blog posts and for medium and large devices, their associated comments. There are additional elements to allow searching for posts containing matching text and about me content. On medium and large devices the additional elements are displayed to the right of the posts, while for extra small and small devices they are stacked under the main content. The Picturefill plugin displays different images for large, medium and small or less devices.

Here’s a screen shot for a large device:

Screen Shot 2015 10 04 at 13 36 54

 

a medium device (the only difference is the image):

Screen Shot 2015 10 04 at 13 37 19

 

and finally a small device - the difference here is the image, hidden comments and the right hand content stacked under the blog post body:

Screen Shot 2015 10 04 at 13 37 41

In order to view the sample you’ll need to deploy the code or install the managed package, as I haven’t yet found a way to make lightning components available via a Force.com site. I have hopes that the new lightning components in Visualforce functionality in the Winter 16 release will be the solution to this, but I haven’t had time to play with this in a site context.

This sample was originally written for a Dreamforce talk using Visualforce and Twitter Bootstrap - you can find out more about this in my developerforce blog post.

Gotchas

One thing that I’ve been struggling with and been unable to find a solution to, is conditionally hiding some content for multiple device sizes. When my blog posts are displayed on extra small or small devices, I want to hide the comments and provide a button to allow the user to display them. In something like Bootstrap I’d specify the button container with the classes hidden-md and hidden-lg. LDS doesn’t have an equivalent of this, so I’ve ended up using a span with a single LDS class - slds-x-small-show (which due to mobile-first means hidden for everything) and then adding another container inside this to hide the content for medium devices:

<span class="slds-x-small-show">
    <div class="medium-hide">
        ... button markup here ...
    </div>
</span>

The medium-hide style is as follows: 

/* hide medium and above */
@media all and (min-width: 768px) {
	.THIS .medium-hide {
        display:none;
	}
}

 

Responsive images also proved a challenge. I use the Picturefill polypill as the HTML5 picture element support is still lacking on some browsers. The latest version of this makes use of the Picture element but the developer console won’t let me save the a component with that markup. For now I’m sticking with version 1 of Picturefill which uses spans. I might take another look at this in the future, as I should be able to add the picture element via JavaScript as part of the initialisation.

When I started writing the code for this blog post, the version of the Lightning Design System was 0.9.1. As I already had a sample using 0.8 I left that alone so there are two static resources in the repository/unmanaged package. LDS is now on 0.9.2 and may well be higher by the time I finish writing the post!

My Domain Required in Winter 16

Don’t forget that the Winter 16 release requires my domain to be set up in order to use Lightning Components.

Code Repository

The code for this sample is available in my Lighting Design System Samples github repository at : 

https://github.com/keirbowden/BBLDS.

There’s also an unmanaged package that you can install to get all of the samples, through be aware that if you do this then the next time I produce a sample you’ll have to uninstall the managed package which will lose any blog data you’ve created.

Related Posts