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, andnextcontrols within the player - Make the
previousandnextcontrols slightly smaller than theplaycontrol - 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
favoriteandsharecontrols 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-loadingto the loading flag element -
Add
data-player-containerto the main player container element, and set the display property tononefor the.containerclass -
Look at
PLAYLIST_DATA.dataand use the console to familiarize yourself with the data structure
- Create an object in
app.jscalledAppand load the script on the page:
<script src="assets/js/app.js"></script>
- Give the
AppobjectRdioService,Templates,Player, andPlaylistproperties, and assign all these properties a value of any empty object - Give the object an
initializefunction — leave the function body empty for now
- Assign
App.RdioServiceto a new instance ofRdioService
- Inside the
initializefunction, create a new instance of thePlayerobject and pass in aplaylistIdkey with a value ofp8056088 - Assign that new object to
App.Player - Call
initializeon document ready so that the application boots when the page loads:
$(document).ready(function() {
...
});
- Take a look at
App.Player.playlistDatain the console, the data should look the same asPLAYLIST_DATA.data
- Create a new object called
Playlistin a file calledplaylist.jsand 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
songswith an empty arrays - Initalize
currentSongwith an empty object - Assign
datato the valuedataparameter passed in to the constructor
- Initalize
- Create an
initializemethod on the prototype of thePlaylistobject — leave the function body empty for now - Call the
initializemethod from the constructor function after the instance variables are assigned
- Back in the
player.jsfile, find thegetPlaylistDatacallback (Hint: it’s where theself.playlistData = dataassignment happens) - In that callback function, create a new
Playlistobject and assign it toApp.Playlist - Pass the callback
datainto the newPlaylistobject - Take a look at
App.Playlist.datain the console, the data should look the same asPLAYLIST_DATA.dataandApp.Player.playlistData
- Create a new object called
Songin a file calledsong.jsand 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:
idand assign it todata.idtitleand assign it todata.nameartistand assign it todata.artistartworkand assign it todata.icon400
- Test it out the new
Songobject 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
rendermethod on the prototype of theSongobject - 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
initializemethodPlaylistobject use aforEachloop to create newSongobjects from each song entry in thedataobject - Push those objects into the
songsarray in thePlaylistobject - Take a look at
App.Playlist.songsin the console, you should see a bunch ofSongobjects - 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-templateand add thedata-template-songdata attribute (This will be the container for the Handlebars template) - Copy one of the
lis from the existing markup and paste it inside the Handlebars container - Add Handlebars variables for each of dynamic pieces of the template (
title,artistandartwork) (Variables in Handlebars are defined by double curly braces, i.e.{{myVariable}})
- Add the template to the
App.Templatesobject 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-playlistto theultag that wraps all of the list items in the playlist - Remove all the static
lielements from the playlist, leaving just the wrappingultag - Create a
rendermethod in thePlaylistprototype to render the playlist - We only want to display the next 5 songs on the playlist, so use the
slicefunction to get the first 5 songs from thesongsarray - Now, use a
forEachloop the render each object in your new array and append it to thedata-playlistulnode - 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
songsarray tocurrentSongso 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
renderoncurrentSongat the end of thePlaylistrender function - In the
Playlistinitializefunction, add a call torenderso that the playlist will be rendered when then object is created
