Stat Tracker

Monday, December 16, 2013

Capturing Signatures with HTML5 Canvas in Salesforce 1 Mobile

Recently Salesforce.com released Salesforce 1, their latest mobile application. Salesforce 1 releases a number of new features that enable developers to create mobile applications. One person I spoke with recently at Dreamforce wanted to know how they could capture signatures in the mobile application. With HTML5 Canvas, Visualforce, and a little JavaScript, you can easily roll your own lightweight signature capture functionality in Salesforce 1.

Here is a brief demonstration video:


So I have included the entire source code below, but a few items about the tricky parts.

1. You will need to setup JavaScript event listeners on the canvas for touchstart, touchmove, and touchend. That is what the canvas will execute when you touch and drag your finger on it.

2. You will need to use JavaScript Remoting to ensure that you properly pass the Canvas content into your Apex Controller so that it can save it. The canvas can be converted into a Base64 String with the Canvase.toDataURI() method. That is how you get the bytes from the Canvas into an Attachment in Salesforce.com.

These are illustrated in the sample code.

And here is the source code for the VF and Apex. If you put these into a Visualforce Tab, and make it enabled for Salesforce 1 Mobile, then you easily reuse this sample code.

jQuery: http://code.jquery.com/jquery-2.1.1.min.js

jQuery Mobile Resources (Download Links)
Version 1.3.2: http://jquerymobile.com/resources/download/jquery.mobile-1.3.2.zip

Latest Version 1.4.4: http://jquerymobile.com/resources/download/jquery.mobile-1.4.4.zip

Source Code:

Visualforce Page Code:

<apex:page controller="AnyObjectSignatureController" showheader="false" sidebar="false" standardStylesheets="false">
<script>var $j = jQuery.noConflict();</script>
<apex:stylesheet value="{!URLFOR($Resource.jquerymobile,'/jquerymobile/jquery.mobile-1.3.2.min.css')}"/>
<apex:includeScript value="{!URLFOR($Resource.jquery)}"  />
<apex:includeScript value="{!URLFOR($Resource.jquerymobile,'/jquerymobile/jquery.mobile-1.3.2.min.js')}"/>

<div data-role="page" id="signatureCaptureHome"> 
<div data-role="content">
<input id="accountNameId" type="text" name="accountName"/>
<input type="button" name="findAccountBtn" onclick="findAccounts();" value="Find Accounts"/>
<h1 id="recordSigId">Record Signature:</h1>
<canvas id="signatureCanvas" height="200px" width="300px"/>
<input id="saveSigButton" type="button" name="SigCap" onclick="saveSignature();" value="Capture Signature"></input>
</div> 
</div> 
<div data-role="page" id="signatureCaptureHome"> 
<div data-role="content">
<input id="accountNameId" type="text" name="accountName"/>
<input type="button" name="findAccountBtn" onclick="findAccounts();" value="Find Accounts"/>
</div> 
</div> 

<script>

    var canvas;
    var context;
    var drawingUtil;
    var isDrawing = false;
    var accountId = '';

function DrawingUtil() 
{
    isDrawing = false;
    canvas.addEventListener("touchstart",start,false);
    canvas.addEventListener("touchmove",draw,false);
    canvas.addEventListener("touchend",stop,false);
    context.strokeStyle = "#FFF";  
}

//Start Event for Signature Captuare on HTML5 Canvas
function start(event) 
{
    isDrawing = true;
    canvas = document.getElementById("signatureCanvas");
    context = canvas.getContext("2d");    
    context.strokeStyle = "rgba(155,0,0,0.5)";      
    context.beginPath();
     context.moveTo(event.touches[0].pageX - canvas.getBoundingClientRect().left,event.touches[0].pageY - canvas.getBoundingClientRect().top);
}

//Event while someone is drawing to caputre the path while they draw....
function draw(event) {
    event.preventDefault();
    if(isDrawing) {     
        context.lineTo(event.touches[0].pageX - canvas.getBoundingClientRect().left,event.touches[0].pageY - canvas.getBoundingClientRect().top);
        context.stroke();
    }
}


