2

Is there a way from Apex to dynamically determine if a field is hidden from any page layout for the current Profile and Record Type?

Basically, I am customizing the Contact Merge functionality. I tried to list all fields dynamically. But the Standard Contact Merge apparently hide all fields that were hidden in the page. This is what I am trying to do.

2

You can use the User Interface API for this requirement.

You can run a very simple anonymous script to see what kind of data you'll get back:

Contact record = [SELECT Id FROM Contact LIMIT 1];
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
    '/services/data/v41.0/ui-api/record-ui/' + record.Id;

HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');

HttpResponse response = new Http().send(request);
system.debug(response);

For me, that data looks something like:

{
    "layoutUserStates": {
        /*irrelevant properties*/
    },
    "layouts": {
        "Contact": {
            "012000000000000AAA": {
                "Full": {
                    "View": {
                        /*irrelevant properties*/
                        "sections": [{
                            /*irrelevant properties*/
                            "layoutRows": [{
                                "layoutItems": [{
                                    /*irrelevant properties*/
                                    "layoutComponents": [{
                                        "apiName": "OwnerId",
                                        "componentType": "Field",
                                        "label": "Owner ID"
                                    }],
                                    /*irrelevant properties*/
                                }, /*more items*/]
                            }, /*more rows*/],
                            /*irrelevant properties*/
                        }, /*more sections*/]
                    }
                }
            }
        }
    },
    "objectInfos": {
        /*so much dang metadata*/
    },
    "records": {
        "003...": {
            "apiName": "Contact",
            "childRelationships": {},
            "fields": {
                "Account": {
                    "displayValue": "Edge Communications",
                    "value": {
                        "apiName": "Account",
                        "childRelationships": {},
                        "fields": {
                            "Id": {
                                "displayValue": null,
                                "value": "001..."
                            },
                            "Name": {
                                "displayValue": null,
                                "value": "Edge Communications"
                            }
                        },
                        "id": "001...",
                        "recordTypeInfo": null
                    }
                },
                "AccountId": {
                    "displayValue": null,
                    "value": "001..."
                },
                /*other fields*/
            },
            "id": "003...",
            "recordTypeInfo": null
        }
    }
}

You will need to define quite a lot of structure to deserialize this data but it can be done. Here is what mine looked like after taking a few shortcuts:

class ResponseBundle { final ResponseLayouts layouts; }
class ResponseLayouts { Map<String, Map<String, Map<String, Layout>>> Contact; }

class Layout { final List<Section> sections; }
class Section { final List<LayoutRow> layoutRows; }
class LayoutRow { final List<LayoutItem> layoutItems; }
class LayoutItem { final List<LayoutComponent> layoutComponents; }
class LayoutComponent { final String apiName, componentType, label; }

And then the deserialization looks like:

Contact record = [SELECT Id FROM Contact LIMIT 1];
Id recordTypeId = SObjectType.RecordType.getKeyPrefix().rightPad(15, '0') + 'AAA';
// RecordType not enabled for Contact in my org
// otherwise you would just use record.RecordTypeId
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
    '/services/data/v41.0/ui-api/record-ui/' + record.Id;

HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');

HttpResponse response = new Http().send(request);
ResponseBundle data = (ResponseBundle)JSON.deserialize(response.getBody(), ResponseBundle.class);
Layout fullView = data.layouts.Contact.get(recordTypeId).get('Full').get('View');

Set<String> apiNames = new Set<String>();
for (Section section : fullView.sections)
    for (LayoutRow layoutRow : section.layoutRows)
        for (LayoutItem layoutItem : layoutRow.layoutItems)
            for (LayoutComponent layoutComponent : layoutItem.layoutComponents)
                apiNames.add(layoutComponent.apiName);

Now, you may notice that there is a "data" section of the response and wonder if that gives you back the same fields. Turns out it does, but with a few extras (all the name-pointing parent objects in addition to the lookup Id. So you can shortcut this process further:

class ResponseBundle { final Map<String, Record> records; }
class Record { final Map<String, Field> fields; }
class Field { }

Id recordId = [SELECT Id FROM Contact LIMIT 1].Id;
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
    '/services/data/v41.0/ui-api/record-ui/' + recordId;

HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');

HttpResponse response = new Http().send(request);
ResponseBundle data = (ResponseBundle)JSON.deserialize(response.getBody(), ResponseBundle.class);
Set<String> dataFields = data.records.get(recordId).fields.keySet();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.