Announcing the Easy Spaces App

Easy Spaces is a fictional event management company that creates and manages custom pop-up spaces (like cafés or game rooms) for companies and individuals, in their offices and homes. This reference application shows how Easy Spaces uses Salesforce to create and manage reservations, and match customers to the right kind of pop-up space.

Easy Spaces demonstrates how to build flexible, user-centric experiences. It also demonstrates how to manage the source for your apps with unlocked packages and use modular source control structures for your apps.

In this post, we’ll explore some of the key features of the Easy Spaces application and walk through sample code, and share how you can install and explore the app yourself.

What’s in this release?

Easy Spaces is based on the WeWork app shared during the TrailheaDX 2018 Opening Keynote. In the Summer 2018 initial release, Easy Spaces is all about integrating Lightning Flow and the Lightning Component framework with the Workspace API and console navigation to create user-centered apps that keep complex processes simple. The Easy Spaces application uses unlocked packages and object-agnostic design to illustrate possibilities for modularizing your Salesforce apps.

At the core of this initial release is the ‘Space Management’ Lightning app. This app uses console navigation and custom application pages to provide a centralized and simplified way to manage reservations and match customers to the right pop-up space.

You can take a quick video tour here.

The Reservation Manager gives Easy Spaces one place to manage how they help customers who need get started with a reservation:

The Spaces Designer is a centralized workspace to help match customers to the best type of pop-up:


Let’s look at how these pages, and the application itself, are built.

Lightning Flow and Base Components

Lightning Flow is central to the functionality of both the Spaces Designer and Reservation Manager. The spaceDesigner and reservationHelper components integrate dynamic flow interviews with other components on the page, using the <lightning:flow> base component.

The spaceDesigner component (in action above), dynamically begins a flow interview based on user interaction with the other components on the page. The markup for the flow component is simple:

<lightning:card iconName="custom:custom50" title="{! 'Designing Space for '+ v.customer}">
     <lightning:flow aura:id="flowCmp" onstatuschange="{!c.handleReset}"/>
</lightning:card>

The actual creation of the flow takes place in the component’s helper:

var flow = component.find("flowCmp");
var inputVariables = [
      {name : "varReservId" , type : "String" , value : record.Id},
      {name : "varMarketId", type: "String", value : record.Market__c},
];
 if(flow){
      flow.startFlow("spaceDesigner", inputVariables);
}

Highlights:

  • Lines 3,4: These lines set values for the flow’s input variables in JSON format. The name attribute should match the variable name defined in the flow.
  • Line 7: The startFlow() method is provided by the <lightning:flow> component.

The spaceDesigner and reservationHelper flows both use custom Lightning components with the lightning:availableForFlowScreens interface to create rich and interactive screens for users. This interface also gives these components access to flow navigation actions, which allow you to create custom options for users.

For example, the customerDetails component shows users a ‘Draft Reservation’ button:

 

The button invokes a method, which triggers a flow navigation action:

draftReservation : function(component, event, helper){
        ...
        var navigate = component.get("v.navigateFlow");
        navigate("NEXT");
    }

Highlights:

  • Line 3: The navigateFlow attribute is provided by the lightning:availableForFlowScreensinterface.
  • Line 4: The navigate() method is also provided by the lightning:availableForFlowScreens interface. The component uses the string value “NEXT” to invoke the same navigation action as the native ‘Next’ button, provided by flow.

Check out the smartGallery and customerDetails components for examples of how to work with flow navigation actions.

The new <lightning:recordForm> component lets Easy Spaces quickly and easily display record-based information throughout the application. It can show information in read-only or editable formats, with built-in respect for field-level security.

In the picture above, the ‘Primary Contact for Reservation’ section of is this base component at work in the customerDetails component:

<lightning:recordForm 
    objectApiName="{!v.sobject}"
    recordId="{!v.recordId}"
    fields="{!v.fieldsArray}" 
    mode="view"
    columns="2"
    onload="{!c.loadMarkets}"/>

Highlights:

  • Line 4: The fields attribute determines what fields to display. The component handles styling for each field based on its data type.
  • Line 5: The mode attribute determines the behavior of the component. Choosing ‘view’ displays information in an editable format.
  • Line 7: The onload attribute lets you execute custom logic when the component’s data loads.
  • Lines 2, 3, 4: The dynamic syntax in this example allows the component to display different sObject records. (More detail on how this works in the ‘Object-Agnostic Design’ section.)

 

Console navigation and Workspace API

Because the Space Management Lightning app uses console navigation, components in the app will display different behavior when users trigger standard actions like force:navigatetoSObject:

 

