Need help retrieving items in multiple playlists - javascript

Got some code that works just fine and will retrieve all the items in a specified playlist but need to amend it so it can loop through an array of playlists and retrieve all items in each list.
I've tried putting for-next loops in various places in the code but, as my javascript is poor, those efforts have failed and I don't know what to do next.
function onGoogleLoad() {
showhide('hidden');
getSearchParameters();
gapi.client.setApiKey(APIKEY);
gapi.client.load('youtube', 'v3', function () {
GatherVideos("", function () {
for (var i = 0; i < allVideos.length; i++) {
console.log(allVideos[i].snippet.title + " published at " + allVideos[i].snippet.publishedAt)
}
showhide('visible');
build_html(allVideos);
});
});
}
ORIGINAL CODE ...
function GatherVideos(pageToken, finished) {
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet, contentDetails',
playlistId: 'UU_FksrzP3q-IuoWTiG501LQ',
maxResults: 50,
pageToken: pageToken
});
request.execute(function(response) {
allVideos = allVideos.concat(response.items);
if (!response.nextPageToken)
finished();
else
GatherVideos(response.nextPageToken, finished);
});
}
END ORIGINAL CODE
NEW CODE WITH ATTEMPT AT LOOPING ...
function GatherVideos(pageToken, finished) {
for (var p=0;p<allPlaylists.length;p++)
{
console.log('Gathering: ' + allPlaylists[p]);
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet, contentDetails',
playlistId: allPlaylists[p],
maxResults: 50,
pageToken: pageToken
});
request.execute(function(response) {
console.log('Executing: ' + request);
allVideos = allVideos.concat(response.items);
if (!response.nextPageToken)
finished();
else
GatherVideos(response.nextPageToken, finished);
});
} //End for loop
}
END NEW CODE ...
function build_html(parArray) {
var n = 0;
var playlistHtml = '';
var rows = Math.floor(parArray.length / vinrow);
var rem = (allVideos.length % vinrow);
if (rem > 0) {
rows++;
}
for (var i = 0; i < rows; i++) {
playlistHtml += '<div class="row">';
for (var k = 0; k < vinrow; k++) {
if (n < parArray.length) {
playlistHtml += '<div id=' + n + ' class="col item"><img class="img-responsive fit-image" src="' +
parArray[n].snippet.thumbnails.default.url + '"><div class="vtitle">' +
parArray[n].snippet.title + '</div></div>';
n++;
} else {
playlistHtml += '<div class="col item"><div class="vtitle"> </div></div>';
}
}
playlistHtml += "</div>";
}
playlist_div.innerHTML = playlistHtml;
}
}
So, need some help about where to place the code which will loop through the array of playlists.

You loop over allPlaylists, building the request and saving it in the request variable. The issue is, that this loops overwrites the request variable each time it is executed. When you later call request.execute(...) you're only executing the last request build (last playlist in the array).
You should move the request execution inside the for-loop.
for (var p = 0; p < allPlaylists.length; p++)
{
console.log('Gathering: ' + allPlaylists[p]);
var request = gapi.client.youtube.playlistItems.list({ /* ... */ });
request.execute(/* ... */);
}
The above already fixes part of the issue. This however doesn't fix the problem in its entirety. You're recursively calling GatherVideos (if there is more than 1 page) which in turn walks through the whole allPlaylists array again. Setting out new requests for each playlist.
To resolve the issue above retrieving videos from a single playlist should be moved into its own method. Using with the current structure is a bit cumbersome for different reasons, so I've rebuild it from scratch using a somewhat different approach. This might not be the exact answer you're looking for, but I hope it will give you some inspiration:
async function listPlaylist(options = {}) {
var maxResults, pendingMaxResults, response;
options = Object.assign({}, options);
maxResults = options.maxResults;
if (Number.isInteger(maxResults) && maxResults > 0) {
// set options.maxResults to a value 1-50
options.maxResults = (maxResults - 1) % 50 + 1;
pendingMaxResults = maxResults - options.maxResults;
} else if (maxResults === "all") {
pendingMaxResults = "all";
}
response = await Promise.resolve(gapi.client.youtube.list(options));
if (response.nextPageToken && (pendingMaxResults === "all" || pendingMaxResults > 0)) {
options.maxResults = pendingMaxResults;
options.pageToken = response.nextPageToken;
return response.items.concat(await listPlaylist(options));
} else {
return response.items;
}
}
(async function () {
var playlistsVideos, videos;
// retrieve all videos of all playlists
playlistsVideos = await Promise.all(allPlaylists.map(function (playlistId) {
return listPlaylist({
id: playlistId,
part: "snippet, contentDetails",
maxResults: "all"
});
}));
// the above variable `playlistsVideos` is in the format:
// [[p1v1, p1v2, p1v3], [p2v1, p2v2, p2v3]] (p = playlist, v = video)
// this needs to be flattened to have the result you want
videos = playlistsVideos.reduce((videos, playlistVideos) => videos.concat(playlistVideos), []);
})();
I'd recommend checking out the guide Using Promises and checking out the documentation for async/await. The above is based upon YouTube API V3. I hope I wrote the code clear enough to let it speak for itself. If you have any question just ask away in the comments.

