Skip to content

Latest commit

 

History

History
482 lines (461 loc) · 14.4 KB

medium-difficulty.md

File metadata and controls

482 lines (461 loc) · 14.4 KB

Step-By-Step Instructions (with some code):


Step 1: Navigate to the parent directory where you want to create your app. Use the express generator to create your app's skeleton:

express -e cuisine-catalog


Step 2: Navigate into the directory and open in VS Code.


Step 3: Open a terminal in VS Code. Change the name of app.js to server.js.


Step 4: Adjust the /bin/www file to reflect those changes so that your server will start properly:

// Change var app = require('../app'); to:
var app = require('../server');


Step 5: Create directories for the model, controller, database (config), and views, then add the corresponding files within each. (views/cuisine models/cuisine.js controllers/cuisine.js config/database.js)


Step 6: Install node modlues and mongoose using npm:

npm install
npm install mongoose


Step 7: Split the terminal at the bottom of VS Code to open a second window for monitoring the server. Start the server using nodemon and test it out.

When you browse to 'localhost:3000' you should see the generic express template.


Step 8: Configure the database connection in database.js:

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/cuisine',
    {useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true}
);

var db = mongoose.connection;
 
db.on('connected', function() {
    console.log(`Connected to MongoDB at ${db.host}:${db.port}`);
});


Step 9: Require the database in the server:

require('./config/database');


Step 10: Define the schema in the model:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var cuisineSchema = new Schema({
    title: {type: String, required: true},
    calories: {type: Number},
    mealType: {type: String, enum: ['Snack', 'Breakfast', 'Lunch', 'Dinner', 'Dessert']},
    recipeUrl: {type: String},
    ingredients: [String]
}, {
    timestamps: true
   }
);

module.exports = mongoose.model('Cuisine', cuisineSchema);


Step 11: Use the terminal to rename users.js --> cuisine.js.


Step 12: Adjust the server to reflect the changes from the previous step:

// Change var userRouter = require('./routes/user'); to:
var cuisineRouter = require('./routes/cuisine');
// Change app.use('/user', userRouter); to:
app.use('/cuisine', cuisineRouter);


Step 13: Configure your router and define a route to create a new cuisine:

var express = require('express');
var router = express.Router();
var cuisineCtrl = require('../controllers/cuisine');

router.get('/new', cuisineCtrl.new);

module.exports = router;


Step 14: Add the controller:

var Cuisine = require('../models/cuisine');

module.exports = {
    new: newCuisine
}

function newCuisine(req, res) {
    res.render('cuisine/new');
}


Step 15: Create a 'new' view page.


Step 16: Create a form within the 'new' view for the user to add an item:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Add Cuisine</title>
</head>
<body>
    <h2>Enter new cuisine:</h2><br>
    <form action="/cuisine" method="POST">
        <label>Recipe Name:
            <input type="text" name="title">
        </label><br><br>
        <label>Meal Type (select one):
            <select name="mealType">
                <option value="Snack">Snack</option>
                <option value="Breakfast">Breakfast</option>
                <option value="Lunch">Lunch</option>
                <option value="Dinner">Dinner</option>
                <option value="Dessert">Dessert</option>
            </select>
        </label><br><br>
        <label>Calories (per serving):
            <input type="text" name="calories">
        </label><br><br>
        <label>Ingredients (separate each with a comma):
            <textarea id="ingredientBox" rows="1" type="text" name="ingredients"></textarea>
            </label><br><br>
        <label>Link to recipe:
            <input id="urlInput" type="text" name="recipeUrl">
        </label><br><br>

        <button type="submit" class="btn btn-success">Add</button>
    </form>
</body>
</html>


Step 17: Add minimal CSS in public/stylesheets/style.css:

#ingredientBox {
  width: 250px;
}

#ingredientBox:focus {
  height: 100px;
}

#urlInput {
  width: 430px;
}


Step 18: Define the POST route:

router.post('/', cuisineCtrl.create);


Step 19: Create a controller for the route:

module.exports = {
    new: newCuisine,
    create
}
...
...
...

function create(req, res) {
    req.body.ingredients = req.body.ingredients.replace(/\s*,\s*/g, ',');
    if (req.body.ingredients) req.body.ingredients = req.body.ingredients.split(',');
    var cuisine = new Cuisine(req.body);
    cuisine.save(function(err) {
        if (err) return res.render('cuisine/new');
        console.log('Added cuisine to database: ' + cuisine);
        res.redirect('/cuisine');
    });
}

Step 20: Navigate to 'localhost:3000/new' in your browser. Fill out the fields and hit the 'Add' button. Check to make sure the POST request shows up in the terminal currently running the server:

new-post


Step 21: Define a route for the index page.

router.get('/', cuisineCtrl.index);


Step 22: Add the corresponding controller.

function index(req, res) {
    Cuisine.find({}, function(err, cuisine) {
        if (err) {
            console.log(err);
        } else {
        res.render('cuisine/index', {title: 'Cuisine List', cuisine});
        }
    });
}


Step 23: Use the terminal to create an 'index' view.


Step 24: Add a button to add, along with a simple table using ejs in the newly created index.ejs:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Cuisine List</title>
</head>
<body>
    <h2>Cuisine List</h2><br>
    <table id="cuisineList">
        <thead>
            <tr>
                <th>Recipe</th>
                <th>Meal<br>Type</th>
                <th>Recipe<br>URL</th>
            </tr>
        </thead>
        <tbody>
            <% cuisine.forEach(function(c) { %>
                <tr>
                    <td><%= c.title %></td>
                    <td><%= c.mealType %></td>
                    <td><a href="http://<%= c.recipeUrl %>">See Recipe</a></td>
                    <td><a href="/cuisine/<%= c._id %>">Details</a></td>
                </tr>
            <% }) %>
        </tbody>
    </table>
    <a href="/cuisine/new">Add Cuisine</a>
</body>
</html>


Step 25: Add some CSS to make it look a little nicer:

table thead th {
  padding: 5px;
  border-bottom: 2px solid #424748;
}

table td {
  padding: 10px;
  text-align: center;
}

#cuisineList td:nth-child(2), #cuisineList td:nth-child(3){
  min-width: 100px;
}


Step 26: Change the default 'localhost:3000' landing page to redirect to 'localhost:3000/cuisine' now that it is properly displaying all items. Do this by changing the route.

router.get('/', function(req, res, next) {
  res.redirect('cuisine/')
  
});


Step 27: Add a route for the 'Details' button that was just created.

router.get('/:id', cuisineCtrl.show);


Step 28: Add the controller for the new route.

function show(req, res) {
    Cuisine.findById(req.params.id, function(err, cuisine){
        if (err) {
            console.log(err);
        } else {
        res.render('cuisine/show', {title: 'Cuisine Details', cuisine});
        }
    });
}


Step 29: Using the terminal, create a 'show' view page.


Step 30: Write the HTML/ejs to display the data for an individual item in the show view:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Cuisine Details</title>
</head>
<body>
    <h2>Cuisine Details</h2><br>
    <div class="details-bold">Recipe Name:</div>
    <div><%= cuisine.title %></div>
    <div class="details-bold">Meal Type:</div>
    <div><%= cuisine.mealType %></div>
    <div class="details-bold">Calories:</div>
    <div><%= cuisine.calories %></div>
    <div class="details-bold">Ingredients:</div>
    <%= cuisine.ingredients.map(i => i).join(', ') %>
    <div class="details-bold">Recipe Link:</div>
    <div><%= cuisine.recipeUrl %></div>
</body>
</html>


Step 31: Add some CSS to clean up the display:

.details-bold {
  font-weight: bold;
  text-decoration: underline;
}


Step 31.5: Use npm to install the method-override package:

npm i method-override

...then require it in server.js:

let methodOverride = require('method-override);

...and add it to the middleware:

app.use(methodOverride('_method'));


Step 32: Add a button to handle deletion:

<tr>
    <td><%= c.title %></td>
    <td><%= c.mealType %></td>
    <td><a href="http://<%= c.recipeUrl %>">See Recipe</a></td>
    <td><a href="/cuisine/<%= c._id %>">Details</a></td>
    <!-- Add the following form here: -->
    <form action="/cuisine/<%= c._id %>?_method=DELETE" method="POST">
    <td><button type="submit" class="btn btn-danger">X</button></td>
    </form>
</tr>


Step 33: Add the route to handle deletions.

router.delete('/:id', cuisineCtrl.delete);


Step 34: Add the corresponding controller.

function deleteOne(req, res) {
    Cuisine.findByIdAndDelete(req.params.id, function(err, cuisine){
        if (err) {
            console.log(err);
        } else {
        console.log('deleting: ' + cuisine);
        }
    })
    res.render('/cuisine')
}


Step 35: Add a button to handle updating an item:

<!-- ... -->
<div class="details-bold">Recipe Link:</div>
    <div><%= cuisine.recipeUrl %></div>
    <!-- Add the button here: -->
    <form action="/cuisine/update/<%= cuisine._id %>?_method=PUT">
        <button type="submit" class="btn btn-warning">Edit</button>
    </form>
</body>


Step 36: Add the route to handle showing the update page.

router.put('/update/:id', cuisineCtrl.showUpdate);


Step 37: Add the corresponding controller.

function showUpdate(req, res) {
    Cuisine.findById(req.params.id, function(err, cuisine) {
        if (err) {
            console.log(err);
        } else {
        res.render('cuisine/update', {title: 'Update Cuisine', cuisine});
        }
    });
    
}


Step 38: Using the terminal, create an 'update' view.


Step 39: Copy the form over from show.ejs to update.ejs, but modify it to auto-populate the values of each field with the current record's info:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>Add Cuisine</title>
</head>
<body>
    <h2>Update cuisine:</h2><br>
    <form action="/cuisine/update/<%= cuisine._id %>" method="POST">
        <label>Recipe Name:
            <input type="text" name="title" value="<%= cuisine.title %>">
        </label><br><br>
        <label>Meal Type (select one):
            <select name="mealType">
                <option selected ><%= cuisine.mealType %></option>
                <option value="Snack">Snack</option>
                <option value="Breakfast">Breakfast</option>
                <option value="Lunch">Lunch</option>
                <option value="Dinner">Dinner</option>
                <option value="Dessert">Dessert</option>
            </select>
        </label><br><br>
        <label>Calories (per serving):
            <input type="text" name="calories" value="<%= cuisine.calories %>">
        </label><br><br>
        <label>Ingredients (separate each with a comma):
            <textarea id="ingredientBox" rows="1" type="text" name="ingredients"><%= cuisine.ingredients.map(i => i).join(', ') %></textarea>
            </label><br><br>
        <label>Link to recipe:
            <input id="urlInput" type="text" name="recipeUrl" value="<%= cuisine.recipeUrl %>">
        </label><br><br>

        <button type="submit" class="btn btn-success">Update</button>
    </form>
</body>
</html>


Step 40: Add the POST route to send the record to be updated.

router.post('/update/:id', cuisineCtrl.update);


Step 41: Add the corresponding controller.

function update(req, res) {
    console.log(req.body);
    req.body.ingredients = req.body.ingredients.replace(/\s*,\s*/g, ',');
    if (req.body.ingredients) req.body.ingredients = req.body.ingredients.split(',');
    Cuisine.findByIdAndUpdate(req.params.id,
        {
            title: req.body.title,
            calories: req.body.calories,
            mealType: req.body.mealType,
            recipeUrl: req.body.recipeUrl,
            ingredients: req.body.ingredients
        },
        {new: true},
        function(err, response) {
            if (err) {
                console.log(err);
            } else {
                res.redirect('/cuisine')
            }
        })
}


Step 42: Profit.