Terug naar lijst

In je lijst met accounts komen grofweg 3 soorten voor: klanten, prospects en partners. Een regelmatig terugkomende wens is verschillende velden te kunnen tonen, afhankelijk van het type account. Het Salesforceplatform heeft natuurlijk iets gevonden wat je hiermee verder helpt namelijk: record types & page layouts. Met page layouts kun je de samenstelling van velden en lijsten met gerelateerde records opstellen zoals jij dat wilt. Aan de hand van record types kun je vervolgens instellen voor welk soort record, in dit geval voor welk type account, je een bepaalde weergave wilt tonen.

Het wordt echter ingewikkelder als je meerdere variabelen krijgt aan de hand waarvan je wilt bepalen welk account een bepaalde layout krijgt. Stel je voor dat je voor prospect accounts verschillende velden terug wilt laten komen, afhankelijk van de industrie. Of stel dat je bijvoorbeeld adresgegevens alleen wilt tonen voor actieve klanten. De record type-werkwijze zou dan impliceren dat je deze variabelen onderbrengt in de record types: je zou dan een lijst krijgen met Klant (actief)Klant (inactief)PartnerProspect (overheid)Prospect (onderwijs), etcetera, etcetera. Om te voorkomen dat je Salesforce admin tegen 2023 moet dealen met record types als “Prospect (retail) – actief – Zuid-Holland (omzet < 10M)” hebben we bij Growteq iets verzonnen om de zichtbaarheid net even wat verfijnder te kunnen instellen.

Enter: de Lightning App Builder, field sets en een Lightning component dat weinig meer is dan een standaardcomponent in een bescheiden jasje.

Eén van de handigste grepen van de Lightning Experience (nog niet over op Lightning? Help ons jullie te helpen door dit te lezen) is de Lightning App Builder. Een paginabouwer waarin je met drag and drop je eigen user interfaces in elkaar klikt; voor ieder object aan heel andere Lightning Page (maakt nu ook de Salesforce Mobile-pagina’s voor je!). Iedere pagina bouw je op middels componenten als de chatter feed, activity timeline, related lists, en: het record detail-component. Dit is de weergave van je record, volgens de page layout die door jou gedefinieerd is. Page layouts en Lightning pages zijn dus 2 verschillende zaken: in de page layout definieer je de velden die je wilt zien, in de Lightning page de rest eromheen. We hebben echter al gezien dat de page layouts voor ons probleem niet toereikend zijn.

Gelukkig is het mogelijk om ook zelf gebouwde componenten toe te voegen. We kunnen een component maken, dat bij het laden van de pagina de lijst met velden ophaalt die je hebt gespecificeerd in een field set die je kunt aanmaken in de object manager.

In de field set bepaal je welke velden in welke volgorde moeten komen te staan. Het Lightning component haalt die velden op en geeft ze weer, op dezelfde manier dat de gewone Record Detail dit laat zien. De code voor dit component vind je onderaan deze blog. Hoe dit vervolgens de oplossing is voor het probleem, heeft te maken met één cruciale functie van de app builder: de mogelijkheid componenten zichtbaar te maken op basis van criteria. Een enigszins verscholen knop onder het kopje Set Component Visibility is hier onze redding:

Hiermee kunnen we de zichtbaarheid van het component instellen, aan de hand van filters op informatie uit het record, de gebruiker, de rechten van de gebruiker, of de form factor (= desktop of telefoon). Filteren op een specifieke veldwaarde is precies wat we hier willen doen. We willen bijvoorbeeld een groep velden met marketinggerelateerde informatie alleen laten zien als het type gelijk is aan Prospect; de salesgerelateerde velden alleen als type = Klant en Active = yes; weer een andere set velden voor Prospects in de industrie Engineering, en een vierde groep velden als het om een Partner gaat. Hiervoor maken we 5 field sets aan: één set velden die we altijd willen laten zien, genaamd Base, en een field set voor elke beschreven groep velden:

