I have a script that makes $.ajax request for a json api. So what I want to do is to build unit test so I can test the result from the ajax request. For example if I get json object back. I know result should include "items" and "result" which is an array. The things is I dont know how to initialize the $.ajax function which is inside a
$("#button").click(function() { });
Here's the skeleton of my javascript index.js file. The file is not complete. as it is longer. I just included the relevant parts. But it works. Here's the app live online http://pctechtips.org/apps/books/
$(document).ready(function() {
var item, tile, author, publisher, bookLink, bookImg;
var outputList = document.getElementById("list-output");
var bookUrl = "https://www.googleapis.com/books/v1/volumes?q=";
var searchData;
$("#search").click(function() {
outputList.innerHTML = ""; //empty html output
searchData = $("#search-box").val();
//handling empty search input field
if(searchData === "" || searchData === null) {
displayError();
}
else {
// console.log(searchData);
// $.get("https://www.googleapis.com/books/v1/volumes?q="+searchData, getBookData()});
$.ajax({
url: bookUrl + searchData,
dataType: "json",
success: function(response) {
console.log(response)
if (response.totalItems === 0) {
alert("no result!.. try again")
}
else {
$("#title").animate({'margin-top': '5px'}, 1000); //search box animation
$(".book-list").css("visibility", "visible");
displayResults(response);
}
},
error: function () {
alert("Something went wrong.. <br>"+"Try again!");
}
});
}
$("#search-box").val(""); //clearn search box
});
});
In your test you need first to prepare a HTML fixture which will contain all the required elements like #search. After preparing it, you can load your script via $.getScript() - it will attach click event listener to #search. Finally, you have to spy on $.ajax and trigger the click manually via $('#search').trigger('click')
Related
This question already has answers here:
What does "async: false" do in jQuery.ajax()?
(7 answers)
Closed 3 years ago.
I have an ajax function and thought it would be nice to include a little ajax-spinner to tell the enduser something is actually happening. This is my current jQuery function:
$('#contact-form').submit(function(e)
{
e.preventDefault();
let overlay = $('#overlay'),
loader = $('#loader-popup');
console.log(overlay);
console.log(loader);
console.log('===================');
//show overlay
overlay.removeClass('hidden');
loader.removeClass('hidden');
console.log(overlay);
console.log(loader);
let formData = new FormData($(this)[0]),
params = [];
$.ajax({
data: formData,
type: 'post',
url: '/pages/contact-us/action/send.php',
cache: false,
contentType: false,
processData: false,
success: function(res)
{
if (res == 1) {
params['type'] = 1;
params['msg'] = 'We will be with you as soon as we can!'
} else {
try {
res = $.parseJSON(res);
let data = [];
$.each(res, function(key, value) {data.push(value)});
params['type'] = 2;
params['msg'] = data.join('<br />')
} catch(e) {
console.log(e);
alert('Huh. that\'s weird, something went wrong! Please try again');
//cause syntax error to stop script working
die()
}
}
validator.displayAlert(params['type'], params['msg'])
},
error: function(res)
{
console.log(res);
alert('Don\'t worry.. it\'s not you, it\'s us.')
}
});
//hide overlay
overlay.addClass('hidden');
loader.addClass('hidden');
});
But weirdly the overlay doesn't show, nor does the loader. What makes this hard to kinda debug and fathom is the console.log output.
first console.log(overlay)
Object [ div#overlay.hidden ]
second console.log(loader)
Object [ div#loader-popup.hidden ]
third console.log(overlay)
Object [ div#overlay ]
fourth console.log(loader)
Object [ div#loader-popup ]
So I can see that my .removeClass() function is working, however, inspecting my page once the form is being submitted shows the elements with the hidden class. If I manually remove that hidden class in the inspector tab then everything shows, so I know it's not a CSS issue.
You can see this happen on a much simpler scale here
I've also tried with .toggle() with no avail.
How do I even begin to debug something that seems to work behind-the-scenes but, not on screen?
You should call hide the overlay in your callback, because it'll be executing asynchronously.
Something like
try {
res = $.parseJSON(res);
let data = [];
$.each(res, function(key, value) {
data.push(value)
});
params['type'] = 2;
params['msg'] = data.join('<br />')
} catch (e) {
console.log(e);
alert('Huh. that\'s weird, something went wrong! Please try again');
//cause syntax error to stop script working
die()
} finally {
//hide overlay
overlay.addClass('hidden');
loader.addClass('hidden');
}
The logic within the $.ajax() call is asynchronous. As such you remove the class then immediately add it back in as the AJAX request is in progress.
To fix this, change the addClass() calls to be made after the AJAX request completes. In your case the best place to do this would be in the complete callback as it will fire whether the AJAX request completed successfully or with an error:
$('#contact-form').submit(function(e) {
e.preventDefault();
let $overlays = $('#overlay, #loader-popup').removeClass('hidden');
let formData = new FormData(this),
params = [];
$.ajax({
// ajax settings...
complete: function() {
$overlays.addClass('hidden');
}
});
});
My code is working fine, but I do not like at all.
I would like to split one file into two files, one containing webServices and another one with a controller.
My file do something like this:
File: Validacion.js (controller)
// Load next view
var MainView = Alloy.createController('index').getView('tabGroup');
// this a function call when I click a button "validar" on ValidaciĆ³n View.
function btnClick(){
var url = 'www.cocoloco.com/whatever';
var webService = Ti.Network.createHTTPClient({
onload: function(e){
// open new view
MainView.open();
// close actual view
$.tabValidacion.close();
$.tabValidacion = null;
},
onerror: function(e){
alert('onerror: ' + e.error);
},
timeout: 5000
});
webService.open('POST', url);
webService.send();
}
But I would like to do something like this below (divided in two files: webServices.js -library- and validation.js -controller-).
The problem is that I always have the message "error" because I pass throught "success = webServices.protocol();" but as far as it is "asynchronous" it doesn't stop and goes to following line of code without having server answer yet.
File: webServices.js (library)
exports.protocol = function(){
var url = 'www.cocoloco.com/whatever';
var webService = Ti.Network.createHTTPClient({
onload: function(e){
// on sucess exit with true
return(true);
},
onerror: function(e){
alert('onerror: ' + e.error);
// on sucess exit with false
return(false);
},
timeout: 5000
});
webService.open('POST', url);
webService.send();
}
File: Validacion.js (controller)
// Load next view
var MainView = Alloy.createController('index').getView('tabGroup');
function btnClick(){
var webServices = require('webServices');
var success = webServices.protocol();
if(success){
// open new view
MainView.open();
// close actual view
$.tabValidacion.close();
$.tabValidacion = null;
}else{
alert('error');
}
}
I have thought about two possible options:
Using promises.
Fire a new event on "success" and use that event run another callback function (in this function I open the new view and close the previous one).
I do not know how difficult is this as far as the event is one file (library) and the callback function in another one (controller)
I have never used any of these solutions, so I do not know how "good" they are.
Any suggestion?
The callback approach works fine in most cases. Just pass the function as a parameter, you can return an object containing anything from a success message to responseText and status.
webServices.js
exports.protocol = function(callback) {
var url = 'www.cocoloco.com/whatever';
var webService = Ti.Network.createHTTPClient({
onload: function(e){
// on success call callback
callback({ success: true });
},
onerror: function(e){
// on error call callback
callback({ success: false });
},
timeout: 5000
});
webService.open('POST', url);
webService.send();
}
Validacion.js
function btnClick(){
var webServices = require('webServices');
webServices.protocol(function(e) {
if(e.success){
// open new view
MainView.open();
// close actual view
$.tabValidacion.close();
$.tabValidacion = null;
} else {
alert('error');
}
});
}
I have the code below to find the next sequential page number and load it at the bottom of the page once the user hits the bottom of the screen.
the loading div slides down and as it is loading and up once it is done... it is set to "display:none" by default
What i need is a line of code in there which basically hides the #loading div if no more pages can be found to load... " var url = "page"+nextpage+".html";" finds the new page... titled 'page 2.html, page3.html' and so on.
Any help would be appreciated. I'm assuming it is easy but I can't find a solution anywhere...
alreadyloading = false;
nextpage = 2;
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() == $(document).height()) {
if (alreadyloading == false) {
$("#loading").slideDown();
var url = "page"+nextpage+".html";
alreadyloading = true;
$.post(url, function(data) {
$('#newcontent').children().last().after(data);
alreadyloading = false;
nextpage++;
$("#loading").slideUp();
});
}
}
});
If there is no such file then the AJAX request will fail, so you can do what you need from inside a "failure" handler. To be able to specify that, you one solution is to move from using $.post to using the more configurable $.ajax, which gives you all the necessary options:
$.ajax({
type: 'POST',
url: url,
success: function(data) {
$('#newcontent').children().last().after(data);
nextpage++;
},
complete: function() {
alreadyloading = false;
$("#loading").slideUp();
}
});
The complete callback contains code which will be executed no matter what happens with the request; the success callback will be executed before complete, but only if the request was successful.
I have the following code build using jquery. When a user copies and pastes a a youtube url, i am suppose to extract the video id is the getVideoId(str) method in jquery. Using the id, i get the video image picture title and contents.
When the textbox->("#url") has a length more than 10, i will make a ajax request. Thus the ajax request is working. But now i have another problem. The very first time when the textbox has more than 10 characters, there is two ajax request being fired (tested using firebug). Than when the user enters more characters, there are many ajax request fired.
Thus this will slow down the process of the last ajax request. I just want to get the data of the youtube link and show a suggest where the user can add the title and content. It is like how the old facebook video video link is. Anyone has a better suggest in improving the codes?
jQuery(document).ready(
function(){
$("#url").keyup(function() {
var $t1 = $("#url").val();
var $length = $t1.length;
var $data;
$("#title").val($length);
$("#content").val($t1);
if($length==0){
alert('zero value');
return;
}
if($length>10){
$data = $.ajax({
url: '<?php echo $this->Html->url(array("action" => "retrieveVideoFeed"));?>',
dataType: "json",
data: {
vid: getVideoId($t1)
},
success: function(data) {
alert('success in getting data');
}
});
return;
}
});
function getVideoId(str) {
var urlRegex = /(http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-\/]))?/;
if (urlRegex.test(str) && str.indexOf('v=') != -1)
{
return str.split('v=')[1].substr(0, 11); // get 11-char youtube video id
} else if (str.length == 11) {
return str;
}
return null;
}
}
);
You could cache the calls and use blur event and not keyup: you are firing a lot of AJAX call because keyup() fires an event each time a key is pressed, you should use blur that fires an event when an input loses focus.
If you cache the calls in an object you can avoid a lot of repeated calls
var cacheUrl = {};
$("#url").blur(function() {
var $t1 = $("#url").val();
var $length = $t1.length;
var $data;
$("#title").val($length);
$("#content").val($t1);
if($length==0){
alert('zero value');
return;
}
if(cacheUrls[$t1] !== undefined && $length>10){
$data = $.ajax({
url: '<?php echo $this->Html->url(array("action" => "retrieveVideoFeed"));?>',
dataType: "json",
data: {
vid: getVideoId($t1)
},
success: function(data) {
//save the data to avoid a future call
cacheUrls[$t1] = data;
alert('success in getting data');
}
});
return;
}elseif ($length>10){
//you already have the data in cacheUrls[$t1]
}
});
EDIT if you want to use the submit key to start the search you could trigger the blur event when you press enter like this:
$("#url").keypress(function(e){
if(e.which == 13){
$(this).blur();
return false;
}
});
I think many ajax requests are fired because you are using $("#url").keyup(function()
so that for every key event in url input the particular funciton will exectue.So, as per i know better to use focusout method instead of keyup.
If you stay with the keyup-Event you maybe want to use an ajaxmanager-plugin for jQuery which can manage queues or limits the number of simultaneous requests.
$.manageAjax.create('myAjaxManager', {
queue: true,
cacheResponse: false,
maxRequests: 1,
queue: 'clear'
});
....
if($length>10){
$data = $.manageAjax.add({ ...
This will prevent having alot of ajaxrequests active at the same time when the user is typing. As soon as he stops the request will not aborted and the results will show up.
I have form autocomplete code that executes when value changes in one textbox. It looks like this:
$('#myTextBoxId)').change(function () {
var caller = $(this);
var ajaxurl = '#Url.Action("Autocomplete", "Ajax")';
var postData = { myvalue: $(caller).val() }
executeAfterCurrentAjax(function () {
//alert("executing after ajax");
if ($(caller).valid()) {
//alert("field is valid");
$.ajax({ type: 'POST',
url: ajaxurl,
data: postData,
success: function (data) {
//some code that handles ajax call result to update form
}
});
}
});
});
As this form field (myTextBoxId) has remote validator, I have made this function:
function executeAfterCurrentAjax(callback) {
if (ajaxCounter > 0) {
setTimeout(function () { executeAfterCurrentAjax(callback); }, 100);
}
else {
callback();
}
}
This function enables me to execute this autocomplete call after remote validation has ended, resulting in autocomplete only when textbox has valid value. ajaxCounter variable is global, and its value is set in global ajax events:
$(document).ajaxStart(function () {
ajaxCounter++;
});
$(document).ajaxComplete(function () {
ajaxCounter--;
if (ajaxCounter <= 0) {
ajaxCounter = 0;
}
});
My problem is in IE (9), and it occurs only when I normally use my form. Problem is that function body inside executeAfterCurrentAjax(function () {...}); sometimes does not execute for some reason. If I uncomment any of two alerts, everything works every time, but if not, ajax call is most of the time not made (I checked this by debugging on server). If I open developer tools and try to capture network or debug javascript everything works as it should.
It seems that problem occurs when field loses focus in the same moment when remote validation request is complete. What I think it happens then is callback function in executeAfterCurrentAjaxCall is executed immediately, and in that moment jquery validation response is not finished yet, so $(caller).valid() returns false. I still do not know how alert("field is valid") helps in that scenario, and that could be sign that I'm wrong and something else is happening. However, changing executeAfterCurrentAjaxCall so it looks like this seems to solve my problem:
function executeAfterCurrentAjax(callback) {
if (ajaxCounter > 0) {
setTimeout(function () { executeAfterCurrentAjax(callback); }, 100);
}
else {
setTimeout(callback, 10);
}
}