Showing posts with label iphone. Show all posts
Showing posts with label iphone. Show all posts

Friday, 7 March 2014

Salesforce1 and Visualforce - Things I've Learned

At the February Meetup of the London Salesforce Developers Group, I presented a session where I went through some of the things I’ve learned while building Visualforce solutions to run in Salesforce1. The slide deck for this session is available on SlideShare, but some of the slides might be difficult to understand without the words that went with them, so in a departure from my usual blogging style I’ve decided to pick out some slides from the deck and explain what I was trying to show.

Capturing Input

Screen Shot 2014 03 06 at 12 37 10

Screen Shot 2014 03 06 at 12 37 43

The <apex:inputField /> component is not supported in Salesforce1if it creates a widget - e.g. date picker (although this component seems to work a lot of the time in iOS but not android).  Rather than trying to implement your own version of a Visualforce input, its better to use the HTML5 type attribute and let the browser determine the appropriate input mechanism to display.  The second slide shows how the the iPhone reacts to type attributes - displaying a date  spinner for a date and a custom keyboard including digits for number.  Unfortunately there isn’t a type for a lookup to another record, so you’ll still need to roll your own solution to this.

Screen Shot 2014 03 06 at 12 44 40

Something many people complain about when using HTML5 or Hybrid mobile applications is that clicking buttons can be unresponsive. Often this is down to the default behaviour of the webkit based browser (so Safari or Chrome at the least), which is to wait 300m/s after a click to see if the user is actually carrying out a double-click.  While this makes sense on the desktop, double tapping isn’t that common in mobile applications (although it is sound advice in Zombieland). The way that I typically work around this is to bind to the touchstart/end events rather than the click event.  When working in JQuery Mobile this is easy, as the framework adds support for these events.  Otherwise, built-in browser support is patchy so you may end up having to use another JavaScript solution such as the Event or Zepto packages. 

window.open()

The next block of slides relates to using window.open() to open a child browser window rather than the regular webview - this only works for iOS, but has allowed me to work around a few issues:

Rotating the iPhone application simply results in a sideways view of the page:

Screen Shot 2014 03 06 at 13 00 46

whereas rotating a page opened in the child browser works as expected:

Screen Shot 2014 03 06 at 13 00 59

List jumping has been acknowledged by Salesforce as a known issue. What happens here is that if you have a clickable list of items that is larger than the viewport, when you scroll down and click an entry, the application jumps back to the top of the list after the click.  However, I found this to be worse when using Bootstrap, in that the jump to the top took place before the click had been handled, resulting in the click being applied to the wrong element.

In the slide below, I’ve clicked on the Blog 5 entry to expand, but the app has jumped back to the top and expanded the Blog 11 entry,  I originally found this when placing delete buttons on a list, so it wasn’t just an irritant, it was affecting the data:

Screen Shot 2014 03 06 at 13 02 24

If I open the list in a child browser though, the item that I click on is the one that expands and the view stays at the correct place:

Screen Shot 2014 03 06 at 13 05 45

The next issue that I covered involved Bootstrap responsive tables.  The way that Boostrap handles tables is to leave it sized as it is, but wrap it in a scrolling element.  This means that when a page containing a responsive table is accessed on a small screen device, the elements outside the table will wrap and the table becomes scrollable:

Screen Shot 2014 03 06 at 13 07 06

Accessing this page in the Salesforce1 app, however, and the page is resized to the width of the table, with the text spanning the full width.  Even worse, if you have any dialogs in the page, they will be the full width of the page too, which means that you would need to scroll around the page to find the dialog message:

Screen Shot 2014 03 06 at 13 10 10

Opening the page inside the child browser respects the fact that the table is scrollable and reflows the text appropriately for the device:

Screen Shot 2014 03 06 at 13 11 40

As I’ve blogged before though, the child browser introduces some additional complexity - you can’t tell that you are in the Salesforce one context for a start, and as I mentioned earlier, it isn’t a solution for Android (although the responsive tables do behave correctly on my Nexus 7 so there is slightly less need for it).

 The final points I made were around mobile development best practice:

Screen Shot 2014 03 06 at 13 16 51

If you are designing applications that will genuinely be used while on the road, with all of the connection and bandwidth issues that brings, you really don’t want to be using much Visualforce at all, instead you should rebuild the app to do most of its work on the device. I presented a session on this topic at Dreamforce that applies equally well to Salesforce1 - you can watch the video here.

 

Saturday, 1 February 2014

Reading QR Codes in Salesforce1

Updated 22/02/2015 to detail the iPhone 6 experience)

(This post covers something I was pretty sure wouldn’t work, so I’m rather pleased to be writing about the solution)

As part of my Ticket to Ride Dreamforce session, I built a mobile application that scanned a QR code and retrieved some information from Salesforce.  I’ve been trying to figure out since then how I can do the same thing in Salesforce1.

