Distinguishing Custom Settings in Apex

In many respects, the Salesforce API treats Hierarchy and List Custom Settings the same way: their schemata are the same, and most, though not all, static methods apply to both types of settings. This works well in the typical use case of calling MyCustomSetting__c.getInstance(). But suppose you’d like to build generic code that operates on settings. How can you tell list from hierarchy settings to handle them appropriately?

Custom Settings are easily identifiable with Schema.DescribeSObjectResult.isCustomSetting(), but there’s no analogue method for custom setting type. Luckily, there’s two critical differences in field behavior on these objects that yield methods of determining which object is which.

  • For a Hierarchy setting, Name is nillable, while it is required for List settings.
  • Inserting a List Custom Setting with a non-null SetupOwnerId results in a FieldIntegrityException.

Hence, a simple way to tell the two apart, given a DescribeSObjectResult:

 public Boolean isHierarchyCustomSetting(Schema.DescribeSObjectResult s) {
    return s.isCustomSetting() && s.fields.getMap().get('Name').getDescribe().isNillable();
 }

 public Boolean isListCustomSetting(Schema.DescribeSObjectResult s) {
    return s.isCustomSetting() && !s.fields.getMap().get('Name').getDescribe().isNillable();
 }

An alternate, but slower, route is to construct an instance with newSobject() and populate the SetupOwnerId field. A (catchable) exception upon insert identifies the class as a List Custom Setting.

Visualforce Traps for Experienced Programmers

Visualforce has a way of punishing those who make assumptions about its behavior based on other development environments (including Apex and even HTML). I’ve been accumulating a list of Visualforce behaviors and features that seem inexplicable to me at first blush, or that I’ve tripped up on multiple times. Whenever I’m debugging, I check this list of mistakes first. Suggested additions are welcome!

Parameters Not Being Set

The <apex:param> component silently does not set its parameter unless the name attribute is populated, although omitting name is not an error, and the attribute doesn’t make sense for command button parameters.

Race Condition / Incorrect Serialization of Actions

It’s common to need to set a value in the controller and subsequently trigger a partial page re-render. In some cases, there’s no controller action required at all; in others, the business logic may be embedded in a controller setter method rather than a Visualforce action.

The naïve way to handle this is an <apex:commandButton> with one or more <apex:param> elements and a reRender, but no action set. Unfortunately, this causes a difficult-to-debug race condition. The server calls that set the parameters and perform the re-render are not serialized, meaning that the re-render will be working with stale data some, but not all, of the time.

Workaround: an empty action method in the controller. This forces a complete server round-trip prior to beginning the re-render.

Required Non-sObject Fields

It seems obvious to use the required attribute on, for example, <apex:inputText> components to obtain the standard Salesforce UI presentation of a red bar beside the component, when working with non-sObject fields. Unfortunately, this doesn’t work. Instead, you can use a workaround with a styled empty <div>.

Misplaced Labels

<apex:outputLabel> is position-dependent. In order to be rendered with the correct style as part of an <apex:pageBlockSectionItem>, it must precede its for element, and not be nested within that element.

<apex:pageBlockSectionItem>
  <apex:outputLabel for="outValue" value="A Label" />
  <apex:outputText value="{! someControllerValue }" id="outValue" />
</apex:pageBlockSectionItem>

The above shows the correct ordering to receive standard Salesforce styling.

reRender Targets and the rendered Attribute

Showing and hiding page elements based on changes to controller values is a very common workflow. It’s easy to forget that you cannot do this by setting the rendered attribute on a target and then performing a reRender on that very same target. Since the elements with rendered="false" are never even sent to the client, they can’t be re-rendered in the normal way.

Instead, wrap the element with the rendered attribute in an <apex:outputPanel> that you reRender to show and hide its content.

Accessing Visualforce Components in JavaScript.

Accessing Visualforce components in JavaScript using document.getElementById() with $Component is highly unintuitive. id values assigned to Visualforce components aren’t global like they are in HTML, so in many cases you need to qualify them in order to obtain a reference to the component. More confusingly, the manner in which you must so qualify the component reference is partially dependent upon where in the page the JavaScript is located.

