How to refresh the parent page after a Save action in a Visualforce page embedded in a page layout?

From the recipe change the save button from

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

to

    <apex:commandLink value="Save" action="{!save}" target="_parent" styleClass="btn" style="text-decoration:none;padding:4px;">

Since commandlink offers a target attribute I make use of it and make the command link exactly like the button using styleclass and style attributes

I tried even the command button with onclick approach but the child page constantly reloaded with the side bar and headers and did not look like it was inline. The commandlink is something I have worked hence the solution above, but it could be done using commandbutton if tweaked a bit more. Since we have what is required I did not push futher:)


An additional mechanism to consider in the event that you can't or don't want to use an <apex:commandLink with the target attribute set to _parent is to use a javascript statement to cause the parent page to refresh itself after successful saving.

The idea here is that you only render the script panel that executes the page refresh at the time you want the outer page to automatically reload (i.e. pressing F5). In this example the script block is displayed after the user clicks save (successfully) and the page inside the iframe (the VF embedded in the page layout) is being rendered back to the browser. The browser executes the now-visible javascript and the outer page is reloaded.

You might also consider setting the showHeader="false" sidebar="false" attributes on the page tag to disable those features if this page is going to be used exclusively embedded in another page layout.

VF Page embedded in a page layout (iframe):

<apex:page standardController="Account" extensions="yourExtensionClass" showHeader="false" sidebar="false">
    <apex:form>

    <apex:pageMessages />

    <apex:outputPanel id="refreshPanel" rendered="{!refreshPage}">
        <!-- This panel is only shown when the controller wants the parent page to perform a 'reload' -->
        <script>
            // force the browser to reload the 'top' page using the current URL
            window.top.location.href = window.top.location.href;
        </script>
    </apex:outputPanel>

    <!-- Your existing stuff -->

    </apex:form>
</apex:page>

The controller extension class:

public with sharing class yourExtensionClass {

    public Boolean refreshPage { get; set; }
    public ApexPages.StandardController stdController { get; set; }

    public yourExtensionClass(ApexPages.StandardController ctrl) {
        stdController = ctrl;

        // initialize the refresh value as false
        refreshPage = false;
    }

    public PageReference save() {

        // use the standard controller's save method
        PageReference pRef = stdController.save();

        if (pRef != null) {
            // set this value last, after successful saving to cause the VF page to show the javascript block
            refreshPage = true;
        }

        return pRef;
    }
}

@Rao's answer works for me in Classic as of Winter '17 release. But if you have a form the user is submitting and you get back errors you may not want the page to have opened in the parent frame, perhaps you want the form to stay where it is and only pop open into the parent frame on success.

This is how I approached it:

  1. Apex controller has a boolean field that the save method sets to true (if success) or false (if errors)
  2. The visualforce page uses the apex:commandButton's oncomplete to call a javascript function
  3. The javascript function checks the boolean field and if true then opens link in parent window

Visualforce Page

    <apex:page standardController="Case" extensions="YourCustomController">

        <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
        <script>$j = jQuery.noConflict();</script>

        <apex:form>

            <apex:pageMessages id="theMessages"/>

            <apex:pageBlock mode="maindetail">

                <apex:pageBlockButtons location="bottom">
                    <apex:commandButton value="Save" action="{!save}" rerender="theMessages,reloadPagePanel" oncomplete="reloadPage();"/>
                </apex:pageBlockButtons>

                <!-- your page content -->

            </apex:pageBlock>

            <apex:outputPanel id="reloadPagePanel" style="display:none">

                <apex:outputLink id="caseDetailLink" value="/{!Case.Id}" target="_parent"/>

                <script>
                    function reloadPage() {
                        if ( '{!success}' == 'true' ) {
                            $j('[id$=caseDetailLink]')[0].click();
                        }
                    }
                </script>

            </apex:outputPanel>

        </apex:form>

    </apex:page>

Apex Controller

    public with sharing class YourCustomController {

        public Boolean success { get; set; }

        public YourCustomController( ApexPages.StandardController stdController ) {
            this.success = false;
        }

        public void save() {

            SavePoint sp = Database.setSavePoint();

            try {

                // do stuff

                ApexPages.addMessage( new ApexPages.Message( ApexPages.Severity.CONFIRM, 'Time Logged' ) );

                this.success = true;

            } catch ( Exception e ) {

                Database.rollback( sp );

                System.debug( LoggingLevel.ERROR, e.getMessage() + ' : ' + e.getStackTraceString() );

                ApexPages.addMessage( new ApexPages.Message( ApexPages.Severity.ERROR, e.getMessage() ) );

                this.success = false;

            }

        }

    }