The mobile application used the Google Zebra Crossing (zxing) Cordova plugin, but I don’t have the capability to add plugins to Salesforce1 so that wasn’t an option.  Most solutions I’ve looked into rely on capturing a QR code, uploading to a server and receiving a response containing the decoded value.  I got this working using a combination of Salesforce1 and Node.js, but its a pretty awful user experience, and I really wouldn’t want to use this mechanism when I was connecting over 3G.

Some googling led me to jsqrcode, a port of zxing to JavaScript, which sounded exactly what I was looking for. This processes a captured image client side (or server side, if you use the equivalent node.js package).  Hence the title of this post is “Reading” rather than “Scanning”.  The problem with this approach is it is far slower and more error prone than scanning, as you are into a capture, try to scan, repeat if fail loop rather than re-orienting the device until it can scan the code.  So why do it, I hear you ask?  The big upside is that you remain in the Salesforce1 application.  Thus, while using a third party application (or using the mobile SDK to build my own) improves the scanning user experience and the likelihood of success, the best I can do is send the user to a Salesforce URL in the browser afterwards, as Salesforce1 doesn’t have a URL scheme aside from the chatter functionality.

My experiments with jsqrcode indicate that capturing an image and processing it on a phone just doesn’t work.  I suspect this is a combination of the size of the images captured (3000x2000 pixels) and the lack of processing power on the device.  I’ve tried reducing the size of the image that the library processes, but due to the way that they are prepared for processing (written to a fixed size canvas) the image is downscaled which also causes a problem.  The same images can be processed okay on an iPad  or desktop, which leads me to believe the processing power is the bigger issue.  If you really need to do this then sending the image back to the server is the only way I’ve found that works. Update: 22/02/2015 - trying this on an iPhone 6 with a QR code emailed as an image works fine, so it looks like the processing power was the problem. It still seems pretty flaky when taking a picture though.

I first created a QR code that when decoded gives the id of a Salesforce record. I then downloaded the zip of the jsqrcode plugin and uploaded this to Salesforce as a static resource and added the various JavaScript files in the order mandated on the site:

<script type="text/javascript"
      src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.2.min.js"></script>
    
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/grid.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/version.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/detector.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/formatinf.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/errorlevel.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/bitmat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/datablock.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/bmparser.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/datamask.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/rsdecoder.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/gf256poly.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/gf256.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/decoder.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/qrcode.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/findpat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/alignpat.js')}"></script>
<script type="text/javascript" src="{!URLFOR($Resource.QRCode, 'jsqrcode-master/src/databr.js')}"></script>

I then have a simple form with JavaScript functions attached to the buttons:

  <form>
    <input type="file" onchange="previewFile()" /><br/>
  <h1>Preview</h1>
   <div style="height:200px">
    <img src="" id="preview" height="200" alt="Image preview..." />
   </div>
  </form>

  <p>If the image above looks clear, click the decode button.  If not, try again!</p>
  <button id="decode" onclick="decode()">Decode</button>

Upon first loading the page, a callback is registered with jsqrcode to alert the result of the code and then redirect to the record matching the id, using the sforce.one JavaScript object if it is available, otherwise using the standard UI URL:

  function read(a)
  {
        alert(a);
		if( (typeof sforce != 'undefined') && (sforce != null) ) {
        	sforce.one.navigateToSObject(a);
        }
        else {
        	window.location="/" + a;
		}
  }
        
  $(document).ready(function() {
        qrcode.callback = read;
  });
  

When the user takes a picture via the file input, the following JavaScript produces a preview:

function previewFile() {
  var preview = document.querySelector('#preview');
  var file    = document.querySelector('input[type=file]').files[0];
  var reader  = new FileReader();

  reader.onloadend = function () {
    preview.src = reader.result;
  }

  if (file) {
    reader.readAsDataURL(file);
  } else {
    preview.src = "";
  }
}

and if the code looks clear enough, clicking the decode button executes the jsqrcode decode function using the source of the image preview:

function decode() {
    try
    {
    var preview=document.querySelector('#preview');
    qrcode.decode(preview.src);
    }
    catch (e)
    {
       alert('Error - ' + e);
    }
}

The full page is available at this gist, and here are some screen shots of it in action:

Screen Shot 2014 01 30 at 16 33 47

Clicking the button to choose a file allows me to capture a new image or pick one already on my iPad:

 Screen Shot 2014 01 30 at 16 36 07

I choose to capture an image that is being displayed on my macbook air which renders into the preview section:

Screen Shot 2014 02 01 at 12 40 31

And if I’m happy with it, I can press the button to decode.  This takes a couple of seconds and then, all things being equal, I’m taken to the record:

 IMG 0067

If there’s a problem, I’ll receive an alert and I can can have another go.  I’ve been pleasantly surprised by how well the codes can be processed - I managed to scan my Dreamforce badge without any issues!  

Finally, take a step back and appreciate what has been done here - an image has been captured from the device, previewed, and the contained QR code decoded, all on the client in HTML5 and JavaScript.  I think that’s pretty amazing and I’m continually impressed by how much more can be done these days without resorting to server side code.

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!