3

Propagating a custom exception error to the record page

Hi,

I have a Visualforce page embedded into an Opportunity page layout.

Interacting with this page results in an Opportunity update trigger being executed.

The code handles an exception with the addError('My error message here'). I wish this error message to be shown on the opportunity record page only, not within the embedded Visualforce page as is currently the case (please see the attachment).enter image description here

Many thanks!

2

Here's my solution. We can't use sidebar hacks, but we can communicate between Visualforce pages, because they all reside on the same domain anyways. Here's my implementation:

Common Controller

You could split these in two, but this is only demonstrative, so I combined the two:

public with sharing class AccountExtension {

    public string severity { get; set; }
    public string summary { get; set; }
    public string detail { get; set; }

    public AccountExtension(ApexPages.StandardController controller) {

    }

    public void showMessage() {
        Map<String, ApexPages.Severity> levels = new Map<String, ApexPages.Severity> {
            'info' => ApexPages.Severity.INFO,
            'warn' => ApexPages.Severity.WARNING,
            'err' => ApexPages.Severity.ERROR,
            'confirm' => ApexPages.Severity.CONFIRM,
            'fatal' => ApexPages.Severity.FATAL
        };
        ApexPages.Severity level = levels.get(severity);
        if(level == null) {
            level = ApexPages.Severity.ERROR;
        }
        ApexPages.addMessage(new ApexPages.Message(level, summary, detail));
    }

    public void createError() {
        severity = 'err';
        summary = 'Quick Summary';
        detail = 'This is where I would show an error message.';
    }
}

Visualforce Page for Viewing Account

Here's the page to view an account. We use a button override so that they'll see this page simply by clicking on an account:

<apex:page standardController="Account" extensions="AccountExtension" showChat="true">
    <apex:form >
    <apex:pageMessages id="messages" showDetail="true"/>
    <apex:actionFunction name="showMessage" action="{!showMessage}" reRender="messages">
        <apex:param name="severity" value="" assignTo="{!severity}"/>
        <apex:param name="summary" value="" assignTo="{!summary}"/>
        <apex:param name="detail" value="" assignTo="{!detail}"/>
    </apex:actionFunction>
    <apex:detail inlineEdit="true" relatedList="true" relatedListHover="true" />
    </apex:form>
    <script>
        function messageHandler(event) {
            var data = JSON.parse(event.data);
            console.log(event);
            if(data.severity && data.summary && data.detail && event.origin == window.location.protocol+'//'+window.location.host) {
                showMessage(data.severity, data.summary, data.detail);
            }
        }
        window.addEventListener('message', messageHandler, true);
    </script>
</apex:page>

Now, we specify a second page that can communicate to the first:

<apex:page standardController="Account" extensions="AccountExtension">
    <apex:form id="form">
        <apex:commandButton action="{!createError}" reRender="form" oncomplete="sendMessageUp()" value="Show Error on Parent" />
        <apex:inputHidden value="{!severity}" id="severity"/>
        <apex:inputHidden value="{!summary}" id="summary"/>
        <apex:inputHidden value="{!detail}" id="detail"/>
    </apex:form>
<script>
function sendMessageUp() {
    var data = {
        severity: document.getElementById('{!$Component.form.severity}').value,
        summary: document.getElementById('{!$Component.form.summary}').value,
        detail: document.getElementById('{!$Component.form.detail}').value
    };
    window.parent.postMessage(JSON.stringify(data), window.location.protocol+'//'+window.location.host+'/');
}
</script>
</apex:page>

Finally, we override the view button, add this second page to the layout, and observe.

This method would also work using sessionStorage and the storage events in HTML5. We could send data in one side, and it'd pop out on the other side for consumption. sessionStorage would have the benefit of letting us place an error-displaying page in one section of our page layout, and an error-generating page elsewhere, and they could communicate without knowing about each other directly.

Note that both solutions require a relatively new browser to work (IE 9, FF, Chrome, Opera, Safari...).

  • Many thanks for these workarounds guys! sfdcfox, your solution worked for me! – ffly2 Jul 30 '14 at 14:12
0

Unfortunately, this isn't possible. Your embedded VF page is within an iframe and a totally separate request to the server.

  • We can indeed communicate in a way that is future compatible. A few ways, in fact. Just have to be creative. – sfdcfox Jul 23 '14 at 18:26
0

Most likely, it will be impossible using the officially supported features of visualforce and apex. But there are unsupported workarounds which can make it. But to be honest - in this use case it seems not worth the effort and risks.

This said, you could try to use this workaround End of javascript sidebar workarounds? to include a script in the default page. Then you could use postMessage() https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage to communicate between the iframe and the top-page in order to add your message to the top-page's DOM. To do this, I would use jquery - but also plain JS can do it. If you use jquery, do it only in the no-conflict-mode to prevent issues: http://api.jquery.com/jquery.noconflict/

But be very careful! Using postMessage() can lead you into security issues! E. g. never use * as targetOrigin.

Also it may happen that Salesforce will break this workaround at some point in the future.

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.