Saturday 21 April 2018

Toast Message from a Visualforce Page

Toast Message from a Visualforce Page

Introduction

Pretty much everything I do now around the user interface is via Lightning Components, typically added to a Lightning page so that I can also include standard components. There’s one aspect that continues to frustrate me - when I need to send the user to a custom new record page programatically, when they press a button in a custom component, for example.

  • I can’t use the force:createRecord standard event because, as the docs say, "This event presents a standard page to create a record. That is, it doesn’t respect overrides on the object’s create action.”.
  • I can’t use a Lightning page as these aren’t URL-addressable and there’s no way to navigate to them.
  • I can’t use force:navigateToComponent as that’s still in beta and, while I’m pretty sure it will be supported in the future, there’s no guarantee it will ever go GA.
  • I can use a Lightning app (.app bundle) as these are URL addressable, but when I do they take me outside the Lightning Experience.

So I use Lightning Out for Visualforce and force:navigateToURL to send the user there. 

Events

As my Lightning components are inside the Visualforce iframe, I have no access to the standard Lightning events, such as force:navigateToURL and, to the point of this post, force:showToast. If I try to instantiate one of these events in a Lightning component:

var evt=$A.get("e.force:navigateToURL");

I’ll get null returned.

For many events this isn’t the end of the world though, as I can add a handler in my Visualforce page when I dynamically create the component:

$A.eventService.
        addHandler({
                    event: 'force:showToast',
                    handler: function(event) { 
                                 // take some action
                    }
});

Then when I instantiate the force:showToast event I’ll get one, and when I fire the event my handler function will be invoked I’m still in Visualforce though, in an iframe and not executing in the one.app container, so it doesn’t help that much so far.

One really important aspect I always forget when adding the custom event handler in Lightning Out for Visualforce - my Lightning app that is the bridge between Visualfoce and the underlying Lightning components needs to declare a dependency for the event, otherwise nothing works. Another few hours of my life I won’t get back and even more annoying as it’s about the fifth time I’ve fallen for it:

<aura:dependency resource="markup://force:showToast" type="EVENT"/>

Posting a Message

Visualforce pages can communicate with Lightning components, via the window.postMessage() JavaScript function. It’s slightly clunky in that I need to identify the target origin that I want to send the message to, but I can pull the message from the toast event and send it on wrapped in a JavaScript object:

var lexOrigin = "https://kabtutorial-developer-edition.lightning.force.com";
var message={type: "EventFromVF",
             message: event.getParams().message};
parent.postMessage(message, lexOrigin);

So now all I need is a Lightning component that can receive this and raise it’s own toast event to actually display the message to the user. I called mine ToastReceiver and  a reduced version of the code is shown below :

window.addEventListener("message", function(event) {
    if ( (event.data.type) && (event.data.type=='EventFromVF') )
        var toastEvent = $A.get('e.force:showToast');
	toastEvent.setParams({
    	        type: 'info',
        	    message: event.data.message
	        });
    	toastEvent.fire();
    }
}, false);

My first thought was to wrap my Visualforce page in a Lightning component that iframes it in. This worked perfectly, but left me in exactly the same situation as before - I didn’t have a supported (GA) mechanism to programmatically navigate to that component. It seems that I’m stuck between a rock and a hard place - I need to navigate to a Visualforce page, but somehow that Visualforce page needs to message a Lightning component on the same browser page.

Enter the Lightning Utility Bar

The Lightning Utility Bar allows me to include the same set of Lightning components across every page of my app, so I added one to my Sales app and added my ToastReceiver to it, remembering to check the box that loads the component in the background when the app opens, otherwise my toast messages only appear after I’ve opened the utility bar element.

The diagram below shows the flow:

Screen Shot 2018 04 21 at 16 59 03

I enter a message in my Lightning component, which fires a toast event (1), this is picked up by the event handle in the Visualforce JavaScript, which posts a message (2) that is received by the Lightning component in the utility bar. This fires it’s own toast event (3) that, as it is executing in the one.app container, displays a toast message to the user.

Note that while my toast message originated from a Lighting component, this can also be used to display toast messages from regular Visualforce pages inside the Lightning experience, just post a message. All standard functionality and all generally available. Not a bad result for a Saturday afternoon’s work, even if I say so myself.

Demo

In the short video below my browser is initially on a Lightning page and clicking the button programmatically sends me to a Visualforce page via the force:navigateToURL event, containing a Lightning component with a message input and a button. Clicking the button starts off the flow described above.

Related Posts

 

8 comments:

  1. Hi Bob, Thanks for the insightful post. I have a question - $A.eventService is this a part of private API? Would it work if I package the visualforce page that uses this API?

    ReplyDelete
  2. Hello Bob,

    Interesting and very nice article, using this can we also have force:editRecord opened on our visual force page?

    ReplyDelete
    Replies
    1. No, the toast message is still being displayed via the lightning container, it's just that the visualforce page "triggers" it. The VF page is still in it's own, non-lightning, iframe.

      Delete
  3. Does this example work on a regular VF page?
    (without lightning experience in the org)

    ReplyDelete
    Replies
    1. No, it's the lightning container that is displaying the message. Outside of lightning I use something like alertify to display messages.

      Delete
  4. Shridhar Puntambekar28 November 2018 at 23:27

    Hey Bob,

    Thank you for this code.
    I have just one question regarding the ToastReceiver component. In the source code, it is not being used anywhere, so how does the message fired from the VF page get received?

    The code is not working for me.
    Please help.

    Best Regards,
    Shri

    ReplyDelete
    Replies
    1. ToastReceiver is listening for messages posted by the Visualforce page. You add it to the utility bar and when it receives a message it raises the toast.

      Delete