This change in component behavior is based entirely on the navigation settings in Lightning App Builder, rather than any specific changes component markup:

 

Components in console apps can implement custom navigation behaviors via the Workspace API, which allows manipulation of tabs and subtabs. Components access this API by including the <lightning:workspaceAPI> component in their markup.

The openRecordAction component is an example of this:

<aura:component implements="lightning:availableForFlowActions">
        <aura:attribute name="recordId" type="String" />
        <aura:attribute name="sobject" type="String"/>
        
        <lightning:workspaceAPI aura:id="workspace" />
</aura:component>

The interaction with the API takes place in the openRecordActionController:

invoke : function(component, event, helper) {
        var recordId = component.get("v.recordId");
        var sobject = component.get("v.sobject");
        var workspaceAPI = component.find("workspace");
        workspaceAPI.openTab({
            url: '/lightning/r/'+sobject+'/'+recordId+'/view'
        }).then(function(response) {
            workspaceAPI.focusTab({tabId : response});
       })
        .catch(function(error) {
            console.log(error);
        });
 },

Highlights:

  • Component markup, line 1: The lightning:availableForFlowActions interface, new in Summer ’18, allows components to be used in flows as a new type of resource called Local Actions. This means the openRecordAction can be used to give a flow interview access to the WorkspaceAPI and custom navigation behavior.
  • Controller.js, line 1: Because the component uses the lightning:availableForFlowActions interface, the controller uses invoke as the name of the method a flow interview should access.
  • Controller.js, lines 4-12: The controller uses the <lightning:workspaceAPI> component to access navigation methods provided by the API. The controller invokes the openTab() method, providing a URL in the new Summer ’18 standardized format as the target for the new tab. The focusTab() method ensures the newly opened tab receives immediate focus, rather than opening in the background.

A flow can then use a simple Decision step to control whether or not the Local Action should be used when ending the interview:

You’ll see examples of how to use the Workspace API to control app behavior, including how to refresh tabs, in the spaceDesigner and reservationHelper components.

Design tokens

Easy Spaces uses design tokens to give custom components uniform styling. You can see this in the pill custom component, which turns a brand-specific green when selected:

In the component’s css, this color is expressed as a token value:

.THIS.selected{
    background-color: token(EScontrastBrand);
}

The custom tokens for Easy Spaces are defined in the defaultTokens bundle:

<aura:tokens extends="force:base">
    <aura:token name="EScolorBrand" value="#B2FDDA"/>
    <aura:token name="EScontrastBrand" value="#012615"/>
</aura:tokens>

Highlights:

  • Line 1: Using extends="force:base" in the tokens markup gives Easy Spaces components access to a variety of standard tokens provided by Salesforce.
  • Lines 2,3: The tokens defined on these lines are the custom tokens defined by Easy Spaces for use throughout their application.

Object-agnostic design

Easy Spaces, like many companies, has processes that cut across different objects. Regardless of whether a person’s data is in the system as a Lead or a Contact, people need to be able to help a customer make reservations and find the right space for their events quickly and easily. Easy Spaces uses object-agnostic design patterns to accomplish this.

We’ve already looked at an example of this with the customerDetails component.

The customerList component is another example. The image below shows two instances of the component on an application page, associated with two different sObject types:

 

The sObject type the component should display is set by a design parameter in the Lightning App Builder.

The markup for the component itself is simple:

<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="reservationManagerController">
    <aura:attribute name="itemsList" type="Object[]" />
    <aura:attribute name="sobject" type="String"/>
    <aura:attribute name="badge" type="String"/>

    <aura:handler name="init" value="{!this}" action="{!c.onInit}"/>

    <div class="slds-card">
        <ul class="slds-list_vertical slds-has-dividers_bottom-space">
            <aura:iteration items="{!v.itemsList}" var="item">
                <li class="slds-item">
                    <c:customerTile item="{!item}" sobject="{!v.badge}"/>
                </li>
            </aura:iteration>
        </ul>
    </div>
</aura:component>

The customerTile component (on line 12), displays record-level detail. It uses the <lightning:tile> base component and accepts a generic object:

<aura:attribute name="item" type="Object"/>
<aura:attribute name="sobject" type="String" />

