├── .gitignore ├── LICENSE ├── README.md ├── ch01_01_2way_binding_simple ├── index.html └── lib │ └── angular │ └── angular.js ├── ch01_02_2way_binding_colorpicker ├── index.html └── lib │ └── angular │ └── angular.js ├── ch01_03_colorpicker_directive ├── colorPickerTemplate.html ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── colorPickerApp.js ├── ch01_04_filter ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── dateApp.js ├── ch02_01_controller_model ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── myApp.js ├── ch02_02_route ├── index.html ├── lib │ ├── angular-route │ │ └── angular-route.js │ └── angular │ │ └── angular.js ├── scripts │ └── myApp.js └── templates │ ├── mainTemplate.html │ ├── userDetailsTemplate.html │ └── userOverviewTemplate.html ├── ch02_03_template_expression ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── myApp.js ├── ch02_04_filter ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── myApp.js ├── ch02_05_service_factory ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── myApp.js ├── ch02_06_service_provider ├── index.html ├── lib │ └── angular │ │ └── angular.js └── scripts │ └── myApp.js ├── ch02_07_directive ├── index.html ├── lib │ └── angular │ │ └── angular.js ├── scripts │ ├── app.js │ ├── controllers │ │ └── mainCtrl.js │ └── directives │ │ └── colorPickerDirective.js └── templates │ └── colorPickerTemplate.html ├── ch03_01_project_init └── app │ ├── index.html │ └── lib │ ├── angular-route │ └── angular-route.js │ └── angular │ └── angular.js ├── ch03_02_details_view ├── app │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ └── angular │ │ │ └── angular.js │ ├── scripts │ │ ├── app.js │ │ └── controllers │ │ │ └── book_details.js │ └── templates │ │ ├── book_details.html │ │ └── book_details_with_expressions.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ └── e2e │ └── templates │ └── book_details.spec.js ├── ch03_03_list_view ├── app │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ └── angular │ │ │ └── angular.js │ ├── scripts │ │ ├── app.js │ │ └── controllers │ │ │ ├── book_details.js │ │ │ └── book_list.js │ └── templates │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ └── e2e │ └── templates │ ├── book_details.spec.js │ └── book_list.spec.js ├── ch03_04_navigation ├── app │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ └── angular │ │ │ └── angular.js │ ├── scripts │ │ ├── app.js │ │ └── controllers │ │ │ ├── book_details.js │ │ │ └── book_list.js │ └── templates │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ └── e2e │ └── templates │ ├── book_details.spec.js │ └── book_list.spec.js ├── ch03_05_first_service ├── app │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ └── angular │ │ │ └── angular.js │ ├── scripts │ │ ├── app.js │ │ ├── controllers │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ └── services │ │ │ └── book_data.js │ └── templates │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ ├── e2e │ └── templates │ │ ├── book_details.spec.js │ │ └── book_list.spec.js │ └── unit │ └── services │ └── book_data.spec.js ├── ch04_00_backend ├── .gitignore ├── package.json └── server.js ├── ch04_01_admin ├── .gitignore ├── app │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ └── angular │ │ │ └── angular.js │ ├── scripts │ │ ├── app.js │ │ ├── controllers │ │ │ ├── admin_book_list.js │ │ │ ├── admin_delete_book.js │ │ │ ├── admin_edit_book.js │ │ │ ├── admin_new_book.js │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ └── services │ │ │ └── book_data.js │ ├── styles │ │ └── main.css │ └── templates │ │ ├── admin │ │ ├── book_delete.html │ │ └── book_form.html │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ ├── e2e │ └── templates │ │ ├── admin_delete_book.spec.js │ │ ├── admin_edit_book.spec.js │ │ ├── admin_new_book.spec.js │ │ ├── book_details.spec.js │ │ └── book_list.spec.js │ └── unit │ └── services │ └── book_data.spec.js ├── ch04_02_tags ├── app │ ├── component_templates │ │ └── directives │ │ │ └── tags.html │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ ├── angular │ │ │ └── angular.js │ │ ├── bootstrap-tokenfield │ │ │ ├── bootstrap-tokenfield.css │ │ │ ├── bootstrap-tokenfield.js │ │ │ └── bootstrap-tokenfield.min.js │ │ ├── bootstrap │ │ │ └── bootstrap.min.css │ │ ├── jquery-ui │ │ │ ├── images │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ └── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── jquery-ui.css │ │ │ └── jquery-ui.min.js │ │ └── jquery │ │ │ ├── jquery-1.10.2.min.js │ │ │ └── jquery-1.10.2.min.map │ ├── scripts │ │ ├── app.js │ │ ├── controllers │ │ │ ├── admin_book_list.js │ │ │ ├── admin_delete_book.js │ │ │ ├── admin_edit_book.js │ │ │ ├── admin_new_book.js │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ ├── directives │ │ │ ├── tags.js │ │ │ └── tokenfield.js │ │ └── services │ │ │ └── book_data.js │ ├── styles │ │ └── main.css │ └── templates │ │ ├── admin │ │ ├── book_delete.html │ │ └── book_form.html │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ ├── e2e │ └── templates │ │ ├── admin_delete_book.spec.js │ │ ├── admin_edit_book.spec.js │ │ ├── admin_new_book.spec.js │ │ ├── book_details.spec.js │ │ └── book_list.spec.js │ └── unit │ ├── directives │ └── tokenfield.spec.js │ └── services │ └── book_data.spec.js ├── ch04_03_http ├── app │ ├── component_templates │ │ └── directives │ │ │ └── tags.html │ ├── index.html │ ├── lib │ │ ├── angular-mocks │ │ │ └── angular-mocks.js │ │ ├── angular-route │ │ │ └── angular-route.js │ │ ├── angular │ │ │ └── angular.js │ │ ├── bootstrap-tokenfield │ │ │ ├── bootstrap-tokenfield.css │ │ │ ├── bootstrap-tokenfield.js │ │ │ └── bootstrap-tokenfield.min.js │ │ ├── bootstrap │ │ │ └── bootstrap.min.css │ │ ├── jquery-ui │ │ │ ├── images │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ └── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── jquery-ui.css │ │ │ └── jquery-ui.min.js │ │ └── jquery │ │ │ ├── jquery-1.10.2.min.js │ │ │ └── jquery-1.10.2.min.map │ ├── scripts │ │ ├── app.js │ │ ├── controllers │ │ │ ├── admin_book_list.js │ │ │ ├── admin_delete_book.js │ │ │ ├── admin_edit_book.js │ │ │ ├── admin_new_book.js │ │ │ ├── admin_reset.js │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ ├── directives │ │ │ ├── tags.js │ │ │ └── tokenfield.js │ │ └── services │ │ │ └── book_data.js │ ├── styles │ │ └── main.css │ └── templates │ │ ├── admin │ │ ├── book_delete.html │ │ ├── book_form.html │ │ └── reset.html │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ ├── e2e │ └── templates │ │ ├── admin_delete_book.spec.js │ │ ├── admin_edit_book.spec.js │ │ ├── admin_new_book.spec.js │ │ ├── book_details.spec.js │ │ └── book_list.spec.js │ └── unit │ ├── directives │ └── tokenfield.spec.js │ └── services │ └── book_data.spec.js ├── ch07_01_requirejs ├── app │ ├── .gitignore │ ├── bower.json │ ├── index.html │ ├── scripts │ │ ├── app.js │ │ ├── config │ │ │ └── routes.js │ │ ├── controllers │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ ├── requireConfig.js │ │ └── services │ │ │ └── book_data.js │ └── templates │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma-e2e.conf.js ├── karma.conf.js └── test │ ├── e2e │ └── templates │ │ ├── book_details.spec.js │ │ └── book_list.spec.js │ ├── testRequireConfig.js │ └── unit │ └── services │ └── book_data.spec.js ├── ch07_02_requirejs_cdn ├── app │ ├── .gitignore │ ├── index.html │ ├── scripts │ │ ├── app.js │ │ ├── config │ │ │ └── routes.js │ │ ├── controllers │ │ │ ├── book_details.js │ │ │ └── book_list.js │ │ ├── requireConfig.js │ │ └── services │ │ │ └── book_data.js │ └── templates │ │ ├── book_details.html │ │ ├── book_details_with_expressions.html │ │ └── book_list.html ├── karma.conf.js └── test │ ├── testRequireConfig.js │ └── unit │ └── services │ └── book_data.spec.js └── ch07_03_mobile └── app ├── .gitignore ├── bower.json ├── index.html ├── scripts ├── app.js └── directives │ └── draggable.js └── styles └── draggable.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | *.iml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 angularjs-de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularJS: Eine praktische Einführung in das JavaScript-Framework 2 | 3 | In diesem Repository finden Sie die Quellcode-Beispiele zum Buch AngularJS: Eine praktische Einführung in das JavaScript-Framework von Philipp Tarasiewicz und Robin Böhm (dpunkt.verlag). 4 | 5 | ## AngularJS Version 6 | Die Beispiele nutzen AngularJS in Version 1.2.10. 7 | -------------------------------------------------------------------------------- /ch01_01_2way_binding_simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 |
12 |

Hello {{yourName}}!

13 |
14 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ch01_02_2way_binding_colorpicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | R:
9 | G:
10 | B:
11 | A: 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ch01_03_colorpicker_directive/colorPickerTemplate.html: -------------------------------------------------------------------------------- 1 | R:
4 | G:
7 | B:
10 | A: 13 | 14 |
17 |
-------------------------------------------------------------------------------- /ch01_03_colorpicker_directive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

AngularJS Schnellstart ColorPicker