Related

class recovery problem generated by do while loop by event

I would like to retrieve the class when I click on the link that contains -> class="prez_col-'+i +'" in the viewPoster function. I don't know if it's because of the html() function or the event that prevents me from retrieving the name of the class from the DOM when I click on
template += '<p class="prez_title">' + data[i].title + '</p><img src="' + condJaq + '" class="prez_jaquette" />';
$("#prez_choiseJaq").html(template);
I tried to put onclick in the template variable:
template += '<p class="prez_title">' + data[i].title + '</p><img src="' + condJaq + '" class="prez_jaquette" />';
$("#prez_choiseJaq").html(template);
I have an error! when on one of the posters displays
File HTML
<div id="prez_rech" class="prez_rech">
<label for="fname">Recherche du film :</label>
<input type="text" placeholder="Entrez votre film ici" id="prez_input">
<xf:button type="button" id="prez_btn">Rechercher</xf:button>
</div>
<div id="prez_choiseJaq"></div>
<footer class="prez_footer">Created by Marilyn</footer>
<script type="module" src="js/vendor/prez/prez.js"></script>
File getValue .js
import { array } from './params.js';
const key = array['key'];
const urlMovie = array['urlMovie'];
const noCover = array['noCover'];
const urlImg = array['urlImg'];
const urlJaq = array['urlJaq'];
var jaq = document.getElementById("prez_choiseJaq");
var input = document.getElementById("prez_input");
var myBtn = document.getElementById("prez_btn");
var rech = document.getElementById("prez_rech");
var jaqSelected = $("a.prez_col-" + i);
var data = [];
var inputRep;
var urlNoCover = urlImg + noCover;
var url = urlMovie + key;
var i;
var test = false;
input.addEventListener("keypress", function (event) {
if (event.key === "Enter") {
event.preventDefault();
inputRep = input.value;
getValue();
}
});
myBtn.addEventListener("click", event => {
event.preventDefault();
inputRep = input.value;
getValue();
});
jaqSelected.click(function() {
alert(jaqSelected);
});
async function getValue() {
console.log(inputRep);
try {
const response = await fetch(url + "&language=fr-FR&query=" + inputRep + "&page=1&include_adult=false");
const responseData = await response.json();
data = responseData?.results;
console.log(data);
if (!data.length) {
alert("Le film que vous demandez n'est pas disponible !");
} else {
viewPoster();
}
} catch (error) {
console.log(error);
}
return data;
};
function viewPoster() {
test = false;
if (data) {
var template = "";
jaq.style.display = "inline-grid";
i = -1;
do {
i += 1;
console.log(i);
let condJaq;
if (data[i].poster_path == null) {
condJaq = urlNoCover;
} else {
condJaq = urlJaq + data[i].poster_path;
};
template += '<p class="prez_title">' + data[i].title + '</p><img src="' + condJaq + '" class="prez_jaquette" />';
$("#prez_choiseJaq").html(template);
} while (i < data.length);
};
};
function selected(arg) {
console.log(arg);
};
export { getValue };
File params.js
var array = {
key: "exemple",
urlMovie: 'https://api.themoviedb.org/3/search/movie?api_key=',
urlSerie: 'https://api.themoviedb.org/3/search/tv?api_key=',
urlImg: 'styles/prez/img/',
urlJaq: "https://image.tmdb.org/t/p/w154",
noCover: "no_cover.jpeg",
};
export { array };
File prez.js
import { array } from './params.js';
import { getValue } from './getValue.js';
do you have an idea ?
Thanks in advance.
There are so many issues here it's difficult to explain why your code isn't working. The issue with the for loop is a candidate for the error you didn't share, but there others.
The primary problem is that you were not adding a click handler for your links.
I've converted your code from module based JS (because I believe that's difficult to do in a snippet), mocked the Movie API call and cleaned up the code to remove most unnecessary globals, leverage jQuery more, and fix the for loop.
var array = {
key: "exemple",
urlMovie: 'https://api.themoviedb.org/3/search/movie?api_key=',
urlSerie: 'https://api.themoviedb.org/3/search/tv?api_key=',
urlImg: 'styles/prez/img/',
urlJaq: "https://image.tmdb.org/t/p/w154",
noCover: "no_cover.jpeg",
};
function mock_fetch(url, rep) {
const query = url + "&language=fr-FR&query=" + rep + "&page=1&include_adult=false"
// response = await fetch(query);
// return await reponse.json()
return { results: [{ poster_path: "This is the poster path"
, title: rep
}
,{ poster_path: "Some other path"
, title: "Some other movie"
}
]
}
}
var data; // this will hold whatever data retrieved by the last query to the movie API (may be null/undefined)
async function getValue(inputRep) {
try {
const responseData = mock_fetch(array.urlMovie + array.key, inputRep);
data = responseData?.results;
if (!data.length) {
alert("Le film que vous demandez n'est pas disponible !");
} else {
viewPoster(data);
}
} catch (error) {
console.error(error);
}
return data;
};
function viewPoster() {
$("#prez_choiseJaq").css("display", "inline-grid");
var template = "";
data.forEach( (film, index) => {
template += `${film.title}</p><img src="${film.poster_path?(array.urlJaq + film.poster_path):array.urlImg+array.noCover}" class="prez_jaquette" />`;
})
$("#prez_choiseJaq").html(template);
};
function selectMovie(event) {
event.preventDefault();
getValue($('#prez_input').val());
}
function doSomethingWithFilm(event) {
let index = $(this).data('index');
console.log(`The index you clicked was ${index}`)
if (data && data[index]) {
console.log(`The data for that index is ${JSON.stringify(data[index])}`)
} else {
console.log(`The data for that index is not available`)
}
}
function init() {
$('#prez_input').keypress(event => { event.key === "Enter" && selectMovie(event) });
$('#prez_btn').on("click", selectMovie);
// Add the click handler for the links as a delegate because the links do not exist at the time this code is executed
$(document).on("click", ".prez_col", doSomethingWithFilm);
}
$(init)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</script>
</head>
<body>
<div id="prez_rech" class="prez_rech">
<label for="fname">Recherche du film :</label>
<input type="text" placeholder="Entrez votre film ici" id="prez_input">
<xf:button type="button" id="prez_btn">Rechercher</xf:button>
</div>
<div id="prez_choiseJaq"></div>
</body>
</html>
Your do {} while () loop condition is trying to loop one beyond your data array. The problem is how you set up and increment your iterator variable: i.
You set your iterator to i = -1; before the loop, then, first thing in the loop you increment it: i += 1;, and the while condition is set to stop looping when i is equal to the array length: while ( i < data.length ). If an array has one element, i must be value 1 to discontinue the loop. At the end of the first pass i is equal to 0. Even in the case of a single array element it is still less than the length of the array so the loop will loop again. One element, two loops. Two elements, three loops. Three elements, four loops, etc.
The easy fix is change:
while (i < data.length);
...to:
while (i < data.length - 1);
let data = ['a','b','c','d','e'];
// ALWAYS ONE TO MANY LOOPS
let i = -1;
do {
i += 1;
console.log(i, data[i]);
} while (i < data.length);
// EASY FIX
i = -1;
do {
i += 1;
console.log(i, data[i]);
} while (i < data.length - 1); // <-- reduce length by one
// BETTER YET
i = 0;
do {
console.log(i, data[i]);
i += 1; // <-- move iterator increment to end of loop
} while (i < data.length);
How to use an iterator variable to control a loop:
Regardless of what type of loop you use: for, while, do while, it makes more sense to me to use your loop iterator, when you need one, as such:
let data = [1,2,3,4,5,6];
let ii = 0;
do {
console.log(ii, data[ii]);
ii++;
} while ( ii < data.length );
Before the loop, set ii to 0. The loop starts, use ii as 0, then at the very end of the loop increment ii. Every element is accessed, and only accessed once.
Here's the function (simple fixed) where you're do {} while () loop is:
function viewPoster() {
test = false;
if (data) {
var template = "";
jaq.style.display = "inline-grid";
i = -1;
do {
i += 1;
console.log(i);
let condJaq;
if (data[i].poster_path == null) {
condJaq = urlNoCover;
} else {
condJaq = urlJaq + data[i].poster_path;
};
template += '<p class="prez_title">' + data[i].title + '</p><img src="' + condJaq + '" class="prez_jaquette" />';
$("#prez_choiseJaq").html(template);
} while (i < data.length - 1);
};
};

