Showing posts tagged with:

Lightning Experience

Salesforce Winter ’17 Platform Highlights

Once again regretfully it’s time to start talking about Winter; the Winter ’17 (v38.0) release that is. This (pre-Dreamforce ’16) release sees the return of a snowman logo (last seen in Winter ’10) – albeit rather glum looking on this occasion – despite the animated wink ;-); perhaps Dreamforce ’16 will cheer him/her up.

The new release is generally available now for pre-release preview. The preview release notes are available here.

The Winter ’17 sandbox preview starts early September 9th, with production orgs being upgraded late September/early October. Full details of the release schedule can be found on the trust.salesforce.com site.

images

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

App Launcher
Winter ’17 re-introduces Application as a primary navigation concept; with previous LEX versions Applications are essentially logical groupings of functions within the App Switcher but had no further relevance – Navigation Menus are defined at the Profile Level not per-Application. With Winter ’17 Custom Applications are defined as Lightning Applications, tabs are added as-per Salesforce Classic – so really the only difference is the position of the App Launcher (far-left of the tab bar) and its ability to open a tab directly. The new App Launcher can also be set as the default page.

Winter 17 - App Launcher

AccountContactRelation Person Account Compatibility
The Summer ’16 release introduced a standard object version of the junction object typically implemented between Account and Contact to support indirect relationships (where more detail is required than ContactRoles allows). Winter ’17 provides Person Account compatibility and also support for Apex Triggers and Validation Rules. This is great functionality; Person Accounts (in a primarily B2C context perhaps) can now reflect any Business Account relationships and vice-versa using standard features.

Lightning Component Actions
Lightning Components that implement the new force:LightningQuickAction or
force:LightningQuickActionWithoutHeader interfaces can be invoked from a Custom Action added to a Page Layout.

Winter 17 - Lightning Component Action

Winter 17 - Lightning Component Action 2

Spanning Relationships Limit Increase
The Spanning Relationships limit applies to declarative features such as formulas, workflow rules and validation rules and constrains the number of unique object references. Previously this limit was 10 but could be lifted to 15 via Salesforce Support. Hopefully the new value of 15 can also be increased as this particular limit can be serious scalability constraint particularly where the org customisation has made no effort in respect to conservation.

Invoke a Process from a Process
Invocable Processes allow one Process to call another Process; this allows a modular design approach to be taken to support reuse of processes as units of logical encapsulation.

Winter 17 - Invocable Process

Flows for Lightning Experience (Beta)
With this beta feature enabled URL-based flows can execute with the Lightning runtime rather than the Salesforce Classic runtime. It’s great to see Force.com Flow (or Visual Workflow) entering the Lightning era – automated conversion of existing Flows to Lightning Experience is a real benefit for implementations that have taken advantage of this incredibly powerful and much overlooked capability. Winter ’17 also provides the ability to embed flows within Lighting Pages (App, Record and Home pages).

Use SLDS in Lightning Apps
Lightning App definitions can now automatically reference the latest Salesforce Lightning Design System (SLDS) styles and design tokens. This is achieved by defining the application as extends=”force:slds”. This approach is recommended over the Static Resource approach where a particular version of the SLDS is statically referenced.

Automated Package Installation via API
ISVs are now able to automate managed package push upgrades via the API. This capability provides support for use cases such as customers accepting an upgrade offer sent via email or submitting a web form to request an upgrade. The Tooling API now supports automated upload of packages, upload status monitoring and the retrieval of installation Urls for distribution to subscribers – via the PackageUploadRequest object (and related).

Omni-Channel Routing for Chats (Beta)
A new Routing Type “Omni” enables Omni-Channel to route and prioritise Live Agent chats alongside other types of Work Item. Previously Live Agent chats were routed using skills and Agent availability only. As with Omni-Channel generally, this feature is Salesforce Classic only at this time.

Lightning Login
Passwords can be frustrating for users and a system administration nightmare. With Lightning Login user authentication can be achieved through a simple mobile-based approval. The user enters their username and clicks the Lightning Bolt, a notification is sent to the Salesforce Authenticator app (iOS and Android), user completes authentication in the app via Fingerprint or PIN. The feature works in both LEX and Salesforce Classic and is assigned to users via Permission Set.

Visualforce for Lightning Experience by Example

This post provides a code example for a multi-purpose Visualforce page that dynamically switches between Salesforce Classic and Lightning Experience (LEX) views based on the detected user setting.

At some future stage (perhaps Dreamforce ’16 will enlighten us here) the platform will likely provide support for auto-conversion of legacy Visualforce assets to the new Lightning UI. For those that can’t wait for this capability and require the means to build custom UI that blends with both Salesforce Classic and Lightning Experience the following example hopefully provides a useful reference. Please note, this is provided for information purposes only.

In conjunction with the example Visualforce markup, the following points should be considered:

1. For each interaction (page or sub-page) a decision should be taken as to the development approach:

CSS Switching: UI structure is consistent across views (and no page block components), therefore style class switching may work.

Conditional Visualforce: UI structure is partially consistent; perhaps the LEX view introduces a new activity timeline component for example but is otherwise closely aligned to the Salesforce Classic view. A lack of Lightning Component Framework expertise or availability may also lead to this approach.

Lightning Components: The LEX view is developed with the Lightning Component Framework; components are added to the Visualforce page via apex:includeLightning and conditionally rendered.

2. The Salesforce Lightning Design System is a CSS framework, not a JavaScript framework. In order to build UI with the SLDS requires working knowledge of a JavaScript library such as JQuery to add interactivity such as tab-switching, drop-down menu animation and tooltip display.

3. The ideal of a completely dynamic solution where certain users have the LEX view and others the Salesforce Classic view is somewhat spoiled by the constraint that application and tab icons must be shared between the two views. Predictably, icons that work well in the LEX Navigation Menu do not work at all in Salesforce Classic. Equally the square application icon style preferred in LEX looks visually jarring when rendered in the Salesforce Classic header.

4. LEX compatible pages should be responsive, Salesforce Classic pages rarely have this requirement.

5. Key Lightning Ready requirements include correct handling of the Navigation Menu expand/collqpse events, use of the Salesforce font and visual consistency with the SLDS guidelines.

The screenshots below show how the example page renders in both Salesforce Classic and LEX views.

Settings - Salesforce Classic View

Settings - LEX View

Example Visualforce page; note this example takes the “Conditional Visualforce” approach introduced above.

[sourcecode language=”html”]

