diff --git a/app/assets/javascripts/addorder.js b/app/assets/javascripts/addorder.js index 02638ca6..f1afd604 100755 --- a/app/assets/javascripts/addorder.js +++ b/app/assets/javascripts/addorder.js @@ -1201,6 +1201,12 @@ $(function() { $("#sxModal").attr('data-for', 'member'); e.preventDefault(); getCardNo(); + + onScan.attachTo(document, { + onScan: function(sCode) { + setCardNo(sCode) + } + }); }); $("#sxModal .btn_cancel").on('click',function(){ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 7317b338..61d4f7a7 100755 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -37,6 +37,7 @@ //= require jquery-fileupload/basic //= require induties/induties.js //= require select2 +//= require onscan.js $(document).ready(function() { $('.count-to').countTo(); diff --git a/app/assets/javascripts/foodcourt.js b/app/assets/javascripts/foodcourt.js index 8e3bf9fa..044e1c3c 100644 --- a/app/assets/javascripts/foodcourt.js +++ b/app/assets/javascripts/foodcourt.js @@ -15,6 +15,7 @@ //= require BSBMaterial/turbolink_admin.js //= require BSBMaterial/demo.js //= require custom.js +//= require onscan.js /* Constant Varaibles */ _CREDIT_PAYMENTS_ = "/   CREDIT PAYMENTS"; diff --git a/app/assets/javascripts/onscan.js b/app/assets/javascripts/onscan.js new file mode 100644 index 00000000..1d90b852 --- /dev/null +++ b/app/assets/javascripts/onscan.js @@ -0,0 +1,373 @@ +/* + * onScan.js - scan-events for hardware barcodes scanners in javascript + */ +var onScan = { + attachTo: function(oDomElement, oOptions) { + if (oDomElement.scannerDetectionData != undefined) { + throw new Error( + "onScan.js is already initialized for DOM element " + oDomElement + ); + } + + var oDefaults = { + onScan: function(sScanned, iQty) {}, // Callback after detection of a successfull scanning: function(){sScancode, iCount)}() + onScanError: function(oDebug) {}, // Callback after detection of a unsuccessfull scanning (scanned string in parameter) + onKeyProcess: function(sChar, oEvent) {}, // Callback after receiving and processing a char (scanned char in parameter) + onKeyDetect: function(iKeyCode, oEvent) {}, // Callback after detecting a keypress (key char in parameter) - in contrast to onKeyProcess, this fires for non-character keys like tab, arrows, etc. too! + onPaste: function(sPasted, oEvent) {}, // Callback after receiving a value on paste, no matter if it is a valid code or not + keyCodeMapper: function(oEvent) { + return String.fromCharCode(oEvent.which); + }, // Custom function to decode a keypress event into a character. Must return decoded character or NULL if the given event should not be processed. + onScanButtonLongPress: function() {}, // Callback after detection of a successfull scan while the scan button was pressed and held down + scanButtonKeyCode: false, // Key code of the scanner hardware button (if the scanner button a acts as a key itself) + scanButtonLongPressTime: 500, // How long (ms) the hardware button should be pressed, until a callback gets executed + timeBeforeScanTest: 100, // Wait duration (ms) after keypress event to check if scanning is finished + avgTimeByChar: 30, // Average time (ms) between 2 chars. Used to do difference between keyboard typing and scanning + minLength: 6, // Minimum length for a scanning + suffixKeyCodes: [9, 13], // Chars to remove and means end of scanning + prefixKeyCodes: [], // Chars to remove and means start of scanning + ignoreIfFocusOn: false, // do not handle scans if the currently focused element matches this selector or object + stopPropagation: false, // Stop immediate propagation on keypress event + preventDefault: false, // Prevent default action on keypress event + reactToKeypress: true, // look for scan input in keyboard events + reactToPaste: false, // look for scan input in paste events + singleScanQty: 1 // Quantity of Items put out to onScan in a single scan + }; + + oOptions = this._mergeOptions(oDefaults, oOptions); + + // initializing options and variables on DomElement + oDomElement.scannerDetectionData = { + options: oOptions, + vars: { + firstCharTime: 0, + lastCharTime: 0, + stringWriting: "", + callIsScanner: false, + testTimer: false, + longPressTimeStart: 0, + longPressed: false + } + }; + + // initializing handlers (based on settings) + if (oOptions.reactToPaste === true) { + oDomElement.addEventListener("paste", this._handlePaste); + } + if (oOptions.scanButtonKeyCode !== false) { + oDomElement.addEventListener("keyup", this._handleKeyUp); + } + if ( + oOptions.reactToKeypress === true || + oOptions.scanButtonKeyCode !== false + ) { + oDomElement.addEventListener("keypress", this._handleKeyPress); + } + }, + + detachFrom: function(oDomElement) { + // detaching all used events + if (oDomElement.scannerDetectionData.options.reactToPaste) { + oDomElement.removeEventListener("paste", this._handlePaste); + } + if (oDomElement.scannerDetectionData.options.scanButtonKeyCode !== false) { + oDomElement.removeEventListener("keyup", this._handleKeyUp); + } + oDomElement.removeEventListener("keypress", this._handleKeyPress); + + // clearing data off DomElement + oDomElement.scannerDetectionData = undefined; + }, + + getOptions: function(oDomElement) { + return oDomElement.scannerDetectionData.options; + }, + + setOptions: function(oDomElement, oOptions) { + // check if some handlers need to be changed based on possible option changes + switch (oDomElement.scannerDetectionData.options.reactToPaste) { + case true: + if (oOptions.reactToPaste === false) { + oDomElement.removeEventListener("paste", this._handlePaste); + } + break; + case false: + if (oOptions.reactToPaste === true) { + oDomElement.addEventListener("paste", this._handlePaste); + } + break; + } + + switch (oDomElement.scannerDetectionData.options.scanButtonKeyCode) { + case false: + if (oOptions.scanButtonKeyCode !== false) { + oDomElement.addEventListener("keyup", this._handleKeyUp); + } + break; + default: + if (oOptions.scanButtonKeyCode === false) { + oDomElement.removeEventListener("keyup", this._handleKeyUp); + } + break; + } + + // merge old and new options + oDomElement.scannerDetectionData.options = this._mergeOptions( + oDomElement.scannerDetectionData.options, + oOptions + ); + + // reinitiallize + this._reinitialize(oDomElement); + }, + + simulate: function(oDomElement, sTestString) { + var oVars = oDomElement["scannerDetectionData"].vars; + oVars.firstCharTime = 0; + oVars.lastCharTime = 0; + oVars.stringWriting = sTestString; + this._validateScanCode(oDomElement); + return this; + }, + + _reinitialize: function(oDomElement) { + var oVars = oDomElement["scannerDetectionData"].vars; + oVars.firstCharTime = 0; + oVars.stringWriting = ""; + }, + + _isFocusOnIgnoredElement: function(oDomElement) { + ignoreSelectors = + oDomElement["scannerDetectionData"].options.ignoreIfFocusOn; + + if (!ignoreSelectors) { + return false; + } + + var oFocused = document.activeElement; + + // checks if ignored element is an array, and if so it checks if one of the elements of it is an active one + if (Array.isArray(ignoreSelectors)) { + for (var i = 0; i < ignoreSelectors.length; i++) { + if (oFocused.matches(ignoreSelectors[i]) === true) { + return true; + } + } + // if the option consists of an single element, it only checks this one + } else if (oFocused.matches(ignoreSelectors)) { + return true; + } + + // if the active element is not listed in the ignoreIfFocusOn option, return false + return false; + }, + + _validateScanCode: function(oDomElement) { + var oScannerData = oDomElement["scannerDetectionData"]; + var oOptions = oScannerData.options; + var iSingleScanQty = oScannerData.options.singleScanQty; + var sScanCode = oScannerData.vars.stringWriting; + var iFirstCharTime = oScannerData.vars.firstCharTime; + var iLastCharTime = oScannerData.vars.lastCharTime; + + switch (true) { + // detect codes that are too short + case sScanCode.length < oOptions.minLength: + var oScanError = { + message: "Receieved code is shorter then minimal length" + }; + break; + + // detect codes that were entered too slow + case iLastCharTime - iFirstCharTime > + sScanCode.length * oOptions.avgTimeByChar: + var oScanError = { + message: "Receieved code was not entered in time" + }; + break; + + // if a code was not filtered out earlier it is valid + default: + oOptions.onScan.call(oDomElement, sScanCode, iSingleScanQty); + var oEvent = new CustomEvent("scan", { + detail: { + scanCode: sScanCode, + qty: iSingleScanQty + } + }); + oDomElement.dispatchEvent(oEvent); + onScan._reinitialize(oDomElement); + return true; + } + + // If an error occurred (otherwise the method would return earlier) create an object for errordetection + oScanError.scanCode = sScanCode; + oScanError.scanDuration = iLastCharTime - iFirstCharTime; + oScanError.avgTimeByChar = oOptions.avgTimeByChar; + oScanError.minLength = oOptions.minLength; + + oOptions.onScanError.call(oDomElement, oScanError); + + var oEvent = new CustomEvent("scanError", oScanError); + oDomElement.dispatchEvent(oEvent); + + onScan._reinitialize(oDomElement); + return false; + }, + + _mergeOptions: function(oDefaults, oOptions) { + var oExtended = {}; + var prop; + for (prop in oDefaults) { + if (Object.prototype.hasOwnProperty.call(oDefaults, prop)) { + oExtended[prop] = oDefaults[prop]; + } + } + for (prop in oOptions) { + if (Object.prototype.hasOwnProperty.call(oOptions, prop)) { + oExtended[prop] = oOptions[prop]; + } + } + return oExtended; + }, + + _getNormalizedKeyNum: function(e) { + var iKeyCode; + if (window.event) { + // IE + iKeyCode = e.keyCode; + } else if (e.which) { + // Netscape/Firefox/Opera + iKeyCode = e.which; + } + return iKeyCode; + }, + + _handleKeyPress: function(e) { + // overwrite the which value of the event with keycode for cross platform compatibility + e.which = onScan._getNormalizedKeyNum(e); + var iKeyCode = e.which; + var oOptions = this["scannerDetectionData"].options; + var oVars = this["scannerDetectionData"].vars; + + oOptions.onKeyDetect.call(this, e.which, e); + + if (onScan._isFocusOnIgnoredElement(this)) { + return; + } + + // If it's just the button of the scanner, ignore it and wait for the real input + if ( + oOptions.scanButtonKeyCode !== false && + iKeyCode == oOptions.scanButtonKeyCode + ) { + // if the button was first pressed, start a timeout for the callback, which gets interrupted if the scanbutton gets released + if (!oVars.longPressed) { + oVars.longPressTimer = setTimeout( + oOptions.onScanButtonLongPress, + oOptions.scanButtonLongPressTime, + this + ); + oVars.longPressed = true; + } + + return; + } + + if (oOptions.stopPropagation) { + e.stopImmediatePropagation(); + } + + if (oOptions.preventDefault) { + e.preventDefault(); + } + + switch (true) { + // If it's not the first character and we encounter a terminating character, trigger scan process + case oVars.firstCharTime && + oOptions.suffixKeyCodes.indexOf(iKeyCode) !== -1: + e.preventDefault(); + e.stopImmediatePropagation(); + this["scannerDetectionData"].vars.callIsScanner = true; + break; + + // If it's the first character and we encountered one of the starting characters, don't process the scan + case !oVars.firstCharTime && + oOptions.prefixKeyCodes.indexOf(iKeyCode) !== -1: + e.preventDefault(); + e.stopImmediatePropagation(); + oVars.callIsScanner = false; + break; + + // Otherwise, just add the character to the scan string we're building + default: + var character = oOptions.keyCodeMapper.call(this, e); + if (character === null) { + return; + } + oVars.stringWriting += character; + oVars.callIsScanner = false; + break; + } + + if (!oVars.firstCharTime) { + oVars.firstCharTime = Date.now(); + } + + oVars.lastCharTime = Date.now(); + + if (oVars.testTimer) { + clearTimeout(oVars.testTimer); + } + + if (oVars.callIsScanner) { + onScan._validateScanCode(this); + oVars.testTimer = false; + } else { + oVars.testTimer = setTimeout( + onScan._validateScanCode, + oOptions.timeBeforeScanTest, + this + ); + } + + oOptions.onKeyProcess.call(this, character, e); + }, + + _handlePaste: function(e) { + // if the focus is on an ignored element, abort + if (onScan._isFocusOnIgnoredElement(this)) { + return; + } + e.preventDefault(); + + var sPasteString = (event.clipboardData || window.clipboardData).getData( + "text" + ); + this.scannerDetectionData.options.onPaste.call(this, sPasteString, event); + + var oVars = this.scannerDetectionData.vars; + oVars.firstCharTime = 0; + oVars.lastCharTime = 0; + oVars.stringWriting = sPasteString; + + // validate the string + onScan._validateScanCode(this); + }, + + _handleKeyUp: function(e) { + // if the focus is on an ignored element, abort + if (onScan._isFocusOnIgnoredElement(this)) { + return; + } + + var iKeyCode = onScan._getNormalizedKeyNum(e); + + // if hardware key is not being pressed anymore stop the timeout and reset + if (iKeyCode == this.scannerDetectionData.options.scanButtonKeyCode) { + clearTimeout(this.scannerDetectionData.vars.longPressTimer); + this.scannerDetectionData.vars.longPressed = false; + } + } + }; + \ No newline at end of file diff --git a/app/views/foodcourt/addorders/detail.html.erb b/app/views/foodcourt/addorders/detail.html.erb index d695a106..d9c96721 100644 --- a/app/views/foodcourt/addorders/detail.html.erb +++ b/app/views/foodcourt/addorders/detail.html.erb @@ -358,7 +358,7 @@
-

Card Tap

+

Scan OR Tap Card

diff --git a/app/views/foodcourt/orders/app_orders.html.erb b/app/views/foodcourt/orders/app_orders.html.erb index 08af8de2..abb6b8db 100644 --- a/app/views/foodcourt/orders/app_orders.html.erb +++ b/app/views/foodcourt/orders/app_orders.html.erb @@ -500,7 +500,7 @@
-

Card Tap

+

Scan OR Tap Card

@@ -519,6 +519,12 @@ $("#sxModal").attr('data-for', 'member'); var sale_id = $('#sale_id').text(); getCardNo(); + + onScan.attachTo(document, { + onScan: function(sCode) { + setCardNo(sCode) + } + }); }); // $('#pay').on('click', function () { // // var sale_id = $('#sale_id').text(); diff --git a/app/views/foodcourt/payments/show.html.erb b/app/views/foodcourt/payments/show.html.erb index c37f8232..b0447040 100755 --- a/app/views/foodcourt/payments/show.html.erb +++ b/app/views/foodcourt/payments/show.html.erb @@ -414,7 +414,7 @@
-

Card Tap

+

Scan OR Tap Card

@@ -721,6 +721,12 @@ $(document).ready(function(){ $("#sxModal").attr('data-for', 'member'); $("#sxModal").show(); getCardNo(); + + onScan.attachTo(document, { + onScan: function(sCode) { + setCardNo(sCode) + } + }); }); function pay_with_card(cardNo) { @@ -1197,6 +1203,12 @@ $(document).ready(function(){ $("#sxModal").attr('data-for', 'member'); $("#sxModal").show(); getCardNo(); + + onScan.attachTo(document, { + onScan: function(sCode) { + setCardNo(sCode) + } + }); } }); @@ -1209,6 +1221,11 @@ $(document).ready(function(){ $("#sxModal").attr('data-for', 'member'); $("#sxModal").show(); getCardNo(); + onScan.attachTo(document, { + onScan: function(sCode) { + setCardNo(sCode) + } + }); } });