6

In Visualforce, it was possible to have a Visualforce parent object page that allowed new child objects to be created through a default layout child edit page with an automatic return back to the Visualforce parent object page. This involved URL hacking as no supported platform mechanism was ever provided.

When running such a parent page in Lightning Experience, the child object edit page does (somewhat miraculously) display but without the parent object name/id populated and the return URL is not followed so the user is left in the child object detail page. So some hacks from the page-based Visualforce world don't work in the very different Lightning Experience world: perhaps not surprising.

But can anyone offer a pattern to address this problem? That is code changes that keep the Classic case working and achieve a similar result in the Lightning Experience case (e.g. sforce.one.createRecord)? This Create record in Lightning with populated fields post suggests the answer is probably no.

PS

For cases where there is a Visualforce page (and Apex controller) for the child page, the cycle works correctly with the retURL getting you back into the parent Visualforce page. So perhaps the solution to getting this to work in Lightning Experience is to write more Visualforce pages...

4
  • In lightning the pattern would be to open a LEX modal window to create the Child Record with the appropriate relationship fields populated. I would suspect that is the pattern they would want us to follow. Aside from that, you could use a VF page to control the navigation back and forth – Eric Dec 4 '16 at 0:09
  • @Eric What I get right now is the LEX modal window but with no fields populated and a "save" stays on the child LEX detail page. If there is a way to write a Visualforce page that is solely concerned with the navigation back and forth `and relationship field values that would be great but I'm not confident there is. So I'm presently thinking of writing a generic "New Child" Visualforce page that would require separate configuration to match the default layout pages which is pretty annoying. But at least then the logic would stay in the URL-based Visualforce domain. – Keith C Dec 4 '16 at 11:00
  • When you open the modal, use your controller to populate the default values of the sObject used by the modal and rerender the modal contents with the same action that opened it. If the modal is on the parent it should stay on parent when saved/closed unless you tell it otherwise. As for the VF it will only work if you take them to standard non-lex pages. With the modal you would have to add all the field level elements as well which would be a pain. I hope they provide us with a component that. – Eric Dec 4 '16 at 16:38
  • 4
    Why are questions related to migration of VF to SLDS so hard to get answers for?? one would think the entire community is going through this. Is it that SLDS is not being widely adopted, large gaps in the ability to even do this?? Is the transition limited to all LEX and no middle ground? – Eric Dec 6 '16 at 3:23
2
+100

The reason to use the URL hacks was to leverage the layout mechanism in Visualforce: the layout could be used for this "new child" case as well as for later editing. Using a custom controller and a Visualforce page (that does work in Lightning Experience) means you have to re-create the layout programmatically which has two problems:

  • When customers want to modify the layout, they can't just use the WYSIWYG layout editor but have to also configure the Visualforce and controller somehow.
  • It isn't a DRY way to develop.

I've posted the page I'm using as a stopgap for now below. This is not a good solution - if you have a better one please add your answer.

It doesn't solve either of the above problems, but does allow the calling code to decide the SObject and fields displayed so the configuration moves to the parent page controller (that in my case already had a configuration mechanism). If I had a few days to spare, I'd base the layout on an Apex metadata call to get the layout (though some caching might be needed if that turned out to be slow).

Controller:

/**
 * Controller that provides an alternative to the LKID/retURL hack as that doesn't work in Lightning Experience.
 * Driven by URL parameters that include API name/value pairs that can set values such as the parent Id.
 * Caller defines which object is shown and which fields - sadly doesn't use the layouts.
 * Presently only supports the "New" case but could be extended to support updating.
 */
public with sharing class GenericDetailController {

    public static final String SOB_TYPE_PARAM = 'sobType';
    public static final String DISPLAY_PARAM = 'display';
    public static final String REQUIRED_FIELDS_PARAM = 'required';
    public static final String READ_ONLY_FIELDS_PARAM = 'readOnly';
    public static final String RET_URL_PARAM = 'retURL';

    public SObjectType type {get; private set;}
    public String[] fields {get; private set;}
    public Map<String, Boolean> requireds {get; private set;}
    public Map<String, Boolean> editables {get; private set;}

    public SObject sob {get; private set;}

    private transient DescribeSObjectResult describe;
    public DescribeSObjectResult getDescribe() {
        if (describe == null) describe = type.getDescribe();
        return describe;
    }

    public GenericDetailController() {

        Map<String, String> params = ApexPages.currentPage().getParameters();

        // Required param
        type = Schema.getGlobalDescribe().get(params.get(SOB_TYPE_PARAM));

        Rights r = new Rights(getDescribe());
        if (!r.c) {
             ApexPages.addMessage(new ApexPages.Message(
                     ApexPages.Severity.ERROR,
                     'You do not have the access rights to create this object'
                     ));
             return;
        }

        sob = type.newSObject();

        // Required param
        fields = params.get(DISPLAY_PARAM).split(',');

        // Optional param
        Set<String> requiredFields = StringUtil.splitCsvsWithTrimSet(params.get(REQUIRED_FIELDS_PARAM));
        requireds = new Map<String, Boolean>();
        for (String field : fields) requireds.put(field, requiredFields.contains(field));

        // Optional param
        Set<String> readOnlyFields = StringUtil.splitCsvsWithTrimSet(params.get(READ_ONLY_FIELDS_PARAM));
        editables = new Map<String, Boolean>();
        for (String field : fields) editables.put(field, !readOnlyFields.contains(field));

        // Any parameter that matches the lower case field API name will be set
        Set<String> allFields = getDescribe().fields.getMap().keySet();
        for (String key : params.keySet()) {
            if (allFields.contains(key.toLowerCase())) {
                // Could do type conversion but generally here just Id values or String so no need
                Object value = params.get(key);
                sob.put(key, value);
            }
        }
    }

    public PageReference save() {
        try {
            upsert sob;
            return retUrl();
        } catch (DmlException e) {
            ApexPages.addMessages(e);
            return null;
        }
    }

    public PageReference cancel() {
        return retUrl();
    }

    private PageReference retUrl() {
        // Required param
        return new PageReference(ApexPages.currentPage().getParameters().get(RET_URL_PARAM));
    }
}

Page:

<apex:page controller="GenericDetailController">
    <apex:form >
        <apex:sectionHeader title="{! describe.label } Edit" subtitle="New {! describe.label }"/>
        <apex:pageMessages />
        <apex:pageBlock title="{! describe.label } Edit" mode="edit" rendered="{! sob != null }">
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{! save }"/>
                <apex:commandButton value="Cancel" action="{! cancel }"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection title="Information" columns="1">
                <apex:repeat var="f" value="{! fields }">
                    <apex:inputField value="{! sob[f] }" required="{! requireds[f] }" rendered="{! editables[f] }"/>
                    <apex:outputField value="{! sob[f] }" rendered="{! !editables[f] }"/>
                </apex:repeat>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

Parent controller:

public PageReference addAbc() {

    ...

    PageReference pr = Page.GenericDetail;

    Map<String, String> parameters = pr.getParameters();
    parameters.put(GenericDetailController.SOB_TYPE_PARAM, String.valueOf(childSobType));
    parameters.put(GenericDetailController.DISPLAY_PARAM, asTightCsv(fields));
    parameters.put(GenericDetailController.READ_ONLY_FIELDS_PARAM, asTightCsv(readOnlys));
    parameters.put(GenericDetailController.REQUIRED_FIELDS_PARAM, asTightCsv(requireds));

    // Pre-populate this
    parameters.put(String.valueOf(referencingChildField), parent.Id);

    // Want to return to here
    parameters.put(GenericDetailController.RET_URL_PARAM, currentPageUrl);

    return pr;
}

private static String asTightCsv(SObjectField[] fields) {
    String[] strings = new String[] {};
    for (SObjectField f : fields) strings.add(String.valueOf(f));
    return String.join(strings, ',');
}
1

The simplest declarative solutions that work in Lightning Experience would be:

1) A "Create a Record" Quick Action in which you can define which fields to show and which ones should contain a default value.

2) Visual Flow. Currently these look quite nice in Lightning despite the fact that creating them still requires Adobe Flash (executing them does not).

In both cases you never actually leave the parent object page. If you require more customization than either of these, that's when you get into custom dev.

6
  • @KeithC - Have you looked into this solution? Looks like it may be the simplest route. Interested to see your response to its possible implementation – Eric Dec 10 '16 at 5:29
  • @Eric My aim is to make an existing managed package "Lightning Ready". Some pages have "New Child" buttons that return the PageReference of the Visualforce standard layout page with URL hacks use to pre-populate the parent fields. Can a Quick Action be hooked into those buttons somehow? – Keith C Dec 10 '16 at 10:59
  • @KeithC Sadly I do not think so. The actions are still pretty basic – Eric Dec 10 '16 at 11:53
  • @KeithC Can you show us some screenshots of what the user currently experiences? Feel free to blur out identifying things if necessary. – Charles T Dec 10 '16 at 15:31
  • Not sure that a screen shot would help much. Let me detail the sequence. There is a "new Child" button on a VF page. In Classic that results in a layout-based edit page opening with some fields pre-populated courtesy of lkid hacks. When that page is saved, the VF page is re-displayed because its URL was included as the retURL. In LEX, the layout-based page is presented as a modal dialog on top of the VF page with no pre-population and a save takes you to that edit page's detail page. The Classic layout-based page no longer exists & the LEX modal dialog doesn't emulate the old VF URL patterns. – Keith C Dec 12 '16 at 16:31

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.