Skip to content

Commit

Permalink
Merge pull request #3 from sf-wdi-27-28/fixed_instructions
Browse files Browse the repository at this point in the history
Fixed instructions - previously lost on this branch
  • Loading branch information
willcauthen committed Apr 4, 2016
2 parents ee49111 + a118c2e commit e256aa1
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 111 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
Full CRUD SPA with mongoose and Express.

Prerequisites:

* html `data-` attributes
* jQuery, AJAX
* `$('asdf').on('click', '.add-song'`
* `$.get`
* `$.post`
* `$(document).ready(function() `
* Access to challenging CSS selectors could be helpful. Example: ` $('[data-album-id=a7397f6f2e]')`
* Express server, static assets
* serving JSON on /api routes
* RESTful design
* JSON serving /api routes
* Bootstrap - the lab will introduce modals
* CRUD with mongoose
* mongoose embedded relationships
* Part 3: mongoose references
* html `data-` attributes
* Controllers using module pattern
* An included write-up serves to introduce this pattern.

Other tools
* bower is used, but students need not interact with it
Expand All @@ -25,6 +29,7 @@ Other tools
## Overview

This lab begins with a basic front-end to display a list of music albums. As we progress through we'll:

* serve the album data from our server's `/api/` routes.
* get the data from the server using ajax and display it on the page with jQuery
* retrieve the data from the database
Expand Down
Binary file added docs/assets/images/sprint3_add_song_button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/sprint3_album_id_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/tunely_edit_album_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions docs/code_samples/sprint6_inline_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

<!-- sample template to use for each song (one-line form) -->

<form class="form-inline" id="{{song.id}}" >
<div class="form-group">
<input type="text" class="form-control song-trackNumber" value="{{song.trackNumber}}">
</div>
<div class="form-group">
<input type="text" class="form-control song-name" value="{{song.name}}">
</div>
<div class="form-group">
<button class="btn btn-danger" data-song-id="{{song._id}}">x</button>
</div>
</form>
16 changes: 0 additions & 16 deletions docs/code_samples/sprint6_inline_form.js

This file was deleted.

203 changes: 203 additions & 0 deletions docs/controllers_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
## Server-side Example: separating route logic into controllers


#### Before Controllers

If you haven't seen this before, you may be surprised to discover that the logic for every route doesn't have to be in `server.js`. If you think about it, though, it's obvious that in large apps, server.js would become very long and hard to work with. _yuck_

#### Controllers and Resources

We're going to use the module pattern to separate out some of the code that's currently in our server.js. Have you heard the term **resource**? A **resource** is just a type of data your app stores -- and it can be thought of as being related to the endpoints in your routes. For example, in Tunely we'll have a `/api/albums/:id` route. `albums` is a resource here. To take full advantage of the module pattern, we'll create a module for each resource where we'll store all the server's logic for interacting with that resource. This logic module will be called the **controller** for that resource. So each resource in your database will have its own **model** and its own **controller**. You might later add `artists` or `record_labels` and give each one its own **controller** and **model**.

#### Modules

You've already seen this pattern when using models!

1. We `export` the relevant objects from a file.
1. We `require` those other files in an `index.js` to group them and then re-`export` everything inside one object.
1. When we need to use those objects, we `require` the directory containing those files (this reads `index.js` and retrieves that combined controller object).

The key here is to realize that `module.exports` always starts as an empty object `{}`.

> If you `module.exports = { key: value }`, you can export anything!
Let's look at an example:

```
├── server.js
└── models
├── index.js
├── quote.js
└── author.js
```

```js
// models/quote.js
// ... some stuff
var Quote = mongoose.model('Quote', QuoteSchema);
module.exports = Quote;
```

```js
// models/author.js
// ... some stuff
var Author = mongoose.model('Author', AuthorSchema);
module.exports = Author;
```

```js
// models/index.js
module.exports.Author = require('./author');
module.exports.Quote = require('./quote');
```

In the above `index.js` is exporting an object that looks like:

```js
{
Author: AuthorModel,
Quote: QuoteModel
}
```

Anywhere we import `models/index`, or even `models`, we get that object.

Let's take a look:

```js
server.js

var db = require('./models');

// later on you can
db.Author.save
db.Author.find
// etc
```

#### Applying this to controllers

Let's refactor `server.js` to use controllers. Here's our starting point:

```js
// server.js


app.get('/api/cards', function cardsIndex(req, res) {
// get all cards from the database
db.Card.find({}, function(err, allCards) {
// add some error checking here!
// respond, sending all cards back
res.json(allCards);
});
}
app.post('/api/cards', function cardsCreate(req, res) {
// make a new card with the form data from req.body
var newCard = new Card({
frontText: req.body.frontText,
backText: req.body.backText
});
}
app.get('/api/cards/:id', function cardsShow(req, res) {
// pull card id out of the request
var cardId = req.params.card_id;
// get single card from the database
db.Card.findOne({_id: cardId}, function(err, thatCard) {
// add some error checking here!
// respond, sending all cards back
res.json(thatCard);
});
}
```
> aww, check out those beautiful RESTful routes!
##### New file structure
```
├── server.js
└── controllers
├── index.js
└── cards.js
└── models
├── index.js
├── authors.js
└── cards.js
```
##### Refactor
```js
// controllers/cards.js

// send all card data back!
function index(req, res) {
// get all cards from the database
db.Card.find({}, function(err, allCards) {
// add some error checking here!
// respond, sending all cards back
res.json(allCards);
});
}

// create a card!
function create(req, res) {
// make a new card with the form data from req.body
var newCard = new Card({
frontText: req.body.frontText,
backText: req.body.backText
});
}

// send data for one card
function show(req, res) {
// pull card id out of the request
var cardId = req.params.card_id;
// get single card from the database
db.Card.findOne({_id: cardId}, function(err, thatCard) {
// add some error checking here!
// respond, sending all cards back
res.json(thatCard);
});
}

var publicMethods = {
index: index,
create: create,
show: show
}
module.exports = publicMethods;
```
Then, in `controllers/index.js`, we require and re-export.
> This step may seem odd right now, but when you have 15 controllers, you'll thank us.
```js
// controllers/index.js
module.exports.cards = require('./cards');
module.exports.someOtherController = require('./someOtherController');
```
Finally in `server.js` we connect these together:
```js
// server.js
var controllers = require('./controllers');

app.get('/api/cards', controllers.cards.index);
app.post('/api/cards', controllers.cards.create);
app.get('/api/cards/:id', controllers.cards.show);
```
![dancing semaphore man](https://media.giphy.com/media/rDroB384ydCvK/giphy.gif)
#### Wrap-up
Using this pattern, it becomes clear where to find the logic for each route, and your `server.js` file becomes much cleaner. It also helps us start using some conventional names for RESTful routes: index, show, create, etc. We also group the logic by **resource**, which makes it easier for future developers on the project to find what they need.
Your `server.js` file is effectively now a list of routes with the controller methods those routes use. When you work with other server architectures, you will run into very similar patterns. Knowing how this works will help you to adapt to other technologies you come across!
26 changes: 16 additions & 10 deletions docs/sprint1.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ Continually verify that your browser console is displaying the `app.js loaded!`
**Goal** display hard-coded data from `app.js` on `index.html`
Let's start on the outside and work our way in.

1. Open `index.html` and find the HTML for an **album**. Convert this into a handlebars template. Make sure you remove the data and place appropriate attributes in place instead. (You can get those attributes from the array of objects provided in `app.js`) Leave `div.albums` in place.
1. Open `index.html` and find the HTML for an **album**. Convert this into a handlebars template by adding the correct script tags. Make sure you remove the data and place appropriate attribute placeholders in place instead. (You can get those attributes from the array of objects provided in `app.js`) Leave `div.albums` in place.

1. Open `app.js` and edit the function `renderAlbums` to display one Album on the page.
You should use HTML just like what you just deleted. Build-up the HTML string and use jQuery to render it on the page.
1. Open `app.js` and edit the function `renderAlbum` to display one Album on the page. Use the handlebars template.

1. Run the function on document-ready and give it `sampleAlbums[0]` (just one album). Verify that the page looks right.

Expand All @@ -46,12 +45,13 @@ $(document).ready(function() {

1. Update your code to use **all** the sampleAlbums. Create a `sampleAlbums.forEach` in your document-ready - use this to call `renderAlbum` for each album.

> Note that we could use the templates `#each` method and pass it all the albums at once. However, we're planning to be able to add individual albums later on, so we'll need the ability to render each album individually. Having two separate render functions and templates (1 for individual albums, 1 for all albums) seems excessive at this point.
1. Later on we'll be clearing out the `div.albums` div. This will unfortunately also remove the script tag holding our handlebars template. Move that script down to the bottom of the document to preserve it.

At this point you should see all 4 hard-coded albums rendered on page.

<details><summary>Rendering all the albums with handlebars</summary>

```js
$(document).ready(function() {
console.log('app.js loaded!');
Expand Down Expand Up @@ -82,15 +82,19 @@ We're going to add the following _index_ route on our server:
GET /api/albums
```

1. Open server.js and create a new route for `/api/albums`
To better organize this app we're going to be using controllers to separate out logic for different `resources`. That means that when you create a route like `/api/albums/:id` you'll put the server code to handle that in a separate file; and reference it's function. [Example](controllers_example.md). If you take a look in `server.js` you'll see that we've already required the controllers for you.

1. Open server.js and create a new route for `/api/albums`. This route's callback should point to `controllers.albums.index`.

1. Serve the hard-coded albums in server.js on `/api/albums`. This is an API route, so let's send JSON.
1. Open `controllers/albums.js` and fill in the index function to return all albums.

1. Serve the hard-coded albums in albums.js on `/api/albums`. This is an API route, so let's send JSON.

1. In `app.js`, use `ajax` to get the albums. Render them on the page.

1. You can safely delete the hard-coded data in `app.js` now!

> The data in `server.js` and `app.js` is different; making it easy to see which data is being rendered on your page.
> The data in `albums.js` and `app.js` is different; making it easy to see which data is being rendered on your page.

## Step 3:
Expand All @@ -99,7 +103,7 @@ Let's setup the database now.

1. Use `npm` to install `mongoose`.

1. In `models/album.js` add a model for our albums. You should be able to determine the datatypes based on the sample data in the server.
1. In `models/album.js` add a model for our albums. You should be able to determine the datatypes based on the sample data we've been using.

1. Export Album in `models/album.js`

Expand Down Expand Up @@ -164,9 +168,11 @@ It usually means that `mongod` is not running.

Now that the database is seeded, let's continue and use it in our `/api/albums` route.

1. Require `./models` in `server.js`.
1. Delete the hard-coded server data.

1. Require `./models` in `controllers/albums.js`.

1. Edit the current `app.get('/api/albums', fun...` to access the database and pull all albums.
1. Edit the current `function index` to access the database and pull all albums.

1. Verify that you're getting the right data on your index page now. Your ajax should still work; but if the `keys` in the data have changed at all you'll have to resolve that.

Expand Down
14 changes: 11 additions & 3 deletions docs/sprint2.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,33 @@ Let's add a post route on the server now. We already know that POST is used to
POST /api/albums
```

1. In `server.js`, after the current `GET /api/albums` add a new route. Start by just `console.log`ing the output and returning the same data you received as json.
1. In `server.js`, after the current `GET /api/albums` add a new route. Create the appropriate function (`function create`) in your albums controller. Start by just `console.log`ing the output and returning the same data you received as json.

> Don't forget to export the `create` function from the controller or it won't be accessible in `server.js`.
1. Add body-parser to the server.

1. You can test this by either using AJAX from your browser's javascript console, or by using curl or postman.

curl:
```bash
curl -X POST http://localhost:3000/api/albums --data "name=Marble+House&textinput=The+Knife&releaseDate=2006&genres=electronica%2C+synth+pop%2C+trip+hop
curl -X POST http://localhost:3000/api/albums --data "name=Marble+House&textinput=The+Knife&releaseDate=2006&genres=electronica%2C+synth+pop%2C+trip+hop"
```

> Hint: If using postman to POST set the BODY type to x-www-form-urlencoded, then set key-value pairs.



## Step 4:

1. In the client-side JS, setup your form handler to make an AJAX post with the data.

1. Verify it's getting logged by the server when you submit.

1. On the server-side break the data we're getting in the `genre` field into an array.
1. On the server-side break the data we're getting in the `genre` field into an array.

> Hint: the `split` method may be handy here.
## Step 5:

Expand All @@ -88,4 +94,6 @@ curl:

![add new field button](/docs/assets/images/add_new_field_button.png)

<!-- note the above image works fine on github, but not in the editor -->

1. Convert the form to a modal and add a link to the right-side of the "Albums" header to open it!
Loading

0 comments on commit e256aa1

Please sign in to comment.