I have two functions that make $.getJSON calls - one looks at JSON stored on server, and the other one makes a call to Flickr.
I have this:
$(document).ajaxStart(function(){
alert('requeststart');
//$('#loading').show();
});
which works when $.getJSON is called with some given path, but it does not raise an alert when $.getJSON makes the call to Flickr. The Flickr call works, and everything loads as it should... it doesn't fire ajaxStart however. (I'm using ajaxStart and ajaxStop to show a loading-gif)
Any ideas?
Here is the function that calls Flickr :
$('#submit').on("click", function (evt) {
evt.preventDefault();
var tag = $('input').val();
if (tag == "") {
alert("please enter a tag");
}
else {
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?",{tags: tag, tagmode: "any", format: "json" },
function(data) {
$.each(data.items, function(i,item){
var name = names[index];
$("#" + name).empty();
$('<img src="' + item.media.m.replace("_m.jpg", ".jpg") + '" />').appendTo("#" + name);
$(' ' + item.title + '').appendTo("#" + name );
index ++;
if (i==5) {
index = 0;
}
});
});
}
});
jQuery only fires those events (ajaxStart and ajaxStop) if it internally uses the XMLHttpRequest object.
However if you request data from a different domain using a callback parameter (JSONP), jQuery does not use XMLHttpRequest, instead it accomplishes that by inserting a <script> tag.
That's why the events get not fired.
As a workaround you could start your loading animation just before the $.getJSON call, and stop it inside the function(data) {.. block.
for multiply requests you have too count them individually. I did it like this:
var loader = 0;
function showloader(cnt){
loader = loader + cnt;
if(loader < 1) {
$('#loading').hide();
} else {
$('#loading').show();
}
}
increase by 1 and call it before $.getJSON and decrease it and fire again when its done. like:
showloader(1);
$.getJSON(
[...]
}).done(function() {
showloader(-1);
[...]
});
Related
The data on the webpage is displayed dynamically and it seems that checking for every change in the html and extracting the data is a very daunting task and also needs me to use very unreliable XPaths. So I would want to be able to extract the data from the XHR packets.
I hope to be able to extract information from XHR packets as well as generate 'XHR' packets to be sent to the server.
The extracting information part is more important for me because the sending of information can be handled easily by automatically triggering html elements using casperjs.
I'm attaching a screenshot of what I mean.
The text in the response tab is the data I need to process afterwards. (This XHR response has been received from the server.)
This is not easily possible, because the resource.received event handler only provides meta data like url, headers or status, but not the actual data. The underlying phantomjs event handler acts the same way.
Stateless AJAX Request
If the ajax call is stateless, you may repeat the request
casper.on("resource.received", function(resource){
// somehow identify this request, here: if it contains ".json"
// it also also only does something when the stage is "end" otherwise this would be executed two times
if (resource.url.indexOf(".json") != -1 && resource.stage == "end") {
var data = casper.evaluate(function(url){
// synchronous GET request
return __utils__.sendAJAX(url, "GET");
}, resource.url);
// do something with data, you might need to JSON.parse(data)
}
});
casper.start(url); // your script
You may want to add the event listener to resource.requested. That way you don't need to way for the call to complete.
You can also do this right inside of the control flow like this (source: A: CasperJS waitForResource: how to get the resource i've waited for):
casper.start(url);
var res, resData;
casper.waitForResource(function check(resource){
res = resource;
return resource.url.indexOf(".json") != -1;
}, function then(){
resData = casper.evaluate(function(url){
// synchronous GET request
return __utils__.sendAJAX(url, "GET");
}, res.url);
// do something with the data here or in a later step
});
casper.run();
Stateful AJAX Request
If it is not stateless, you would need to replace the implementation of XMLHttpRequest. You will need to inject your own implementation of the onreadystatechange handler, collect the information in the page window object and later collect it in another evaluate call.
You may want to look at the XHR faker in sinon.js or use the following complete proxy for XMLHttpRequest (I modeled it after method 3 from How can I create a XMLHttpRequest wrapper/proxy?):
function replaceXHR(){
(function(window, debug){
function args(a){
var s = "";
for(var i = 0; i < a.length; i++) {
s += "\t\n[" + i + "] => " + a[i];
}
return s;
}
var _XMLHttpRequest = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
this.xhr = new _XMLHttpRequest();
}
// proxy ALL methods/properties
var methods = [
"open",
"abort",
"setRequestHeader",
"send",
"addEventListener",
"removeEventListener",
"getResponseHeader",
"getAllResponseHeaders",
"dispatchEvent",
"overrideMimeType"
];
methods.forEach(function(method){
window.XMLHttpRequest.prototype[method] = function() {
if (debug) console.log("ARGUMENTS", method, args(arguments));
if (method == "open") {
this._url = arguments[1];
}
return this.xhr[method].apply(this.xhr, arguments);
}
});
// proxy change event handler
Object.defineProperty(window.XMLHttpRequest.prototype, "onreadystatechange", {
get: function(){
// this will probably never called
return this.xhr.onreadystatechange;
},
set: function(onreadystatechange){
var that = this.xhr;
var realThis = this;
that.onreadystatechange = function(){
// request is fully loaded
if (that.readyState == 4) {
if (debug) console.log("RESPONSE RECEIVED:", typeof that.responseText == "string" ? that.responseText.length : "none");
// there is a response and filter execution based on url
if (that.responseText && realThis._url.indexOf("whatever") != -1) {
window.myAwesomeResponse = that.responseText;
}
}
onreadystatechange.call(that);
};
}
});
var otherscalars = [
"onabort",
"onerror",
"onload",
"onloadstart",
"onloadend",
"onprogress",
"readyState",
"responseText",
"responseType",
"responseXML",
"status",
"statusText",
"upload",
"withCredentials",
"DONE",
"UNSENT",
"HEADERS_RECEIVED",
"LOADING",
"OPENED"
];
otherscalars.forEach(function(scalar){
Object.defineProperty(window.XMLHttpRequest.prototype, scalar, {
get: function(){
return this.xhr[scalar];
},
set: function(obj){
this.xhr[scalar] = obj;
}
});
});
})(window, false);
}
If you want to capture the AJAX calls from the very beginning, you need to add this to one of the first event handlers
casper.on("page.initialized", function(resource){
this.evaluate(replaceXHR);
});
or evaluate(replaceXHR) when you need it.
The control flow would look like this:
function replaceXHR(){ /* from above*/ }
casper.start(yourUrl, function(){
this.evaluate(replaceXHR);
});
function getAwesomeResponse(){
return this.evaluate(function(){
return window.myAwesomeResponse;
});
}
// stops waiting if window.myAwesomeResponse is something that evaluates to true
casper.waitFor(getAwesomeResponse, function then(){
var data = JSON.parse(getAwesomeResponse());
// Do something with data
});
casper.run();
As described above, I create a proxy for XMLHttpRequest so that every time it is used on the page, I can do something with it. The page that you scrape uses the xhr.onreadystatechange callback to receive data. The proxying is done by defining a specific setter function which writes the received data to window.myAwesomeResponse in the page context. The only thing you need to do is retrieving this text.
JSONP Request
Writing a proxy for JSONP is even easier, if you know the prefix (the function to call with the loaded JSON e.g. insert({"data":["Some", "JSON", "here"],"id":"asdasda")). You can overwrite insert in the page context
after the page is loaded
casper.start(url).then(function(){
this.evaluate(function(){
var oldInsert = insert;
insert = function(json){
window.myAwesomeResponse = json;
oldInsert.apply(window, arguments);
};
});
}).waitFor(getAwesomeResponse, function then(){
var data = JSON.parse(getAwesomeResponse());
// Do something with data
}).run();
or before the request is received (if the function is registered just before the request is invoked)
casper.on("resource.requested", function(resource){
// filter on the correct call
if (resource.url.indexOf(".jsonp") != -1) {
this.evaluate(function(){
var oldInsert = insert;
insert = function(json){
window.myAwesomeResponse = json;
oldInsert.apply(window, arguments);
};
});
}
}).run();
casper.start(url).waitFor(getAwesomeResponse, function then(){
var data = JSON.parse(getAwesomeResponse());
// Do something with data
}).run();
I may be late into the party, but the answer may help someone like me who would fall into this problem later in future.
I had to start with PhantomJS, then moved to CasperJS but finally settled with SlimerJS. Slimer is based on Phantom, is compatible with Casper, and can send you back the response body using the same onResponseReceived method, in "response.body" part.
Reference: https://docs.slimerjs.org/current/api/webpage.html#webpage-onresourcereceived
#Artjom's answer's doesn't work for me in the recent Chrome and CasperJS versions.
Based on #Artjom's answer and based on gilly3's answer on how to replace XMLHttpRequest, I have composed a new solution that should work in most/all versions of the different browsers. Works for me.
SlimerJS cannot work on newer version of FireFox, therefore no good for me.
Here is the the generic code to add a listner to load of XHR (not dependent on CasperJS):
var addXHRListener = function (XHROnStateChange) {
var XHROnLoad = function () {
if (this.readyState == 4) {
XHROnStateChange(this)
}
}
var open_original = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async, unk1, unk2) {
this.requestUrl = url
open_original.apply(this, arguments);
};
var xhrSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
var xhr = this;
if (xhr.addEventListener) {
xhr.removeEventListener("readystatechange", XHROnLoad);
xhr.addEventListener("readystatechange", XHROnLoad, false);
} else {
function readyStateChange() {
if (handler) {
if (handler.handleEvent) {
handler.handleEvent.apply(xhr, arguments);
} else {
handler.apply(xhr, arguments);
}
}
XHROnLoad.apply(xhr, arguments);
setReadyStateChange();
}
function setReadyStateChange() {
setTimeout(function () {
if (xhr.onreadystatechange != readyStateChange) {
handler = xhr.onreadystatechange;
xhr.onreadystatechange = readyStateChange;
}
}, 1);
}
var handler;
setReadyStateChange();
}
xhrSend.apply(xhr, arguments);
};
}
Here is CasperJS code to emit a custom event on load of XHR:
casper.on("page.initialized", function (resource) {
var emitXHRLoad = function (xhr) {
window.callPhantom({eventName: 'xhr.load', eventData: xhr})
}
this.evaluate(addXHRListener, emitXHRLoad);
});
casper.on('remote.callback', function (data) {
casper.emit(data.eventName, data.eventData)
});
Here is a code to listen to "xhr.load" event and get the XHR response body:
casper.on('xhr.load', function (xhr) {
console.log('xhr load', xhr.requestUrl)
console.log('xhr load', xhr.responseText)
});
Additionally, you can also directly download the content and manipulate it later.
Here is the example of the script I am using to retrieve a JSON and save it locally :
var casper = require('casper').create({
pageSettings: {
webSecurityEnabled: false
}
});
var url = 'https://twitter.com/users/username_available?username=whatever';
casper.start('about:blank', function() {
this.download(url, "hop.json");
});
casper.run(function() {
this.echo('Done.').exit();
});
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 8 years ago.
I have a function that pulls images from an API to insert them in an animated scroller. It looks like this (simplified for clarity):
function getPhotos(id) {
$.when(Service.getPhotos(id))
.then(function(results) {
var photos = results.photos,
scroller = $("#scroller");
// Add the photos to the scroller
for (var i = 0; i < photos.length; i++) {
var photo = photos[i].url;
// Only add a photo if its URL is valid
if (photoExists(photo) == 200) {
scroller.append("<li><img src='" + photo + "' /></li>");
} else {
console.log("Photo " + photo + " doesn't exist");
}
}
})
.fail(function(err) {
console.log(err);
});
}
However, the photo URLs don't always resolve to valid images, so I run them through the photoExists() function:
function photoExists(photo) {
var http = jQuery.ajax({
type: "HEAD",
url: photo,
async: false
});
return http.status;
}
If a given photo URL returns a status code of 200, then I know the image exists and I insert it into the scroller; otherwise, I skip over it so that I don't insert a broken image.
The problem here is that async: false – because this isn't asynchronous, the whole UI locks up until everything completes, which can be a very long time depending on how many photo URLs I have to loop through.
If I use async: true, however, then the photoExists() function attempts to return the status code before the AJAX request itself actually completes – so photoExists(photo) never returns 200, resulting in nothing being added to the scroller.
How would I adjust this so that photoExists() can run asynchronously and therefore avoid locking up the UI, but still return the correct status code so I can insert the photos into the scroller properly?
You need to provide a callback function for your photoExists function. Something like this:
function photoExists(photo, callback) {
var http = jQuery.ajax({
type: "HEAD",
url: photo,
async: true,
success: function(){
callback(photo, true);
},
error: function(){
callback(photo, false);
}
});
}
Then use it like so:
for (var i = 0; i < photos.length; i++) {
var photo = photos[i].url;
// Only add a photo if its URL is valid
photoExists(photo, function(photo, isSuccessful){
if (isSuccessful) {
scroller.append("<li><img src='" + photo + "' /></li>");
} else {
console.log("Photo " + photo + " doesn't exist");
}
});
}
Added photo to callback function to avoid possible closure issues with the for loop
In my application listView('refresh') doesn't work. Here is my code
I create listView dynamically
var str = "<ul data-role='listview' data-inset='true' id='mylist'>";
for(var i = 0; i<data.length; i++ ){
str += "<li>"+data[i].note.text+"</li>";
}
str += "</ul>"
$('#content').append(str);
function addnote(){
var note_text = $('#note_text').val();
var note_lat = $('#lat').val();
var note_lng = $('#lng').val();
$.ajax({
type: "POST",
beforeSend: function (jqXHR) {
jqXHR.setRequestHeader(KEY1, _key1);
jqXHR.setRequestHeader(KEY2, _key2);
},
url:SERVER_URL+"api/addNotes/",
data: {type: 'text',note_text: note_text, note_lat: note_lat , note_lng: note_lng},
success: function(data, textStatus, jqXHR) {
if (data.status == "ok"){
$.mobile.changePage("file:///android_asset/www/index.html?"+_key1+"|"+_key2+"|");
}
else{
alert("Something wrong");
}
},
error: function(jqXHR, textStatus, errorThrown) {
alert("Error=" + errorThrown);
},
complete: function() {
$('#mylist').listview('refresh');
}
});
}
I read many reports and forums there said I have to call listview('refresh') in ajax's complete function. But in my code it doesn't work, could anyone tell me what is the problem here?
Refresh is for when you are adding elements to an existing, enhanced listview. If you are creating the entire listview dynamically you need to trigger a "create" on the parent div.
So if you had <div id="container><ul></ul></div>, you would need to call $("#container").trigger("create").
You probably want to see your data after adding some values. If you want to do this listview.refresh doesn't help you after adding some values you have to call you data again, i.e you have to call getNote() again.
I think it helps.
Try this for you complete function instead
complete: function() {
$('#mylist').listview();
}
As someone else mentions the refresh method is for when you are adding listitems to a listview that JQM has already created.
First of all , You must understand how does the refresh work, it will just refresh the listview's css when you add a new LI so that the new LI will have a right css,
function createItem(tx,results){
var len = results.rows.length;
console.log("lisitem len "+len);
for(var i = 0;i<len;i++){
var content = results.rows.item(i).content;
var id = results.rows.item(i).id;
var string = '<li>'+content+' ['+results.rows.item(i).date+']</li>';
$("#all_list").append(string);
};
freshList("all_list");
}
freshList("all_list"); it will refresh the listview ,if you don't do it, all of the LI will display like below.
<li> 测试成功 [2013-10-02]</li>
the LI doesn't have a css description
that's why your ajax operation doesn't work
I'm using a ajax script to load content from other pages, without having to reload the browser.
For now I'm retrieving the content of the #inside div, but I'm using a full-background slideshow (#full) wich needs to be loaded as wel.
Maybe this can be achieved by loading the content of the #full div also, but I don't know how I could do that.
This is my code:
// Self-Executing Anonymous Function to avoid more globals
(function() {
// Home link isn't dynamic, so default set class name to it to match how dynamic classes work in WordPress
$(".home li.home").removeClass("home").addClass("current_page_item");
// Add spinner via JS, cuz would never need it otherweise
$("body").append("<img src='http://themeclubhouse.digwp.com/images/ajax-loader.gif' id='ajax-loader' />");
var
$mainContent = $("#wrapper"),
$ajaxSpinner = $("#ajax-loader"),
$searchInput = $("#s"),
$allLinks = $("a"),
$el;
// Auto-clear search field
$searchInput.focus(function() {
if ($(this).val() == "Search...") {
$(this).val("");
}
});
$('a:urlInternal').live('click', function(e) {
// Caching
$el = $(this);
if ((!$el.hasClass("comment-reply-link")) && ($el.attr("id") != 'cancel-comment-reply-link')) {
var path = $(this).attr('href').replace(base, '');
$.address.value(path);
$(".current_page_item").removeClass("current_page_item");
$allLinks.removeClass("current_link");
$el.addClass("current_link").parent().addClass("current_page_item");
return false;
}
// Default action (go to link) prevented for comment-related links (which use onclick attributes)
e.preventDefault();
});
// Fancy ALL AJAX Stuff
$.address.change(function(event) {
if (event.value) {
$ajaxSpinner.fadeIn();
$mainContent
.empty()
.load(base + event.value + ' #content', function() {
$ajaxSpinner.fadeOut();
$mainContent.fadeIn();
});
}
var current = location.protocol + '//' + location.hostname + location.pathname;
if (base + '/' != current) {
var diff = current.replace(base, '');
location = base + '/#' + diff;
}
});
})(); // End SEAF
try to repeat the procedure:
// Fancy ALL AJAX Stuff
$.address.change(function(event) {
if (event.value) {
//load ajax image
$ajaxSpinner.fadeIn();
$mainContent.empty().load(base + event.value + ' #content', function() {
$ajaxSpinner.fadeOut();
$mainContent.fadeIn();
});
// repeat here
//load another div
$mainContent.empty().load(base + event.value + ' #mydiv1', function() {
$mainContent
});
//load another div
$mainContent.empty().load(base + event.value + ' #mydiv2', function() {
$mainContent
});
}
let me know if it works, Ciao! :)
I'm playing and trying to learn a little more about AJAX in MVC.
Currently, I have the following block of code in an MVC view. The idea is that when the link is clicked, the app will fire the FlagInappropriate() method in the controller and display a message in the link that was clicked and disable the link.
<script type="text/javascript">
function flagInappropriate(postId) {
var url = "Home/FlagAsInappropriate/" + postId;
$.post(url, function (data) {
if (data) {
$('#LinkAppropriate').text('Post has been flagged');
$('#LinkAppropriate').attr("href", "javascript:void(0);");
} else {
alert('Post cannot be flagged');
}
});
}
</script>
<h1>Index</h1>
Flag as inappropriate
Currently this is working and have no complaints with it. My next step is to be able to have any number of links call this javascript method. See the example below:
<script type="text/javascript">
function flagInappropriate(postId) {
var url = "Home/FlagAsInappropriate/" + postId;
var callingObject = CallingObjectIDGetter();
$.post(url, function (data) {
if (data) {
$('#' + callingObject).text('Post has been flagged');
$('#' + callingObject).attr("href", "javascript:void(0);");
} else {
alert('Post cannot be flagged');
}
});
}
</script>
<h1>Index</h1>
Flag as inappropriate
Flag as inappropriate
Flag as inappropriate
Flag as inappropriate
Flag as inappropriate
Given this scenario, how do I derive and manipulate the object that called the Javascript method? In short, what would I use in place of the line var callingObject = CallingObjectIDGetter();?
Thanks!
Instead of binding the events in your markup, why don't you leverage jQuery and do this:
markup:
Flag as inappropriate
... more links go here...
javascript:
function flagInappropriate(callingObject, url) {
$.post(url, function (data) {
if (data) {
$('#' + callingObject).text('Post has been flagged');
$('#' + callingObject).attr("href", "javascript:void(0);");
} else {
alert('Post cannot be flagged');
}
});
}
$('a').click(function(e) {
e.preventDefault();
flagInappropriate($(this).attr('id'), $(this).attr('href'));
});