IE Caching issue is breaking my lookup field - javascript

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();;

Related

window.document.addEventListener behavior inconsistent and appears wrong (JS)

My script takes CSV input and from that finds a user's name. It then creates a URL given the user's name.
From there, the script opens the user's URL, collects some data about the user, and puts that info into an array for later output.
My problem is with the window.document.addEventListener. the specific code line is as follows:
element.document.addEventListener("DOMContentLoaded",getSomething(),false);
The strange behavior is as follows:
With the statement above, the listener fires and getSomething() code begins execution. However, the page is not loaded. in the console I can see that the page contents are simply nothing more than an empty body.
Changing "getSomething()" to "getSomething" (in the addEventListener code line) causes the pages to eventually load, however, the getSomething function is never executed (apparently addEventListener did not fire.)
some introduction to the code that follows:
variable testURLs is an array containing a user's URL.
function controlOpenWindows() is not fully set up but its intent is to determine when a window is ready to close, and when data from as many as four opened windows is collected, all four will close and four more will open. four is arbitrary. there are over 900 user URLs so just limiting number open at any one time.
The function that closes the previously opened windows makes the call to open more windows.
Please note that you would need a login id and password to open specific user pages. so passing the URL to you in this post would not be helpful. I'm hoping you can help without that specific info.
function closeOpenedWindow(index){
switch (index) {
case 0:
blnZero=true;
break;
case 1:
blnOne=true;
break;
case 2:
blnTwo=true;
break;
case 3:
blnThree=true;
}
if (blnZero===true && blnOne===true && blnTwo===true && blnThree===true) {
for (p=0; p<4; p++) {
openedWindow[p].close();
count +=1;
controlOpenWindows();
}
}
}
function controlOpenWindows() {
debugger;
testURLs=[];
blnZero=false;
blnOne=true;
blnTwo=true;
blnThree=true;
if (editorProfileURL.length>=4) {
testURLs[0]= editorProfileURL.shift();
testURLs[1] =editorProfileURL.shift();
testURLs[2]=editorProfileURL.shift();
testURLs[3]=editorProfileURL.shift();
} else {
for (n=0; n<editorProfileURL.length; n++) {
testURLs[n]=editorProfileURL[n];
}
}
testURLs.forEach(openWindow);
}
controlOpenWindows();
function openWindow(element1, index1, array1) {
openedWindow[index1]= window.open(element1);
}
function loaded(element, index, array) {
element.document.addEventListener("DOMContentLoaded", getSomething(), false);
}
openedWindow.forEach(loaded);
function getSomething() {
debugger;
var whichPage=this.document.URL;
function whichIndex(element, index, array) {
if (element.document.URL==whichPage) {
return element.document.URL;
}
}
var foundIndex=openedWindow.findIndex(whichIndex);
var reg=/The page you were looking for doesn*/g;
if (openedWindow[0].document.getElementsByClassName("container not-found").length>0) {
if(openedWindow[foundIndex].document.getElementsByClassName("container not-found")[foundIndex].innerHTML.match(reg)) {
closeOpenedWindow(foundIndex);
}
} else {
var firstEdit=openedWindow[foundIndex].document.getElementsByClassName("user-last-edit")[0].innerHTML;
var lastEditDaysAgo=openedWindow[foundIndex].document.getElementsByClassName("transaction-header-time")[0].innerHTML;
var rank=openedWindow[foundIndex].doucment.getElementsByClassName("user-rank")[0].innerHTML;
var editCount=openedWindow[foundIndex].document.getElementsByClassName("user-stats-value")[1].innerHTML;
updatedEditorInfo.push();
updatedEditorInfo.push(firstEdit + ",");
updatedEditorInfo.push(lastEditDaysAgo+ ",");
updatedEdtiorInfo.push(rank + ",");
updatedEditorInfo.push(editoCount + ",");
updatedEditorInfo.push("\n");
console.log(updatedEditorInfo);
alert(updatedEditorInfo);
//closeOpenedWindow();
//controlOpenWindows();
}
closeOpenedWindow(foundIndex);
controlOpenWindows();
}
Thanks for taking a look.
You need to pass a reference to the function instead of calling it directly:
element.document.addEventListener("DOMContentLoaded",getSomething,false);

Twitch TV JSON API Issue

So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});

tampermonkey script stops working if I change the page

I am using Tampermonkey to save time on frequent tasks. The goal is to get content of an element on www.example1.com, navigate to another page, and do stuff there. The starting page is www.example1.com as seen from match. This is the code I am using:
//#match http://example1.com
var item = document.getElementById("myId").textContent;
window.open("http://example2.com","_self");
setTimeOut(function(
//perform clicks on this page
){},3000);
None of the code after changing URLs ever gets executed. Why, and what is the workaround?
Allow the userscript on both urls and use GM_setValue/GM_getValue to organize the communication.
//#match http://example1.com
//#match http://example2.com
//#grant GM_getValue
//#grant GM_setValue
if (location.href.indexOf('http://example1.com') == 0) {
GM_setValue('id', Date.now() + '\n' + document.getElementById("myId").textContent);
window.open("http://example2.com","_self");
} else if (location.href.indexOf('http://example2.com') == 0) {
var ID = GM_getValue('id', '');
if (ID && Date.now() - ID.split('\n')[0] < 10*1000) {
ID = ID.split('\n')[1];
.............. use the ID
}
}
This is a simplified example. In the real code you may want to use location.host or location.origin or match location.href with regexp depending on what the real urls are.
To pass complex objects serialize them:
GM_setValue('test', JSON.stringify({a:1, b:2, c:"test"}));
try { var obj = JSON.parse(GM_getValue('test')); }
catch(e) { console.error(e) }

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

Ajax call inside a timeout function getting called multiple times

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)

Categories