I am at a convention now working a sales space (effectively, no less than once I began penning this), and I actually did not understand how a lot I loved this a part of the job. Whereas I’ve attended a number of conferences publish COVID (err, effectively, “publish” could also be too robust of a phrase), that is the primary sales space I’ve labored at in years. One of many first issues I did once I arrived was verify and see how we have been going to get contacts by way of badge scanning. Not surprisingly, the convention organizers steered a local app. Me being me – I instantly considered how the app’s options might be completed by way of the online. There’s nothing unsuitable with the native app (truly, it is fairly buggy at instances), however I dislike putting in native apps for occasions. 9 instances out of ten I overlook to delete it off my cellphone though I will by no means use it once more. I’ve now constructed a web-based model of the appliance, and whereas it is definitely ugly as hell, I assumed I might share how I did it.
The online app has the next options:
- By way of person interplay, begin a digicam feed so you possibly can level it at a badge and scan the QR code.
- Parse the outcomes from the QR code and allow you to retailer the contact persistently.
- Render the checklist of contacts so you possibly can see who you’ve gotten scanned.
- Lastly, let the person click on to obtain the contacts as a zipper file.
Let’s go into element on how I constructed every of those components.
The QR Scanner
For the primary a part of the appliance, I wanted a QR scanner. I knew that an internet web page might get entry to a person’s digicam (by way of getUserMedia
, an API I’ve used previously) and I knew it might render it to display by way of a video
tag. The onerous half can be that stream and looking for a QR code.
Fortunately I got here throughout an awesome library that simplified most of that work: https://github.com/nimiq/qr-scanner. The library handles getting digicam entry, displaying it on display, and looking for and parse the QR code. As an FYI, there’s a native API for barcode detection that helps QR codes, nevertheless it’s just about a Chromium factor solely now. The QR Scanner library I used will make use of it if it exists although.
After grabbing the required JS library, this is how I used it. First, I started with a video
tag in my structure:
<video id="cam" model="width:300px; peak:300px;"></video>
In JavaScript, there are a number of steps. First, I get a pointer to the DOM factor:
videoElem = doc.querySelector('#cam');
Subsequent, I make an occasion of the scanner:
qrScanner = new QrScanner(
videoElem,
scanResult,
{ returnDetailedScanResult: true },
);
scanResult
is successful handler. To start out scanning, you utilize this technique:
qrScanner.begin();
For my app, I tied this to a button you possibly can click on to start out the scanning course of. The success handler is handed an object that may comprise, shock shock, the results of the scan as textual content. Now got here the enjoyable half.
Parsing the Outcomes
Once I examined my badge at this convention, the QR code contained vCard data. A vCard string is contact info in a considerably easy format. (You’ll be able to learn extra about it on the spec). Here is an instance (supply from https://docs.fileformat.com/electronic mail/vcf/):
BEGIN:VCARD
VERSION:2.1
N:Gump;Forrest;;Mr.
FN:Forrest Gump
ORG:Bubba Gump Shrimp Co.
TITLE:Shrimp Man
PHOTO;GIF:http://www.instance.com/dir_photos/my_photo.gif
TEL;WORK;VOICE:(111) 555-1212
TEL;HOME;VOICE:(404) 555-1212
ADR;WORK;PREF:;;100 Waters Edge;Baytown;LA;30314;United States of America
LABEL;WORK;PREF;ENCODING#QUOTED-PRINTABLE;CHARSET#UTF-8:100 Waters Edge#0D#
#0ABaytown, LA 30314#0D#0AUnited States of America
ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
LABEL;HOME;ENCODING#QUOTED-PRINTABLE;CHARSET#UTF-8:42 Plantation St.#0D#0A#
Baytown, LA 30314#0D#0AUnited States of America
EMAIL:forrestgump@instance.com
REV:20080424T195243Z
END:VCARD
It is not a very tough format and I used to be capable of finding a number of pre-built JavaScript libraries on the market, however there have been all a bit flakey. I made a decision to construct my very own, and whereas it is most likely not utterly sturdy, it does the job. My intent was to parse the fields in addition to give them nicer names the place doable. Here is the perform I wrote:
perform parseVCard(str) {
let outcome = {};
let fieldMap = {
'N':'title',
'FN':'fullname',
'EMAIL':'electronic mail',
'TITLE':'title',
'ORG':'org',
'EMAIL':'electronic mail',
'ADR':'deal with',
'TEL':'phone',
'VERSION':'model'
}
str = str.trim();
str.break up(/[rn]/).forEach(l => {
let [ key, value ] = l.break up(':');
if(key === 'BEGIN' || key === 'END') return;
// Okay, so until key has ; in it, we're easy
if(key.indexOf(';') === -1) {
outcome[fieldMap[key]] = worth.trim();
} else {
// So in concept, it'll ALWAYS be sort=
let [newKey, newType] = key.break up(';');
// and kind may be TYPE=(nothing), so let's simply maintain it easy
newType = newType.exchange('TYPE=','');
/*
so sort ought to all the time be clean or a price, however I've seen FAX,FAX which is not legitimate,
so I'll break up and [0]
*/
if(newType.size) {
newType = newType.break up(',')[0].toLowerCase();
}
outcome[fieldMap[newKey]] = {
sort:newType,
worth:worth
}
}
});
return outcome;
}
For probably the most half, that is simply string parsing, however observe that some fields in a contact report have varieties, like addresses and cellphone numbers. The results of this perform is a pleasant JavaScript object that is an array of fields with nicer names, values, and the place it exists, varieties.
So going again to the scan operation, that is how I deal with it:
perform scanResult(r) {
qrScanner.cease();
contact = parseVCard(r.information);
contactOrig = r.information;
resultElem.innerText = contact.title;
addElem.removeAttribute('disabled');
}
I flip off the present scanner. Parse the info and put it aside in addition to the unique string in a world variable, after which replace the DOM to mirror a brand new scan that got here in. I exploit the title worth as a label.
Did I point out that the UI wasn’t fairly?
So, as a fast check, I requested my two greatest buddies to ship me footage of their badges from latest conferences. One had a vCard and one didn’t, as a substitute having another bizarre ~ delimited format.
12688~Scott~Stroz~noyb@noyb.com~MySQL Developer Advocate~Oracle~5559755049~12345
Alright, so at this level, my app can scan a badge, parse the vCard, and now we have to put it aside.
Persisting Contacts
To deal with persistence, I made a decision to utilize IndexedDB. A number of years again, I went hardcore deep into client-side storage. I wrote posts on it, gave shows, hell I even wrote a guide on it. However because the house hasn’t actually modified a lot (so far as I do know), I have not used it a lot not too long ago. I am undoubtedly going to be performing some extra up to date posts on the subject, however for now, I used the Dexie library. I plan on running a blog extra on this later within the month, however this is an instance of how darn cool it’s.
First, I arrange an initialize my database:
contactsDb = new Dexie('contactsDb');
contactsDb.model(1).shops({contacts:'++id,contact.fullname'})
Within the second line, I outline a set named contacts
with an auto quantity major key and an index on a contact’s title. I did not find yourself utilizing the index, nevertheless it’s there if I would like it. That is not an inventory of each a part of the report I will be saving, simply the vital info associated to keys and indexes.
To really save my information, this is what I did:
await contactsDb.contacts.put({ contact, originalContact:contactOrig, created:new Date() });
Yeah, that is it. I retailer the ‘good’ contact, the unique contact, and a date stamp. However that is actually it. In my app, I wished to render the contacts. I started with an empty desk:
<desk id="contactsTable">
<thead>
<tr>
<th>Title</th>
<th>Created</th>
</tr>
</thead>
<tbody>
</tbody>
</desk>
After which constructed a rendering perform like so:
// earlier within the code
tableElem = doc.querySelector('#contactsTable tbody');
async perform renderContacts() {
let contacts = await contactsDb.contacts.toArray();
let html="";
contacts.forEach(c => {
html += `
<tr>
<td>${c.contact.fullname ?? c.contact.title}</td>
<td>${dtFormat(c.created)}</td>
</tr>`;
});
tableElem.innerHTML = html;
}
The Dexie line is the toArray()
half. Silly easy and a lot simpler than “native” IndexedDB calls. Here is the oh-so-lovely outcome:
Downloading a Zip
For the ultimate a part of the appliance, I added a button that may hearth off a course of to export and save the contacts. I discovered a cool library for this, JSZip. It is most likely one of many best zip libraries I’ve ever seen. When mixed when one other library, FileSaver, this is your entire routine:
async perform downloadContacts() {
let zip = new JSZip();
let contacts = await contactsDb.contacts.toArray();
contacts.forEach(c => {
let file = c.id + '.vcf';
zip.file(file, c.originalContact);
});
zip.generateAsync({ sort: 'blob' }).then(perform (content material) {
saveAs(content material, 'contacts.zip');
});
}
I seize the contacts, iterate, give them a reputation primarily based on the first key, after which simply generate it and put it aside. That is it!
Code + Demo
If you wish to play with this your self and have a QR code containing a vCard, you possibly can see it on-line right here: https://cfjedimaster.github.io/webdemos/badgescanner/index.html I additionally included a snazzy rainbow horizontal rule as a result of why the heck not.
The whole code could also be discovered right here: https://github.com/cfjedimaster/webdemos/tree/grasp/badgescanner
Now, there’s so much to be desired with my demo. It is not mobile-friendly when it comes to structure. Additionally, as straightforward because the QR Scanner library was to make use of, it did get a bit gradual on me at instances. I might maintain up my badge and have to attend for it to ‘see’ the code. Typically it was extremely quick although. I might most likely have a look at the library nearer and discover methods to enhance the efficiency.
As all the time, for those who’ve acquired an opinion on this, please let me know!
Picture by Claudio Schwarz on Unsplash