9

I updated the question due to the issue being specifically for a dynamic component

Currently, I have a custom VisualForce page used to fill out a new Opportunity. Everything works as intended except for a certain scenario.

The issue is when you select save when all the required fields aren't populated. When you choose to save and not all of the fields are populated, some of the fields are retained (specifically lookup fields) and others (date/time picklists and text boxes) are not retained after the page is refreshed due to a page error. Please note: the values are being put out by a dynamic component.

I understand partially why this happens (some values are not stored in the server and certain ones are not) but, I do not know exactly how to work-around the issue. I have come up with a few decent attempts but I haven't had any success.

Has anyone found a way to work-around this issue?

Here is some code to better understand and I figure I will get asked to share the code anyway.

Controller Code

public with sharing class ExampleController
{
    private ApexPages.StandardController InheritedStandardController { get; set; }

    public Component.Apex.PageBlockSection DynamicSection { get { return this.CreateDynamicSection(); } }

    public Opportunity CurrentOpportunity { get; set; }

    public ExampleController(ApexPages.StandardController InheritedStandardController)
    {
        this.InheritedStandardController = InheritedStandardController;
        this.CurrentOpportunity = (Opportunity)InheritedStandardController.getRecord();
    }

    private Component.Apex.PageBlockSection CreateDynamicSection()
    {
        Component.Apex.PageBlockSection InternalDynamicSection = new Component.Apex.PageBlockSection();
        InternalDynamicSection.expressions.value= '{!Opportunity}';

        for(Schema.FieldSetMember Field : Schema.SObjectType.Contact.fieldSets.getMap().get('OpportunityFields').getFields())
        {
            Component.Apex.Column OpportunityColumn = new Component.Apex.Column();
            OpportunityColumn.Facets.Header = new Component.Apex.OutputText(value = this.FieldLabelCorrection(Field.getLabel()));
            OpportunityColumn.expressions.value = '{!Opportunity.' + Field.getFieldPath() + '}';
            InternalDynamicSection.childComponents.add(OpportunityColumn);
        }

        return InternalDynamicSection;
    }

    public PageReference save()
    {
        this.OpportunityName = this.CurrentOpportunity.Name;
        if(this.OpportunitySavedProperly() == false)
            return new PageReference( '/apex/EditNewPage');

        PageReference CorrectRedirectionPage = new PageReference( '/' + this.CurrentOpportunity.Id);
        CorrectRedirectionPage.setRedirect(true);
        return CorrectRedirectionPage;
    }


    private Boolean OpportunitySavedProperly()
    {
        try
        {
            Utility.UpsertWithoutSahring(this.CurrentOpportunity);
            return true;
        }
        catch(Exception ExceptionWasThrown)
        { 
            ApexPages.Message FailureMessage = new ApexPages.Message(ApexPages.Severity.FATAL, ExceptionWasThrown.getDmlMessage(0)) ;
            ApexPages.addMessage(FailureMessage);
            return false;
        }
    }
}

public without sharing class Utility
{
    public static void UpsertWithoutSahring(Opportunity SomeOpportunity)
    {
        try
        {
            UPSERT SomeOpportunity;
        }
        cacth(Exception DMLFailure){ throw DMLFailure; }
    }
}

VisualForce Page

<apex:page tabStyle="Opportunity" standardController="Opportunity" extensions="ExampleController">
    <apex:pageBlock title="Opportunity Edit" tabStyle="Opportunity" id="OpportunityInfo">

        <apex:pageBlockButtons>
            <apex:commandButton action="{save}" value="Save"/>
        </apex:pageBlockButtons>

        <apex:pagemessages />

        <apex:dynamicComponent componentValue="{!DynamicSection}"/>

    </apex:pageBlock>
</apex:page>


Solutions I Have Tried

The first thing I tried was changing the PageReference return null like so:

public PageReference save()
{
    if(this.OpportunitySaved() == false)
        return null;

    PageReference CorrectRedirectionPage = new PageReference( '/' + this.CurrentOpportunity.Id);
    CorrectRedirectionPage.setRedirect(true);
    return CorrectRedirectionPage;
}

But it did not work that way.

I tried using methods with apex:params but was unsuccessful and from the input that Matt Lacey gave, that would not bear the fruit I was looking for.

I also tried to see if I could save the information somewhere prior to a save. So I had to alter my code like the following:

