Soundcloud widget & for loop - javascript

i created music blog using soundcloud widgets. I trigger a button "Play all", so when you hit it, it plays all widgets.
$(function(){
var playAll = $('.playAll');
var widget0 = SC.Widget(playAll.find('iframe')[0]);
var widget1 = SC.Widget(playAll.find('iframe')[1]);
var widget2 = SC.Widget(playAll.find('iframe')[2]);
widget0.bind(SC.Widget.Events.FINISH,function() {
widget1.play();
widget0.unbind(SC.Widget.Events.FINISH);
});
widget1.bind(SC.Widget.Events.FINISH,function() {
widget2.play();
widget1.unbind(SC.Widget.Events.FINISH);
});
$("#playSound").click(function() {
widget0.toggle();
});
});
It works, but what i'm trying to do is "for" loop, but because lack of js / jquery skills, it doesn't work.

I love the enthusiasm to refactor code and the desire to use a higher level pattern, looping, instead of hard coding like you have. That being said the "middle space" problem is one of the harder problems to solve when iterating over a list.
You have to decide on a couple of things:
You have a task your going to perform n-1 times so you have to decide if your going to skip the first element or the last.
What task your going to perform in each iteration.
What you'll do after the loop finishes.
If you'll use a functional solution or a classic "for" loop.
Lets start with the task. We'll take the task from your example and put it in a function.
function connectFrames(now, next) {
now.bind(SC.Widget.Events.FINISH, function() {
next.play();
now.unbind(SC.Widget.Events.FINISH);
});
}
With this function we can use the index from our loop to pass in the current and next elements. The only exception will be when we are on the last element when there will be no "next", so that is the one we will skip. We will use the jQuery each for a modern / functional solution.
//One query to select all iframes inside .playAll
var playAll = $('.playAll iframe');
//The jQuery "loop" http://api.jquery.com/each/
playAll.each(function(index) {
var now = playAll[index],
next = playAll[index + 1];
//If their is no next... skip.
if(!next) return;
//Use the frames we have in our function
connectFrames(now, next);
});
The last thing we have to do is write in our final step which would be to start playing the content and we are done. Here is the complete code:
function connectFrames(now, next) {
now.bind(SC.Widget.Events.FINISH, function() {
next.play();
now.unbind(SC.Widget.Events.FINISH);
});
}
//One query to select all iframes inside .playAll
var playAll = $('.playAll iframe');
//The jQuery "loop" http://api.jquery.com/each/
playAll.each(function(index) {
var now = playAll[index],
next = playAll[index + 1];
//If their is no next... skip.
if(!next) return;
//Use the frames we have in our function
connectFrames(now, next);
});
//initiator from example
$("#playSound").click(function() {
playAll[0].toggle();
});

Assuming the HTML looks somewhat like this :
<div class="playAll">
<div>
<iframe></iframe>
<iframe></iframe>
<iframe></iframe>
</div>
</div>
You get get the list of all iframes inside the div with class "playAll" (no matter the depth), and iterate:
var iframes = $(".playAll iframe");
for (i = 0; i < iframes.length; i++) {
// do something with iframes[i]
}
http://api.jquery.com/descendant-selector/

Related

Building a JS automated slideshow, but loop and/or setInterval instances run concurrently?

