Salesforce Spring ’17 Platform Highlights

Accompanied by a huge (472-page) release notes document, the Spring ’17 release rolls out in early February. For perhaps the first time (to my recognition) a Lightning Experience (LEX) only theme is identifiable across the newly introduced set of features. This isn’t exhaustive but interesting nonetheless as historically any functional disparity typically favoured Salesforce Classic. Adoption rates for LEX will certainly be one-factor behind this approach, it’s also not surprising to see investment going into the future platform. What this does underscore is the growing status of Salesforce Classic as a legacy platform despite the high proportion of implementations yet to transition.

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

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

spring17logo

– features are GA if not indicated otherwise

External Services (Beta / LEX-only)
This new LEX-only feature provides a non-programmatic method for invoking external web service operations. The external service is registered (via JSON Schema) which generates proxy/wrapper Apex classes that can then be introduced into Flows which encapsulate the required business process automation. It will be interesting to see the code generated by the registration process, this could be a useful convenience irrespective of whether Flow is being. As a huge advocate of Flow (or Visual Workflow) it’s great to see investment that enables additional use cases. I’m slightly sceptical about non-technical treatment of API consumption scenarios, but that said anything that reduces the requirement for code should be a good thing.

external-service

Data Integration Rules
Data.com Clean Rules will be replaced with a generic framework for the definition of Data Integration Rules provided by external parties. Some rules will incur a cost, others such as the Data.com Geocode Address rules are free-of-charge. The term integration in this context relates to data enrichment i.e. a rule updates mapped fields on a given record from an external data source. The update can occur on-demand per-record or en-masse via data refresh (note, the Geocode address rule does not support data refresh). The ISV side of things isn’t clear at this stage in respect to publication of new Integration Rules.

External Data – Cross-org Adapter for Salesforce Connect
The REST API based Cross-org adapter for Salesforce Connect now supports write operations. Given the cost implications of this approach it will be interesting to see how much traction this gains – Salesforce-to-Salesforce whilst technically very different (data synchronisation etc.) does address the same set of use cases without the additional licence cost.

Lightning Console Apps (Beta)
Spring ’17 delivers a beta version of Lightning Console Apps. As one would expect Sales and Service standard console apps are provided plus the ability to define a custom console app. New Lightning Components (Related List, Related Record) and a Lightning Page Template (3 columns) are provided to enable implementation of console record detail pages comparable with Salesforce Classic. Given the beta status, lack of migration path and the number of omitted features it’s unlikely that customers will view this as a viable option right now, however now is the time to start thinking about a transition later in the year. Clearly compatibility with Omni-Channel, telephony solutions etc. will be fundamental to such planning.

lightning-console

Lightning App Builder
The Lightning App Builder gets a serious overhaul in Spring ’17 with a new UI structure, additional templates, page cloning and the ability to assign record pages by app, record type and profile being the feature highlights. The latter point being key to delivering a customised experience across users and business processes.

lightning-app-builder

Lightning API (Developer Preview)
The Lightning API is a REST API targeted at custom development use cases (mostly but not exclusively mobile) where the response payload can include data, metadata and layout information. Clearly the value-add here is efficiency – one API call to retrieve all the information that should be required to build the requisite UI/interaction. It should be noted that the API is rate-limited and will return a 503 if the limit is reached; careful client-side cache control logic should reduce the risk of this happening.

Lightning Experience – Filter Reports via URL
At long last it is now possible to set field filter values for reports via URL in LEX. This simple capability has historically played a significant role in most implementations; custom links on report detail pages, bookmarked reports etc. Good to see another LEX obstacle removed.

ISV – Subscriber Org Debugging
ISV partners now have access to the ISV Customer Debugger, meaning for each LMO a single Apex Debugger session can be run at a time against a customer sandbox. The Apex Debugger runs in the Force.com IDE (i.e. eclipse) and provides enhanced support for code debugging; breakpoints, step into/over, call stack inspection etc. This powerful tool is normally a paid-for service – providing a free license to ISV partners will be well appreciated.