10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ch01_03_colorpicker_directive/scripts/colorPickerApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var colorPickerApp = angular.module('colorPickerApp', []); 4 | 5 | colorPickerApp.controller('MainCtrl', function ($scope) { 6 | $scope.onColorChange = function(r,g,b,a) { 7 | console.log('onColorChange', r, g, b, a); 8 | }; 9 | }); 10 | 11 | colorPickerApp.directive('colorPicker', function () { 12 | return { 13 | scope: { 14 | r: '@initR', 15 | g: '@initG', 16 | b: '@initB', 17 | a: '@initA', 18 | onChange: '&' 19 | }, 20 | restrict: 'E', 21 | templateUrl: 'colorPickerTemplate.html', 22 | link: function(scope) { 23 | var COLORS = ['r', 'g', 'b', 'a']; 24 | 25 | COLORS.forEach(function(value) { 26 | scope.$watch(value, function(newValue, oldValue) { 27 | if (newValue !== oldValue) { 28 | if (angular.isFunction(scope.onChange)) { 29 | scope.onChange(generateColorChangeObject()); 30 | } 31 | } 32 | }); 33 | }); 34 | 35 | var generateColorChangeObject = function() { 36 | var obj = {}; 37 | 38 | COLORS.forEach(function(value) { 39 | obj[value] = scope[value]; 40 | }); 41 | 42 | return obj; 43 | }; 44 | } 45 | }; 46 | }); 47 | -------------------------------------------------------------------------------- /ch01_04_filter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Today's date: 9 | {{ now | date:'dd. MMMM yyyy' }}
10 | And the time is: 11 | {{ now | date:'HH:mm:ss' }}
12 | {{ 'This is an example for a long text' | truncate:18 }} 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ch01_04_filter/scripts/dateApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var dateApp = angular.module('dateApp', []); 4 | 5 | dateApp.controller('DateCtrl', function ($scope, $timeout) { 6 | $scope.now = 'Loading...'; 7 | 8 | var updateTime = function() { 9 | $timeout(function() { 10 | $scope.now = new Date(); 11 | updateTime(); 12 | }, 1000); 13 | }; 14 | 15 | updateTime(); 16 | }); 17 | 18 | dateApp.filter('truncate', function () { 19 | return function (input, charCount) { 20 | var output = input; 21 | 22 | if (output.length > charCount) { 23 | output = output.substr(0, charCount) + '...'; 24 | } 25 | 26 | return output; 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /ch02_01_controller_model/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ch02_01_controller_model/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', []); 4 | 5 | angular.module('myApp').controller('MyCtrl', function ($scope) { 6 | // a simple JavaScript object 7 | $scope.user = { 8 | name: 'John Doe', 9 | age: 27, 10 | email: 'john@doe.com' 11 | }; 12 | 13 | // a simple JavaScript array 14 | $scope.family = [ 15 | 'James Doe', 'Clarissa Doe', 'Ted Doe' 16 | ]; 17 | 18 | // a primitive value 19 | $scope.loggedIn = true; 20 | }); 21 | -------------------------------------------------------------------------------- /ch02_02_route/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

myApp

9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ch02_02_route/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', ['ngRoute']); 4 | 5 | angular.module('myApp').config(function ($routeProvider) { 6 | $routeProvider 7 | .when('/', { 8 | templateUrl: 'templates/mainTemplate.html', 9 | controller: 'MainCtrl' 10 | }) 11 | .when('/user/:userId', { 12 | templateUrl: 'templates/userDetailsTemplate.html', 13 | controller: 'UserDetailsCtrl' 14 | }) 15 | .when('/user', { 16 | templateUrl: 'templates/userOverviewTemplate.html', 17 | controller: 'UserOverviewCtrl' 18 | }) 19 | .otherwise({ 20 | redirectTo: '/' 21 | }); 22 | }); 23 | 24 | angular.module('myApp').controller('MainCtrl', function () { 25 | console.log('MainCtrl invoked!'); 26 | }); 27 | 28 | angular.module('myApp').controller('UserDetailsCtrl', function ($scope, $routeParams) { 29 | console.log('UserDetailsCtrl invoked with userId: ' + $routeParams.userId); 30 | $scope.userId = $routeParams.userId; 31 | }); 32 | 33 | angular.module('myApp').controller('UserOverviewCtrl', function () { 34 | console.log('UserOverviewCtrl invoked!'); 35 | }); 36 | -------------------------------------------------------------------------------- /ch02_02_route/templates/mainTemplate.html: -------------------------------------------------------------------------------- 1 |

mainTemplate

-------------------------------------------------------------------------------- /ch02_02_route/templates/userDetailsTemplate.html: -------------------------------------------------------------------------------- 1 |

userDetailsTemplate with userId: {{ userId }}

-------------------------------------------------------------------------------- /ch02_02_route/templates/userOverviewTemplate.html: -------------------------------------------------------------------------------- 1 |

userOverviewTemplate

-------------------------------------------------------------------------------- /ch02_03_template_expression/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

10 | Hello, {{user.name}}!
11 | Five years ago you were 12 | {{user.age - 5}} years old. 13 |

14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ch02_03_template_expression/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', []); 4 | 5 | angular.module('myApp').controller('MyCtrl', function ($scope) { 6 | // a simple JavaScript object 7 | $scope.user = { 8 | name: 'John Doe', 9 | age: 27, 10 | email: 'john@doe.com' 11 | }; 12 | 13 | // a simple JavaScript array 14 | $scope.family = [ 15 | 'James Doe', 'Clarissa Doe', 'Ted Doe' 16 | ]; 17 | 18 | // a primitive value 19 | $scope.loggedIn = true; 20 | }); 21 | -------------------------------------------------------------------------------- /ch02_04_filter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

10 | Hello, {{user.name | uppercase}}!
11 | Five years ago you were 12 | {{user.age - 5}} years old. 13 |

14 |
15 | 16 |
17 |

18 | Hello, {{user.name | alternatingCase}}!
19 | Five years ago you were 20 | {{user.age - 5}} years old. 21 |

22 |
23 | 24 |
25 | Family members of {{user.name}} (all): 26 | 29 |
30 | 31 |
32 | Family members of {{user.name}} (filtered by filter-filter set to 'clarissa'): 33 | 38 |
39 | 40 |
41 | Family members of {{user.name}} (filtered by custom filter endsWithDoe): 42 | 45 |
46 | 47 |
48 | Family members of {{user.name}} (filtered by custom filter endsWith with parameter 'Doe'): 49 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ch02_04_filter/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', []); 4 | 5 | angular.module('myApp').controller('MyCtrl', function ($scope) { 6 | // a simple JavaScript object 7 | $scope.user = { 8 | name: 'John Doe', 9 | age: 27, 10 | email: 'john@doe.com' 11 | }; 12 | 13 | // a simple JavaScript array 14 | $scope.family = [ 15 | 'James Doe', 'Clarissa Doe', 'Ted Doe', 16 | 'Burk Smith', 'Samantha Jones', 'Bill Brooks' 17 | ]; 18 | 19 | // a primitive value 20 | $scope.loggedIn = true; 21 | }); 22 | 23 | angular.module('myApp').filter('alternatingCase', function () { 24 | return function (input) { 25 | var output = '', 26 | tmp; 27 | 28 | for (var i = 0; i < input.length; i++) { 29 | tmp = input.charAt(i); 30 | 31 | if (i % 2 === 0) { 32 | output += tmp.toUpperCase(); 33 | } 34 | else { 35 | output += tmp.toLowerCase(); 36 | } 37 | } 38 | 39 | return output; 40 | }; 41 | }); 42 | 43 | angular.module('myApp').filter('endsWithDoe', function () { 44 | return function (inputArray) { 45 | var outputArray = []; 46 | 47 | angular.forEach(inputArray, function (item) { 48 | if (item.length >= 3 49 | && item.substring(item.length - 3) === 'Doe') { 50 | outputArray.push(item); 51 | } 52 | }); 53 | 54 | return outputArray; 55 | }; 56 | }); 57 | 58 | angular.module('myApp').filter('endsWith', function () { 59 | return function (inputArray, endsWith) { 60 | var outputArray = [], 61 | subString, 62 | hasMinLen, 63 | isSubStringMatching; 64 | 65 | angular.forEach(inputArray, function (item) { 66 | hasMinLen = item.length >= endsWith.length; 67 | subString = item.substring(item.length - endsWith.length); 68 | isSubStringMatching = subString === endsWith; 69 | 70 | if (hasMinLen && isSubStringMatching) { 71 | outputArray.push(item); 72 | } 73 | }); 74 | 75 | return outputArray; 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /ch02_05_service_factory/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch02_05_service_factory/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', []); 4 | 5 | angular.module('myApp').controller('MyCtrl', function(log) { 6 | log.debug('MyCtrl has been invoked!'); 7 | }); 8 | 9 | angular.module('myApp').factory('log', function () { 10 | // Service Implementation 11 | var log = function (level, message) { 12 | console.log('[' + level + '] ' + message); 13 | }; 14 | 15 | // Public API 16 | return { 17 | error: function (message) { 18 | log('ERROR', message); 19 | }, 20 | info: function (message) { 21 | log('INFO', message); 22 | }, 23 | debug: function (message) { 24 | log('DEBUG', message); 25 | } 26 | }; 27 | }); 28 | -------------------------------------------------------------------------------- /ch02_06_service_provider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch02_06_service_provider/scripts/myApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp', []); 4 | 5 | angular.module('myApp').provider('log', function () { 6 | /* Private state/methods */ 7 | var prefix = '', 8 | suffix = ''; 9 | 10 | // Service Implementation 11 | var log = function (level, message) { 12 | console.log(prefix + '[' + level + '] ' + message + suffix); 13 | }; 14 | 15 | /* Public state/methods */ 16 | // Configuration methods / Public methods 17 | this.setPrefix = function (newPrefix) { 18 | prefix = newPrefix; 19 | }; 20 | 21 | this.setSuffix = function (newSuffix) { 22 | suffix = newSuffix; 23 | }; 24 | 25 | this.$get = function () { 26 | // Public API 27 | return { 28 | error: function (message) { 29 | log('ERROR', message); 30 | }, 31 | info: function (message) { 32 | log('INFO', message); 33 | }, 34 | debug: function (message) { 35 | log('DEBUG', message); 36 | } 37 | }; 38 | }; 39 | }); 40 | 41 | angular.module('myApp').config(function(logProvider) { 42 | logProvider.setPrefix('[myApp]'); 43 | logProvider.setSuffix('.'); 44 | }); 45 | 46 | angular.module('myApp').controller('MyCtrl', function(log) { 47 | log.debug('MyCtrl has been invoked'); 48 | }); 49 | -------------------------------------------------------------------------------- /ch02_07_directive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ch02_07_directive/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var colorPickerApp = angular.module('colorPickerApp', []); 4 | -------------------------------------------------------------------------------- /ch02_07_directive/scripts/controllers/mainCtrl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | colorPickerApp.controller('MainCtrl', function ($scope) { 4 | $scope.onColorChange = function(r,g,b,a) { 5 | console.log('onColorChange', r, g, b, a); 6 | }; 7 | }); 8 | -------------------------------------------------------------------------------- /ch02_07_directive/scripts/directives/colorPickerDirective.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | colorPickerApp.directive('colorPicker', function () { 4 | return { 5 | scope: { 6 | r: '@initR', 7 | g: '@initG', 8 | b: '@initB', 9 | a: '@initA', 10 | onChange: '&' 11 | }, 12 | restrict: 'E', 13 | templateUrl: 'templates/colorPickerTemplate.html', 14 | link: function(scope) { 15 | var COLORS = ['r', 'g', 'b', 'a']; 16 | 17 | COLORS.forEach(function(value) { 18 | scope.$watch(value, function(newValue, oldValue) { 19 | if (newValue !== oldValue) { 20 | if (angular.isFunction(scope.onChange)) { 21 | scope.onChange(generateColorChangeObject()); 22 | } 23 | } 24 | }); 25 | }); 26 | 27 | var generateColorChangeObject = function() { 28 | var obj = {}; 29 | 30 | COLORS.forEach(function(value) { 31 | obj[value] = scope[value]; 32 | }); 33 | 34 | return obj; 35 | }; 36 | } 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /ch02_07_directive/templates/colorPickerTemplate.html: -------------------------------------------------------------------------------- 1 | R:
4 | G:
7 | B:
10 | A: 13 | 14 |
17 |
-------------------------------------------------------------------------------- /ch03_01_project_init/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |

Hello {{ name }}

9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ch03_02_details_view/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ch03_02_details_view/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /ch03_02_details_view/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', function ($scope) { 4 | $scope.book = { 5 | title : 'JavaScript für Enterprise-Entwickler', 6 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 7 | isbn : '978-3-89864-728-1', 8 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 9 | numPages : 302, 10 | author : 'Oliver Ochs', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ch03_02_details_view/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

20 |

21 |
22 |

23 |

-------------------------------------------------------------------------------- /ch03_02_details_view/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch03_02_details_view/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch03_02_details_view/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/angular/angular.js', 17 | 'app/lib/angular-route/angular-route.js', 18 | 'app/lib/angular-mocks/angular-mocks.js', 19 | 'app/scripts/app.js', 20 | 'app/scripts/**/*.js', 21 | 'test/unit/**/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_DEBUG, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: ['Chrome'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: true 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /ch03_02_details_view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch03_02_details_view/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | describe("E2E: book details view", function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo('/'); 5 | }); 6 | 7 | it('should show the correct book details', function() { 8 | browser().navigateTo('#/books/978-3-89864-728-1'); 9 | 10 | expect(element('.bm-book-title').html()).toBe( 11 | 'JavaScript für Enterprise-Entwickler' 12 | ); 13 | expect(element('.bm-book-subtitle').html()).toBe( 14 | 'Professionell programmieren im Browser und auf dem Server' 15 | ); 16 | expect(element('.bm-book-isbn').html()).toBe( 17 | 'ISBN: 978-3-89864-728-1' 18 | ); 19 | expect(element('.bm-book-num-pages').html()).toBe( 20 | 'Seiten: 302' 21 | ); 22 | expect(element('.bm-book-author').html()).toBe( 23 | 'Autor: Oliver Ochs' 24 | ); 25 | expect(element('.bm-book-publisher-name').html()).toBe( 26 | 'dpunkt.verlag' 27 | ); 28 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 29 | 'http://dpunkt.de/' 30 | ); 31 | expect(element('.bm-book-abstract').html()).toBe( 32 | 'JavaScript ist längst nicht mehr nur für' + 33 | ' klassische Webprogrammierer interessant.' 34 | ); 35 | }); 36 | 37 | }); -------------------------------------------------------------------------------- /ch03_03_list_view/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ch03_03_list_view/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }); 15 | }); -------------------------------------------------------------------------------- /ch03_03_list_view/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', function ($scope) { 4 | $scope.book = { 5 | title : 'JavaScript für Enterprise-Entwickler', 6 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 7 | isbn : '978-3-89864-728-1', 8 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 9 | numPages : 302, 10 | author : 'Oliver Ochs', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ch03_03_list_view/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, $filter) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | $scope.books = [ 9 | { 10 | title : 'JavaScript für Enterprise-Entwickler', 11 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 12 | isbn : '978-3-89864-728-1', 13 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 14 | numPages : 302, 15 | author : 'Oliver Ochs', 16 | publisher : { 17 | name: 'dpunkt.verlag', 18 | url : 'http://dpunkt.de/' 19 | } 20 | }, 21 | { 22 | title : 'Node.js & Co.', 23 | subtitle : 'Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln', 24 | isbn : '978-3-89864-829-5', 25 | abstract : 'Nach dem Webbrowser erobert JavaScript nun auch den Webserver.', 26 | numPages : 334, 27 | author : 'Golo Roden', 28 | publisher : { 29 | name: 'dpunkt.verlag', 30 | url : 'http://dpunkt.de/' 31 | } 32 | }, 33 | { 34 | title : 'CoffeeScript', 35 | subtitle : 'Einfach JavaScript', 36 | isbn : '978-3-86490-050-1', 37 | abstract : 'CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.', 38 | numPages : 200, 39 | author : 'Andreas Schubert', 40 | publisher : { 41 | name: 'dpunkt.verlag', 42 | url : 'http://dpunkt.de/' 43 | } 44 | } 45 | ]; 46 | 47 | // This is just to demonstrate the programmatic usage of a filter 48 | var orderBy = $filter('orderBy'); 49 | 50 | var titles = $scope.books.map(function(book) { 51 | return {title: book.title}; 52 | }); 53 | 54 | console.log('titles before ordering', titles); 55 | 56 | // This is the actual invocation of the filter 57 | titles = orderBy(titles, 'title'); 58 | 59 | console.log('titles after ordering', titles); 60 | }); 61 | -------------------------------------------------------------------------------- /ch03_03_list_view/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

20 |

21 |
22 |

23 |

-------------------------------------------------------------------------------- /ch03_03_list_view/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch03_03_list_view/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
-------------------------------------------------------------------------------- /ch03_03_list_view/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch03_03_list_view/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/angular/angular.js', 17 | 'app/lib/angular-route/angular-route.js', 18 | 'app/lib/angular-mocks/angular-mocks.js', 19 | 'app/scripts/app.js', 20 | 'app/scripts/**/*.js', 21 | 'test/unit/**/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_DEBUG, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: ['Chrome'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: true 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /ch03_03_list_view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch03_03_list_view/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | }); -------------------------------------------------------------------------------- /ch03_03_list_view/test/e2e/templates/book_list.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book list view", function () { 4 | 5 | // Define the array of books in the expected order. 6 | // Sorted by title. 7 | var expectedBooks = [ 8 | { 9 | title: 'CoffeeScript', 10 | isbn: '978-3-86490-050-1', 11 | author: 'Andreas Schubert' 12 | }, 13 | { 14 | title: 'JavaScript für Enterprise-Entwickler', 15 | isbn: '978-3-89864-728-1', 16 | author: 'Oliver Ochs' 17 | }, 18 | { 19 | title: 'Node.js & Co.', 20 | isbn: '978-3-89864-829-5', 21 | author: 'Golo Roden' 22 | } 23 | ]; 24 | 25 | // Derive an array that only contains titles 26 | // for easier expectation checks. 27 | var orderedTitles = expectedBooks.map(function(book) { 28 | return book.title; 29 | }); 30 | 31 | beforeEach(function () { 32 | browser().navigateTo('#/books'); 33 | browser().reload(); 34 | }); 35 | 36 | var selector = 'table.bm-book-list tr'; 37 | 38 | it('should show the correct number of books', function () { 39 | expect(repeater(selector).count()).toEqual(expectedBooks.length); 40 | }); 41 | 42 | it('should show the books in the proper order', function() { 43 | // Are they in the expected order (ascending sorted by title)? 44 | expect(repeater(selector).column('book.title')).toEqual(orderedTitles); 45 | }); 46 | 47 | it('should show the correct book information', function() { 48 | // Do the other book details (isbn, author) match? 49 | for (var i = 0, n = expectedBooks.length; i < n; i++) { 50 | expect(repeater(selector).row(i)) 51 | .toEqual( 52 | [ 53 | expectedBooks[i].title, 54 | expectedBooks[i].author, 55 | expectedBooks[i].isbn 56 | ] 57 | ); 58 | } 59 | }); 60 | 61 | it('should allow filtering by book title', function() { 62 | // Coffee 63 | var searchText = orderedTitles[0].substr(0, 6); 64 | input('searchText').enter(searchText); 65 | expect( 66 | repeater(selector).column('book.title') 67 | ).toEqual([orderedTitles[0]]); 68 | }); 69 | 70 | it('should allow filtering by author', function() { 71 | // Andreas 72 | var searchText = expectedBooks[0].author.substr(0, 7); 73 | input('searchText').enter(searchText); 74 | expect( 75 | repeater(selector).column('book.title') 76 | ).toEqual([orderedTitles[0]]); 77 | }); 78 | 79 | it('should allow filtering by isbn', function() { 80 | // 050-1 81 | var searchText = expectedBooks[0].isbn.substr(-5, 5); 82 | input('searchText').enter(searchText); 83 | expect( 84 | repeater(selector).column('book.title') 85 | ).toEqual([orderedTitles[0]]); 86 | }); 87 | }); -------------------------------------------------------------------------------- /ch03_04_navigation/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ch03_04_navigation/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }) 15 | .otherwise({ 16 | redirectTo: '/books' 17 | }); 18 | }); -------------------------------------------------------------------------------- /ch03_04_navigation/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', function ($scope, $location) { 4 | $scope.book = { 5 | title : 'JavaScript für Enterprise-Entwickler', 6 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 7 | isbn : '978-3-89864-728-1', 8 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 9 | numPages : 302, 10 | author : 'Oliver Ochs', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | $scope.goToListView = function() { 18 | $location.path('/books'); 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /ch03_04_navigation/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, $filter) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | $scope.books = [ 9 | { 10 | title : 'JavaScript für Enterprise-Entwickler', 11 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 12 | isbn : '978-3-89864-728-1', 13 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 14 | numPages : 302, 15 | author : 'Oliver Ochs', 16 | publisher : { 17 | name: 'dpunkt.verlag', 18 | url : 'http://dpunkt.de/' 19 | } 20 | }, 21 | { 22 | title : 'Node.js & Co.', 23 | subtitle : 'Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln', 24 | isbn : '978-3-89864-829-5', 25 | abstract : 'Nach dem Webbrowser erobert JavaScript nun auch den Webserver.', 26 | numPages : 334, 27 | author : 'Golo Roden', 28 | publisher : { 29 | name: 'dpunkt.verlag', 30 | url : 'http://dpunkt.de/' 31 | } 32 | }, 33 | { 34 | title : 'CoffeeScript', 35 | subtitle : 'Einfach JavaScript', 36 | isbn : '978-3-86490-050-1', 37 | abstract : 'CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.', 38 | numPages : 200, 39 | author : 'Andreas Schubert', 40 | publisher : { 41 | name: 'dpunkt.verlag', 42 | url : 'http://dpunkt.de/' 43 | } 44 | } 45 | ]; 46 | 47 | // This is just to demonstrate the programmatic usage of a filter 48 | var orderBy = $filter('orderBy'); 49 | 50 | var titles = $scope.books.map(function(book) { 51 | return {title: book.title}; 52 | }); 53 | 54 | console.log('titles before ordering', titles); 55 | 56 | // This is the actual invocation of the filter 57 | titles = orderBy(titles, 'title'); 58 | 59 | console.log('titles after ordering', titles); 60 | }); 61 | -------------------------------------------------------------------------------- /ch03_04_navigation/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 |