I am attempting to build a very simple automated slideshow from scratch, but I'm coming across some difficulty. I've built working slideshows before, but not ones that were automated. So I began building one and tried using a for loop structure or the setInterval() method to mimic a loop:
$(function carousel() {
$('.slide:not(:first-child)').hide();
var slide1 = $('.slide:first-child');
var slide2 = $('.slide:nth-child(2)');
var slide3 = $('.slide:nth-child(3)');
var slide4 = $('.slide:last-child');
function moveSlide(currentSlide, nextSlide) {
setInterval(function () {
currentSlide.hide("slide", {direction: "left"}, 1000);
setTimeout(function () {
nextSlide.show("slide", {direction: "right"}, 1000);
}, 1000);
}, 1500);
}
var arr = [moveSlide(slide1, slide2), moveSlide(slide2, slide3), moveSlide(slide3, slide4)];
var i = 0;
setInterval(function () {
if (i < arr.length) {
arr[i] += 1;
console.log(i + "=>" + arr[i]);
} else {
return;
}
i++;
}, 1500);
});
Here's a Codepen.
Unfortunately, this did not go well, and I know why. I understand that in JS, the code will continue to execute and will not wait for the information in a loop to finish if using setInterval or setTimeout. So my question is, what would be a good workaround that doesn't require using an external library or plugin? If you could try to stick as close to my source code as possible that would be awesome. Thanks!
There are a few problems with your code. Calling moveSlide() will hide the specified slide and (after the timeout) show the specified next slide, but your use of setInterval() within that function means it will keep trying to hide the same first slide and then show the next one.
The line with var arr = [moveSlide(slide1, slide2),... is calling the moveSlide() function immediately and putting its return values into the array. So that means you've got several intervals all running (one per call to moveSlide()), and all stepping over each other trying to hide and show the same elements. Also the return value is undefined, so basically you've got an array full of undefined.
What I would suggest you do instead is something like the following:
$(function carousel() {
// get a list of *all* slides:
var slides = $('.slide');
// hide all but the first:
slides.slice(1).hide();
var current = 0;
setInterval(function() {
// hide the current slide:
slides.eq(current).hide(1000);
// increment the counter, wrapping around from end of the
// list to the beginning as required:
current = (current + 1) % slides.length;
// show the next slide after a timeout:
setTimeout(function () {
// note that `current` was incremented already:
slides.eq(current).show(1000);
}, 1000);
}, 3500); // make the interval larger than the hide/show cycle
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="slide">Slide 1</div>
<div class="slide">Slide 2</div>
<div class="slide">Slide 3</div>
<div class="slide">Slide 4</div>
<div class="slide">Slide 5</div>
Notice that I don't need individual variables for the individual slides, I just have a single slides variable that is a jQuery object containing all of the slides. This means you can easily change the number of slides on the page without changing the JS at all.
Note that I was too impatient to get jQueryUI to work in the snippet so I've just used a simple .hide() and .show(), but obviously that isn't the important part of the code I've shown.

Why is this jquery .each loop stopping my function call in the middle?

This is the entire function I am working on:
function collapse(){
var i = 0;
var currColors = [];
var currTitles = [];
// Get the color and the course id of all the current courses
$('.course').each(function (){
if (rgb2hex($(this).css("background-color")) !== "#f9f9f9") {
currColors.push(rgb2hex($(this).css("background-color")));
currTitles.push($(this).children(".course-id").text());
$(this).children(".course-delete").remove();
$(this).children(".course-id").text("");
$(this).css('background', "#f9f9f9");
}
});
alert("made it");
// Redistribute the classes
// This is where reverse lookup will eventually happen
i = 0;
$('div.course').each(function (){
if (i>=currTitles.length){
return false;
}
$(this).children(".course-id").text(currTitles[i]);
$(this).css('background', currColors[i++]);
$(this).append("<button id='temp' class='course-delete'>X</button>");
});
var i = currColors.length-1;
};
And this is the problematic section
$('.course').each(function (){
if (rgb2hex($(this).css("background-color")) !== "#f9f9f9") {
currColors.push(rgb2hex($(this).css("background-color")));
currTitles.push($(this).children(".course-id").text());
$(this).children(".course-delete").remove();
$(this).children(".course-id").text("");
$(this).css('background', "#f9f9f9");
}
});
I have this function collapse that is supposed to fill in the blank spot in a list of divs that the user has been adding to the screen if they delete one. This function was working fine at one point, but obviously I have screwed it up some how.
There are only and always will be 6 '.course' items because that is the size of the list. When collapse() is called it stops running after n times where n is the number of '.course' elements currently in use. The loop stops there and the whole function stops there. I have tried putting alert() statements everywhere but still no luck. If someone spots what I am missing I would really appreciate it.
I have figured out a work around for now, but I would like to know why my entire function is crashing in the middle.

Passing variable between functions in Javascript (Titanium Studio Desktop)

Hopefully I can articulate this problem well. I'm working on a simple audio player in Titanium Desktop. The primary code I'm focused on right now is the following:
function pickMusicFolder (){
var win = Titanium.UI.getCurrentWindow();
win.openFolderChooserDialog(function(folderResponse) {
var file = Titanium.Filesystem.getFile(folderResponse[0]);
var listing = file.getDirectoryListing();
for (var i = 0; i < listing.length; i++) {
if (listing[i].isDirectory()) {
// if the listing is a directory, skip over it
$('#main').append('DIRECTORY: ' + listing[i].nativePath() +
'<br />');
// continue;
}
else {
// otherwise, print the filename of the file to the #main content window
var songOnList = listing[i].nativePath();
var songURL = songOnList.replace(/\\/g,"/");
$('#main ul').append('<li>' + songURL + '</li>');
}
}
});
};
function playSong(songURL){
var currentSong = Titanium.Media.createSound(songURL);
currentSong.play();
this.stopPlayback = stopPlayback;
function stopPlayback(currentSong){
currentSong.stop();
}
}
And then related HTML:
<input type="image" src="img/player_stop.png" name="stopPlayback" onClick="playSong.stopPlayback(songURL)" />
<input type="image" src="img/folder_add.png" name="pickMusicFolder" onClick="pickMusicFolder()" />
Now, both pickMusicFolder and playSong itself work properly. However, stopPlayback isn't working, I'm having a really hard time grasping how to deal with different functions for playing and stopping audio, since the code that's generating the clickable links is completely compartmentalized within the pickMusicFolder function, while the code for stopping playback is only attached to the one separate interface button.
I simply need access to the songURL variable between multiple functions in order to be able to perform operations on one solitary song while it's playing (or not). I'm avoiding resorting to global variables, as I find that to be sort of a cop out.
Anyone have any ideas? Any tips are much appreciated! (Oh, and please ignore my ugly code; was trying a bunch of hack-y solutions before posting.)
Very quickly solution is to store currentSong as property of function playSong:
function playSong(songURL){
var currentSong = Titanium.Media.createSound(songURL);
playSong.currentSong = currentSong; // store curernt playing song as property of function
currentSong.play();
}
playSong.stopPlayback = function() {
if (playSong.currentSong) {
playSong.currentSong.stop();
delete playSong.currentSong;
}
};
when song will be stopped just remove this property that would mean no song are playing now
You should use a closure. See below
var stopPlayback = function() { /* Placeholder, defined later */ };
var playSong = function(songURL){
var currentSong = Titanium.Media.createSound(songURL);
currentSong.play();
// Redefine stopSong in the parent scope
stopPlayback = function(){
// currentSong is available to this function via closure
currentSong.stop();
}
}
HTML Edit
<input type="image" src="img/player_stop.png" name="stopPlayback" onClick="stopPlayback()" />
As a side note, you should not be using HTML attributes to attach JavaScript events. This is a great series on event attachment, but what you really should read is this part. Also, every JS library provides methods for attaching events to make your life easier.

jQuery: Problem assigning setIntervals to an array

I'm attempting to run multiple animations (slideshows of sorts) on one page, but the code is only working for one of the (in my case) 3 slideshows that are actually present.
The issue is not with the animation but with the actual initialisation and running of functions (explained better below by looking at the code):
The HTML:
<div class="someclass1" rel="slideshow" type="fade" duration=8500>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass2" rel="slideshow" type="slide" duration=4000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass3" rel="slideshow" type="fade" duration=5000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
jQuery:
$(function() {
var plays = [];
var duration = 0;
var targets = [];
var t = "";
var $obs = $('div[rel="slideshow"]')
for(var x = 0; x < $obs.length; x++){
$obs.eq(x).children('.wrapper').eq(0).addClass('active');
$obs.eq(x).children('.wrapper').css({opacity: 0.0});
$obs.eq(x).children('.active').css({opacity: 1.0});
$obs.eq(x).children('.navigation a.slide-buttons').eq(0).addClass('current');
// Set duration
duration = $obs.eq(x).attr('duration');
// Set target
targets = $obs.eq(x).attr('class').split(' ');
t = '';
for(var i=0; i<targets.length; i++){
t += '.' + targets[i];
}
if($obs.eq(x).attr('type')==='fade'){
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
}
else if($obs.eq(x).attr('type')==='slide'){
plays[x] = setInterval(function(){slideSwitch(t);}, duration);
}
}
});
Through testing, I have shown that the loop runs successfully and passes the appropriate target and duration to either fadeSwitch or slideSwitch for all 3 runs of the loop.
fadeSwitch and slideSwitch are identical except for the animation part, for example:
function fadeSwitch(target) {
var $active = $(target+' .active');
if ( $active.length === 0 ){ $active = $(target+' .wrapper:first');}
var $next = $active.next('.wrapper').length ? $active.next('.wrapper')
: $(target+' .wrapper:first');
// FADE ANIMATIONS
$active.animate({opacity : 0.0}, 500, function() {
$active.addClass('last-active');
});
$next.animate({opacity: 1.0}, 500, function() {
$active.removeClass('active last-active');
$next.addClass('active');
});
}
However this function will run only using the last found target (i.e t = '.someClass3'). Even though by placing console.log alerts in the setInterval functions I know that it is applying the correct variables.
e.g.
plays[0] = setInterval(function(){fadeSwitch('.someclass1');}, 8500);
plays[1] = setInterval(function(){fadeSwitch('.someclass2');}, 4000);
plays[2] = setInterval(function(){fadeSwitch('.someclass3');}, 5000);
Yet as I have tried to (badly) explain, if I place a console.log inside of fadeSwitch to test what is being passed as the target when it runs (remember it is set to run after an interval, so by the time the .someClass1 function runs for the first time, the plays[] array is full and finished) the log shows that the target is always .someClass3 and it never succesfully runs for anything else but that last entered target.
Any suggestions or help is greatly appreciated.
Thank you.
The value of t is being "closed over" by your anonymous functions when you call setInterval. For every iteration of your loop you create a new anonymous function, and like you said, at the time t has the right value.
The problem is that by the time each function executes t's value has changed (it will hold the last value of the loops), and all three anonymous functions refer to the same t variable (that is the nature of a closure and the lexical scoping of javascript). The quick fix is to give each anonymous function the right value and not a reference to t:
Change this:
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
to this:
plays[x] = setInterval((function(t2){ return function(){ fadeSwitch(t2); }; })(t), duration);
And obviously the same for the same line with slideSwitch.
Another thing I felt I should point out: You're using invalid attributes in your html, consider finding an alternative, like hidden embedded markup (e.g. <div class="duration" style="display:none">5000</div>), or class names, or html5 data attributes, instead of <div duration=5000>

Help with JQuery Callback

As per my previous question, I have a working animation which fades in and out each element within the div slideshow. The problem is that I want this animation to continue from the beginning once it has reached the last element. I figured that was easy and that I'd just place an infinite loop inside my JQuery function, but for some reason if I insert an infinite loop, no animation displays and the page hangs. I also cannot find anything in the documentation about how properly place a callback. How can I get this code to restart from the beginning of the animation once it finishes iterating over each object and why is an infinite loop not the right way to go about this?
<div id="slideshow">
<p>Text1</p>
<p>Text2</p>
<p>Test3</p>
<p>Text4</p>
</div>
<script>
$(document).ready(function() {
var delay = 0;
$('#slideshow p').each(
function (index, item)
{
$(this).delay(delay).fadeIn('slow').delay(800).fadeOut('slow');
delay += 2200;
}
);
});
</script>
You could do something like this:
$(function() {
var d = 2200;
function loopMe() {
var c = $('#slideshow p').each(function (i) {
$(this).delay(d*i).fadeIn('slow').delay(800).fadeOut('slow');
}).length;
setTimeout(loopMe, c * d);
}
loopMe();
});
You can give it a try here.
Instead of keeping up with a delay, you can just multiple it by the current index in the loop...since the first index is 0, the first one won't be delayed at all, then 2200ms times the amount of elements later, do the loop again. In the above code d is the delay, so it's easily adjustable, and c is the count of elements.
This solution is in my opinion more elegant, also more natural, it is easier to control, to correctly edit values of delays etc. I hope you'll like it.
$(document).ready(function () {
var elementsList = $('#slideshow p');
function animationFactory(i) {
var element = $(elementsList[i % elementsList.length]);
return function () {
element.delay(200).fadeIn('slow').delay(800).fadeOut('slow', animationFactory(i + 1));
};
}
animationFactory(0)();
});

Categories

Resources