ISV – List Lightning Bolt Solutions
At some point after the initial rollout it will be possible for Salesforce partners to distribute Bolt solutions as managed packages listed on the AppExchange. A Bolt solution is prefabricated Community template typically targeting a vertical solution or providing a foundation for horizontal solutions such as booking/event management, membership etc. It will be interesting to see the traction for listed Bolt solutions over the coming months, I think the opportunity here could be significant for both ISV and consulting partners. Anyway it’s great to see the AppExchange now supported low-level Lightning Components and high-level Bolt solutions in addition to the standards Apps and Consultancy services.

Skype for Salesforce (Beta)
Check online status, chat and make audio/video calls directly within Salesforce. Chat transcripts can be saved as Notes. All useful and convenient functionality. A “Skype for Business” component can be added to LEX Pages via the Lightning App Builder. The component provides the same functionality for records with at least one email address field.

Kanban Views
List views now support easy switching between the default grid layout and the Kanban style visual layout. Custom Kanban settings and record types are also supported, the latter appear as tabs above the board.

kanban-settings

AccountContactRelation Enhancements
The AccountContactRelation standard object which allows Contacts to be related to multiple Accounts receives further enhancement in Spring ’17. Relationships can now be defined between Person Accounts and Contacts, the object supports process automation (workflow, Process Builder etc.) and custom buttons, links and actions. I’m a big fan of this seemingly innocuous feature; great to see the additional capabilities being added each release.

Apex Stub API
The Apex Stub API provides a mocking framework for use in test code development. Typically mocks are used to isolate code for specific logical test cases or to allow streamlining of unit tests. Mocking frameworks are common to other programming languages, as such it’s great to have a standard approach for Apex.

InvocableMethod Invocations via Process Builder

As I’ve previously stated the combination of Process Builder and Apex Actions is incredibly powerful.

This short post adds the answer to a frequently asked question; how many times is the InvocableMethod called when records are modified in bulk?

To answer this with an example. With default batch settings (200 records) and 201 modified records the InvocableMethod is called twice (2 times – 1 invocation per-batch), first call with 200 records then a second call with 1 record. Not 201 individual calls!

If the Process applies entry criteria met by only 10 of the first batch – plus the single record in the second batch – there would again be 2 calls (10, then 1). So the assertion holds true; one invocation per batch.

This bulk behaviour is why the @InvocableMethod input parameter is a list of course; however it isn’t obvious in the related documentation.

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.


<!-- 
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" 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" 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" 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" 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" 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" 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" 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" 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" 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>

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 Omni-Channel

This post provides a technical view on the Salesforce Omni-Channel feature-set added in the Summer ’15 (beta) and Winter ’16 (GA) releases.

In functional terms Omni-Channel enables use-cases where work items are proactively pushed to specific agents based on defined rules in relation to priority, capacity and availability. The model extends the traditional Queue approach through routing configurations that define how work items (Cases, Leads etc.) are sized, prioritised and the required routing logic (least active agent or most available). Agent capacity and availability is configurable per work item type, or service channel in Omni-Channel terminology. Queue membership controls the applicability of a given agent for a given work item. As such queues are defined to represent the work item assignment structure (skills, regions, teams, products, knowledge etc.) – as would typically be the case outside of Omni-Channel. Once a work item is assigned to an Omni-Channel enabled queue automated routing to an available queue member takes place. Where automated assignment fails, pending work-items are held on the queue and routing attempts made each time agent availability changes. Agent work load is represented by ownership of the records represented by work items.

From an agent perspective Omni-Channel interactions are handled via an Omni-Channel Widget in the Salesforce Console. The widget supports accept/decline behaviour and manual presence status setting. “Away for Lunch”, “Available for Cases” being illustrative examples. The work item display in the widget is controlled via the primary compact layout for the object the work item represents.

Omni-Channel Console Widget