<lightning:tile label="{!v.item.name}" title="{!v.item.name}" href="{! '/lightning/r/' + v.sobject + '/' + v.item.Id + '/view'}">
        <aura:set attribute="media">
            <lightning:icon iconName="{! 'standard:'+ v.sobject }" />
        </aura:set>
        <dl class="slds-dl_horizontal">
            <dt class="slds-dl_horizontal__label">
                <p class="slds-truncate" title="Email">Email:</p>
            </dt>
            <dd class="slds-dl_horizontal__detail slds-tile__meta">
                <p class="slds-truncate" title="{!v.item.email}">{!v.item.email}</p>
            </dd>
            <dt class="slds-dl_horizontal__label">
                    <p class="slds-truncate" title="Reservation Status">Status:</p>
            </dt>
            <dd class="slds-dl_horizontal__detail slds-tile__meta">
                <p class="slds-truncate" title="{!v.item.status}">{!v.item.status}</p>
            </dd>
        </dl>
</lightning:tile>

Easy Spaces builds these object-agnostic components on top of services provided by Custom Metadata Types and Apex. These services create a standardized pattern Lightning components can use to handle and display customer record data.

The backbone of these services in Apex is the customerServices class. This class uses an inner class to define a standardized ‘Customer’, with the attributes common to all Easy Spaces customers, no matter the object:

public class customerServices{

*
*
*

    public class Customer{

        //attributes that we associate with customers
        @auraEnabled
        public String email {get; set;}

        @auraEnabled
        public String name {get; set;}

        @auraEnabled
        public String city {get; set;}

        @auraEnabled
        public String state {get; set;}

        @auraEnabled
        public String status {get; set;}

        @auraEnabled
        public String Id {get; set;}

        //putting them together, we get our wrapper object:
        public Customer(String name, String email, String city, String state, String status, String custId){
            this.name = name;
            if(String.isNotEmpty(email)) this.email = email;
            if(String.isNotEmpty(city)) this.city = city;
            if(String.isNotEmpty(state)) this.state = state;
            if(String.isNotEmpty(status)) this.status = status;
            if(String.isNotEmpty(custId)) this.Id = custId;    
        }
               
}

Highlights:

  • The @auraEnabled variables allow Lightning components (and other parts of the Easy Spaces application) a standardized dot notation for field-level attributes. The syntax becomes Customer.city instead of Lead.City or Contact.MailingCity. You can see this dot notation at work in the customerTile.
  • The .isNotEmpty() checks allow for flexible usage of the Customer wrapper object across the application. Not every component has to use every attribute.

The class also exposes a getCustomerFields method which allows components or other parts of the system to pass in a specific sObject name and receive back a ‘Customer’ object, with the API names of the fields holding ‘Customer’ data for that particular sObject:

@auraEnabled
    public static Customer getCustomerFields(String objectType){
        Customer customer;
        for(Customer_Fields__mdt c: [SELECT Customer_Name__r.QualifiedAPIName, Customer_Email__r.QualifiedAPIName, Customer_City__c, Customer_State__c, Customer_Status__r.QualifiedAPIName
            FROM Customer_Fields__mdt WHERE Sobject_Type__r.QualifiedAPIName = :objectType]){
                customer = new Customer(c.Customer_Name__r.QualifiedAPIName, c.Customer_Email__r.QualifiedAPIName, c.Customer_City__c, c.Customer_State__c, '', '');
        }
        return customer;
    }

Highlights:

  • The query refers to a Custom Metadata Type called Customer Fields, which holds information like field API names for customer data in the Easy Spaces schema.
  • The QualifiedAPIName attribute is what makes sure schema information returns in a format (i.e. ‘My_Custom_Field__c’) that can be used by <force:recordData> or <lightning:recordForm>.
  • This method returns a Customer wrapper object so that calling components can use a standardized dot notation to access schema information: Customer.name, Customer.email, etc.

Modular design and unlocked packaging

Easy Spaces also shows how you can build applications on top of granular units or modules of metadata. Specific features, like the use of design tokens or object-agnostic patterns, add to the modularity of Easy Spaces by making parts of the code base reusable and flexible. But you’ll get the best understanding of what modular design really means for Easy Spaces by looking at how Easy Spaces is organized in source control (link below).

The process of building the Easy Spaces application is the basis for our Getting Started with Modular Development and Unlocked Packaging series. To install the Easy Spaces application yourself, you’ll actually install several unlocked packages rather than one giant package.

These packages are maintained as a distinct folders within a unified project in source control. You can see dependencies between packages by looking at the project’s sfdx-project.json file. For more insight into the process behind organizing metadata into these distinct package modules, check out this post.

Source code

The source code and installation instructions are available in this GitHub repository.

Summary

The Easy Spaces app illustrates how you can combine the power of Lightning Flow, the Lightning Component framework and console navigation to create dynamic and user-centric applications. And thanks to the power of unlocked packages and Salesforce DX, you can explore this sample app yourself, quickly and easily.

More resources

Published
June 14, 2018

Leave your comments...

Announcing the Easy Spaces App