Saturday, 7 May 2011

Refreshing Record Detail from Embedded Visualforce Page

Strange how things go in cycles.  This week there's been several questions on the discussion boards, asking how to refresh the standard record view page from an Visualforce page.  An example of an embedded page is shown below:


The purpose of the embedded page allows the user to choose a country ISO code and when the save button is clicked, populate the account billing country field with the full name of the account.

The controller method to carry out the save:

public PageReference save()
{
 Account acc=(Account) stdCtrl.getRecord();
 acc.BillingCountry=chosenCountry;
  
 return stdCtrl.save();
}


As this is an extension controller, the account is retrieved from the standard controller to update the field. The return value is the result of the standard controller save method, which is the standard record view page.

Unfortunately, as the Visualforce page is embedded in the standard view via an iframe, this simply puts the full record view page inside the existing record view page:


Not exactly what I had in mind!

Unfortunately there's no way to return a page reference to the browser that tells it to refresh the entire page, rather than just the iframe.   It is possible, however, to use javascript to carry out a client side redirect back to the record view.  Thus there needs to be some javascript that is conditionally rendered into the page after the save (and only after the save, otherwise the page will continually refresh) that executes the refresh.

The full revised controller is shown below - the new aspect is the refreshPage property, which is initialised to false in the constructor and set to true when the record is saved. I've also changed the controller to return null, as I need the embedded page to refresh and render the javascript.

public class EmbeddedController
{
 public ApexPages.StandardController stdCtrl {get; set;}
 public String chosenCountry {get; set;}
 public Boolean refreshPage {get; set;}
 
 public EmbeddedController(ApexPages.standardController std)
 {
  stdCtrl=std;
  refreshPage=false;
 }
 
 public PageReference save()
 {
  Account acc=(Account) stdCtrl.getRecord();
  acc.BillingCountry=chosenCountry;
  refreshPage=true;
  stdCtrl.save();
  
                return null;
 }
}


and the important new element in the page:

<apex:outputPanel rendered="{!refreshPage}">
   <script>
      window.top.location='/{!Account.id}';
   </script>
</apex:outputPanel>


Now when I click the save button in the embedded Visualforce page, the entire record view page refreshes and my chosen country appears in the billing address.

Update: 22/12/2012

A number of the comments for this post have asked about achieving this via the service cloud console.  This mechanism won't work I'm afraid, due to the browser same origin policy.  Looking at my javascript error console in chrome, I see 'Unsafe Javascript' exceptions when the refresh method fires.  I've also investigated converting the embedded page to a custom console component and using the integration toolkit to refresh the enclosing tab, but the same errors are thrown, event after whitelisting every domain involved.

