Reputation: 8717
I'd like to be able to scan barcodes via a hand held scanner and handle the results with Javascript.
A barcode-scanner works almost like a keyboard. It outputs the scanned/translated (barcode->number) data raw (right?). Actually I just need to catch the output and proceed. But how?
Here's some pseudocode I'd like to make work:
$(document).on("scanButtonDown", "document", function(e) {
// get scanned content
var scannedProductId = this.getScannedContent();
// get product
var product = getProductById(scannedProductId);
// add productname to list
$("#product_list").append("<li>" + product.name + "</li>");
});
I found this and this good questions but I'd like to get more information about the handling. Just to focus a textarea may be not enough in my case.
Upvotes: 74
Views: 172700
Reputation: 66
If you are using react, this hook for handling the reader could be useful
import { useState, useEffect, useCallback } from 'react';
interface ScannerOptions {
prefix?: string;
suffix?: string;
maxDelay?: number;
}
interface ScannerConfig {
scannerOptions?: ScannerOptions;
}
type OnScanHandler = (scannedCode: string) => void;
export const useBarcodeScanner = (
config: ScannerConfig = {},
onScan: OnScanHandler
) => {
const [sequence, setSequence] = useState<string>('');
const [timeoutId, setTimeoutId] = useState<number | null>(null);
const resetSequence = useCallback(() => {
setSequence('');
if (timeoutId !== null) {
clearTimeout(timeoutId);
setTimeoutId(null);
}
}, [timeoutId]);
const addToSequence = useCallback(
(key: string) => {
const character = encodeKey(key);
const { prefix = '', suffix = '\n', maxDelay = 1000 } = config.scannerOptions || {};
if (timeoutId) {
clearTimeout(timeoutId);
}
const newTimeoutId = window.setTimeout(() => {
resetSequence();
}, maxDelay);
setTimeoutId(newTimeoutId);
if (character === prefix || character === suffix || character.length === 1) {
setSequence((prev) => prev + character);
if (character === suffix) {
onScan(sequence + character);
resetSequence();
}
}
},
[config.scannerOptions, onScan, resetSequence, sequence, timeoutId]
);
const handleKeyPress = useCallback(
(event: KeyboardEvent) => {
addToSequence(event.key);
},
[addToSequence]
);
useEffect(() => {
window.addEventListener('keydown', handleKeyPress);
return () => {
window.removeEventListener('keydown', handleKeyPress);
};
}, [handleKeyPress]);
return {
reset: resetSequence,
};
};
const encodeKey = (key: string): string => {
switch (key) {
case 'Enter':
return '\n';
case 'Tab':
return '\t';
case 'Spacebar':
return ' ';
default:
return key;
}
};
Upvotes: 0
Reputation: 1743
BarcodeScanner.vue
component code is here:
<template>
<input type="hidden" name="_barcode" v-model="finalCode" />
</template>
<script>
export default {
props: {
onSuccess: {
type: Function,
required: true
},
minLength: {
type: Number,
default: () => 10
}
},
data() {
return {
code: "",
finalCode: "",
fromScanner: false,
reading: false
};
},
mounted() {
document.addEventListener("keypress", this.documentKeyboardListener);
},
destroyed() {
document.removeEventListener("keypress", this.documentKeyboardListener);
},
methods: {
documentKeyboardListener(e) {
if (e.target.nodeName !== 'BODY') return;
if (e.code === "Enter") {
if (this.reading && this.code.length > this.minLength) {
if (this.onSuccess)
this.onSuccess(this.code);
this.finalCode = this.code;
this.code = "";
this.fromScanner = true;
}
} else {
this.code += e.key; //while this is not an 'enter' it stores the every key
}
//run a timeout of 200ms at the first read and clear everything
if (!this.reading) {
this.reading = true;
setTimeout(() => {
this.code = "";
this.reading = false;
this.fromScanner = false;
}, 200); //200 works fine for me but you can adjust it
}
},
},
};
</script>
You can invoke the component anywhere:
...
<barcode-scanner onSuccess="yourListener"/>
...
(Js scanner code is taken from Hanz Herdel)
Upvotes: 2
Reputation: 686
This code works fine for me, you can try it
var barcode = '';
var interval;
document.addEventListener('keydown', function(evt) {
if (evt.code === 'F12'){
evt.preventDefault();
}
if (interval){
clearInterval(interval);
}
if (evt.code == 'Enter') {
if (barcode){
$('#barcode').val(barcode);
console.log(barcode);
}
barcode = '';
return;
}
if (evt.key != 'Shift'){
barcode += evt.key;
}
interval = setInterval(() => barcode = '', 20);
});
Upvotes: 1
Reputation: 91
I wanted to share this topic using React too, as I struggled a lot with it.
I think most of the barcode scanners, such as Hanz Herdel said, terminate with ENTER. In my case, I found easier to wrap the input in a form and catch the submission event, prevent default and retrieve the value of the input.
I preferred this type of approach so to handle any type of barcode length, instead to check the length of it.
Here's how I handled it in React:
import { useState } from "react";
export default function Modal() {
const [repairArticles, setRepairArticles] = useState([]);
function handleBarcodeInput(e) {
e.preventDefault();
const input = e.target.querySelector("input");
const value = input.value;
setRepairArticles((prev) => {
return (prev = [...prev, value]);
});
input.value = "";
}
return (
<div>
<form onSubmit={(e) => handleBarcodeInput(e)} >
<input id="barcode-input" />
<button type="submit" className="hidden" />
</form>
<div className="mt-3">
{repairArticles.map((el, index) => {
return <p key={index}>{el}</p>;
})}
</div>
</div>
)
}
Upvotes: 5
Reputation: 219
Needs hardening but this routine, which counts on scanned data being sent in under 100ms, is working in production. Thanks to @jfbloom22 and other answers for inspiration and reminding me of monitorEvents.
It appears scanners need to be set to send "HID Keyboard"-type data(?) and be set to terminate with "Enter".
Although is purely JavaScript logic, was written in TypeScript app for a PCF (Power Apps Component Framework) project that allows the app to accept scan data without the need to focus on an input box. Globals were used as a convenience.
public Scan(evt: Event): void {
const e:KeyboardEvent = evt as KeyboardEvent;
const timeDiff = e.timeStamp - CheckInPCF.LastTimeStamp;
CheckInPCF.LastTimeStamp = e.timeStamp; //"global"
//console.log(e.key + ': ' + timeDiff);
if (timeDiff < 100) {
if (e.key == 'Enter') {
//Assemble complete scan text
CheckInPCF.ScanText = CheckInPCF.FirstCharacterCandidate + CheckInPCF.ScanText; //.replace('\u000D','');
//console.log('finished: ' + CheckInPCF.ScanText);
CheckInPCF._this._notifyOutputChanged(); //Power Apps related
}
else {
CheckInPCF.ScanText += e.key;
}
}
else {
CheckInPCF.ScanText = '';
CheckInPCF.FirstCharacterCandidate = e.key;
}
}
Upvotes: 2
Reputation: 301
I'm little late but I made this work around based in some answers here.
let code = "";
let reading = false;
document.addEventListener('keypress', e => {
//usually scanners throw an 'Enter' key at the end of read
if (e.keyCode === 13) {
if(code.length > 10) {
console.log(code);
/// code ready to use
code = "";
}
} else {
code += e.key; //while this is not an 'enter' it stores the every key
}
//run a timeout of 200ms at the first read and clear everything
if(!reading) {
reading = true;
setTimeout(() => {
code = "";
reading = false;
}, 200); //200 works fine for me but you can adjust it
}
});
Upvotes: 20
Reputation: 368
Tried all the solutions, but not worked as expected. I found very easiest solution onscan.js I have application using angular 8.
Very simple and good implementation.
For angular 8, I followed steps:
1.npm install onscan.js --save
2.open angular.json, add one entry to script array as "node_modules/onscan.js/onscan.min.js"
3.In component class, implement interface AfterViewInit
declare var onscan:any;
ngAfterViewInit(): void {
//Put focus to textbox and press scanner button
onScan.attachTo(document, {
suffixKeyCodes: [13], // enter-key expected at the end of a scan
reactToPaste: true, // Compatibility to built-in scanners in paste-mode (as opposed to keyboard-mode)
onScan: function (sCode, iQty) { // Alternative to document.addEventListener('scan')
console.log('Scanned: ' + iQty + 'x ' + sCode);
},
});
}
Best thing is scanned text appears into focued textbox element
Hope this help.
Upvotes: 6
Reputation: 871
Here is working fine.
It's working when input has focus and input hasn't focus
on_scanner() // init function
function on_scanner() {
let is_event = false; // for check just one event declaration
let input = document.getElementById("scanner");
input.addEventListener("focus", function () {
if (!is_event) {
is_event = true;
input.addEventListener("keypress", function (e) {
setTimeout(function () {
if (e.keyCode == 13) {
scanner(input.value); // use value as you need
input.select();
}
}, 500)
})
}
});
document.addEventListener("keypress", function (e) {
if (e.target.tagName !== "INPUT") {
input.focus();
}
});
}
function scanner(value) {
if (value == '') return;
console.log(value)
}
HTML
<input type="text" id="scanner" placeholder="scanner">
Upvotes: 4
Reputation: 832
After a lot of research and testing, what worked the best for me was to capture input from a barcode scanner without focusing a form input. Listen to the keydown
and textInput
events.
The textInput
event acts like a paste
event. It has then entire barcode data. In my case I am looking for UPC barcodes. The e.preventDefault()
prevents the barcode data from being inserted into a form input:
document.addEventListener('textInput', function (e){
if(e.data.length >= 6){
console.log('IR scan textInput', e.data);
e.preventDefault();
}
});
I have tested this on Android 4.4 and 7.0 with a CipherLab IR scanner.
Example for listening to the keydown
event. In my case I am able to assume that as long as a form input is not focused, the user is scanning a barcode.
let UPC = '';
document.addEventListener("keydown", function(e) {
const textInput = e.key || String.fromCharCode(e.keyCode);
const targetName = e.target.localName;
let newUPC = '';
if (textInput && textInput.length === 1 && targetName !== 'input'){
newUPC = UPC+textInput;
if (newUPC.length >= 6) {
console.log('barcode scanned: ', newUPC);
}
}
});
Of course, rather than checking the length of the string to determine a scan, you can listen for the e.keyCode === 13
in the keydown
event listener.
Not all IR scanners will trigger the textInput
event. If your device does not, then you can check to see if it is emitting something similar with:
monitorEvents(document.body);
Found this monitoring trick here: How do you log all events fired by an element in jQuery?
Upvotes: 26
Reputation: 111
This is an extension to the answer given by Hanz Herdel incase you are using one of the PosX scanners or any other scanner that are capable of adding a special symbol to the beginning of the characters. In this case, the tilde (~) symbol:
let barcode = "";
let reading = false;
document.addEventListener("keydown", e => {
//console.log(e.key);
if (e.key == 'Enter') {
if (barcode.length == 17) {
if (barcode.charAt(0) == '~') {
console.log(barcode);
barcode = "";
}
}
}
else {
if (e.key != 'Shift') {
barcode += e.key;
}
}
if (!reading) {
reading = true;
setTimeout( () => {
barcode = "";
reading = false;
}, 200);
}
}, true)
You can change the barcode length and the timeout speed to your liking but this worked perfect for me.
Upvotes: 2
Reputation: 898
var txt = "";
function selectBarcode() {
if (txt != $("#focus").val()) {
setTimeout('use_rfid()', 1000);
txt = $("#focus").val();
}
$("#focus").select();
setTimeout('selectBarcode()', 1000);
}
$(document).ready(function () {
setTimeout(selectBarcode(),1000);
});
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="text" name="tag" id="focus" placeholder="Use handheld RFID scanner">
Upvotes: 1
Reputation: 2391
I've just started working on a plugin that handles barcode scanning and credit card scanning (built on jQuery):
https://github.com/ericuldall/jquery-pos
Simple implementation:
$(function(){
$(document).pos();
$(document).on('scan.pos.barcode', function(event){
var barcode = event.code;
//handle your code here....
});
});
So far this plugin is only tested with one type of scanner and codes containing only digits, but if you have further requirements that aren't working with it, I'd be happy to adapt it to your needs. Please check out the github page and give it a whirl. Contributions are encouraged.
E
Upvotes: 1
Reputation: 10675
Your pseudo code won't work, because you don't have access to the scanner to catch events like scanButtonDown
. Your only option is a HID scanner, which behaves exactly like a keyboard. To differentiate scanner input from keyboard input you have two options: Timer-based or prefix-based.
Timer-based
The scanner is likely to input characters much quicker than a user can (sensibly) with a keyboard. Calculate how quickly keystrokes are being received and buffer fast input into a variable to pass to your getProductsId
function. @Vitall wrote a reusable jQuery solution for catching barcode scanner input, you would just need to catch the onbarcodescanned event.
Prefix-based
Most scanners can be configured to prefix all scanned data. You can use the prefix to start intercepting all input and once you've got your barcode you stop intercepting input.
Full disclosure: I work as a consultant to Socket Mobile, Inc. who make handheld scanners.
Upvotes: 64
Reputation: 944202
A barcode-scanner works almost like a keyboard.
It depends on the model. Every one that I've used works exactly like a keyboard (at least as far as the computer is concerned)
It outputs the scanned/translated (barcode->number) data raw (right?).
It outputs keycodes.
$(document).on("scanButtonDown"
You probably want keypress
, not scanButtonDown
.
Look at the event object to determine the "key" that was pressed.
To determine when the entire code has been scanned, you might get an "end of data" key (possibly a space or a return) or you might have to just count how many characters are being input.
Upvotes: 7