I have an audio player with several songs. For each song, I want to display a div
below that has the lyrics to that specific song, while hiding the lyrics for all the other songs.
This is the script for the audio player, which works:
initAudio($('#playlist li:first-child'));
function initAudio(element){
var song = element.attr('song');
var title = element.text();
var cover = element.attr('cover');
var artist = element.attr('artist');
var album = element.attr('album');
//Create audio object
audio = new Audio('public/songs/'+ song);
//Insert audio info
$('.artist').text(artist);
$('.title').text(title);
$('.album').text(album);
//Insert song cover
$('img.cover').attr('src','public/images/' + cover);
$('#playlist li').removeClass('active');
element.addClass('active');
// Set timer to 0
$('#duration').html('0:00');
// Maintain volume on song change
audio.volume = $('#volume').val() / 100;
}
And here is a simplified version of the playlist and lyrics HTML:
<ul id="playlist">
<li class="song-list active" song="song-1.wav" data-id="song-1"></li>
<li class="song-list" song="song-2.wav" data-id="song-2"></li>
<li class="song-list" song="song-3.wav" data-id="song-3"></li>
</ul>
<div id="song-1" class="lyrics">Lyrics to song 1</div>
<div id="song-2" class="lyrics">Lyrics to song 2</div>
<div id="song-3" class="lyrics">Lyrics to song 3</div>
I tried a lot of things, to no avail. I'm not even sure how to target each song as it plays to extract the data-attribute
.
Any ideas?
Note: For the next steps we assume to target "song-1".
data-id
-attribute by calling element.attr('data-id')
;id="song-1"
by calling $('#song-1')
;$('#' + element.attr('data-id'))
(take a look at step 1 if you don't know why).$('.lyrics').hide()
and only display the wanted one $('#' + element.attr('data-id')).show()
.initAudio($('#playlist li:first-child'));
function initAudio( element ) {
// this version performs slightly better than the one in the 1, 2, 3 explanations
$('.lyrics').hide().filter('#' + element.attr('data-id')).show()
}
$('#playlist').on('click', function( oEvent ) {
initAudio( $( oEvent.target ) )
} );
<ul id="playlist">
<li class="song-list active" song="song-1.wav" data-id="song-1">Click Me 1!</li>
<li class="song-list" song="song-2.wav" data-id="song-2">Click Me 2!</li>
<li class="song-list" song="song-3.wav" data-id="song-3">Click Me 3!</li>
</ul>
<div id="song-1" class="lyrics">Lyrics to song 1</div>
<div id="song-2" class="lyrics">Lyrics to song 2</div>
<div id="song-3" class="lyrics">Lyrics to song 3</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This works like you want it to (I hope so - at least) but isn't an ideal solution; just wanted to explain whats going on! The downside about the above solution is a lot of unnecessary computing.
So here is an advanced mockup (with less computing):
//initAudio($('#playlist li:first-child'));
initAudio($('#playlist').find('li:first-child')); // performs better
function initAudio( element ) {
// if you chain it this way you don't have to cache $('.lyrics')
$('.lyrics') // get collection of all div.lyrics
.filter('.active').removeClass('active') // target the current div.lyrics.active and remove class "active"
.end() // get collection div.lyrics back again
.filter('#' + element.attr('data-id')).addClass('active'); // filter target div.lyrics#song-[1|2|3] and add class "active"
}
$('#playlist').on('click', function( oEvent ) {
initAudio( $( oEvent.target ) )
} );
.lyrics { /* this will prevent to show all lyrics before you call initAudio() */
display: none;
}
.lyrics.active { /* style active lyrics as you like */
display: initial;
}
<ul id="playlist">
<li class="song-list active" song="song-1.wav" data-id="song-1">Click Me 1!</li>
<li class="song-list" song="song-2.wav" data-id="song-2">Click Me 2!</li>
<li class="song-list" song="song-3.wav" data-id="song-3">Click Me 3!</li>
</ul>
<!-- set one as active in advance if you like -->
<div id="song-1" class="lyrics active">Lyrics to song 1</div>
<div id="song-2" class="lyrics">Lyrics to song 2</div>
<div id="song-3" class="lyrics">Lyrics to song 3</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Additional Recommondation
To keep valid markup while using custom HTML5 attributes simply prepend your attributes with data-
.
So consider to change
song="song-#.wav"
to
data-song="song-#.wav"
and your markup will be valid. (The same for the other attributes as well cover
to data-cover
, artist
to data-artist
, etc.).
References for jQuery methods that are not used in OP
Additional Inspiration
Depending on how you build your site there would be also more straight forward solutions than storing data into markup with data-*
-attributes.
Why not separating the data from the actual HTML. For doing so you could deploy the data as a variable.
var sIdCurrent = undefined,
oData = {
'song-1': { artist: 'artist 1', lyrics: 'Lyrics to song 1' },
'song-2': { artist: 'artist 2', lyrics: 'Lyrics to song 2' },
'song-3': { artist: 'artist 3', lyrics: 'Lyrics to song 3' },
};
function initAudio( sId ) {
if ( sId === sIdCurrent ) return;
sIdCurrent = sId;
//$('#artist').find('span').text( oData[ sId ].artist );
//$('#lyrics').find('span').text( oData[ sId ].lyrics );
// the above can be abstracted to
for ( sKey in oData[ sId ] ) {
$('#' + sKey).find('span').text( oData[ sId ][ sKey ] );
}
}
initAudio('song-1');
$('#playlist').on('click', function( oEvent ) {
initAudio( oEvent.target.id )
} );
<!-- As you can see the markup is much cleaner now.
No need to store data uneffectively in markup. -->
<ul id="playlist">
<li class="song-list active" id="song-1">Click Me 1!</li>
<li class="song-list" id="song-2">Click Me 2!</li>
<li class="song-list" id="song-3">Click Me 3!</li>
</ul>
<div id="artist">Artist: <span><span></div>
<div id="lyrics">Lyrics: <span><span></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>