Salesforce has several pages that attempt to explain this, but by far the most useful is this set of examples.

Where else is Visualforce likely to trip up coders trying to apply experience from other environments?

Free Events in Click & Pledge

Click & Pledge nicely supports online registration for both free and paid/ticketed events. However, one point of confusion for both users and staff is that a “$0.00” line item is displayed on the registration page even for free events, and for free events using the named event model, a “Payment Information” section is displayed on the second page of the registration UI.

Fortunately, C&P offers the ability to include custom CSS in the template designer. Some simple CSS suppresses these payment-related elements, granting a more streamlined free registration experience for both named and anonymous events. The effect isn’t perfect, but until Click & Pledge permits deeper modification of its registration component, I’m satisfied with the results. Enhancements are welcome!

Bridging Click & Pledge and Salesforce Campaigns with Process and Flow

This post has been extensively revised to broaden the applicability of the solution, covering anonymous and named Click & Pledge events.

Click & Pledge provides functionality to add contacts who register for an event to a Salesforce campaign. However, contacts are added with the first available status (often ‘Sent’), rather than a value showing them as registered or attended. Because campaigns provide a standard, deduplicated, reportable data architecture that’s independent of registration package, it’s valuable to propagate C&P status updates into the associated campaigns. Further, integrating registration information into campaigns increases the value of the Campaign History related list provided on every contact, offering an at-a-glance summary of recent invitations and responses thereto.

At the same time, we can add value to the Campaign Member object and support common reporting needs by marking who’s attending each event as a guest of whom, using a custom lookup field Registered by to the Contact on the Campaign Member object.

Because both of the registration objects used by Click and Pledge (C&P Event Registered Attendee and C&P Event Registrant) may be associated with C&P Temporary Contacts that can be linked to Contacts in any order, at different times, and via asynchronous process, we use an adaptable structure with two processes and two autolaunched flows. The processes and flows create and update Campaign Members incrementally as information becomes available, and re-run on each modification to the registration objects to propagate updates.

It’s also important to take into account anonymous events. Click & Pledge creates C&P Registered Attendee records for anonymous registrations, but they’re never linked to contacts — only the C&P Event Registrant is. Our flows take this variation into account.

Process 1: Registered Attendees

Triggered on the C&P Event Registered Attendee object upon creation or modification.

Because we cannot rely on a Contact ever being linked to the registered attendee (in the case of anonymous registrations), the process’s single action group is run without criteria. An update to a registered attendee without a contact attached may stem from a check-in event.

The process simply invokes Flow 1 with the Registered Attendee as its parameter. The flow is responsible for extracting the other required information from the object hierarchy.

Process 2: Registrants

Triggered on the C&P Event Registrant object upon creation or modification.

Even in the case of anonymous events, a Contact must ultimately be assigned to the registrant. This process therefore has a single action group criterion, [CnP_PaaS_EVT__Event_registrant_session__c].CnP_PaaS_EVT__ContactId__c is not null.

The only action is Run Flow, triggering Flow 2 with the Registrant ID as a parameter. This process is necessary not only to update the Registered by field, but also to allow our flows to source a Contact value from the registrant object for anonymous events.

Flow 1: Update Campaign Members

Called by Process 1 and Flow 2

This flow contains the meat of the functionality. It accepts one input variable, holding the ID of the C&P Event Registered Attendee. The flow has the following structure.

Update Campaign Members flow

Four Fast Lookup elements assign the C&P Event Registered Attendee, C&P Event, Campaign Member, and C&P Event Registrant objects to sObject variables. Note that the registered attendee is guaranteed not to be null by the calling process, but the other objects are nullable, and the registrant will often be null dependent upon the user’s processing of temporary contacts.

The flow uses five formulas to draw data from these four objects. Note that because the object hierarchy involves many nullable lookup fields and connections to contacts that may be populated in any order, we guard cross-object field references with null checks.

CampaignId

Evaluates to the Campaign assigned to the registration level, if any, or to that assigned to the event, if any, or null.

