QR code and Offline storage
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
Try it in Sails: adding QR code for login link in person index
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.