Ajax call inside a timeout function getting called multiple times - javascript

I'm using this great piece of javascript (http://www.deadosaurus.com/detect-a-usb-barcode-scanner-with-javascript/) to recognize USB barcode scanners input (just really fast typed keyboard strokes) without having the focus on any input field in a website. The thing works great as it is, but what I need to do is to figure out if the scanned item is of type A or type B before I can do anything with the scan result, and that is where the Ajax-call comes into play like this:
$(document).ready(function() {
var pressed = false;
var chars = [];
$(window).keypress(function(e) {
if (e.which >= 48 && e.which <= 57) {
chars.push(String.fromCharCode(e.which));
}
console.log(e.which + ":" + chars.join("|"));
if (pressed == false) {
setTimeout(function(){
if (chars.length >= 10) {
var barcode = chars.join("");
console.log("Barcode Scanned: " + barcode);
// Here is the ajax-call.
checkItemType(barCode).success(function(response) {
// Do stuff with the response.
});
}
chars = [];
pressed = false;
},500);
}
pressed = true;
});
});
And the function:
function checkItemType(barCode) {
// Example parsing for the id I want to do work with.
var itemId = parseInt(barCode.substr(9, 6).replace(/^[ 0]/g, ''));
var data = { itemId: itemId };
return $.ajax({
type: 'POST',
url: 'Controller/CheckItemType',
traditional: true,
dataType: 'json',
data: data
});
}
For the first time when I scan the item, all things work fine. The second time I do a scan, the checkItemType-function gets called 2x and the third time it gets called 4x, usually capping at 8x.
I just cant get my head around this.. there is a if-clause for chars.length >= 10 and the chars list should clear out after the barcode scanners input stops. Removing the ajax-call fixes the issue as I said before, but I am unable to really figure out a way to make the call outside of the timeout function. Guess I should add that I'm still learning javascript and I'm really out of ideas for this one. Thanks in advance.
EDIT: Success code didn't really matter so removed it as suggested.

The bug you describe sounds like some other place in your code binds the handler again.
Do you somehow reload some portion of your page (maybe through ajax), which could trigger the $(window).keypress( ... ) binding a second time ?

I've had a look at the code and think I've got it fixed. I've simply added chars = []; pressed = false; after you have created the barcode and it then resets after a barcode is scanned. Have a look below:
JSFiddle
if (pressed === false) {
setTimeout(function () {
console.log("Timeout");
if (chars.length >= 10) {
var barcode = chars.join("");
chars = [];
pressed = false;
console.log("Barcode Scanned: " + barcode);
// Here is the ajax-call.
checkItemType(barCode).success(function (response) {
// Do stuff with the response.
});
}
chars = [];
pressed = false;
}, 500);
}
pressed = true;

I would say it is a timing/synchronization issue. You setup for each event/keystroke a timeout in 500 ms: that means that after 500 ms from the first keystroke you start to run your (first) function: chars.length > 10 and then while you do the Ajax call (because it is slow) the second timeout fires: chars.length is still > 10 so you go and run again checkItemType... and so on until the first call will set the chars = [].
The number of times checkItemType si executed is related to the delay between the keystrokes and the time to run checkItemType. If you only reset chars = [] after you check it for >= 10 you still can't be sure then the second timeout didn't already passed that condition.
One way to be sure that you run checkItemType only once would be to have only one timeout set on the first keystroke and then if chars.lenngth < 10 when it fires set it up again to run in another x ms...etc.

if (pressed == false) -> if(!pressed) or if(pressed === false)

Related

How to slow down an Ajax call?

