Friday 10th July, 2020
By Sandy Galloway

Editor QR Scanner Integration

Recently we received an interesting forum post asking if it was possible to integrate a QR scanner into Editor. I'm glad to say that it is indeed possible and in this blog post I'll demonstrate how to create a field type plug-in for Editor which enables a QR code to be read into a field. I've used the third-party Instascan library to implement this within your own system.

Before we dive any deeper, here is a preview of what you can expect from this plug-in. Just edit or create a new row and press the "Scan" button next to the Description fields input element. From here you should be able to scan a QR code and see the result in the input element.

Name City Description

Quick start

If you'd like to use Editor with Instascan as a QR scanner, you can do so by installing Instascan from NPM (npm install --save instascan) or directly import it in a script tag:

JS

You will also need the integration plug-in for it to work with Editor, which can be downloaded here:

JS

Finally, (yes it is that easy!) you will need to set the fields.type option to be qr - e.g.:

fields: [
    {
        label: 'Code:',
        name: 'code',
        type: 'qr'
    },
    // ...
]

From here you will find that when you edit or create a record you are able to press the scan button that appears next to the input for the field that you have designated as a QR field. This should then display a live feed to your webcam and when you scan a QR code the value will be shown in the input element.

Building the plug-in

Using it is easy enough, so let's dive into how it all works, and we can investigate how to create custom field type plug-ins for Editor.

Instascan

First we need a QR code scanner and Instascan is a library we can use for that. It describes itself as a "Real time webcam-driven HTML5 QR code scanner", which is perfect for what we need.

There are a couple of API methods and configuration options that we will not discuss here but they may be useful to you, I'd encourage you to read through their readme file on github so that you can set up Instascan to suit your own needs. They also have a demo page which demonstrates the functionality that they provide nicely.

Integrating Instascan with Editor

To make the QR code scanner fully reusable and allow for multiple QR codes in a single form, we'll create a custom field type plug-ins for Editor. We need to define three functions for our Editor plug-in:

  • create - Called when the field is first initialised
  • get - Get the value from the field
  • set - Set the value into the field.

create

This is where nearly everything interesting in this plug-in happens! Here is the structure of our function, broken down into sections (as it becomes quite large when all put together):

create: function (conf) {
    create: function (conf) {
        // Section 1 - DOM setup, including container
        // Section 2 - Instascan setup
        // Section 3 - Toggle control
        // Section 4 - Close behaviour

        return container;
    },

The first section in our create function is used to construct the DOM for the field as we need to bring together a couple of different elements. Note that we assign the input element to the conf object, allowing it to be accessed in the other plug-in methods.

// Section 1 - DOM setup
var safeId = DataTable.Editor.safeId(conf.id);
var video = $('<video/>').css({
    display: 'none',
    'max-width': '100%',
    padding: '2% 0%',
});
var input = $('<input id="' + safeId + '"/>');
var scan = $('<button>Scan</button>').css({ margin: '0% 1%' });
var container = $('<div/>').append(input).append(scan).append(video);

conf.qrInput = input;

Next up is to initialise the Instascan instance. We need to add a scan listener to the Instascan instance. This will be triggered when a QR code is detected and will set the input element's value to be the value of the QR code. Another touch that is added here is to add a border to both the input element and the video feed for half a second. This visually indicates to the user that a scan has taken place.

var scanner = new Instascan.Scanner({ video: video[0] });
scanner.addListener('scan', function (content) {
    input.val(content).css({ border: 'blue 2px solid' });
    video.css({ border: 'blue 2px solid' });

    setTimeout(() => {
        input.css({ border: 'none' });
        video.css({ border: 'none' });
    }, 500);
});

Now that we have an Instascan instance ready, we need to use it! In this plug-in we'll activate the camera for a field when there is a click on the the scan button element defined above. To enable the scan to start we use the Instascan.Camera.getCameras method which returns a Promise followed by the start() method for the instance. We'll also show the video element so the user can see what the camera sees. The inverse of the toggle calls the stop() method of the Instascan instance. In both cases the button's content is updated to reflect what clicking on it will do.

// Section 3 - Toggle control
scan.on('click', function () {
    if (this.innerHTML === 'Scan') {
        Instascan.Camera.getCameras()
            .then(function (cameras) {
                if (cameras.length > 0) {
                    video.css({ display: 'block' });
                    scanner.start(cameras[0]);
                } else {
                    console.error('No cameras found.');
                }
            })
            .catch(function (e) {
                console.error(e);
            });

        this.innerHTML = 'Stop';
    } else if (this.innerHTML === 'Stop') {
        video.css({ display: 'none' });
        scanner.stop();
        this.innerHTML = 'Scan';
    }
});

The final thing to add is a listener for the closing of the form to ensure that we stop scanning for QR codes and turn off the camera when the form is closed.

// Section 4 - Close behaviour
this.on('close', function () {
    scanner.stop();
});

get

The get function is a simple one. Because we have added the input element to the config object in the create function and the Instascan scan event handler we installed in the create function writes the value to it, we can just take it's value:

get: function ( conf ) {
    return conf.qrInput.val();
}

set

The set function is also relatively straight forward, in this case setting the value given into the input element:

set: function (conf, val) {
    conf.qrInput.val(val !== null ? val : '');
},

Putting all of these functions together into a plug in gives us the behaviour that is shown above. The final script can be downloaded from:

JS

Advanced Use

There are of course more advanced ways to make use of a QR reader integration with Editor. You may have QR codes that represent a series of strings separated by commas that you would like to place directly into a table. You could read the code, split the result up and place it in each field respectively, or based on the scanned code, query another API to fill out other fields (e.g. a geo-location API).

It would also be possible to implement a count system. Say you have an inventory of items and you need to scan each one and keep track of the count of each. You could run Instascan outside of the table and use the DataTables API to increment the count for each item type on each scan.

Feedback

As always, we are keen to hear how you are using DataTables and Editor. Please drop us a message in the forum with how you are getting on with our software, or if you have run into any problems, or have ideas for future enhancements. We would love to know if people are able to integrate Instascan into their projects.

Additions Following Feedback

Since publishin this blog post we have recieved some feedback on how it can be improved on mobile devices, both for Android and IOS. Thanks to Daniel Bierschwale for contributing these.

Rear Camera

Most mobile devices have both a rear and front facing camera, which one you want to target may well depend on the device that you are using and the application. On android accessing the rear camera is simple, you simply need to start scanning on the second camera. This means changing

scanner.start(cameras[0]);

to this

scanner.start(cameras[1]);

However on IOS it is not this simple. As detailed in this Github issue, you have to modify the camera.js file and set

facingMode: "environment"

A downside to this however is that you are unable to switch between the cameras.

First Frame Freezing on IOS Fix

When opening the camera for the first time on IOS sometimes the video does not play straight away and only the first frame is displayed. This can be fixed by editing the video tag to be

<video autoplay muted id="video" controls="true"/>