You’ve been tasked with developing a new front end feature. HTML, CSS, and JavaScript are nothing new to you, in fact you even know a few tricks to get this feature out the door. It doesn’t take you long and the code works like a charm, yet you have a looming suspicion that some of the code might not be up to par. You’re likely right, and you’re definitely better than that.
We often write code without paying attention to the bigger picture, or overall code base. Upon stepping back we notice areas of duplicate code, ripe for refactoring. It’s time to build more modular front ends, focusing on the reusability of HTML, CSS, and JavaScript, and to take maintainability to heart.
- Within the playlist, position the album artwork to the left of the song title and artist name
- Keep the album artwork vertically centered with the song title and artist name
HTML
<li class="playlist-song flag">
<figure class="flag-object">
<img src="assets/img/#.jpg" alt="Artist Name">
</figure>
<div class="flag-body">
<h3 class="h-subheadline h-bold">Song Title</h3>
<h4 class="artist h-byline">Artist Name</h4>
</div>
</li>
CSS
/* Flag object
================================== */
.flag {
display: table;
width: 100%;
}
.flag-object,
.flag-body {
display: table-cell;
vertical-align: middle;
}
.flag-object img {
display: block;
}
.flag-body {
width: 100%;
}
/* Playlist
================================== */
.playlist-song .flag-object {
padding: 0 20px;
}
.playlist-song .flag-object img {
border-radius: 5px;
height: 66px;
}
.playlist-song .flag-body {
padding-right: 20px;
}
- Reusing styles from positioning the album artwork, add a
Currently loading…
section at the top of the file to displayed before the playlist loads
HTML
<div class="loading flag">
<figure class="flag-object">
<img src="assets/img/loading.gif" alt="Currently loading...">
</figure>
<div class="flag-body">
<h3>Currently loading…</h3>
</div>
</div>
CSS
/* Loading
================================== */
.loading {
color: #95959a;
margin: 0 auto;
padding: 66px;
width: 292px;
}
.loading .flag-object {
padding-right: 10px;
}
.loading .flag-object img {
height: 22px;
}
- Add
previous
,play
, andnext
controls within the player - Make the
previous
andnext
controls slightly smaller than theplay
control - Keep all controls vertically centered
HTML
<header class="player cover-art controls-container">
<menu class="controls">
<li>
<a class="control-prev ir" href="#">Previous</a>
</li>
<li>
<a class="control-play ir" href="#">Play/Pause</a>
</li>
<li>
<a class="control-next ir" href="#">Next</a>
</li>
</menu>
</header>
CSS
/* Controls
================================== */
.controls-container {
position: relative;
}
.controls {
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .5));
bottom: 0;
margin: 0;
padding: 44px 20px 22px 20px;
position: absolute;
text-align: center;
width: 100%;
}
.controls li {
display: inline-block;
vertical-align: middle;
}
.controls a {
background-image: url("../img/controls.png");
background-image: url(“../img/controls.svg");
border: 2px solid #fff;
border-radius: 50%;
display: block;
height: 38px;
margin: 0 8px;
width: 38px;
}
.controls a:hover {
background-color: rgba(0, 0, 0, .5);
}
.controls .control-play {
height: 44px;
width: 44px;
}
- Add
favorite
andshare
controls within each song in the playlist - Reuse as many styles from the player controls as possible
HTML
<menu class="controls player-controls">
...
</menu>
...
<div class="flag-body controls-container">
<h3 class="h-subheadline h-bold">Song Title</h3>
<h4 class="artist h-byline">Artist Name</h4>
<menu class="controls playlist-controls">
<li>
<a class="control-fav ir" href="#">Favorite</a>
</li>
<li>
<a class="control-share ir" href="#">Share</a>
</li>
</menu>
</div>
CSS
/* Controls
======================================================= */
.controls-container {
position: relative;
}
.controls {
margin: 0;
position: absolute;
text-align: center;
}
.controls li {
display: inline-block;
vertical-align: middle;
}
.controls a {
background-image: url("../img/controls.png");
background-image: url("../img/controls.svg");
border-radius: 50%;
border-style: solid;
border-width: 2px;
display: block;
}
/* Player
======================================================= */
.player-controls {
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .5));
bottom: 0;
padding: 44px 20px 22px 20px;
width: 100%;
}
.player-controls a {
border-color: #fff;
height: 38px;
margin: 0 8px;
width: 38px;
}
.player-controls a:hover {
background-color: rgba(0, 0, 0, .5);
}
.player-controls .control-play {
height: 44px;
width: 44px;
}
/* Playlist
======================================================= */
.playlist-controls {
background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 35px);
height: 100%;
padding: 0 20px 0 40px;
right: 0;
top: 0;
}
.playlist-controls li {
position: relative;
top: 50%;
transform: translateY(-50%);
}
.playlist-controls a {
border-color: #bfbfbf;
height: 32px;
width: 32px;
}
.playlist-controls a:hover {
border-color: #7c7c87;
}
- Animate the player controls to appear from the bottom upon hovering over the player
- Animate the song controls to appear from the right upon hovering over a song
- Add hardware acceleration to each of the animations
HTML
<menu class="controls player-controls boost">
...
</menu>
<menu class="controls playlist-controls boost">
...
</menu>
CSS
/* Controls
================================== */
.controls-container {
overflow: hidden;
position: relative;
}
.controls {
margin: 0;
position: absolute;
text-align: center;
transition: all .2s ease-in-out;
}
/* Player
================================== */
.player-controls {
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .5));
bottom: 0;
padding: 44px 20px 22px 20px;
transform: translateY(100%);
width: 100%;
}
.player:hover .player-controls {
transform: translateY(0);
}
/* Playlist (below now playing)
================================== */
.playlist-controls {
background: linear-gradient(to right, rgba(255, 255, 255, 0), fff 35px);
height: 100%;
padding: 0 20px 0 40px;
right: 0;
top: 0;
transform: translateX(100%);
}
.playlist-song:hover .playlist-controls {
transform: translateX(0);
}
- Our application will be using a few JavaScript libraries, let’s load these files on the page
- We can load jQuery and Handlebars from a CDN:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>
- Add local files that allow us to interact with Rdio:
<script src="assets/js/vendor/jquery.rdio.js"></script>
<script src="assets/js/vendor/rdio-service.js"></script>
<script src="assets/js/player.js"></script>
<script src="assets/js/settings.js"></script>
- Load a JSON file with our playlist data (to prevent us from having to constantly hit Rdio during development):
<script src="data/playlist.json"></script>
-
Add
data-player-loading
to the loading flag element -
Add
data-player-container
to the main player container element, and set the display property tonone
for the.container
class -
Look at
PLAYLIST_DATA.data
and use the console to familiarize yourself with the data structure
- Create an object in
app.js
calledApp
and load the script on the page:
<script src="assets/js/app.js"></script>
- Give the
App
objectRdioService
,Templates
,Player
, andPlaylist
properties, and assign all these properties a value of any empty object - Give the object an
initialize
function — leave the function body empty for now
- Assign
App.RdioService
to a new instance ofRdioService
- Inside the
initialize
function, create a new instance of thePlayer
object and pass in aplaylistId
key with a value ofp8056088
- Assign that new object to
App.Player
- Call
initialize
on document ready so that the application boots when the page loads:
$(document).ready(function() {
...
});
- Take a look at
App.Player.playlistData
in the console, the data should look the same asPLAYLIST_DATA.data
- Create a new object called
Playlist
in a file calledplaylist.js
and load the script on the page:
<script src="assets/js/playlist.js"></script>
- Assign it to a constructor function that takes a parameter of
data
- Set some instance variables in that function:
data
,songs
,currentSong
- Initalize
songs
with an empty arrays - Initalize
currentSong
with an empty object - Assign
data
to the valuedata
parameter passed in to the constructor
- Initalize
- Create an
initialize
method on the prototype of thePlaylist
object — leave the function body empty for now - Call the
initialize
method from the constructor function after the instance variables are assigned
- Back in the
player.js
file, find thegetPlaylistData
callback (Hint: it’s where theself.playlistData = data
assignment happens) - In that callback function, create a new
Playlist
object and assign it toApp.Playlist
- Pass the callback
data
into the newPlaylist
object - Take a look at
App.Playlist.data
in the console, the data should look the same asPLAYLIST_DATA.data
andApp.Player.playlistData
- Create a new object called
Song
in a file calledsong.js
and load the script on the page:
<script src="assets/js/song.js"></script>
- Assign it to a constructor function that takes a parameter of
data
- Set some instance variables in that function:
id
and assign it todata.id
title
and assign it todata.name
artist
and assign it todata.artist
artwork
and assign it todata.icon400
- Test it out the new
Song
object with the following code:
data = {
"id": "t8209409",
"radioKey": "sr8209409",
"baseIcon": "album/2/b/5/00000000000a85b2/1/square-200.jpg",
"artistUrl": "/artist/Architecture_In_Helsinki/",
"duration": 220,
"album": "Contact High",
"albumUrl": "/artist/Architecture_In_Helsinki/album/Contact_High/",
"shortUrl": "http://rd.io/x/QHlRKz4bUw/",
"albumArtist": "Architecture In Helsinki",
"canStream": true,
"embedUrl": "https://rd.io/e/QHlRKz4bUw/",
"trackNum": 1,
"albumArtistKey": "r86460",
"icon": "http://rdio1img-a.akamaihd.net/album/2/b/5/00000000000a85b2/1/square-200.jpg",
"name": "Contact High",
"artistKey": "r86460",
"url": "/artist/Architecture_In_Helsinki/album/Contact_High/track/Contact_High/",
"icon400": "http://rdio3img-a.akamaihd.net/album/2/b/5/00000000000a85b2/1/square-400.jpg",
"artist": "Architecture In Helsinki",
"albumKey": "a689586"
}
mySong = new Song(data)
- Add data attributes to the markup in the places where the song information should be rendered, we’ll use:
data-song-title
,data-song-artist
,data-song-artwork
- Be sure to get both places for the artwork attribute
- Create an
render
method on the prototype of theSong
object - Use jQuery to render the song title, artist and artwork to the browser using the data attributes as your selectors
- Try rendering you song object from the previous section:
mySong.render()
- In the
initialize
methodPlaylist
object use aforEach
loop to create newSong
objects from each song entry in thedata
object - Push those objects into the
songs
array in thePlaylist
object - Take a look at
App.Playlist.songs
in the console, you should see a bunch ofSong
objects - See if you can render one of them. (Hint:
App.Playlist.songs[2].render()
)
- Now we need to create a Handlebars template so that we can render each song to the player’s playlist
- Add a script tag with the type of
text/x-handlebars-template
and add thedata-template-song
data attribute (This will be the container for the Handlebars template) - Copy one of the
li
s from the existing markup and paste it inside the Handlebars container - Add Handlebars variables for each of dynamic pieces of the template (
title
,artist
andartwork
) (Variables in Handlebars are defined by double curly braces, i.e.{{myVariable}}
)
- Add the template to the
App.Templates
object and give it the nameplaylistSong
- Use the Handlebars.compile function like so:
Handlebars.compile($('[data-template-song]').html())
- Test the template rendering with the following code:
data = {
title: "Contact High",
artist: "Architecture In Helsinki",
artwork: "http://rdio3img-a.akamaihd.net/album/2/b/5/00000000000a85b2/1/square-400.jpg"
}
App.Templates.playlistSong(data)
- Add a data attribute of
data-playlist
to theul
tag that wraps all of the list items in the playlist - Remove all the static
li
elements from the playlist, leaving just the wrappingul
tag - Create a
render
method in thePlaylist
prototype to render the playlist - We only want to display the next 5 songs on the playlist, so use the
slice
function to get the first 5 songs from thesongs
array - Now, use a
forEach
loop the render each object in your new array and append it to thedata-playlist
ul
node - Try out your new render method by calling
App.Playlist.render()
in the console
- At the beginning of the render method, assign the first song in the
songs
array tocurrentSong
so that we can render that song to the player - Since the first song is now our
currentSong
, let move that element to the bottom of the array using the following code:
this.songs = this.songs.concat(this.songs.splice(0, 1));
- Call
render
oncurrentSong
at the end of thePlaylist
render function - In the
Playlist
initialize
function, add a call torender
so that the playlist will be rendered when then object is created