Execute a Callback function on multiple elements - javascript

I had a question about the way I could execute a callback on multiple elements in my div.
So here it is. I have a div that contains two columns (col1 and col2, both taking 50% of the parent div and on float:left) many projects displayed as images. A big project is twice bigger (height) than a small one.
The configuration created makes it possible to display properly the projects to fill the div and not leave blank parts. So, basically, if we have 3 projects, we get a big project in col1 and 2 small in col2.
With 4 projects, we get a small + big in col1 and a big + small in col2.
And so on.
Problem is, when i'm filtering the different projects (in this example, by, for example, displaying the projects made in JS only, I use show() and hide() which works properly.
If animated, the projects just disappear because the hide() seems to be called while the show() isn't completed yet.
I was then told about the call back function but it seems to work only on one element, not multiples. Which is not optimal since my filtering always displays multiple projects.
I would like to know how to execute the function properly, so it works on all the projects because it seems to apply to only one.
Thanks in advance.
<script>
function update_projects(projects_ids){
console.log("Update projects : " + projects_ids);
var projects_top = $('.projects').offset().top;
if ($(window).scrollTop() > projects_top) {
$(window).scrollTop(projects_top);
}
var projects_config = generate_configuration(projects_ids.length);
var project_index = 0;
//$('.project').show();
$('.project').slideUp(2000, function(){
var odd = false;
for (var i = 0; i < projects_config.col1.length; ++i) {
var block_type = projects_config.col1[i];
var project_id = projects_ids[project_index++];
var odd_selector = '';
if (block_type == 'small') {
if (odd == false) {
odd_selector = '.left';
} else {
odd_selector = '.right';
}
odd = !odd;
}
$('.col1 .project_' + project_id + '.' + block_type + odd_selector).show();
}
odd = false;
for (var i = 0; i < projects_config.col2.length; ++i) {
var block_type = projects_config.col2[i];
var project_id = projects_ids[project_index++];
var odd_selector = '';
if (block_type == 'small') {
if (odd == false) {
odd_selector = '.left';
} else {
odd_selector = '.right';
}
odd = !odd;
}
$('.col2 .project_' + project_id + '.' + block_type + odd_selector).show();
}
});
resize();
}

As you've discovered, the completion callback on an animation is called once per element, not once per set.
To have a callback that is invoked when all of the animations have finished, use the .promise() method on the collection:
$('.myClass').slideUp(2000).promise().then(function() {
// not called until all animations have finished
...
});

Related

FCC Twitch API Project: if-else inside for loop doesn't iterate through all possibilities

So I'm doing a freeCodeCamp project that shows information regarding specific streamers. I have bumped into multiple problems, regarding if statements inside a for loop (I think).
First, the Status is always "Offline" for every streamer, second, the borders around the logos don't change except for the first one. If it worked, I'm still not sure whether the border colors would be correct though (red for offline streams, green for online).
If you watch closely, what I did was I provided a specific id for each iteration of streamer info, 1 for the corresponding image, and 1 for status, so I can target them with the i variable inside an if statement.
Here is the JS code, it is better visible via Codepen using the link I provided, mainly because you see the whole work and I don't know how to format code properly so that you don't have to use horizontal scrollbar to see long lines. :/
Thanks in advance!
$(document).ready(function() {
var channels = ["freecodecamp", "wudijo", "ThijsHS", "HSdogdog", "Sjow"];
for (var i = 0; i < channels.length; i++) {
var channelURL = "https://wind-bow.gomix.me/twitch-api/channels/"+ channels[i] +"?callback=?";
var streamURL = "https://wind-bow.gomix.me/twitch-api/streams/"+ channels[i] +"?callback=?";
$.getJSON(channelURL, function(data1) {
var logo = data1.logo;
var name = data1.display_name;
var twitchLink = data1.url;
var status;
$.getJSON(streamURL, function(data2) {
if (data2.stream === null) {
status = "Offline";
}
else {
status = data2.stream.channel.status;
}
$("#followerInfo").append("<div class = 'row'><div class = 'col-md-4'><a href = '"+ twitchLink +"' target = 'blank'><img id = 'img"+ i +"' src = '"+ logo +"'></a></div><div class = 'col-md-4'><p>" + name + "</p></div><div class = 'col-md-4'><p id = 'status"+ i +"'>" + status + "</p></div></div>");
if (data2.stream === null) {
$("#img"+ i +"").addClass('img-offline');
}
else {
$("#img"+ i +"").addClass('img-online');
}
});
});
}
});
If you console.log(channelURL) you can see that only "Sjow" his data is being called. This happens because your loop finishes before any requests are made, therefore only the last value in the array (Sjow) is used.
You can fix this by following this answer.