<!–
Name: Settings.page (abbreviated)
Copyright © 2016 Force365
======================================================
======================================================
Purpose:
——-
Example Settings management page.

Multiple purpose page shared between LEX and Classic themes.
> Theme3—Salesforce Classic 2010 user interface theme
> Theme4d—Modern “Lightning Experience” Salesforce theme
> Theme4t—Salesforce1 mobile Salesforce theme

Functions :
View and maintain Application Settings, Target Objects and Data Sources.
======================================================
======================================================
History
——-
redacted.
–>
<apex:page tabStyle="Settings__tab"
controller="SettingsController"
readOnly="false"
title="{!$Label.UI_Text_MDM} {!$Label.UI_Text_Settings}"
docType="html-5.0">

<apex:form >

<!– Lightning Experience Theme [Theme4d] + Theme4t—Salesforce1 mobile Salesforce theme –>
<apex:outputPanel id="lexPanel" rendered="{!OR( $User.UIThemeDisplayed == ‘Theme4d’, $User.UIThemeDisplayed == ‘Theme4t’) }">

<apex:includeScript value="{!URLFOR($Resource.PrimaryResources, ‘/js/jquery-1.11.3.min.js’)}" />
<apex:stylesheet value="{!URLFOR($Resource.SLDS202, ‘assets/styles/salesforce-lightning-design-system-vf.css’)}" />

<script type=’text/javascript’>
var $j = jQuery.noConflict();

$j(document).ready(function(){
init();
});

