Saturday 11 January 2014

Visualforce in Salesforce1

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:

IMG 1086 

while clicking the link to go to Page 2 navigates in the same window, including a back button, and displays the message as expected:

IMG 1087 

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:

 IMG 1088

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.

 

19 comments:

  1. I found that the user-agent inside salesforce1 doesn't have the word "Safari" like it has on mobile safari.
    Inspecting html inside salesforce1 app is impossible too, did you find a way to do this?

    ReplyDelete
    Replies
    1. I open it in chrome on my desktop - using https:///one/one.app - that lets me see my JavaScript errors in all their glory :)

      Delete
    2. The problem here is inspecting the visualforce page in the iPad. I came across several css bugs/errors that happen only inside the salesforce1 app and not in mobile safari :( but thanks for that one

      Delete
    3. Nelson, you could run Chrome on the iPad.

      Delete
  2. I can't prove it by salesforce1 because my android os is 4.1.1, but I've used window.location.href.substr(0,4).toLowerCase() successfully on my personal phonegap apps to detect install (file) vs browser (http).

    ReplyDelete
  3. It seems this is only a 2 step process support for Salesforce1. If we have a VF page, and we want to open another page from here, we should only be using navigateURL() method to redirect it to, becuase if we used window.open() in that page then the lookup fields in second VF page stops working :( ...

    ReplyDelete
  4. Include these files and sforce.one.navigateToURL and the others ought to work.

    "/jslibrary/1408043316000/sfdc/SfdcCore.js"
    "/auraFW/javascript/9CWByHqele7x78MwTMyO8g/aura_prod.js"
    "/jslibrary/1403810648000/sfdc/AuraAlohaFrameNavigator.js"
    "/sforce/one/30.0/api.js"

    ReplyDelete
  5. In salesforce1, on mobile card click it opens the same vf page in full mode.
    But if i want to open some other vf page on mobile card click is there any way to do this ??
    Can i do this using javascript somehow ??

    ReplyDelete
  6. Hi Bob,

    Do you have hack over the uper icon of back button. I need to know when this back button press in jquery or javascript.

    Please help .

    Thanks

    ReplyDelete
  7. Hi Bob,
    Is there any easy way to detect vf page using salesforce1 or mobile device on server side via apex ?

    ReplyDelete
    Replies
    1. global static Boolean isMobile() {
      String device = System.currentPageReference().getParameters().get('device');
      return (device == 'mobile');
      }

      Delete
  8. Hi Bob

    Is there a way to prepopulate fields on child VF page? My scenario is: I have a page on Opportunity and upon clicking a link another page opens up - new contact page in edit mode. The problem is I want to prepopulate few fields like account name on contact edit page. PLs help.

    Thanks

    ReplyDelete
  9. for me helps incudes this strings:


    order is requared

    ReplyDelete
  10. Its not working in Winter16.

    ReplyDelete
    Replies
    1. Anything in particular? There's a few things in the post above.

      Delete
  11. Hi Bob, Is it mandatory to enable lightening experience to have the sforce.one.navigateToURL(), sforce.one.createRecord() working properly ? In my code i have used something like this in a command link: onclick="javascript:sforce.one.navigateToURL('/00T/e?followup=1&retURL=%2F{!Acc2.id}&tsk5=Call&inline=1'); and it isn't working properly. The navigation is happening in the mobile app, however the full page is not loading. I'm not able to drag the page down or up. It is not dragable or re-sizable. please help

    ReplyDelete
    Replies
    1. Not as far as I'm aware. I don't think URL hacks work in Salesforce1 though.

      Delete
  12. Hi Bob, I also having the same issue related to URL hack.
    In my case, I wants redirect to Report from inline VF page with filter in URL hack using one javascript function:
    function RedirectToReportInLE(){
    var url = '/00O11000000G4ZN?pc0=00N11000001caDV&pn0=eq&pv0={!LEFT(Account.Id, 15)}&retURL=%2F{!Account.Id}';

    sforce.one.navigateToURL(url);
    }

    This function redirect to right report but filter is missing :(.

    I hear a lot about you. Please reply me on above issue ASP. Thanks :)

    ReplyDelete
  13. How can we override back button in SF1?

    ReplyDelete