Multiple JSON parses + parsing top 10 games from Twitch API

Alright. So the following code fetches only the first channel from the list, I want it to be fetching all the streams from the JSON. ( Bonus question: How can I format all the fetched streams into a nice row-per-row grid which features three streams in each row )
<script>
var twitchApi = "https://api.twitch.tv/kraken/streams";
$.getJSON(twitchApi, function (json) {
var streamGame = json.streams[0].game;
var streamThumb = json.streams[0].preview;
var streamVideo = json.streams[0].name;
$('#twitch').append('<li><iframe src="http://player.twitch.tv/?channel=' + streamVideo + '"></iframe></li>');
}
);
</script>
And the second thing I need help with is how to create a script which fetches the top 10 games from this JSON : https://api.twitch.tv/kraken/games/top .. Should be quite similar to the one above but my brain is frozen and I need to finish this.
You can get what you need to get done with a combination of loops and inline-block elements. Here, I used jQuery to create rows of three cells each.
var twitchApi = "https://api.twitch.tv/kraken/streams";
$.getJSON(twitchApi, function (json) {
var streamRow = $("<div class='stream-row'></div>").appendTo("#twitch"); // Create first row.
var streamCell;
var streamVideo;
for (var i = 0; i < json.streams.length; i++)
{
if (i % 3 == 0 && i > 0)
{
// Create a new row every multiple of 3.
streamRow = $("<div class='stream-row'></div>");
$("#twitch").append(streamRow);
}
// Create a cell with a video and add it to the row.
streamVideo = json.streams[i].channel.name;
streamCell = $("<div>", {
css: {
"class": "stream-cell",
"display": "inline-block"
}
});
streamCell.append('<iframe src="http://player.twitch.tv/?channel=' + streamVideo + '"></iframe>');
streamRow.append(streamCell);
}
});
You use another loop for the top ten thing. The API already returns 10 games, so I used the length instead of hard-coding 10.
var twitchApi = "https://api.twitch.tv/kraken/games/top";
$.getJSON(twitchApi, function (json) {
for (var i = 0; i < json.top.length; i++)
{
// Do whatever you need here with the info from the JSON.
console.log(json.top[i].game.name)
}
});
For fetching more than one stream at same time with the same data you can use a for loop.
Fixed your streamVideo variable, ".channel" was missing before ".name"(I recommed using a JSON viewer to get a clear vision of the structure, like Online JSON Viewer
And made the script so 10 iframes are displayed(also grabbed the embed code from twitch, your embed was so small).
The styling I let it to your own, I know nothing about styling iframes, you can try setting 30% width for each, so there are 3 per row, and the others go in the bottom row.
var twitchApi = "https://api.twitch.tv/kraken/streams";
$.getJSON(twitchApi, function(json) {
for (i = 0; i < 10; i++) {
var streamGame = json.streams[i].game;
var streamThumb = json.streams[i].preview;
var streamVideo = json.streams[i].channel.name;
$('#twitch').append('<li><iframe src="https://player.twitch.tv/?channel=' + streamVideo + '" frameborder="0" scrolling="no" height="378" width="620"></iframe><li>');
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

JS function to hide or show elements

I have to JSON files, I load them both into two tables like this:
$(window).load(function(){
$.getJSON('http://1xxxxxx/xxxxx_1_18.json', function(data) {
var output="<div class='outer'>";
for (var i in data.lbclassic118) {
output+="<div style='visibility:hidden;' class='lbclassic118'id="+"age" + data.lbclassic118[i].ageinweeks+">"+ '<table class="table table-responsive"><tr class="cabecera"><th colspan="3"><center><strong>Age (weeks)'+ data.lbclassic118[i].ageinweeks+'</strong></center></th></tr><tr><td rowspan="3">Body Weight (g)</td><td>average</td><td><strong>'+ data.lbclassic118[i].average+'</strong></td></tr><tr><td>range min</td><td><strong>'+ data.lbclassic118[i].rangemin+'</strong></td></tr><tr><td>range mmax</td><td><strong>'+ data.lbclassic118[i].rangemmax+'</strong></td></tr><tr><td rowspan="3">feed sonsumption</td><td>kj bird day</td><td><strong>'+ data.lbclassic118[i].kjbirdday+'</strong></td></tr><tr><td>g bird day</td><td><strong>'+ data.lbclassic118[i].gbirdday+'</strong></td></tr><tr><td>cumulative</td><td><strong>'+ data.lbclassic118[i].cumulative+'</strong></td></tr></table>' +"</div>";
}
output+="</div>";
document.getElementById("placeholder1").innerHTML=output;
});
});
$(window).load(function(){
$.getJSON('http://xxxxxx/xxxxxx.json', function(data) {
var output="<div class='outer'>";
for (var i in data.lbclassic1990) {
output+="<div style='visibility:hidden;' class='lbclassic1990'id="+"age" + data.lbclassic1990[i].ageinweeks+">"+ '<table class="table table-responsive"><tr class="cabecera"><th colspan="3"><center><strong>Age (weeks) '+ data.lbclassic1990[i].ageinweeks+'</strong></center></th></tr><tr><td>Egg No. per H.H.</td><td>cumul.</td><td><strong>'+data.lbclassic1990[i].cumul+'</strong></td></tr><tr><td rowspan="2">Rate of Lay %</td><td>per H.H.</td><td><strong>'+data.lbclassic1990[i].perhh+'</strong></td></tr><tr><td>per H.D.</td><td><strong>'+data.lbclassic1990[i].perhd+'</strong></td></tr><tr><td rowspan="2"> Egg Weight (g)</td><td>egg weight in week</td><td><strong>'+data.lbclassic1990[i].eggweightinweek+'</strong></td></tr><tr><td>egg mass cumul.</td><td><strong>'+data.lbclassic1990[i].eggmasscumul+'</strong></td></tr><tr><td rowspan="2">Egg Mass -- g/H.D. -- kg/H.H.</td><td>egg mass in week</td><td><strong>'+data.lbclassic1990[i].eggmassinweek+'</strong></td></tr><tr><td>egg mass cumul.</td><td><strong>'+data.lbclassic1990[i].eggmasscumul2+'</strong></td></tr></table>' +"</div>";
}
output+="</div>";
document.getElementById("placeholder2").innerHTML=output;
});
});
The information comes up as it should, I have no problems with that.
However, what I'm trying to do is just show ONE table at a time, not all the tables at the same time (don't want a table for each element in the JSONs) to be seen, but only one a time.
For that I'm implemeting a function that with a slider control will show or hide the tables.
Here's an images of the HTML output data structure:
Now, what I want to to is hide or show different DIVs (tables) with this script:
<script>
function leslider(valor) {
var elementos_lbclassic118 = document.getElementsByClassName("lbclassic118");
var elementos_lbclassic1990 = document.getElementsByClassName("lbclassic1990");
var total_elementos = elementos_lbclassic118.length + elementos_lbclassic1990.length;
var i;
for (i = 1; i < total_elementos.length+1; i++) {
document.getElementById("age"+i).style.visibility = "hidden";
}
document.getElementById("age"+valor).style.visibility = "visible";
}
However it won't work, the 1st JSON will show all the elements but never hide it, and the second one will place them all on top of each other, not sure where I'm failing.
I guess you are wrong "leslider" function.
How to use this "leslider" function, let's me see this code.
I found the culprit, I was getting length from a numeric, value. Here's the updated function.
<script>
function leslider(valor) {
var elementos_lbclassic118 = document.getElementsByClassName("lbclassic118");
var elementos_lbclassic1990 = document.getElementsByClassName("lbclassic1990");
var total_elementos = elementos_lbclassic118.length + elementos_lbclassic1990.length;
var i;
for (i = 1; i < total_elementos+1; i++) {
document.getElementById("age"+i).style.display = "none";
}
document.getElementById("age"+valor).style.display = "block";
}

Event listener fails to attach or remove in some contexts

I've created a script that attaches an event listener to a collection of pictures by default. When the elements are clicked, the listener swaps out for another event that changes the image source and pushes the id of the element to an array, and that reverses if you click on the swapped image (the source changes back and the last element in the array is removed). There is a button to "clear" all of the images by setting the default source and resetting the event listener, but it doesn't fire reliably and sometimes fires with a delay, causing only the last element in a series to be collected.
TL;DR: An event fires very unreliably for no discernible reason, and I'd love to know why this is happening and how I should fix it. The JSFiddle and published version are available below.
I've uploaded the current version here, and you can trip the error by selecting multiple tables, pressing "Cancel", and selecting those buttons again. Normally the error starts on the second or third pass.
I've also got a fiddle.
The layout will be a bit wacky on desktops and laptops since it was designed for phone screens, but you'll be able to see the issue and inspect the code so that shouldn't be a problem.
Code blocks:
Unset all the selected tables:
function tableClear() {
//alert(document.getElementsByClassName('eatPlace')[tableResEnum].src);
//numResTables = document.getElementsByClassName('eatPlace').src.length;
tableArrayLength = tableArray.length - 1;
for (tableResEnum = 0; tableResEnum <= tableArrayLength; tableResEnum += 1) {
tableSrces = tableArray[tableResEnum].src;
//alert(tableSrcTapped);
if (tableSrces === tableSrcTapped) {
tableArray[tableResEnum].removeEventListener('click', tableUntap);
tableArray[tableResEnum].addEventListener('click', tableTap);
tableArray[tableResEnum].src = window.location + 'resources/tableBase.svg';
} /*else if () {
}*/
}
resTableArray.splice(0, resTableArray.length);
}
Set/Unset a particular table:
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'resources/tableBase.svg');
resTableArray.shift(this);
};
tableTap = function () {
$(this).unbind('click', tableTap);
$(this).bind('click', tableUntap);
this.setAttribute('src', 'resources/tableTapped.svg');
resTableArray.push($(this).attr('id'));
};
Convert the elements within the 'eatPlace' class to an array:
$('.eatPlace').bind('click', tableTap);
tableList = document.getElementsByClassName('eatPlace');
tableArray = Array.prototype.slice.call(tableList);
Table instantiation:
for (tableEnum = 1; tableEnum <= tableNum; tableEnum += 1) {
tableImg = document.createElement('IMG');
tableImg.setAttribute('src', 'resources/tableBase.svg');
tableImg.setAttribute('id', 'table' + tableEnum);
tableImg.setAttribute('class', 'eatPlace');
tableImg.setAttribute('width', '15%');
tableImg.setAttribute('height', '15%');
$('#tableBox').append(tableImg, tableEnum);
if (tableEnum % 4 === 0) {
$('#tableBox').append("\n");
}
if (tableEnum === tableNum) {
$('#tableBox').append("<div id='subbles' class='ajaxButton'>Next</div>");
$('#tableBox').append("<div id='cazzles' class='ajaxButton'>Cancel</div>");
}
}
First mistake is in tapping and untapping tables.
When you push a Table to your array, your pushing its ID.
resTableArray.push($(this).attr('id'));
It will add id's of elements, depending on the order of user clicking the tables.
While untapping its always removing the first table.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
resTableArray.shift(this);
So, when user clicks tables 1, 2, 3. And unclicks 3, the shift will remove table 1.
Lets fix this by removing untapped table
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'http://imgur.com/a7J8OJ5.png');
var elementID = $(this).attr('id');
var elementIndex = resTableArray.indexOf(elementID);
resTableArray.splice(elementIndex, 1);
};
So you were missing some tables after untapping.
Well lets fix tableClear,
You have a array with tapped tables, but you are searching in main array.
function tableClear() {
tableLen = resTableArray.length;
for (var i = 0; i < tableLen; i++) {
var idString = "#" + resTableArray[i];
var $element = $(idString);
$element.unbind('click', tableUntap);
$element.bind('click', tableTap);
$element.attr("src", 'http://imgur.com/a7J8OJ5.png');
}
resTableArray = [];
}
Im searching only tapped tables, and then just untap them and remove handlers.
fiddle: http://jsfiddle.net/r9ewnxzs/
Your mistake was to wrongly remove at untapping elements.