Omni-Channel integrates seamlessly with Live Agent, enabling a standardised approach to routing and agent capacity across channels. Seamless in this context relates to the end-result, there are configuration changes required to transition from standalone Live Agent to an Omni-Channel integrated state. The objects supported by Omni-Channel are limited to those objects supported by Queues i.e. Cases, Chats, SOS video calls, Social posts, Orders, Leads, Custom objects.

Hopefully the preceding paragraphs sufficiently set the scene for the technical aspects covered below.

Data Model

Omni-Channel Data Model

As the model above shows Omni-Channel is underpinned by a significant number of standard objects. The majority of the objects support queryable, createable and updateable access levels which provides extensibility where standard features require augmentation. The one notable exception to this is the UserPresenceStatus object which records the current presence status for agents, this object is read-only. This limitation will prevent custom solutions (or more likely 3rd party AppExchange solutions) from programmatically manipulating the agent presence; this could be significant where solutions wish to share state with Omni-Channel at the application level.

Extension Points

Beyond the ability to extend Omni-Channel through direct data-level interactions (Apex or API) there are additional extension points to consider.

1. Custom Fields. The UserServicePresence (aka User Presence) and AgentWork objects support the addition of custom fields.

2. Apex Triggers / Validation Rules. The UserServicePresence object supports both Apex Triggers and Validation Rules. In the former case custom logic could be introduced to take action when specific presences are set. In the latter case conditional logic could be applied to prevent presence changes, although the console widget behaves erratically in response to validation rule failures.

3. Custom Console Footer Components. For each Service Channel (Lead, Case etc.) a footer component can be specified to open when a work item of the service channel type is opened. The customisation potential here is considerable.

4. Omni-Channel SOAP API Objects. Full set of objects exposed via the standard SOAP API.

5. Omni-Channel Objects for the Salesforce Console Integration Toolkit. Extensions to the standard console JavaScript API to support manipulation of Omni-Channel objects. Straightforward enough. A key point here to note is that unlike Apex or API transactions, the JavaScript API does support update of agent presence.

