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.
Related
I have a for loop like so:
var currentLargest
for (var site in sites) {
// Each site is a website (www.mysite1.com, www.mysite2.com, etc.)
$.getJSON(site).then(function(data) {
if (data > currentLargest) {
currentLargest = data
}
});
}
// Here is where I want to use "currentLargest"
It checks multiple different websites to get the largest value. However, I can't extract the data I need after the for loop. I can only interact with currentLargest from within the promise, but that data is only relevant for ONE of the sites (it hasn't finished looping through all them until after the for loop).
How can I access the currentLargest value after the for loop containing the getJSON promises?
// Your code:
/*
var currentLargest
for (var site in sites) {
// Each site is a website (www.mysite1.com, www.mysite2.com, etc.)
$.getJSON(site).then(function(data) {
if (data > currentLargest) {
currentLargest = data
}
});
}
*/
// Updated:
const sitePromisesArr = sites.map((site) => $.getJSON(site));
$.when(sitePromisesArr)
.then((valArr, index) => {
// The "valArr" here will be an Array with the responses
const largest = valArr.reduce((current, next) => next > current ? next : current, 0);
// This will be the largest value.
console.log(largest);
// This will be the site that contains the largest value.
console.log(sitePromisesArr[index]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You need to use a async loop and then execute a callback once all the async getJSON requests are finished. You could use http://caolan.github.io/async/docs.html#each to help with this.
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));
}
});
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();
...
}
geturls(data,function(urls){
var data = {
"data": [
{ "userProfile": userP },
{ "urls": urls }
]
};
res.send(data);
});
function getUrls(data,done){
links = new Array();
for (var i=0; i<data.length; i++){
user = data[i]
Url.find({where:{data.id}}).success(function(url){
links.push({
"url": ur.text,
"date": data.syncedTime
});
if (urls.length == data.length){
done(links);
}
});
}
}
My problem with my code is this:
I'm returning the response through a callback once data collected in my array equals the length of the parent array. This is obviously a very dangerous and not so elegant solution. As, suppose I get a .failure from Url database, then my urls.length won't be equal with data.length. So, I'm a bit confused how to go about this.
Any help?
It will be easy for you, if you use async.js.
I used mapSeries here. It takes 3 parameters.
collection/array
iterator, which will be called for each item in the passed collection/array with 2 arguments. 1. item in collection, 2. callback. After completing the job in iterator, You should call the callback in node style(err first, results follows).
Final callback, which will be called after all the items in the collection mapped.
function getUrls(data,done){
var async = require('async');
async.mapSeries(data, function(user, cb) {//If you want it to be async `async.map`
Url.find({where:{user.id}}).success(function(url){
cb(null, {
"url": url.text,
"date": user.syncedTime
});
});
}, function(err, results) {
//results is an array. Its the same as `links` in your old code.
done(results);
});
}
geturls(data,function(urls){
var data = {
"data": [
{ "userProfile": userP },
{ "urls": urls }
]
};
res.send(data);
});
Use recursion:
function getUrls(data,done) {
var links = new Array();
function doGetUrl(i) {
var user = data[i];
Url.find({where:{data.id}}).
success(function(url){
links.push({
"url": ur.text,
"date": data.syncedTime
});
if (links.length == data.length){
done(links);
} else {
doGetUrl(i + 1); // get next url
}
}).
failure(function(err) {
doGetUrl(i); // on error, try to get current url again
// other error handling code
});
}
doGetUrl(0);
}
I would probably make use of the complete callback, in jQuery terms. Have a counter that records how many records have been processed and update this in complete, as this executes on success or failure. Then when that counter is >= the length of the data array you can exit.
As an aside, I would always do a >= rather than an == for the comparison you are doing there, that way, if for any crazy reason, the count is upped more than it should you still exit.
If alll you want to do is avoif the problem of checking links.length to determine when you are done then I think its just a matter of adding a separate counter that gets incremented even if the urk database fails. If you do that you can continue using your current stype where the async requests are run in parallel.
var nreq = 0;
for (var i=0; i<data.length; i++){
doTheAsyncOperation(function(){
//Run this part in both the success and error cases
nreq = nreq + 1;
if(nreq >= data.length){ done(links) }
})
}
On the other hand, if you want to run one query after the other you will need to rewrite the for to use recursion. This time, you don't need to worry about keeping a separate counter since you know when the final request runs:
function loop(i){
if(i >= data.length){
done(links);
}else{
doTheAsyncOperation(function(){
loop(i+1);
})
}
}
loop(0);
Finally, its good to know how to code this sort of patterns yourself but in the long run I highly recommend using a control flow library to keep things cleaner.
first I got JSON data via web server just like
$.getJSON(url,function(){
//my callback function;
});
And now I've got data as following:
{entries:[{title:'foo',id:'UUID',finished:null},{title:'bar',id:'UUID',finished:null},{title:'baz',id:'UUID',finished:null}]}
I have to find one specific JSON entry by it's UUID, and after that I need to modify one part for example, make a new json data:
{title:'foo',id:'UUID',finished:true}
And send back to server by using
$.post(url, data);
I'm totally lost myself with this situation... can anyone help?
Assuming that you've put the data in a variable called result, like this:
var result = {entries:[{title:'foo',id:'UUID',finished:null},{title:'bar',id:'UUID',finished:null},{title:'baz',id:'UUID',finished:null}]}
You could do a for-loop:
for ( var i=0; i<result.entries.length; i++ ) {
if (result.entries[i].id == 'the_UUID_you_are_looking_for') {
var entry = result.entries[i]; // "entry" is now the entry you were looking for
// ... do something useful with "entry" here...
}
}
Edit - I've written the full solution below, to further illustrate the idea and avoid misunderstandings:
// Get data from the server
$.getJSON("url", function(result) {
// Loop through the data returned by the server to find the UUId of interest
for ( var i=0; i<result.entries.length; i++ ) {
if (result.entries[i].id == 'the_UUID_you_are_looking_for') {
var entry = result.entries[i];
// Modifiy the entry as you wish here.
// The question only mentioned setting "finished" to true, so that's
// what I'm doing, but you can change it in any way you want to.
entry.finished = true;
// Post the modified data back to the server and break the loop
$.post("url", result);
break;
}
}
}
Try this:
var modified = false, myUuid = 'some uuid';
for (i = 0; i < data.entries.length; i++) {
if (data.entries[i].id === myUuid) {
data.entries[i].finished = true;
modified = true;
break;
}
}
if (modified) {
$.post(url, data);
}
You need to loop through your data. Alternatively you could restructure your JSON:
{"entries":{"UUID1":{"title":"foo", "finished": false }}, {"UUID2":{"title":"bar", "finished":false}}}