Issue With Pagination link from Instagram API

This is my first time working with pagination. The issue I am having is that I am getting multiple responses from the Instagram API after a certain amount of clicks to the pagination button. I think I have narrowed down the issue to the fact that the function is probably firing multiple times. Take a look at my functions.
Receives the data and sorts it to the other functions:
function sortAndStore(data) {
var images = data.data,
pagLink = data.pagination.next_url;
var newImages = [];
for (i = 0; i < images.length; i++) {
var link = images[i].link,
standardRes = images[i].images.standard_resolution.url,
thumb = images[i].images.thumbnail.url;
var tempImages = new Object();
tempImages.link = link;
tempImages.standard_res = standardRes;
tempImages.thumbnail = thumb;
newImages.push(tempImages);
}
createLayout(newImages);
loadMore(pagLink);
}
Creates the desired layout (sloppy right now but working):
function createLayout(data) {
var images = data;
if ($('#authorizeInsta').css('display') === 'inline') {
$('#authorizeInsta').hide();
// Adds additonal structure
$('<div id="instagramFeed" class="row-fluid" data-count="0"></div>').insertAfter('.direct_upload_description_container');
}
if (!$('#feedPrev').length > 0) {
$('<ul id="feedNav" class="pagination"><li><a id="feedPrev" href="#">Prev</a></li><li><a id="feedNext" href="#">Next</a></li></div>').insertAfter('#instagramFeed');
}
var count = $('#instagramFeed').data('count'),
countParse = parseInt(count);
newCount = countParse + 1;
$('<div id="row' + newCount + '" class="span3">').appendTo('#instagramFeed');
$('#instagramFeed').data('count', newCount);
for (i = 0; i < images.length; i++) {
var link = images[i].link,
standardRes = images[i].standard_res,
thumb = images[i].thumbnail,
newImage = '<img data-image="' + standardRes + '" src="' + thumb + '" class="feedImage" id="feedImage' + i + '"/>';
$(newImage).appendTo('#row' + newCount + '');
}
imageSelect();
}
Pagination function:
function loadMore(link) {
var pagLink = link;
console.log(pagLink);
$('#feedPrev').unbind('click').click(function(event) {
$.ajax({
url: link,
type: 'GET',
dataType: 'jsonp',
})
.done(function(data) {
sortAndStore(data);
})
.fail(function(data, response) {
console.log(data);
console.log(response);
});
return false;
});
}
I understand that the issue is probably here in the sortAndStore function
createLayout(newImages);
loadMore(pagLink);
And here is what the pagination link console logs out to. The issue being that I clicked the button three times and I got four responses. The first two times were fine. I got one pagination link, but the third time I received two response.
If you can see a different issue or suggest a different way to structure my functions it would be greatly appreciated. The data parameter in the sortAndStore function is the data from the original Instagram API call.
Thanks,
Figured it out! The issue was that every time the pagination button was clicked the browser was storing a new value for pagLink. Therefore, after clicking the button twice, there were two stored variables which made two pagination API calls.
The fix is to redefine the variable every time a new pagination link goes through the function, not to define an additional pagLink variable.
So this:
function sortAndStore(data) {
var images = data.data;
pagLink = data.pagination.next_url;
Instead of this:
function sortAndStore(data) {
var images = data.data,
pagLink = data.pagination.next_url;
So the solution was to redefine the variable, not to add an additional one, like I was doing by accident.

Categories