I am building a jQuery search suggestion script based upon two Google API's. Each API outputs a "relevance" integer (which I am returning next to each item to demonstrate) and I want to be able to order the results by that integer for each item.
How can I do this? I tried making the script output everything into one variable but I couldn't quite work it out.
A working demo can be seen here: http://jsfiddle.net/rEPf3/
My jQuery code is:
$(document).ready(function(){
$("#search").keyup(function(){
$.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data){
var suggestion="";
for(var key in data[1]){
if(data[4]["google:suggesttype"][key]=="NAVIGATION"){
suggestion+="<li><a href='"+data[1][key]+"'>"+data[2][key]+"</a> <i>("+data[4]["google:suggestrelevance"][key]+")</i></li>";
}else{
suggestion+="<li>"+data[1][key]+" <i>("+data[4]["google:suggestrelevance"][key]+")</i></li>";
}
}
$("#suggest").html(suggestion);
});
$.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data){
var suggestion2="";
for(var key in data.result){
suggestion2+="<li>"+data.result[key].name+" <i>("+data.result[key].score*4+")</i></li>";
}
$("#suggest2").html(suggestion2);
});
});
});
I think the cleanest way is to push the results from each dataset into an externally scoped variable, then sort and render from that. Example is below.
var combined = [],
completed = 0;
$(document).ready(function () {
$("#search").keyup(function () {
combined = [];
completed = 0;
$.getJSON("http://suggestqueries.google.com/complete/search?q=" + $("#search").val() + "&client=chrome&callback=?", function (data) {
for (var key in data[1]) {
if (data[4]["google:suggesttype"][key] == "NAVIGATION") {
combined.push({
href : data[1][key],
text : data[2][key],
score : parseInt(data[4]["google:suggestrelevance"][key],10)
});
} else {
combined.push({
text : data[1][key],
score : parseInt(data[4]["google:suggestrelevance"][key],10)
});
}
}
if ( ++completed == 2 ) complete();
});
$.getJSON("https://www.googleapis.com/freebase/v1/search?query=" + $("#search").val() + "&limit=3&encode=html&callback=?", function (data) {
for (var key in data.result) {
combined.push({
text : data.result[key].name,
score : parseInt(data.result[key].score,10) * 14
});
}
if ( ++completed == 2 ) complete();
});
});
});
function complete(){
console.log(combined);
combined.sort(function(a,b){ return b.score - a.score; });
var buffer = [];
combined.forEach(function(result){
buffer.push("<li>"+result.text+" <i>("+result.score+")</i></li>")
})
$("#suggest").html(buffer.join(""));
}
Edit
This solution doesn't take into account the fact that the user may be typing at a faster pace than the APIs, that API calls may not come back in order, and doesn't do anything to try to limit the number of calls made to each API. To make this behave more consistently (and more efficiently):
Change the keypress handler such that each key press cancels any previous timeouts then sets a new timeout at a reasonable delay (300ms seems a reasonable place to start) which then triggers the API calls
Wrap each API call in an immediately executed function so that you can reference the value of a global counter at the time each API call was made. Increment the counter with each keypress, and don't process the response from API calls where the counter didn't match
Here is the full code for you, you have to append all the results to one container and sort in .ajaxComplete event
$(document).ready(function () {
$("#search").keyup(function () {
$("#suggest").empty();
$.getJSON("http://suggestqueries.google.com/complete/search?q=" + $("#search").val() + "&client=chrome&callback=?", function (data) {
var suggestion = "";
for (var key in data[1]) {
if (data[4]["google:suggesttype"][key] == "NAVIGATION") {
suggestion += "<li><a href='" + data[1][key] + "'>" + data[2][key] + "</a> <i>(" + data[4]["google:suggestrelevance"][key] + ")</i></li>";
} else {
suggestion += "<li>" + data[1][key] + " <i>(" + data[4]["google:suggestrelevance"][key] + ")</i></li>";
}
}
$("#suggest").append(suggestion);
});
$.getJSON("https://www.googleapis.com/freebase/v1/search?query=" + $("#search").val() + "&limit=3&encode=html&callback=?", function (data) {
var suggestion2 = "";
for (var key in data.result) {
suggestion2 += "<li>" + data.result[key].name + " <i>(" + data.result[key].score * 4 + ")</i></li>";
}
$("#suggest").append(suggestion2);
});
$(document).ajaxComplete(function (event, xhr, settings) {
$("#suggest").html($("#suggest li").sort(function (a, b) {
return (parseInt($(a).find("i").html(), 10) > parseInt($(b).find("i").html(), 10));
}));
});
});
});
http://jsfiddle.net/rEPf3/8/
Try like this
Add this line before to the for loop
data[4]["google:suggestrelevance"].sort();
See Demo
Updated
Try combining the data sets by using a single variable
See Demo
Put them together and sort.
Following is the code.
Using promise to know both ajax requests are completed.
$(document).ready(function(){
$("#search").keyup(function(){
var mergedData = [];
var promise1 = $.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data){
var suggestion="";
console.log(data);
var arr = [];
for(var i in data[1]){
arr[i] = {value : data[1][i], rel : data[4]['google:suggestrelevance'][i]};
}
$.extend(mergedData,arr);
arr.sort(function(a, b){
return (b['rel']-a['rel']);
});
});
var promise2 = $.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data){
console.log('data of second request', data);
var suggestion2="";
var arr = [];
for(var key in data.result){
arr[key] = {value : data.result[key].name, rel : data.result[key].score};
}
$.extend(mergedData,arr);
$("#suggest2").html(suggestion2);
});
$.when(promise1, promise2).then(function(){
mergedData.sort(function(a, b){
return (b['rel']-a['rel']);
});
var suggestion = '';
for(var key in mergedData){
suggestion+='<li>' + mergedData[key].value + ' ' + mergedData[key].rel + '</li>';
}
$("#suggest").html(suggestion);
});
});
});
Updated working jsfiddle : http://jsfiddle.net/rEPf3/13/
Related
Simply i just loop an array, and submit data with get in the loops, but i runs so fast that the server stops running. I mini Ddos myself doing this. How i can i make the loop wait until the calls finish, perhaps adding a 1 sek break between loops
$( document ).on("submit", "#add_links", function() {
var error = 0;
var success = 0;
var total = 0;
//Gets data from input field
var new_urls = $("#new_urls").val();
var array_urls = new_urls.split("\n");
var promiss = [];
array_urls.forEach(function(entry) {
var request = $.get("action.php",
{
add_link: "1",
url: encodeURIComponent(entry.trim()),
},
function(data, status){
console.log("Data: " + data + "\nStatus: " + status);
if (data == 1)
{
success++;
total++;
//update fields removed in this post
$("#success_count").html((success));
$("#total_count").html((total));
}
if (data == 2) {
error++;
total++;
//update fields removed in this post
$("#error_count").html((error));
$("#total_count").html((total));
}
});
promiss.push(request);
});
$.when.apply(null, promiss).done(function(){
//do something when done;
});
return false;
});
You could use recursive function to achieve this.
Example
$(document).on("submit", "#add_links", function() {
var error = 0;
var success = 0;
var total = 0;
var new_urls = $("#new_urls").val();
var array_urls = new_urls.split("\n");
var promiss = [];
let index = 0;
function sendAjaxCall() {
if(count >= array_urls.length) return;
var request = $.get(
"action.php",
{
add_link: "1",
url: encodeURIComponent(array_urls[index].trim())
},
function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data == 1) {
success++;
total++;
$("#success_count").html(success);
$("#total_count").html(total);
}
if (data == 2) {
error++;
total++;
$("#error_count").html(error);
$("#total_count").html(total);
}
count++;
promiss.push(request);
sendAjaxCall();
}
);
}
$.when.apply(null, promiss).done(function() {
$("#close_bug_reportwindow").html(
"Import done, close tab by clicking here"
);
$("#close_icon").html('(<i class="fas fa-times"></i>)');
$("#progress").remove();
});
return false;
});
So I have a loop, which performs an ajax call on each iteration and I want to set the progress bar updated.. But it is not updating, it goes to 100% directly when ending...
I've tried to put the bar update call outside the success action (inside the loop directly) but it isn't working either..
$('button.page').on('click', function(e){
var $userList = textArray($('#page-userlist').val().replace('http://lop/', '').split(/\n/));
var $proxyList = textArray($('#page-proxylist').val().replace('http://', '').split(/\n/));
var $question = $('#page-question').val();
var data = {
question: $question,
users: $userList,
proxies: $proxyList
};
var i = 0, p = 0, max = data.proxies.length, totalusers = data.users.length, percent = 0;
$('#log').append("\n" + moment().calendar() + "\n");
var progressbar = $('#page-progress');
$.each(data.users, function(k, u){
if(typeof(p) !== 'undefined' && p !== null && p > 0)
{
if(i % 10 == 0 && i > 1) p++;
if(p == max) return false;
}
var proxy = data.proxies[p];
percent = Math.round((i / totalusers) * 100);
$.ajax({
type: "POST",
url: Routing.generate('viral_admin_bot_page'),
data: {question: $question, user: u, proxy: proxy},
success: function(result) {
$('#log').append("\nAtacado usuario " + u + " con proxy: " + proxy + "\n");
$(progressbar).width(percent + "%");
},
error: function(error) {
$('#log').append(error);
}
});
i++;
});
});
If i do console.log(percent); it is updating perfectly on each iteration, so I don't know where can be the problem.
Here is my code (without the ajax call because it isn't the problem) http://jsfiddle.net/dvo1dm03/20/
it will output to console the percentage, the objetive is to update the bar to the percentage completed in each loop, so it goes in "realtime" with loop.
Ok, here's how to do it asynchrounously.
var speed = 75;
var number_of_calls_returned = 0; // add number_of_calls_returned++ in your ajax success function
var number_of_total_calls;
var loaded = false;
function processUserData(){
if( number_of_calls_returned < number_of_total_calls){
setTimeout(function(){processUserData();}, 200);
}
else{
//received all data
// set progressbar to 100% width
loaded = true;
$("#page-progress").animate({width: "100%"},500);
$("#page-proxylist").val("Received data");
}
}
function updateProgress(percent, obj){
setTimeout(function(x){
if(!loaded)
$(obj).width(x + "%");
}, percent*speed, percent);
}
$('button.page').on('click', function (e) {
var $userList = textArray($('#page-userlist').val().replace('http://lop/', '').split(/\n/));
var $proxyList = textArray($('#page-proxylist').val().replace('http://', '').split(/\n/));
var $question = $('#page-question').val();
var data = {
question: $question,
users: $userList,
proxies: $proxyList
};
var i = 0,
p = 0,
max = data.proxies.length,
totalusers = data.users.length,
percent = 0;
//$('#log').append("\n" + moment().calendar() + "\n");
var progressbar = $('#page-progress');
number_of_total_calls = totalusers;
$.each(data.users, function (k, u) {
if (typeof (p) !== 'undefined' && p !== null && p > 0) {
if (i % 10 == 0 && i > 1) p++;
if (p == max) return false;
}
var proxy = data.proxies[p];
percent = (i / totalusers) * 100; //much smoother if not int
updateProgress(percent, progressbar);
i++;
// simulate ajax call
setTimeout(function(){number_of_calls_returned++;}, Math.random()*2000);
});
//callback function
setTimeout(function(){processUserData();}, 200);
});
var textArray = function (lines) {
var texts = []
for (var i = 0; i < lines.length; i++) {
// only push this line if it contains a non whitespace character.
if (/\S/.test(lines[i])) {
texts.push($.trim(lines[i]));
}
}
return texts;
}
Check it out here! jsFiddle (really cool!)
Your problem is cause by the fact that you have a closure for your success function and every success function shares the same percent variable. You can fix it like this:
success: function(percent, result) {
$('#log').append("\nAtacado usuario " + u + " con proxy: " + proxy + "\n");
$(progressbar).width(percent + "%");
}.bind(percent),
Where you'll need to shim bind in older browsers, or like this, which is a little uglier, but should work everywhere without a shim:
success: (function(percent) { return function(result) {
$('#log').append("\nAtacado usuario " + u + " con proxy: " + proxy + "\n");
$(progressbar).width(percent + "%");
}; }( percent ),
if what you want is to increase the update bar with each success of AJAX calls I'd suggest an easier solution (I've simplified the js code for clarity's sake):
$('button').click(function (e) {
var i = 0,
cont = 0,
totalusers = 100,
percent = 0;
var progressbar = $('#page-progress');
for (; i < totalusers; i++) {
$.ajax({
type: "POST",
url: '/echo/json/',
data: {
question: 'something',
user: 1,
proxy: 2
},
success: function (result) {
cont += 1;
percent = Math.round((cont / totalusers) * 100);
progressbar.width(percent + "%");
},
error: function (error) {
$('#log').append(error);
}
});
};
});
You can see it in action in this fiddle.
Hope this helps or at least give you some ideas.
Update the progress bar using setTimeout method.
it will wait for some time and then update the width of progressbar.
myVar = setTimeout("javascript function",milliseconds);
Thanks,
Ganesh Shirsat
I would like to make a recommendation of trying to make a self contained example that doesn't rely on the post so that it is easier for you or us to solve the problem
As well, you can console log elements so you could try logging the progressbar element, percent and the response of the ajax request
(This code is to replace the javascript sections of the fiddler)
var i = 0;
moveProgress();
function moveProgress(){
if(i < 10000)
{
setTimeout(function(){
$('#page-progress').width((i / 1000) * 100);
moveProgress();
},2);
i++;
}
}
The reason that it wasn't working was because the loop ran so fast that it was loaded by the time the script loaded it, the timeout allows you to delay the execution a bit(Though not necessarily recommended to use because of potential threading issues.
I have function making a $http call and displaying some results. After this particular function, I added another function that will make further calls upon the scroll event (using the ng-Infinite-Scroll module).
My problem is that I can't seem to be able to append the results from the second and following calls to the results displayed by the default call.
Here's my code.
$scope.getDetails = function (id) {
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=12').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.$watch(function() {
return $scope.artist;
}, function() {
var pos = $scope.artist.name.toLowerCase().indexOf(', the');
if (pos != -1) {
$scope.artist.name = 'The ' + $scope.artist.name.slice(0, pos);
}
});
var _page = 1;
$scope.releases = [];
$scope.loadDetails = function() {
_page++;
console.log(_page);
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=' + _page + '&per_page=12').then(function(data2) {
$scope.releases = data2.releases;
});
};
$scope.clicked = true;
$scope.sliding = true;
}
Where the following function will call the first page and 12 items:
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=12').
success(function(data2) {
$scope.releases = data2.releases;
});
Later on, I trigger the loadDetails function and it'll make the calls correctly (page 2, 3, and so on), and update the $scope.releases, but so far when this happens, instead of displaying the new results, it disappears all completely once the loadDetails function is called.
var _page = 1;
$scope.releases = [];
$scope.loadDetails = function() {
_page++;
console.log(_page);
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=' + _page + '&per_page=12').then(function(data2) {
$scope.releases = data2.releases;
});
};
I assume that instead of redefining $scope.releases, I have to append the results from the second and following calls to it, but I can't see how i'd do this.
Any hints?
Here's a working Plunker.
I think primarily the problem is that id was missing from loadDetails signature, and .then wasn't waiting for $http.get to return the releases. I substituted .success, and it works.
Here is an update of your plunker: http://plnkr.co/edit/gSwRNUn9mBvhWDtIaEzD
$scope.getDetails = function(id) {
$scope._page = 1;
$scope.releases = [];
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=12').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.clicked = true;
$scope.sliding = true;
};
$scope.loadDetails = function(id) {
console.log(id);
if (!angular.isUndefined(id)) {
$scope._page++;
console.log($scope._page);
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=' + $scope._page + '&per_page=12').
success(function(data2) {
for (var i = 0; i < data2.releases.length; i++) {
$scope.releases.push(data2.releases[i]);
}
});
}
};
I'm just using a loop and .push to append to the array.
I hope I'm not missing something obvious here.
function renderViews(containerId) {
var root = '../Views/';
var viewsDomStr = '';
for (var i = 0; i < bundles.views.length; i++) {
$.get(root + bundles.views[i], function (data) {
viewsDomStr = viewsDomStr.concat(data);
});
}
console.log(viewsDomStr);
$('#' + containerId).append(viewsDomStr);
}
The problem is that the viewsDomStr is updated according to data from server only inside the for loop. For console.log(viewsDomStr); all I get is a reset to ''.
The function you are calling is asynchron.
Try with
function renderViews(containerId) {
var root = '../Views/';
var viewsDomStr = '';
function cb(){
console.log(viewsDomStr);
$('#' + containerId).append(viewsDomStr);
}
for (var i = 0; i < bundles.views.length; i++) {
$.get(root + bundles.views[i], function (data) {
viewsDomStr = viewsDomStr.concat(data);
cb();
});
}
}
The problem is the $.get request is asynchronous so the program continues on and doesn't wait for it. You want to use viewsDomStr inside the $.get function.
function renderViews(containerId) {
var root = '../Views/';
for (var i = 0; i < bundles.views.length; i++) {
$.get(root + bundles.views[i], function (data) {
console.log(data);
$('#' + containerId).append(data);
});
}
// This section runs before $.get is finished
}
EDIT: I've found that viewsDomStr is actually redundant. You are just adding text to the element so you can just add it to the $.get.
Since get method sends asynchronous request, you can check response every 1 sec using setInterval:
function renderViews(containerId) {
var root = '../Views/';
var viewsDomStr = '';
var success = false;
for (var i = 0; i < bundles.views.length; i++) {
$.get(root + bundles.views[i], function (data) {
viewsDomStr = viewsDomStr.concat(data);
success = true;
});
}
var t = setInterval(function(){
if(success) {
console.log(viewsDomStr);
$('#' + containerId).append(viewsDomStr);
clearInterval(t);
}
},1000);
}
The anonymous function of the get method will be asynchronous (as per the execution of get itself).
In short, it all happens too fast.
I'm just trying to learn some ajax so I wrote some code for basically an address book to pull some data. My javascript is rubbish but I cannot seem to understand what I am doing wrong, the error points to function ajaxCall but I see no issue with that function either:
(function () {
var searchForm = document.getElementById("search-form"),
searchField = document.getElementById("q"),
getAllButton = document.getElementById("get-all"),
target = document.getElementById("output");
var addr = {
search: function (event) {
var output = document.getElementById("output");
//start ajax call
ajaxCall("data/contacts.json", output, function (data) {
var searchValue = searchField.value,
addrBook = data.addressBook,
count = addrBook.length,
i;
//stop default behavior
event.preventDefault();
//clear target
target.innerHTML = "";
if (count > 0 && searchValue !== "") {
for (i = 0; i < count; i++) {
var obj = addrBook[i],
isItFound = obj.name.indexOf(searchValue);
if (isItFound !== -1) {
target.innerHTML += '<p>' + obj.name + ', ' + obj.email + '<p>';
} //end if isItFound
} //end for loop
} //end if count check
}); //end ajax call
}, //end method search
getAllContacts: function () {
var output = document.getElementById("output");
ajaxCall("data/contacts.json", output, function (data) {
var addrBook = data.addressBook,
count = addrBook.length,
i;
target.innerHTML = "";
if (count > 0) {
for (i = 0; i < count; i++) {
var obj = addrBook[i];
target.innerHTML += '<p>' + obj.name + ', ' + obj.email + '<p>';
} //end for loop
} //end if
}); //end ajax call
}, //end method getAllContacts
setActiveSection: function () {
this.parentNode.setAttribute("class", "active");
}, //end method setActiveSection
removeActiveSection: function () {
this.parentNode.removeAttribute("class");
}, //end method removeActiveSection
addHoverClass: function () {
searchForm.setAttribute("class", "hovering");
}, //end method addHoverClass
removeHoverClass: function () {
searchForm.removeAttribute("class");
} //end method removeHoverClass
} //end addr object
searchField.addEventListener("keyup", addr.search, false);
searchField.addEventListener("focus", addr.addActiveSection, false);
searchField.addEventListener("blur", addr.removeActiveSection, false);
getAllButton.addEventListener("click", addr.getAllContacts, false);
searchForm.addEventListener("submit", addr.search, false);
searchForm.addEventListener("mouseover", addr.addHoverClass, false);
searchForm.addEventListener("mouseout", addr.removeHoverClass, false);
})(); //end anon function
function getHTTPObject() {
var xhr;
//in most cases this first if is executed
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
//otherwise support crappy IE6 and below
else if (window.ActiveXObject) {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
}
return xhr;
}
function ajaxCall(dataUrl, outputElement, callback) {
//get ajax object
var request = getHTTPObject();
outputElement.innerHTML = "Loading...";
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
//good ajax response..now save it
var contacts = JSON.parse(request.responseText);
if (typeof callback === "function")
callback(contacts);
} //end upper if
} //end onreadystatechange
request.open("GET", dataUrl, true);
request.send(null);
}
The javascript development tools keeps giving me an unexpected token } on line 97 but that changes all so often. Am I missing a curly brace somewhere?
I did put your code to this fiddle and fixed the errors as far as i can.
You missed some curly braces and semicolons. Also, you used ajaxCall() and getHTTPObject() before they were declared. Check it out. Unfortunately, i dont know if the problem is already fixed, but now the code is valid at least :)
Btw: (in my opinion) such long Code-Samples are always better pasted into a fiddle. Not only because you can focus on the probably messy code here while referring to the complete code sample somewhere else, also because you can make sure that there are no syntax-errors as you can quickly validate you code using jsLint before asking the question here.
You must re-check what your JSON response is, in console, and see if it is invalid.
Because at that very 97 line you say that you are parsing a response.