This sprint we will:
- add an embedded Song model to our Album model
- change the UI to allow users to see songs in the embedded model
Note: as we go through this if you get stuck make use of the hints, your neighbors and the solutions.
You must complete all of the previous sprint before starting this sprint. (excludes stretch challenges)
In this step we'll be changing our Album schema to have an embedded schema that contains songs.
The data from the database will look a little like this:
{ genres: [ 'new wave', 'indie rock', 'synth pop' ],
songs:
[ { _id: a665ff1678209c64e51b4e6a,
trackNumber: 1,
name: 'Swamped' },
{ _id: a665ff1678209c64e51b4e64,
trackNumber: 7,
name: 'Tight Rope' } ],
_id: a665ff1678209c64e51b4e63,
releaseDate: '2008, November 18',
name: 'Comalies',
artistName: 'Lacuna Coil',
__v: 0 },
- note that
songs
has been added and is an array of other records!
We're going to create an embedded model for songs and embed it in albums. That's right, Albums usually have unique Songs on them.
-
Create a
models/song.js
-
Open the file and create a model with properties like:
name: String,
trackNumber: Number
-
Export the module and require it in
models/index.js
-
Require
./song.js
in./albums.js
-
Alter the schema of Album to have a songs array that uses the
Song.schema
You may have seen embedded models defined in the same file as the model that it's embedded in; that's OK. Here we're building it in a separate file.
Let's add seeds. Some basic data is provided for you. We're going to use this data for all albums for now, even though it's not accurate.
-
Copy the sample songs into
seed.js
-
Write a
forEach
loop that adds the sample songs to each sampleAlbum inseed.js
. Compare your work to the sample above. -
Run
node seed.js
-
Fix any issues you encounter, until you can see that it's also adding songs for each album.
Let's go back to app.js
and our html. If you check the output of your AJAX call, you should see that we're already retrieving songs with each album. Double-check this before you proceed (in the browser console).
We'll change the client to add another <li>
after the ones that are already being generated for each album. We'll list our songs in there.
For now we're just going to make this super simple and output something like:
<li class="list-group-item">
<h4 class="inline-header">Songs:</h4>
<span> – (1) Swamped – (2) Heaven's a Lie – (3) Daylight Dancer – (4) Humane – (5) Self Deception – (6) Aeon – (7) Tight Rope – </span>
</li>
- Refactory your current handlebars template. Change it to use
#each
to display each song's name from thealbum
object.
Your HTML can look like what we have above, or you could make it a little better looking.
Checkout the solution for this step if you're struggling with the template.
Now let's create the functionality to add new songs. To do this we're going to use a button to open a bootstrap modal.
Fortunately, the modal is already setup for you in index.html
. We will have to add a button to trigger it. Also since the modal will be used for creating a song for any album we'll have to track that.
We're going to do this by setting a data-attribute called album-id
on the modal itself. We will set this attribute when we display the modal.
Let's pseudo-code this.
// this function will be called when someone clicks a button to create a new song
// it has to determine what album (in the DB) the song will go to
function handleNewSongButtonClick() {
// get the current album's id from the row the button is in
// set the album-id data attribute on the modal (jquery)
// display the modal
}
First we need to make sure we have the album id so we can use it later. We'll set a data-attribute on each album row first so that each row has it's ID listed.
-
In the template HTML for each album add a new data-album-id attribute to the top
<div class='row album'>
. -
Set the value of that attribute to
album._id
. -
Refresh the page and inspect the HTML in the browser. Make sure the attribute is set and different for each album.
-
Add a button inside the panel-footer.
button code <div class='panel-footer'> <button class='btn btn-primary add-song'>Add Song</button> </div>
CSS IDs must be unique, that's why we used a class here. We'll just use a compound CSS selector.
-
Use jQuery to bind to these buttons' click events.
-
In your click event-handler get the current album row's data-album-id attribute.
Hint: you may want to read the jQuery documentation for
parents
orclosest
anddata
$('#albums').on('click', '.add-song', function(e) {
console.log('add-song clicked!');
var id= $(this).closest('.album').data('album-id'); // "5665ff1678209c64e51b4e7b"
console.log('id',id);
});
The above code might be new to you. We've added a second CSS-selector in the 'on' arguments. Because the .add-song component is not on the page at document-ready our event-listener cannot bind to it. Instead we'll bind to something above it like
body
or#albums
. As long as it's on the page when we add our event-listener we will be able to capture the click and if it's on the '.add-song' handle it in our function.
- Set the data-attribute
album-id
on the#songModal
. We'll use this to keep track of which album the modal is referring to at any time.
$('#songModal').data('album-id', currentAlbumId);
- You can open a bootstrap modal by selecting it in jQuery and calling the
.modal()
function. After setting the data-album-id attribute in your function, open the modal. It should appear on screen!
Suggested reading: See the bootstrap docs
So we should now have a working modal, but it doesn't do anything yet. Let's add a function to handle the submit on the modal and POST the form data as a new song.
```pseudo
// call this when the button on the modal is clicked
function handleNewSongSubmit(e) {
e.preventDefault();
// get data from modal fields
// get album ID
// POST to SERVER
// clear form
// close modal
// update the correct album to show the new song
}
```
> You don't have to fill in all of the code here just yet, read further.
-
Create the
handleNewSongSubmit
function. -
We'll need the album-id in order to build the correct URL for the AJAX POST. Our URL will eventually be like
http://localhost:3000/api/albums/:album_id/songs
. In thehandleNewSongSubmit
function get the correct id from the modal. Build the URL and save it as a variable. -
Prepare the AJAX POST. For data make sure you get the
.val
from the input fields. -
Don't forget to call handleNewSongSubmit when the modal button is clicked.
Hint: The modal doesn't actually have a form. Use .val to get the data from the input fields and construct an object for your POST data.
Now we need to add the POST route on the server. We're going to be using request-params (URL parameters) this time.
-
In
controllers/albumsSongsController.js
build theapp.post
callback method for '/api/albums/:album_id/songs'. Get the id from the request and find the correct album in the database. -
Configure the route in
server.js
to call thecreate
function. -
In your method in
controllers/albumsSongsController
create the new Song and add it to the Album. -
Save your results and respond to the client with JSON.
Hint: when connecting to the database make sure that Song has been exported in
models/index.js
-
Add a
GET /api/albums/:albumId
route (show method), it should respond with the requested album including it's songs. Depending on your choice below, you may or may not need this right away.You can easily test this route by finding a valid ID and then using postman, curl or your browser console with code like:
$.get('/api/albums/56fdd7b7febebdd208a38934').success(function(data) { console.log(data) });
To get back and display the created song on the page, you have a couple of options:
- You could have
POST /api/albums/:albumId/songs
return just the created song.- Then make a request to
GET /api/albums/:albumId
to get the entire album and render that on the page. - You'll need to add the
show
function inalbumsController
and route inserver.js
.
- Then make a request to
This is a very common approach and probably the most standard.
The solutions will take this route (and you're encouraged to as well).
- You could have your
POST /api/albums/:albumId/songs
route return the entire album instead of just the song. Then:- Re-render the album on the page.
This has the advantage of reducing the number of requests from client to server. But usually a POST response contains just the created record (not it's parent).
Hint:
$('#id-to-modal').modal('hide');
bootstrap modal docs
-
Add imageUrl as a property on Albums. Update everything to use it!
-
Add the remaining GET and POST routes to Create and Read.
GET /api/albums/:album_id/songs/:id
GET /api/albums/:album_id/songs
- Add track length as a field for each album.
You should now have the following API routes at a minimum:
GET /api/albums
POST /api/albums
GET /api/albums/:id
POST /api/albums/:albumId/songs
You should also have a working app! Congratulations!