Duplication of images following JavaScript success function running correctly - javascript

Each time the "success" function of this code block executes, some unwanted events happen:
If I dont refresh the browser and enter the same name again, a duplicated image appears. This happens each time the code is run.
If I don't refresh the browser and type a name that doesn't exist, wheres before the search returned a name that did, then both images are displayed on the page.
How do I stop the duplication? I've looked at alternatives to .append in jQuery, but none are having the desired result.
I think I also need the query to reset each time its run, other wise it appears this also causes complication.
var friendName;
function findFriend() {
friendName = $('#friendsearch').val();
console.log(friendName);
var query = new Parse.Query(Parse.User);
query.equalTo("username", friendName); // find users that match
query.find({
success: function (friendMatches) {
// This section is always run, no matter the outcome. Depending on the input depends on which result is shown
if (friendMatches.length === 0)
// console.log('NO MATCH FOUND!!!');
$(".no_user").show();
else // Query executed with success
imageURLs = [];
for (var i = 0; i < friendMatches.length; i++) {
var object = friendMatches[i];
imageURLs.push(object.get('pic'));
}
// If the imageURLs array has items in it, set the src of an IMG element to the first URL in the array
for (var j = 0; j < imageURLs.length; j++) {
$('#imgs').append("<img src='" + imageURLs[j] + "'/>");
}
console.log('MATCH FOUND!!!');
},
// Only if the code breaks and cannot either find or not find a user should this error be returned
error: function (error) {
alert('Opps we have a problem' + error.message);
}
});
}
// This captures the users input and is triggered when the user presses the find
$('#find_button').click(function (e) {
findFriend();
});

You need to remove the old image(s) before adding the new one(s):
$('#imgs').empty();
When to clear is another issue (I think this is your 'unwanted event #2'):
success: function (friendMatches) {
// clear images first, so that previous search results
// don't show when the current search returns 0 results
$('#imgs').empty();
...
}

Related

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

Limting Search Results in Jekyll Search