function init(){
// tab hide/show content.
$j(‘.slds-tabs–scoped__item’).off(‘click’);

$j(‘.slds-tabs–scoped__item’).on(‘click’, function(){
$j(this).addClass(‘slds-active’);
$j(this).find(‘a’).attr(‘aria-selected’, true);
var contentToShow = $j(‘#’+$j(this).find(‘a’).attr(‘aria-controls’));

$j(contentToShow).removeClass(‘slds-hide’);
$j(contentToShow).addClass(‘slds-show’);

$j(this).siblings().removeClass(‘slds-active’);
$j(this).siblings().find(‘a’).attr(‘aria-selected’, false);
$j(contentToShow).siblings(‘.slds-tabs–scoped__content’).removeClass(‘slds-show’);
$j(contentToShow).siblings(‘.slds-tabs–scoped__content’).addClass(‘slds-hide’);
});

// data table menu drop downs.
$j(‘.slds-dropdown-trigger .slds-button’).off(‘click’);

$j(‘.slds-dropdown-trigger .slds-button’).on(‘click’, function(){

event.stopPropagation();

if ($j(this).parent().hasClass(‘slds-is-open’)){
$j(this).parent().removeClass(‘slds-is-open’);
$j(this).parent().attr(‘aria-expanded’, false);
} else {
$j(‘.slds-dropdown-trigger’).removeClass(‘slds-is-open’);
$j(‘.slds-dropdown-trigger’).attr(‘aria-expanded’, false);

$j(this).parent().addClass(‘slds-is-open’);
$j(this).parent().attr(‘aria-expanded’, true);
}
});

$j(document).click( function(){
$j(‘.slds-dropdown-trigger’).removeClass(‘slds-is-open’);
$j(‘.slds-dropdown-trigger’).attr(‘aria-expanded’, false);
});

// tooltip hide/show on hover over information icons related to form elements.
$j( ‘.slds-form-element__icon’ ).hover(
function() {

var tooltip = $j(this).find(‘.slds-popover’);
tooltip.css( { position:’absolute’,left:’0px’,margin:’-1.5rem’,width:’20rem’ } );

var t = $j(tooltip).outerHeight()-15;
t*=-1;
tooltip.css( { top:t } );

tooltip.removeClass(‘slds-hide’);
tooltip.addClass(‘slds-show’);
}, function() {

var tooltip = $j(this).find(‘.slds-popover’);
tooltip.removeClass(‘slds-show’);
tooltip.addClass(‘slds-hide’);
}
);
}

function showSpinner(){
$j(‘.slds-spinner_container’).removeClass(‘slds-hide’);
$j(‘.slds-spinner_container’).addClass(‘slds-show’);

var winWidth = $j(document).width();
var winHeight = $j(document).height();

$j(‘.slds-spinner_container’).css({ ‘width’: winWidth,’height’: winHeight });
}

function hideSpinner(){
$j(‘.slds-spinner_container’).removeClass(‘slds-show’);
$j(‘.slds-spinner_container’).addClass(‘slds-hide’);
}

</script>

<style>
.myapp-icon-container-background-color {
background-color: #73BFB3;
}
.myapp-column–overflow-visible {
overflow: visible !important;
}
html body.sfdcBody {
padding: 0 !important;
}
</style>

<!– custom scope wrapper –>
<div class="myapp">

<div class="slds-spinner_container slds-hide">
<div class="slds-spinner–brand slds-spinner slds-spinner–medium" aria-hidden="false" role="alert">
<div class="slds-spinner__dot-a"></div>
<div class="slds-spinner__dot-b"></div>
</div>
</div>

<!– header –>
<div class="slds-page-header" role="banner">
<div class="slds-media slds-media–center">
<div class="slds-media__figure">
<span class="slds-icon_container myapp-icon-container-background-color">
<img class="slds-icon" src="{!URLFOR($Resource.myapp__IconResources, ‘Settings-icons_white-01.png’)}" alt="portrait" />
</span>
</div>
<div class="slds-media__body">
<p class="slds-text-body–small page-header__info">{!$Label.UI_Text_MDM}</p>
<p class="slds-page-header__title slds-truncate slds-align-middle" title="{!$Label.UI_Text_Settings}">{!$Label.UI_Text_Settings}</p>
</div>
</div>
</div>
<!– / header –>

<!– content –>
<apex:outputPanel id="missingStandardPermissionsPanelLEX" rendered="{!NOT(hasStandardPermissions)}">
<div class="slds-notify slds-notify–alert slds-theme–error" role="alert">
<span class="slds-assistive-text">Info</span>
<h2>{!$Label.UI_Error_No_Customize_Application_Permissions}</h2>
</div>
</apex:outputPanel>

<apex:outputPanel id="noCustomPermissionPanelLEX" rendered="{!AND(hasStandardPermissions,NOT($Permission.Manage_Settings))}">
<div class="slds-notify slds-notify–alert slds-theme–error" role="alert">
<span class="slds-assistive-text">Info</span>
<h2>{!$Label.UI_Error_No_Manage_Settings_Permission}</h2>
</div>
</apex:outputPanel>

<apex:outputPanel id="msgsRefreshPanelLEX">
<span class="slds-assistive-text">Info</span>
<apex:Messages id="msgsLEX" styleclass="slds-notify slds-notify–alert slds-theme–error" html-role="alert"/>
</apex:outputPanel>

<apex:outputPanel id="mainPanelLEX" rendered="{!AND(showMainPanel,$Permission.Manage_Settings,hasStandardPermissions)}">

<div class="slds-tabs–scoped">
<ul class="slds-tabs–scoped__nav" role="tablist">
<li class="slds-tabs–scoped__item slds-text-heading–label {!IF(selectedTab==’applicationSettings’, ‘slds-active’, ”)}" title="{!$Label.UI_Text_Application_Settings}" role="presentation">
<a class="slds-tabs–scoped__link" href="javascript:void(0);" role="tab" tabindex="0" aria-selected="true" aria-controls="tab-scoped-1" id="tab-scoped-1__item">{!$Label.UI_Text_Application_Settings}</a>
</li>
<li class="slds-tabs–scoped__item slds-text-heading–label {!IF(selectedTab==’targetObjects’, ‘slds-active’, ”)} {!IF(appSettings.IsActive__c, ‘slds-show’, ‘slds-hide’)}" title="{!$Label.UI_Text_Target_Objects}" role="presentation">
<a class="slds-tabs–scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-2" id="tab-scoped-2__item">{!$Label.UI_Text_Target_Objects}</a>
</li>
<li class="slds-tabs–scoped__item slds-text-heading–label {!IF(selectedTab==’dataSources’, ‘slds-active’, ”)} {!IF(appSettings.IsActive__c, ‘slds-show’, ‘slds-hide’)}" title="{!$Label.UI_Text_Data_Sources}" role="presentation">
<a class="slds-tabs–scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-3" id="tab-scoped-3__item">{!$Label.UI_Text_Data_Sources}</a>
</li>
<li class="slds-tabs–scoped__item slds-text-heading–label {!IF(selectedTab==’dynamicHierarchies’, ‘slds-active’, ”)} {!IF(appSettings.IsActive__c, ‘slds-show’, ‘slds-hide’)}" title="{!$Label.UI_Text_Dynamic_Hierarchies}" role="presentation">
<a class="slds-tabs–scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-4" id="tab-scoped-4__item">{!$Label.UI_Text_Dynamic_Hierarchies}</a>
</li>
<li class="slds-tabs–scoped__item slds-text-heading–label {!IF(selectedTab==’customRollups’, ‘slds-active’, ”)} {!IF(appSettings.IsActive__c, ‘slds-show’, ‘slds-hide’)}" title="{!$Label.UI_Text_Custom_Rollups}" role="presentation">
<a class="slds-tabs–scoped__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-scoped-5" id="tab-scoped-5__item">{!$Label.UI_Text_Custom_Rollups}</a>
</li>
</ul>
<div id="tab-scoped-1" class="slds-tabs–scoped__content {!IF(selectedTab==’applicationSettings’, ‘slds-show’, ‘slds-hide’)}" role="tabpanel" aria-labelledby="tab-scoped-1__item">

<apex:outputPanel layout="block" id="applicationSettingsRefreshPanelLEX">

<h3 class="slds-section-title–divider">{!$Label.UI_Text_Application_Settings}</h3>
<div class="slds-m-vertical–small"></div>
<fieldset class="slds-form–compound">
<div class="form-element__group">
<div class="slds-form-element__row">
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!appSettings.IsActive__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.IsActive__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-IsActive" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.IsActive__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!appSettings.TriggersActive__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.TriggersActive__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-TriggersActive" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.TriggersActive__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
</div>
<div class="slds-form-element__row">
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!appSettings.IsHierarchiesActive__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.IsHierarchiesActive__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-IsHierarchiesActive" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.IsHierarchiesActive__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!appSettings.PersistNoMatches__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.PersistNoMatches__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-PersistNoMatches" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.PersistNoMatches__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
</div>
<div class="slds-form-element__row">
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!appSettings.ShowHelpCaptions__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.ApplicationSettings__c.fields.ShowHelpCaptions__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-ShowHelpCaptions" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.ShowHelpCaptions__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-checkbox">
<apex:inputCheckbox value="{!sysSettings.DisplayOnSettingsPage__c}" />
<span class="slds-checkbox–faux"></span>
<span class="slds-form-element__label slds-truncate">{!$ObjectType.SystemSettings__c.fields.DisplayOnSettingsPage__c.Label}</span>
</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-DisplayOnSettingsPage" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.SystemSettings__c.fields.DisplayOnSettingsPage__c.inlineHelpText}</p>
</div>
</div>
</div>
</div>
</div>
<div class="slds-form-element__row">
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-form-element__label slds-truncate" for="select-AuditLogLevel">{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.Label}</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-AuditLogLevel" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}</p>
</div>
</div>
</div>
<div class="slds-form-element__control slds-size–1-of-1 slds-small-size–1-of-1 slds-medium-size–2-of-3 slds-large-size–1-of-2">
<div class="slds-select_container">
<apex:selectList id="select-AuditLogLevel"
styleClass="slds-select"
value="{!selectedAuditLogLevel}"
multiselect="false"
size="1"
title="{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}">
<apex:selectOptions value="{!auditLogLevelOptions}"/>
</apex:selectList>
</div>
</div>
</div>
<div class="slds-form-element slds-size–1-of-2"></div>
</div>
<div class="slds-form-element__row">
<div class="slds-form-element slds-size–1-of-2">

<label class="slds-form-element__label slds-truncate" for="text-MaxJobsDisplayDays">{!$ObjectType.ApplicationSettings__c.fields.MaxJobsDisplayDays__c.Label}</label>
<div class="slds-form-element__icon">
<a href="javascript:void(0);">
<svg aria-hidden="true" class="slds-icon slds-icon–x-small slds-icon-text-default">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#info’)}" />
</svg>
<span class="slds-assistive-text">Help</span>
</a>
<div id="help-MaxJobsDisplayDays" class="slds-popover slds-popover–tooltip slds-nubbin–bottom-left slds-hide" role="tooltip" aria-live="polite">
<div class="slds-popover__body slds-text-longform">
<p>{!$ObjectType.ApplicationSettings__c.fields.MaxJobsDisplayDays__c.inlineHelpText}</p>
</div>
</div>
</div>
<div class="slds-form-element__control slds-size–1-of-1 slds-small-size–1-of-1 slds-medium-size–2-of-3 slds-large-size–1-of-2">
<apex:inputText id="text-MaxJobsDisplayDays" styleClass="slds-input" value="{!appSettings.MaxJobsDisplayDays__c}" html-type="number" />
</div>
</div>
<div class="slds-form-element slds-size–1-of-2"></div>
</div>
</div>
</fieldset>