Apex Code

public Opportunity CloningOpportunity { get; set; }

public PageReference RestoreFromClone()
{
    if(this.CloningOpportunity != null)
    {
        this.CurrentOpportunity.Name = this.CloningOpportunity.Name;
        this.CloningOpportunity = null;
    }

    return null;
}

public PageReference CloneCurrentOpportunity()
{
    this.CloningOpportunity = this.CurrentOpportunity.clone(false, true, false, false);
    return null;
}

Added in VisualForce

<apex:page tabStyle="Opportunity" standardController="Opportunity" extensions="ExampleController" action="{!RestoreFromClone}">
    <apex:actionFunction name="SpecialtySave" action="{!save}"/>
    <apex:actionFunction name="CloneInCaseOfCancel" action="{!CloneCurrentOpportunity}" immediate="true"/>

        ....
        <apex:commandButton action="{CloneAndSave}" value="Save"/>

        <script>
            function CloneAndSave()
            {
                CloneInCaseOfCancel();
                SpecialtySave();
            }
        </script>
        ....

What I was expecting is, the CloneInCaseOfFailure method to clone the Opportunity and stick it to the view state (hence why I chose that function to be immediate), the if there was a failure on save, it would hit the RestoreFromClone method and restore the current opportunity with the clone.

Problem is, despite everything, the clone still remains null. I've tried making it static and it still doesn't stick to the view state.

Hopefully, this will help find a solution, since the only other way to resolve this is by moving out of the dynamic component entirely (which I do not want to do).

Apologies for not posting that this was in a dynamic component sooner. This may have clarified a few things.

  • Where Visualforce fields are bound to non-transient controller variables, I assume the values will always be presented in the the page after an error. Are you saying that isn't the case here? – Keith C Jun 26 '14 at 21:09
  • That's correct. – Programmable Medley Jun 26 '14 at 21:39
7

We have used dynamic components in one piece of code and based on that experience since then have avoided them.

When normal Visualforce tags are used, the component tree is part of the serialized view state and that is necessary for the entered values to be displayed under error conditions. (The life cycle is likely to be this or similar.)

But you will notice that you have to use get methods for dynamic components and can't hold (non-transient) references to them. When we created a case on this (in 2013), the Salesforce support response was:

... dynamic components are not serializable ... is completely something working as designed ...

(Our assumption is that if they can't be serializable as controller fields they probably can't be serialized as part of the page component tree either.)

The bottom line appears to be that dynamic components don't behave well when errors occur. So the straightforward choices are to either just live with that or to re-implement without the dynamic components.

PS

Had another case where dynamic components were the simplest way and have successfully (so far) worked around the field value not being retained problem via JavaScript. See the answer to How to keep components out of the Visualforce view state when they are not needed?.

| improve this answer | |
  • 1
    Wow, that's terribly disappointing news. Good information to know. It looks like I have to re-implement infortunately which would make my VisualForce an absolute nightmare but, I know the client wouldn't stand for anything less. – Programmable Medley Jun 28 '14 at 2:16
1

I think the issue is that you're returning a new page reference from the save() action method. Instead, try returning null. Doing so simply causes the current page to refresh (or rerender based on VF markup) and it should keep those other values filled in.

| improve this answer | |
  • That was my first thought but, even returning null doesn't solve the issue unfortunately. – Programmable Medley Jun 26 '14 at 23:04
  • I went ahead and added some solutions that I have tried and did not resolve my solution. – Programmable Medley Jun 26 '14 at 23:24
  • Are you using getRecord() on the standard controller to set CurrentOpportunity? If not, you're capturing input on one object instance and then trying to save a completely different one. You definitely shouldn't need actionFunctions and params for this, but note that with those the params don't work unless you specify a rerender attribute... don't ask why, just a platform quirk! – Matt Lacey Jun 27 '14 at 0:58
  • I am using get record, since I am getting to this page via passthrough and need the correct record for the Record Type Id. Good to know about the apex:params since I qouldn't have known otherwise. – Programmable Medley Jun 27 '14 at 2:01
  • I updated my question since I did not mention that these field values are rendered by a dynamic component. Hopefully, this update will shed some light on a few things. Aside from going straight JavaScript or removing the dynamic VisualForce, I'm at my wits end. – Programmable Medley Jun 27 '14 at 23:27

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.