I've been trying without success to limit results in the search script included in my site.
Here is the original script:
jQuery(function() {
// Initialize lunr with the fields to be searched, plus the boost.
window.idx = lunr(function () {
this.field('id');
this.field('title');
this.field('content', { boost: 10 });
this.field('categories');
});
// Get the generated search_data.json file so lunr.js can search it locally.
window.data = $.getJSON('/search.json');
// Wait for the data to load and add it to lunr
window.data.then(function(loaded_data){
$.each(loaded_data, function(index, value){
window.idx.add(
$.extend({ "id": index }, value)
);
});
});
// Event when the form is submitted
$("#site_search").submit(function(event){
event.preventDefault();
var query = $("#search_box").val(); // Get the value for the text field
var results = window.idx.search(query); // Get lunr to perform a search
display_search_results(results); // Hand the results off to be displayed
});
function display_search_results(results) {
var $search_results = $("#search_results");
// Wait for data to load
window.data.then(function(loaded_data) {
// Are there any results?
if (results.length) {
$search_results.empty(); // Clear any old results
// Iterate over the results
results.forEach(function(result) {
var item = loaded_data[result.ref];
// Build a snippet of HTML for this result
var appendString = '<li>' + item.title + '</li>';
// Add the snippet to the collection of results.
$search_results.append(appendString);
});
} else {
// If there are no results, let the user know.
$search_results.html('<li><b><u>NO RESULTS FOUND</u></b></li>');
}
});
}
});
And I've tried without success to include this limiting statement when iterating over the results:
// Iterate over the results
for (var i = 0; i < results.length && i < 5; i++) {
var item = loaded_date[results[i]];
I've fiddled around for it for quite some time, and can't seem to find what is amiss.
Any assistance is greatly appreciated.
-d
What error were you getting, your for loop looks fine.
An alternative approach would be to use Array#slice to limit the number of results you iterate over.
results.slice(0, 5).forEach(function (result) {
// snip
})
Replace your existing iteration over the results, where you have results.forEach.
This will work even if there are less than 5 results, and will take the first 5 if there are more than 5 results.

how to use jQuerys each with checking an image exists

I have some source that actually drives me crazy...
First, I have a object like this:
var extensions = {"jpg":".jpg","JPG":".JPG","png":".png","PNG":".PNG"};
Now I want to iterate the object to check if a image with one of these extensions exists. The source for this looks like:
var imgURL = "some/path/";
var imgName = "myFileName";
var counter = 0;
$.each(extensions, function( key, imgExtension ){
var tmpImgUrl = imgURL + imgName + imgExtension;
console.log('test');
$( ".imageCheck" ).unbind().attr( "src", tmpImgUrl ).error( function(){
console.log( tmpImgUrl );
counter++;
if( counter >= Object.keys( extensions ).length){
return false;
}
});
});
This each should take the image name and try to test if there is an error for each extension. Actually it does what it should do. Only thing is, counter doesn't gets counted up and the console log appears wrong.
I expact the log should look like:
test
some/path/myFileName.jpg
test
some/path/myFileName.JPG
test...
and so on. But it appers like:
test
test
test
test
some/path/myFileName.jpg
some/path/myFileName.JPG
...
actually I want to use the image check to return a break (false) for the each so not all extensions get checked. (performance)
And also I really need the counter when exiting the check part....
Any guesses what going wrong here ?
Create a loader to do the image test.
Init the index to test.
Create an <img>, add onload and onerror behavior. onload will notify the user, or you can event let the loader accept callbacks for onload to call it, onerror will try to load image with next type.
The tryLoad function first check if index is equal to extensions.length, if it is, which means all possibles are failed, notify user the image load failed, and do some fail fallback or something.
Otherwise, it use current index with give fileName and url to create imagepath, set it to img.src and increase the index for next attempts.
call the tryload to start the process.
Now the image stops attempt when current url is valid, and will try the extesions one-by-one until no other can be used.
// Inits, can also be put into the loader.
var extensions = ['.jpg', '.JPG', '.png', '.PNG'];
var imgUrl = "some/path/";
var loader = function(fileName, success, fail) {
// Start at first image type.
var index = 0;
// Create a img for loading.
var $image = $('<img>');
// Add success and fail behavior to the image.
$image
.load(function() {
// Do some notification or....
console.log('success', $image.attr('src'));
// If you give a success callback, call it
// Or you can write the logic here.
if ($.isFunction(success)) {
success();
}
})
.error(function() {
console.log('fail', $image.attr('src'));
// Try to load next image
tryLoad(index);
});
// The function for attempts.
var tryLoad = function() {
// When all attemps tried.
if (index === extensions.length) {
alert('Boom, all failed');
// Do some fallbacks....
// $image.remove();
// If you give a fail callback, call it
// Or you can write the logic here.
if ($.isFunction(fail)) {
fail();
}
return;
}
// Create url to load.
var tmpImgUrl = imgURL + fileName + extensions[index];
console.log('test', tmpImgUrl);
$image.attr('src', tmpImgUrl);
++index;
};
// Start the first attempt.
tryLoad();
// return jquery wrapper of image. It'll have image only if any of the test is passed.
return $image;
};
You won't want to use jQuery's .each to loop through an object - the proper way to do this is using javascript's for in loop.
Looping through your extensions object should look like this:
for(var key in extensions){
console.log("Key", key, "Value",extensions.key);
}
You can then do whatever it is you need with those keys or values using the above looping construct.

Variable in for-loop not being reassigned each iteration

I've got a for loop inside a for loop. The first loop should get a username, get their rating, append them to the same list item, then start over with the next username, rating, append, and so on and so forth until it's gone through ever user in the friends list.
//Get usernames
var current = Parse.User.current();
var relation = current.relation("FriendRelations");
relation.query().find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
//This shouldn't increment until the rating has been retrieved in the
// next function.
theuser = results[i].getUsername();
$('ul').prepend('<li id = "frienditems_' + i + '"><div id ="friendname">' +
results[i].getUsername() + '</div></li>');
//Get friend's rating.
//This is a query within a query.
var GameScore = Parse.Object.extend("Rating");
var query = new Parse.Query(GameScore);
query.equalTo("user", results[i].getUsername());
query.find({
success: function(result) {
//The log just outputs the last user's name each time, because I guess the
//other function already looped through completely, so it's forever set to the last one?**
console.log(theuser);
for (var y = 0; y < result.length; y++) {
var object = result[y].get("Rating");
//Logging the rating.
console.log("Rating:" + object);
$('#frienditems_' + y).append('<p class="friendrating">' + object + '</p>');
}
},
error: function(error) {}
});
}
}
});
Here is my console.log:
John
Rating:5
John
Rating:50
John
Rating:43
John
Rating:80
I want it to be this:
George
Rating:5
Smith
Rating:50
Robert
Rating:43
John
Rating:80
Can anyone help? I've looked at other questions, but I can't figure out how to apply them to my situation. I wish I could at least access the first functions results within the second function.
UPDATE: Mihail's answer really helped me out. The console log now shows data being retrieved in the correct order. But it's still not all appending to its respective list item:
Your issue is caused by the asynchronous call to query.find(). You practically order the browser to retrieve your information from the cloud database and tell him what to do with the result if it retrieves anything successfully, while the code continues execution (in your case, the for loop still iterates through the first collection).
By the time the first request ends, for reaches the end and because the second query's success is in the scope of the first, the variable theuser is still instantiated and has the last value available.
To prevent that, you can change the scope of the variable using a function call with your parameter. I've rewritten your code and it looks like this:
var current = Parse.User.current();
var relation = current.relation("FriendRelations");
var $ul = $('ul')
relation.query().find({
success:function(results){
for(var i = 0; i < results.length; i++) {
theuser = results[i].getUsername();
// you can use <<var $friendrating = $("<li id='frienditems_" + i"'/>").html(...... >>
var $friendrating = $(document.createElement("li")).attr("id", "frienditems_" + i).html('<div class="friendname">' + results[i].getUsername() + '</div>')
$ul.prepend($friendrating);
getUserRating(theuser, $friendrating);
}
}
});
function getUserRating(theUser, $node) {
//Get friend's rating.
//This is a query within a query.
var GameScore = Parse.Object.extend("Rating");
var query = new Parse.Query(GameScore);
query.equalTo("user", theUser);
query.find({
success: function(result) {
//The log just outputs the last user's name each time, because I guess the
//other function already looped through completely, so it's forever set to the last one?**
console.log(theUser);
for (var y = 0; y < result.length; y++) {
var object = result[y].get("Rating");
//Logging the rating.
console.log("Rating:" +object);
$node.append('<p class="friendrating">' + object + '</p>');
}
},
error: function(error) {
}
});
}
Please, be advised that i haven't tested it and i'm not 100% sure it will work on the first try.
because the anonymous callback function success: function(result) {} is called after the for loop finishes in this case, query.find() is an asynchronous call, meaning the for loop will continue independently to the next iteration no matter if the query.find() has returned or not.
variable theuser is always the value of theuser in the last for loop because the for loop is able to complete before any of the callback functions are triggered

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