21 |
22 |

23 |

24 | Zurück -------------------------------------------------------------------------------- /ch03_04_navigation/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch03_04_navigation/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
-------------------------------------------------------------------------------- /ch03_04_navigation/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch03_04_navigation/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/angular/angular.js', 17 | 'app/lib/angular-route/angular-route.js', 18 | 'app/lib/angular-mocks/angular-mocks.js', 19 | 'app/scripts/app.js', 20 | 'app/scripts/**/*.js', 21 | 'test/unit/**/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_DEBUG, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: ['Chrome'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: true 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /ch03_04_navigation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch03_04_navigation/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch03_04_navigation/test/e2e/templates/book_list.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book list view", function () { 4 | 5 | // Define the array of books in the expected order. 6 | // Sorted by title. 7 | var expectedBooks = [ 8 | { 9 | title: 'CoffeeScript', 10 | isbn: '978-3-86490-050-1', 11 | author: 'Andreas Schubert' 12 | }, 13 | { 14 | title: 'JavaScript für Enterprise-Entwickler', 15 | isbn: '978-3-89864-728-1', 16 | author: 'Oliver Ochs' 17 | }, 18 | { 19 | title: 'Node.js & Co.', 20 | isbn: '978-3-89864-829-5', 21 | author: 'Golo Roden' 22 | } 23 | ]; 24 | 25 | // Derive an array that only contains titles 26 | // for easier expectation checks. 27 | var orderedTitles = expectedBooks.map(function(book) { 28 | return book.title; 29 | }); 30 | 31 | beforeEach(function () { 32 | browser().navigateTo('#/books'); 33 | browser().reload(); 34 | }); 35 | 36 | var selector = 'table.bm-book-list tr'; 37 | 38 | it('should show the correct number of books', function () { 39 | expect(repeater(selector).count()).toEqual(expectedBooks.length); 40 | }); 41 | 42 | it('should show the books in the proper order', function() { 43 | // Are they in the expected order (ascending sorted by title)? 44 | expect(repeater(selector).column('book.title')).toEqual(orderedTitles); 45 | }); 46 | 47 | it('should show the correct book information', function() { 48 | // Do the other book details (isbn, author) match? 49 | for (var i = 0, n = expectedBooks.length; i < n; i++) { 50 | expect(repeater(selector).row(i)) 51 | .toEqual( 52 | [ 53 | expectedBooks[i].title, 54 | expectedBooks[i].author, 55 | expectedBooks[i].isbn 56 | ] 57 | ); 58 | } 59 | }); 60 | 61 | it('should allow filtering by book title', function() { 62 | // Coffee 63 | var searchText = orderedTitles[0].substr(0, 6); 64 | input('searchText').enter(searchText); 65 | expect( 66 | repeater(selector).column('book.title') 67 | ).toEqual([orderedTitles[0]]); 68 | }); 69 | 70 | it('should allow filtering by author', function() { 71 | // Andreas 72 | var searchText = expectedBooks[0].author.substr(0, 7); 73 | input('searchText').enter(searchText); 74 | expect( 75 | repeater(selector).column('book.title') 76 | ).toEqual([orderedTitles[0]]); 77 | }); 78 | 79 | it('should allow filtering by isbn', function() { 80 | // 050-1 81 | var searchText = expectedBooks[0].isbn.substr(-5, 5); 82 | input('searchText').enter(searchText); 83 | expect( 84 | repeater(selector).column('book.title') 85 | ).toEqual([orderedTitles[0]]); 86 | }); 87 | 88 | it('should appropriately navigate to details view', function() { 89 | var i = 0, 90 | detailsLink = selector + ':nth-child('+ (i+1) +') a'; 91 | element(detailsLink).click(); 92 | 93 | expect( 94 | browser().location().path() 95 | ).toEqual('/books/' + expectedBooks[i].isbn); 96 | }); 97 | 98 | }); -------------------------------------------------------------------------------- /ch03_05_first_service/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }) 15 | .otherwise({ 16 | redirectTo: '/books' 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', 4 | function ($scope, $location, $routeParams, BookDataService) { 5 | var isbn = $routeParams.isbn; 6 | $scope.book = BookDataService.getBookByIsbn(isbn); 7 | 8 | $scope.goToListView = function() { 9 | $location.path('/books'); 10 | }; 11 | }); 12 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, BookDataService) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | $scope.books = BookDataService.getBooks(); 9 | }); 10 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/scripts/services/book_data.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.factory('BookDataService', function() { 4 | var srv = {}; 5 | 6 | srv._books = [ 7 | { 8 | title : 'JavaScript für Enterprise-Entwickler', 9 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 10 | isbn : '978-3-89864-728-1', 11 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 12 | numPages : 302, 13 | author : 'Oliver Ochs', 14 | publisher : { 15 | name: 'dpunkt.verlag', 16 | url : 'http://dpunkt.de/' 17 | } 18 | }, 19 | { 20 | title : 'Node.js & Co.', 21 | subtitle : 'Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln', 22 | isbn : '978-3-89864-829-5', 23 | abstract : 'Nach dem Webbrowser erobert JavaScript nun auch den Webserver.', 24 | numPages : 334, 25 | author : 'Golo Roden', 26 | publisher : { 27 | name: 'dpunkt.verlag', 28 | url : 'http://dpunkt.de/' 29 | } 30 | }, 31 | { 32 | title : 'CoffeeScript', 33 | subtitle : 'Einfach JavaScript', 34 | isbn : '978-3-86490-050-1', 35 | abstract : 'CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.', 36 | numPages : 200, 37 | author : 'Andreas Schubert', 38 | publisher : { 39 | name: 'dpunkt.verlag', 40 | url : 'http://dpunkt.de/' 41 | } 42 | } 43 | ]; 44 | 45 | // Service implementation 46 | srv.getBookByIsbn = function(isbn) { 47 | for (var i = 0, n = srv._books.length; i < n; i++) { 48 | if (isbn === srv._books[i].isbn) { 49 | return angular.copy(srv._books[i]); 50 | } 51 | } 52 | 53 | return null; 54 | }; 55 | 56 | srv.getBooks = function() { 57 | // Copy the array in order not to expose internal data structures 58 | return angular.copy(srv._books); 59 | }; 60 | 61 | // Public API 62 | return { 63 | getBookByIsbn: function(isbn) { 64 | return srv.getBookByIsbn(isbn); 65 | }, 66 | getBooks: function() { 67 | return srv.getBooks(); 68 | } 69 | }; 70 | }); 71 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 |

21 |
22 |

23 |

24 | Zurück 25 | -------------------------------------------------------------------------------- /ch03_05_first_service/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch03_05_first_service/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
-------------------------------------------------------------------------------- /ch03_05_first_service/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch03_05_first_service/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/angular/angular.js', 17 | 'app/lib/angular-route/angular-route.js', 18 | 'app/lib/angular-mocks/angular-mocks.js', 19 | 'app/scripts/app.js', 20 | 'app/scripts/**/*.js', 21 | 'test/unit/**/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_DEBUG, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: ['Chrome'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: true 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /ch03_05_first_service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch03_05_first_service/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch03_05_first_service/test/e2e/templates/book_list.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book list view", function () { 4 | 5 | // Define the array of books in the expected order. 6 | // Sorted by title. 7 | var expectedBooks = [ 8 | { 9 | title: 'CoffeeScript', 10 | isbn: '978-3-86490-050-1', 11 | author: 'Andreas Schubert' 12 | }, 13 | { 14 | title: 'JavaScript für Enterprise-Entwickler', 15 | isbn: '978-3-89864-728-1', 16 | author: 'Oliver Ochs' 17 | }, 18 | { 19 | title: 'Node.js & Co.', 20 | isbn: '978-3-89864-829-5', 21 | author: 'Golo Roden' 22 | } 23 | ]; 24 | 25 | // Derive an array that only contains titles 26 | // for easier expectation checks. 27 | var orderedTitles = expectedBooks.map(function(book) { 28 | return book.title; 29 | }); 30 | 31 | beforeEach(function () { 32 | browser().navigateTo('#/books'); 33 | browser().reload(); 34 | }); 35 | 36 | var selector = 'table.bm-book-list tr'; 37 | 38 | it('should show the correct number of books', function () { 39 | expect(repeater(selector).count()).toEqual(expectedBooks.length); 40 | }); 41 | 42 | it('should show the books in the proper order', function() { 43 | // Are they in the expected order (ascending sorted by title)? 44 | expect(repeater(selector).column('book.title')).toEqual(orderedTitles); 45 | }); 46 | 47 | it('should show the correct book information', function() { 48 | // Do the other book details (isbn, author) match? 49 | for (var i = 0, n = expectedBooks.length; i < n; i++) { 50 | expect(repeater(selector).row(i)) 51 | .toEqual( 52 | [ 53 | expectedBooks[i].title, 54 | expectedBooks[i].author, 55 | expectedBooks[i].isbn 56 | ] 57 | ); 58 | } 59 | }); 60 | 61 | it('should allow filtering by book title', function() { 62 | // Coffee 63 | var searchText = orderedTitles[0].substr(0, 6); 64 | input('searchText').enter(searchText); 65 | expect( 66 | repeater(selector).column('book.title') 67 | ).toEqual([orderedTitles[0]]); 68 | }); 69 | 70 | it('should allow filtering by author', function() { 71 | // Andreas 72 | var searchText = expectedBooks[0].author.substr(0, 7); 73 | input('searchText').enter(searchText); 74 | expect( 75 | repeater(selector).column('book.title') 76 | ).toEqual([orderedTitles[0]]); 77 | }); 78 | 79 | it('should allow filtering by isbn', function() { 80 | // 050-1 81 | var searchText = expectedBooks[0].isbn.substr(-5, 5); 82 | input('searchText').enter(searchText); 83 | expect( 84 | repeater(selector).column('book.title') 85 | ).toEqual([orderedTitles[0]]); 86 | }); 87 | 88 | it('should appropriately navigate to details view', function() { 89 | var i = 0, 90 | detailsLink = selector + ':nth-child('+ (i+1) +') a'; 91 | element(detailsLink).click(); 92 | 93 | expect( 94 | browser().location().path() 95 | ).toEqual('/books/' + expectedBooks[i].isbn); 96 | }); 97 | 98 | }); -------------------------------------------------------------------------------- /ch03_05_first_service/test/unit/services/book_data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: BookDataService', function () { 4 | 5 | var BookDataService; 6 | 7 | // load the application module 8 | beforeEach(module('bmApp')); 9 | 10 | // get a reference to the service 11 | beforeEach(inject(function (_BookDataService_) { 12 | BookDataService = _BookDataService_; 13 | })); 14 | 15 | describe('Public API', function() { 16 | it('should include a getBookByIsbn() function', function () { 17 | expect(BookDataService.getBookByIsbn).toBeDefined(); 18 | }); 19 | 20 | it('should include a getBooks() function', function () { 21 | expect(BookDataService.getBooks).toBeDefined(); 22 | }); 23 | }); 24 | 25 | describe('Public API usage', function() { 26 | describe('getBookByIsbn()', function() { 27 | it('should return the proper book object (valid isbn)', function() { 28 | var isbn = '978-3-86490-050-1', 29 | book = BookDataService.getBookByIsbn(isbn); 30 | expect(book.title).toBe('CoffeeScript'); 31 | }); 32 | 33 | it('should return null (invalid isbn)', function() { 34 | var isbn = 'test', 35 | book = BookDataService.getBookByIsbn(isbn); 36 | expect(book).toBeNull(); 37 | }); 38 | }); 39 | 40 | describe('getBooks()', function() { 41 | it('should return a proper array of book objects', function() { 42 | var books = BookDataService.getBooks(); 43 | expect(books.length).toBe(3); 44 | }); 45 | }); 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /ch04_00_backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ch04_00_backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey-simple-backend", 3 | "description": "A simple backend for the BookMonkey example application.", 4 | "private": true, 5 | "version": "0.0.1", 6 | "dependencies": { 7 | "express": "3.x" 8 | }, 9 | "main": "server" 10 | } -------------------------------------------------------------------------------- /ch04_01_admin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ch04_01_admin/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 | 9 |
10 |

BookMonkey

11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }) 15 | 16 | /* Admin routes */ 17 | .when('/admin/books', { 18 | // we reuse the template from the list view 19 | templateUrl: 'templates/book_list.html', 20 | controller: 'AdminBookListCtrl' 21 | }) 22 | .when('/admin/books/new', { 23 | templateUrl: 'templates/admin/book_form.html', 24 | controller: 'AdminNewBookCtrl' 25 | }) 26 | .when('/admin/books/:isbn/edit', { 27 | templateUrl: 'templates/admin/book_form.html', 28 | controller: 'AdminEditBookCtrl' 29 | }) 30 | .when('/admin/books/:isbn/delete', { 31 | templateUrl: 'templates/admin/book_delete.html', 32 | controller: 'AdminDeleteBookCtrl' 33 | }) 34 | 35 | /* Default route */ 36 | .otherwise({ 37 | redirectTo: '/books' 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/admin_book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminBookListCtrl', function ($scope, BookDataService) { 4 | $scope.isAdmin = true; 5 | 6 | $scope.getBookOrder = function(book) { 7 | return book.title; 8 | }; 9 | 10 | $scope.books = BookDataService.getBooks(); 11 | }); 12 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/admin_delete_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminDeleteBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | var isbn = $routeParams.isbn; 5 | $scope.book = BookDataService.getBookByIsbn(isbn); 6 | 7 | $scope.deleteBook = function(isbn) { 8 | BookDataService.deleteBookByIsbn(isbn); 9 | goToAdminListView(); 10 | }; 11 | 12 | $scope.cancel = function() { 13 | goToAdminListView(); 14 | }; 15 | 16 | var goToAdminListView = function() { 17 | $location.path('/admin/books'); 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/admin_edit_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminEditBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | $scope.isEditMode = true; 5 | $scope.submitBtnLabel = 'Buch editieren'; 6 | 7 | var isbn = $routeParams.isbn; 8 | $scope.book = BookDataService.getBookByIsbn(isbn); 9 | 10 | $scope.submitAction = function() { 11 | BookDataService.updateBook($scope.book); 12 | goToAdminListView(); 13 | }; 14 | 15 | $scope.cancelAction = function() { 16 | goToAdminListView(); 17 | }; 18 | 19 | var goToAdminListView = function() { 20 | $location.path('/admin/books'); 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/admin_new_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminNewBookCtrl', function ($scope, $location, BookDataService) { 4 | $scope.book = {}; 5 | $scope.submitBtnLabel = 'Buch anlegen'; 6 | 7 | $scope.submitAction = function() { 8 | BookDataService.storeBook($scope.book); 9 | goToAdminListView(); 10 | }; 11 | 12 | $scope.cancelAction = function() { 13 | goToAdminListView(); 14 | }; 15 | 16 | var goToAdminListView = function() { 17 | $location.path('/admin/books'); 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', 4 | function ($scope, $location, $routeParams, BookDataService) { 5 | var isbn = $routeParams.isbn; 6 | $scope.book = BookDataService.getBookByIsbn(isbn); 7 | 8 | $scope.goToListView = function() { 9 | if ($location.path().indexOf('/admin') === 0) { 10 | $location.path('/admin/books'); 11 | } 12 | else { 13 | $location.path('/books'); 14 | } 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ch04_01_admin/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, BookDataService) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | $scope.books = BookDataService.getBooks(); 9 | }); 10 | -------------------------------------------------------------------------------- /ch04_01_admin/app/styles/main.css: -------------------------------------------------------------------------------- 1 | .split-screen { 2 | display: inline-block; 3 | box-sizing: border-box; 4 | vertical-align: top; 5 | width: 48%; 6 | } 7 | 8 | .simple-border { 9 | border: black dashed 1px; 10 | } 11 | 12 | .padded { 13 | padding: 10px; 14 | } 15 | 16 | form input.ng-invalid.ng-dirty { 17 | background-color: #ffcbc1; 18 | } 19 | 20 | form input.ng-invalid:focus { 21 | outline-color: red; 22 | } 23 | 24 | form input.ng-valid.ng-dirty { 25 | background-color: #1dca07; 26 | } -------------------------------------------------------------------------------- /ch04_01_admin/app/templates/admin/book_delete.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |

3 | Soll das Buch 4 | "{{ book.title }}" 5 | wirklich gelöscht werden? 6 |

7 | 9 | 10 | -------------------------------------------------------------------------------- /ch04_01_admin/app/templates/admin/book_form.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |
3 |

Neues Buch anlegen

4 |

Buch editieren

5 | 6 |
7 | 9 | 10 | Bitte geben Sie einen Buchtitel ein. 11 | 12 |
13 | 14 |
16 | 17 | 20 | 21 | Bitte geben Sie eine ISBN ein. 22 | 23 |
24 | 25 |
27 | 28 | 30 | 31 | 32 | Das Buch muss min. eine Seite enthalten. 33 | 34 | 35 | Bitte geben Sie eine gültige Seitenzahl ein. 36 | 37 | 38 |
39 | 40 | 42 | 43 | Bitte geben Sie einen Autor ein. 44 | 45 |
46 | 47 | 49 | 50 | Bitte geben Sie einen Verlag ein. 51 | 52 |
53 | 54 | 56 | 57 | 58 | Bitte geben Sie eine gültige URL ein. 59 | 60 | 61 | Bitte geben Sie die Webseite des Verlags ein. 62 | 63 | 64 |
65 | 66 | 68 | 71 |
72 |
73 |
74 |

Vorschau

75 |
77 |
78 |
-------------------------------------------------------------------------------- /ch04_01_admin/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 |

21 |
22 |

23 |

24 | Zurück 25 | -------------------------------------------------------------------------------- /ch04_01_admin/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch04_01_admin/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 | 4 | 5 | 6 | 7 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 |
8 | 11 | 12 | 15 | 18 | 19 | 25 | 27 | Löschen 28 | 29 |
33 |

34 | 35 | Neues Buch anlegen 36 | 37 |

-------------------------------------------------------------------------------- /ch04_01_admin/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch04_01_admin/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/angular/angular.js', 17 | 'app/lib/angular-route/angular-route.js', 18 | 'app/lib/angular-mocks/angular-mocks.js', 19 | 'app/scripts/app.js', 20 | 'app/scripts/**/*.js', 21 | 'test/unit/**/*.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 33 | reporters: ['progress'], 34 | 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | 40 | // enable / disable colors in the output (reporters and logs) 41 | colors: true, 42 | 43 | 44 | // level of logging 45 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 46 | logLevel: config.LOG_DEBUG, 47 | 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: false, 51 | 52 | 53 | // Start these browsers, currently available: 54 | // - Chrome 55 | // - ChromeCanary 56 | // - Firefox 57 | // - Opera 58 | // - Safari (only Mac) 59 | // - PhantomJS 60 | // - IE (only Windows) 61 | browsers: ['Chrome'], 62 | 63 | 64 | // If browser does not capture in given timeout [ms], kill it 65 | captureTimeout: 60000, 66 | 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: true 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /ch04_01_admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch04_01_admin/test/e2e/templates/admin_delete_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | enterExampleBook('button.bm-submit-btn'); 21 | }); 22 | 23 | var selector = 'table.bm-book-list tr'; 24 | 25 | it('should navigate to admin list view containing the edited book entry', function () { 26 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 27 | for (var i = 0, n = editLinks.length; i < n; i++) { 28 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 29 | executeAndCheck(i); 30 | break; 31 | } 32 | } 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | var executeAndCheck = function(editLinkIndex) { 39 | var entryCount = 4; 40 | 41 | expect( 42 | repeater(selector).count() 43 | ).toBe(entryCount); 44 | 45 | // specify selector for the delete link 46 | var deleteLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-delete-link'; 47 | 48 | // click on delete link 49 | element(deleteLink).click(); 50 | 51 | expect( 52 | browser().location().path() 53 | ).toEqual('/admin/books/' + exampleBook.isbn + '/delete'); 54 | 55 | // in deletion dialog click on delete button 56 | element('button.bm-delete-btn').click(); 57 | 58 | // admin list view should show up 59 | expect( 60 | browser().location().path() 61 | ).toEqual('/admin/books'); 62 | 63 | // list shouldn't contain the deleted entry anymore 64 | expect( 65 | repeater(selector).column('book.title') 66 | ).not().toContain(exampleBook.title); 67 | 68 | expect( 69 | repeater(selector).count() 70 | ).toBe(entryCount - 1); 71 | }; 72 | 73 | var enterExampleBook = function(clickSelector) { 74 | input('book.title').enter(exampleBook.title); 75 | input('book.subtitle').enter(exampleBook.subtitle); 76 | input('book.isbn').enter(exampleBook.isbn); 77 | input('book.abstract').enter(exampleBook.abstract); 78 | input('book.numPages').enter(exampleBook.numPages); 79 | input('book.author').enter(exampleBook.author); 80 | input('book.publisher.name').enter(exampleBook.publisher.name); 81 | input('book.publisher.url').enter(exampleBook.publisher.url); 82 | 83 | element(clickSelector).click(); 84 | }; 85 | }); 86 | -------------------------------------------------------------------------------- /ch04_01_admin/test/e2e/templates/admin_edit_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | enterExampleBook('button.bm-submit-btn'); 21 | }); 22 | 23 | var selector = 'table.bm-book-list tr'; 24 | 25 | it('should navigate to admin list view containing the edited book entry', function () { 26 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 27 | for (var i = 0, n = editLinks.length; i < n; i++) { 28 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 29 | executeAndCheck(i); 30 | break; 31 | } 32 | } 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | var executeAndCheck = function(editLinkIndex) { 39 | // specify selector for the edit link 40 | var editLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-edit-link'; 41 | 42 | // click on edit link 43 | element(editLink).click(); 44 | 45 | // in edit form enter 'TEST' into book title field 46 | input('book.title').enter('TEST'); 47 | 48 | // click on submit button 49 | element('button.bm-submit-btn').click(); 50 | 51 | // admin list view should show up 52 | expect( 53 | browser().location().path() 54 | ).toEqual('/admin/books'); 55 | 56 | // there should be a 'TEST' entry 57 | expect( 58 | repeater(selector).column('book.title') 59 | ).toContain('TEST'); 60 | 61 | // list shouldn't contain entry with old title 62 | expect( 63 | repeater(selector).column('book.title') 64 | ).not().toContain(exampleBook.title); 65 | }; 66 | 67 | var enterExampleBook = function(clickSelector) { 68 | input('book.title').enter(exampleBook.title); 69 | input('book.subtitle').enter(exampleBook.subtitle); 70 | input('book.isbn').enter(exampleBook.isbn); 71 | input('book.abstract').enter(exampleBook.abstract); 72 | input('book.numPages').enter(exampleBook.numPages); 73 | input('book.author').enter(exampleBook.author); 74 | input('book.publisher.name').enter(exampleBook.publisher.name); 75 | input('book.publisher.url').enter(exampleBook.publisher.url); 76 | 77 | element(clickSelector).click(); 78 | }; 79 | }); 80 | -------------------------------------------------------------------------------- /ch04_01_admin/test/e2e/templates/admin_new_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | }); 21 | 22 | var selector = 'table.bm-book-list tr'; 23 | 24 | it('should navigate to admin list view having one more book entry', function () { 25 | enterExampleBook('button.bm-submit-btn'); 26 | 27 | expect( 28 | browser().location().path() 29 | ).toEqual('/admin/books'); 30 | 31 | expect( 32 | repeater(selector).column('book.title') 33 | ).toContain(exampleBook.title); 34 | }); 35 | 36 | it('should navigate to admin list view without having one more book entry', function () { 37 | enterExampleBook('button.bm-cancel-btn'); 38 | 39 | expect( 40 | browser().location().path() 41 | ).toEqual('/admin/books'); 42 | 43 | expect( 44 | repeater(selector).column('book.title') 45 | ).not().toContain(exampleBook.title); 46 | }); 47 | 48 | var enterExampleBook = function(clickSelector) { 49 | input('book.title').enter(exampleBook.title); 50 | input('book.subtitle').enter(exampleBook.subtitle); 51 | input('book.isbn').enter(exampleBook.isbn); 52 | input('book.abstract').enter(exampleBook.abstract); 53 | input('book.numPages').enter(exampleBook.numPages); 54 | input('book.author').enter(exampleBook.author); 55 | input('book.publisher.name').enter(exampleBook.publisher.name); 56 | input('book.publisher.url').enter(exampleBook.publisher.url); 57 | 58 | element(clickSelector).click(); 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /ch04_01_admin/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch04_02_tags/app/component_templates/directives/tags.html: -------------------------------------------------------------------------------- 1 | 2 | {{ tag }} 3 | -------------------------------------------------------------------------------- /ch04_02_tags/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |

BookMonkey

21 |
22 | 23 |
24 |
25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ch04_02_tags/app/lib/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angularjs-de/dpunkt-buch-beispiele/b94596cd63468df61064b3cbbcf19d9c9dda2c67/ch04_02_tags/app/lib/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /ch04_02_tags/app/lib/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angularjs-de/dpunkt-buch-beispiele/b94596cd63468df61064b3cbbcf19d9c9dda2c67/ch04_02_tags/app/lib/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }) 15 | 16 | /* Admin routes */ 17 | .when('/admin/books', { 18 | // we reuse the template from the list view 19 | templateUrl: 'templates/book_list.html', 20 | controller: 'AdminBookListCtrl' 21 | }) 22 | .when('/admin/books/new', { 23 | templateUrl: 'templates/admin/book_form.html', 24 | controller: 'AdminNewBookCtrl' 25 | }) 26 | .when('/admin/books/:isbn/edit', { 27 | templateUrl: 'templates/admin/book_form.html', 28 | controller: 'AdminEditBookCtrl' 29 | }) 30 | .when('/admin/books/:isbn/delete', { 31 | templateUrl: 'templates/admin/book_delete.html', 32 | controller: 'AdminDeleteBookCtrl' 33 | }) 34 | 35 | /* Default route */ 36 | .otherwise({ 37 | redirectTo: '/books' 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/admin_book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminBookListCtrl', function ($scope, BookDataService) { 4 | $scope.isAdmin = true; 5 | 6 | $scope.getBookOrder = function(book) { 7 | return book.title; 8 | }; 9 | 10 | $scope.books = BookDataService.getBooks(); 11 | }); 12 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/admin_delete_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminDeleteBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | var isbn = $routeParams.isbn; 5 | $scope.book = BookDataService.getBookByIsbn(isbn); 6 | 7 | $scope.deleteBook = function(isbn) { 8 | BookDataService.deleteBookByIsbn(isbn); 9 | goToAdminListView(); 10 | }; 11 | 12 | $scope.cancel = function() { 13 | goToAdminListView(); 14 | }; 15 | 16 | var goToAdminListView = function() { 17 | $location.path('/admin/books'); 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/admin_edit_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminEditBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | $scope.isEditMode = true; 5 | $scope.submitBtnLabel = 'Buch editieren'; 6 | 7 | var isbn = $routeParams.isbn; 8 | $scope.book = BookDataService.getBookByIsbn(isbn); 9 | 10 | $scope.submitAction = function() { 11 | BookDataService.updateBook($scope.book); 12 | goToAdminListView(); 13 | }; 14 | 15 | $scope.cancelAction = function() { 16 | goToAdminListView(); 17 | }; 18 | 19 | var goToAdminListView = function() { 20 | $location.path('/admin/books'); 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/admin_new_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminNewBookCtrl', function ($scope, $location, BookDataService) { 4 | $scope.book = {}; 5 | $scope.submitBtnLabel = 'Buch anlegen'; 6 | 7 | $scope.submitAction = function() { 8 | BookDataService.storeBook($scope.book); 9 | goToAdminListView(); 10 | }; 11 | 12 | $scope.cancelAction = function() { 13 | goToAdminListView(); 14 | }; 15 | 16 | var goToAdminListView = function() { 17 | $location.path('/admin/books'); 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', 4 | function ($scope, $location, $routeParams, BookDataService) { 5 | var isbn = $routeParams.isbn; 6 | $scope.book = BookDataService.getBookByIsbn(isbn); 7 | 8 | $scope.goToListView = function() { 9 | if ($location.path().indexOf('/admin') === 0) { 10 | $location.path('/admin/books'); 11 | } 12 | else { 13 | $location.path('/books'); 14 | } 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, BookDataService) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | $scope.books = BookDataService.getBooks(); 9 | }); 10 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/directives/tags.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.directive('tags', function() { 4 | return { 5 | restrict: 'E', 6 | scope: { 7 | tagData: '=' 8 | }, 9 | templateUrl: 'component_templates/directives/tags.html' 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /ch04_02_tags/app/scripts/directives/tokenfield.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.directive('tokenfield', function(BookDataService) { 4 | return { 5 | restrict: 'A', 6 | scope: { 7 | // tokenfield holds a two-way bounded 8 | // reference to the book object 9 | tokenfield: '=' 10 | }, 11 | link: function(scope, elem) { 12 | // only call $apply when directive is initialized 13 | // to avoid 'digest already in progress' 14 | var initialized = false; 15 | 16 | elem.tokenfield({ 17 | autocomplete: { 18 | source: BookDataService.getTags(), 19 | delay: 100 20 | }, 21 | showAutocompleteOnFocus: false, 22 | allowDuplicates: false, 23 | createTokensOnBlur: true 24 | }).on('afterCreateToken', function (e) { 25 | addToken(e.token.value); 26 | }).on('removeToken', function (e) { 27 | removeToken(e.token.value); 28 | }); 29 | 30 | function addToken(token) { 31 | if (initialized) { 32 | // $apply() to trigger dirty checking 33 | // because of 3rd-party callback 34 | scope.$apply(function() { 35 | scope.tokenfield.tags.push(token); 36 | }); 37 | } 38 | } 39 | 40 | function removeToken(token) { 41 | if (initialized) { 42 | // $apply() to trigger dirty checking 43 | // because of 3rd-party callback 44 | scope.$apply(function() { 45 | var tags = scope.tokenfield.tags, 46 | i = tags.length; 47 | while(i--) { 48 | if (token === tags[i]) { 49 | tags.splice(i, 1); 50 | break; 51 | } 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function init() { 58 | if (angular.isDefined(scope.tokenfield.tags)) { 59 | if (scope.tokenfield.tags.length > 0) { 60 | // this call emits an 'afterCreateToken' event 61 | // and this would imply a 'digest already in progress' 62 | // without the initialized flag 63 | elem.tokenfield('setTokens', scope.tokenfield.tags); 64 | } 65 | } 66 | else { 67 | scope.tokenfield.tags = []; 68 | } 69 | 70 | initialized = true; 71 | } 72 | 73 | init(); 74 | } 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /ch04_02_tags/app/styles/main.css: -------------------------------------------------------------------------------- 1 | .bm-content { 2 | padding: 10px; 3 | } 4 | 5 | .split-screen { 6 | display: inline-block; 7 | box-sizing: border-box; 8 | vertical-align: top; 9 | width: 48%; 10 | } 11 | 12 | .simple-border { 13 | border: black dashed 1px; 14 | } 15 | 16 | .padded { 17 | padding: 10px; 18 | } 19 | 20 | form input.ng-invalid.ng-dirty { 21 | background-color: #ffcbc1; 22 | } 23 | 24 | form input.ng-invalid:focus { 25 | outline-color: red; 26 | } 27 | 28 | form input.ng-valid.ng-dirty { 29 | background-color: #1dca07; 30 | } 31 | 32 | .bm-tag { 33 | font-family: 'Consolas', monospace; 34 | font-size: 0.9em; 35 | background-color: #276dff; 36 | color: #ffffff; 37 | margin: 2px; 38 | padding: 2px; 39 | border-radius: 8px; 40 | } -------------------------------------------------------------------------------- /ch04_02_tags/app/templates/admin/book_delete.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |

Soll das Buch "{{ book.title }}" wirklich gelöscht werden?

3 | 5 | 6 | -------------------------------------------------------------------------------- /ch04_02_tags/app/templates/admin/book_form.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |
3 |

Neues Buch anlegen

4 |

Buch editieren

5 | 6 |
7 | 9 | 10 | Bitte geben Sie einen Buchtitel ein. 11 | 12 |
13 | 14 |
16 | 17 | 20 | 21 | Bitte geben Sie eine ISBN ein. 22 | 23 |
24 | 25 |
27 | 28 | 30 | 31 | 32 | Das Buch muss min. eine Seite enthalten. 33 | 34 | 35 | Bitte geben Sie eine gültige Seitenzahl ein. 36 | 37 | 38 |
39 | 40 | 42 | 43 | Bitte geben Sie einen Autor ein. 44 | 45 |
46 | 47 | 49 | 50 | Bitte geben Sie einen Verlag ein. 51 | 52 |
53 | 54 | 56 | 57 | 58 | Bitte geben Sie eine gültige URL ein. 59 | 60 | 61 | Bitte geben Sie die Webseite des Verlags ein. 62 | 63 | 64 |
65 | 66 | 67 | 68 | 70 | 73 |
74 |
75 |
76 |

Vorschau

77 |
79 |
80 |
-------------------------------------------------------------------------------- /ch04_02_tags/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 | 21 |

22 |
23 |

24 |

25 | Zurück 26 | -------------------------------------------------------------------------------- /ch04_02_tags/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch04_02_tags/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 | 4 | 5 | 6 | 7 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 |
8 | 11 | 12 | 15 | 18 | 19 | 25 | 27 | Löschen 28 | 29 |
33 |

34 | 35 | Neues Buch anlegen 36 | 37 |

-------------------------------------------------------------------------------- /ch04_02_tags/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch04_02_tags/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/jquery/jquery-1.10.2.min.js', 17 | 'app/lib/jquery-ui/jquery-ui.min.js', 18 | 'app/lib/bootstrap-tokenfield/bootstrap-tokenfield.min.js', 19 | 'app/lib/angular/angular.js', 20 | 'app/lib/angular-route/angular-route.js', 21 | 'app/lib/angular-mocks/angular-mocks.js', 22 | 'app/scripts/app.js', 23 | 'app/scripts/**/*.js', 24 | 'test/unit/**/*.js' 25 | ], 26 | 27 | 28 | // list of files to exclude 29 | exclude: [ 30 | 31 | ], 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 36 | reporters: ['progress'], 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | 43 | // enable / disable colors in the output (reporters and logs) 44 | colors: true, 45 | 46 | 47 | // level of logging 48 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 49 | logLevel: config.LOG_DEBUG, 50 | 51 | 52 | // enable / disable watching file and executing tests whenever any file changes 53 | autoWatch: false, 54 | 55 | 56 | // Start these browsers, currently available: 57 | // - Chrome 58 | // - ChromeCanary 59 | // - Firefox 60 | // - Opera 61 | // - Safari (only Mac) 62 | // - PhantomJS 63 | // - IE (only Windows) 64 | browsers: ['Chrome'], 65 | 66 | 67 | // If browser does not capture in given timeout [ms], kill it 68 | captureTimeout: 60000, 69 | 70 | 71 | // Continuous Integration mode 72 | // if true, it capture browsers, run tests and exit 73 | singleRun: true 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /ch04_02_tags/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch04_02_tags/test/e2e/templates/admin_delete_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | enterExampleBook('button.bm-submit-btn'); 21 | }); 22 | 23 | var selector = 'table.bm-book-list tr'; 24 | 25 | it('should navigate to admin list view containing the edited book entry', function () { 26 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 27 | for (var i = 0, n = editLinks.length; i < n; i++) { 28 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 29 | executeAndCheck(i); 30 | break; 31 | } 32 | } 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | var executeAndCheck = function(editLinkIndex) { 39 | var entryCount = 4; 40 | 41 | expect( 42 | repeater(selector).count() 43 | ).toBe(entryCount); 44 | 45 | // specify selector for the delete link 46 | var deleteLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-delete-link'; 47 | 48 | // click on delete link 49 | element(deleteLink).click(); 50 | 51 | expect( 52 | browser().location().path() 53 | ).toEqual('/admin/books/' + exampleBook.isbn + '/delete'); 54 | 55 | // in deletion dialog click on delete button 56 | element('button.bm-delete-btn').click(); 57 | 58 | // admin list view should show up 59 | expect( 60 | browser().location().path() 61 | ).toEqual('/admin/books'); 62 | 63 | // list shouldn't contain the deleted entry anymore 64 | expect( 65 | repeater(selector).column('book.title') 66 | ).not().toContain(exampleBook.title); 67 | 68 | expect( 69 | repeater(selector).count() 70 | ).toBe(entryCount - 1); 71 | }; 72 | 73 | var enterExampleBook = function(clickSelector) { 74 | input('book.title').enter(exampleBook.title); 75 | input('book.subtitle').enter(exampleBook.subtitle); 76 | input('book.isbn').enter(exampleBook.isbn); 77 | input('book.abstract').enter(exampleBook.abstract); 78 | input('book.numPages').enter(exampleBook.numPages); 79 | input('book.author').enter(exampleBook.author); 80 | input('book.publisher.name').enter(exampleBook.publisher.name); 81 | input('book.publisher.url').enter(exampleBook.publisher.url); 82 | 83 | element(clickSelector).click(); 84 | }; 85 | }); 86 | -------------------------------------------------------------------------------- /ch04_02_tags/test/e2e/templates/admin_edit_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | enterExampleBook('button.bm-submit-btn'); 21 | }); 22 | 23 | var selector = 'table.bm-book-list tr'; 24 | 25 | it('should navigate to admin list view containing the edited book entry', function () { 26 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 27 | for (var i = 0, n = editLinks.length; i < n; i++) { 28 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 29 | executeAndCheck(i); 30 | break; 31 | } 32 | } 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | var executeAndCheck = function(editLinkIndex) { 39 | // specify selector for the edit link 40 | var editLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-edit-link'; 41 | 42 | // click on edit link 43 | element(editLink).click(); 44 | 45 | // in edit form enter 'TEST' into book title field 46 | input('book.title').enter('TEST'); 47 | 48 | // click on submit button 49 | element('button.bm-submit-btn').click(); 50 | 51 | // admin list view should show up 52 | expect( 53 | browser().location().path() 54 | ).toEqual('/admin/books'); 55 | 56 | // there should be a 'TEST' entry 57 | expect( 58 | repeater(selector).column('book.title') 59 | ).toContain('TEST'); 60 | 61 | // list shouldn't contain entry with old title 62 | expect( 63 | repeater(selector).column('book.title') 64 | ).not().toContain(exampleBook.title); 65 | }; 66 | 67 | var enterExampleBook = function(clickSelector) { 68 | input('book.title').enter(exampleBook.title); 69 | input('book.subtitle').enter(exampleBook.subtitle); 70 | input('book.isbn').enter(exampleBook.isbn); 71 | input('book.abstract').enter(exampleBook.abstract); 72 | input('book.numPages').enter(exampleBook.numPages); 73 | input('book.author').enter(exampleBook.author); 74 | input('book.publisher.name').enter(exampleBook.publisher.name); 75 | input('book.publisher.url').enter(exampleBook.publisher.url); 76 | 77 | element(clickSelector).click(); 78 | }; 79 | }); 80 | -------------------------------------------------------------------------------- /ch04_02_tags/test/e2e/templates/admin_new_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/books/new'); 19 | browser().reload(); 20 | }); 21 | 22 | var selector = 'table.bm-book-list tr'; 23 | 24 | it('should navigate to admin list view having one more book entry', function () { 25 | enterExampleBook('button.bm-submit-btn'); 26 | 27 | expect( 28 | browser().location().path() 29 | ).toEqual('/admin/books'); 30 | 31 | expect( 32 | repeater(selector).column('book.title') 33 | ).toContain(exampleBook.title); 34 | }); 35 | 36 | it('should navigate to admin list view without having one more book entry', function () { 37 | enterExampleBook('button.bm-cancel-btn'); 38 | 39 | expect( 40 | browser().location().path() 41 | ).toEqual('/admin/books'); 42 | 43 | expect( 44 | repeater(selector).column('book.title') 45 | ).not().toContain(exampleBook.title); 46 | }); 47 | 48 | var enterExampleBook = function(clickSelector) { 49 | input('book.title').enter(exampleBook.title); 50 | input('book.subtitle').enter(exampleBook.subtitle); 51 | input('book.isbn').enter(exampleBook.isbn); 52 | input('book.abstract').enter(exampleBook.abstract); 53 | input('book.numPages').enter(exampleBook.numPages); 54 | input('book.author').enter(exampleBook.author); 55 | input('book.publisher.name').enter(exampleBook.publisher.name); 56 | input('book.publisher.url').enter(exampleBook.publisher.url); 57 | 58 | element(clickSelector).click(); 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /ch04_02_tags/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch04_02_tags/test/unit/directives/tokenfield.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: tokenfield', function () { 4 | 5 | var $compile, 6 | $rootScope, 7 | element, 8 | scope; 9 | 10 | var testTags = ['test1', 'test2', 'test3']; 11 | 12 | // load the application module 13 | beforeEach(module('bmApp')); 14 | 15 | // get a reference to all used services 16 | beforeEach(inject(function (_$compile_, _$rootScope_) { 17 | $compile = _$compile_; 18 | $rootScope = _$rootScope_; 19 | })); 20 | 21 | // actual init logic 22 | beforeEach(function() { 23 | scope = $rootScope.$new(); 24 | 25 | scope.book = { 26 | tags: angular.copy(testTags) 27 | }; 28 | 29 | element = $compile('')(scope); 30 | }); 31 | 32 | it('should properly create available tokens on initialization', function () { 33 | var tokens = element.parent().find('div.token'); 34 | 35 | expect(tokens.length).toBe(testTags.length); 36 | 37 | tokens.each(function(index, token) { 38 | expect(angular.element(token).data('value')).toEqual(testTags[index]); 39 | }); 40 | }); 41 | 42 | it('should properly add new tokens', function () { 43 | var tokenCount = element.parent().find('div.token').length, 44 | tokenInput = element.parent().find('input.token-input'), 45 | testToken = 'test4'; 46 | 47 | tokenInput.focus(); 48 | tokenInput.val(testToken); 49 | tokenInput.blur(); 50 | 51 | var tokenCountAfter = element.parent().find('div.token').length; 52 | expect(tokenCountAfter).toBe(tokenCount + 1); 53 | expect(scope.book.tags.length).toBe(tokenCountAfter); 54 | expect(element.parent().html()).toContain(testToken); 55 | }); 56 | 57 | it('should properly remove new tokens', function () { 58 | var indexToRemove = 0; 59 | 60 | angular.element( 61 | element.parent().find('div.token')[indexToRemove] 62 | ).find('a.close').click(); 63 | 64 | expect(element.parent().find('div.token').length).toBe(testTags.length - 1); 65 | expect(scope.book.tags.length).toBe(testTags.length - 1); 66 | expect(element.parent().html()).not.toContain(testTags[indexToRemove]); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /ch04_03_http/app/component_templates/directives/tags.html: -------------------------------------------------------------------------------- 1 | 2 | {{ tag }} 3 | -------------------------------------------------------------------------------- /ch04_03_http/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |

BookMonkey

21 |
22 | 23 |
24 |
25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ch04_03_http/app/lib/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angularjs-de/dpunkt-buch-beispiele/b94596cd63468df61064b3cbbcf19d9c9dda2c67/ch04_03_http/app/lib/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /ch04_03_http/app/lib/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angularjs-de/dpunkt-buch-beispiele/b94596cd63468df61064b3cbbcf19d9c9dda2c67/ch04_03_http/app/lib/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bmApp = angular.module('bmApp', ['ngRoute']); 4 | 5 | bmApp.config(function ($routeProvider) { 6 | $routeProvider.when('/books/:isbn', { 7 | //templateUrl: 'templates/book_details_with_expressions.html', 8 | templateUrl: 'templates/book_details.html', 9 | controller: 'BookDetailsCtrl' 10 | }) 11 | .when('/books', { 12 | templateUrl: 'templates/book_list.html', 13 | controller: 'BookListCtrl' 14 | }) 15 | 16 | /* Admin routes */ 17 | .when('/admin/books', { 18 | // we reuse the template from the list view 19 | templateUrl: 'templates/book_list.html', 20 | controller: 'AdminBookListCtrl' 21 | }) 22 | .when('/admin/books/new', { 23 | templateUrl: 'templates/admin/book_form.html', 24 | controller: 'AdminNewBookCtrl' 25 | }) 26 | .when('/admin/books/:isbn/edit', { 27 | templateUrl: 'templates/admin/book_form.html', 28 | controller: 'AdminEditBookCtrl' 29 | }) 30 | .when('/admin/books/:isbn/delete', { 31 | templateUrl: 'templates/admin/book_delete.html', 32 | controller: 'AdminDeleteBookCtrl' 33 | }) 34 | 35 | /* Route to reset backend */ 36 | .when('/admin/reset', { 37 | templateUrl: 'templates/admin/reset.html', 38 | controller: 'AdminResetCtrl' 39 | }) 40 | 41 | /* Default route */ 42 | .otherwise({ 43 | redirectTo: '/books' 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/admin_book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminBookListCtrl', function ($scope, BookDataService) { 4 | $scope.isAdmin = true; 5 | 6 | $scope.getBookOrder = function(book) { 7 | return book.title; 8 | }; 9 | 10 | BookDataService.getBooks().then(function(res) { 11 | $scope.books = res.data; 12 | }, function(error) { 13 | console.log('An error occurred!', error); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/admin_delete_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminDeleteBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | var isbn = $routeParams.isbn; 5 | BookDataService.getBookByIsbn(isbn).then(function(res) { 6 | $scope.book = res.data; 7 | }, function(error) { 8 | console.log('An error occurred!', error); 9 | }); 10 | 11 | $scope.deleteBook = function(isbn) { 12 | BookDataService.deleteBookByIsbn(isbn).then(function() { 13 | goToAdminListView(); 14 | }, function(error) { 15 | console.log('An error occurred!', error); 16 | }); 17 | 18 | }; 19 | 20 | $scope.cancel = function() { 21 | goToAdminListView(); 22 | }; 23 | 24 | var goToAdminListView = function() { 25 | $location.path('/admin/books'); 26 | }; 27 | }); 28 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/admin_edit_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminEditBookCtrl', function ($scope, $routeParams, $location, BookDataService) { 4 | $scope.isEditMode = true; 5 | $scope.submitBtnLabel = 'Buch editieren'; 6 | 7 | var isbn = $routeParams.isbn; 8 | BookDataService.getBookByIsbn(isbn).then(function(res) { 9 | $scope.book = res.data; 10 | }, function(error) { 11 | console.log('An error occurred!', error); 12 | }); 13 | 14 | $scope.submitAction = function() { 15 | BookDataService.updateBook($scope.book).then(function() { 16 | goToAdminListView(); 17 | }, function(error) { 18 | console.log('An error occurred!', error); 19 | }); 20 | }; 21 | 22 | $scope.cancelAction = function() { 23 | goToAdminListView(); 24 | }; 25 | 26 | var goToAdminListView = function() { 27 | $location.path('/admin/books'); 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/admin_new_book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminNewBookCtrl', function ($scope, $location, BookDataService) { 4 | $scope.book = {}; 5 | $scope.submitBtnLabel = 'Buch anlegen'; 6 | 7 | $scope.submitAction = function() { 8 | BookDataService.storeBook($scope.book).then(function() { 9 | goToAdminListView(); 10 | }, function(error) { 11 | console.log('An error occurred!', error); 12 | }); 13 | }; 14 | 15 | $scope.cancelAction = function() { 16 | goToAdminListView(); 17 | }; 18 | 19 | var goToAdminListView = function() { 20 | $location.path('/admin/books'); 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/admin_reset.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('AdminResetCtrl', function ($scope, $http) { 4 | $scope.reset = false; 5 | 6 | $http.get('http://localhost:4730/api/reset').then(function () { 7 | $scope.reset = true; 8 | }, function (error) { 9 | console.log('An error occurred!', error); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookDetailsCtrl', 4 | function ($scope, $location, $routeParams, BookDataService) { 5 | var isbn = $routeParams.isbn; 6 | 7 | BookDataService.getBookByIsbn(isbn).then(function(res) { 8 | $scope.book = res.data; 9 | }, function(error) { 10 | console.log('An error occurred!', error); 11 | }); 12 | 13 | $scope.goToListView = function() { 14 | if ($location.path().indexOf('/admin') === 0) { 15 | $location.path('/admin/books'); 16 | } 17 | else { 18 | $location.path('/books'); 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.controller('BookListCtrl', function ($scope, BookDataService) { 4 | $scope.getBookOrder = function(book) { 5 | return book.title; 6 | }; 7 | 8 | BookDataService.getBooks().then(function(res) { 9 | $scope.books = res.data; 10 | }, function(error) { 11 | console.log('An error occurred!', error); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/directives/tags.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.directive('tags', function() { 4 | return { 5 | restrict: 'E', 6 | scope: { 7 | tagData: '=' 8 | }, 9 | templateUrl: 'component_templates/directives/tags.html' 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /ch04_03_http/app/scripts/services/book_data.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | bmApp.factory('BookDataService', function($http) { 4 | var srv = {}; 5 | 6 | srv._baseUrl = 'http://localhost:4730'; 7 | 8 | // Service implementation 9 | srv.getBookByIsbn = function(isbn) { 10 | return $http.get( 11 | srv._baseUrl + '/api/books/' + isbn 12 | ); 13 | }; 14 | 15 | srv.getBooks = function() { 16 | return $http.get( 17 | srv._baseUrl + '/api/books' 18 | ); 19 | }; 20 | 21 | srv.getTags = function() { 22 | return $http.get( 23 | srv._baseUrl + '/api/tags' 24 | ); 25 | }; 26 | 27 | srv.storeBook = function(book) { 28 | return $http.post( 29 | srv._baseUrl + '/api/books', book 30 | ); 31 | }; 32 | 33 | srv.updateBook = function(book) { 34 | return $http.put( 35 | srv._baseUrl + '/api/books/' + book.isbn, book 36 | ); 37 | }; 38 | 39 | srv.deleteBookByIsbn = function(isbn) { 40 | return $http.delete( 41 | srv._baseUrl + '/api/books/' + isbn 42 | ); 43 | }; 44 | 45 | // Public API 46 | return { 47 | getBookByIsbn: function(isbn) { 48 | return srv.getBookByIsbn(isbn); 49 | }, 50 | getBooks: function() { 51 | return srv.getBooks(); 52 | }, 53 | getTags: function() { 54 | return srv.getTags(); 55 | }, 56 | storeBook: function(book) { 57 | return srv.storeBook(book); 58 | }, 59 | updateBook: function(book) { 60 | return srv.updateBook(book); 61 | }, 62 | deleteBookByIsbn: function(isbn) { 63 | return srv.deleteBookByIsbn(isbn); 64 | } 65 | }; 66 | }); -------------------------------------------------------------------------------- /ch04_03_http/app/styles/main.css: -------------------------------------------------------------------------------- 1 | .bm-content { 2 | padding: 10px; 3 | } 4 | 5 | .split-screen { 6 | display: inline-block; 7 | box-sizing: border-box; 8 | vertical-align: top; 9 | width: 48%; 10 | } 11 | 12 | .simple-border { 13 | border: black dashed 1px; 14 | } 15 | 16 | .padded { 17 | padding: 10px; 18 | } 19 | 20 | form input.ng-invalid.ng-dirty { 21 | background-color: #ffcbc1; 22 | } 23 | 24 | form input.ng-invalid:focus { 25 | outline-color: red; 26 | } 27 | 28 | form input.ng-valid.ng-dirty { 29 | background-color: #1dca07; 30 | } 31 | 32 | .bm-tag { 33 | font-family: 'Consolas', monospace; 34 | font-size: 0.9em; 35 | background-color: #276dff; 36 | color: #ffffff; 37 | margin: 2px; 38 | padding: 2px; 39 | border-radius: 8px; 40 | } -------------------------------------------------------------------------------- /ch04_03_http/app/templates/admin/book_delete.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |

Soll das Buch "{{ book.title }}" wirklich gelöscht werden?

3 | 5 | 6 | -------------------------------------------------------------------------------- /ch04_03_http/app/templates/admin/book_form.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |
3 |

Neues Buch anlegen

4 |

Buch editieren

5 | 6 |
7 | 9 | 10 | Bitte geben Sie einen Buchtitel ein. 11 | 12 |
13 | 14 |
16 | 17 | 20 | 21 | Bitte geben Sie eine ISBN ein. 22 | 23 |
24 | 25 |
27 | 28 | 30 | 31 | 32 | Das Buch muss min. eine Seite enthalten. 33 | 34 | 35 | Bitte geben Sie eine gültige Seitenzahl ein. 36 | 37 | 38 |
39 | 40 | 42 | 43 | Bitte geben Sie einen Autor ein. 44 | 45 |
46 | 47 | 49 | 50 | Bitte geben Sie einen Verlag ein. 51 | 52 |
53 | 54 | 56 | 57 | 58 | Bitte geben Sie eine gültige URL ein. 59 | 60 | 61 | Bitte geben Sie die Webseite des Verlags ein. 62 | 63 | 64 |
65 | 66 | 67 | 68 | 70 | 73 |
74 |
75 |
76 |

Vorschau

77 |
79 |
80 |
-------------------------------------------------------------------------------- /ch04_03_http/app/templates/admin/reset.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 |

Backend wurde erfolgreich zurückgesetzt!

3 |

Backend wird gerade zurückgesetzt...

-------------------------------------------------------------------------------- /ch04_03_http/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 | 21 |

22 |
23 |

24 |

25 | Zurück 26 | -------------------------------------------------------------------------------- /ch04_03_http/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch04_03_http/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 |

Administrationsbereich

2 | 4 | 5 | 6 | 7 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 |
8 | 11 | 12 | 15 | 18 | 19 | 25 | 27 | Löschen 28 | 29 |
33 |

34 | 35 | Neues Buch anlegen 36 | 37 |

-------------------------------------------------------------------------------- /ch04_03_http/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch04_03_http/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'app/lib/jquery/jquery-1.10.2.min.js', 17 | 'app/lib/jquery-ui/jquery-ui.min.js', 18 | 'app/lib/bootstrap-tokenfield/bootstrap-tokenfield.min.js', 19 | 'app/lib/angular/angular.js', 20 | 'app/lib/angular-route/angular-route.js', 21 | 'app/lib/angular-mocks/angular-mocks.js', 22 | 'app/scripts/app.js', 23 | 'app/scripts/**/*.js', 24 | 'test/unit/**/*.js' 25 | ], 26 | 27 | 28 | // list of files to exclude 29 | exclude: [ 30 | 31 | ], 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 36 | reporters: ['progress'], 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | 43 | // enable / disable colors in the output (reporters and logs) 44 | colors: true, 45 | 46 | 47 | // level of logging 48 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 49 | logLevel: config.LOG_DEBUG, 50 | 51 | 52 | // enable / disable watching file and executing tests whenever any file changes 53 | autoWatch: false, 54 | 55 | 56 | // Start these browsers, currently available: 57 | // - Chrome 58 | // - ChromeCanary 59 | // - Firefox 60 | // - Opera 61 | // - Safari (only Mac) 62 | // - PhantomJS 63 | // - IE (only Windows) 64 | browsers: ['Chrome'], 65 | 66 | 67 | // If browser does not capture in given timeout [ms], kill it 68 | captureTimeout: 60000, 69 | 70 | 71 | // Continuous Integration mode 72 | // if true, it capture browsers, run tests and exit 73 | singleRun: true 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /ch04_03_http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey", 3 | "version": "0.0.1", 4 | "description": "A simple AngularJS app serving as an example for the German book 'AngularJS: Eine praktische Einführung in das JavaScript-Framework'.", 5 | "main": "app/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "posttest": "karma start karma-e2e.conf.js" 12 | }, 13 | "repository": "", 14 | "keywords": [ 15 | "angularjs", 16 | "angularjs-de" 17 | ], 18 | "author": "Philipp Tarasiewicz , Robin Böhm ", 19 | "homepage": "http://angularjs.de/", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "karma": "0.12.0", 23 | "karma-chrome-launcher": "0.1.2", 24 | "karma-jasmine": "0.2.2", 25 | "karma-ng-scenario": "0.1.0", 26 | "karma-ng-html2js-preprocessor": "0.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch04_03_http/test/e2e/templates/admin_delete_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/reset'); 19 | browser().navigateTo('/#/admin/books/new'); 20 | browser().reload(); 21 | enterExampleBook('button.bm-submit-btn'); 22 | }); 23 | 24 | var selector = 'table.bm-book-list tr'; 25 | 26 | it('should navigate to admin list view containing the edited book entry', function () { 27 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 28 | for (var i = 0, n = editLinks.length; i < n; i++) { 29 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 30 | executeAndCheck(i); 31 | break; 32 | } 33 | } 34 | 35 | done(); 36 | }); 37 | }); 38 | 39 | var executeAndCheck = function(editLinkIndex) { 40 | var entryCount = 4; 41 | 42 | expect( 43 | repeater(selector).count() 44 | ).toBe(entryCount); 45 | 46 | // specify selector for the delete link 47 | var deleteLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-delete-link'; 48 | 49 | // click on delete link 50 | element(deleteLink).click(); 51 | 52 | expect( 53 | browser().location().path() 54 | ).toEqual('/admin/books/' + exampleBook.isbn + '/delete'); 55 | 56 | // in deletion dialog click on delete button 57 | element('button.bm-delete-btn').click(); 58 | 59 | // admin list view should show up 60 | expect( 61 | browser().location().path() 62 | ).toEqual('/admin/books'); 63 | 64 | // list shouldn't contain the deleted entry anymore 65 | expect( 66 | repeater(selector).column('book.title') 67 | ).not().toContain(exampleBook.title); 68 | 69 | expect( 70 | repeater(selector).count() 71 | ).toBe(entryCount - 1); 72 | }; 73 | 74 | var enterExampleBook = function(clickSelector) { 75 | input('book.title').enter(exampleBook.title); 76 | input('book.subtitle').enter(exampleBook.subtitle); 77 | input('book.isbn').enter(exampleBook.isbn); 78 | input('book.abstract').enter(exampleBook.abstract); 79 | input('book.numPages').enter(exampleBook.numPages); 80 | input('book.author').enter(exampleBook.author); 81 | input('book.publisher.name').enter(exampleBook.publisher.name); 82 | input('book.publisher.url').enter(exampleBook.publisher.url); 83 | 84 | element(clickSelector).click(); 85 | }; 86 | }); 87 | -------------------------------------------------------------------------------- /ch04_03_http/test/e2e/templates/admin_edit_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/reset'); 19 | browser().navigateTo('/#/admin/books/new'); 20 | browser().reload(); 21 | enterExampleBook('button.bm-submit-btn'); 22 | }); 23 | 24 | var selector = 'table.bm-book-list tr'; 25 | 26 | it('should navigate to admin list view containing the edited book entry', function () { 27 | element(selector + ' a.bm-edit-link').query(function (editLinks, done) { 28 | for (var i = 0, n = editLinks.length; i < n; i++) { 29 | if (angular.element(editLinks[i]).text() === exampleBook.title) { 30 | executeAndCheck(i); 31 | break; 32 | } 33 | } 34 | 35 | done(); 36 | }); 37 | }); 38 | 39 | var executeAndCheck = function(editLinkIndex) { 40 | // specify selector for the edit link 41 | var editLink = selector + ':nth-child('+ (editLinkIndex+1) +') a.bm-edit-link'; 42 | 43 | // click on edit link 44 | element(editLink).click(); 45 | 46 | // in edit form enter 'TEST' into book title field 47 | input('book.title').enter('TEST'); 48 | 49 | // click on submit button 50 | element('button.bm-submit-btn').click(); 51 | 52 | // admin list view should show up 53 | expect( 54 | browser().location().path() 55 | ).toEqual('/admin/books'); 56 | 57 | // there should be a 'TEST' entry 58 | expect( 59 | repeater(selector).column('book.title') 60 | ).toContain('TEST'); 61 | 62 | // list shouldn't contain entry with old title 63 | expect( 64 | repeater(selector).column('book.title') 65 | ).not().toContain(exampleBook.title); 66 | }; 67 | 68 | var enterExampleBook = function(clickSelector) { 69 | input('book.title').enter(exampleBook.title); 70 | input('book.subtitle').enter(exampleBook.subtitle); 71 | input('book.isbn').enter(exampleBook.isbn); 72 | input('book.abstract').enter(exampleBook.abstract); 73 | input('book.numPages').enter(exampleBook.numPages); 74 | input('book.author').enter(exampleBook.author); 75 | input('book.publisher.name').enter(exampleBook.publisher.name); 76 | input('book.publisher.url').enter(exampleBook.publisher.url); 77 | 78 | element(clickSelector).click(); 79 | }; 80 | }); 81 | -------------------------------------------------------------------------------- /ch04_03_http/test/e2e/templates/admin_new_book.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book creation view", function () { 4 | var exampleBook = { 5 | title : 'JavaScript effektiv', 6 | subtitle : '68 Dinge, die ein guter JavaScript-Entwickler wissen sollte', 7 | isbn : '978-3-86490-127-0', 8 | abstract : 'Wollen Sie JavaScript wirklich beherrschen?', 9 | numPages : 240, 10 | author : 'David Herman', 11 | publisher : { 12 | name: 'dpunkt.verlag', 13 | url : 'http://dpunkt.de/' 14 | } 15 | }; 16 | 17 | beforeEach(function () { 18 | browser().navigateTo('/#/admin/reset'); 19 | browser().navigateTo('/#/admin/books/new'); 20 | browser().reload(); 21 | }); 22 | 23 | var selector = 'table.bm-book-list tr'; 24 | 25 | it('should navigate to admin list view having one more book entry', function () { 26 | enterExampleBook('button.bm-submit-btn'); 27 | 28 | expect( 29 | browser().location().path() 30 | ).toEqual('/admin/books'); 31 | 32 | expect( 33 | repeater(selector).column('book.title') 34 | ).toContain(exampleBook.title); 35 | }); 36 | 37 | it('should navigate to admin list view without having one more book entry', function () { 38 | enterExampleBook('button.bm-cancel-btn'); 39 | 40 | expect( 41 | browser().location().path() 42 | ).toEqual('/admin/books'); 43 | 44 | expect( 45 | repeater(selector).column('book.title') 46 | ).not().toContain(exampleBook.title); 47 | }); 48 | 49 | var enterExampleBook = function(clickSelector) { 50 | input('book.title').enter(exampleBook.title); 51 | input('book.subtitle').enter(exampleBook.subtitle); 52 | input('book.isbn').enter(exampleBook.isbn); 53 | input('book.abstract').enter(exampleBook.abstract); 54 | input('book.numPages').enter(exampleBook.numPages); 55 | input('book.author').enter(exampleBook.author); 56 | input('book.publisher.name').enter(exampleBook.publisher.name); 57 | input('book.publisher.url').enter(exampleBook.publisher.url); 58 | 59 | element(clickSelector).click(); 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /ch04_03_http/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch04_03_http/test/unit/directives/tokenfield.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: tokenfield', function () { 4 | 5 | // just to match the baseUrl in the BookDataService 6 | var baseUrl = 'http://localhost:4730'; 7 | 8 | var $compile, 9 | $rootScope, 10 | $httpBackend, 11 | element, 12 | scope; 13 | 14 | var testTags = ['test1', 'test2', 'test3']; 15 | 16 | // load the application module 17 | beforeEach(module('bmApp')); 18 | 19 | // get a reference to all used services 20 | beforeEach(inject(function (_$compile_, _$rootScope_, _$httpBackend_) { 21 | $compile = _$compile_; 22 | $rootScope = _$rootScope_; 23 | $httpBackend= _$httpBackend_; 24 | })); 25 | 26 | // define trained responses 27 | beforeEach(function() { 28 | $httpBackend.when('GET', baseUrl + '/api/tags').respond( 29 | testTags 30 | ); 31 | }); 32 | 33 | // actual init logic 34 | beforeEach(function() { 35 | scope = $rootScope.$new(); 36 | 37 | scope.book = { 38 | tags: angular.copy(testTags) 39 | }; 40 | 41 | element = $compile('')(scope); 42 | $httpBackend.flush(); 43 | }); 44 | 45 | it('should properly create available tokens on initialization', function () { 46 | var tokens = element.parent().find('div.token'); 47 | 48 | expect(tokens.length).toBe(testTags.length); 49 | 50 | tokens.each(function(index, token) { 51 | expect(angular.element(token).data('value')).toEqual(testTags[index]); 52 | }); 53 | }); 54 | 55 | it('should properly add new tokens', function () { 56 | var tokenCount = element.parent().find('div.token').length, 57 | tokenInput = element.parent().find('input.token-input'), 58 | testToken = 'test4'; 59 | 60 | tokenInput.focus(); 61 | tokenInput.val(testToken); 62 | tokenInput.blur(); 63 | 64 | var tokenCountAfter = element.parent().find('div.token').length; 65 | expect(tokenCountAfter).toBe(tokenCount + 1); 66 | expect(scope.book.tags.length).toBe(tokenCountAfter); 67 | expect(element.parent().html()).toContain(testToken); 68 | }); 69 | 70 | it('should properly remove new tokens', function () { 71 | var indexToRemove = 0; 72 | 73 | angular.element( 74 | element.parent().find('div.token')[indexToRemove] 75 | ).find('a.close').click(); 76 | 77 | expect(element.parent().find('div.token').length).toBe(testTags.length - 1); 78 | expect(scope.book.tags.length).toBe(testTags.length - 1); 79 | expect(element.parent().html()).not.toContain(testTags[indexToRemove]); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmonkey-requirejs", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/angularjs-de/dpunkt-buch-beispiele", 5 | "authors": [ 6 | "Robin Böhm ", 7 | "Philipp Tarasiewicz " 8 | ], 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "requirejs": "~2.1.9", 19 | "angular": "~1.2.0", 20 | "angular-route": "~1.2.0" 21 | }, 22 | "devDependencies": { 23 | "angular-mocks": "~1.2.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'angularRoute' 4 | ], function (angular) { 5 | 'use strict'; 6 | return angular.module('bmApp', ['ngRoute']); 7 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/config/routes.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'app', 3 | 'controllers/book_list', 4 | 'controllers/book_details' 5 | ], function (app) { 6 | 'use strict'; 7 | app.config(function ($routeProvider) { 8 | $routeProvider.when('/books/:isbn', { 9 | //templateUrl: 'templates/book_details_with_expressions.html', 10 | templateUrl: 'templates/book_details.html', 11 | controller : 'BookDetailsCtrl' 12 | }) 13 | .when('/books', { 14 | templateUrl: 'templates/book_list.html', 15 | controller : 'BookListCtrl' 16 | }) 17 | .otherwise({ 18 | redirectTo: '/books' 19 | }); 20 | }); 21 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'app', 3 | 'services/book_data' 4 | ], function (app) { 5 | "use strict"; 6 | app.controller('BookDetailsCtrl', 7 | function ($scope, $location, $routeParams, BookDataService) { 8 | var isbn = $routeParams.isbn; 9 | $scope.book = BookDataService.getBookByIsbn(isbn); 10 | 11 | $scope.goToListView = function () { 12 | $location.path('/books'); 13 | }; 14 | }); 15 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'app', 3 | 'services/book_data' 4 | ], function (app) { 5 | "use strict"; 6 | app.controller('BookListCtrl', function ($scope, BookDataService) { 7 | $scope.getBookOrder = function (book) { 8 | return book.title; 9 | }; 10 | 11 | $scope.books = BookDataService.getBooks(); 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/requireConfig.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | angular: '../bower_components/angular/angular', 4 | angularRoute: '../bower_components/angular-route/angular-route' 5 | }, 6 | shim: { 7 | 'angular' : {'exports' : 'angular'}, 8 | 'angularRoute': ['angular'] 9 | }, 10 | priority: [ 11 | "angular" 12 | ] 13 | }); 14 | 15 | require( [ 16 | 'angular', 17 | 'app', 18 | 'config/routes' 19 | ], function(angular, app) { 20 | 'use strict'; 21 | var $html = angular.element(document.getElementsByTagName('html')[0]); 22 | 23 | angular.element().ready(function() { 24 | $html.addClass('ng-app'); 25 | angular.bootstrap($html, [app['name']]); 26 | }); 27 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/app/scripts/services/book_data.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'app' 4 | ], function (angular, app) { 5 | "use strict"; 6 | app.factory('BookDataService', function () { 7 | var srv = {}; 8 | 9 | srv._books = [ 10 | { 11 | title : 'JavaScript für Enterprise-Entwickler', 12 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 13 | isbn : '978-3-89864-728-1', 14 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 15 | numPages : 302, 16 | author : 'Oliver Ochs', 17 | publisher: { 18 | name: 'dpunkt.verlag', 19 | url : 'http://dpunkt.de/' 20 | } 21 | }, 22 | { 23 | title : 'Node.js & Co.', 24 | subtitle : 'Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln', 25 | isbn : '978-3-89864-829-5', 26 | abstract : 'Nach dem Webbrowser erobert JavaScript nun auch den Webserver.', 27 | numPages : 334, 28 | author : 'Golo Roden', 29 | publisher: { 30 | name: 'dpunkt.verlag', 31 | url : 'http://dpunkt.de/' 32 | } 33 | }, 34 | { 35 | title : 'CoffeeScript', 36 | subtitle : 'Einfach JavaScript', 37 | isbn : '978-3-86490-050-1', 38 | abstract : 'CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.', 39 | numPages : 200, 40 | author : 'Andreas Schubert', 41 | publisher: { 42 | name: 'dpunkt.verlag', 43 | url : 'http://dpunkt.de/' 44 | } 45 | } 46 | ]; 47 | 48 | // Service implementation 49 | srv.getBookByIsbn = function (isbn) { 50 | for (var i = 0, n = srv._books.length; i < n; i++) { 51 | if (isbn === srv._books[i].isbn) { 52 | return srv._books[i]; 53 | } 54 | } 55 | 56 | return null; 57 | }; 58 | 59 | srv.getBooks = function () { 60 | // Copy the array in order not to expose internal data structures 61 | return angular.copy(srv._books); 62 | }; 63 | 64 | // Public API 65 | return { 66 | getBookByIsbn: function (isbn) { 67 | return srv.getBookByIsbn(isbn); 68 | }, 69 | getBooks : function () { 70 | return srv.getBooks(); 71 | } 72 | }; 73 | }); 74 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 |

21 |
22 |

23 |

24 | Zurück 25 | -------------------------------------------------------------------------------- /ch07_01_requirejs/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch07_01_requirejs/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
-------------------------------------------------------------------------------- /ch07_01_requirejs/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for e2e tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | basePath: '', 7 | 8 | files: [ 9 | 'test/e2e/**/*.js' 10 | ], 11 | 12 | logLevel: config.LOG_DEBUG, 13 | 14 | autoWatch: false, 15 | 16 | browsers: ['Chrome'], 17 | 18 | frameworks: ['ng-scenario'], 19 | 20 | singleRun: true, 21 | 22 | urlRoot: '/_karma_/', 23 | 24 | proxies: { 25 | '/': 'http://localhost:8080/' 26 | } 27 | 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /ch07_01_requirejs/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine','requirejs'], 12 | 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | {pattern: 'app/bower_components/angular/angular.js', included: false}, 18 | {pattern: 'app/bower_components/angular-route/angular-route.js', included: false}, 19 | {pattern: 'app/bower_components/angular-mocks/angular-mocks.js', included: false}, 20 | {pattern: 'app/scripts/app.js', included: false}, 21 | {pattern: 'app/scripts/**/*.js', included: false}, 22 | {pattern: 'test/unit/**/*.js', included: false}, 23 | 24 | 'test/testRequireConfig.js' 25 | 26 | ], 27 | 28 | 29 | // list of files to exclude 30 | exclude: [ 31 | 32 | ], 33 | 34 | 35 | // test results reporter to use 36 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_DEBUG, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: false, 55 | 56 | 57 | // Start these browsers, currently available: 58 | // - Chrome 59 | // - ChromeCanary 60 | // - Firefox 61 | // - Opera 62 | // - Safari (only Mac) 63 | // - PhantomJS 64 | // - IE (only Windows) 65 | browsers: ['Chrome'], 66 | 67 | 68 | // If browser does not capture in given timeout [ms], kill it 69 | captureTimeout: 60000, 70 | 71 | 72 | // Continuous Integration mode 73 | // if true, it capture browsers, run tests and exit 74 | singleRun: true 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /ch07_01_requirejs/test/e2e/templates/book_details.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book details view", function() { 4 | 5 | beforeEach(function() { 6 | browser().navigateTo('/'); 7 | }); 8 | 9 | it('should show the correct book details', function() { 10 | browser().navigateTo('#/books/978-3-89864-728-1'); 11 | 12 | expect(element('.bm-book-title').html()).toBe( 13 | 'JavaScript für Enterprise-Entwickler' 14 | ); 15 | expect(element('.bm-book-subtitle').html()).toBe( 16 | 'Professionell programmieren im Browser und auf dem Server' 17 | ); 18 | expect(element('.bm-book-isbn').html()).toBe( 19 | 'ISBN: 978-3-89864-728-1' 20 | ); 21 | expect(element('.bm-book-num-pages').html()).toBe( 22 | 'Seiten: 302' 23 | ); 24 | expect(element('.bm-book-author').html()).toBe( 25 | 'Autor: Oliver Ochs' 26 | ); 27 | expect(element('.bm-book-publisher-name').html()).toBe( 28 | 'dpunkt.verlag' 29 | ); 30 | expect(element('.bm-book-publisher-name').attr('href')).toBe( 31 | 'http://dpunkt.de/' 32 | ); 33 | expect(element('.bm-book-abstract').html()).toBe( 34 | 'JavaScript ist längst nicht mehr nur für' + 35 | ' klassische Webprogrammierer interessant.' 36 | ); 37 | }); 38 | 39 | it('should appropriately navigate to list view', function() { 40 | browser().navigateTo('#/books/978-3-89864-728-1'); 41 | 42 | element('.bm-list-view-btn').click(); 43 | 44 | expect( 45 | browser().location().path() 46 | ).toEqual('/books'); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/test/e2e/templates/book_list.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("E2E: book list view", function () { 4 | 5 | // Define the array of books in the expected order. 6 | // Sorted by title. 7 | var expectedBooks = [ 8 | { 9 | title: 'CoffeeScript', 10 | isbn: '978-3-86490-050-1', 11 | author: 'Andreas Schubert' 12 | }, 13 | { 14 | title: 'JavaScript für Enterprise-Entwickler', 15 | isbn: '978-3-89864-728-1', 16 | author: 'Oliver Ochs' 17 | }, 18 | { 19 | title: 'Node.js & Co.', 20 | isbn: '978-3-89864-829-5', 21 | author: 'Golo Roden' 22 | } 23 | ]; 24 | 25 | // Derive an array that only contains titles 26 | // for easier expectation checks. 27 | var orderedTitles = expectedBooks.map(function(book) { 28 | return book.title; 29 | }); 30 | 31 | beforeEach(function () { 32 | browser().navigateTo('#/books'); 33 | browser().reload(); 34 | }); 35 | 36 | var selector = 'table.bm-book-list tr'; 37 | 38 | it('should show the correct number of books', function () { 39 | expect(repeater(selector).count()).toEqual(expectedBooks.length); 40 | }); 41 | 42 | it('should show the books in the proper order', function() { 43 | // Are they in the expected order (ascending sorted by title)? 44 | expect(repeater(selector).column('book.title')).toEqual(orderedTitles); 45 | }); 46 | 47 | it('should show the correct book information', function() { 48 | // Do the other book details (isbn, author) match? 49 | for (var i = 0, n = expectedBooks.length; i < n; i++) { 50 | expect(repeater(selector).row(i)) 51 | .toEqual( 52 | [ 53 | expectedBooks[i].title, 54 | expectedBooks[i].author, 55 | expectedBooks[i].isbn 56 | ] 57 | ); 58 | } 59 | }); 60 | 61 | it('should allow filtering by book title', function() { 62 | // Coffee 63 | var searchText = orderedTitles[0].substr(0, 6); 64 | input('searchText').enter(searchText); 65 | expect( 66 | repeater(selector).column('book.title') 67 | ).toEqual([orderedTitles[0]]); 68 | }); 69 | 70 | it('should allow filtering by author', function() { 71 | // Andreas 72 | var searchText = expectedBooks[0].author.substr(0, 7); 73 | input('searchText').enter(searchText); 74 | expect( 75 | repeater(selector).column('book.title') 76 | ).toEqual([orderedTitles[0]]); 77 | }); 78 | 79 | it('should allow filtering by isbn', function() { 80 | // 050-1 81 | var searchText = expectedBooks[0].isbn.substr(-5, 5); 82 | input('searchText').enter(searchText); 83 | expect( 84 | repeater(selector).column('book.title') 85 | ).toEqual([orderedTitles[0]]); 86 | }); 87 | 88 | it('should appropriately navigate to details view', function() { 89 | var i = 0, 90 | detailsLink = selector + ':nth-child('+ (i+1) +') a'; 91 | element(detailsLink).click(); 92 | 93 | expect( 94 | browser().location().path() 95 | ).toEqual('/books/' + expectedBooks[i].isbn); 96 | }); 97 | 98 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/test/testRequireConfig.js: -------------------------------------------------------------------------------- 1 | // we get all the test files automatically 2 | var tests = []; 3 | for (var file in window.__karma__.files) { 4 | if (window.__karma__.files.hasOwnProperty(file)) { 5 | if (/spec\.js$/i.test(file)) { 6 | tests.push(file); 7 | } 8 | } 9 | } 10 | 11 | require.config({ 12 | paths: { 13 | angular: '/base/app/bower_components/angular/angular', 14 | angularRoute: '/base/app/bower_components/angular-route/angular-route', 15 | angularMocks: '/base/app/bower_components/angular-mocks/angular-mocks' 16 | 17 | }, 18 | baseUrl: '/base/app/scripts/', 19 | shim: { 20 | 'angular' : {'exports' : 'angular'}, 21 | 'angularRoute': ['angular'], 22 | 'angularMocks': { 23 | deps:['angular'], 24 | 'exports':'angular.mock' 25 | } 26 | }, 27 | deps: tests, 28 | callback: window.__karma__.start 29 | }); -------------------------------------------------------------------------------- /ch07_01_requirejs/test/unit/services/book_data.spec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'angularMocks', 4 | 'services/book_data'], function (angular, mock) { 5 | 6 | 7 | 'use strict'; 8 | 9 | describe('Service: BookDataService', function () { 10 | 11 | var BookDataService; 12 | 13 | // load the application module 14 | beforeEach(module('bmApp')); 15 | 16 | // get a reference to the service 17 | beforeEach(inject(function (_BookDataService_) { 18 | BookDataService = _BookDataService_; 19 | })); 20 | 21 | describe('Public API', function () { 22 | it('should include a getBookByIsbn() function', function () { 23 | expect(BookDataService.getBookByIsbn).toBeDefined(); 24 | }); 25 | 26 | it('should include a getBooks() function', function () { 27 | expect(BookDataService.getBooks).toBeDefined(); 28 | }); 29 | }); 30 | 31 | }); 32 | 33 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BookMonkey 6 | 7 | 8 |
9 |

BookMonkey

10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'angularRoute' 4 | ], function (angular) { 5 | 'use strict'; 6 | return angular.module('bmApp', ['ngRoute']); 7 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/config/routes.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'app', 3 | 'controllers/book_list', 4 | 'controllers/book_details' 5 | ], function (app) { 6 | 'use strict'; 7 | app.config(function ($routeProvider) { 8 | $routeProvider.when('/books/:isbn', { 9 | //templateUrl: 'templates/book_details_with_expressions.html', 10 | templateUrl: 'templates/book_details.html', 11 | controller : 'BookDetailsCtrl' 12 | }) 13 | .when('/books', { 14 | templateUrl: 'templates/book_list.html', 15 | controller : 'BookListCtrl' 16 | }) 17 | .otherwise({ 18 | redirectTo: '/books' 19 | }); 20 | }); 21 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/controllers/book_details.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'app', 4 | 'services/book_data' 5 | ], function (angular, app) { 6 | "use strict"; 7 | 8 | app.controller('BookDetailsCtrl', 9 | function ($scope, $location, $routeParams, BookDataService) { 10 | var isbn = $routeParams.isbn; 11 | $scope.book = BookDataService.getBookByIsbn(isbn); 12 | 13 | $scope.goToListView = function () { 14 | $location.path('/books'); 15 | }; 16 | }); 17 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/controllers/book_list.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'app', 4 | 'services/book_data' 5 | ], function (angular, app) { 6 | "use strict"; 7 | 8 | app.controller('BookListCtrl', function ($scope, BookDataService) { 9 | $scope.getBookOrder = function (book) { 10 | return book.title; 11 | }; 12 | 13 | $scope.books = BookDataService.getBooks(); 14 | }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/requireConfig.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | angular: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular', 4 | angularRoute: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-route' 5 | }, 6 | shim: { 7 | 'angular' : {'exports' : 'angular'}, 8 | 'angularRoute': ['angular'] 9 | }, 10 | priority: [ 11 | "angular" 12 | ] 13 | }); 14 | 15 | require( [ 16 | 'angular', 17 | 'app', 18 | 'config/routes' 19 | ], function(angular, app) { 20 | 'use strict'; 21 | var $html = angular.element(document.getElementsByTagName('html')[0]); 22 | 23 | angular.element().ready(function() { 24 | $html.addClass('ng-app'); 25 | angular.bootstrap($html, [app['name']]); 26 | }); 27 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/scripts/services/book_data.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'app' 4 | ], function (angular, app) { 5 | "use strict"; 6 | 7 | app.factory('BookDataService', function () { 8 | var srv = {}; 9 | 10 | srv._books = [ 11 | { 12 | title : 'JavaScript für Enterprise-Entwickler', 13 | subtitle : 'Professionell programmieren im Browser und auf dem Server', 14 | isbn : '978-3-89864-728-1', 15 | abstract : 'JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.', 16 | numPages : 302, 17 | author : 'Oliver Ochs', 18 | publisher: { 19 | name: 'dpunkt.verlag', 20 | url : 'http://dpunkt.de/' 21 | } 22 | }, 23 | { 24 | title : 'Node.js & Co.', 25 | subtitle : 'Skalierbare, hochperformante und echtzeitfähige Webanwendungen professionell in JavaScript entwickeln', 26 | isbn : '978-3-89864-829-5', 27 | abstract : 'Nach dem Webbrowser erobert JavaScript nun auch den Webserver.', 28 | numPages : 334, 29 | author : 'Golo Roden', 30 | publisher: { 31 | name: 'dpunkt.verlag', 32 | url : 'http://dpunkt.de/' 33 | } 34 | }, 35 | { 36 | title : 'CoffeeScript', 37 | subtitle : 'Einfach JavaScript', 38 | isbn : '978-3-86490-050-1', 39 | abstract : 'CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.', 40 | numPages : 200, 41 | author : 'Andreas Schubert', 42 | publisher: { 43 | name: 'dpunkt.verlag', 44 | url : 'http://dpunkt.de/' 45 | } 46 | } 47 | ]; 48 | 49 | // Service implementation 50 | srv.getBookByIsbn = function (isbn) { 51 | for (var i = 0, n = srv._books.length; i < n; i++) { 52 | if (isbn === srv._books[i].isbn) { 53 | return srv._books[i]; 54 | } 55 | } 56 | 57 | return null; 58 | }; 59 | 60 | srv.getBooks = function () { 61 | // Copy the array in order not to expose internal data structures 62 | return angular.copy(srv._books); 63 | }; 64 | 65 | // Public API 66 | return { 67 | getBookByIsbn: function (isbn) { 68 | return srv.getBookByIsbn(isbn); 69 | }, 70 | getBooks : function () { 71 | return srv.getBooks(); 72 | } 73 | }; 74 | }); 75 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/templates/book_details.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

    5 |
  • 7 |
  • 9 |
  • 11 |
  • 12 | Verlag: 13 | 17 | 18 |
  • 19 |
20 |

21 |
22 |

23 |

24 | Zurück 25 | -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/templates/book_details_with_expressions.html: -------------------------------------------------------------------------------- 1 |

{{ book.title }}

2 |

{{ book.subtitle }}

3 |

4 |

    5 |
  • ISBN: {{ book.isbn }}
  • 6 |
  • Seiten: {{ book.numPages }}
  • 7 |
  • Autor: {{ book.author }}
  • 8 |
  • 9 | Verlag: 10 | {{ book.publisher.name }} 13 |
  • 14 |
15 |

16 |
17 |

{{ book.abstract }}

-------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/app/templates/book_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
-------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration for unit tests 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine','requirejs'], 12 | 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | {pattern: 'app/scripts/**/*.js', included: false}, 17 | {pattern: 'test/unit/**/*.js', included: false}, 18 | 19 | 'test/testRequireConfig.js' 20 | 21 | ], 22 | 23 | 24 | // list of files to exclude 25 | exclude: [ 26 | 27 | ], 28 | 29 | 30 | // test results reporter to use 31 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 32 | reporters: ['progress'], 33 | 34 | 35 | // web server port 36 | port: 9876, 37 | 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | 43 | // level of logging 44 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_ERROR, 46 | 47 | 48 | // enable / disable watching file and executing tests whenever any file changes 49 | autoWatch: false, 50 | 51 | 52 | // Start these browsers, currently available: 53 | // - Chrome 54 | // - ChromeCanary 55 | // - Firefox 56 | // - Opera 57 | // - Safari (only Mac) 58 | // - PhantomJS 59 | // - IE (only Windows) 60 | browsers: ['Chrome'], 61 | 62 | 63 | // If browser does not capture in given timeout [ms], kill it 64 | captureTimeout: 60000, 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, it capture browsers, run tests and exit 69 | singleRun: true 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/test/testRequireConfig.js: -------------------------------------------------------------------------------- 1 | // we get all the test files automatically 2 | var tests = []; 3 | for (var file in window.__karma__.files) { 4 | if (window.__karma__.files.hasOwnProperty(file)) { 5 | if (/spec\.js$/i.test(file)) { 6 | tests.push(file); 7 | } 8 | } 9 | } 10 | 11 | require.config({ 12 | paths: { 13 | angular: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular', 14 | angularRoute: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-route', 15 | angularMocks: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-mocks' 16 | 17 | }, 18 | baseUrl: '/base/app/scripts/', 19 | shim: { 20 | 'angular' : {'exports' : 'angular'}, 21 | 'angularRoute': ['angular'], 22 | 'angularMocks': { 23 | deps:['angular'], 24 | 'exports':'angular.mock' 25 | } 26 | }, 27 | deps: tests, 28 | callback: window.__karma__.start 29 | }); -------------------------------------------------------------------------------- /ch07_02_requirejs_cdn/test/unit/services/book_data.spec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'angular', 3 | 'angularMocks', 4 | 'services/book_data'], function () { 5 | 6 | 7 | 'use strict'; 8 | 9 | describe('Service: BookDataService', function () { 10 | 11 | var BookDataService; 12 | 13 | // load the application module 14 | beforeEach(module('bmApp')); 15 | 16 | // get a reference to the service 17 | beforeEach(inject(function (_BookDataService_) { 18 | BookDataService = _BookDataService_; 19 | })); 20 | 21 | describe('Public API', function () { 22 | it('should include a getBookByIsbn() function', function () { 23 | expect(BookDataService.getBookByIsbn).toBeDefined(); 24 | }); 25 | 26 | it('should include a getBooks() function', function () { 27 | expect(BookDataService.getBooks).toBeDefined(); 28 | }); 29 | }); 30 | 31 | describe('Public API usage', function () { 32 | describe('getBookByIsbn()', function () { 33 | it('should return the proper book object (valid isbn)', function () { 34 | var isbn = '978-3-86490-050-1', 35 | book = BookDataService.getBookByIsbn(isbn); 36 | expect(book.title).toBe('CoffeeScript'); 37 | }); 38 | 39 | it('should return null (invalid isbn)', function () { 40 | var isbn = 'test', 41 | book = BookDataService.getBookByIsbn(isbn); 42 | expect(book).toBeNull(); 43 | }); 44 | }); 45 | 46 | describe('getBooks()', function () { 47 | it('should return a proper array of book objects', function () { 48 | var books = BookDataService.getBooks(); 49 | expect(books.length).toBe(3); 50 | }); 51 | }); 52 | }); 53 | 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /ch07_03_mobile/app/.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | -------------------------------------------------------------------------------- /ch07_03_mobile/app/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-ng-touch-example", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/angularjs-de/dpunkt-buch-beispiele", 5 | "authors": [ 6 | "Robin Böhm ", 7 | "Philipp Tarasiewicz " 8 | ], 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "angular": "~1.2.0", 19 | "angular-touch": "~1.2.0" 20 | }, 21 | "devDependencies": { 22 | "angular-mocks": "~1.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ch07_03_mobile/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | AngularJS ngTouch Example 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
Click: {{click}}
17 |
18 | 19 |
20 |
Swipe-right: {{right}}
21 |
Swipe-left: {{left}}
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ch07_03_mobile/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('app', ['ngTouch']); 4 | -------------------------------------------------------------------------------- /ch07_03_mobile/app/scripts/directives/draggable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('app').directive('draggable', function ($swipe) { 4 | return { 5 | restrict: 'A', 6 | link: function (scope, element) { 7 | var width = element[0].offsetWidth, 8 | height = element[0].offsetHeight; 9 | 10 | var toggleActive = function () { 11 | element.toggleClass('swipe-active'); 12 | }; 13 | 14 | $swipe.bind(element, { 15 | 'start': toggleActive, 16 | 'move': function (coords) { 17 | element.css('left', coords.x-(width/2) + "px"); 18 | element.css('top', coords.y-(height/2) + "px"); 19 | }, 20 | 'end': toggleActive, 21 | 'cancel': toggleActive 22 | }); 23 | } 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /ch07_03_mobile/app/styles/draggable.css: -------------------------------------------------------------------------------- 1 | body > div { 2 | width: 200px; 3 | height: 100px; 4 | border: 1px solid black; 5 | } 6 | 7 | .draggable { 8 | background-color: red; 9 | position: absolute; 10 | width: 100px; 11 | height: 100px; 12 | border-radius: 50%; 13 | left: 300px; 14 | } 15 | 16 | .swipe-active { 17 | opacity: 0.5; 18 | } 19 | 20 | .ng-click-active { 21 | background-color: green; 22 | } 23 | --------------------------------------------------------------------------------