Het component dat we met de naam Display Field Set hebben opgeslagen, is beschikbaar in de app builder. Voor iedere field set voegen we deze toe aan de Accountpagina, en in het rechterpaneel vullen we voor ieder blokje de parameters in: namelijk de naam van de betreffende field set, het aantal kolommen, en of het label van de field set gebruikt moet worden als een sectiekopje.

Vervolgens kunnen we ook de filters instellen. We gebruiken voor het blokje Partner Information bijvoorbeeld typeInstallation Partner of Technology Partner. Het stukje Marketing laten we alleen zien als type = Prospect, enzovoorts. Op deze manier kunnen we compleet finetunen welke informatie we wanneer willen zien, zonder hele aantallen record types en page layouts bij te houden! Daar komt natuurlijk wel het onderhoud van een aantal field sets voor terug. Het verschil is echter dat ieder nieuw veld alleen toegevoegd hoeft te worden aan de betreffende field set, in plaats van aan alle bestaande page layouts.

Het resultaat is als volgt voor een prospect:

Veranderen we de Industry naar Engineering, krijgen we:

En voor een Customer ziet de pagina er als volgt uit:

Hier kunnen we natuurlijk nog meer variaties aan toevoegen door de velden naar nog meer field sets uit te splitsen, en deze conditioneel weer te geven. 100% dynamisch!

Een aantal kanttekeningen die hier bij horen:

  1. Bij het aanmaken van een nieuw record is de getoonde weergave die van de betreffende page layout. Ook wanneer je de Edit action gebruikt om het record te bewerken, wordt de page layout gebruikt. Het is daarom het handigst om één page layout aan te maken met daarin alle relevante velden.
  2. Bij het bewerken middels inline edit (het potloodje) verandert alléén het betreffende blokje in een formulier, en verschijnt de Saveknop hier direct onder. Wanneer je meerdere blokjes tegelijk wilt bewerken kan dit verwarrend zijn. Dit heeft ermee te maken dat ieder blokje een op zichzelf staand bewerkformulier is. Dit zorgt er ook voor dat het laden iets langer kan duren.
  3. De weergave is iets anders dan de standaarddetailweergave. Dit is vooral uitlijning en kleur; de verschillen zijn klein. Daarbij werden normaal gesproken een aantal specifieke standaardvelden net iets handiger getoond, zoals adresvelden en naamvelden (de zogenaamde compound fields). Dit is in deze componenten niet ondersteund. Wel krijg je daarvoor terug dat je de velden in meerdere kolommen kunt tonen.

De code:

fieldSet.cmp:

<aura:component description="fieldSet" implements="force:hasSObjectName,force:hasRecordId,flexipage:availableForRecordHome" controller="AuraController_FieldSet">
    <aura:attribute name="fieldSetName" type="String" required="true" access="global" />
    <aura:attribute name="showLabel" type="Boolean" default="true" access="global" />
    <aura:attribute name="columns" type="Integer" default="2" access="global" />

    <aura:attribute name="label" type="String" access="private" />
    <aura:attribute name="fields" type="List" access="private" />
    <aura:attribute name="errorMessage" type="String" access="private" />
    <aura:attribute name="expanded" type="Boolean" access="private" default="true"/>

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

    <article class="slds-card">
        <div class="slds-card__body slds-card__body_inner">
            <aura:if isTrue="{!not(empty(v.errorMessage))}">
                <div class="slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error" role="alert">
                <span class="slds-icon_container slds-icon-utility-error slds-m-right_x-small" title="Description of icon when needed">
                    <lightning:icon iconName="utility:error" alternativeText="Error!" variant="error"/>
                </span>
                    <h2>The component could not be loaded. Error message: {!v.errorMessage}</h2>
                </div>
            </aura:if>

            <aura:if isTrue="{!empty(v.errorMessage)}">
                <div class="{!'slds-section ' + ( v.expanded ? ' slds-is-open' : '' )}">
                    <aura:if isTrue="{!v.showLabel}">
                        <h3 class="slds-section__title slds-theme_shade">
                            <button aria-controls="content" aria-expanded="{!v.expanded}" class="slds-button slds-section__title-action" onclick="{!c.clickSwitch}">
                                <lightning:icon iconName="utility:switch" size="xx-small" class="slds-section__title-action-icon slds-button__icon slds-button__icon_left"/>
                                <span class="slds-truncate slds-p-horizontal_small" title="{!v.label}">{!v.label}</span>
                            </button>
                        </h3>
                    </aura:if>

                    <div aria-hidden="{!not(v.expanded)}" id="content" class="slds-section__content">
                        <lightning:recordForm
                                recordId="{!v.recordId}"
                                objectApiName="{!v.sObjectName}"
                                mode="view"
                                fields="{!v.fields}"
                                columns="{!v.columns}"
                                density="auto"
                                onsuccess="{!c.onSuccess}"
                        />
                    </div>
                </div>
            </aura:if>
        </div>
    </article>
