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:
Clicking the button to choose a file allows me to capture a new image or pick one already on my iPad:
I choose to capture an image that is being displayed on my macbook air which renders into the preview section:
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:
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.
The "Choose File" button doesn't work in the Android version of Salesforce1. Works fine in Chrome mobile interface. Other than that, very cool. Thank you for posting.
ReplyDeleteIt works in the HTML5 version of Salesforce1 (one/one.app) in my version of android as well - 4.1.3.
DeleteHey Bob, this is an excellent article and something I was looking for. Totally appreciate your hard work. After working through the code myself, I have one question for you
ReplyDelete1. What is the URL that your QR code outputs. The application reads the code for me on iPad but and I see the correct URL on alert pop up. But when I press OK, it takes me to a screen which says nothing and when I pull down the screen to refresh it says "The page you are trying to access is not supported on mobile devices".
here is an example of my URL
https://cloudapps-xxxx-xxx--xxxxx.xxxx.my.salesforce.com/a0IZ0000002jJdN (the same URL as the record possesses on web).
Thanks in advance
My code just has the id of a record in the system, then I use the sforce.one() JavaScript object to navigate to the mobile version of that record.
DeleteThanks Bob I got it working except for like @Marco Cestaro said, "Choose File" button doesn't work for Android.
DeleteHi Bob!
ReplyDeleteThank you for great article.
I think a way when we take a picture and than trying to decode it (in browser by JS or on any server) it is not good way initially.
We should integrate Zxing in our App or call their Scanner App by startActivityForResult (in Android) and than get result and work with it.
But we can not do it with SF1, we have no access to its code.
And two questions, hopefully you can tell me something about that:
(1) Is it possible to call another Android App by startActivityForResult from HTML5 (JS) placed in our VF page and opened in SF1 app?
(2) I found a special Scanner attachment on a market with its driver for mobile device (but didn't use it yet). I thought: and how I can integrate it to SF1?
And I got an idea: what if this scanner device could replace a standard camera completely, and we could use it by calling from JS like a regular camera call. And the most important thing that this device could return back to JS not a picture file, but a text string (!) with decoded information. And we could do everything what we want with that information. Do you think it is technically possible in SF1?
thank you
Hi Bob,
ReplyDeleteHow can i access mobile contacts from the Salesforce1 app.
I tried using cordova, but it didn't work.
navigator.contacts.find(["displayName"], function(contacts){
alert('success'+contacts);
}, function(){
alert('failure');
}, {});
Thanks
Hello Bob,
ReplyDeleteNice tutorial.
I was able to decode QR codes in iPhone by resizing the photo taken from the iPhone.
Million Thanks.
This was great Bob!! Thank you!
ReplyDeleteI created an apex class to accept a serial number from the QR scanner and return the record ID:
public with sharing class snlookup {
String sn= ApexPages.currentPage().getParameters().get('sn');
public ApexPages.StandardSetController setCon {
get {
if(setCon == null) {
setCon = new ApexPages.StandardSetController(Database.getQueryLocator([select SN__c, name, Id from unit__c WHERE SN__c = :sn]));
}
return setCon;
}
set;
}
public List getunits() {
return (List) setCon.getRecords();
}
}
Wow! Nice information. There is wonderful on "Reading QR Codes in Salesforce1". I am intimidated by the value of in progression on this website. There are a lot of excellent assets here. Definitely I will visit this place again soon.
ReplyDeleteI think, Today, Quick Response Codes can be a lot more attractive and a lot more useful with the addition of image tools like logos, colors, and photographs.
Here is some helpful information about QR Codes.
Its huge pain. Still can't able to solve it. In android can't able to take picture using mobile camera. Any solutions for it?
ReplyDeletehi Bob,
ReplyDeleteIs there any way using this code, i can scan the image and get the corresponding Id for that.
Thank you
Regards,
Paddy
hi All,
ReplyDeleteI am also getting the following error after I uploaded QR code:
Error - ReferenceError: qrcode is not defined.
hi All,
ReplyDeleteI am also getting the following error after I uploaded QR code:
Error - ReferenceError: qrcode is not defined.
Hi Bob. Thank you for the tutorial. Its amazing. May I know where did you add the VF page button? was it on the home page component?
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteHi Bob. Thank you for this article. I have one question after decoding I need to save the value in Particular field, how this can be achieved?
ReplyDeleteThanks in advance.
The content of the QR code is stored in a variable named 'a' - my stuff then navigates to the object or URL - you would need to take that 'a', store that in a record and then save it.
Delete