In my previous post, I covered how to use a Lightning component in a Visualforce (VF) page and mentioned an issue with navigation events. These events no longer work. The problem is VF pages are loaded into an iframe element in the Lightning Experience. Navigation events like force:navigateToObjectHome are handled by the one.app container in the parent frame. Events raised in the VF page stop at the iframe boundary and don't bubble up into the parent frame.
In this post, I discuss how to get navigation events working in a Lightning Out host like a VF page. My approach avoids the need to detect the host environment inside of a component. Instead, components continue to raise events as usual and are still decoupled from the environment. Events are handled explicitly in the Lightning Out host where the environment detection logic is isolated.
Raising Navigation Events
The first step is to get the navigation events firing properly. The Lightning code below will throw an error when run in a VF page.
The event variable is null because
$A.get() can't find the event type. The issue is the force.* events are not recognized as a dependency by Lightning Out. Fortunately, the fix is straightforward. Add the force dependency to your Lightning dependency app.
With this change, the navigation events will fire properly in your components; however, there's still a problem. Nothing happens. This brings us to the second step which is to add event handlers in the VF host page.
Handling Navigation Events
At this point, events are firing, but nothing is listening. The app.one host comes with built-in listeners that properly handle all the force events, but a VF page is either isolated in its own iframe (Lightning Experience) or running outside of Lightning (Salesforce Classic). This means any VF page hosting a Lightning component will have to explicitly handle navigation events raised by the component and any dependencies. How it handles these events depends on whether the page is running in the Lightning Experience or Salesforce Classic.
Salesforce Classic, on the other hand, does not run app.one. Handlers have to detect this scenario and provide an alternate means to navigate. For example,
window.location may be set with the proper URL corresponding to the type of event.
The code snippet above is from a VF page hosting a component named customObjectEdit. It adds a handler for force:navigateToObjectHome which detects the presence of the sforce.one object. If the object exists,
sforce.one.navigateToObjectHome is invoked to go to the given sObject's home page. If it doesn't exist, the page is running in Classic mode, and
window.location is used instead.
I like this approach because the Lightning component is decoupled from the host and communicates via events. The VF host page handles the events and reacts appropriately.
What About Other Events?
There is a work-around for this that is not supported by Salesforce in any way. I discovered it while looking at the implementation of sforce.one. It uses an object SfdcApp which calls window.postMessage() to forward events to the app.one container. SfdcApp is not intended for public consumption, so Salesforce may change its implementation between releases.
Displaying a Toast Message
With this in mind, a new event handler can be added to the VF page to handle force:showToast.
Just add the snippet above to the
$Lightning.createComponent() callback. The
postEventToOneApp method will forward the event to the app.one container if the SfdcApp object exists (Lightning Experience) or invoke a fallback action when SfdcApp is not available (Salesforce Classic).
To sum things up, a VF page has to handle force events explicitly when hosting Lightning components. The sforce.one library makes this easier when running in the Lightning Experience or Salesforce1. In Classic mode, you'll need to write custom code for each event. Digging deeper, you can use the SfdcApp object to forward events to the one.app container, but this isn't supported by Salesforce.