IF(!ISBLANK({!RegisteredAttendeeSobject.CnP_PaaS_EVT__Registration_level__c}) && !ISBLANK({!RegistrationLevelSobject.CnP_PaaS_EVT__Campaign__c}), {!RegistrationLevelSobject.CnP_PaaS_EVT__Campaign__c},
IF(!ISBLANK({!RegisteredAttendeeSobject.CnP_PaaS_EVT__EventId__c}) && !ISBLANK({!EventSobject.CnP_PaaS_EVT__Campaign__c}), {!EventSobject.CnP_PaaS_EVT__Campaign__c}, null))

ContactId

Evaluates to the Contact assigned to the registered attendee, unless the event is marked as anonymous, in which case it evaluates to the contact of the registrant. May be null if neither contact has been assigned.

IF(!ISBLANK({!RegisteredAttendeeSobject.CnP_PaaS_EVT__EventId__c}) && {!EventSobject.CnP_PaaS_EVT__Anonymous__c} && !ISBLANK({!RegisteredAttendeeSobject.CnP_PaaS_EVT__Registrant_session_Id__c}) && !ISBLANK({!Registrant.CnP_PaaS_EVT__ContactId__c}), {!Registrant.CnP_PaaS_EVT__ContactId__c}, {!RegisteredAttendeeSobject.CnP_PaaS_EVT__ContactId__c})

RegistrantId

Evaluates to the Contact assigned to the Registrant, unless it is the same as the Contact of the attendee. Used to populate the Registered by field.

IF(!ISBLANK({!RegisteredAttendeeSobject.CnP_PaaS_EVT__Registrant_session_Id__c}) && !ISBLANK({!Registrant.CnP_PaaS_EVT__ContactId__c}) && {!Registrant.CnP_PaaS_EVT__ContactId__c} != {!ContactId}, {!Registrant.CnP_PaaS_EVT__ContactId__c}, null)

CMStatusExisting

Evaluates to the campaign member status value to use if an existing Campaign Member is located. It will not overwrite an ‘Attended’ value; this copes with check-in events performed on the multiple registered attendees that may be linked to a single contact and registrant for anonymous events.

IF({!RegisteredAttendeeSobject.CnP_PaaS_EVT__CheckIn_Status__c} = 'Checked-In', 'Attended', IF(!ISPICKVAL({!CM.Status}, 'Attended'), 'Registered', TEXT({!CM.Status})))

CMStatusNew

Evaluates to the campaign member status value to use if a new Campaign Member is being created.

IF({!RegisteredAttendeeSobject.CnP_PaaS_EVT__CheckIn_Status__c} = 'Checked-In', 'Attended', 'Registered')

Structure

Because the calling processes cannot guarantee that appropriate information will be available in the object hierarchy in all circumstances, the flow uses a decision element to decide whether to proceed; it continues if both ContactId and CampaignId are non-null.

These conditions being met, if the Campaign Member is null, a new record is created and inserted, using CMStatusNew, ContactId, CampaignId, and RegistrantId. If not, the Campaign Member is updated, and CMStatusExisting is used.

If the registrant has not yet been populated (for a named event), the Registered by field will be populated when Process 2 and Flow 2 fire.

Flow 2: Update Registered Attendees

Called by Process 2, calls Flow 1

This flow is required for handling anonymous registrations (where a contact is assigned only to the registrant object) and for populating the “Registered by” field on the Campaign Member in the circumstance that the C&P Temporary Contact corresponding to the registrant is processed after one or more of those corresponding to the associated attendees. It accepts as input parameter the ID of the modified C&P Event Registrant object. The flow has the following structure.

Update Registered Attendees flow

A Fast Lookup assigns all C&P Registered Attendees linked to the registrant to an sObject collection variable. A loop iterates over the collection and invokes Flow 1 with the required parameter. No criteria are applied to the registered attendees; all evaluation is handled in Flow 1.

Summary

Processes and Flows offer an easy way to migrate Click and Pledge registration information to campaigns in real time. Unifying information about event registrations with campaigns enhances reportability, particularly in a context where multiple registration packages or types of event are in use (Click and Pledge, Google Forms, small events without online registrations), and allows staff to easily inspect event registrations on the Contact record, even without access to Click & Pledge.

Null Relationships and Short-Circuiting Behavior in Salesforce Formulas, Process Builder, Flow, and Apex