Node.js Function Flow

When I get a request, I want it to generate a 4-character code, then check if it already exists in the database. If it does, then generate a new code. If not, add it and move on. This is what I have so far:
var code = "";
var codeFree = false;
while (! codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code=" + code, function(err, result) {
if (! err) {
if (result.rows.length > 0) {
codeFree = false;
} else {
codeFree = true;
}
} else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
This does not do nearly what I want it to do. How can I handle something like this?
You are doing an async task.
When you have an asyncronous task inside your procedure, you need to have a callback function which is going to be called with the desired value as its argument.
When you found the free code, you call the function and passing the code as its argument, otherwise, you call the getFreeCode function again and passing the same callback to it. Although you might consider cases when an error happens. If your the db call fails, your callback would never get called. It is better to use a throw/catch mechanism or passing another argument for error to your callback.
You can achieve what you need to do by doing it this way:
function getFreeCode(callback) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
if(!err) {
if(result.rows.length > 0) {
getFreeCode(callback);
} else {
callback(code);
}
}else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
// in your main:
getFreeCode(function (code) {
console.log(' this code was free: ' + code)
})
I recommend you look into two alternatives to help deal with asynchronous code.
node generator functions using the 'yield' keyword
promises
Using generators requires running a recent version of node with the --harmony flag. The reason I recommend generators is because you can write code that flows the way you expect.
var x = yield asyncFunction();
console.log('x = ' + x);
The previous code will get the value of x before logging x.
Without yielding the console.log would write out x before the async function was finished getting the value for x.
Your code could look like this with generators:
var client = {
execute: function (query) {
var timesRan = 0;
var result = [];
return function () {
return setTimeout(function () {
result = ++timesRan < 4 ? ['length_will_be_1'] : [];
return result;
},1);
};
}
};
function* checkCode () {
var code;
var codeFree = false;
while(!codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
try {
var result = yield client.execute("select * from codes where code="+code);
codeFree = result.rows.length > 0 ? false : true;
}catch(e) {
console.log('DB ERR: %s', err);
} finally {
console.log(codeFree);
}
console.log('here');
}
}
checkCode().next();
You would leave off the client object. I only added that to make a working example that fakes an async call.
If you have to use an older version of node or do not like the yield syntax then promises could be a worthy option.
There are many promise libraries. The reason I recommend promises is that you can write code that flows the way you expect:
asyncGetX()
.then(function (x) {
console.log('x: ' + x);
});
The previous code will get the value of x before logging x.
It also lets you chain async functions and runs them in order:
asyncFunction1()
.then(function (result) {
return asyncFunction2(result)
})
.then(function (x) { /* <-- x is the return value from asyncFunction2 which used the result value of asyncFunction1 */
console.log('x: ' + x);
});
Your code could look like this with the 'q' promise library:
var Q = require('q');
var client = {
timesRan: 0,
execute: function (query, callback) {
var self = this;
var result = {};
setTimeout(function () {
console.log('self.timesRan: ' + self.timesRan);
result.rows = ++self.timesRan < 4 ? ['length = 1'] : [];
callback(null, result);
},1);
}
};
function checkCode () {
var deferred = Q.defer();
var codeFree = false;
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log('rand: %s', rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
console.log('err: '+err+', result: ' + JSON.stringify(result));
console.log('result.rows.length: ' + result.rows.length);
if(!err) {
if(result.rows.length > 0) {
codeFree = false;
console.log('result.rows: %s, codeFree: %s', result.rows, codeFree);
checkCode();
} else {
codeFree = true;
console.log('line 36: codeFree: ' + codeFree);
deferred.resolve(code);
}
}else {
console.log('DB ERR: %s', err);
deferred.reject(err);
}
console.log(codeFree);
});
console.log('waiting for promise');
return deferred.promise;
}
checkCode()
.then(function (code) {
console.log('success with code: ' + code);
})
.fail(function(err) {
console.log('failure, err: ' + err);
});
Also omit the client object here. I only added that to make a working example that fakes an async call.
Promises and generators definitely take some time to get used to. It's worth it because they make the code a lot easier to follow in the end than code written with nested callbacks.