</aura:component>

fieldSet.design:

<design:component label="Display Field Set">
    <design:attribute name="fieldSetName" label="Field set API name" description="API name of your field set"/>
    <design:attribute name="columns" label="# Columns" description="Number of columns. The fields will be evenly distributed among the columns." />
    <design:attribute name="showLabel" label="Show label" description="Show to name of the field set as a section header"/>
</design:component>

fieldSetController.js
({
    doInit: function(component, event, helper) {
        var fieldSetName = component.get("v.fieldSetName");
        var sObjectName = component.get("v.sObjectName");
        var action = component.get("c.getFieldSetDescribe");
        action.setParams(
            {
                "sObjectName": sObjectName,
                "fieldSetName": fieldSetName
            }
        );
        action.setCallback(this, function(response) {
            if(response.getState() === "SUCCESS") {
                var describe = response.getReturnValue();
                console.log(describe);
                component.set("v.label", describe.label);
                component.set("v.fields", describe.fieldNames);
            } else {
                var errors = response.getError();
                var message = "Unknown error";
                if(errors && errors.length > 0) {
                    message = errors[0].message;
                }
                component.set("v.errorMessage", message);
            }
        });
        $A.enqueueAction(action);
    },

    clickSwitch: function(component, event, helper) {
       component.set("v.expanded", !component.get("v.expanded"));
    },

    onSuccess: function(component, event, helper) {
        $A.get('e.force:refreshView').fire();
    }
})
AuraController_FieldSet.apex:

public with sharing class AuraController_FieldSet {
    @AuraEnabled
    public static FieldSetDescribe getFieldSetDescribe(String sObjectName, String fieldSetName) {
        SObjectType sObjectType = Schema.getGlobalDescribe().get(sObjectName);
        if(sObjectType == null) {
            throw new AuraHandledException('SObject type not found: ' + sObjectName);
        }
        DescribeSObjectResult describe = sObjectType.getDescribe();
        FieldSet fieldSet = describe.fieldSets.getMap().get(fieldSetName);
        return convertFieldSet(fieldSet);
    }

    private static FieldSetDescribe convertFieldSet(FieldSet fieldSet) {
        FieldSetDescribe describe = new FieldSetDescribe(fieldSet.label, fieldSet.name);
        for(FieldSetMember member: fieldSet.fields) {
            describe.addField(member.fieldPath);
        }
        return describe;
    }

    public class FieldSetDescribe {
        @AuraEnabled
        public String label {
            get; private set;
        }
        @AuraEnabled
        public String name {
            get; private set;
        }
        @AuraEnabled
        public List<String> fieldNames {
            get; private set;
        }

        public FieldSetDescribe(String label, String name) {
            this.label = label;
            this.name = name;
            this.fieldNames = new List<String>();
        }

        public void addField(String field) {
            fieldNames.add(field);
        }
    }

    public class FieldSetException extends Exception {}

}