What happens when you refer to a field across a lookup relationship, and the lookup relationship is null? The answer turns out to vary across contexts in non-obvious ways. In the course of debugging some Process Builder logic, I came up with a summary. In all of the examples below, I’m using a custom object called Test Base Object with a nullable lookup relationship Account__c.

Object Setup

Formula Fields

When a cross-object reference is used in a formula field and the lookup is null, the value of the cross-object reference is also null. No exception is thrown.

Boolean logic treats the null value as false. Since no exception occurs, the ordering of Boolean clauses is irrelevant and no short-circuit evaluation is needed to obtain correct results.

Process Builder

Process Builder’s condition and formula-based triggers appear to operate like formulas, but actually handle null relationships very differently. Dereferencing a null field in a Process Builder condition or formula always results in an exception. With complex logic in conditions for running actions, this can be tricky to debug. The errors it produces for users are opaque and frustrating, often preventing any mutation of the involved object.

Process Builder Error

Fortunately, Boolean operators and functions (AND and OR, including the implicit logical operations used in condition-based triggers) in the Process Builder context perform short-circuit evaluation. In other words, references across the lookup relationship can be guarded by checks against null lookups earlier in the evaluation order such that evaluation will stop before reaching the cross-object relationship, avoiding an exception. The evaluation order is left-to-right for the AND() and OR() functions and the && and || operators, and top-to-bottom for condition lists.

Safe Process Builder Condition Pattern

Using conditions in Process Builder, always precede a cross-object field reference (assuming a nullable lookup relationship) with a null check. As in this example, protect the [Test_Base_Object__c].Account__c.Name reference with a preceding “Is null” condition on [Test_Base_Object__c].Account__c. Because the criteria are set to require “All of the conditions are met (AND)”, if Condition 1 evaluates to false (indicating a null value in the lookup field), evaluation of conditions will immediately stop, and no exception will be thrown.

Note that this won’t work the same way using an OR condition. OR short-circuits on true values, and short-circuiting because of a null lookup is often not the desired behavior. In many cases, it’s easier to handle possible null relationships by using customized logic and nesting an AND with the above null-check within the OR. Constructing a formula may be more straightforward.

Formulas in Process Builder short-circuit in the same way, whether using the AND() and OR() functions or the && and || operators. The following pattern is safe.

Safe Process Builder Formula Pattern

Flow

sObject variables in Flow present a challenge. While sObject variables can be null, and cross-object field references that traverse a null variable will result in an exception being thrown, one cannot directly test an sObject variable’s nullity within a Flow formula. ISBLANK(sObjectVariable) and ISNULL(sObjectVariable) aren’t legal and will prevent your Flow from being activated.

There are a couple of ways to work around this limitation.

One is to check the sObject variable’s nullity using a Decision element before using any formulas that references its fields. (See the release notes on cross-object references in Flow). Unfortunately, this may not be practicable in a flow where formulas make complex decisions or calculate across a number of different objects (see my discussion of bridging Click & Pledge with Salesforce Campaigns for an example).

Another option, if the variable is populated using a lookup element from a given Id value, is to check the nullity of the Id value field in the formula that performs the cross-object reference. Like in Process Builder, logical functions in Flow formulas use short-circuit evaluation. This allows you to effectively guard cross-object references against nulls in the circumstance that the potentially-null sObject variable is looked up from an Id field, rather than other criteria.

Finally, as discussed in the Summer ‘14 release notes, you can provide a fault path. While this offers less of an opportunity to handle decision-making or conditional data within a single formula, it permits clearly expressing error- and null-handling within the logic of the flow itself.

Apex

In most cases, Apex handles null relationships in the same way Process Builder formulas do. However, there’s one variant case: the code below does not crash.

Good Apex Code

Accessing the relationship path directly from the queried object simply results in a null; no exception is thrown.

However, this only works when you traverse the relationship via the queried sObject. If the intermediate object value (which is null) is assigned to another variable before dereferencing its field, you get a NullPointerException.

Like Process Builder formulas, Apex supports short-circuit evaluation. The code below outputs null, null, and false before finally throwing an exception at line 10.

Bad Apex Code