<div class="slds-m-vertical–small"></div>
</apex:outputPanel> <!– application settings refresh panel –>

<apex:commandButton id="saveButtonLEX"
action="{!saveApplicationSettingsAction}"
value="{!$Label.UI_Button_Label_Save}"
rerender="mainPanelLEX,msgsRefreshPanelLEX"
onclick="showSpinner();"
oncomplete="init();hideSpinner();"
styleclass="slds-button slds-button–neutral" />

<div class="slds-m-vertical–large"></div>
</div>
<div id="tab-scoped-2" class="slds-tabs–scoped__content {!IF(selectedTab==’targetObjects’, ‘slds-show’, ‘slds-hide’)}" role="tabpanel" aria-labelledby="tab-scoped-2__item">

<apex:outputPanel id="targetObjectsRefreshPanelLEX">
<apex:outputPanel rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}">
<div class="slds-notify slds-notify–alert slds-theme–alert-texture" role="alert">
<span class="slds-assistive-text">Info</span>
<h2>{!$Label.UI_Help_Caption_Target_Object_Settings}</h2>
</div>
</apex:outputPanel>

<apex:dataTable value="{!targetObjects}" var="to" headerClass="slds-text-heading–label slds-truncate"
styleClass="slds-table slds-table–bordered slds-max-medium-table–stacked-horizontal slds-no-row-hover slds-table–striped"
columnClasses="slds-cell-wrap slds-truncate" >
<apex:column value="{!to.targetObjectLabel}" headerValue="{!$Label.UI_Column_Header_Setting_Name}" html-data-label="{!$Label.UI_Column_Header_Setting_Name}"/>

<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Normalisation}" html-data-label="{!$Label.UI_Column_Header_Active_For_Normalisation}">
<apex:inputCheckbox value="{!to.isActiveForNormalisation}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Matching}" html-data-label="{!$Label.UI_Column_Header_Active_For_Matching}">
<apex:inputCheckbox value="{!to.isActiveForMatching}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Merge}" html-data-label="{!$Label.UI_Column_Header_Active_For_Merge}">
<apex:inputCheckbox value="{!to.isActiveForMerge}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Conversion}" html-data-label="{!$Label.UI_Column_Header_Active_For_Conversion}">
<apex:inputCheckbox value="{!to.isActiveForConversion}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Reparenting}" html-data-label="{!$Label.UI_Column_Header_Active_For_Reparenting}">
<apex:inputCheckbox value="{!to.isActiveForReparenting}" disabled="true" />
</apex:column>
<apex:column html-data-label="{!$Label.UI_Text_Action}" styleClass="myapp-column–overflow-visible">

