Context is Everything
You Are in a Maze of Twisty Passages, All Alike
When building applications around Lightning components on the Salesforce platform, there comes a time when you need to know where you are - in Salesforce1 or the Lightning Experience for example. Winter 16 upped the ante by allowing Lightning components to be embedded in Visualforce pages. I was expecting there to be a mechanism to get at this information via the Lightning component framework, but unfortunately that turned out not to be the case, so I had to spend some time investigating.
Desperately Seeking Context
My problem was how to figure out from inside a Lightning component whether I was executing in:
- Salesforce1
- Lightning Experience (LightingX)
- Visualforce inside Salesforce1/LightningX
- Visualforce in Salesforce Classic
Lightning Detector
Looking through the docs, there are a couple of events that are only available to components executing in Salesforce1/LightingX:
- force:showToast
This displays a message in a popup (or really a popdown, as it appears under the header at the top of the view) - force:navigateToURL
This allows navigation to a URL from a lightning component
The docs state that it these methods are only available in Salesforce1, but as far as I’ve been able to tell, LightningX is pretty much Salesforce1 for the desktop.
In my JavaScript controller or helper, I can attempt to instantiate the event and based on the success or failure, deduce whether I am in the Lightning context:
isLightning: function() { return $A.get("e.force:showToast"); }
Mobilize
The availability of the above events tells my component if it is executing in Lightning, but not which of Salesforce1 / LightningX is the container. The only way I’ve found to reliably detect this is to use the blunt instrument that is the user agent,
I’ve tested on an iPhone, iPad and Android phone, both for the HTML5 and installed application versions of Salesforce1, and the user agent string always contains the word ‘mobile’. However, when I am running LightningX on the desktop, this is not the case.
Note that this is by no means exhaustive testing, so if you are considering using this technique make sure to verify this behaviour on your target devices.
The controller/helper can detect mobile based on the presence or absence of mobile in the user agent as follows:
isMobile: function() { var userAgent=window.navigator.userAgent.toLowerCase(); return (-1!=userAgent.indexOf('mobile')); }
Visualforce
I’ve found that this scenario causes the most confusion, as the assumption is that as code from a lightning component is executing, the context must somehow switch to Lightning while that is happening. A better way to think about this is in terms of the container. In Salesforce1 and LightningX, the containing application is built with Lightning components, whereas when a Lightning component is embedded in a Visualforce page, the page is the container.
The ability to detect if a Visualforce page is embedded in a Salesforce1 application has been around for a while now, and it is equally applicable to LightningX - check if the sforce and sforce.one objects are present. I can detect this in my controller/helper as follows:
hasSforceOne : function() { var sf; try { sf=(sforce && sforce.one); } catch (exc) { sf=false; } return sf; }
Putting it all Together
Going back to my list of scenarios, from my lightning component I can detect each of these using the functions defined earlier as follows:
- Salesforce1
isLightning() && isMobile()
- Lightning Experience (LightingX)
isLightning() && (!isMobile())
- Visualforce inside Salesforce1/LightningX
(!isLightning()) && hasSforceOne()
- Visualforce in Salesforce Classic
(!isLightning()) && (!hasSforceOne())