I was trying to do a JS script that takes the last videos from a YouTube channel. I wanted to take, from each video, the title, id and thumbnail, to use them in my page. My first option was creating a global array with all the videos, to use it in other functions (createThumbnail, createPlayer...). This is the code where I take the channel's latest videos:
user = 'PiewDiePie'
var videos = [];
$.getJSON('http://gdata.youtube.com/feeds/users/' + user + '/uploads?alt=json-in-script&format=5&callback=?', null, function(data) {
var feed = data.feed;
$.each(feed.entry, function(i, entry) {
var video = {
title: entry.title.$t,
id: entry.id.$t.match('[^/]*$'),
thumbnails: entry.media$group.media$thumbnail
};
videos.push(video);
});
alert('Inside function: ' + videos[0].title);
});
alert('Outside function: ' + videos[0].title);
// Functions with the videos array
The problem here is that the first alert shows the title but, the second one, gives the following error:
Uncaught TypeError: Cannot read property 'title' of undefined
Finally, I've changed my script avoiding the use global scope variables but I can't understand why this wasn't working. I'm new to javascript, probably this is something evident for an expert...
Thank you very much
Your problem is one of chronology.
Your request to the YouTube API is asynchronous, meaning while it's happening the rest of your code after it will continue to be executed. So when your second alert fires, the request hasn't completed yet, and so the videos array is not yet populated.
Instead, your second alert needs to wait for the request to resolve.
Change
$.getJSON(...
to
var req = $.getJSON(....
Then wrap your second alert in a callback for the request
req.done(function() { alert('Outside function: ' + videos[0].title); });
Related
I'm doing a webapp with html+jquery and a java rest-service backend.
I have a textfield, with typeahead suggestions, so every character the user types in the field
will trigger a server-round trip and update the list of typeahead suggestions.
Essential parts of the code:
var showTypeaheadSuggestions = function(data) {
// update ui-element ...
}
var displayFailure = function() {
// update ui-element ...
}
var searchText = $("#searchText");
var searchTextKeyup = function() {
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
It's basically working.
But I was thinking abt what happens if you type, for example, 2 letters "ab" (that will trigger first a request for "a" and then a request for "ab")...
Then, what happens if the "a" response takes a bit longer to process, and arrives after the "ab" response?
Do I need to detect this in my code, to throw away the "a" response?
In http://api.jquery.com/jquery.ajax/ it does says:
Promise callbacks — .done(), .fail(), .always(), and .then() — are
invoked, in the order they are registered.
What does that mean exactly?
I was hoping this means $.ajax() would automatically handle the above scenario correct.
But when I do a small test (on the server-side I simply injected a 2 secs sleep-delay, only when the search-string is exactly "a"),
it turns out it does not behave as I expected.
The typeahead list will first get updated with the "ab" response, and then when the "a" response
arrives, it also updates, so the typeahead list gets the wrong suggestions.
What is the established way to handle this correctly?
There's another approach if you want to keep the server side code without changes. You can actually wrap the return functions inside a class and create instances for each request, then store the latest instance in a global scoped variable and check if the owner of the invoked method does match the latest instance:
var lastRequest;
var searchText = $("#searchText");
function requestClass()
{
var that = this;
this.showTypeaheadSuggestions = function(data) {
//Update ui-element
if (lastRequest == that)
console.log('suggestions retrieved: ' + data);
else
console.log('this response (' + data + ') is ignored');
};
this.displayFailure = function() {
//Update ui-element
console.log('failure');
};
}
var searchTextKeyup = function() {
var request = new requestClass();
lastRequest = request;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(request.showTypeaheadSuggestions).fail(request.displayFailure);
}
searchText.on('keyup', searchTextKeyup);
I have tested this with the small-test you proposed in the question (adding a 2 seconds delay when the search string does match the 'a' character) and the result is the following:
suggestions retrieved: ab
this response (a) is ignored
One of the ways I approached this problem was to assign an ID for each time you call it, and pass it as an ID to server side. When your server is done processing it, it then sends the result back along with it's id.
Then, every time the client side code runs, the ID will increment. For example:
var callback_id = 0;
var searchText = $("#searchText");
var searchTextKeyup = function() {
callback_id ++;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
data : callback_id,
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
Then, when you receive the response back, you check if the id is the current one. In the event that the user fires two events at once, your ajax event will be triggered twice, once with callback_id = 0, and one with callback_id = 1.
The last thing you have to then do is an if statement only updating your TypeaheadSuggestions if the callback_id is the most current one by comparing the id sent back from your server response.
You must compare new input text with text you sent, and if it what user wants to find - you will show it, otherwise do nothing with response.
For example :
var searchText = $("input").val()
$.ajax({
....
data: {searchText : searchText}
success: funtion(){
if($("input").val()==searchText){
//SHOW RESULTS
}
}
})
The Promises interface returns to you a "Promise object" immediately so that you can use a different syntax for the callback.
Instead of:
asyncCall(callback);
You can use:
asyncCall()
.then(callback);
And you can chain these:
authorizeAccount()
.then(getNames)
.then(processNames);
The callbacks will be executed in the order you register them -- processNames will wait for getNames to resolve first.
The "established" way to handle this "correctly" is probably to add some client-side debouncing so that only the whole request ('query' instead of 'q', 'qu' 'que'...) is processed when you are typing the word:
http://underscorejs.org/#debounce with a timeout that expires a response if it takes too long to come back.
So yesterday, I added some Jquery code and it worked perfectly. Today I was adding some more for a new part of my website and it did not work. When I tried the code I added yesterday, that stopped working as well. The first thing I do is create a function to get anything from the url that I need. The first 2 inputs are for following users. The first input should send the id variable to the url bar and is retrieved in the php file called follow.php. The second input should do the same thing. Finally the last function is for liking peoples posts. It sends the type of like(like or unlike) to the php file named like.php and is retrieved there. And should post the retrieved data into a div called likes. However, none of which is occurring when the buttons are pressed. Even when I just send an alert to make sure the on click is working, nothing happens either.
Here is my code:
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
$('input#sub').on('click', function(){
var id = getParameterByName('id');
$.get('follow.php' , {id: id}, function(data){
location.reload();
});
});
$('input#sub1').on('click', function(){
var id = getParameterByName('id');
$.get('unfollow.php' , {id: id}, function(data){
location.reload();
});
});
function doAction(postid , type){
alert("Works");
$.post('like.php' , {postid: postid type: type} , function(data){
$('div#likes').text(data);
});
}
Let's try to sum things up. I'm gonna change most of your code simply because you don't need all of that, otherwise jQuery wouldn't be helpful at all.
Assuming your caller is this anchor:
<img id="like" src="Social/down.png"/>
After this you can manipulate the clicked object getting its attributes easily through:
$('.idsfromdb').click(function(){
var id = $(this).prop('id');
var another_id_obj = $('newselector').prop('id');
// more logic, ajax,...
});
You can attach this kind of functions to every click event and then manipulate the object, call a php file, and so on... I hope I helped you somehow with this.
open google developer tools > network and see if request is made and if it has all data you want as request and if there is anything in response.
if there is request with all parameters, you have problem on server side,
if parameters not same that you are trying to send with ajax, than prevent buttons default action, you must prevent it every time when using ajax especially when button submits form.
$('input#sub').on('click', function(event){
event.preventDefault();
//your ajax
}
I'm trying to loop through every 1000 elements in a S3 bucket. This is because 1000 elements in the maximum returned by a get request. If there are more than 1000 elements, it get paginated, and the get request returns with a field call IsTruncated as true, and a marker (NextMarker) element to pass to the next call, letting the next get request start at the next 1000 elements.
I'm getting the data from the get request as a parameter in a callback function, and attempting to store the two pieces of above information in global variables for use in an outer loop. However, the outer loop goes off to infinity because the global variables are never modified in my get request callback function. I've tried using window.variable inside the callback to no avail. Could anyone help me restructure this code to accomplish my goals?
Thanks
Outter loop is commented out for debugging purposes. There are a number of debugging console.log statement I used to determined the root of the problem.
<script type="text/javascript">
s3_bucket = "link_to_s3_bucket";
var go = true;
var marker = "";
//while(go){
console.log('pass');
console.log(s3_bucket + marker);
$.get(
s3_bucket+marker,
"{}",
function(data) {
$(data).find('Key').each(function(i, key) {
key = key.innerHTML;
$("<a />", {
href : s3_bucket+key,
text : key
}).prependTo("#links");
$("<br />").prependTo("#links");
});
window.go = $(data).find('IsTruncated')[0].innerHTML;
window.marker = "&marker=" + $(data).find('NextMarker')[0].innerHTML;
},
"xml"
);
//}
console.log(go);
console.log(marker);
</script>
Your data returns asynchronously from Amazon, so those variables haven't been defined yet when you call those console logs. Put the console logs inside of the callback after the variable assignments.
I have a list of facebook user id numbers from an xml response and all I want to do is write html that places their picture next to their name. Unfortunately I am having a strange problem:
var friendList = "";
$(xml).find("id").each(function ()
{
var tId = $(this).text();
var tUrl = "/" + tId;
var perName = "";
FB.api(tUrl, function(response) {
perName += response.name;
});
alert(perName);
friendList += "<div class=\"picSpacer\"><img src=\"https://graph.facebook.com/"+tId+"/picture/?type=large\" class=\"friendDIV\" /><div class=\"nameBox\">"+perName+"</div></div>";
});
With this code it works, but if I remove the alert it does not work. The alert pops up undefined. It's as if the string perName has to be accessed once before it actually contains the user's name. I don't understand how this can be.
it's simple.
FB.api() performs an asynchronous request, when it's finished
function(response) {
//do entire DOM manipulation here
}
gets called.
Now alert() delays execution long enough, so that that callback gets calland and perName gets defined.
Just move your DOM manipulation code into the callback to make sure you actually have the response.
Background
I'm writing an asynchronous comment system for my website, after reading plenty of tutorials on how to accomplish this I started building one from scratch. The comments are pulled using a JSON request and displayed using Javascript (jQuery). When the user adds a new comment it goes through the hoops and finally is sent via AJAX to the backend where it's added to the database. In the success section of the AJAX request I had the script empty the comments, then repull the new list (including the new post) and redisplay them.
Problem
While that was all nice, since it's making the page much shorter, then much longer it messes up where the user is viewing the page. I wanted to have it readjust the page back down to the end of the comment list (where the add comment form is). It also re-enables the add button, which was disabled when the clicked it to prevent impatient people from spamming.
$('#commentList').empty();
getComments('blog', $('input#blogId').val());
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
While this worked all well and good in theory, it seemed that the browser was getting ahead of itself and processing the window.location before the getComments function was done. So I read a little more and googled it and it seemed people were saying (for similar problems) to use callback functions, so I came up with this:
$('#commentList').empty();
getComments('blog', $('input#blogId').val(), function() {
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
});
This generates no javascript errors according to FireFox, but nothing within the callback function is working, it's not re-enabling the button nor changing the window.location.
Any ideas? Better ways to go about it? Do I have a glaring typo that I can't seem to see?
Thanks!
Update
I was under the impression the callback functions were a standard thing you could use.
function getComments(type, id)
{
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++)
{
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" )
{
legend.text((x+1) + ' - ' + data[x].name);
}
else
{
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
}
});
}
This is called on document ready. Pulling all the comments and displaying them inside the #commentList div. When the user submits his/her comment it performs an AJAX request to a PHP script that adds the new comment to the database, upon success of this I have this:
$('#commentList').empty();
getComments('blog', $('input#blogId').val());
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
Deletes all the comments from the page.
Uses JSON to request the comments again (including the users new one).
Moves the page to the #addComment anchor, which is where their new comment would be displayed.
Re-enables the add comment button.
The problem is that the browser does the window.location line before the getComments function is done rendering all the comments, so as the page grows the user isn't looking anywhere near their new comment.
I expect here the problem is your getComments() function (for which more detail is required). You're supplying a third argument being a callback but does the function actually use a callback? What is it doing?
Certain jQuery functions provide callbacks but this isn't an automatic feature. If you're waiting for a user to type a comment you need to trigger the relevant event when they click "Done" or whatever they do.
Ok, try this:
function get_comments(type, id, callback) {
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++) {
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" ) {
legend.text((x+1) + ' - ' + data[x].name);
} else {
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
if (typeof callback != 'undefined') {
callback();
}
}
});
}
Note: the difference here is that a third argument is supplied to get_comments() which is a callback that'll be called at the end of your $.getJSON() callback. That'll give you the proper ordering you want.
I might also suggest not constructing the HTML like that but including it in your page and hiding/unhiding it as necessary. It tends to be much more performant that dynamic HTML and have less issues (eg new HTML, unless you use $().live() will not have relevant event handlers).
Edit: Made the callback optional as per the comments. With the above code you can call the function without or without the callback.
Simple. Re-enable the button and go to the anchor after you receive the request and process the information. Like so:
function getComments(type, id)
{
// ADDED
$('#commentList').empty();
$.getJSON("/ajax/"+type+"/comments?jsoncallback=&id="+id, function(data) {
for (var x = 0; x < data.length; x++)
{
var div = $("<div>").addClass("comment").appendTo("#commentList");
var fieldset = $("<fieldset>");
var legend = $("<legend>").addClass("commentHeader");
if ( data[x].url == "" )
{
legend.text((x+1) + ' - ' + data[x].name);
}
else
{
$("<a>").attr({href: data[x].url}).text((x+1) + ' - ' + data[x].name).appendTo(legend);
}
legend.appendTo(fieldset);
$("<div>").addClass("date").text(data[x].timestamp).appendTo(fieldset);
$("<p>").addClass("comment").text(data[x].content).appendTo(fieldset);
fieldset.appendTo(div);
}
// ADDED
window.location = "#addComment";
$('#comAdd').removeAttr('disabled');
});
}
Personal opinion: rather than fetching all comments, why not fetch comments from a certain date? When you load the page, include a server time in the response. The Javascript uses this to query behind the scenes (to automatically check for new comments). The JSON response includes a new server time, which is used in the next response.
How would you handle deleted comments? Easy: have a deleted_on column in your database table, query it, and spit that out in the JSON response along with new posts.
Suggestion: instead of #addcomment, ID comments by timestamp.