I have a function in JS contains a loop, that calls an AJAX call every iteration. The call to inserts checked elements into a DB and returns the results of those elements in the same page in the next section.
The problem I have is that when I check for e.g. 4 checkboxes out of 3 groupes, the only checkboxes of the last group gets added to the page. However, when I use alert(), I can see all elements.
I used setTimeout, but I got error in the code. I also added lines to give more time to AJX call, but the problem remains. So I wonder if there is a solution to slow down the code without using alert().
This is my script:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
//
// alert() : It works in this case.
//
// for (var i=0; i<5000000; i++) ; it doesn't work
}, 400);
}
Thank you in advance for your help and time.
I will likely be downvoted for mentioning this, because it is not a recommended procedure, but I believe every coder should have all facts.
In jQuery AJAX construct, there is option async:false, which will delay the script from continuing UNTIL the AJAX has completed processing. Needless to say, if things go wrong in the AJAX the browser could freeze. A lot depends on who your users are, and amount of traffic -- on a few of my ten-user in-house projects it was an acceptable solution.
$.ajax({
async: false,
type: 'post',
url: 'ajax/ax.php',
data: 'request=',
success: function(d){
if (d.length) alert(d);
}
});
Ref:
What does "async: false" do in jQuery.ajax()?
The better idea, however, is to look into the Promises interface, with methods like .when() and .then()
References:
https://jsfiddle.net/v86bc028/2/
http://jqfundamentals.com/chapter/ajax-deferreds#
http://digitizor.com/jquery-html-callback-function-using-promise/#
how does jquery's promise method really work?
The problem you're running into deals with asynchronous functions, or the A in AJAX. If you don't know what an asynchronous function is, there are many others who can explain it better than I can, so give that a google.
What's happening without the alert() in there is your code makes 4 sever calls, but all 4 get sent out before you get a response to any of them. With the alert() (or setTimeout), you're giving the code time to received each response to a call before the next one is made.
There are several ways you can approach this, the first way is by calling the next call after the first receives a response. The second way is to use an async function to call all 4 at once on different chains(?). I'm not the best at explaining this part, but there's plenty of code to be found on SO and online.
I think you have a more generic problem in your code, since you seem to need to delay your executions to wait till sth. else is finished, instead of getting anounced when it is done.
The line that annoys me most is this one
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
what exactly is update doing? How is it implemented?
I assume it does sth. like divCategorie.innerHTML += ajaxPage.texte;
Wich is highly unfavorable, since the browser has to parse and rebuild, whatever there already is in divCategorie.innerHTML.
Just appending the new Markup would be better.
long way short: maybe a good hack would be to insert some hidden node as a placeholder (so you kan keep order, although the AJAX-requests may return in a different order) and replace that node with the real content, as soon as it arrives.
Kind of like this:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
//this is done immediately, and therefore preserves the order of the loop,
//without any delays/timeouts
var placeholder = document.createElement("div");
placeholder.className = "placeholder";
placeholder.style.display = "none";
divCategorie.appendChild(placeholder);
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
//convert the passed text into a DocumentFragment
var frag = fragment(ajaxPage.texte);
//replacing the placeholder with the fragment
divCategorie.insertBefore(frag, placeholder);
divCategorie.removeChild(placeholder);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
//this is also pretty horrible to me:
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
}, 400);
}
I think you should do some major refactoring. And take a look into Promises.
// * -> DocumentFragment
//strings/primitives are parsed as HTML-markup,
//null / undefined is ignored
//Arraylike structures are parsed recursively
var fragment = (function(container){
return function(src){
return reducer(document.createDocumentFragment(), src);
}
function reducer(frag, node){
var i, len, fc, c, r;
if(node === Object(node)){
if("nodeType" in node){
//dom nodes
frag.appendChild(node);
}else{
//Arraylike structures, like NodeLists or jQuery-Objects, or just plain Arrays
for(i = 0, len = ("length" in node && node.length)|0, r = reducer; i < len; (i in node) && r(frag, node[i]));
}
}else if(node != null) {
//strings (all primitives)
for((c=container).innerHTML = node; fc = c.firstChild; frag.appendChild(fc));
}
return frag;
}
})(document.createElement("div"));

AngularJS - autocomplete with $http requests

