174 lines
5.3 KiB
JavaScript
174 lines
5.3 KiB
JavaScript
var scanner = (function() {
|
|
var _callback = null;
|
|
var _allow_raw = true;
|
|
var _allowed_types = null;
|
|
var _camera = null;
|
|
var _modal = null;
|
|
var _buffer = '';
|
|
var _last_key_ts = 0;
|
|
var _max_key_gap = 0;
|
|
var _HW_THRESHOLD = 50;
|
|
|
|
function _on_paste(e) {
|
|
if (!_callback) return;
|
|
var el = document.activeElement;
|
|
if (el && el.tagName === 'TEXTAREA') return;
|
|
var text = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('text');
|
|
if (!text) return;
|
|
text = text.trim();
|
|
if (text.length < 3) return;
|
|
e.preventDefault();
|
|
_resolve(text);
|
|
}
|
|
|
|
function _on_keydown(e) {
|
|
var now = Date.now();
|
|
var gap = now - _last_key_ts;
|
|
|
|
if (gap > 500) {
|
|
_buffer = '';
|
|
_max_key_gap = 0;
|
|
} else if (_buffer.length > 0) {
|
|
_max_key_gap = Math.max(_max_key_gap, gap);
|
|
}
|
|
_last_key_ts = now;
|
|
|
|
if (e.key === 'Enter' && _buffer.length >= 3) {
|
|
var barcode = _buffer;
|
|
var is_scanner_input = _max_key_gap <= _HW_THRESHOLD;
|
|
_buffer = '';
|
|
_max_key_gap = 0;
|
|
if (is_scanner_input) {
|
|
e.preventDefault();
|
|
_remove_typed_barcode(barcode);
|
|
_resolve(barcode);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (e.key.length === 1) {
|
|
_buffer += e.key;
|
|
}
|
|
}
|
|
|
|
function _remove_typed_barcode(barcode) {
|
|
var el = document.activeElement;
|
|
if (!el || !('value' in el)) return;
|
|
if (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA') return;
|
|
|
|
var value = String(el.value || '');
|
|
if (!value.endsWith(barcode)) return;
|
|
el.value = value.slice(0, value.length - barcode.length);
|
|
$(el).trigger('input');
|
|
}
|
|
|
|
function _resolve(barcode) {
|
|
if (!barcode || !_callback) return;
|
|
|
|
$.ajax({
|
|
url: server_url + 'ics/api/engine/barcode_lookup.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: { json: JSON.stringify({ barcode: barcode, action: 'read' }) },
|
|
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
|
|
success: function(res) {
|
|
if (res && res.success) {
|
|
if ((!_allow_raw && res.type === 'raw') || (_allowed_types && _allowed_types.indexOf(res.type) === -1)) {
|
|
_show_error('This page only accepts generated barcode labels.');
|
|
return;
|
|
}
|
|
_callback(res.sku || '', res.product_name || '', res.uom || '', res);
|
|
return;
|
|
}
|
|
_show_error('Barcode not found: ' + barcode);
|
|
},
|
|
error: function(xhr) {
|
|
var msg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : 'Scanner error. Please try again.';
|
|
_show_error(msg === 'Barcode not found.' ? 'Barcode not found: ' + barcode : msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
function _build_modal() {
|
|
if (_modal) return;
|
|
$('body').append(`
|
|
<div class="modal fade" id="scanner_modal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="ti ti-scan me-2"></i>Scan Barcode</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="scanner_qr_reader"></div>
|
|
<div id="scanner_error" class="alert alert-danger mt-3 d-none"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`);
|
|
_modal = new bootstrap.Modal(document.getElementById('scanner_modal'));
|
|
document.getElementById('scanner_modal').addEventListener('hidden.bs.modal', _stop_camera);
|
|
}
|
|
|
|
function _start_camera() {
|
|
if (_camera) return;
|
|
if (typeof Html5Qrcode === 'undefined') {
|
|
_show_error('Camera scanner library is not available.');
|
|
return;
|
|
}
|
|
_camera = new Html5Qrcode('scanner_qr_reader');
|
|
_camera.start(
|
|
{ facingMode: 'environment' },
|
|
{ fps: 10, qrbox: { width: 250, height: 120 } },
|
|
function(decodedText) {
|
|
_modal.hide();
|
|
_resolve(decodedText);
|
|
},
|
|
function() {}
|
|
).catch(function(err) {
|
|
_show_error('Camera error: ' + err);
|
|
});
|
|
}
|
|
|
|
function _stop_camera() {
|
|
if (!_camera) return;
|
|
_camera.stop().catch(function() {}).finally(function() {
|
|
_camera = null;
|
|
});
|
|
}
|
|
|
|
function _show_error(msg) {
|
|
var $err = $('#scanner_error');
|
|
if ($err.length) {
|
|
$err.text(msg).removeClass('d-none');
|
|
setTimeout(function() { $err.addClass('d-none'); }, 3000);
|
|
return;
|
|
}
|
|
if (typeof bootbox !== 'undefined') bootbox.alert(msg);
|
|
}
|
|
|
|
return {
|
|
init: function(options) {
|
|
options = options || {};
|
|
_callback = options.onScan || null;
|
|
_allow_raw = options.allowRaw !== false;
|
|
_allowed_types = Array.isArray(options.allowedTypes) ? options.allowedTypes : null;
|
|
document.removeEventListener('keydown', _on_keydown);
|
|
document.removeEventListener('paste', _on_paste);
|
|
document.addEventListener('keydown', _on_keydown);
|
|
document.addEventListener('paste', _on_paste);
|
|
_build_modal();
|
|
},
|
|
open_camera: function() {
|
|
if (!_modal) _build_modal();
|
|
_modal.show();
|
|
setTimeout(_start_camera, 400);
|
|
},
|
|
destroy: function() {
|
|
document.removeEventListener('keydown', _on_keydown);
|
|
document.removeEventListener('paste', _on_paste);
|
|
_stop_camera();
|
|
}
|
|
};
|
|
})();
|