├── .gitignore ├── README.md ├── app ├── app.js └── scripts │ ├── controllers │ ├── index.js │ ├── main.js │ └── todo.js │ ├── directives │ ├── index.js │ └── todo.js │ └── services │ ├── data.js │ └── index.js ├── mock └── todos.json ├── package.json ├── public ├── index.html ├── mock │ └── todos.json ├── styles │ ├── checkbox-empty.svg │ ├── checkbox-filled.svg │ └── main.css ├── templates │ ├── navbar.html │ └── todo.html └── vendor │ └── angular.js ├── src ├── api │ └── index.js ├── app.js ├── database.js ├── models │ └── todo.js └── seed.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vendor.bundle.js 3 | todo.bundle.js 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Building a MEAN Application 3 | 4 | This is the course repo for [Building a MEAN Application](https://teamtreehouse.com/library/building-a-mean-application) on [Treehouse](https://teamtreehouse.com/) by Huston Hedinger and Ken Howard. 5 | 6 | ## Quick Start 7 | 8 | 1. Fork this repo 9 | 1. `git clone ` 10 | 1. Follow along with course and hack away 11 | 1. `git commit -am "commit some cool changes"` 12 | 1. `git push origin someCoolBranch` pushes changes to your fork. Great for sharing code and asking for comments on commits. 13 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular.module('todoListApp', []); 6 | 7 | require('./scripts/services'); 8 | require('./scripts/directives'); 9 | require('./scripts/controllers'); 10 | -------------------------------------------------------------------------------- /app/scripts/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular.module('todoListApp').controller('mainCtrl', require('./main')); 6 | angular.module('todoListApp').controller('todoCtrl', require('./todo')); 7 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function MainCtrl ($scope, dataService) { 4 | 5 | dataService.getTodos(function(response){ 6 | var todos = response.data.todos; 7 | $scope.todos = todos; 8 | }); 9 | 10 | $scope.addTodo = function() { 11 | $scope.todos.unshift({name: "This is a new todo.", 12 | completed: false}); 13 | }; 14 | 15 | } 16 | 17 | module.exports = MainCtrl; 18 | -------------------------------------------------------------------------------- /app/scripts/controllers/todo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function TodoCtrl ($scope, dataService) { 4 | 5 | $scope.deleteTodo = function(todo, index) { 6 | dataService.deleteTodo(todo).then(function() { 7 | $scope.todos.splice(index, 1); 8 | }); 9 | }; 10 | 11 | $scope.saveTodos = function() { 12 | var filteredTodos = $scope.todos.filter(function(todo){ 13 | if(todo.edited) { 14 | return todo 15 | }; 16 | }) 17 | dataService.saveTodos(filteredTodos) 18 | .finally($scope.resetTodoState()); 19 | }; 20 | 21 | $scope.resetTodoState = function() { 22 | $scope.todos.forEach(function(todo) { 23 | todo.edited = false; 24 | }); 25 | } 26 | } 27 | 28 | module.exports = TodoCtrl; 29 | -------------------------------------------------------------------------------- /app/scripts/directives/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular.module('todoListApp').directive('todo', require('./todo')); 6 | -------------------------------------------------------------------------------- /app/scripts/directives/todo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function ToDoDirective () { 4 | return { 5 | templateUrl: 'templates/todo.html', 6 | replace: true, 7 | controller: 'todoCtrl' 8 | } 9 | } 10 | 11 | module.exports = ToDoDirective; 12 | -------------------------------------------------------------------------------- /app/scripts/services/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function DataService ($http, $q) { 4 | 5 | this.getTodos = function(cb) { 6 | $http.get('/api/todos').then(cb); 7 | }; 8 | 9 | this.deleteTodo = function(todo) { 10 | if (!todo._id) { 11 | return $q.resolve(); 12 | } 13 | return $http.delete('/api/todos/' + todo._id).then(function() { 14 | console.log("I deleted the " + todo.name + " todo!"); 15 | }); 16 | }; 17 | 18 | this.saveTodos = function(todos) { 19 | var queue = []; 20 | todos.forEach(function(todo) { 21 | var request; 22 | if(!todo._id) { 23 | request = $http.post('/api/todos', todo); 24 | } else { 25 | request = $http.put('/api/todos/' + todo._id, todo).then(function(result) { 26 | todo = result.data.todo; 27 | return todo; 28 | }); 29 | } 30 | queue.push(request); 31 | }); 32 | return $q.all(queue).then(function(results) { 33 | console.log("I saved " + todos.length + " todos!"); 34 | }); 35 | }; 36 | 37 | } 38 | 39 | module.exports = DataService; 40 | -------------------------------------------------------------------------------- /app/scripts/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular.module('todoListApp').service('dataService', require('./data')); 6 | -------------------------------------------------------------------------------- /mock/todos.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "reply to dad's tech question email from two weeks ago"}, 3 | {"name": "reveal identity of the left shark"}, 4 | {"name": "sing karaoke like nobody's watching"}, 5 | {"name": "reply to dad's follow up email after the first reply"} 6 | ] 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mean-todo", 3 | "version": "1.0.0", 4 | "description": "This is the course repo for [Building a MEAN Application](https://teamtreehouse.com/library/building-a-mean-application) on [Treehouse](https://teamtreehouse.com/) by Huston Hedinger and Ken Howard.", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/treehouse/mean-todo.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/treehouse/mean-todo/issues" 17 | }, 18 | "homepage": "https://github.com/treehouse/mean-todo#readme", 19 | "dependencies": { 20 | "angular": "^1.4.8", 21 | "body-parser": "1.14.2", 22 | "express": "4.13.4", 23 | "mongoose": "4.4.5" 24 | }, 25 | "devDependencies": { 26 | "webpack": "1.12.14" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

My TODOs!

13 | 14 |
15 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/mock/todos.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "clean the house"}, 3 | {"name": "water the dog"}, 4 | {"name": "feed the lawn"}, 5 | {"name": "pay dem bills"}, 6 | {"name": "run"}, 7 | {"name": "swim"} 8 | ] 9 | -------------------------------------------------------------------------------- /public/styles/checkbox-empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/styles/checkbox-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /public/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #2d3339; 3 | font-family: "Varela Round"; 4 | text-align: center; 5 | background: #edeff0; 6 | font-size: 16px; } 7 | 8 | a { 9 | color: #3f8abf; 10 | text-decoration: none; } 11 | a:hover { 12 | color: #65a1cc; } 13 | 14 | .list { 15 | background: #fff; 16 | width: 80%; 17 | min-width: 500px; 18 | margin: 80px auto 0; 19 | border-top: 40px solid #5a6772; 20 | text-align: left; } 21 | .list .item { 22 | border-bottom: 2px solid #edeff0; 23 | padding: 17px 0 18px 17px; } 24 | .list .item label { 25 | padding-left: 5px; 26 | cursor: pointer; } 27 | .list .item .editing-label { 28 | margin-left: 5px; 29 | font-family: "Varela Round"; 30 | border-radius: 2px; 31 | border: 2px solid #a7b9c4; 32 | font-size: 16px; 33 | padding: 15px 0 15px 10px; 34 | width: 60%; } 35 | .list .item .actions { 36 | float: right; 37 | margin-right: 20px; } 38 | .list .item .actions .delete { 39 | color: #ed5a5a; 40 | margin-left: 10px; } 41 | .list .item .actions .delete:hover { 42 | color: #f28888; } 43 | .list .item.editing-item .actions { 44 | margin-top: 17px; } 45 | .list .edited label:after { 46 | content: " edited"; 47 | text-transform: uppercase; 48 | color: #a7b9c4; 49 | font-size: 14px; 50 | padding-left: 5px; } 51 | .list .completed label { 52 | color: #a7b9c4; 53 | text-decoration: line-through; } 54 | .list .add { 55 | padding: 25px 0 27px 25px; } 56 | 57 | input[type="checkbox"] { 58 | display:none; 59 | } 60 | input[type="checkbox"] + span { 61 | display:inline-block; 62 | width:19px; 63 | height:19px; 64 | margin:-1px 4px 0 0; 65 | vertical-align:middle; 66 | background:url(checkbox-empty.svg) left top no-repeat; 67 | cursor:pointer; 68 | } 69 | input[type="checkbox"]:checked + span { 70 | background:url(checkbox-filled.svg) left top no-repeat; 71 | } 72 | 73 | nav ul { 74 | text-align: left; 75 | list-style: none; 76 | margin-left: 0; 77 | padding: 0; 78 | } 79 | nav ul li { 80 | display: inline-block; 81 | } 82 | nav ul li a { 83 | display: block; 84 | margin: 0; 85 | padding: 10px 15px; 86 | color: #444; 87 | background-color: #adadad; 88 | } 89 | -------------------------------------------------------------------------------- /public/templates/navbar.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /public/templates/todo.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | Save 8 | Delete 9 |
10 |
-------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var Todo = require('../models/todo'); 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/todos', function(req, res) { 9 | Todo.find({}, function(err, todos) { 10 | if (err) { 11 | return res.status(500).json({ message: err.message }); 12 | } 13 | res.json({ todos: todos }); 14 | }); 15 | }); 16 | 17 | router.post('/todos', function(req, res) { 18 | var todo = req.body; 19 | Todo.create(todo, function(err, todo) { 20 | if (err) { 21 | return res.status(500).json({ err: err.message }); 22 | } 23 | res.json({ 'todo': todo, message: 'Todo Created' }); 24 | }); 25 | }); 26 | 27 | router.put('/todos/:id', function(req, res) { 28 | var id = req.params.id; 29 | var todo = req.body; 30 | if (todo && todo._id !== id) { 31 | return res.status(500).json({ err: "Ids don't match!" }); 32 | } 33 | Todo.findByIdAndUpdate(id, todo, {new: true}, function(err, todo) { 34 | if (err) { 35 | return res.status(500).json({ err: err.message }); 36 | } 37 | res.json({ 'todo': todo, message: 'Todo Updated' }); 38 | }); 39 | }); 40 | 41 | router.delete('/todos/:id', function(req, res) { 42 | var id = req.params.id; 43 | Todo.findByIdAndRemove(id, function(err, result) { 44 | if (err) { 45 | return res.status(500).json({ err: err.message }); 46 | } 47 | res.json({ message: 'Todo Deleted' }); 48 | }); 49 | }); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var parser = require('body-parser'); 5 | var router = require('./api'); 6 | 7 | var app = express(); 8 | 9 | require('./database'); 10 | require('./seed'); 11 | 12 | app.use('/', express.static('public')); 13 | app.use(parser.json()); 14 | 15 | app.use('/api', router); 16 | 17 | app.listen(3000, function() { 18 | console.log("The server is running on port 3000!"); 19 | }); 20 | -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | 5 | mongoose.connect('mongodb://localhost/mean-todo', function(err) { 6 | if (err) { 7 | console.log('Failed connecting to MongoDB!'); 8 | } else { 9 | console.log('Successfully connected to MongoDB!'); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/models/todo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | 5 | // todo.name 6 | // todo.completed 7 | 8 | var todoSchema = new mongoose.Schema({ 9 | name: String, 10 | completed: Boolean 11 | }); 12 | 13 | var model = mongoose.model('Todo', todoSchema); 14 | 15 | module.exports = model; 16 | -------------------------------------------------------------------------------- /src/seed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Todo = require('./models/todo'); 4 | 5 | var todos = [ 6 | 'Feed the dog', 7 | 'Walk the kids', 8 | 'Water the trees' 9 | ]; 10 | 11 | todos.forEach(function (todo, index) { 12 | Todo.find({ 'name': todo }, function(err, todos) { 13 | if (!err && !todos.length) { 14 | Todo.create({ completed: false, name: todo }); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'), 2 | path = require('path'); 3 | 4 | module.exports = { 5 | context: __dirname + '/app', 6 | entry: { 7 | app: './app.js', 8 | vendor: ['angular'] 9 | }, 10 | output: { 11 | path: __dirname + '/public/scripts', 12 | filename: 'todo.bundle.js' 13 | }, 14 | plugins: [ 15 | new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") 16 | ] 17 | }; 18 | --------------------------------------------------------------------------------