sforce.console.presence.setServicePresenceStatus(statusId, function(result) { ..}

6. Pre-assignment. Omni-Channel routing is applied when a work item is assigned to a Queue via record ownership. Automated routing logic then assigns the work item to a user based on priority, availability and capacity rules. Granular control of the user-assignment is not provided. Use cases that require one agent to be preferred over another based on logic such as last customer contact are not supported. Finer grained routing of this type should be addressed in the pre-assignment step, i.e. before the queue assignment. Agent presence and capacity are accessible via code and could therefore be queried as part of a custom user-level routing process. The AgentWork object is also createable suggesting that a record representing a pre-assignment could be added to ensure visibility of the work item to Omni-Channel.

References
Omni-Channel for Administrators
Omni-Channel Developer’s Guide

Update – 10th March

With Omni-Channel current agent workload is determined by work item records open in the Salesforce console not record ownership. Closing the tab for a Lead record has the effect of setting the related AgentWork record status to closed.

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

Process Builder Apex Actions

A technical best practice in the Salesforce domain is to employ Apex code predominantly as an enabler for the declarative platform capabilities. This light-touch approach is simple in concept; the minimum amount of Apex code necessary is introduced to enable non-technical build features to solution the functional requirement. In the majority case this works very well and provides a strong maintainability story. In more complex scenarios complete technical solution options can be warranted and are perfectly acceptable, but one should always preclude the Apex-as-glue approach first.

In practice this model can be challenging to implement. The solution design process must be driven by individuals who are both expert in the current-release declarative capabilities of the platform and have the skill and experience to deconstruct a requirement into a structured solution design composed of declarative and technical components. Typically functional experts revert to technical solution options as a last-resort and don’t always think in terms of blended solutions. Salesforce technical resources don’t usually have a deep understanding or practical experience of the functional capabilities of the platform – some do, most don’t consider this a development concern or don’t have the opportunity to build skills in this area.

The recent introduction of the (Lightning) Process Builder with its ability to invoke Apex Actions (via the InvocableMethod annotation) provides a powerful shared language between functional and technical perspectives, enabling a clear decomposition of functional and technical solution components. To elaborate on this, process automation solutions can be implemented (i.e. Processes) that are comprised of declarative functionality but also with delegation to Apex code where gaps exist. In other words a clear exemplar of the model described previously. Adding parameterised Apex Actions in this context enables declarative configuration of the technical component and removes the black-box issues related to Apex Trigger components etc.

The screenshot below shows an Apex Action invocation within the Process Builder design environment.

Process Builder - Apex Action

In consideration to the concept of Processes (or indeed Flows – the technology that underpins Process Builder) invoking Apex Actions, a new development paradigm becomes possible, one that avoids black box, inflexible Apex Triggers and places control and configuration in the hands of non-technical resources (business analyst, administrator, app builder etc.).

From the development perspective Apex Actions should be coded as reusable, encapsulated components with flexible configuration (via parameters) and simply dropped into Processes as required. Note, Actions are also invocable directly from the REST API – a further point of potential for this approach. This component-based architecture should work well for all parties. The question of when to write an Apex Trigger or an Apex Action will be influenced primarily by factors such as whether the logic applies to all record modifications or is selective and whether bulk operations must be supported at default batch sizes – at the time of writing there are governor limit considerations with Process Builder that should not apply to correctly written bulk-safe Apex Triggers.

Custom Metadata Types – Salesforce Winter ’16

1 Create New Custom Metadata Type

This post provides a high-level overview of the Winter ’16 enhancements to the Custom Metadata Types platform capability.

Custom Metadata Types – The App Configuration Engine for Force.com

There are in my view two distinct ways to consider Custom Metadata Types (CMT); firstly as analogous to Custom Settings and secondly as an architecturally significant paradigm shift in regard to platform extensibility. In the former case CMT can be viewed as a straightforward, almost like-for-like replacement to List Custom Settings – with the added benefit that records can be deployed as metadata. There are of course considerable differences between the two, however conceptually this view is simplistic and approachable. In respect to the latter case, prior to CMT platform extensibility for Force.com could be viewed as a vertical model where new instances of pre-defined metadata types are created to deliver custom interactions. Custom Metadata Types enable a horizontal extensibility model where new type definitions can be introduced and instances created as metadata. The platform is no longer constrained to the pre-defined set of metadata types, developers have freedom to extend the model and deploy both the types and instances freely across environments. This horizontal extensibility model enables a host of new use cases such as bespoke development frameworks that abstract or extend the Force.com platform, an idea variously described as Platform-on-a-Platform or Custom Platform.

Custom Metadata Types were introduced as a beta release in Spring ’15, with the GA release in Summer ’15. The enhancements added to Winter ’16 appear to represent another milestone on the journey toward an increasingly capable platform extensibility model where custom types can be related to standard types, perhaps to override or extend platform behaviour. This is definitely a key area of the Force.com platform to pay attention to over subsequent releases.

Note, the native user interface for Custom Metadata Type administration shown in the following screenshots is a new Winter ’16 feature, previously Metadata API calls were required to define types and manage associated records.

Key Concepts

Metadata Type
As the screenshot below shows, Custom Metadata Types support Custom Fields and Page Layouts, all very consistent with the Custom Object and Custom Setting equivalents (although not layouts in the Custom Setting case). At this point it’s worth considering the fact that all standard metadata types are comprised of a collection of attributes, for example an ApexClass has Name, Body attributes in the same way a CustomField has Name, Label, DisplayType attributes. This is how Force.com platform metadata is structured. The difference between a CMT and a Custom Object or Setting isn’t the definition it’s the type of data stored; with a CMT we’re recording metadata. Taking a somewhat obscure example, we could invent a new proprietary platform language called Opex (;-)), define a CMT called OpexClass with a Body attribute etc., populate it with metadata records that represent a System namespace and ship some actual ApexClass instances to translate and run the Opex code. I’ll concede this isn’t a practical example, however the point I hope should be clear.