<div class="slds-dropdown-trigger slds-dropdown-trigger–click" aria-expanded="false">
<button type="button" class="slds-button slds-button–icon-border-filled" aria-haspopup="true">
<svg aria-hidden="true" class="slds-button__icon">
<use xmlns:xlink="http://www.w3.org/1999/xlink&quot; xlink:href="{!URLFOR($Resource.myapp__SLDS202, ‘/assets/icons/utility-sprite/svg/symbols.svg#down’)}" />
</svg>
<span class="slds-assistive-text">{!$Label.UI_Text_Actions}</span>
</button>
<div class="slds-dropdown slds-dropdown–right">
<ul class="slds-dropdown__list" role="menu">

<apex:outputPanel rendered="{!hasTargetObjectPageAccess}">
<li class="slds-dropdown__item">
<apex:commandLink style="color:#0070d2;" action="{!editTargetObjectAction}" value="{!$Label.UI_Text_Edit}" id="targetObjectEditCommandLinkLEX" styleclass="slds-truncate" html-role="menuitem">
<apex:param name="s" value="{!to.targetObjectName}"/>
</apex:commandLink>
</li>
</apex:outputPanel>

<li class="slds-dropdown__item">
<apex:commandLink style="color:#0070d2;"
action="{!deleteTargetObjectAction}"
rerender="targetObjectsRefreshPanelLEX,msgsRefreshPanelLEX"
value="{!$Label.UI_Text_Del}"
id="targetObjectDeleteCommandLinkLEX"
onclick="if(!confirm(‘{!$Label.UI_Text_Confirmation_Proceed_To_Delete}’)){return};"
oncomplete="init();" >
<apex:param name="s" value="{!to.targetObjectName}"/>
</apex:commandLink>
</li>

</ul>
</div>
</div>
</apex:column>

</apex:dataTable>

<!– if no target objects show columns headers and line "No results" beneath –>
<apex:outputPanel rendered="{!IF(NOT(hasTargetObjects),true,false)}">
<div class="slds-text-heading–label-normal slds-text-align–center slds-m-vertical–xx-large">{!$Label.UI_Text_No_Items_To_Display}</div>
</apex:outputPanel>

<div class="slds-m-vertical–small"></div>
<apex:actionFunction name="addTargetObject" action="{!addTargetObjectAction}" />
<button class="slds-button slds-button–neutral" onclick="addTargetObject()">{!$Label.UI_Button_Label_Add}</button>
<div class="slds-m-vertical–large"></div>

</apex:outputPanel> <!– targetObjectsRefreshPanelLEX –>
</div>
<div id="tab-scoped-3" class="slds-tabs–scoped__content {!IF(selectedTab==’dataSources’, ‘slds-show’, ‘slds-hide’)}" role="tabpanel" aria-labelledby="tab-scoped-3__item">
</div>
<div id="tab-scoped-4" class="slds-tabs–scoped__content {!IF(selectedTab==’dynamicHierarchies’, ‘slds-show’, ‘slds-hide’)}" role="tabpanel" aria-labelledby="tab-scoped-4__item">
</div>
<div id="tab-scoped-5" class="slds-tabs–scoped__content {!IF(selectedTab==’customRollups’, ‘slds-show’, ‘slds-hide’)}" role="tabpanel" aria-labelledby="tab-scoped-5__item">
</div>
</div>
<div class="slds-m-vertical–large"></div>

</apex:outputPanel>
<!– / content –>

<!– footer –>
<!– / footer –>

</div> <!– custom scope wrapper –>

</apex:outputPanel> <!– Theme4d/Theme4t –>

<!– Salesforce Classic Theme [Theme3] –>
<apex:outputPanel rendered="{!AND( $User.UIThemeDisplayed <> ‘Theme4d’, $User.UIThemeDisplayed <> ‘Theme4t’) }">

<style>
.activeTab { background-color: #F1F1F1; color:black; background-image:none; height:22px; font-family:Arial,​Helvetica,​sans-serif; font-size:12px }
.inactiveTab { background-color: #C0C0C0; color:black; background-image:none; height:22px; font-family:Arial,​Helvetica,​sans-serif; font-size:12px }
</style>

<apex:sectionHeader subtitle="{!$Label.UI_Text_Settings}" title="{!$Label.UI_Text_MDM}" />

<apex:outputPanel id="missingStandardPermissionsPanel" rendered="{!NOT(hasStandardPermissions)}">
<apex:pageMessage summary="{!$Label.UI_Error_No_Customize_Application_Permissions}" severity="warning" strength="1" />
</apex:outputPanel>

<apex:outputPanel id="noCustomPermissionPanel" rendered="{!AND(hasStandardPermissions,NOT($Permission.Manage_Settings))}">
<apex:pageMessage summary="{!$Label.UI_Error_No_Manage_Settings_Permission}" severity="warning" strength="1" />
</apex:outputPanel>

<apex:outputPanel id="msgsPanel">
<apex:pageMessages id="msgs" />
</apex:outputPanel>

<apex:outputPanel id="mainPanel" rendered="{!AND(showMainPanel,$Permission.Manage_Settings,hasStandardPermissions)}">
<apex:tabPanel switchType="client" value="{!selectedTab}" id="mainTabPanel" tabClass="activeTab" inactiveTabClass="inactiveTab">
<apex:tab label="{!$Label.UI_Text_Application_Settings}" name="applicationSettings" id="applicationSettingsTab">

<div style="position: relative; min-height:400px; height:auto !important; height:400px;">
<apex:actionstatus id="pageActionStatus">
<apex:facet name="start">
<div class="waitingSearchDiv" id="loaderDiv" style="background-color: #fbfbfb; height: 100%;opacity:0.65;width:100%;">
<div class="waitingHolder" style="top: 74.2px; width: 91px;">
<img class="waitingImage" src="/img/loading.gif" title="Please Wait…" />
<span class="waitingDescription">{!$Label.UI_Text_Saving}</span>
</div>
</div>
</apex:facet>
<apex:facet name="stop"></apex:facet>
</apex:actionstatus>

<apex:pageBlock id="applicationSettingsPageBlock" title="{!$Label.UI_Text_Application_Settings}" mode="edit" >

<apex:pageBlockButtons >
<apex:commandButton id="saveButton"
action="{!saveApplicationSettingsAction}"
value="{!$Label.UI_Button_Label_Save}"
rerender="mainPanel"
status="pageActionStatus"/>
</apex:pageBlockButtons>

<apex:pageMessage summary="{!$Label.UI_Help_Caption_Application_Settings}" severity="info" strength="1" rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}" />

<apex:pageBlockSection showHeader="true" columns="2" collapsible="false" title="{!$Label.UI_Text_Application_Settings}">
<apex:inputField value="{!appSettings.IsActive__c}" />
<apex:inputField value="{!appSettings.TriggersActive__c}" />
<apex:inputField value="{!appSettings.IsHierarchiesActive__c}" />
<apex:inputField value="{!appSettings.PersistNoMatches__c}" />
<apex:inputField value="{!appSettings.ShowHelpCaptions__c}" />
<apex:pageBlockSectionItem />

<apex:pageBlockSectionItem >
<apex:outputLabel value="{!$Label.UI_Text_Audit_Log_Level}" for="auditLogLevelSelectList" />
<apex:selectList id="auditLogLevelSelectList"
value="{!selectedAuditLogLevel}"
multiselect="false"
size="1"
style="width:100px"
title="{!$ObjectType.ApplicationSettings__c.fields.AuditLogLevel__c.inlineHelpText}">
<apex:selectOptions value="{!auditLogLevelOptions}"/>
</apex:selectList>
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem />

<apex:inputField value="{!appSettings.MaxJobsDisplayDays__c}" />
<apex:inputField value="{!appSettings.MaxSearchResultsPerDataSource__c}" />
<apex:inputField value="{!appSettings.SearchTermMinimumCharacterCount__c}" />
<apex:inputField value="{!appSettings.DefaultFuzzyMatchThresholdPercentage__c}" />
<apex:pageBlockSectionItem />
<apex:pageBlockSectionItem />

<apex:inputField value="{!appSettings.MaxRecordsPerDataExport__c}" />
<apex:pageBlockSectionItem />
<apex:inputField value="{!appSettings.MaxChildRecordSearchResults__c}" />
<apex:pageBlockSectionItem />

<apex:outputField value="{!appSettings.NamespaceApexPrefix__c}" />
<apex:outputField value="{!appSettings.NamespaceComponentPrefix__c}" />
<apex:outputField value="{!appSettings.NamespacePrefix__c}" />
</apex:pageBlockSection>

</apex:pageBlock>
</div>
</apex:tab>

<apex:tab label="{!$Label.UI_Text_Target_Objects}" name="targetObjects" id="targetObjectsTab" rendered="{!$Setup.ApplicationSettings__c.IsActive__c}">
<apex:outputPanel id="targetObjectsRefreshPanel">
<apex:pageBlock id="targetObjectsPageBlock" title="{!$Label.UI_Text_Target_Object_Settings}" mode="edit" >
<apex:pageBlockButtons >
<apex:commandButton id="addTargetObjectButton" action="{!addTargetObjectAction}" value="{!$Label.UI_Button_Label_Add}" rendered="{!hasTargetObjectPageAccess}" />
</apex:pageBlockButtons>

<apex:pageMessage summary="{!$Label.UI_Help_Caption_Target_Object_Settings}" severity="info" strength="1" rendered="{!$Setup.ApplicationSettings__c.ShowHelpCaptions__c}" />

<apex:pageBlockTable value="{!targetObjects}" var="to" columnsWidth="15px,20%,15%,15%,15%,15%,15%" >
<apex:column headerValue="{!$Label.UI_Column_Header_Action}">
<apex:commandLink action="{!editTargetObjectAction}" value="{!$Label.UI_Text_Edit}" id="targetObjectEditCommandLink" style="color:#015BA7;" rendered="{!hasTargetObjectPageAccess}">
<apex:param name="s" value="{!to.targetObjectName}"/>
</apex:commandLink>
<apex:commandLink action="{!deleteTargetObjectAction}" rerender="targetObjectsRefreshPanel,msgsPanel" value="{!$Label.UI_Text_Del}" id="targetObjectDeleteCommandLink" style="padding-left: 10px; color:#015BA7;" onclick="if(!confirm(‘{!$Label.UI_Text_Confirmation_Proceed_To_Delete}’)){return};" >
<apex:param name="s" value="{!to.targetObjectName}"/>
</apex:commandLink>
</apex:column>
<apex:column value="{!to.targetObjectLabel}" headerValue="{!$Label.UI_Column_Header_Setting_Name}" />
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Normalisation}">
<apex:inputCheckbox value="{!to.isActiveForNormalisation}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Matching}">
<apex:inputCheckbox value="{!to.isActiveForMatching}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Merge}">
<apex:inputCheckbox value="{!to.isActiveForMerge}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Conversion}">
<apex:inputCheckbox value="{!to.isActiveForConversion}" disabled="true" />
</apex:column>
<apex:column headerValue="{!$Label.UI_Column_Header_Active_For_Reparenting}">
<apex:inputCheckbox value="{!to.isActiveForReparenting}" disabled="true" />
</apex:column>

</apex:pageBlockTable>

<!– if no target objects show columns headers and line "No results" beneath –>
<apex:outputPanel rendered="{!IF(NOT(hasTargetObjects),true,false)}">
<div style="padding: 5px; color: #808080; font: 11px Arial,Helvetica,sans-serif;">{!$Label.UI_Text_No_Search_Results}</div>
</apex:outputPanel>

</apex:pageBlock>
</apex:outputPanel>
</apex:tab>

<apex:tab label="{!$Label.UI_Text_Data_Sources}" name="dataSources" id="datasourcesTab" rendered="{!$Setup.ApplicationSettings__c.IsActive__c}">
</apex:tab>

<apex:tab label="{!$Label.UI_Text_Dynamic_Hierarchies}" name="dynamicHierarchies" id="dynamicHierachiesTab" rendered="{!AND($Setup.ApplicationSettings__c.IsActive__c, $Setup.ApplicationSettings__c.IsHierarchiesActive__c)}">
</apex:tab>

<apex:tab label="{!$Label.UI_Text_Custom_Rollups}" name="customRollups" id="customRollupsTab" rendered="{!AND($Setup.ApplicationSettings__c.IsActive__c)}">
</apex:tab>
</apex:tabPanel>
</apex:outputPanel> <!– mainPanel –>

</apex:outputPanel> <!– NOT Theme4d/Theme4t = Salesforce Classic –>

</apex:form>

</apex:page>

[/sourcecode]

Salesforce Summer ’16 Platform Highlights

Thankfully it’s time to start thinking Summer; the Summer ’16 (v37.0) release that is. Sporting a cheery (but perhaps more Autumnal than Summer) fireworks logo, the new release is available now for partner preview. The release notes are generally available here.

The Summer ’16 sandbox preview starts early May (7th/8th), with production orgs being upgraded early June. Full details of the release schedule are listed in this official blog post.

As expected Summer ’16 is an evolutionary release not revolutionary and continues the trend of Lighting Experience consolidation. As well as closing the gap on Classic functionality, a number of interesting net-new LEX features have been added.

_16__1_

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

Sandbox-to-Sandbox Cloning
Recent releases have provided some interesting enhancements in the sandbox area (post refresh scripts, increased edition allowances etc.), Summer ’16 build on such improvements with a new function that allows a sandbox to be created as a clone of another sandbox (as opposed to a production org). Superficially this sounds like a useful capability; on further thought however this could have a significant impact on development process, allowing QA sandboxes to be cloned as copies of development at the end of sprint (as just one example). Any uni-lateral sandbox-to-sandbox deployments could theoretically be replaced with a clone. Multiple development sandboxes converging into a single upstream org would be the exception. Cloning is also supported by the Tooling API, enabling full automation of environment management. I’ve been unable test this feature as sandbox copy doesn’t appear to be enabled in pre-release orgs, however it would appear that data can be included in the clone. How data copy works between the different sandbox types is yet to be seen.

Summer 16 - Sandbox Clone

Force.com Flow REST API (Pilot)
New resources have been added to describe Flows and to manipulate Flow Interviews. The intent of this will be to enable the development of fully customised user experiences for Flow. Whilst possible to some degree via CSS, the user interface for Flow is difficult to customise in respect to branding, fine-grained control over layout, responsive behaviour etc.. The new API resources allow development of a Flow user interface in any technology that can manipulate a REST API.

Custom Metadata Types – Relationship Fields (Pilot)
CMT now support relationship fields that reference other CMT, custom objects or one of the core CRM standard objects (Account, Contact, Lead, Opportunity, Case). Previously pseudo-relationship fields were required of the text type that held the Name or id of the parent record; the new functionality is clearly a big improvement on this.

Apex – Populated SObject Field Map
As a programming convenience it is now possible to get a map of the fields populated in memory for an SObject instance. Previously, trial-and-error coding approaches have been required to identify whether particular fields are populated; this is prevalent in dynamic code and prone to error. Useful simplifications to the Apex language are always good news.

Lightning Components LockerService
LockerService introduces a new security architecture for Lightning Components comprised of technologies and techniques. Summer ’16 introduces LockerService in the form of two critical updates for existing orgs; one for internal components, the other for communities. New orgs will be auto-enabled by default; Winter ’17 will see all orgs auto-enabled. In short LockerService provides component namespace isolation in respect to DOM access and JS visibility, additionally only public documented Lightning JS APIs can be accessed. There’s considerably more to LockerService than this description covers, the link below provides more detail.

https://developer.salesforce.com/blogs/developer-relations/2016/04/introducing-lockerservice-lightning-components.html

Shield Platform Encryption
Shield Platform Encryption now supports Custom Date Fields and compatible fields within Managed Packages. The breadth of platform features that work with the shield data encryption-at-rest technology has also been extended; the new vertical clouds, Organisation Sync and Salesforce-to-Salesforce being interesting examples.

Create a Calendar from Standard/Custom Object Data
Personal Calendar views can now be created directly from data held in Standard or Custom objects. The calendar configuration requires that fields are specified for event start and end dates and also the name. There are currently limits to the number of events that can be viewed and also no means to share or subscribe to a calendar view of this type. Hopefully the next release will support public calendars of this type; the requirement for this style of data presentation is very common.

Summer 16 - Calendar

Process Builder – Execute Multiple Action Groups
Summer ’16 sees further investment in Process Builder which is great news for implementation practitioners using the process automation capabilities of the platform. Previously a process execution (or Flow interview behind the scenes) applied the logic of a single action groups and stopped. With Summer ’16 action groups can be configured with finish behaviour that continues to to evaluate the next action group in sequence. As such multiple logical conditions can be evaluated and acted upon within a single process execution. This new extension will help reduce the number of processes necessary to deliver even simple business process automations and as such is great maintainability improvement.

User Switcher
Regardless of role, most Salesforce users require access to multiple orgs at some stage; Summer ’16 introduces the convenience of a user switcher located on the drop-down menu accessed from the profile picture in Lightning Experience. Adding a user name provides one-click access from this menu to the org. Streamlined cross-org navigation has been long outstanding; beyond the obvious sandbox access use cases, in my experience the level of multiple-org access continues to increase over time.

Summer 16 - User Switcher

Associate Contacts to Multiple Accounts
The Contact to Account relationship is now extended to support one direct relationship plus multiple indirect relationships. A new junction object [AccountContactRelation] provides a standard Roles picklist, plus the ability to add Custom Fields. The inability to customise Contact Roles historically has resulted in the prevalence of custom approaches to Account to Contact relationship modelling. Note, it doesn’t appear possible to add process automation to the new object.

Summer 16 - AccountContactRelation 2

Summer 16 - AccountContactRelation 1

Enhanced Email
Emails sent from the Lightning Email Composer are now recorded as strongly-defined Email records rather than Tasks. A new Email standard object has been added which supports workflow, custom fields, layouts, triggers etc. Email records can be associated with multiple contacts, leads and person accounts and a single account, opportunity etc. Enhanced Email will be enabled by default where Email to Case is not in use. Representing emails as Tasks has never made complete sense; with Enhanced Email, Email records can be used as the basis for business process. Note, workflow on the Email object is limited to updating Case fields, this will limit the applicable use cases for this new functionality.

Salesforce Spring ’16 Platform Highlights

There’s no better way to kick-start the New Year than to indulge in a bit of release-note exploration for the upcoming Spring release. This exercise is best performed with the latest release notes to hand, a brand new pre-release org to play with and the previous release certification exams safely completed. The links below are provided for convenience.

Spring ’16 Pre-release Sign-up
Spring ’16 Release Notes

The rollout dates for the primary production instances are the 6th (2nd release weekend), 12th and 13th February (final release weekend); specific instance dates are stated on the trust.salesforce.com site.

As expected the Spring ’16 release is focused on consolidation and enhancement of Lightning Experience; this feature set feels substantially more enterprise-ready as the intermittent performance and stability issues observed previously appear to be addressed and the feature gap from Salesforce Classic has been diminished in key areas such as reporting. A robust Lightning Experience that can be released to business users with confidence can’t come soon enough for many, although with features gaps remaining and Person Account compatibility at Beta status the Summer release may present a more realistic timeline.

Butterfly16

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

Developer Sandbox Limits Increase
All editions that include sandbox licenses have significantly increased limits. Enterprise Edition customers now get access to 25 developer sandboxes instead of 1. I had to read this a few times to take the news in. The single sandbox constraint for EE customers has been a challenge for many implementations trying to adopt development lifecycle best-practices or to simply isolate testing from development or UAT or indeed develop multiple projects in parallel. The new limit will provide a significant level of flexibility here and promote a standard-based approach, I hope.

Post Sandbox Copy Script
A new Apex interface (SandboxPostCopy) enables Apex script to be executed automatically post Sandbox copy operation (create or refresh). In a similar fashion to the PostInstallScript interface used by ISV to apply configuration steps and data creation tasks following package installation the new interface should help prepare a sandbox using a standardised, validated and automated approach.

Lightning Experience – Person Account Compatibility (Beta)
Person Account compatibility is highly anticipated by all B2C implementations unable to consider Lighting Experience otherwise. With the Spring ’16 release a beta status compatibility is provided which at least provides an ability to test and explore Lightning Experience in a sandbox setting.

Lightning Experience – UI Enhancements
List View filters can now be edited on-the-fly and record detail pages support inline editing. Both features providing enhancement to the general user experience. The ability to view embedded charts and to manipulate filters on the List View page is a real improvement in this area. Inline editing simply reinstates a capability taken for granted by most. It is also possible to define custom navigation menus and assign by User Profile to deliver a customised view to different users.

Lightning Experience – Reporting
The reporting feature gap between Lighting Experience and Salesforce Classic prior to Spring ’16 made for difficult reading. The new release establishes some degree of feature parity with Dashboard Filters, Dynamic Dashboards, Dashboard table components and the ability to view record details on Matrix Reports all making a welcome return.

Lightning Experience – Detect User Experience
Last point in relation to Lightning Experience; support is now provided for Apex script to reliably detect the current user experience, i.e. Salesforce1, Lightning Experience, Salesforce Classic. New Apex methods are available (User.UITheme and UserInfo.getUiTheme()) that provide a standardised approach that replaces the previous use of the sforce.one JavaScript global (and its unsupported approach caveat).

Files Connect for Box (Pilot)
Files held on the Box cloud are now accessible directly in Salesforce via the Files Connect feature. Given how commonplace it is that Salesforce and Box are used in concert a standardised approach is highly convenient particularly where support extends to on-premise data sources (Sharepoint etc.).

Custom Metadata Types
Custom Metadata Types have been enhanced to support bulk creation scenarios and the upsert operation. Picklist fields are also supported, although this a beta status feature for Spring ’16. CMT provide the basis for a variety of platform-on-a-platform use cases or simply convenient application configuration management. Continued investment in this area is great news for the developer community.

Apex Test Suites
As a long-time advocate of a structured approach to Apex Unit Test classes the new Test Suite features is an excellent introduction. In short Test Classes can be arbitrarily grouped in the context of a parent Test Suite label, the suite itself can then be selected at the time of test execution from the New Suite run option. Test Suite definition and invocation takes place in the Developer Console.

Developer Console - New Suite Run

Apex Unit Tests
New developers writing Apex Unit tests have suffered for years with the platform constraint that setup and non-setup objects can’t be created in the same Apex transaction (Mixed DML Operation Error). Typically this is problematic where User records are created in the test context alongside test records such as Accounts etc. With Spring ’16 it is now possible to create the setup object via @future method. A second improvement in context is the ability to change record creation date field values using the System.Test.setCreatedDate method. Where record processing logic is temporal in nature this ability will be helpful in writing tests that correctly validate the code logic.

Lightning Out (Beta)
Lightning Out provides the capability to securely embed Lighting Components into a remote application running on any external platform or container. This YouTube link provides a great introduction to the power and potential of Lightning Out.

Platform Security Health Check
Spring ’16 provides an interesting new security Health Check feature that enables the current org configuration to be compared against a Salesforce recommended baseline. Any feature that highlights security risk or vulnerability is positive addition and should help mitigate against complacency.

Platform Health Check

Salesforce Winter ’16 Platform Highlights

With September upon us and Dreamforce 2015 on the near horizon it’s time to dispel thoughts of sunshine and holidays and get focused on the upcoming Winter release. Whilst it’s undoubtedly depressing to be reading the word Winter in early September, there’s plenty in the new release to raise the spirits. It’s also clear that with significant platform changes emerging, i.e. Lighting Experience, this is an important time to be on the front-foot in terms of keeping personal platform expertise up-to-date. For Salesforce professionals or engaged users, the Winter ’16 release presents a great opportunity to reset your knowledge of the platform.

Pre-release sign-up appears to be offline at present, however existing pre-release orgs should be updated and available to explore. A preview set of release notes can be downloaded from here. The rollout window for production instances is between the 25th September and the 17th October, specific instance dates are stated on the trust.salesforce.com site.

winter release

This post briefly outlines selected highlights related to the Force.com platform (in no order of significance).

– features are GA if not indicated otherwise

Lightning Experience
As the marketing literature states; a new, modern, intelligent user experience – or, in other words the new Salesforce.

Lighting Experience Record Detail Page

With Winter ’16 we’re entering a new transitional era for the platform with features migrating from the Salesforce Classic UI (Aloha) across to Salesforce Lightning Experience presumably over the course of a series of releases. This journey starts with a focused Sales Cloud offering. The updated user interface looks like a significant advancement, however many users will need to navigate both interfaces which may degrade the overall user experience. It should be noted that access to the Lighting Experience desktop experience is permission based (profile or permission set) and therefore user access can be selective. The new experience is more than a cosmetic update, a number of object-specific features (e.g. Account Insights, Opportunity Workspace) are introduced that increase the functional richness provided and deliver a more focused view than the generic interaction patterns applied in the Aloha interface.

Lightning Experience – Visualforce Support – Beta
Visualforce support in Salesforce Lighting Experience will be GA in the Spring ’16 release, for Winter ’16 this is desginated a beta status. Support in this context means rendering with the Aloha look and feel, pages will not inherit a Lighting Experience visual style via the container, instead Visualforce pages will need to be redeveloped using Lightning Components and the Lightning Design System to reflect the new visual style. Where investments have been made in Visualforce (ISVs for example) the lack of migration path could represent a significant amount of work. The Lightning Design System (LDS) provides the CSS and design guidelines necessary to develop user interface components that are consistent with the Lightning Experience visual design.

Platform Cache
A new Force.com platform cache has been added that supports both org-level and session-level data caching. The cache can be accessed from Apex via the Cache namespace and provides obvious performance and reliability benefits. A Session cache has been required for Visualforce for some time to help reduce viewstate pressure, the Org cache will remove the need for Custom Setting based solutions for shared/global data. Available cache space can be distributed across partitions to provide control over application-level usage.

Platform Cache Setup Page

Apex Debugger
An extension to the Eclipse based Force.com IDE that provides support for breakpoints. This is a major advancement for Apex development. Step Over, Step Into, Run To… all the usual breakpoint related features are supported. At long last Apex has some degree of basic parity with other languages where this capability is taken for granted. The release notes mention that for some customers the Apex Debugger may incur additional costs, the detail of which is yet to be seen.

Process Builder – Query Bucketing
The Lighting Process Builder is a powerful new platform automation feature – unfortunately to date Processes have not been bulk-safe and have a tendency to throw platform limit exceptions during bulk record operations; typically Soql query limit exceptions. The mitigation for which has been to suppress the batch size to a safe point – a reasonable but not entirely satisfactory resolution. With the Winter ’16 release Soql queries within a transaction are bucketed, meaning combined, to reduce the overall number of queries executed. In principle, this practice enables the limit to be avoided and the transaction to complete. This feels like a temporary resolution to the problem while a more significant re-engineering takes place, I understand that the issues here are not trivial.

Writeable External Objects
An obvious next step for Lighting Connect – the ability to write back to External Objects. Previously External Objects have provided read-only views on the external datasets. The new write support covers OData and custom Apex adapters but not the Salesforce adapter. Record modifications via the UI occur synchronously, while Apex invoked changes are applied via an asynchronous queue.

Extensible Community Templates
Winter ’16 Community Builder templates are now extensible meaning additional Salesforce functionality, content and navigation paths can be configured extending the available features beyond the constraints of the predefined templates. This additional level of configuration takes place directly within the Community Builder, removing the need to use Site.com Studio. Winter ’16 template pages are compatible with Lightning technology meaning Lighting components can be used. This topic is huge but suffice to say the new streamlined development workflow and ability to extend the pre-defined templates to new use cases both represent a significant step forward for Community Templates.

Broadcast Groups – Pilot
Chatter Groups, whether public, private or unlisted can now be configured to allow only the group owner or manager to create posts. Anyone who’s used Chatter groups extensively will recognise the requirement to support one-way, focused communication and to avoid off-topic posts. I can think of a few Salesforce partner community groups that will be implementing this feature asap.

Chatter Questions – Similar Questions and Articles
Chatter Questions now supports the display of Similar Questions and Knowledge Articles inline while typing the question text. An obvious extension, to a highly useful feature, that should reduce the level of duplicate questions and increase the consistency in answers provided, particularly where deflection occurs via knowledge base article.

Restricted Picklists – Pilot
A new “Strictly enforce picklist values” attribute for picklist fields can be used to prevent input of values outside of the defined list via the data APIs. This new feature should be highly beneficial to anyone loading data and avoids the historic silent-error problem where picklist data mismatches go unnoticed.

More Rollup Summary Fields
The new limit is 25 per-object, replacing the old limit of 10. RSF are a great platform feature; incredibly powerful, intuitive for the end-user and quick to implement.