How can I iterate up and down a large javascript array in chunks?

I'm trying to write a function that takes a large array and iterates up and down it in a set number of chunks via a previous and next button. I have the next button working fine but cannot get it to reverse the array the same way I go forward. Here's what I have:
Javscript
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var next = true;
var increment = function(array,next) {
if (array.chunk < array.length) {
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
if (next) {
array.chunk += array.chunkSize;
$( '#test' ).html('<p>' + chunk + '</p>');
}
else {
var slice = array.slice(
array.chunk,
Math.min(array.chunk+array.chunkSize, array.length));
array.chunk -= array.chunkSize;
$( '#test' ).html(chunk);
}
}
}
$("#prev").click(function() {
increment(text);
});
$("#button").click(function() {
increment(text, next);
});
}
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var increment = function(array,next) {
if(next) {
array.chunk = Math.min(array.chunk + array.chunkSize, array.length);
} else {
array.chunk = Math.max(array.chunk - array.chunkSize, 0);
}
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
}
$("#prev").click(increment(text,false));
$("#button").click(increment(text, true));
}
Is this what you need? Fastly coded, and without testing so use with caution.
Okay, so first of all I really suggest breaking up your code. It looks like this is a response back from a server. I would in the response from the server, parse the data just like you are (side note, why don't you just return json from the server?) but use a call back to handle pagination.
var returnData = data.split(' ');
addPagination(returnData);
Under addPagination I would handle the DOM manipulation:
function addPagination(array) {
$('#container').show();
var incremental = incrementArray(array);
var responseSpan = $('#response');
$('#previous').click(function() {
incremental.previous();
showText();
});
$('#next').click(function() {
incremental.next();
showText();
});
showText();
function showText() {
responseSpan.text(incremental.array.join(', '));
}
}
But to handle the actual shifting of the array, I would use some function outside of your own. This uses Object Oriented JavaScript, so it is a bit more complex. It stores the original array in memory, and has 2 methods (next, previous), and has 1 attribute (array):
function incrementArray(array) {
_increment = 2;
var increment = _increment < array.length ? _increment : array.length;
var index = -2;
this.next = function () {
var isTopOfArray = index + increment > array.length - increment;
index = isTopOfArray ? array.length - increment : index + increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.previous = function () {
var isBottomOfArray = index - increment < 0;
index = isBottomOfArray ? 0 : index - increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.next();
return this;
}
To test it, use this jsFiddle.

Meteor JS: Use subset of same subscribed data

I built out a custom pagination script to display data for my app. It works wonderfully. However, I am having a slight problem when it comes to trying to figure out how to grab a subset of the same paginated subscription.
Meteor.startup(function(){
Session.setDefault('page', 1);
Session.setDefault('recordCount', 0);
Session.setDefault('recordsPerPage', 10);
Session.setDefault('currentIndustry', null);
Session.setDefault('currentMapArea', null);
Session.setDefault('gmapLoaded', false);
});
Deps.autorun(function () {
Meteor.call('getJobsCount', Session.get('currentIndustry'), Session.get('currentMapArea'), function (err, count) {
Session.set('recordCount', count);
});
Meteor.subscribe('pagedRecords', Session.get('page'), Session.get('recordsPerPage'), Session.get('currentIndustry'), Session.get('currentMapArea'));
});
Template.gmap.rendered = function() {
if(!Session.get('gmapLoaded'))
gmaps.initialize();
}
var templateName = "jobs";
function plotCities(jobs) {
var addresses = _.chain(jobs)
.countBy('address')
.pairs()
.sortBy(function(j) {return -j[1];})
.map(function(j) {return j[0];})
.slice(0, 99)
.value();
gmaps.clearMap();
$.each(_.uniq(addresses), function(k, v){
var addr = v.split(', ');
Meteor.call('getCity', addr[0].toUpperCase(), addr[1], function(error, city){
if(city) {
var opts = {};
opts.lng = city.loc[1];
opts.lat = city.loc[0];
opts.population = city.pop;
opts._id = city._id;
gmaps.addMarker(opts);
}
});
});
}
Template[templateName].helpers({
selected: function(){
return Session.get('recordsPerPage');
}
});
Template[templateName].pages = function() {
var numPages = Math.ceil(Session.get('recordCount') / Session.get('recordsPerPage'));
var currentPage = Session.get('page');
var totalPages = Session.get('recordCount');
var prevPage = Number(currentPage) - 1;
var nextPage = Number(currentPage) + 1;
var html = '<div class="pagination-cont"><ul class="pagination">';
if (numPages !== 1) {
if (currentPage > 1) {
html += '<li>«</li>';
}
for (var i = currentPage; (i <= numPages) && (i - currentPage < 4); i++) {
if (i < 1) continue;
if (i !== currentPage)
html += '<li>' + i + '</li>';
else
html += '<li class="active">' + i + '</li>';
}
if (currentPage < numPages) {
html += '<li>»</li>';
}
}
html += '</ul></div>';
return html;
}
Template[templateName].jobs = function() {
var options = {};
var cursor;
if(!Session.get('currentMapArea')) {
cursor = Jobs.find({}, {limit: 500});
plotCities(cursor.fetch());
}
return Jobs.find({}, { limit: Session.get('recordsPerPage') });
}
Template[templateName].rendered = function(){
var select = $('#perPage');
var option = select.attr('_val');
$('option[value="' + option + '"]').attr("selected", "selected");
select.selectpicker({
style: 'btn-info col-md-4',
menuStyle: 'dropdown-inverse'
});
}
Template[templateName].events({
'click div.select-block ul.dropdown-menu li': function(e){
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#perPage option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('recordsPerPage');
if(val != oldVal)
Session.set('recordsPerPage', Number(val));
},
'click .pageNum': function(e){
e.preventDefault();
var num = $(e.currentTarget).data('page');
Session.set('page', Number(num));
}
});
Currently, by default, only 10 records per page show up (unless the user selects from a drop-down a different amount). I have a plotCities function that I am using to try to plot the top 100 cities from the subset that is returned, however, I can't grab the top 100 because only 10 at a time show up.
Is there anyway to do what I am describing?
Ok, so the jobsPerCity and jobs are two totally different things, so I would use a separate on-fly-collection for the first one. Nothing will be stored in the database but the client will "think" that there is actually a jobsPerCity collection, which you can use to plot your map. The way you can achieve this is to define another named collection:
// only on the client !!!
var jobsPerCity = new Meteor.Collection("jobsPerCity");
On the server you will need to define a custom publish method:
Meteor.publish('jobsPerCity', function (options) {
var self = this;
var cities = new Meteor.Collection(null);
var jobToCity = {};
handle1 = Jobs.find({/* whatever condition you want */}).observeChanges({
added: function (id, fields) {
jobToCity[id] = fields.address.split(',')[0].toUpper();
cities.upsert({ _id: jobToCity[id] }, { $inc: { jobsCount: 1 } });
},
removed: function (id) {
cities.upsert({ _id: jobToCity[id] }, { $inc: { jobsCount: -1 } });
delete jobToCity[id];
},
changed: function (id, fields) {
// left as an exercise ;)
},
});
handle2 = cities.find({}, {sort: {jobsCount: -1}, limit: 100}).observeChanges({
added: function (id, fields) {
self.added('jobsPerCity', id, fields);
},
changed: function (id, fields) {
self.changed('jobsPerCity', id, fields);
},
removed: function (id) {
self.removed('jobsPerCity', id);
},
});
self.ready();
self.onStop(function () { handle1.stop(); handle2.stop(); });
});
and your good to go :)
EDIT (simple solution for more static data)
If the data is not going to be updated very often (as #dennismonsewicz suggested in one of his comments), the publish method can be implemented in a much simpler way:
Meteor.publish('jobsPerCity', function (options) {
var self = this, jobsPerCity = {};
Jobs.find({/* whatever condition you want */}).forEach(function (job) {
var city = job.address.split(',')[0].toUpper();
jobsPerCity[city] = jobsPerCity[city] !== undefined ? jobsPerCity[city] + 1 : 1;
});
_.each(jobsPerCity, function (jobsCount, city) {
self.added('jobsPerCity', city, { jobsCount: jobsCount });
});
self.ready();
});

How can I eliminate this ugly duplicate code during a retry-Ajax scenario?

Due to the callback-invoking nature of getGamesByPlayerId (which happens to be an Ajax call), I can't seem to figure out how to eliminate the duplicate code in the following:
// Load the player's games.
gc.api.getGamesByPlayerId(gc.game.player.id, gc.game.player.access_token, function(data) {
if(data.status_code === 401) {
// Call may have failed due to being called too fast. Retry...
gc.api.getGamesByPlayerId(gc.game.player.id, gc.game.player.access_token, function(data) {
if(data.status_code === 401) {
// Call may have failed due to being called too fast. Retry...
gc.api.getGamesByPlayerId(gc.game.player.id, gc.game.player.access_token, function(data) {
if(data.status_code === 401) {
// Call may have failed due to being called too fast. Retry...
gc.api.getGamesByPlayerId(gc.game.player.id, gc.game.player.access_token, function(data) {
if(data.status_code === 401) {
// OK. It's safe to assume the server is current, and that
// we truly are not authorized to do this.
alert("You are not authorized.");
} else {
// Add games to HTML.
for( var i = 0; i < data.length; i++ ) {
var html = '<li>' + data[i].id + '</li>';
$('#games').append(html);
}
}
});
} else {
// Add games to HTML.
for( var i = 0; i < data.length; i++ ) {
var html = '<li>' + data[i].id + '</li>';
$('#games').append(html);
}
}
});
} else {
// Add games to HTML.
for( var i = 0; i < data.length; i++ ) {
var html = '<li>' + data[i].id + '</li>';
$('#games').append(html);
}
}
});
} else {
// Add games to HTML.
for( var i = 0; i < data.length; i++ ) {
var html = '<li>' + data[i].id + '</li>';
$('#games').append(html);
}
}
});
Normally, I would think to use a for-loop, but that will not work because I don't want to fire off the Ajax calls in quick succession. I want the retry to fire only if the preceding call fails.
Ignoring the circumstances for which you would need to make the same request multiple times in a row, you could probably accomplish this with the use of a recursive function. For example, something like:
loadPlayerGames(4);
function loadPlayerGames(triesLeft) {
gc.api.getGamesByPlayerId(gc.game.player.id, gc.game.player.access_token, function(data) {
if(data.status_code !== 401) {
// Add games to HTML.
for( var i = 0; i < data.length; i++ ) {
var html = '<li>' + data[i].id + '</li>';
$('#games').append(html);
}
} else if(triesLeft <= 0) {
// OK. It's safe to assume the server is current, and that
// we truly are not authorized to do this.
alert("You are not authorized.");
} else {
// Call may have failed due to being called too fast. Retry...
loadPlayerGames(triesLeft - 1);
}
});
}

Categories