2 Custom Metadata Type

The protected component attribute applies to Managed Packages; meaning visibility of the CMT in a Subscriber org.

When defining a new CustomField for a Custom Metadata Type, there are limitations to the field types that can be specified and as-per Custom Settings there are no picklist or relationship fields (as yet anyway).

3 New Field

Field Manageability is a new concept in Winter ’16 to understand, again in relation to Managed Packages. In short this setting provides field-level editability control, selectable values being:

Locked after release : Field value is locked after deployment (includes the developer org).
Subscriber editable : As the name suggests; deployed (developer) updates will not override subscriber field value changes.
Upgradable : Locked in the subscriber org, developer can edit and deploy upgrades.

4 Custom Metadata Type with Fields

Metadata Record
A Metadata Record is really where Custom Metadata Types head off on their own path; a Metadata Record as the name implies represents an instance of the metadata type as a record that can be manipulated by the Metadata API, deployed via Change Set and packaged. The significance of which is obvious but incredibly powerful. It becomes possible, for example, to track Metadata Records using Source Code Control tools and to deploy metadata plus configuration via a single deployment transaction. No more, 2 stage deployments or clumsy post-install data loading.

5 New Metadata Record

As can be seen in the preceding screenshot it is possible to define a Protected Component setting at the Metadata Record level. This enables the type to be public but records to be selectively hidden in the subscriber org – a very flexible capability.

6 Metadata Records

Key Benefits

As mentioned in the introduction the Custom Metadata Type platform feature is still emerging, in my view at least, the most interesting aspects are potentially yet to be revealed, however there are definitely some key benefits to highlight with the Winter ’16 release.

For Enterprise : Manual steps within an otherwise automated Application Lifecycle Management process can cause compliance issues and release management inefficiency. Custom Metadata Types enable application configurations to be deployed as part of a seamless, one-step deployment process thereby removing manual friction. Configuration management tools can also track and version control the application definition and its configuration state.

For Partners : A long time issue for ISVs has been the deployment of application configuration data as part of the managed package installation process. Post install scripts provide one option, but creating data via Apex script doesn’t scale well or deliver the required fine-grained control over subscriber org configurability and upgradeability. Custom Metadata Types address both issues.

The screenshot below shows both a Custom Metadata Type and Metadata Records added to a Managed Package definition.

7 Packageable

Note, the benefits stated above are those practical benefits of the capability in relation to its generic capability, the actual benefit for many developers will be the flex

Implementation Considerations

Audit trail : Changes to both Custom Metadata Types and Metadata Record are visible via the Setup Audit Trail, this is new to the Winter ’16 release.

8 Audit Trail

Metadata Record Access : Metadata Records can be accessed via SOQL query only, there is no direct Apex support. Note the __mdt suffix.

Widget__mdt[] widgets = 
 [select QualifiedApiName, Height__c, Width__c from Widget__mdt];

Metadata Record Modification : Custom Metadata Types do not support DML operations via Apex, the Metadata API must be used. For use cases where configuration data needs to be created via code, CMT may not be an effective approach.

Relationship Fields : At some stage (Spring ’16 perhaps) in the future evolution of Custom Metadata Types I would expect support for relationships to be provided. This I believe is where the feature will really take-off.

Apex Testing : Currently Metadata Records are visible in Apex unit tests (without SeeAllData=true), it’s likely that simulated test data will be supported in a future release to enable testing under different configurations.

Permissions : The permissions model for Custom Metadata Types is limited, the Metadata Records are either visible or not at the org-level. A finer-grained permission model, perhaps just at the record level would be an obvious progression.

References

https://developer.salesforce.com/blogs/engineering/2015/08/custom-metadata-types-winter-16.html

https://help.salesforce.com/HTViewHelpDoc?id=custommetadatatypes_overview.htm

http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_forcecom_development_custom_metadata.htm

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.