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:
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.