Sunday 22 April 2012

Mobile Apps with Visualforce and JQuery Mobile

I've been spending some of my time building mobile functionality recently.  One thing that has put me off in the past has been the multitude of devices that need to be supported if I want to maximize usage.  Building the same functionality for iOS, Android and others doesn't fill me with joy.

HTML5 has the "write once, run anywhere" capability that first attracted me to Java back in JDK 1.0 days. The downside to this it that it does reduce the functionality available.  For example, offline storage is problematic at the moment with different browsers supporting different standards, and the standards themselves being subject to change, while access to native functionality is still in very early days (Android makes the camera available through javascript but there aren't many other examples out there).   It very much feels like the future though, so I've been heading down that route.

My first foray into developing a mobile front end was for our BGFM product for Dreamforce 2010.  This was simply some Visualforce pages sized and styled appropriately for the iPhone.  The obvious downside to that was that a device with any other form factor needed separate pages (or at least pages that could adjust themselves appropriately).  However, towards the middle of 2011 I came across JQuery Mobile (JQM) and starting using the 1.0 alpha releases.  The great thing about JQM is that it takes care of the cross device side of things, leaving you with one app that works across all popular smartphones, tablets etc.  Even better, it will also work against older devices, dropping back down to basic HTML if necessary.  For details, documentation, tutorials and samples, check out the JQuery Mobile site.

Building Visualforce pages that leverage JQM is pretty straightforward.  Lifting from the getting started page from the JQM site:

<apex:page showHeader="false" sidebar="false" standardStyleSheets="false">
<html> 
    <head> 
    <title>My Page</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
    <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
</head> 
<body> 

<div data-role="page">

    <div data-role="header">
        <h1>My Title</h1>
    </div><!-- /header -->

    <div data-role="content">   
           <ul data-role="listview" data-inset="true" data-filter="true">
          <li><a href="#">Acura</a></li>
          <li><a href="#">Audi</a></li>
          <li><a href="#">BMW</a></li>
          <li><a href="#">Cadillac</a></li>
          <li><a href="#">Ferrari</a></li>
           </ul>
    </div><!-- /content -->

</div><!-- /page -->

</body>
</html>
</apex:page>

Opening this in a mobile device gives:




















As you can see from the code, I've had to do nothing to style this appropriately for the device, simply giving the unordered list a data-role of listview means that JQM takes care of all the heavy lifting. Adding buttons for simple navigation is also straightforward, and JQM provides some transitions to mimic native apps. Again, its all taken care of by the markup:

<a href="index.html" data-role="button" data-inline="true">Cancel</a>
<a href="index.html" data-role="button" data-inline="true" data-theme="b">Save</a>

resulting in a couple of appropriately styled buttons at the bottom of the page:





















Where things get a little more interesting is if you have a form in the page and the buttons are submitting the form rather than simply moving to a new page, as is the case in most of the Visualforce pages I write.

In that case I'd like to use an <apex:commandLink> component, but I can't provide the additional attributes to lay things out correctly - e.g. data-inline="true". While I could write some Javascript to take care of this, in the first instance I'm trying to keep things simple, so I use an <apex:actionFunction> component and tie this to the JQM specific link via an onlick handler as follows:

   <apex:form id="jsfrm">
    <apex:actionFunction action="{!save}" name="save"/>
          .....
      <a href="#" data-role="button" data-inline="true" 
                onclick="$.mobile.showPageLoadingMsg(); save()">Save</a>
          .....
   </apex:form>

The $.mobile.showPageLoadingMsg(); function call simply displays a spinner to let the user know something is happening.

 Standard <apex:inputField/> components also render well most of the time - sometimes I end up using the input text/textarea/checkbox etc to allow for a greater level of control, but a basic form can be created with the minimum of effort.  For example, a simple form to create an account by specifying its name and industry only requires the following markup:

<apex:page showHeader="false" sidebar="false" standardStyleSheets="false" standardController="Account">
<html> 
    <head> 
    <title>Create Account</title> 
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
    <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
</head> 
<body> 

<div data-role="page">

    <div data-role="header">
        <h1>Create Account</h1>
    </div><!-- /header -->

    <div data-role="content">   
      <apex:form id="jsfrm">
        <apex:actionFunction action="{!save}" name="save"/>
        <apex:outputLabel for="name" value="Name"/>
        <apex:inputField id="name" value="{!Account.Name}"/>
        
        <apex:outputLabel for="industry" value="Industry"/>
        <apex:inputField id="industry" value="{!Account.Industry}"/>
        
        <a href="index.html" data-role="button" data-inline="true">Cancel</a>
        <a href="index.html" data-role="button" data-inline="true" data-theme="b" 
                   onclick="$.mobile.showPageLoadingMsg(); save();">Save</a><br />
      </apex:form>
     </div><!-- /content -->

</div><!-- /page -->

</body>
</html>
</apex:page>

Produces the page shown below - not too bad for a few lines of markup:




















Obviously once the record is saved, the user is redirected to the standard view page which looks pretty awful on a phone, so standard controllers probably aren't going to be a silver bullet for this, making it the usual challenge for editions below enterprise.

I've put together a demo application that allows a contact to take a survey. Its hosted on an unauthenticated Force.com site so is publicly available.  A couple of sample screen shots are shown below:



If you'd like to try out the demo application, simply click here.

A couple of points to note:

  • I've put this together over a few weekends so I wouldn't be in the least bit surprised if there were some glitches waiting.  
  • I've only tested it with an iPhone and webkit browser, though I can't see why there would be issues with different devices.
  • This is a real functioning application, so the feedback will be stored in my Salesforce instance - so if you feel like leaving real feedback, I may even act on it!

16 comments:

  1. I tried it with Opera Mini Browser on Android (S2) and it looks real good.

    ReplyDelete
  2. Bob, It worked like charm on my android device.

    A quick question, i have a custom apex/visualforce solution that i am planning to make it mobile ready. should changing the regular jQuery library to jQuery mobile does the trick?

    Your page was like wizard , so how does this behave for single interface pages which have buttons and pop-ups?

    What are your thoughts?

    Thanks,
    Harry Jobs

    ReplyDelete
    Replies
    1. Hi Harry,

      Each of the pages in the survey can act as a standalone. This isn't a true JQueryMobile "app" where all pages are present as divs in a single HTML page. Thus it should behave appropriately.

      In terms of making it mobile ready, you'll need to change the attributes on the HTML elements in the main, and change the page structure to map that of JQM. I don't think you'll just be able to flip it that easily - I've built mine from scratch from the page side, but been able to reuse controllers across JQM and regular pages.

      Delete
  3. Thanks, Bob for your insight.

    Best,
    Harry

    ReplyDelete
  4. Hello,

    your blog has very useful content for developer prospective.
    thanks for the help man.

    ReplyDelete
  5. You can do it with some simple CSS tricks or jQuery plugins. Try the effect as a background, or as a header or footer. Don't go overboard and frustrate your users.

    jquery jobs

    ReplyDelete
  6. Is there any way to refer to the new account page after saving the record without going to the standard salesforce page?.

    regards
    rk

    ReplyDelete
    Replies
    1. Definitely. The simplest way would be to create an extension controller that implements its own save method. This delegates the actual writing to the database to the standard controller, and can then return a reference to whatever page you like.

      Delete
  7. Hi Bob, thanks for the awesome work. Did you try swipe function from jquerymobile on VisualForce page? I tried to implement that, but no luck. If you know how, could you write a post for that?

    Thanks

    ReplyDelete
  8. Hello Bob, how is onclick="save();" is associated with the apex:actionfunction Save? I am not able to get the code running.

    ReplyDelete
    Replies
    1. When you define an actionfunction, this creates a JavaScript function based on the name. So as I have an actionfunction with the name attribute of 'save", Visualforce creates a JavaScript function called 'save' that executes the postback. Thus I can execute the save() method from the onclick handler and that will post the form back. Check out the docs for actionfunction at : http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_actionFunction.htm

      Delete
  9. good post thank you
    anyone can easily learn

    ReplyDelete
  10. great post with proper example
    thanks dear

    ReplyDelete
  11. Hi Bob

    I just copied and tried the code here in my dev org. Somehow the scripts do not load and give me a plain looking vf page page.

    Could you please advise?

    Regards
    Rajat

    ReplyDelete
  12. Great post and insights. I really want to thank you for taking the time to put this together.
    Nowadays, with a variety of uses sky is the limit for mobile applications. An amazing mobile app regularly makes a similarly great online vicinity in the applications market.

    ReplyDelete