I have an application in AngularJS and I want to implement an autocomplete input for a certain list of programs.
My problem is that I have lots of programs in my database and I don't want to load them all when the page loads. Instead I load pages and have a button that loads the next page when clicked.
scope.loadPrograms = function() {
Programs.getPage($scope.page)
.success(function(data) {
$scope.allprograms.push.apply($scope.allprograms, data.campaigns);
$scope.page++;
if(data.pagination.pages < $scope.page) {
$scope.page = -1;
}
})
.error(function(data){
alert('There has been an error. Please try again later!');
});
}
and the button
<md-button ng-click="loadPrograms()" ng-show="page != -1">Load more data</md-button>
So this approach makes me do a request everytime I write/delete a letter in the autocomplete input, given the fact that I don't have all the program loaded on $scope. Is it ok to make so many request? Is there another approach?
Thanks.
EDIT
Ok so now I put a delay on the autocomplete, but the method doesn't work anymore.
// Search for programs
scope.querySearch = function(query) {
if (typeof pauseMonitor !== 'undefined') {
$timeout.cancel(pauseMonitor);
}
pauseMonitor = $timeout(function() {
var results = query ? scope.allprograms.filter(createFilterFor(query)) : [];
return results;
}, 250);
};
// Create filter function for a query string
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(programs) {
return (programs.name.toLowerCase().indexOf(lowercaseQuery) != -1);
};
};
It enters in the createFilterFor method, finds a good match but doesn't show it anymore.
If you need to retrieve a set of words for the purpose of auto completion from a large database, one simple trick is to use $timeout with some time threshold which can detect the pauses of the user typing.
The idea is to prevent a request being generated for every key. You look for a pause in the user typing pattern and make your request there for the letters typed. This is a simple implementation of this idea in your key handler.
function processInput(input) {
if (typeof pauseMonitor !== 'undefined') {
$timeout.cancel(pauseMonitor);
pauseMonitor = $timeout(function() {
//make your request here
}, 250);
}
Take a look at ng-model-options
you can set a debounce time and some other interesting things.
ng-model-options="{ debounce: '1000' }"
Above line means the input value will be updated in the model after 1 sec

Fire an event when an NFC card is presented

I am attempting to build a webapp on a Chromebook, I need it to read RFID card serial numbers with an ACR122U NFC. I am using chrome-nfc.
I am reading cards happily, but I do not know how to fire an event when a card is presented.
Are there any events in chrome-nfc I can use to know when a card has been presented to the reader?
EDIT: I have been trying to use chrome.nfc.wait_for_tag, but it does not behave as I would expect.
// With a card on the reader
chrome.nfc.wait_for_tag(device, 10000, function(tag_type, tag_id){
var CSN = new Uint32Array(tag_id)[0];
console.log ( "CSN: " + CSN );
});
[DEBUG] acr122_set_timeout(round up to 1275 secs)
DEBUG: InListPassiveTarget SENS_REQ(ATQA)=0x4, SEL_RES(SAK)=0x8
DEBUG: tag_id: B6CA9B6B
DEBUG: found Mifare Classic 1K (106k type A)
[DEBUG] nfc.wait_for_passive_target: mifare_classic with ID: B6CA9B6B
CSN: 1805372086
// with no card on the reader
chrome.nfc.wait_for_tag(device, 10000, function(tag_type, tag_id){
var CSN = new Uint32Array(tag_id)[0];
console.log ( "CSN: " + CSN );
});
[DEBUG] acr122_set_timeout(round up to 1275 secs)
DEBUG: found 0 target, tg=144
Both return the results as above immediately, it does not seem to matter what number I use for a timeout...
If I call the function with no card on the reader, and then immediately put the card on the reader after function call, I get no output in the console.
I'm not familiar with chrome-nfc, but taking a shot in the dark by reverse engineering the source, it looks like you would want to use the wait_for_tag method, like:
chrome.nfc.wait_for_tag(device, 3000, function(tag_type, tag_id) {
// Do your magic here.
});
...Where device is your reader, 3000 is the maximum time to wait (in ms), and replacing // Do your magic here. with your desired logic. If it times out, both tag_type and tag_id will be null.
If you wanted to wait indefinitely, you could just recursively call a function with the above code. Example:
function waitAllDay(device) {
chrome.nfc.wait_for_tag(device, 1000, function(tag_type, tag_id) {
if(tag_type !== null && tag_id !== null)
{
// Do your magic here.
}
waitAllDay(device);
});
}
That's assuming you want it to continue waiting even after a tag has been presented. Wrap the waitAllDay(device); in an else if you want it to stop once a tag is read.
UPDATE: It seems the wait_for_tag method does not work as intended, so I'm proposing a second solution. I'm leaving the existing solution in place in case the method is fixed by the developers of chrome-nfc.
Another thing to try is to use chrome.nfc.read, passing in a timeout option, inside a window.setInterval.
var timer = window.setInterval(function () {
chrome.nfc.read(device, { timeout: 1000 }, function(type, ndef) {
if(!!type && !!ndef) {
// Do your magic here.
// Uncomment the next line if you want it to stop once found.
// window.clearInterval(timer);
}
});
}, 1000);
Be sure and call window.clearInterval(timer) whenever you want it to stop watching for tags.
While I do not consider this a proper solution; here is a workaround I am using for the time being.
function listen_for_tag(callback, listen_timeout){
var poll_delay = 400; //ms
var listen_loop = null;
if(!listen_timeout){
listen_timeout = 99999999;
}
function check_for_tag(){
if(listen_timeout < 0) {
clearInterval(listen_loop);
console.log("we didnt find a tag. finished");
}
chrome.nfc.wait_for_tag(dev_manager.devs[0].clients[0], 10, function(tag_type, tag_id){
console.log ( "FOUND A TAG!!" );
clearInterval(listen_loop);
// handle the callback (call it now)
var C = callback;
if (C) {
callback = null;
window.setTimeout(function() {
C(tag_type, tag_id);
}, 0);
}
});
listen_timeout -= poll_delay;
}
listen_loop = setInterval(check_for_tag, poll_delay);
}

how to design an elegant js polling routine?

I have a requirement where I need to poll the database via ajax from js to check for a status. If the status is "active" then the polling should stop and an alert should popup "case is now active". The js should check the db every 2 seconds until the db status returns "active." Can you provide an elegant js routine for this? Here's some general js to show what I want to do:
function ReportAsActivePoll()
{
for(var i=0; i<10; i++)
{
setTimeout(StatusIsActive,(i*2000));
if(statusIsActive)
{
ReportAsActive();
break;
}
}
}
var statusIsActive = false;
function StatusIsActive(case)
{
statusIsActive = GetStatusFromDB(case) == "active";
}
function ReportAsActive()
{
alert("case is now active")
}
A few notes:
I know the code above is not correct. It's just for illustrative purposes.
The code above will call StatusIsActive 10 times. I would like the calls to stop/break/discontinue after status is active. However, I think polling requires to queue up all the calls ahead of time so I'm not sure how to achieve this.
Use setInterval() and clearInterval() for simplicity. Like so:
<script type="text/javascript">
function checkStatus(theCase) {
var intervalId = window.setInterval(function() {
if (getStatusFromDb(theCase) == 'active') {
clearInterval(intervalId)
reportAsActive()
}
}, 2000)
}
function reportAsActive()
{
alert("case is now active")
}
var tmpCounter = 0
function getStatusFromDb(theCase)
{
if (tmpCounter++ == 4) return "active"
}
checkStatus('case 123')
</script>
You should also consider making functions start with a lowercase letter, because that is the normal JS convention. By choosing another style, you risk having case-sensitive errors that are annoying to track down.
You need to use setInterval instead of your setTimeout and when you received a valid response you have to remove this interval with clearInterval.
So you need to do something like this
var intervalID = window.setInterval(function(){
var resFromYourDB = ...; // get your result via ajax
if (resFromYourDB['active']){
window.clearInterval(intervalID);
// do you alert
}
}, 2000)
This way it will be polling your server till it will get active as a response and not a predefined amount of time as with setTimeout. Also when it will get this response it will properely stops.

IE Caching issue is breaking my lookup field

I'm doing a project which uses javascript to get info from a view (written in Python and using the Django interface) based on the text a user enters in a field (querying on every keyup), and then display that info back. Basically, this either displays 'no job found' or displays the name, username, and balance for that job. In Firefox, this all works great. I can enter a JobID, it tells me the ID is new, and I can create the job. I can then immediately come back to the page and enter that ID, and my lookup returns the right info about the job.
The thing is, Internet Explorer 8 is being lazy. If I type a job ID in IE8, my functions calls the lookup page (/deposits/orglookup/?q=123) and gets a value. So if, for example, it gets False, I can then create a new job with that ID. If I then browse back and enter that same number in that same lookup field, Internet Explorer does not refresh the lookup page, so it returns false again. If I browse to that lookup page, I see that false value, but if I refresh it, I get the right information again. Any idea on how I can force this query every time I type in my lookup field, and not like IE refer to the cached page?
I will add that it does not do me much good to fix this on a per-user basis, as this is an organization-wide application, so I really could use a fix I can write into my code somewhere to force IE to actually refresh the lookup page every time it is supposed to.
Here's the code for the lookup function, if it helps. It is a bit messy, but I didn't write it so I'll try to include everything relevant:
$("#id_JobID").keyup(
function(event){
//only fire gets on 0-9, kp 0-9, backspace, and delete
if (event.keyCode in { 96:1, 97:1, 98:1, 99:1, 100:1, 101:1, 102:1, 103:1, 104:1, 105:1,
46:1,48:1, 49:1, 50:1, 51:1, 52:1, 53:1, 54:1, 55:1, 56:1, 57:1, 8:1})
{
if ($("#loadimg").attr("src") != "/static/icons/loading.gif") {
$("#loadimg").attr("src", "/static/icons/loading.gif");
}
if ($("#loadimg").length < 1) {
$("#id_JobID").parent().append("<img id=loadimg src=/static/icons/loading.gif>");
}
clearTimeouts(null); //clear all existing timeouts to stop any running lookups
GetCounter++;
currLoc = window.location.href.slice(window.location.href.indexOf('?') + 1).split('/').slice(-2,-1);
if (currLoc == 'restorebatch') {
var TimeoutId = setTimeout(function() {dynamicSearch('restorelookup');}, 400);
} else {
var TimeoutId = setTimeout(function() {dynamicSearch('orglookup');}, 400);
}
//alert(TimeoutID);
TimeoutBag[GetCounter] = {
'RequestNumber': GetCounter,
'TimeoutId': TimeoutId
}
}
}
);
function clearTimeouts(TimeoutBagKeys) //TimeoutBagKeys is an array that contains keys into the TimeoutBag of Timeout's you want to clear
{
if(TimeoutBagKeys == null) //if TimeoutBagKeys is null, clear all timeouts.
{
for (var i = 0; i < TimeoutBag.length; i++)
{
if (TimeoutBag[i] != null) {
clearTimeout(TimeoutBag[i].TimeoutId);
}
}
}
else //otherwise, an array of keys for the timeout bag has been passed in. clear those timeouts.
{
var ClearedIdsString = "";
for (var i = 0; i < TimeoutBagKeys.length; i++)
{
if (TimeoutBag[TimeoutBagKeys[i]] != null)
{
clearTimeout(TimeoutBag[TimeoutBagKeys[i]].TimeoutId);
ClearedIdsString += TimeoutBag[TimeoutBagKeys[i]].TimeoutId;
}
}
}
}
function dynamicSearch(viewname) {
$(".lookup_info").slideUp();
if ($("#id_JobID").val().length >= 3) {
var orgLookupUrl = "/deposits/" + viewname + "/?q=" + $("#id_JobID").val();
getBatchInfo(orgLookupUrl);
}
else if ($("#id_JobID").val().length == 0) {
$("#loadimg").attr("src", "/static/icons/blank.gif");
$(".lookup_info").slideUp();
}
else {
$("#loadimg").attr("src", "/static/icons/loading.gif");
$(".lookup_info").slideUp();
}
}
function getBatchInfo(orgLookupUrl) {
$.get(orgLookupUrl, function(data){
if (data == "False") {
$("#loadimg").attr("src", "/static/icons/red_x.png");
$(".lookup_info").html("No batch found - creating new batch.");
$("#lookup_submit").val("Create");
$(".lookup_info").slideDown();
toggleDepInputs("on");
}
else {
$("#loadimg").attr("src", "/static/icons/green_check.png");
$("#lookup_submit").val("Submit");
$(".lookup_info").html(data);
$(".lookup_info").slideDown()
toggleDepInputs("off");
};
});
}
There are three solutions to this:
Use $.post instead of $.get.
Add a random GET parameter to your URL, e.g. ?update=10202203930489 (of course, it needs to be different on every request).
Prohibit caching on server-side by sending the right headers (if-modified-since).
You need to make the URL unique for every request. The failproof way is to introduce new GET parameter which has a timestamp as its value - so the URL is unique with every request, since timestamp is always changing, so IE can't cache it.
url = "/deposits/orglookup/?q=123&t=" + new Date().getTime()
So instead of only one parameter (q) you now have two (q and t) but since servers usually don't care bout extra parameters then it's all right
One trick that often works is to append a timestamp to the lookup URL as a querystring parameter, thus generating a unique URL each time the request is made.
var orgLookupUrl = "/deposits/" +
viewname + "/?q=" +
$("#id_JobID").val() + "&time=" + new Date().getTime();;

Categories