32 comments:

  1. Refreshing the entire record with this would that handle the validation rules also without having to put the validation handling into a controller?

    Does that make sense? I'm still learning apex so my terminology/ knowledge is limited. Thanks

    ReplyDelete
  2. The validation rule would most likely throw an exception when the record was saved. In that case the page would redirect to an error page unless the exception was caught.

    ReplyDelete
  3. I keep getting this error when I run the Controller: "Incompatible types since an instance of sObject is never an instance of Account."

    ReplyDelete
  4. It works correctly for me - have you made any changes to the page or controller? There's various reasons why you might see this error - one is if you have a custom class called Account with is masking the Account sobject type.

    ReplyDelete
  5. Nice post.

    But to raise the ante a bit, what if the page is embedded in a page layout which is shown in the Console?

    The above logic throws you out of the Console and into account or case or whatever entity you are modifying.

    How to get round that?

    It seems you can't use the Console Toolkit to refresh VF Pages either.

    /Mats

    ReplyDelete
    Replies
    1. I've spent some time trying to get this to work in the service cloud console, but to no avail. I've updated the blog post to reflect this, and detail the routes that I tried.

      Delete
  6. Interesting question. I'm going to have a play around with that when I get time.

    ReplyDelete
    Replies
    1. VF page refreshing a Service Cloud Console tab.

      Did you get a chance to investigate this ?

      My scenario is as follows :
      I have Service Cloud users viewing a subtab that displays a Contact layout, which contains a VF page component.
      The VF page component has inline edit enabled.

      When a user does an inline edit within the VF page and presses save, I want the whole subtab to refresh.

      any ideas ?

      Delete
    2. I forgot all about this if I'm honest. I will try to make time to look into this as I'd like to play around more with the console.

      Delete
    3. I've spent some time trying to get this to work in the service cloud console, but to no avail. I've updated the blog post to reflect this, and detail the routes that I tried.

      Delete
  7. You can always use a var in the parent container object as the conduit to pass messages from the VF embedded page to itself on a refresh...

    ReplyDelete
  8. Hi Bob, I used this method for a while and it's been great. I recently ran into this issue:

    On a standard page layout, using a visualforce page within, I have a droplist of email templates and a button to redirect to '/email/author/emailauthor.jsp?' with parameters. The issue is that the parent URL starts as cs9.salesforce.com(sandbox) but the visualforce page and javascript redirect runs as c.cs9.visual.force.com. So the final redirect is c.cs9.visual.force.com/email/author/emailauthor.jsp?template_id=00XA00000019aZtMAI Bug in the final page is the same errors in Chrome you saw.. except this is not a custom page, it's the email editor trying to render the contents of the iframe for it's HTML editor.

    Correct URL redirect should be: cs9.salesforce.com/email/author/emailauthor.jsp?template_id=00XA00000019aZtMAI

    Any reference to the baseURL from within the visualforce OR controller will always yield the visual.force.com url. And not including the baseURL will imply to use the url of the child iframe. Then you get into issues with the security of the browser and not being allowed to reference the parent URL without implementing some kind of document.domain solution.

    Solution was to save a custom setting of the REAL baseURL for standard pages, and pass that into the URL to redirect to. I could have parsed the c.cs9.visual.force.com to get the instance and compile the URL too.

    Anyway, thought this was a good place to share for others since I could not find another good example.

    ReplyDelete
  9. Is it possible to refresh just the inline vf instead of parent page on closing a popup?

    ReplyDelete
  10. I am getting an error, Unsupported attribute extension in .

    I am new to apex, so here is my class

    public class CaseExtention
    {
    public ApexPages.StandardController stdCtrl {get; set;}
    public Boolean refreshPage {get; set;}

    public CaseExtention(ApexPages.standardController std)
    {
    stdCtrl=std;
    refreshPage=false;
    }

    public PageReference save()
    {
    refreshPage=true;
    stdCtrl.save();

    return null;
    }
    }

    I reference the extension in the first line, but I get the above error. Is there something I have to do in the vf page?

    ReplyDelete
    Replies
    1. This usually means you've referred to an attribute by the wrong name in the page - unfortunately you've only posted the controller so its difficult to tell what the problem might be.

      Delete
  11. Great post Bob. Currently the page scrolls to the section of the embedded VF page when it refreshes. Is there a way you can control this to scroll to any section of choice? If not, how can one add functionality to scroll to the bottom of the page?

    Thanks

    ReplyDelete
  12. Thanks so much for this post! Question, this seems to cause a mini-page refresh when the command button is clicked, followed immediately by the full refresh. It's a little jarring. Are you aware of any way to stop the commandbutton from causing that first apparent refresh?

    ReplyDelete
    Replies
    1. I know this question was posted 2 years ago, but I figured out a solution and wanted to provide it here in case someone else wants to fix this. Basically I created an extra outputPanel with the following javascript:

      function refreshAgmt(){
      window.top.location = "{!redirectUrl}";
      }


      The {!redirectURL} is a string value in my class that points to the URL you want to go to. The next step is to add a rerender="NameOfOutputPanel" and oncomplete="refreshAgmt();". This essentially refreshes the outputPanel (that has nothing in it) virtually not showing any refresh on the page (in order to update the value of {!redirectURL} on the page) and then refreshes the top page to that URL.

      Delete
    2. Woops, meant to say "add a rerender="NameOfOutputPanel" and oncomplete="refreshAgmt();" to the end of your commandButton."

      Delete
  13. We use onclick and oncomplete together in Commandbutton and we invoke refreshPrimaryTabById() method oncomplete to refresh the page in service cloud console. That works.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi. Could you share some more details about how you got this to work? I've had a particularly hard time retrieving the Primary Tab ID to pass into the refresh method.

      Delete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Hi,

    I have popped a vf page from a button of on the std case page (of console layout).
    I am inserting some values from the pop up vf page to the std case page.

    But to see the updated values in the cosole case page, I need to manually refresh the console.

    Is there any way, can that console page be automatically refreshed when the values are inserted in to case object?

    Please let me know.

    Thanks,
    Babu.

    ReplyDelete
  16. Nice post. its of great help....

    ReplyDelete
  17. Hi Bob,
    I have a requirement to Display a Visual Force Page in Custom object Page Layout On Click of Custom Detail Button.

    Is it Possible? If yes, Could You Please Guide me
    Thanks in Advance!!

    ReplyDelete
  18. Thanks alot, you have saved my day.

    ReplyDelete
  19. Thank you a lot. Works perfectly. You put a smile on ma head !

    ReplyDelete
  20. I have a inline vf page on parent record. Page shows list of child items which can be edited and save button. I am able to update entire page on update. But it gives problem when custom validation rule on child record makes the update from inline vf page fail. The child record does not get updated and we do not get any error message. Can you please guide on my issue.

    ReplyDelete
  21. Great, thanks Bob, was able to apply this to a custom/quick action in the chatter feed on opportunity records.
    Now to get it into production.

    ReplyDelete
  22. Bob! It's now late 2017 and I'm still using this blog post. Magic! Thanks, thanks, thanks.

    My use case is very similar to your example, except I've built a replacement related list VF component on a master/detail relationship. The detail needs to update a field on the master... and the detail VF component on the page switches between view & edit modes. Your solution is simple and (somewhat) clean.

    https://developer.salesforce.com/forums/ForumsMain?id=9060G000000MUYqQAO

    ReplyDelete