//Event when someone stops drawing their signature line
function stop(event) {
    if(isDrawing) {
        context.stroke();
        context.closePath();
        isDrawing = false;
    }
}

canvas = document.getElementById("signatureCanvas");
context = canvas.getContext("2d");
drawingUtil = new DrawingUtil(canvas);

function saveSignature()
{
var strDataURI = canvas.toDataURL();
    // alert(strDataURI);
    strDataURI = strDataURI.replace(/^data:image\/(png|jpg);base64,/, "");
//alert(strDataURI);
AnyObjectSignatureController.saveSignature(strDataURI,accountId,processResult);
}

function processResult(result)
{
alert(JSON.stringify(result));
}

function findAccounts()
{
var nameValue = document.getElementById("accountNameId").value;
AnyObjectSignatureController.findAccounts(nameValue, processSearchResult);

function processSearchResult(result)
{
$j = jQuery.noConflict();
//$j("#accountList").html("");
$j.each(result, function(i, record) {accountId = record.Id; $j("#recordSigId").html("Record Signature: " + record.Name);});
$j("#recordSigId").trigger("update");
//$j("#accountList").trigger("update");
//alert(JSON.stringify(result));
}


</script>

</apex:page>

Apex Controller:
global with sharing class AnyObjectSignatureController 
{
public AnyObjectSignatureController()
{
}
@RemoteAction
global static List<Account> findAccounts(String name)
{
name = '%' + name + '%';
List<Account> accounts = [Select Id, Name from Account where Name like :name];
return accounts;
}
@RemoteAction
global static String saveSignature(String signatureBody, String parentId) 
{
try
{
system.debug('Record Id == ' + parentId);
system.debug(signatureBody);
Attachment a = new Attachment();
a.ParentId = parentId;
a.Body = EncodingUtil.base64Decode(signatureBody);
a.ContentType = 'image/png';
a.Name = 'Signature Capture.png';
insert a;
return '{success:true, attachId:' + a.Id + '}';
}catch(Exception e)
{
return JSON.serialize(e);
}
return null;
}

}

Sunday, October 13, 2013

Dreamforce 2013 Sessions - Lets Rock

Dreamforce is upon us! In just a few weeks San Francisco will turn into the mecca for cloud computing with almost 100,000 cloud devotees making the annual pilgrimage.  This will be my third year presenting at Dreamforce and I can honestly say each year the Developer Zone has gotten better and better. This year I will be presenting or contributing on four different sessions. And for the first time I'll be co-presenting a session with another person! I'm excited to be working with Tim McDonald on our administrator and developer session.

Come check out the sessions, contribute to the conversations on the chatter feeds, and get your brain ready for data downloads!

Case Study: Building a Mobile App for Field Services

Wednesday, November 20th: 4:00 PM - 4:30 PM
Moscone Center West, Mobile Theater

Description

The Salesforce Platform allows you to architect complete solutions for entire lines of business, whether it's desktop or mobile users. Join us as we focus on how users can build a fully featured mobile solution for Field Service engineers. 

By dissecting an HTML 5 Hybrid Application for the Service Cloud, you'll get exposure to building a complete mobile application using the jQuery Mobile for UI, Salesforce Mobile SDK for Security &amp; REST API Access, NFC Phonegap Plugin for Serial Number Scanning and Automatic Case Assignment, HTML5 Canvas for Signature Capture Camera Access for Case Documentation &amp; Attachemnt on the Case in Salesforce, and Chatter API for Social Feeds on Cases.
Speakers:
Cory CowgillThe Warranty Group

Apex Trigger Debugging: Solving the Hard Problems

Wednesday, November 20th: 11:45 AM - 12:30 PM
Moscone Center West, 2020
Full

Description

Apex Triggers can be your best friend or your worst enemy. When a trigger is firing properly your data is under control and remains sane, but when a trigger doesn't fire properly, your users can be faced with the frustration of exceptions when saving a record, or worse: incorrect data. Join us to learn tips and tricks on how to debug and solve the most complex issues, including: Ambiguous Field Validation, After Insert Activity Errors, and SOQL and Governor Limit Errors. You'll learn the origins of these kinds of advanced trigger issues and gain solutions for avoiding them.
Speakers:
Cory Cowgill

Clicks AND Code: A Dreamforce Session for Administrators AND Developers

Wednesday, November 20th: 9:00 AM - 9:50 AM
The Westin St. Francis San Francisco, California West
Full

Description

Administrators seem to have adopted the mantra of “Clicks not Code.” However, more often than not, the customization of the Salesforce Platform through the use of code provided by a developer is not only necessary, but required for a successful implementation. Join us as we present best practices for administrators to use when engaging their developer counterparts, while providing some tips and tricks for developers to quickly respond to the requests placed before them.
Speakers:
Cory CowgillWest Monroe Partners
Tim McDonaldNew Tangram, LLC

Force.com Careers: How Do I Get There From Here?

Thursday, November 21st: 11:00 AM - 11:45 AM
Moscone Center West, 2020

Description

Do you love developing on the Salesforce Platform, but wonder what the next steps are for your career? Join our panelists to hear about various career paths, including Consultant, Architect, Product Manager, and AppExchange Developer, to name a few. These experts will share the pros and cons of their careers and also the path to get there.
Speakers:
Carolina Ruiz MedinaFinancialForce.com
Cory CowgillWest Monroe Partners
Leah McGowen-Haresalesforce.com
Cheryl Porrosalesforce.com
Ayori Selassiesalesforce.com
Andy OgnenoffCloud Sherpas
Kevin O'HaraLevelEleven


Can't get into one of my sessions because its full? Don't worry, sessions, presentations and source code will be distributed to the general public after the sessions. Have a question for me? Hit me up in the Developer Zone during the conference. I usually camp out there either by the hackathon, theaters, or coffee station. Hit me up in dreamforce chatter or on twitter @corycowgill and I'm happy to discuss anything Force.com related.

Looking forward to another awesome year!



Wednesday, July 24, 2013

Force.com Data Model - Enumeration Tables versus Picklists

The Salesforce Platform allows customers to build robust, relational data models to suit any need. In fact, with tools like Schema Builder it is so simple to get started building that it can be a bit of a double-edged sword. The simplicity allows functions that once rested solely in the hands of a Database administrator to be performed by a Business Analyst, or even the End Users. However, with great power always comes great responsibility.

The number one problem I have encountered working with clients who have performed Salesforce.com self-implementations is data model related. There are several common mistakes self implementers should avoid. In this blog post I'll be discussing how heavy data normalization can work against you on the platform.

This problem often occurs when the implementation was run by an internal IT team who have traditional SQL skill sets. They will create a custom object for every single enumeration table they think is needed without regards to how SFDC relational data models actually work (picklists for example).
Heavily Normalized Data Model in MySQL

This leads to headaches when building standard reports, and usability issues when viewing and editing data with standard SFDC pages. For the above example the mult-select picklist for "Payment Options" would show up as a Related List, and the Marketing Status would show up as a Lookup. If we created this same data model in SFDC it would look like this:
Erroneously built Data Model in SFDC - Heavily Normalized Data Model in SFDC

And this would manifest itself on the Standard UI as this:

Illustration: Ugly UI

The Related List at the bottom "allows" for the multi select picklist, and the lookup in the detail section allows for the lookup. This is very nasty for end users! Imagine if you had dozens of multi-select picklists! You would have dozens of related lists! And the users would have to click multiple times to enter a payment option, and they need to do a lookup each time for the marketing status.

Not to mention they can't easily filter on Payment Options for List Views and Reports.

This can easily be corrected by using picklist and multi-select picklists in SFDC. If we use those types of fields our data model removes those 3 objects and everything resides on the Company object. The correct data model looks like this:
Who! Only 1 object! 


And it manifests itself in the UI like this:
Much Cleaner!

This is much cleaner in the UI, allows easy reporting and filtering, and saves us 3 objects we don't have to build on the back end.

In future posts I'll discuss the inverse problem where data is too heavily de-normalized on the platform.

In short, the key to building successful data models on the platform is to delicately balance the need for custom objects ("tables") and features of the Salesforce platform (picklists, multi-select picklists, record types, etc).