Barcode scanning with device camera in SAPUI5 applications (without a native container)
The release of iOS 11 (on September 19, 2017) has bought some good news for anyone wanting to incorporate barcode scanning into their SAPUI5 applications – support has been added to for the media capture API. This means there is now the ability to access a device camera (and microphone) directly from Safari (and all other major browsers except Opera mini – see http://caniuse.com/#feat=stream), opening up the possibility to use barcode scanning in applications without the need for a native container (i.e. Cordova).
In this blog I will demonstrate how a device camera can be accessed within a SAPUI5 application to scan a barcode and populate the value of the barcode into an input field. This will be achieved in a plain SAPUI5 web application which can be accessed via any device that has a camera and a supported browser (we will implement a fallback solution when the application is accessed via a desktop or device with no camera). We will make use of an external JavaScript library called QuaggaJS which will handle the detection and decoding of barcodes.
What you will need
- Some basic SAPUI5 knowledge
- An IDE or text editor
- The latest release of QuaggaJS library
- Somewhere to publish your application
- A mobile device with a camera
It is assumed that you already have (or know how to create) a UI5 project with a component, manifest, view and controller…if not, then suggest you create one via WebIDE using the “SAPUI5 Application” template.
Once you have your project, create a folder “libs” and add the QuaggaJS library (quagga.min.js) under it.
Next, specify the resource in your manifest under sap.ui5 > resources > js (you could chose to load the library using an alternate method if preferred)
"resources": {
"css": [
{
"uri": "css/style.css"
}
],
"js": [
{
"uri": "libs/quagga.min.js"
}
]
}
Add a property to the device model to track whether the device accessing our application has video capability. To check this we use method MediaDevices.getUserMedia. Calling this method will prompt the user to allow access to their camera (if the device has one)…note that it is possible to leave this check to a later time (like when the user attempts to click the scan button which is the actual point the application would like to access the camera), however we are doing it here so that the model property can be used in our view to control the visibility of the scan button.
createDeviceModel: function() {
var oModel = new JSONModel(Device);
oModel.setDefaultBindingMode("OneWay");
// Disable the scan barcode button by default
oModel.setProperty("/barcodeScanEnabled",false);
if(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
navigator.mediaDevices.getUserMedia({video:true}).then(function(stream){
// device supports video, which means will enable the scan button
oModel.setProperty("/barcodeScanEnabled",true);
}).catch(function(err){
// not supported, barcodeScanEnabled already default to false
});
}
return oModel;
}
Add the following controls to the view: a label, an input control to store the barcode value and a button to trigger the scanning (visible if the device is capable).
<Label text="Barcode value" />
<Input id="scannedValue" placeholder="{= ${device>/barcodeScanEnabled} ? 'Use scan button to enter barcode' : 'Enter barcode manually' }" editable="{= !${device>/barcodeScanEnabled} }" />
<Button icon="sap-icon://bar-code" text="Scan" tooltip="Scan barcode" visible="{device>/barcodeScanEnabled}" press="onScanForValue">
<layoutData>
<l:GridData span="L2 M2" />
</layoutData>
</Button>
Add the two methods below to the controller. The first is the event handler for our scan button press which will create and open a dialog – the content of the dialog is a single HTML div that is used to display the video feed from the camera. We have attached a handler for the afterOpen event which will call method _initQuagga (our second controller method) to initialise Quagga and then start the video stream. For configuration options see https://serratus.github.io/quaggaJS/#configobject.
onScanForValue: function(oEvent){
if(!this._oScanDialog){
this._oScanDialog = new sap.m.Dialog({
title : "Scan barcode",
contentWidth : "640px",
contentHeight : "480px",
horizontalScrolling : false,
verticalScrolling : false,
stretchOnPhone : true,
content : [new sap.ui.core.HTML({
id : this.createId("scanContainer"),
content : "<div />"
})],
endButton : new sap.m.Button({
text : "Cancel",
press : function(oEvent){
this._oScanDialog.close();
}.bind(this)
}),
afterOpen : function(){
this._initQuagga(this.getView().byId("scanContainer").getDomRef()).done(function(){
// Initialisation done, start Quagga
Quagga.start();
}).fail(function(oError){
// Failed to initialise, show message and close dialog...this should not happen as we have
// already checked for camera device ni /model/models.js and hidden the scan button if none detected
MessageBox.error(oError.message.length ? oError.message : ("Failed to initialise Quagga with reason code " + oError.name),{
onClose: function(){
this._oScanDialog.close();
}.bind(this)
});
}.bind(this));
}.bind(this),
afterClose : function(){
// Dialog closed, stop Quagga
Quagga.stop();
}
});
this.getView().addDependent(this._oScanDialog);
}
this._oScanDialog.open();
},
_initQuagga: function(oTarget){
var oDeferred = jQuery.Deferred();
// Initialise Quagga plugin - see https://serratus.github.io/quaggaJS/#configobject for details
Quagga.init({
inputStream: {
type : "LiveStream",
target : oTarget,
constraints : {
width : {min: 640},
height : {min: 480},
facingMode : "environment"
}
},
locator: {
patchSize : "medium",
halfSample : true
},
numOfWorkers : 2,
frequency : 10,
decoder : {
readers : [{
format : "code_128_reader",
config : {}
}]
},
locate : true
}, function(error) {
if (error) {
oDeferred.reject(error);
} else {
oDeferred.resolve();
}
});
if(!this._oQuaggaEventHandlersAttached){
// Attach event handlers...
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
// The following will attempt to draw boxes around detected barcodes
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
}.bind(this));
Quagga.onDetected(function(result) {
// Barcode has been detected, value will be in result.codeResult.code. If requierd, validations can be done
// on result.codeResult.code to ensure the correct format/type of barcode value has been picked up
// Set barcode value in input field
this.getView().byId("scannedValue").setValue(result.codeResult.code);
// Close dialog
this._oScanDialog.close();
}.bind(this));
// Set flag so that event handlers are only attached once...
this._oQuaggaEventHandlersAttached = true;
}
return oDeferred.promise();
}
Final step to be able to test your application is to publish it to a location that is accessible by the device you are testing with – this could be a SAP gateway, SCP or any other web server.
A working example can be found here and the full code can be accessed here.
If you need a sample barcode to scan you can use this one or generate your own here.
Note that the code provided is configured to detect barcodes with symbology of Code 128, alternate formats can be specified in the configuration object passed to Quagga.init.
Hopefully you have found parts of this blog useful…feel free to leave any feedback or questions below.
Nice blog Ian, thanks for sharing. Do you know if/when support for this arrived in Android?
Thanks Mike. It’s already supported in Android if using Android browser, Chrome, Opera (non mini version) or Firefox…other less popular browsers seem to have some support but may require tweaking 🙂
Hi Lan,
I tested in Web IDE ,but here i am not able to see Barcode scanner via camera thru mobile device(andriod).
I got below output ,could you please tell me how to scan barcode via camera ( Andriod device ).
Hi Lan,
Did you solved the issue?
Regards,
Aniu
Hey Lan,
I am able to scan bar code using my laptop camera, but not with mobile or ipad.. Please suggest how can I ? App is asking to Enter bar code manually. Appreciate help.
Thanks
Shashank
hey blount international how are you,
you can solve this issue, I have the same issue too.
thanks regards...
Hai,
I am getting "get user media is not defined" while run in tab and android. but it works on browser.
Can u tell me how to solve the issue for opening the camera in android and tablet.
thanks and regards,
vamsi
Hello Ian,
do you happen to have a solution for scanning QR codes as well?
Regards
Wolfgang
Hey need help please I have this erreur :
Uncaught TypeError: Cannot read property 'setAttribute' of undefined
Did u solve this?
Maybe I can give some hint regarding QuaggaJS library. It won't work for you if you are using an Apple device with iOS-Version < iOS 11. The versions before don't support getUserMedia/Stream API which is necessary for the live stream scanning. Android should work though (support getUserMedia API).
Furthermore QuaggaJS can't be used with every browser. These APIs have to be supported.
Nevertheless I can't confirm this blog example, because I have only an iPhone 5 with iOS 10.3.3.
You can find more information to QuaggaJS here.
Readers of this blog might be interesting in this site, which shows the other features available in the HTML5 API:
https://whatwebcando.today/
It even detects which features are enabled on your browser/device
Thanks Ian for the code.
For those concern, I have tested this app in every ways I can think of, below is the result that can save you some times:
If you are on PC browser then you will be fine.
If you are on Android, WP (yes Windows Phone), you will be fine too.
If you are on iOS 11, only Safari will work. Apple only supports getUserMedia to run on their own Safari. Other browsers have to run on a controlled version of WKWebView. Earlier version of iOS won't work either. Well play, Apple!
This is a well-known issue that you can read more using keyword "getusermedia ios chrome"
Thank you, Jan for the code.
Maybe you can help me. I implemented exactly what you exposed and tried https://ijmacgregor.github.io/sapui5-barcode-scanner/ and it works from my iPhone.
However, my app is deployed: under SCP accound and it doesn;t ask me if I allow my camera to be opened. It only opens the Dialog but no access to camera (neither the pop=up to allow or not as it does when I tried your app). Do you know if this is the reason? Do I need to publishe the application somewhere else?
Thank you very much.
Alexandra
Hi,
I have implemented the example by following the blog and deployed the application on launchpad.
When I navigate back to launchpad and try to open it again, The scan dialog cannot be closed using
I am getting error : "uncaught typeerror: cannot read property 'getopenstate' of null".
Please suggest if any tweak in code required.
Thanks,
Kumar Guarv
Very useful Blog. Thanks for sharing.
Thank you for this blog.
The detection works well. But the border isn't shown in my example. But I use the same code as you... What could be wrong?
HI Ian MacGregor,
Thank you for this blog.
It is working fine for me, but it is working only for code 128 reader, Please help me how the other barcode type will work.
Thanks.