QR code and Offline storage

QC code handling

Generating QR code example 1: Using external API service

To generate a QR code image, to most easier way is to use external service. For example, http://goqr.me/api/ provides a QR code generation service by URL.

Example Usage


<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Example" />

The image encoded a string data “Example”.

Parameters

  • size: QR code image size in width x height
  • data: encoded data

Please modify person/index.ejs, adding the following image at the top.

<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=<%='https://sailsjs.com/'%>" />

Generating QR code example 2: qrcode-generator module

Instead of depending on an external API service, we could also use third-party module to generate QR code locally. qrcode-generator is module that works on both Sails (Nodejs) and Browser environment

Adding the module to project

npm i --save qrcode-generator

Modify PersonController.index action

index: async function (req, res) {
	var qrcode = require('qrcode-generator');
	var qr = qrcode(4, 'L');
	qr.addData('https//sailsjs.com/');
	qr.make();
	var persons = await Person.find();
	return res.view('person/index', { 'persons': persons, 'qrsrc':qr.createDataURL() });
},

Modify person index.ejs

We then replace the previous image src with following.

<img src="<%- qrsrc %>" />

Scanning QR example 1: using Camera in Browser

jsQR is a third-party QR code scanning module that works on both Sails (Nodejs) and browser. So that we could use Browser to capture images via HTML5 getUserMedia API.

IMPORTANT: Using Camera in Browser only works for HTTPS website. Thus, we need to setup a ngrok tunnel for our Sails.

./ngrok http 1337

Adding dependency

For nodejs

npm i --save jsqr

For using in Browser

<script src="https://cdn.jsdelivr.net/npm/jsqr@1.1.1/dist/jsQR.js"></script>

Adding Controller action

Please add the following controller action to PersonController.

qrcode: async function(req, res) {
	return res.view('person/qrcode');
},

Adding ejs file

Please create person/qrcode.ejs as follows.

<script src="https://cdn.jsdelivr.net/npm/jsqr@1.1.1/dist/jsQR.js"></script>
<form action="/person/qrcode" method="POST">
<input id="qrinput" name="qrcode" />
<input type="submit" />
</form>
<canvas id="canvas"></canvas>
<script>
	var video = document.createElement("video");
	var canvasElement = document.getElementById("canvas");
	var canvas = canvasElement.getContext("2d");
	var qrinput = document.getElementById('qrinput');
	function drawLine(begin, end, color) {
		canvas.beginPath();
		canvas.moveTo(begin.x, begin.y);
		canvas.lineTo(end.x, end.y);
		canvas.lineWidth = 4;
		canvas.strokeStyle = color;
		canvas.stroke();
	}
	function onVideoFrame() {
		if (video.readyState === video.HAVE_ENOUGH_DATA) {
			canvasElement.height = video.videoHeight;
			canvasElement.width = video.videoWidth;
			canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
			var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
			var code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert" });
			if (code) {
				drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
				drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
				drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
				drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
				qrinput.value = code.data;
			}
		}
		requestAnimationFrame(onVideoFrame);
	} 
	(async function() {
		video.srcObject = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }});
		video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
		video.play();
		requestAnimationFrame(onVideoFrame);
	})();
</script>

Config route

Please add the following route in config/routes.js.

'/person/qrcode':'PersonController.qrcode',

Scanning QR example 2: External scanner hardware

A external barcode scanner is just act as a keyboard device. It would works for both Mobile and Desktop. To facilitate the hardware, we need to set autofocus to the target input element.

<input id="qrinput" name="qrcode" autofocus />

The scanned QR code data would fill-in to the input immediately by the scanner hardware.

Offline storage

Checking online/offline status

HTML5 provide us a constant to check for the device online/offline status. It is Navigator.onLine. You could see most browsers supported this feature in https://caniuse.com/#feat=online-status.

if (navigator.onLine) {
	// currently online
} else {
	// currently offline
}

IndexedDB via Dexie.js

IndexedDB is a low-level API for client-side storage (in web browser) of large amounts of structured data, including files/blobs. Thus, there are several wrappers built on top of IndexedDB for providing easy-to-access high-level API. So that developers could use it in a convenient way.

We are going to use Dexie.js, one of a wrapper for IndexedDB. It works for both Nodejs and Browser. Thus, it makes IndexedDB available for Nodejs application as a local database API.

Adding module

For Nodejs

npm i --save dexie

For Browser

<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>

Example 1: Implementing QR code scan history

Adding code to offline database after scan

Please add the following code at the beginning of <script> in person/qrcode.ejs.

<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>
<script>
var db = new Dexie("qrcode_database");
db.version(1).stores({
  qrcode: '++id,code'
});
//...
</script>

Adding a history controller action

Please add the following controller action to PersonController.

qrhistory: async function(req, res) {
	return res.view('person/qrhistory');
},

Adding a history ejs

Please create person/qrcode.ejs as follows.

<ul id="result">
</ul>
<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>
<script>
var db = new Dexie("qrcode_database");
db.version(1).stores({
	qrcode: '++id,code'
});

(async function() {
	let data = await db.qrcode.toArray();
	console.log(data);
	document.getElementById('result').innerHTML = data.map(d => `<li>${d.code.data}</li>`).join('');
})();
</script>

Config history route

Please add the following route in config/routes.js.

'/person/qrhistory':'PersonController.qrhistory',

HTML5 Web Storage

localStorage is a native HTML5 API introduced in the early stage of HTML5. Thus, most browsers support this feature without any external JS or Polyfill, even IE 8.

Example 1: Adding remember username option to login

Please modify user/login.ejs as follows.

Adding a checkbox for remember username

Please add the follow HTML before <button> of the login form.

<form ...>
	<!-- ... -->
	<div class="form-group">
		<input type="checkbox" id="remember" />
		Remember username
	</div>
	<button ...>
</form>

Adding Javascript for condition checking

Before function submitForm in <script> tag, we add the 3 lines for receiving the username from localStorage if it has value. At the beginning of function submitForm, we storage the username value from input to localStorage.

var username = localStorage.getItem('username');
if (username) {
	document.getElementById('exampleInputEmail1').value = username;
}

async function submitForm(oFormElement) {
	var remember = document.getElementById('remember').checked;
	if (remember) {
		localStorage.setItem('username', document.getElementById('exampleInputEmail1').value);
	}
	//...
}

Scanning QR using Browser

Scanning QR code by external scanner

Offline storage

HTML5 localStorage

IndexedDB via Dexie.js

Written with StackEdit.