Recently I’ve been porting a few apps to run in Salesforce1 - typicality these are HTML5 apps that users ran in the device browser and had to log in to each time they wanted to use them, but Salesforce1 avoids that.
As well as porting, I was trying to improve the user experience and make the apps behave consistently if they were running in the browser or inside Salesforce1, and I got caught out a couple of times with behaviour that was either unexpected or something I just couldn’t do. I should stress that these aren’t shortcomings of Salesforce1, its more about how Visualforce pages are displayed inside web view containers.
Pages Opened in the Child Browser don’t have the sforce.one JavaScript object
In a project I was working on recently I had a Visualforce page in Salesforce1 that could be used to open other Visualforce pages. I originally developed this using the sforce.one navigation methods, which opened the relative links in the same page with back buttons etc. Each of these pages relied on the sforce.one JavaScript object being present to provide mobile specific behaviour.
I then refactored this, as I wanted to open in a child browser window so that the user could bounce around a few other pages but retain the close button to jump straight back to the main page. While the sforce.one.navigateToURL() should open absolute URLs in a child browser, even if I specified the target Visualforce page as an absolute URL, the platform appeared to know that it was on the same site and opened it as a relative URL, without using a child browser window.
Relying on my knowledge of the Salesforce mobile SDK, I used the window.open() JavaScript function, which opened the page in the desired fashion, but all of my mobile functionality was broken. After a short amount of digging around it transpired that the sforce.one object was undefined, so as far as my page was concerned it was running on a regular browser.
I’ve put together a couple of pages to demo this. Both pages output some content to describe whether they are running in Salesforce1 or not, and the first page provides a link to the second
Page1:
<apex:page showheader="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false">
<html>
<head>
<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"></meta>
</head>
<body>
<apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"/>
<p style="font-size:24px; text-align:center">Page 1</p>
<div style="display:none" id="sf1">
<p style="font-size:20px; text-align:center">I am running in Salesforce1 :)</p>
</div>
<div style="display:none" id="notsf1">
<p style="font-size:20px; text-align:center">I am not running in Salesforce1 :(</p>
</div>
<p><a href="javascript:sforce.one.navigateToURL('/apex/SforceDotTwo');">Click to open page two.</a></p>
<script>
$(document).ready(function () {
if ( (typeof sforce != 'undefined') && (sforce != null) ) {
$('#sf1').toggle();
}
else {
$('#notsf1').toggle();
}
});
</script>
</body>
</html>
</apex:page>
Page2 :
<apex:page showheader="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false">
<html>
<head>
<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"></meta>
</head>
<body>
<apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"/>
<p style="font-size:24px; text-align:center">Page 2</p>
<div style="display:none" id="sf1">
<p style="font-size:20px; text-align:center">I am running in Salesforce1 :)</p>
</div>
<div style="display:none" id="notsf1">
<p style="font-size:20px; text-align:center">I am not running in Salesforce1 :(</p>
</div>
<script>
$(document).ready(function () {
if ( (typeof sforce != 'undefined') && (sforce != null) ) {
$('#sf1').toggle();
}
else {
$('#notsf1').toggle();
}
});
</script>
</body>
</html>
</apex:page>
Opening the first page in Salesforce1 displays the “Running in Salesforce1” message as expected:
while clicking the link to go to Page 2 navigates in the same window, including a back button, and displays the message as expected:
Changing the page 2 navigation link to the following:
<p><a href="javascript:window.open('/apex/SforceDotTwo');">Click to open page two.</a></p>
and clicking the link displays the second page in a child browser window (note the close button), but the message has now changed to indicate that the application is not running in Salesforce1:
Accessing external URLs via window.open generates an embedded browser window on a mobile device, with a close button. Don’t use navigation here, as you’ll end up pulling your app into the embedded browser as well as the containing app. When running from the desktop it will open in the same browser window where you would want navigation.
As I was just relying on knowing that I was running in Salesforce1 to style the page and add a few buttons, I could just pass a parameter on the URL to indicate I was running in the application.
HTML5 or Installed Application?
When a Visualforce page is rendered in Salesforce1, there’s no way that I’ve been able to find to determine whether Salesforce1 is the HTML5 web application running in the device’s browser or the installed application. Checking the User Agent for the browser doesn’t help, as they will report the same values based on the device itself.
Update 24/02/2014: The iOS versions of Salesforce1 installed application report a user agent containing the string ‘SalesforceTouchContainer’, so this can be used to determine that the page is running in the installed application. On my Nexus 7, however, the only difference is in the version of Chrome which obviously can’t be relied on to be the same on another Nexus 7, so Android is still problematic. Of course, relying on the user agent is a somewhat flawed approach as applications like the Dolphin web browser allow you to set your own user agent string, but it is the best that we have.
Most of the time I don’t care about this, but I have hit one situation where I wanted to render a particular toolbar button for the installed application but not the HTML5 web application. There is also no concept of a child browser in the HTML5 application - URLs open in the current window or a new tab/window depending on the browser configuration and how the link is constructed.
This isn’t specific to Salesforce1 - it appears to be problematic for Cordova hybrid apps in general, and most of the solutions rely on inspecting the JavaScript window object to see if the cordova object is present, or trying to react to deviceReady events. Unfortunately, as Visualforce is iframed into the Salesforce1 application, all of this information is hidden from my Visualforce page so I ended up reworking my pages to behave in an identical but slightly more clunky fashion in all cases.
If anyone has a solution to this I’d love to hear about it.