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!

Friday 6 April 2012

Re-order Formula Report Columns

A non-Visualforce/Apex blog for a change this week. I was working on some reports for our Salesforce project management application, and needed to create a number of summary formula columns. Having created the first three, I wanted to change the order that these formula columns appeared in the report, and was surprised to find that this isn't supported through the UI - if you remove all formula columns from the report and add them back in, they are displayed in the order that they were created.   Searching on the Salesforce Idea Exchange only threw up this open idea.

Below is an example report I've put together to demonstrate this using Accounts.  I've added a single formula column that totals the account employees based on the country:
















I then add a column to total the Annual Revenue, which displays to the right of the Country Employees column:














If I then decide that I'd like the Country Revenue column displayed first, the only way to achieve this through the UI is to delete the formula columns and recreate them in the desired order.  Maybe acceptable when there are only a couple of formula columns, but not an option once there's more than a handful.

Next up was to turn to the metadata API via the Force.com IDE.  As I'd just gone with the defaults when setting up the project, the only metadata components I had were Classes, Pages, Components and Triggers.  Right-clicking (or ctrl-click as I am now a Macbook Air user) the project name brings up the context menu for the project - selecting the Force.com menu followed by the Add/Remove Metadata Components brings up the following dialog:



















Clicking the Add/Remove button (eventually) brings up a list of the available metadata.  Reports appears so I tick the entry for my report:
























and click ok as many times as it takes to close the various dialogs.  When prompted, I select Yes to refresh the project.  Once the revised components have been pulled down from the server, opening the file associated with my report shows :

    <aggregates>
        <calculatedFormula>EMPLOYEES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA1</developerName>
        <isActive>true</isActive>
        <masterLabel>Country Employees</masterLabel>
        <scale>2</scale>
    </aggregates>
    <aggregates>
        <calculatedFormula>SALES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA2</developerName>
        <downGroupingContext>ACCOUNT.ADDRESS1_COUNTRY</downGroupingContext>
        <isActive>true</isActive>
        <masterLabel>Country Revenue</masterLabel>
        <scale>2</scale>
    </aggregates>

Looking promising - not only can I see the formula columns but they also have developer names that imply some sort of ordering. First up I tried changing the order that the columns appear in the XML and saving that. Unfortunately, this change was simply reverted when the file was changed.

Next, I changed the developerName for each column - changing the Country Employees to FORMULA2 and Country Revenue to FORMULA1. Looking better once the save completed - the ordering of the columns in the file had been reversed as shown below:

    <aggregates>
        <calculatedFormula>SALES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA1</developerName>
        <downGroupingContext>ACCOUNT.ADDRESS1_COUNTRY</downGroupingContext>
        <isActive>true</isActive>
        <masterLabel>Country Revenue</masterLabel>
        <scale>2</scale>
    </aggregates>
    <aggregates>
        <calculatedFormula>EMPLOYEES:SUM</calculatedFormula>
        <datatype>number</datatype>
        <developerName>FORMULA2</developerName>
        <isActive>true</isActive>
        <masterLabel>Country Employees</masterLabel>
        <scale>2</scale>
    </aggregates>

Encouraging, but the acid test is to re-run the report:






















Success!  Comparing with the report screenshot above, the Country Revenue and Country Employees columns have changed places.  Not quite a simple as it might be through the UI, but certainly a lot quicker than deleting and recreating formula fields!