├── README.md ├── seed ├── .editorconfig ├── css │ └── style.css ├── img │ └── favicon.ico ├── index.html └── js │ └── libs │ └── angular.js └── solution ├── .editorconfig ├── css └── style.css ├── img └── favicon.ico ├── index.html └── js ├── app.js ├── libs └── angular.js ├── todo.controller.js ├── todo.directive.js ├── todo.service.js └── todoAutofocus.directive.js /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Ultimate Angular: Fundamental Starter App Seed 4 | 5 | > This repo serves as the seed project for the Ultimate Angular 1.x Fundamentals course as well as the final solution in `solution` directory. Come and [learn Angular 1.x](https://ultimatecourses.com/courses/angularjs), latest features, architecture and components! 6 | 7 | # Starter Seed 8 | 9 | A starter project for the Ultimate Angular 1.x [Fundamentals course](https://ultimatecourses.com/learn/angularjs-fundamentals). 10 | 11 | ### Running this app 12 | 13 | All you need to do is run a local server, if you're on OSX you can run a python command from the command line: 14 | 15 | ``` 16 | cd 17 | python -m SimpleHTTPServer 18 | ``` 19 | 20 | If you're on Windows, you can also download and run python, or if it's easier you can setup a local server by using your favourite IDE. 21 | 22 | ### Course members 23 | 24 | I'd love to see what you've built after mastering the core of Angular, consider submitting a PR to this repo to a new branch name (under your chosing). That way, solutions or things you've built after this course would be amazing to see! I wish you luck in your endeavours. 25 | 26 | > Non-course members (this project is open source on GitHub) 27 | 28 | Checkout the `solution` directory for an awesome Todo App. If you want to build exactly that and learn the mechanics behind it (and much more) then dive into [Ultimate AngularJS: Starter Course](https://app.ultimatecourses.com/course/angularjs-fundamentals). 29 | -------------------------------------------------------------------------------- /seed/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /seed/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { 10 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 11 | color: #5C5C5C; 12 | padding: 50px; 13 | } 14 | 15 | ul { 16 | list-style: none; 17 | } 18 | 19 | .todo { 20 | position: relative; 21 | width: 350px; 22 | margin: 0 auto; 23 | padding: 0 0 12px; 24 | background: #fff; 25 | border: 1px solid; 26 | border-color: #dfdcdc #d9d6d6 #ccc; 27 | border-radius: 2px; 28 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 29 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 30 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 31 | } 32 | 33 | .todo:before, .todo:after { 34 | content: ''; 35 | position: absolute; 36 | z-index: -1; 37 | height: 4px; 38 | background: #fff; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | .todo:after { 44 | left: 0; 45 | right: 0; 46 | bottom: -3px; 47 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 48 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 49 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todo:before { 53 | left: 2px; 54 | right: 2px; 55 | bottom: -5px; 56 | border-color: #c4c4c4; 57 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 58 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 59 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 60 | } 61 | .todo__form { 62 | padding: 0 0 0 40px; 63 | } 64 | .todo__form input { 65 | border: 0; 66 | padding: 15px 10px; 67 | width: 100%; 68 | outline: 0; 69 | font-size: 16px; 70 | } 71 | .todo__remaining { 72 | padding: 10px 15px 0px 50px; 73 | font-style: italic; 74 | font-size: 12px; 75 | } 76 | .todo__list { 77 | border-top: 1px solid #e6ebed; 78 | } 79 | .todo__list:before { 80 | content: ''; 81 | width: 3px; 82 | z-index: 2; 83 | border: 1px solid #f2e3df; 84 | border-width: 0 1px; 85 | position: absolute; 86 | top: 0px; 87 | bottom: 0px; 88 | left: 35px; 89 | } 90 | 91 | .todo__list li { 92 | position: relative; 93 | padding: 0 15px 0 50px; 94 | line-height: 21px; 95 | font-size: 12px; 96 | color: #8b8f97; 97 | border-bottom: 1px solid #e6ebed; 98 | } 99 | .todo__list li:hover a { 100 | opacity: 1; 101 | } 102 | .todo__list li a { 103 | text-decoration: none; 104 | color: #6bb3ca; 105 | font-size: 22px; 106 | cursor: pointer; 107 | transition: opacity 0.2s ease-in-out; 108 | opacity: 0; 109 | position: absolute; 110 | top: 8px; 111 | right: 10px; 112 | } 113 | .todo__list li p, 114 | .todo__list li input { 115 | line-height: 40px; 116 | margin: 0; 117 | border: 0; 118 | outline: 0; 119 | width: 100%; 120 | } 121 | 122 | input[type=checkbox] { 123 | display: none; 124 | } 125 | 126 | input[type=checkbox]:checked + .toggle:after { 127 | opacity: 1; 128 | } 129 | 130 | .todo__list .toggle { 131 | display: block; 132 | height: 35px; 133 | width: 35px; 134 | position: absolute; 135 | top: 0px; 136 | bottom: 0px; 137 | left: 0px; 138 | text-indent: 100%; 139 | overflow: hidden; 140 | cursor: pointer; 141 | } 142 | 143 | .toggle:after { 144 | content: ''; 145 | position: absolute; 146 | width: 7px; 147 | height: 3px; 148 | background: transparent; 149 | top: 14px; 150 | left: 13px; 151 | border: 2px solid #aaa; 152 | border-top: none; 153 | border-right: none; 154 | -webkit-transform: rotate(-45deg); 155 | -moz-transform: rotate(-45deg); 156 | -o-transform: rotate(-45deg); 157 | -ms-transform: rotate(-45deg); 158 | transform: rotate(-45deg); 159 | opacity: 0; 160 | } 161 | 162 | .todo__list .toggle:before { 163 | content: ''; 164 | width: 15px; 165 | height: 15px; 166 | background: #faf9f9; 167 | border: 1px solid #6bb3ca; 168 | border-radius: 2px; 169 | position: absolute; 170 | top: 9px; 171 | left: 9px; 172 | -webkit-box-shadow: 0 1px 1px #dfecf4; 173 | -moz-box-shadow: 0 1px 1px #dfecf4; 174 | box-shadow: 0 1px 1px #dfecf4; 175 | } 176 | 177 | .todo__list .toggle:hover:before { 178 | -webkit-box-shadow: 0 0 3px #6bb3ca; 179 | -moz-box-shadow: 0 0 3px #6bb3ca; 180 | box-shadow: 0 0 3px #6bb3ca; 181 | } 182 | 183 | .todo__list .done .toggle:before, .todo__list .toggle:active:before { 184 | border-color: #c0c0c0 #ccc #d8d8d8; 185 | -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 186 | -moz-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 187 | box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 188 | } 189 | .todo-controls { 190 | margin: 0 15px 12px 50px; 191 | height: 12px; 192 | } 193 | 194 | .todo-controls li { 195 | float: left; 196 | } 197 | 198 | .todo-controls li + li { margin-left: 10px } 199 | 200 | .todo-controls .right { float: right } 201 | 202 | .todo-controls a { 203 | display: block; 204 | margin: 0; 205 | opacity: .6; 206 | } 207 | 208 | .todo-controls a:hover { opacity: 1 } 209 | 210 | .todo-pagination { 211 | margin: 12px 12px 0 50px; 212 | height: 22px; 213 | } 214 | -------------------------------------------------------------------------------- /seed/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/ultimate-angular-starter-seed/9c66f829e3aa2eb5d196726cc936affb77f38359/seed/img/favicon.ico -------------------------------------------------------------------------------- /seed/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /solution/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /solution/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { 10 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 11 | color: #5C5C5C; 12 | padding: 50px; 13 | } 14 | 15 | ul { 16 | list-style: none; 17 | } 18 | 19 | .todo { 20 | position: relative; 21 | width: 350px; 22 | margin: 0 auto; 23 | padding: 0 0 12px; 24 | background: #fff; 25 | border: 1px solid; 26 | border-color: #dfdcdc #d9d6d6 #ccc; 27 | border-radius: 2px; 28 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 29 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 30 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 31 | } 32 | 33 | .todo:before, .todo:after { 34 | content: ''; 35 | position: absolute; 36 | z-index: -1; 37 | height: 4px; 38 | background: #fff; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | .todo:after { 44 | left: 0; 45 | right: 0; 46 | bottom: -3px; 47 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 48 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 49 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todo:before { 53 | left: 2px; 54 | right: 2px; 55 | bottom: -5px; 56 | border-color: #c4c4c4; 57 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 58 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 59 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); 60 | } 61 | .todo__form { 62 | padding: 0 0 0 40px; 63 | } 64 | .todo__form input { 65 | border: 0; 66 | padding: 15px 10px; 67 | width: 100%; 68 | outline: 0; 69 | font-size: 16px; 70 | } 71 | .todo__remaining { 72 | padding: 10px 15px 0px 50px; 73 | font-style: italic; 74 | font-size: 12px; 75 | } 76 | .todo__list { 77 | border-top: 1px solid #e6ebed; 78 | } 79 | .todo__list:before { 80 | content: ''; 81 | width: 3px; 82 | z-index: 2; 83 | border: 1px solid #f2e3df; 84 | border-width: 0 1px; 85 | position: absolute; 86 | top: 0px; 87 | bottom: 0px; 88 | left: 35px; 89 | } 90 | 91 | .todo__list li { 92 | position: relative; 93 | padding: 0 15px 0 50px; 94 | line-height: 21px; 95 | font-size: 12px; 96 | color: #8b8f97; 97 | border-bottom: 1px solid #e6ebed; 98 | } 99 | .todo__list li:hover a { 100 | opacity: 1; 101 | } 102 | .todo__list li a { 103 | text-decoration: none; 104 | color: #6bb3ca; 105 | font-size: 22px; 106 | cursor: pointer; 107 | transition: opacity 0.2s ease-in-out; 108 | opacity: 0; 109 | position: absolute; 110 | top: 8px; 111 | right: 10px; 112 | } 113 | .todo__list li p, 114 | .todo__list li input { 115 | line-height: 40px; 116 | margin: 0; 117 | border: 0; 118 | outline: 0; 119 | width: 100%; 120 | } 121 | 122 | input[type=checkbox] { 123 | display: none; 124 | } 125 | 126 | input[type=checkbox]:checked + .toggle:after { 127 | opacity: 1; 128 | } 129 | 130 | .todo__list .toggle { 131 | display: block; 132 | height: 35px; 133 | width: 35px; 134 | position: absolute; 135 | top: 0px; 136 | bottom: 0px; 137 | left: 0px; 138 | text-indent: 100%; 139 | overflow: hidden; 140 | cursor: pointer; 141 | } 142 | 143 | .toggle:after { 144 | content: ''; 145 | position: absolute; 146 | width: 7px; 147 | height: 3px; 148 | background: transparent; 149 | top: 14px; 150 | left: 13px; 151 | border: 2px solid #aaa; 152 | border-top: none; 153 | border-right: none; 154 | -webkit-transform: rotate(-45deg); 155 | -moz-transform: rotate(-45deg); 156 | -o-transform: rotate(-45deg); 157 | -ms-transform: rotate(-45deg); 158 | transform: rotate(-45deg); 159 | opacity: 0; 160 | } 161 | 162 | .todo__list .toggle:before { 163 | content: ''; 164 | width: 15px; 165 | height: 15px; 166 | background: #faf9f9; 167 | border: 1px solid #6bb3ca; 168 | border-radius: 2px; 169 | position: absolute; 170 | top: 9px; 171 | left: 9px; 172 | -webkit-box-shadow: 0 1px 1px #dfecf4; 173 | -moz-box-shadow: 0 1px 1px #dfecf4; 174 | box-shadow: 0 1px 1px #dfecf4; 175 | } 176 | 177 | .todo__list .toggle:hover:before { 178 | -webkit-box-shadow: 0 0 3px #6bb3ca; 179 | -moz-box-shadow: 0 0 3px #6bb3ca; 180 | box-shadow: 0 0 3px #6bb3ca; 181 | } 182 | 183 | .todo__list .done .toggle:before, .todo__list .toggle:active:before { 184 | border-color: #c0c0c0 #ccc #d8d8d8; 185 | -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 186 | -moz-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 187 | box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); 188 | } 189 | .todo-controls { 190 | margin: 0 15px 12px 50px; 191 | height: 12px; 192 | } 193 | 194 | .todo-controls li { 195 | float: left; 196 | } 197 | 198 | .todo-controls li + li { margin-left: 10px } 199 | 200 | .todo-controls .right { float: right } 201 | 202 | .todo-controls a { 203 | display: block; 204 | margin: 0; 205 | opacity: .6; 206 | } 207 | 208 | .todo-controls a:hover { opacity: 1 } 209 | 210 | .todo-pagination { 211 | margin: 12px 12px 0 50px; 212 | height: 22px; 213 | } 214 | -------------------------------------------------------------------------------- /solution/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/ultimate-angular-starter-seed/9c66f829e3aa2eb5d196726cc936affb77f38359/solution/img/favicon.ico -------------------------------------------------------------------------------- /solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /solution/js/app.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app', []); 3 | -------------------------------------------------------------------------------- /solution/js/todo.controller.js: -------------------------------------------------------------------------------- 1 | function TodoController(TodoService) { 2 | var ctrl = this; 3 | ctrl.newTodo = ''; 4 | ctrl.list = []; 5 | function getTodos() { 6 | TodoService 7 | .retrieve() 8 | .then(function (response) { 9 | ctrl.list = response; 10 | }); 11 | } 12 | ctrl.addTodo = function () { 13 | if (!ctrl.newTodo) { 14 | return; 15 | } 16 | TodoService 17 | .create({ 18 | title: ctrl.newTodo, 19 | completed: false 20 | }) 21 | .then(function (response) { 22 | ctrl.list.unshift(response); 23 | ctrl.newTodo = ''; 24 | }); 25 | }; 26 | ctrl.removeTodo = function (item, index) { 27 | TodoService 28 | .remove(item) 29 | .then(function (response) { 30 | ctrl.list.splice(index, 1); 31 | }); 32 | }; 33 | ctrl.updateTodo = function (item, index) { 34 | if (!item.title) { 35 | ctrl.removeTodo(item, index); 36 | return; 37 | } 38 | TodoService 39 | .update(item); 40 | }; 41 | ctrl.getRemaining = function () { 42 | return ctrl.list.filter(function (item) { 43 | return !item.completed; 44 | }); 45 | }; 46 | ctrl.toggleState = function (item) { 47 | TodoService 48 | .update(item) 49 | .then(function () { 50 | 51 | }, function () { 52 | item.completed = !item.completed; 53 | }); 54 | }; 55 | getTodos(); 56 | } 57 | 58 | angular 59 | .module('app') 60 | .controller('TodoController', TodoController); 61 | -------------------------------------------------------------------------------- /solution/js/todo.directive.js: -------------------------------------------------------------------------------- 1 | function todoApp() { 2 | return { 3 | restrict: 'E', 4 | controller: 'TodoController as todo', 5 | template: ` 6 |
7 |
8 | 9 |
10 | 33 |

34 | 35 | You have {{ todo.getRemaining().length }} of {{ todo.list.length }} items todo! 36 | 37 | 38 | You are super productive! 39 | 40 |

41 |
42 | ` 43 | }; 44 | } 45 | 46 | angular 47 | .module('app') 48 | .directive('todoApp', todoApp); 49 | -------------------------------------------------------------------------------- /solution/js/todo.service.js: -------------------------------------------------------------------------------- 1 | function TodoService($http) { 2 | 3 | var API = '//jsonplaceholder.typicode.com/todos/'; 4 | 5 | function create(todo) { 6 | return $http.post(API, todo).then(function (response) { 7 | return response.data; 8 | }); 9 | } 10 | function retrieve() { 11 | return $http.get(API).then(function (response) { 12 | return response.data.splice(0, 10); 13 | }); 14 | } 15 | function update(todo) { 16 | return $http.put(API + todo.id).then(function (response) { 17 | return response.data; 18 | }); 19 | } 20 | function remove(todo) { 21 | return $http.delete(API + todo.id).then(function (response) { 22 | return response.data; 23 | }); 24 | } 25 | 26 | return { 27 | create: create, 28 | retrieve: retrieve, 29 | update: update, 30 | remove: remove, 31 | }; 32 | } 33 | 34 | angular 35 | .module('app') 36 | .factory('TodoService', TodoService); 37 | -------------------------------------------------------------------------------- /solution/js/todoAutofocus.directive.js: -------------------------------------------------------------------------------- 1 | function todoAutofocus() { 2 | return { 3 | restrict: 'A', 4 | scope: false, 5 | link: function ($scope, $element, $attrs) { 6 | $scope.$watch($attrs.todoAutofocus, function (newValue, oldValue) { 7 | if (!newValue) { 8 | return; 9 | } 10 | setTimeout(function () { 11 | $element[0].focus(); 12 | }, 0); 13 | }); 14 | } 15 | }; 16 | } 17 | 18 | angular 19 | .module('app') 20 | .directive('todoAutofocus', todoAutofocus); 21 | --------------------------------------------------------------------------------