├── .gitignore ├── LICENSE ├── README.md ├── chapter1 ├── angularjs-hello-world-screenshot.png ├── angularjs-hello-world.html ├── basic-angularjs-app-screenshot.png └── basic-angularjs-app.html ├── chapter10 ├── routing-diagram.png ├── routing-example │ ├── app │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── scripts │ │ │ ├── app.js │ │ │ ├── controllers.js │ │ │ ├── services.js │ │ │ └── vendors │ │ │ │ ├── angular-route.js │ │ │ │ ├── angular-route.min.js │ │ │ │ ├── angular.js │ │ │ │ ├── angular.min.js │ │ │ │ └── jquery-1.11.1.js │ │ ├── styles │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ └── main.css │ │ └── views │ │ │ ├── login.html │ │ │ ├── team_details.html │ │ │ └── team_list.html │ ├── fifa.js │ ├── package.json │ └── server.js ├── routing-html5-mode.png └── simple-routing.html ├── chapter11 ├── directive-broken-reference │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-with-link │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-with-restrict │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-with-scope-advanced │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-with-scope │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-with-template │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── ng-include │ ├── app.js │ ├── index.html │ └── stock.html └── ng-switch │ └── index.html ├── chapter12 ├── angular-mocks.js ├── angular.js ├── angular.min.js ├── karma.conf.js ├── package.json ├── stockDirective.js ├── stockDirectiveBehaviorSpec.js └── stockDirectiveRenderSpec.js ├── chapter13 ├── chapter-13-AngularJS Digest Cycle.png ├── chapter-13-AngularJS-Lifecycle.png ├── directive-advanced-transclusion │ ├── app.js │ ├── directive.js │ └── index.html ├── directive-compile │ ├── app.js │ ├── directive.js │ └── index.html ├── directive-controllers │ ├── app.js │ ├── index.html │ ├── main.css │ ├── tab.js │ └── tabs.js ├── directive-custom-validator │ ├── app.js │ ├── directive.js │ └── index.html ├── directive-google-chart │ ├── app.js │ ├── googleChartLoader.js │ ├── index.html │ └── pieChart.js ├── directive-no-transclusion │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── directive-slider │ ├── app.js │ ├── index.html │ ├── jquery.nouislider.css │ ├── jquery.nouislider.min.js │ └── noui-slider.js └── directive-transclusion │ ├── app.js │ ├── directive.js │ ├── index.html │ └── stock.html ├── chapter14 ├── appUnderTest │ ├── app │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── scripts │ │ │ ├── app.js │ │ │ ├── controllers.js │ │ │ ├── services.js │ │ │ └── vendors │ │ │ │ ├── angular-route.js │ │ │ │ ├── angular-route.min.js │ │ │ │ ├── angular.js │ │ │ │ ├── angular.min.js │ │ │ │ └── jquery-1.11.1.js │ │ ├── styles │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ └── main.css │ │ └── views │ │ │ ├── login.html │ │ │ ├── team_details.html │ │ │ └── team_list.html │ ├── fifa.js │ ├── package.json │ └── server.js ├── protractor.conf.js ├── routingSpecWithPageObjects.js └── simpleRoutingSpec.js ├── chapter15 └── batarang-screenshot.png ├── chapter2 ├── angularjs-manual-bootstrap.html ├── controller-click-message.html ├── creating-controller.html ├── hello-controller-screenshot.png ├── hello-controller.html ├── module-example.html ├── more-directives.html ├── ng-bind-once.html ├── ng-repeat-across-elements.html ├── ng-repeat-example-1-screenshot.png ├── ng-repeat-example-1.html ├── ng-repeat-helper-variables.html ├── ng-repeat-object.html └── ng-repeat-track-by-id.html ├── chapter3 ├── angular-mocks.js ├── angular.js ├── angular.min.js ├── chapter-3-test-results.png ├── controller.js ├── controllerSpec.js ├── karma.conf.js ├── package.json └── simpleSpec.js ├── chapter4 ├── checkbox-example.html ├── form-error-messages.html ├── form-styling.html ├── form-validation.html ├── nested-forms.html ├── ng-messages.html ├── ng-model-options.html ├── select-example.html ├── simple-form.html ├── simple-ng-model-2.html ├── simple-ng-model.html └── two-forms-databinding.html ├── chapter5 ├── item-service-using-provider │ ├── app.js │ └── index.html ├── item-service-using-service │ ├── app.js │ └── index.html ├── log-example.html ├── need-for-service │ ├── app.js │ └── index.html └── simple-angularjs-service │ ├── app.js │ └── index.html ├── chapter6 ├── package.json ├── promise-chain.png ├── public │ ├── http-defaults.html │ ├── http-defaults.js │ ├── http-get-example.html │ ├── http-post-example.html │ ├── index.html │ ├── logging-interceptor.html │ ├── logging-interceptor.js │ └── ng-resource-example.html └── server.js ├── chapter7 ├── angular-mocks.js ├── angular.js ├── angular.min.js ├── karma.conf.js ├── notesApp1-mocks.js ├── notesApp1.js ├── notesApp1ServiceSpec.js ├── notesApp1Spec.js ├── notesApp1SpecWithMock.js ├── notesApp1SpecWithSpies.js ├── notesApp1SpecWithSpyReturn.js ├── package.json ├── serverApp.js ├── serverAppSpec.js ├── serverAppWithService.js ├── serverAppWithServiceSpec.js ├── simpleCtrl1.js ├── simpleCtrl1Spec.js ├── simpleCtrl2.js └── simpleCtrl2Spec.js ├── chapter8 ├── custom-filters.html ├── filter-arrays.html ├── filter-example-1-screenshot.png ├── filter-example-1.html ├── filter-number-string-screenshot.png └── filter-number-string.html └── chapter9 ├── angular-mocks.js ├── angular.js ├── angular.min.js ├── karma.conf.js ├── package.json ├── timeAgoFilter.js ├── timeAgoFilterOptionalArgumentSpec.js └── timeAgoFilterSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Shyam Seshadri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angularjs-up-and-running 2 | ======================== 3 | 4 | All the source code for the [AngularJS Up & Running Book for O'Reilly](http://shop.oreilly.com/product/0636920033486.do) 5 | 6 | *Steps To Server Files Locally* 7 | 8 | Using NodeJS 9 | ``` 10 | git clone https://github.com/shyamseshadri/angularjs-up-and-running 11 | cd angularjs-up-and-running 12 | npm init 13 | (accept all defaults) 14 | npm install --save http-server 15 | node node_modules/http-server/bin/http-server 16 | ``` 17 | 18 | connect browser to http://localhost:8080 19 | example directory is served 20 | 21 | Using Python 22 | 23 | ``` 24 | git clone https://github.com/shyamseshadri/angularjs-up-and-running 25 | cd angularjs-up-and-running 26 | python -m SimpleHTTPServer 27 | ``` 28 | 29 | connect browser to http://localhost:8000 30 | example directory is served 31 | 32 | -------------------------------------------------------------------------------- /chapter1/angularjs-hello-world-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter1/angularjs-hello-world-screenshot.png -------------------------------------------------------------------------------- /chapter1/angularjs-hello-world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 |

Hello

10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter1/basic-angularjs-app-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter1/basic-angularjs-app-screenshot.png -------------------------------------------------------------------------------- /chapter1/basic-angularjs-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Hello {{1 + 2}}

7 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter10/routing-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter10/routing-diagram.png -------------------------------------------------------------------------------- /chapter10/routing-example/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter10/routing-example/app/favicon.ico -------------------------------------------------------------------------------- /chapter10/routing-example/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FIFA Teams 5 | 6 | 7 | 8 | 9 |
10 |
11 | FIFA TEAMS 12 |
13 |
14 | 15 | Login 16 | 17 | 18 | 19 | Logout 20 | 21 |
22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter10/routing-example/app/scripts/app.js 2 | angular.module('fifaApp', ['ngRoute']) 3 | .config(function($routeProvider) { 4 | 5 | $routeProvider.when('/', { 6 | templateUrl: 'views/team_list.html', 7 | controller: 'TeamListCtrl as teamListCtrl' 8 | }) 9 | .when('/login', { 10 | templateUrl: 'views/login.html' 11 | }) 12 | .when('/team/:code', { 13 | templateUrl: 'views/team_details.html', 14 | controller:'TeamDetailsCtrl as teamDetailsCtrl', 15 | resolve: { 16 | auth: ['$q', '$location', 'UserService', 17 | function($q, $location, UserService) { 18 | return UserService.session().then( 19 | function(success) {}, 20 | function(err) { 21 | $location.path('/login'); 22 | $location.replace(); 23 | return $q.reject(err); 24 | }); 25 | }] 26 | } 27 | }); 28 | $routeProvider.otherwise({ 29 | redirectTo: '/' 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/scripts/controllers.js: -------------------------------------------------------------------------------- 1 | // File: chapter10/routing-example/app/scripts/controllers.js 2 | angular.module('fifaApp') 3 | .controller('MainCtrl', ['UserService', 4 | function(UserService) { 5 | var self = this; 6 | self.userService = UserService; 7 | 8 | // Check if the user is logged in when the application 9 | // loads 10 | // User Service will automatically update isLoggedIn 11 | // after this call finishes 12 | UserService.session(); 13 | }]) 14 | 15 | .controller('TeamListCtrl', ['FifaService', 16 | function(FifaService) { 17 | var self = this; 18 | self.teams = []; 19 | 20 | FifaService.getTeams().then(function(resp) { 21 | self.teams = resp.data; 22 | }); 23 | }]) 24 | 25 | .controller('LoginCtrl', ['UserService', '$location', 26 | function(UserService, $location) { 27 | var self = this; 28 | self.user = {username: '', password: ''}; 29 | 30 | self.login = function() { 31 | UserService.login(self.user).then(function(success) { 32 | $location.path('/'); 33 | }, function(error) { 34 | self.errorMessage = error.data.msg; 35 | }) 36 | }; 37 | }]) 38 | 39 | .controller('TeamDetailsCtrl', 40 | ['$location', '$routeParams', 'FifaService', 41 | function($location, $routeParams, FifaService) { 42 | var self = this; 43 | self.team = {}; 44 | FifaService.getTeamDetails($routeParams.code) 45 | .then(function(resp){ 46 | self.team = resp.data; 47 | }, function(error){ 48 | $location.path('/login'); 49 | }); 50 | }]); 51 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/scripts/services.js: -------------------------------------------------------------------------------- 1 | // File: chapter10/routing-example/app/scripts/services.js 2 | angular.module('fifaApp') 3 | .factory('FifaService', ['$http', 4 | function($http) { 5 | return { 6 | getTeams: function() { 7 | return $http.get('/api/team'); 8 | }, 9 | 10 | getTeamDetails: function(code) { 11 | return $http.get('/api/team/' + code); 12 | } 13 | } 14 | }]) 15 | .factory('UserService', ['$http', function($http) { 16 | var service = { 17 | isLoggedIn: false, 18 | 19 | session: function() { 20 | return $http.get('/api/session') 21 | .then(function(response) { 22 | service.isLoggedIn = true; 23 | return response; 24 | }); 25 | }, 26 | 27 | login: function(user) { 28 | return $http.post('/api/login', user) 29 | .then(function(response) { 30 | service.isLoggedIn = true; 31 | return response; 32 | }); 33 | } 34 | }; 35 | return service; 36 | }]); 37 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/scripts/vendors/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.11 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,d,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),f=r.current;m=y(b,function(b){g.enter(b,null,m||c).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=f.scope=b;l.$emit("$viewContentLoaded"); 7 | l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,h,g){return{restrict:"ECA",priority:-400,link:function(a,c){var b=g.current,f=b.locals;c.html(f.$template);var y=d(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));y(a)}}}p=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,c){return d.extend(Object.create(a), 8 | c)}function h(a,d){var b=d.caseInsensitiveMatch,f={originalPath:a,regexp:a},g=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;g.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=new RegExp("^"+a+"$",b?"i":"");return f}var g={};this.when=function(a,c){var b=d.copy(c);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=d.extend(b,a&&h(a,b));if(a){var f="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[f]=d.extend({redirectTo:a},h(f,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,c,b,f,h,p,x){function l(b){var e=s.current; 10 | (v=(n=k())&&e&&n.$$route===e.$$route&&d.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?c.path(t(e.redirectTo,e.params)).search(e.params).replace():c.url(e.redirectTo(e.pathParams,c.path(),c.search())).replace()),f.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,c;d.forEach(a,function(b,e){a[e]=d.isString(b)?h.get(b):h.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(c=e.templateUrl)&&(d.isFunction(c)&&(c=c(e.params)),c=x.getTrustedResourceUrl(c),d.isDefined(c)&&(e.loadedTemplateUrl=c,b=p(c)));d.isDefined(b)&&(a.$template=b);return f.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function k(){var a,b;d.forEach(g,function(f,g){var q;if(q=!b){var h=c.path();q=f.keys;var l={};if(f.regexp)if(h=f.regexp.exec(h)){for(var k=1,m=h.length;k 2 |
4 |
7 | 38 |
39 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/views/team_details.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 | 9 | () 10 |
11 |
12 |
13 | Nickname 14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | FIFA Ranking 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | Association 31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 | Head Coach 39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 | Captain 47 |
48 |
49 | 50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /chapter10/routing-example/app/views/team_list.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | 17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /chapter10/routing-example/fifa.js: -------------------------------------------------------------------------------- 1 | var FIFA = {}; 2 | 3 | FIFA.TEAMS_LIST = [ 4 | { 5 | code:'ESP', 6 | name:'Spain', 7 | rank: 1, 8 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg' 9 | }, 10 | 11 | { 12 | code: 'GER', 13 | name: 'Germany', 14 | rank: 2, 15 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/b/ba/Flag_of_Germany.svg' 16 | }, 17 | 18 | { 19 | code: 'POR', 20 | name: 'Portugal', 21 | rank: 3, 22 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/5/5c/Flag_of_Portugal.svg' 23 | }, 24 | 25 | { 26 | code: 'COL', 27 | name: 'Colombia', 28 | rank: 4, 29 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/2/21/Flag_of_Colombia.svg' 30 | }, 31 | 32 | { 33 | code: 'URU', 34 | name: 'Uruguay', 35 | rank: 5, 36 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/f/fe/Flag_of_Uruguay.svg' 37 | } 38 | ]; 39 | 40 | FIFA.TEAM_DETAILS = { 41 | 'ESP' : { 42 | fifaCode: 'ESP', 43 | fifaRanking: 1, 44 | name: 'Spain', 45 | nickname: 'La Furia Roja (The Red Fury)', 46 | association: 'Real Federación Española de Fútbol (RFEF)', 47 | headCoach: 'Vicente del Bosque', 48 | captain: 'Iker Casillas', 49 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg', 50 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/3/31/Spain_National_Football_Team_badge.png/417px-Spain_National_Football_Team_badge.png' 51 | }, 52 | 53 | 'GER' : { 54 | fifaCode: 'GER', 55 | fifaRanking: 2, 56 | name: 'Germany', 57 | nickname: 'Nationalmannschaft (national team)', 58 | association: 'German Football Association', 59 | headCoach: 'Joachim Löw', 60 | captain: 'Philipp Lahm', 61 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/b/ba/Flag_of_Germany.svg', 62 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/e/e3/DFBEagle.svg/442px-DFBEagle.svg.png' 63 | }, 64 | 65 | 'POR' : { 66 | fifaCode: 'POR', 67 | fifaRanking: 3, 68 | name: 'Portugal', 69 | nickname: 'A Selecção', 70 | association: 'Federação Portuguesa de Futebol', 71 | headCoach: 'Paulo Bento', 72 | captain: 'Cristiano Ronaldo', 73 | flagUrl:'http://upload.wikimedia.org/wikipedia/commons/5/5c/Flag_of_Portugal.svg', 74 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/5/5f/Portuguese_Football_Federation.svg/424px-Portuguese_Football_Federation.svg.png' 75 | }, 76 | 77 | 'COL' : { 78 | fifaCode: 'COL', 79 | fifaRanking: 4, 80 | name: 'Colombia', 81 | nickname: 'Los Cafeteros (The Coffee Growers)', 82 | association: 'Federación Colombiana de Fútbol (FCF)', 83 | headCoach: 'José Pékerman', 84 | captain: 'Mario Yepes', 85 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/2/21/Flag_of_Colombia.svg', 86 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/6/61/Federacion_Colombiana_de_Futbol_logo.svg/302px-Federacion_Colombiana_de_Futbol_logo.svg.png' 87 | }, 88 | 89 | 'URU' : { 90 | fifaCode: 'URU', 91 | fifaRanking: 5, 92 | name: 'Uruguay', 93 | nickname: 'Los Charrúas', 94 | association: 'Asociación Uruguaya de Fútbol (AUF)', 95 | headCoach: 'Óscar Tabárez', 96 | captain: 'Diego Lugano', 97 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/f/fe/Flag_of_Uruguay.svg', 98 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/d/d1/Uruguay_football_association.svg/195px-Uruguay_football_association.svg.png' 99 | } 100 | }; 101 | 102 | 103 | exports.FIFA = FIFA; 104 | -------------------------------------------------------------------------------- /chapter10/routing-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FIFA_TEAMS", 3 | "description": "FIFA TEAMS APP For Angular Routing", 4 | "version": "0.0.1", 5 | "private": true, 6 | "engines": { 7 | "node": "0.10.x", 8 | "npm": "1.3.x" 9 | }, 10 | "dependencies": { 11 | "express" : "4.1.1", 12 | "body-parser" : "1.0.2", 13 | "serve-static" : "1.1.0", 14 | "method-override" : "1.0.0", 15 | "express-session" : "1.0.4", 16 | "passport": "0.2.0", 17 | "compression": "1.0.2", 18 | "passport-local": "1.0.0", 19 | "cookie-parser": "1.0.1", 20 | "morgan": "1.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter10/routing-example/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by abhiroop on 5/6/14. 3 | */ 4 | var express = require('express'), 5 | http = require('http'), 6 | passport = require('passport'), 7 | morgan = require('morgan'), 8 | compress = require('compression'), 9 | bodyParser = require('body-parser'), 10 | methodOverride = require('method-override'), 11 | cookieParser = require('cookie-parser'), 12 | session = require('express-session'), 13 | LocalStrategy = require('passport-local').Strategy, 14 | serverStatic = require('serve-static'), 15 | FIFA = require('./fifa').FIFA; 16 | 17 | var USER = {username: 'admin', password: 'admin'}; 18 | 19 | var app = express(); 20 | app.use(morgan()); 21 | app.use(compress()); 22 | app.use(bodyParser()); 23 | 24 | passport.serializeUser(function(user, done) { 25 | done(null, user); 26 | }); 27 | 28 | passport.deserializeUser(function(user, done) { 29 | done(null, user); 30 | }); 31 | app.use(methodOverride()); 32 | app.use(cookieParser()); 33 | 34 | app.use(session({ 35 | secret : 'almvnirtgd#$DFsa25452*AYD*D*S!@!#adsda))Ddsadsax', 36 | cookie: {httpOnly: true, secure: false, maxAge: 86400000}, 37 | store: new session.MemoryStore() 38 | })); 39 | 40 | app.use(passport.initialize()); 41 | app.use(passport.session()); 42 | 43 | 44 | passport.use(new LocalStrategy(function(username, password, done) { 45 | if (USER.username === username) { 46 | if (password === USER.password) { 47 | done(null, USER); 48 | } else { 49 | done(null, false, {msg: 'Incorrect password'}); 50 | } 51 | } else { 52 | done(null, false, {msg: 'Could not find user with username ' + username}); 53 | } 54 | })); 55 | 56 | 57 | 58 | app.use('/', serverStatic(__dirname + '/app')); 59 | 60 | var isLoggedIn = function(req, res, next) { 61 | if (req.isAuthenticated()) { 62 | next(); 63 | } else { 64 | res.send({ 65 | msg: 'Please login to access this information' 66 | }, 400); 67 | } 68 | }; 69 | 70 | app.get('/api/team', function(req, res) { 71 | res.send(FIFA.TEAMS_LIST); 72 | }); 73 | 74 | app.post('/api/login', function(req, res, next) { 75 | passport.authenticate('local', function(err, user) { 76 | if (err) {return next(err); } 77 | if (!user) { return res.send({loginStatus: false, msg: 'Unable to login'}, 400); } 78 | req.logIn(user, function(err) { 79 | if (err) { return res.send({msg: 'Error logging in', err: err}, 500); } 80 | return res.send({loginStatus: true, user: user}); 81 | }); 82 | })(req, res, next); 83 | }); 84 | 85 | app.get('/api/session', isLoggedIn, function(req, res) { 86 | res.send({ 87 | loginStatus: true, 88 | user: req.user 89 | }); 90 | }); 91 | 92 | app.get('/api/team/:code', isLoggedIn, function(req, res) { 93 | var code = req.params.code; 94 | res.send(FIFA.TEAM_DETAILS[code]); 95 | }); 96 | 97 | app.get('/api/logout', function(req, res) { 98 | req.logout(); 99 | res.redirect('/#/login'); 100 | }); 101 | 102 | var port = process.env.PORT || 8000; 103 | app.listen(port); 104 | console.log('Please go to http://localhost:' + port); 105 | 106 | -------------------------------------------------------------------------------- /chapter10/routing-html5-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter10/routing-html5-mode.png -------------------------------------------------------------------------------- /chapter10/simple-routing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS Routing 5 | 6 | 7 | 8 | 9 | 10 | 11 |

AngularJS Routing Application

12 | 13 | 18 | 19 |
20 | 21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /chapter11/directive-broken-reference/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-broken-reference/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120} 9 | ]; 10 | self.changeAllStocks = function() { 11 | for (var i = 0; i < 4; i++) { 12 | self.stocks[i] = { 13 | name: 'Controller Stock', 14 | price: 200, 15 | previous: 250 16 | }; 17 | } 18 | }; 19 | 20 | self.changeFirstStock = function() { 21 | self.stocks[0].name = 'Changed First Stock'; 22 | }; 23 | }]); 24 | -------------------------------------------------------------------------------- /chapter11/directive-broken-reference/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-broken-reference/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'A', 7 | scope: { 8 | stockData: '=' 9 | }, 10 | link: function($scope, $element, $attrs) { 11 | $scope.getChange = function(stock) { 12 | return Math.ceil(((stock.price - stock.previous) / 13 | stock.previous) * 100); 14 | }; 15 | 16 | $scope.changeStock = function() { 17 | $scope.stockData = { 18 | name: 'Directive Stock', 19 | price: 500, 20 | previous: 200 21 | }; 22 | }; 23 | } 24 | }; 25 | }]); 26 | -------------------------------------------------------------------------------- /chapter11/directive-broken-reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks Using Repeaters

10 | 11 |
12 |
13 |
14 |
15 | 16 |

List of Stocks Repeating Manually

17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /chapter11/directive-broken-reference/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 | 16 | 19 |
20 | -------------------------------------------------------------------------------- /chapter11/directive-with-link/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-link/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | }]); 13 | -------------------------------------------------------------------------------- /chapter11/directive-with-link/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-link/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'AE', 7 | link: function($scope, $element, $attrs) { 8 | $scope.getChange = function(stock) { 9 | return Math.ceil(((stock.price - stock.previous) / 10 | stock.previous) * 100); 11 | }; 12 | } 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter11/directive-with-link/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter11/directive-with-link/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter11/directive-with-restrict/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-restrict/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | 13 | self.getChange = function(stock) { 14 | return Math.ceil(( 15 | (stock.price - stock.previous) / stock.previous) * 100); 16 | }; 17 | }]); 18 | -------------------------------------------------------------------------------- /chapter11/directive-with-restrict/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-restrict/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'AE' 7 | }; 8 | }]); 9 | -------------------------------------------------------------------------------- /chapter11/directive-with-restrict/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter11/directive-with-restrict/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope-advanced/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-scope-advanced/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | 13 | self.onStockSelect = function(price, name) { 14 | console.log('Selected Price ', price, 'Name ', name); 15 | }; 16 | }]); 17 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope-advanced/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-scope-advanced/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'A', 7 | scope: { 8 | stockData: '=', 9 | stockTitle: '@', 10 | whenSelect: '&' 11 | }, 12 | link: function($scope, $element, $attrs) { 13 | $scope.getChange = function(stock) { 14 | return Math.ceil(((stock.price - stock.previous) / 15 | stock.previous) * 100); 16 | }; 17 | 18 | $scope.onSelect = function() { 19 | $scope.whenSelect({ 20 | stockName: $scope.stockData.name, 21 | stockPrice: $scope.stockData.price, 22 | stockPrevious: $scope.stockData.previous 23 | }); 24 | }; 25 | } 26 | }; 27 | }]); 28 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope-advanced/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope-advanced/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-scope/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | }]); 13 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-scope/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'A', 7 | scope: { 8 | stockData: '=' 9 | }, 10 | link: function($scope, $element, $attrs) { 11 | $scope.getChange = function(stock) { 12 | return Math.ceil(((stock.price - stock.previous) / 13 | stock.previous) * 100); 14 | }; 15 | } 16 | }; 17 | }]); 18 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter11/directive-with-scope/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter11/directive-with-template/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-template/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | 13 | self.getChange = function(stock) { 14 | return Math.ceil(((stock.price - stock.previous) / 15 | stock.previous) * 100); 16 | }; 17 | }]); 18 | -------------------------------------------------------------------------------- /chapter11/directive-with-template/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/directive-with-template/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html' 6 | }; 7 | }]); 8 | -------------------------------------------------------------------------------- /chapter11/directive-with-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter11/directive-with-template/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter11/ng-include/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter11/ng-include/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | 13 | self.stockTemplate = 'stock.html'; 14 | 15 | self.getChange = function(stock) { 16 | return Math.ceil(((stock.price - stock.previous) / 17 | stock.previous) * 100); 18 | }; 19 | }]); 20 | -------------------------------------------------------------------------------- /chapter11/ng-include/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter11/ng-include/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change: 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter11/ng-switch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Switch App 5 | 6 | 7 | 8 |
9 |

Conditional Elements in HTML

10 | 13 | 16 | 19 | 22 | 23 |
24 |
25 | Tab 1 is selected 26 |
27 |
28 | Tab 2 is selected 29 |
30 |
31 | Tab 3 is selected 32 |
33 |
34 | No known tab selected 35 |
36 |
37 |
38 | 39 | 40 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /chapter12/karma.conf.js: -------------------------------------------------------------------------------- 1 | // File: chapter12/karma.conf.js 2 | // Karma configuration 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine'], 8 | files: [ 9 | 'angular.min.js', 10 | 'angular-mocks.js', 11 | '*.js' 12 | ], 13 | exclude: [], 14 | port: 8080, 15 | logLevel: config.LOG_INFO, 16 | autoWatch: true, 17 | browsers: ['Chrome'], 18 | singleRun: false 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /chapter12/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajs-up-running-chapter-12", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "karma": "0.12.28", 6 | "karma-jasmine": "~0.2.0", 7 | "karma-chrome-launcher": "~0.1.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter12/stockDirective.js: -------------------------------------------------------------------------------- 1 | // File: chapter12/stockDirective.js 2 | angular.module('stockMarketApp', []) 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'A', 7 | scope: { 8 | stockData: '=', 9 | stockTitle: '@', 10 | whenSelect: '&' 11 | }, 12 | link: function($scope, $element, $attrs) { 13 | $scope.getChange = function(stock) { 14 | return Math.ceil(((stock.price - stock.previous) / 15 | stock.previous) * 100); 16 | }; 17 | $scope.onSelect = function() { 18 | $scope.whenSelect({ 19 | stockName: $scope.stockData.name, 20 | stockPrice: $scope.stockData.price, 21 | stockPrevious: $scope.stockData.previous 22 | }); 23 | }; 24 | } 25 | }; 26 | }]); 27 | -------------------------------------------------------------------------------- /chapter12/stockDirectiveBehaviorSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter12/stockDirectiveBehaviorSpec.js 2 | describe('Stock Widget Directive Behavior', function() { 3 | 4 | beforeEach(module('stockMarketApp')); 5 | 6 | var compile, mockBackend, rootScope; 7 | 8 | // Step 1 9 | beforeEach(inject(function($compile, $httpBackend, $rootScope) { 10 | compile = $compile; 11 | mockBackend = $httpBackend; 12 | rootScope = $rootScope; 13 | })); 14 | 15 | it('should have functions and data on scope correctly', 16 | function() { 17 | // Step 2 18 | var scope = rootScope.$new(); 19 | var scopeClickCalled = ''; 20 | scope.myStock = { 21 | name: 'Best Stock', 22 | price: 100, 23 | previous: 200 24 | }; 25 | scope.title = 'the best'; 26 | scope.userClick = function(stockPrice, 27 | stockPrevious, 28 | stockName) { 29 | scopeClickCalled = stockPrice + 30 | ';' + stockPrevious + 31 | ';' + stockName; 32 | }; 33 | 34 | // Step 3 35 | mockBackend.expectGET('stock.html').respond( 36 | '
' + 37 | '
'); 38 | 39 | // Step 4 40 | var element = compile( 41 | '
' + 46 | '
' 47 | )(scope); 48 | 49 | // Step 5 50 | scope.$digest(); 51 | mockBackend.flush(); 52 | 53 | // Step 6 54 | var compiledElementScope = element.isolateScope(); 55 | 56 | expect(compiledElementScope.stockData) 57 | .toEqual(scope.myStock); 58 | expect(compiledElementScope.getChange( 59 | compiledElementScope.stockData)).toEqual(-50); 60 | 61 | 62 | // Step 7 63 | expect(scopeClickCalled).toEqual(''); 64 | 65 | compiledElementScope.onSelect(); 66 | 67 | expect(scopeClickCalled).toEqual('100;200;Best Stock'); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /chapter12/stockDirectiveRenderSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter12/stockDirectiveRenderSpec.js 2 | describe('Stock Widget Directive Rendering', function() { 3 | 4 | beforeEach(module('stockMarketApp')); 5 | 6 | var compile, mockBackend, rootScope; 7 | 8 | // Step 1 9 | beforeEach(inject(function($compile, $httpBackend, $rootScope) { 10 | compile = $compile; 11 | mockBackend = $httpBackend; 12 | rootScope = $rootScope; 13 | })); 14 | 15 | it('should render HTML based on scope correctly', function() { 16 | // Step 2 17 | var scope = rootScope.$new(); 18 | scope.myStock = { 19 | name: 'Best Stock', 20 | price: 100, 21 | previous: 200 22 | }; 23 | scope.title = 'the best'; 24 | 25 | // Step 3 26 | mockBackend.expectGET('stock.html').respond( 27 | '
' + 28 | '
'); 29 | 30 | // Step 4 31 | var element = compile('
')(scope); 34 | 35 | // Step 5 36 | scope.$digest(); 37 | mockBackend.flush(); 38 | 39 | // Step 6 40 | expect(element.html()).toEqual( 41 | '
' + 42 | 'This is the best' + 43 | '
' + 44 | '
' + 45 | '100' + 46 | '
'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /chapter13/chapter-13-AngularJS Digest Cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter13/chapter-13-AngularJS Digest Cycle.png -------------------------------------------------------------------------------- /chapter13/chapter-13-AngularJS-Lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter13/chapter-13-AngularJS-Lifecycle.png -------------------------------------------------------------------------------- /chapter13/directive-advanced-transclusion/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-advanced-transclusion/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | }]); 13 | -------------------------------------------------------------------------------- /chapter13/directive-advanced-transclusion/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-advanced-transclusion/directive.js 2 | angular.module('stockMarketApp').directive('simpleStockRepeat', 3 | [function() { 4 | return { 5 | restrict: 'A', 6 | // Capture and replace the entire element 7 | // instead of just its content 8 | transclude: 'element', 9 | // A $transclude is passed in as the fifth 10 | // argument to the link function 11 | link: function($scope, $element, $attrs, ctrl, $transclude) { 12 | var myArray = $scope.$eval($attrs.simpleStockRepeat); 13 | 14 | var container = angular.element( 15 | '
'); 16 | for (var i = 0; i < myArray.length; i++) { 17 | // Create an element instance with a new child 18 | // scope using the clone linking function 19 | var instance = $transclude($scope.$new(), 20 | function(clonedElement, newScope) { 21 | // Expose custom variables for the instance 22 | newScope.currentIndex = i; 23 | newScope.stock = myArray[i]; 24 | }); 25 | // Add it to our container 26 | container.append(instance); 27 | } 28 | 29 | // With transclude: 'element', the element gets replaced 30 | // with a comment. Add our generated content 31 | // after the comment 32 | $element.after(container); 33 | } 34 | }; 35 | }]); 36 | -------------------------------------------------------------------------------- /chapter13/directive-advanced-transclusion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 | We found {{stock.name}} at {{currentIndex}} 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter13/directive-compile/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-compile/app.js 2 | 3 | angular.module('dynamicFormApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.username = ''; 7 | self.password = ''; 8 | }]); 9 | -------------------------------------------------------------------------------- /chapter13/directive-compile/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-compile/directive.js 2 | angular.module('dynamicFormApp') 3 | .directive('formElement', [function() { 4 | 5 | return { 6 | restrict: 'E', 7 | require: '^form', 8 | scope: true, 9 | compile: function($element, $attrs) { 10 | var expectedInputAttrs = { 11 | 'required': 'required', 12 | 'ng-minlength': 'ngMinlength', 13 | 'ng-pattern': 'ngPattern' 14 | // More here to be implemented 15 | }; 16 | 17 | // Start extracting content from the HTML 18 | var validationKeys = $element.find('validation'); 19 | var presentValidationKeys = {}; 20 | var inputName = $attrs.name; 21 | angular.forEach(validationKeys, function(validationKey) { 22 | validationKey = angular.element(validationKey); 23 | presentValidationKeys[validationKey.attr('key')] = 24 | validationKey.text(); 25 | }); 26 | 27 | // Start generating final element HTML 28 | var elementHtml = '
' + 29 | ''; 30 | elementHtml += ''; 49 | 50 | elementHtml += '
'; 51 | $element.html(elementHtml); 52 | 53 | return function($scope, $element, $attrs, formCtrl) { 54 | $scope.validators = angular.copy(presentValidationKeys); 55 | $scope.hasError = function(key) { 56 | return !!formCtrl[inputName]['$error'][key]; 57 | }; 58 | }; 59 | } 60 | }; 61 | }]); 62 | -------------------------------------------------------------------------------- /chapter13/directive-compile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dynamic Form App 5 | 6 | 7 | 8 |
9 |
10 | 16 | 17 | Please enter a username 18 | 19 | 20 | Username must be atleast 5 characters 21 | 22 | 23 | 24 | Username is {{mainCtrl.username}} 25 | 26 | 32 | 33 | Please enter a password 34 | 35 | 36 | Password must only be alphanumeric characters 37 | 38 | 39 | Password is {{mainCtrl.password}} 40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /chapter13/directive-controllers/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-controllers/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | 7 | self.startedTime = new Date().getTime(); 8 | self.stocks = [ 9 | {name: 'First Stock', price: 100, previous: 220}, 10 | {name: 'Second Stock', price: 140, previous: 120}, 11 | {name: 'Third Stock', price: 110, previous: 110}, 12 | {name: 'Fourth Stock', price: 400, previous: 420} 13 | ]; 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter13/directive-controllers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | This is the first tab. 13 | The app started at {{mainCtrl.startedTime | date}} 14 | 15 | 16 | This is the second tab 17 |
18 | Stock Name: {{stock.name}} 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /chapter13/directive-controllers/main.css: -------------------------------------------------------------------------------- 1 | .tab-headers div { 2 | display: inline-block; 3 | padding: 10px; 4 | border: 1px solid black; 5 | cursor: pointer; 6 | } 7 | 8 | .tab-headers div.selected { 9 | background-color: greenyellow; 10 | } 11 | -------------------------------------------------------------------------------- /chapter13/directive-controllers/tab.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-controllers/tab.js 2 | angular.module('stockMarketApp') 3 | .directive('tab', [function() { 4 | return { 5 | restrict: 'E', 6 | transclude: true, 7 | template: '
', 8 | require: '^tabs', 9 | scope: true, 10 | link: function($scope, $element, $attr, tabCtrl) { 11 | tabCtrl.registerTab($attr.title, $scope); 12 | } 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter13/directive-controllers/tabs.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-controllers/tabs.js 2 | angular.module('stockMarketApp') 3 | .directive('tabs', [function() { 4 | return { 5 | restrict: 'E', 6 | transclude: true, 7 | scope: true, 8 | template: '
' + 9 | '
' + 12 | ' ' + 13 | '
' + 14 | '
' + 15 | '
', 16 | controller: function($scope) { 17 | var currentIndex = 0; 18 | $scope.tabs = []; 19 | this.registerTab = function(title, scope) { 20 | if ($scope.tabs.length === 0) { 21 | scope.selected = true; 22 | } else { 23 | scope.selected = false; 24 | } 25 | $scope.tabs.push({title: title, scope: scope}); 26 | }; 27 | 28 | $scope.selectTab = function(index) { 29 | currentIndex = index; 30 | for (var i = 0; i < $scope.tabs.length; i++) { 31 | $scope.tabs[i].scope.selected = currentIndex === i; 32 | } 33 | }; 34 | 35 | $scope.isSelectedTab = function(index) { 36 | return currentIndex === index; 37 | }; 38 | } 39 | }; 40 | }]); 41 | -------------------------------------------------------------------------------- /chapter13/directive-custom-validator/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-custom-validator/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | this.zip = ''; 6 | }]); 7 | -------------------------------------------------------------------------------- /chapter13/directive-custom-validator/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-custom-validator/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('validZip', [function() { 4 | var zipCodeRegex = /^\d{5}(?:[-\s]\d{4})?$/g; 5 | return { 6 | restrict: 'A', 7 | require: 'ngModel', 8 | link: function($scope, $element, $attrs, ngModelCtrl) { 9 | ngModelCtrl.$validators.zip = function(value) { 10 | return zipCodeRegex.test(value); 11 | }; 12 | } 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter13/directive-custom-validator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 10 | 11 | 12 | 13 |
14 |

Zip Code Input

15 |
Zips are allowed in one of the following formats
16 | 21 |
22 | Enter valid zip code: 23 | 27 |
Zipcode is invalid
28 | 29 |
30 |
31 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter13/directive-google-chart/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-google-chart/app.js 2 | 3 | angular.module('googleChartApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.pieChartData = [ 7 | {label: 'First', value: 25}, 8 | {label: 'Second', value: 54}, 9 | {label: 'Third', value: 75} 10 | ]; 11 | 12 | self.pieChartConfig = { 13 | title: 'One Two Three Chart', 14 | firstColumnHeader: 'Counter', 15 | secondColumnHeader: 'Actual Value' 16 | }; 17 | 18 | self.changeData = function() { 19 | self.pieChartData[1].value = 25; 20 | }; 21 | }]); 22 | -------------------------------------------------------------------------------- /chapter13/directive-google-chart/googleChartLoader.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-google-chart/googleChartLoader.js 2 | angular.module('googleChartApp') 3 | .factory('googleChartLoaderPromise', 4 | ['$q', '$rootScope', '$window', 5 | function($q, $rootScope, $window) { 6 | // Create a Deferred Object 7 | var deferred = $q.defer(); 8 | 9 | // Load Google Charts API asynchronously 10 | $window.google.load('visualization', '1', 11 | { 12 | packages: ['corechart'], 13 | callback: function() { 14 | // Once loaded, trigger the resolve, 15 | // but inside a $apply as the event happens 16 | // outside of AngularJS lifecycle 17 | $rootScope.$apply(function() { 18 | deferred.resolve(); 19 | }); 20 | } 21 | }); 22 | 23 | // Return the promise object for the directive 24 | // to chain onto. 25 | return deferred.promise; 26 | }]); 27 | -------------------------------------------------------------------------------- /chapter13/directive-google-chart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Google Chart App 5 | 6 | 7 | 8 |
9 |
10 | 13 |
14 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /chapter13/directive-google-chart/pieChart.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-google-chart/pieChart.js 2 | angular.module('googleChartApp') 3 | .directive('pieChart', ['googleChartLoaderPromise', 4 | function(googleChartLoaderPromise) { 5 | var convertToPieChartDataTableFormat = 6 | function(firstColumnName, secondColumnName, data) { 7 | var pieChartArray = [[firstColumnName, secondColumnName]]; 8 | for (var i = 0; i < data.length; i++) { 9 | pieChartArray.push([data[i].label, data[i].value]); 10 | } 11 | return google.visualization.arrayToDataTable( 12 | pieChartArray); 13 | }; 14 | 15 | return { 16 | restrict: 'A', 17 | scope: { 18 | chartData: '=', 19 | chartConfig: '=' 20 | }, 21 | link: function($scope, $element) { 22 | 23 | googleChartLoaderPromise.then(function() { 24 | var chart = new google.visualization.PieChart( 25 | $element[0]); 26 | 27 | $scope.$watch('chartData', function(newVal, oldVal) { 28 | var config = $scope.chartConfig; 29 | if (newVal) { 30 | chart.draw( 31 | convertToPieChartDataTableFormat( 32 | config.firstColumnHeader, 33 | config.secondColumnHeader, 34 | newVal), 35 | {title: $scope.chartConfig.title}); 36 | } 37 | }, true); 38 | }); 39 | } 40 | }; 41 | }]); 42 | -------------------------------------------------------------------------------- /chapter13/directive-no-transclusion/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-no-transclusion/app.js 2 | 3 | angular.module('stockMarketApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.stocks = [ 7 | {name: 'First Stock', price: 100, previous: 220}, 8 | {name: 'Second Stock', price: 140, previous: 120}, 9 | {name: 'Third Stock', price: 110, previous: 110}, 10 | {name: 'Fourth Stock', price: 400, previous: 420} 11 | ]; 12 | }]); 13 | -------------------------------------------------------------------------------- /chapter13/directive-no-transclusion/directive.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-no-transclusion/directive.js 2 | angular.module('stockMarketApp') 3 | .directive('stockWidget', [function() { 4 | return { 5 | templateUrl: 'stock.html', 6 | restrict: 'A', 7 | scope: { 8 | stockData: '=' 9 | }, 10 | link: function($scope, $element, $attrs) { 11 | $scope.getChange = function(stock) { 12 | return Math.ceil(((stock.price - stock.previous) / 13 | stock.previous) * 100); 14 | }; 15 | } 16 | }; 17 | }]); 18 | -------------------------------------------------------------------------------- /chapter13/directive-no-transclusion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stock Market App 5 | 6 | 7 | 8 |
9 |

List of Stocks

10 |
11 |
12 | This content will be blown away 13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter13/directive-no-transclusion/stock.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Name: 4 | 6 | 7 | Price: 8 | 10 | 11 | Percentage Change 12 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /chapter13/directive-slider/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter13/directive-slider/app.js 2 | 3 | angular.module('sliderApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | 7 | self.selectedValue = 2000; 8 | 9 | self.textValue = 4000; 10 | 11 | self.setSelectedValue = function() { 12 | self.selectedValue = self.textValue; 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter13/directive-slider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slider App 5 | 6 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | The current value of the slider is {{mainCtrl.selectedValue}} 20 |
21 | 22 | 26 | 27 | 28 | 29 |
30 | 35 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /chapter13/directive-slider/jquery.nouislider.css: -------------------------------------------------------------------------------- 1 | 2 | /* Functional styling; 3 | * These styles are required for noUiSlider to function. 4 | * You don't need to change these rules to apply your design. 5 | */ 6 | .noUi-target, 7 | .noUi-target * { 8 | -webkit-touch-callout: none; 9 | -webkit-user-select: none; 10 | -ms-touch-action: none; 11 | -ms-user-select: none; 12 | -moz-user-select: none; 13 | -moz-box-sizing: border-box; 14 | box-sizing: border-box; 15 | } 16 | .noUi-base { 17 | width: 100%; 18 | height: 100%; 19 | position: relative; 20 | } 21 | .noUi-origin { 22 | position: absolute; 23 | right: 0; 24 | top: 0; 25 | left: 0; 26 | bottom: 0; 27 | } 28 | .noUi-handle { 29 | position: relative; 30 | z-index: 1; 31 | } 32 | .noUi-stacking .noUi-handle { 33 | /* This class is applied to the lower origin when 34 | its values is > 50%. */ 35 | z-index: 10; 36 | } 37 | .noUi-stacking + .noUi-origin { 38 | /* Fix stacking order in IE7, which incorrectly 39 | creates a new context for the origins. */ 40 | *z-index: -1; 41 | } 42 | .noUi-state-tap .noUi-origin { 43 | -webkit-transition: left 0.3s, top 0.3s; 44 | transition: left 0.3s, top 0.3s; 45 | } 46 | .noUi-state-drag * { 47 | cursor: inherit !important; 48 | } 49 | 50 | /* Slider size and handle placement; 51 | */ 52 | .noUi-horizontal { 53 | height: 18px; 54 | } 55 | .noUi-horizontal .noUi-handle { 56 | width: 34px; 57 | height: 28px; 58 | left: -17px; 59 | top: -6px; 60 | } 61 | .noUi-horizontal.noUi-extended { 62 | padding: 0 15px; 63 | } 64 | .noUi-horizontal.noUi-extended .noUi-origin { 65 | right: -15px; 66 | } 67 | .noUi-vertical { 68 | width: 18px; 69 | } 70 | .noUi-vertical .noUi-handle { 71 | width: 28px; 72 | height: 34px; 73 | left: -6px; 74 | top: -17px; 75 | } 76 | .noUi-vertical.noUi-extended { 77 | padding: 15px 0; 78 | } 79 | .noUi-vertical.noUi-extended .noUi-origin { 80 | bottom: -15px; 81 | } 82 | 83 | /* Styling; 84 | */ 85 | .noUi-background { 86 | background: #FAFAFA; 87 | box-shadow: inset 0 1px 1px #f0f0f0; 88 | } 89 | .noUi-connect { 90 | background: #3FB8AF; 91 | box-shadow: inset 0 0 3px rgba(51,51,51,0.45); 92 | -webkit-transition: background 450ms; 93 | transition: background 450ms; 94 | } 95 | .noUi-origin { 96 | border-radius: 2px; 97 | } 98 | .noUi-target { 99 | border-radius: 4px; 100 | border: 1px solid #D3D3D3; 101 | box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; 102 | } 103 | .noUi-target.noUi-connect { 104 | box-shadow: inset 0 0 3px rgba(51,51,51,0.45), 0 3px 6px -5px #BBB; 105 | } 106 | 107 | /* Handles and cursors; 108 | */ 109 | .noUi-dragable { 110 | cursor: w-resize; 111 | } 112 | .noUi-vertical .noUi-dragable { 113 | cursor: n-resize; 114 | } 115 | .noUi-handle { 116 | border: 1px solid #D9D9D9; 117 | border-radius: 3px; 118 | background: #FFF; 119 | cursor: default; 120 | box-shadow: inset 0 0 1px #FFF, 121 | inset 0 1px 7px #EBEBEB, 122 | 0 3px 6px -3px #BBB; 123 | } 124 | .noUi-active { 125 | box-shadow: inset 0 0 1px #FFF, 126 | inset 0 1px 7px #DDD, 127 | 0 3px 6px -3px #BBB; 128 | } 129 | 130 | /* Handle stripes; 131 | */ 132 | .noUi-handle:before, 133 | .noUi-handle:after { 134 | content: ""; 135 | display: block; 136 | position: absolute; 137 | height: 14px; 138 | width: 1px; 139 | background: #E8E7E6; 140 | left: 14px; 141 | top: 6px; 142 | } 143 | .noUi-handle:after { 144 | left: 17px; 145 | } 146 | .noUi-vertical .noUi-handle:before, 147 | .noUi-vertical .noUi-handle:after { 148 | width: 14px; 149 | height: 1px; 150 | left: 6px; 151 | top: 14px; 152 | } 153 | .noUi-vertical .noUi-handle:after { 154 | top: 17px; 155 | } 156 | 157 | /* Disabled state; 158 | */ 159 | [disabled].noUi-connect, 160 | [disabled] .noUi-connect { 161 | background: #B8B8B8; 162 | } 163 | [disabled] .noUi-handle { 164 | cursor: not-allowed; 165 | } 166 | -------------------------------------------------------------------------------- /chapter13/directive-slider/jquery.nouislider.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | $.Link (part of noUiSlider) - WTFPL */ 4 | (function(c){function m(a,c,d){if((a[c]||a[d])&&a[c]===a[d])throw Error("(Link) '"+c+"' can't match '"+d+"'.'");}function r(a){void 0===a&&(a={});if("object"!==typeof a)throw Error("(Format) 'format' option must be an object.");var h={};c(u).each(function(c,n){if(void 0===a[n])h[n]=A[c];else if(typeof a[n]===typeof A[c]){if("decimals"===n&&(0>a[n]||7a&&(n=this.a("negative"),k=this.a("negativeBefore"));a=Math.abs(a).toFixed(d).toString();a=a.split(".");this.a("thousand")?(m=c(a[0]).match(/.{1,3}/g),m=c(m.join(c(this.a("thousand"))))):m=a[0];this.a("mark")&&1")[0]};k.prototype.H=function(a){this.method="val";this.j=document.createElement("input");this.j.name=a;this.j.type="hidden"};k.prototype.G=function(a){function h(a,c){return[c?null:a,c?a:null]}var d=this;this.method="val";this.target=a.on("change",function(a){d.B.val(h(c(a.target).val(),d.t),{link:d,set:!0})})};k.prototype.p=function(a,h,d,k){this.g=d;this.update=!k;if("string"=== 9 | typeof a&&0===a.indexOf("-tooltip-"))this.K(a,h);else if("string"===typeof a&&0!==a.indexOf("-"))this.H(a);else if("function"===typeof a)this.target=!1,this.method=a;else{if(a instanceof c||c.zepto&&c.zepto.isZ(a)){if(!h){if(a.is("input, select, textarea")){this.G(a);return}h="html"}if("function"===typeof h||"string"===typeof h&&a[h]){this.method=h;this.target=a;return}}throw new RangeError("(Link) Invalid Link.");}};k.prototype.write=function(a,c,d,k){if(!this.update||!1!==k)if(this.u=a,this.F=a= 10 | this.format(a),"function"===typeof this.method)this.method.call(this.target[0]||d[0],a,c,d);else this.target[this.method](a,c,d)};k.prototype.q=function(a){this.g=new r(c.extend({},a,this.g instanceof r?this.g.r:this.g))};k.prototype.J=function(a){this.B=a};k.prototype.I=function(a){this.t=a};k.prototype.format=function(a){return this.g.L(a)};k.prototype.A=function(a){return this.g.w(a)};k.prototype.p.prototype=k.prototype;c.Link=k})(window.jQuery||window.Zepto);/* 11 | 12 | $.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */ 13 | (function(c){function m(e){return"number"===typeof e&&!isNaN(e)&&isFinite(e)}function r(e){return c.isArray(e)?e:[e]}function k(e,b){e.addClass(b);setTimeout(function(){e.removeClass(b)},300)}function u(e,b){return 100*b/(e[1]-e[0])}function A(e,b){if(b>=e.d.slice(-1)[0])return 100;for(var a=1,c,f,d;b>=e.d[a];)a++;c=e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return d+u(c,0>c[0]?b+Math.abs(c[0]):b-c[0])/(100/(e.c[a]-d))}function a(e,b){if(100<=b)return e.d.slice(-1)[0];for(var a=1,c,f,d;b>=e.c[a];)a++;c= 14 | e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return 100/(e.c[a]-d)*(b-d)*(c[1]-c[0])/100+c[0]}function h(a,b){for(var c=1,g;(a.dir?100-b:b)>=a.c[c];)c++;if(a.m)return g=a.c[c-1],c=a.c[c],b-g>(c-g)/2?c:g;a.h[c-1]?(g=a.h[c-1],c=a.c[c-1]+Math.round((b-a.c[c-1])/g)*g):c=b;return c}function d(a,b){if(!m(b))throw Error("noUiSlider: 'step' is not numeric.");a.h[0]=b}function n(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'."); 15 | c.each(b,function(b,g){var d;"number"===typeof g&&(g=[g]);if(!c.isArray(g))throw Error("noUiSlider: 'range' contains invalid value.");d="min"===b?0:"max"===b?100:parseFloat(b);if(!m(d)||!m(g[0]))throw Error("noUiSlider: 'range' value isn't numeric.");a.c.push(d);a.d.push(g[0]);d?a.h.push(isNaN(g[1])?!1:g[1]):isNaN(g[1])||(a.h[0]=g[1])});c.each(a.h,function(b,c){if(!c)return!0;a.h[b]=u([a.d[b],a.d[b+1]],c)/(100/(a.c[b+1]-a.c[b]))})}function E(a,b){"number"===typeof b&&(b=[b]);if(!c.isArray(b)||!b.length|| 16 | 2
").addClass(f[2]),g=["-lower","-upper"];a.dir&&g.reverse();d.children().addClass(f[3]+" "+f[3]+g[b]);return d}function Q(a,b){b.j&&(b=new c.Link({target:c(b.j).clone().appendTo(a),method:b.method,format:b.g},!0));return b}function R(a,b){var d,f=[];for(d=0;d").appendTo(b).addClass(f[1])}function V(d,b,m){function g(){return t[["width","height"][b.k]]()}function n(a){var b,c=[q.val()];for(b=0;bp&&(p=h(b,p));p=Math.max(Math.min(parseFloat(p.toFixed(7)),100),0);if(p===x[g])return 1===l.length?!1:p===H||p===k?0:!1;d.css(b.style,p+"%");d.is(":first-child")&&d.toggleClass(f[17],50d&&(e+=Math.abs(d)),100 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter14/appUnderTest/app/favicon.ico -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FIFA Teams 5 | 6 | 7 | 8 | 9 |
10 |
11 | FIFA TEAMS 12 |
13 |
14 | 17 | 18 | Logout 19 | 20 |
21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/appUnderTest/app/scripts/app.js 2 | angular.module('fifaApp', ['ngRoute']) 3 | .config(function($routeProvider) { 4 | 5 | $routeProvider.when('/', { 6 | templateUrl: 'views/team_list.html', 7 | controller: 'TeamListCtrl as teamListCtrl' 8 | }) 9 | .when('/login', { 10 | templateUrl: 'views/login.html', 11 | }) 12 | .when('/team/:code', { 13 | templateUrl: 'views/team_details.html', 14 | controller:'TeamDetailsCtrl as teamDetailsCtrl', 15 | resolve: { 16 | auth: ['$q', '$location', 'UserService', 17 | function($q, $location, UserService) { 18 | return UserService.session().then( 19 | function(success) {}, 20 | function(err) { 21 | $location.path('/login'); 22 | return $q.reject(err); 23 | }); 24 | }] 25 | } 26 | }); 27 | $routeProvider.otherwise({ 28 | redirectTo: '/' 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/scripts/controllers.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/appUnderTest/app/scripts/controllers.js 2 | angular.module('fifaApp') 3 | .controller('MainCtrl', ['UserService', 4 | function(UserService) { 5 | var self = this; 6 | self.userService = UserService; 7 | 8 | // User Service will automatically update isLoggedIn 9 | // after this call finishes 10 | UserService.session(); 11 | }]) 12 | 13 | .controller('TeamListCtrl', ['FifaService', 14 | function(FifaService) { 15 | var self = this; 16 | self.teams = []; 17 | 18 | FifaService.getTeams().then(function(resp) { 19 | self.teams = resp.data; 20 | }); 21 | }]) 22 | 23 | .controller('LoginCtrl', ['UserService', '$location', 24 | function(UserService, $location) { 25 | var self = this; 26 | self.user = {username: '', password: ''}; 27 | 28 | self.login = function() { 29 | UserService.login(self.user).then(function(success) { 30 | $location.path('/team'); 31 | }, function(error) { 32 | console.error('Unable to login'); 33 | }) 34 | }; 35 | }]) 36 | 37 | .controller('TeamDetailsCtrl', ['$location', '$routeParams', 'FifaService', 38 | function($location, $routeParams, FifaService) { 39 | var self = this; 40 | self.team = {}; 41 | FifaService.getTeamDetails($routeParams.code).then(function(resp){ 42 | self.team = resp.data; 43 | }, function(error){ 44 | $location.path('/login'); 45 | }); 46 | }]); 47 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/scripts/services.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/appUnderTest/app/scripts/services.js 2 | angular.module('fifaApp') 3 | .factory('FifaService', ['$http', 4 | function($http) { 5 | return { 6 | getTeams: function() { 7 | return $http.get('/api/team'); 8 | }, 9 | 10 | getTeamDetails: function(code) { 11 | return $http.get('/api/team/' + code); 12 | } 13 | } 14 | }]) 15 | .factory('UserService', ['$http', function($http) { 16 | var service = { 17 | isLoggedIn: false, 18 | 19 | session: function() { 20 | return $http.get('/api/session').then(function(response) { 21 | service.isLoggedIn = true; 22 | return response; 23 | }); 24 | }, 25 | 26 | login: function(user) { 27 | return $http.post('/api/login', user) 28 | .then(function(response) { 29 | service.isLoggedIn = true; 30 | return response; 31 | }); 32 | } 33 | }; 34 | return service; 35 | }]); 36 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/scripts/vendors/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.11 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,d,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),f=r.current;m=y(b,function(b){g.enter(b,null,m||c).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=f.scope=b;l.$emit("$viewContentLoaded"); 7 | l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,h,g){return{restrict:"ECA",priority:-400,link:function(a,c){var b=g.current,f=b.locals;c.html(f.$template);var y=d(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));y(a)}}}p=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,c){return d.extend(Object.create(a), 8 | c)}function h(a,d){var b=d.caseInsensitiveMatch,f={originalPath:a,regexp:a},g=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;g.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=new RegExp("^"+a+"$",b?"i":"");return f}var g={};this.when=function(a,c){var b=d.copy(c);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=d.extend(b,a&&h(a,b));if(a){var f="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[f]=d.extend({redirectTo:a},h(f,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,c,b,f,h,p,x){function l(b){var e=s.current; 10 | (v=(n=k())&&e&&n.$$route===e.$$route&&d.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?c.path(t(e.redirectTo,e.params)).search(e.params).replace():c.url(e.redirectTo(e.pathParams,c.path(),c.search())).replace()),f.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,c;d.forEach(a,function(b,e){a[e]=d.isString(b)?h.get(b):h.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(c=e.templateUrl)&&(d.isFunction(c)&&(c=c(e.params)),c=x.getTrustedResourceUrl(c),d.isDefined(c)&&(e.loadedTemplateUrl=c,b=p(c)));d.isDefined(b)&&(a.$template=b);return f.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function k(){var a,b;d.forEach(g,function(f,g){var q;if(q=!b){var h=c.path();q=f.keys;var l={};if(f.regexp)if(h=f.regexp.exec(h)){for(var k=1,m=h.length;k 2 | 35 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/views/team_details.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 | 9 | () 10 |
11 |
12 |
13 | Nickname 14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | FIFA Ranking 22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | Association 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | Head Coach 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | Captain 46 |
47 |
48 | 49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/app/views/team_list.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | 17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/fifa.js: -------------------------------------------------------------------------------- 1 | var FIFA = {}; 2 | 3 | FIFA.TEAMS_LIST = [ 4 | { 5 | code:'ESP', 6 | name:'Spain', 7 | rank: 1, 8 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg' 9 | }, 10 | 11 | { 12 | code: 'GER', 13 | name: 'Germany', 14 | rank: 2, 15 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/b/ba/Flag_of_Germany.svg' 16 | }, 17 | 18 | { 19 | code: 'POR', 20 | name: 'Portugal', 21 | rank: 3, 22 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/5/5c/Flag_of_Portugal.svg' 23 | }, 24 | 25 | { 26 | code: 'COL', 27 | name: 'Colombia', 28 | rank: 4, 29 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/2/21/Flag_of_Colombia.svg' 30 | }, 31 | 32 | { 33 | code: 'URU', 34 | name: 'Uruguay', 35 | rank: 5, 36 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/f/fe/Flag_of_Uruguay.svg' 37 | } 38 | ]; 39 | 40 | FIFA.TEAM_DETAILS = { 41 | 'ESP' : { 42 | fifaCode: 'ESP', 43 | fifaRanking: 1, 44 | name: 'Spain', 45 | nickname: 'La Furia Roja (The Red Fury)', 46 | association: 'Real Federación Española de Fútbol (RFEF)', 47 | headCoach: 'Vicente del Bosque', 48 | captain: 'Iker Casillas', 49 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg', 50 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/3/31/Spain_National_Football_Team_badge.png' 51 | }, 52 | 53 | 'GER' : { 54 | fifaCode: 'GER', 55 | fifaRanking: 2, 56 | name: 'Germany', 57 | nickname: 'Nationalmannschaft (national team)', 58 | association: 'German Football Association', 59 | headCoach: 'Joachim Löw', 60 | captain: 'Philipp Lahm', 61 | flagUrl: 'http://upload.wikimedia.org/wikipedia/en/b/ba/Flag_of_Germany.svg', 62 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/e/e3/DFBEagle.svg/442px-DFBEagle.svg.png' 63 | }, 64 | 65 | 'POR' : { 66 | fifaCode: 'POR', 67 | fifaRanking: 3, 68 | name: 'Portugal', 69 | nickname: 'A Selecção', 70 | association: 'Federação Portuguesa de Futebol', 71 | headCoach: 'Paulo Bento', 72 | captain: 'Cristiano Ronaldo', 73 | flagUrl:'http://upload.wikimedia.org/wikipedia/commons/5/5c/Flag_of_Portugal.svg', 74 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/5/5f/Portuguese_Football_Federation.svg/424px-Portuguese_Football_Federation.svg.png' 75 | }, 76 | 77 | 'COL' : { 78 | fifaCode: 'COL', 79 | fifaRanking: 4, 80 | name: 'Colombia', 81 | nickname: 'Los Cafeteros (The Coffee Growers)', 82 | association: 'Federación Colombiana de Fútbol (FCF)', 83 | headCoach: 'José Pékerman', 84 | captain: 'Mario Yepes', 85 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/2/21/Flag_of_Colombia.svg', 86 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/6/61/Federacion_Colombiana_de_Futbol_logo.svg/302px-Federacion_Colombiana_de_Futbol_logo.svg.png' 87 | }, 88 | 89 | 'URU' : { 90 | fifaCode: 'URU', 91 | fifaRanking: 5, 92 | name: 'Uruguay', 93 | nickname: 'Los Charrúas', 94 | association: 'Asociación Uruguaya de Fútbol (AUF)', 95 | headCoach: 'Óscar Tabárez', 96 | captain: 'Diego Lugano', 97 | flagUrl: 'http://upload.wikimedia.org/wikipedia/commons/f/fe/Flag_of_Uruguay.svg', 98 | logoUrl: 'http://upload.wikimedia.org/wikipedia/en/thumb/d/d1/Uruguay_football_association.svg/195px-Uruguay_football_association.svg.png' 99 | } 100 | }; 101 | 102 | 103 | exports.FIFA = FIFA; 104 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FIFA_TEAMS", 3 | "description": "FIFA TEAMS APP For Angular Routing", 4 | "version": "0.0.1", 5 | "private": true, 6 | "engines": { 7 | "node": "0.10.x", 8 | "npm": "1.3.x" 9 | }, 10 | "dependencies": { 11 | "express" : "4.1.1", 12 | "body-parser" : "1.0.2", 13 | "serve-static" : "1.1.0", 14 | "method-override" : "1.0.0", 15 | "express-session" : "1.0.4", 16 | "passport": "0.2.0", 17 | "compression": "1.0.2", 18 | "passport-local": "1.0.0", 19 | "cookie-parser": "1.0.1", 20 | "morgan": "1.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter14/appUnderTest/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by abhiroop on 5/6/14. 3 | */ 4 | var express = require('express'), 5 | http = require('http'), 6 | passport = require('passport'), 7 | morgan = require('morgan'), 8 | compress = require('compression'), 9 | bodyParser = require('body-parser'), 10 | methodOverride = require('method-override'), 11 | cookieParser = require('cookie-parser'), 12 | session = require('express-session'), 13 | LocalStrategy = require('passport-local').Strategy, 14 | serverStatic = require('serve-static'), 15 | FIFA = require('./fifa').FIFA; 16 | 17 | var USER = {username: 'admin', password: 'admin'}; 18 | 19 | var app = express(); 20 | app.use(morgan()); 21 | app.use(compress()); 22 | app.use(bodyParser()); 23 | 24 | passport.serializeUser(function(user, done) { 25 | done(null, user); 26 | }); 27 | 28 | passport.deserializeUser(function(user, done) { 29 | done(null, user); 30 | }); 31 | app.use(methodOverride()); 32 | app.use(cookieParser()); 33 | 34 | app.use(session({ 35 | secret : 'almvnirtgd#$DFsa25452*AYD*D*S!@!#adsda))Ddsadsax', 36 | cookie: {httpOnly: true, secure: false, maxAge: 86400000}, 37 | store: new session.MemoryStore() 38 | })); 39 | 40 | app.use(passport.initialize()); 41 | app.use(passport.session()); 42 | 43 | 44 | passport.use(new LocalStrategy(function(username, password, done) { 45 | if (USER.username === username) { 46 | if (password === USER.password) { 47 | done(null, USER); 48 | } else { 49 | done(null, false, {msg: 'Incorrect password'}); 50 | } 51 | } else { 52 | done(null, false, {msg: 'Could not find user with username ' + username}); 53 | } 54 | })); 55 | 56 | 57 | 58 | app.use('/', serverStatic(__dirname + '/app')); 59 | 60 | var isLoggedIn = function(req, res, next) { 61 | if (req.isAuthenticated()) { 62 | next(); 63 | } else { 64 | res.send({ 65 | msg: 'Please login to access this information' 66 | }, 400); 67 | } 68 | }; 69 | 70 | app.get('/api/team', function(req, res) { 71 | res.send(FIFA.TEAMS_LIST); 72 | }); 73 | 74 | app.post('/api/login', function(req, res, next) { 75 | passport.authenticate('local', function(err, user) { 76 | if (err) {return next(err); } 77 | if (!user) { return res.send({loginStatus: false, msg: 'Unable to login'}, 400); } 78 | req.logIn(user, function(err) { 79 | if (err) { return res.send({msg: 'Error logging in', err: err}, 500); } 80 | return res.send({loginStatus: true, user: user}); 81 | }); 82 | })(req, res, next); 83 | }); 84 | 85 | app.get('/api/session', isLoggedIn, function(req, res) { 86 | res.send({ 87 | loginStatus: true, 88 | user: req.user 89 | }); 90 | }); 91 | 92 | app.get('/api/team/:code', isLoggedIn, function(req, res) { 93 | var code = req.params.code; 94 | res.send(FIFA.TEAM_DETAILS[code]); 95 | }); 96 | 97 | app.get('/api/logout', function(req, res) { 98 | req.logout(); 99 | res.redirect('/#/login'); 100 | }); 101 | 102 | var port = process.env.PORT || 8000; 103 | app.listen(port); 104 | console.log('Please go to http://localhost:' + port); 105 | 106 | -------------------------------------------------------------------------------- /chapter14/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/protractor.conf.js 2 | exports.config = { 3 | // The address of a running selenium server. 4 | seleniumAddress: 'http://localhost:4444/wd/hub', 5 | 6 | // The address where our server under test is running 7 | baseUrl: 'http://localhost:8000/', 8 | 9 | // Capabilities to be passed to the webdriver instance. 10 | capabilities: { 11 | 'browserName': 'chrome' 12 | }, 13 | 14 | // Spec patterns are relative to the location of the 15 | // spec file. They may include glob patterns. 16 | specs: ['*Spec*.js'], 17 | 18 | // Options to be passed to Jasmine-node. 19 | jasmineNodeOpts: { 20 | showColors: true // Use colors in the command line report. 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /chapter14/routingSpecWithPageObjects.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/routingSpecWithPageObjects.js 2 | 3 | // The Page Objects are ideally in separate files 4 | // to allow for reuse across all the tests, 5 | // but here are listed together for ease of understanding 6 | 7 | function TeamsListPage() { 8 | this.open = function() { 9 | browser.get('/'); 10 | }; 11 | 12 | this.getTeamsListRows = function() { 13 | return element.all(by.repeater('team in teamListCtrl.teams')); 14 | }; 15 | 16 | this.getRankForRow = function(row) { 17 | return element( 18 | by.repeater('team in teamListCtrl.teams') 19 | .row(row).column('team.rank')); 20 | }; 21 | 22 | this.getNameForRow = function(row) { 23 | return element( 24 | by.repeater('team in teamListCtrl.teams') 25 | .row(row).column('team.name')); 26 | }; 27 | 28 | this.isLoginLinkVisible = function() { 29 | return element(by.css('.login-link')).isDisplayed(); 30 | }; 31 | 32 | this.isLogoutLinkVisible = function() { 33 | return element(by.css('.logout-link')).isDisplayed(); 34 | }; 35 | } 36 | 37 | describe('Routing Test With Page objects', function() { 38 | 39 | it('should show teams on the first page', function() { 40 | var teamsListPage = new TeamsListPage(); 41 | 42 | teamsListPage.open(); 43 | 44 | expect(teamsListPage.getTeamsListRows().count()).toEqual(5); 45 | 46 | expect(teamsListPage.getRankForRow(0).getText()) 47 | .toEqual('1'); 48 | expect(teamsListPage.getNameForRow(0).getText()) 49 | .toEqual('Spain'); 50 | 51 | expect(teamsListPage.getRankForRow(4).getText()) 52 | .toEqual('5'); 53 | expect(teamsListPage.getNameForRow(4).getText()) 54 | .toEqual('Uruguay'); 55 | 56 | // Check that login link is shown and 57 | // logout link is hidden 58 | expect(teamsListPage.isLoginLinkVisible()).toBe(true); 59 | expect(teamsListPage.isLogoutLinkVisible()).toBe(false); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /chapter14/simpleRoutingSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter14/simpleRoutingSpec.js 2 | 3 | describe('Routing Test', function() { 4 | 5 | it('should show teams on the first page', function() { 6 | // Open the list of teams page 7 | browser.get('/'); 8 | 9 | // Check if there are 5 rows in the repeater 10 | var rows = element.all( 11 | by.repeater('team in teamListCtrl.teams')); 12 | expect(rows.count()).toEqual(5); 13 | 14 | // Check the first row details 15 | var firstRowRank = element( 16 | by.repeater('team in teamListCtrl.teams') 17 | .row(0).column('team.rank')); 18 | var firstRowName = element( 19 | by.repeater('team in teamListCtrl.teams') 20 | .row(0).column('team.name')); 21 | expect(firstRowRank.getText()).toEqual('1'); 22 | expect(firstRowName.getText()).toEqual('Spain'); 23 | 24 | // Check the last row details 25 | var lastRowRank = element( 26 | by.repeater('team in teamListCtrl.teams') 27 | .row(4).column('team.rank')); 28 | var lastRowName = element( 29 | by.repeater('team in teamListCtrl.teams') 30 | .row(4).column('team.name')); 31 | expect(lastRowRank.getText()).toEqual('5'); 32 | expect(lastRowName.getText()).toEqual('Uruguay'); 33 | 34 | // Check that login link is shown and 35 | // logout link is hidden 36 | expect(element(by.css('.login-link')).isDisplayed()) 37 | .toBe(true); 38 | expect(element(by.css('.logout-link')).isDisplayed()) 39 | .toBe(false); 40 | }); 41 | 42 | it('should allow logging in', function() { 43 | // Navigate to the login page 44 | browser.get('#/login'); 45 | 46 | var username = element( 47 | by.model('loginCtrl.user.username')); 48 | var password = element( 49 | by.model('loginCtrl.user.password')); 50 | 51 | // Type in the username and password 52 | username.sendKeys('admin'); 53 | password.sendKeys('admin'); 54 | 55 | // Click on the login button 56 | element(by.css('.btn.btn-success')).click(); 57 | 58 | // Ensure that the user was redirected 59 | expect(browser.getCurrentUrl()) 60 | .toEqual('http://localhost:8000/#/'); 61 | 62 | // Check that login link is hidden and 63 | // logout link is show 64 | expect(element(by.css('.login-link')).isDisplayed()) 65 | .toBe(false); 66 | expect(element(by.css('.logout-link')).isDisplayed()) 67 | .toBe(true); 68 | 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /chapter15/batarang-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter15/batarang-screenshot.png -------------------------------------------------------------------------------- /chapter2/angularjs-manual-bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Hello {{1 + 2}}rd time AngularJS

7 | 8 | 11 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter2/controller-click-message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | {{ctrl.message}} AngularJS. 6 | 7 | 10 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /chapter2/creating-controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello AngularJS 4 | 5 | Hello {{1 + 1}}nd time AngularJS 6 | 7 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /chapter2/hello-controller-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter2/hello-controller-screenshot.png -------------------------------------------------------------------------------- /chapter2/hello-controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | {{ctrl.helloMsg}} AngularJS. 6 |
7 | {{ctrl.goodbyeMsg}} AngularJS 8 | 9 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter2/module-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello AngularJS 4 | 5 | Hello {{1 + 1}}nd time AngularJS 6 | 7 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter2/more-directives.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Notes App 5 | 13 | 14 | 15 | 16 | 17 |
19 | {{note.label}} 20 | 23 | 24 |
25 | 26 | 29 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /chapter2/ng-bind-once.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 | 7 |
8 | Without Bind Once:
9 | With Bind Once:
10 |
11 | 12 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /chapter2/ng-repeat-across-elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
{{note.label}}
Done: {{note.done}}
13 | 14 | 15 | 18 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /chapter2/ng-repeat-example-1-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter2/ng-repeat-example-1-screenshot.png -------------------------------------------------------------------------------- /chapter2/ng-repeat-example-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | {{note.label}} 8 | 9 |
10 | 11 | 14 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /chapter2/ng-repeat-helper-variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 |
First Element: {{$first}}
8 |
Middle Element: {{$middle}}
9 |
Last Element: {{$last}}
10 |
Index of Element: {{$index}}
11 |
At Even Position: {{$even}}
12 |
At Odd Position: {{$odd}}
13 | 14 | {{note.label}} 15 | 16 |

17 |
18 | 19 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /chapter2/ng-repeat-object.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | {{note.label}} 8 | 9 |
10 | 11 | 14 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter2/ng-repeat-track-by-id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | DOM Elements change every time someone clicks 8 |
9 | {{note.$$hashKey}} 10 | {{note.label}} 11 | 12 |
13 | 14 |
15 | DOM Elements are reused every time someone clicks 16 |
17 | {{note.$$hashKey}} 18 | {{note.label}} 19 | 20 |
21 | 22 | 25 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /chapter3/chapter-3-test-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter3/chapter-3-test-results.png -------------------------------------------------------------------------------- /chapter3/controller.js: -------------------------------------------------------------------------------- 1 | // File: chapter3/controller.js 2 | angular.module('notesApp', []) 3 | .controller('ListCtrl', [function() { 4 | 5 | var self = this; 6 | self.items = [ 7 | {id: 1, label: 'First', done: true}, 8 | {id: 2, label: 'Second', done: false} 9 | ]; 10 | 11 | self.getDoneClass = function(item) { 12 | return { 13 | finished: item.done, 14 | unfinished: !item.done 15 | }; 16 | }; 17 | }]); 18 | -------------------------------------------------------------------------------- /chapter3/controllerSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter3/controllerSpec.js 2 | describe('Controller: ListCtrl', function() { 3 | // Instantiate a new version of my module before each test 4 | beforeEach(module('notesApp')); 5 | 6 | var ctrl; 7 | 8 | // Before each unit test, instantiate a new instance 9 | // of the controller 10 | beforeEach(inject(function($controller) { 11 | ctrl = $controller('ListCtrl'); 12 | })); 13 | 14 | it('should have items available on load', function() { 15 | expect(ctrl.items).toEqual([ 16 | {id: 1, label: 'First', done: true}, 17 | {id: 2, label: 'Second', done: false} 18 | ]); 19 | }); 20 | 21 | it('should have highlight items based on state', function() { 22 | var item = {id: 1, label: 'First', done: true}; 23 | 24 | var actualClass = ctrl.getDoneClass(item); 25 | expect(actualClass.finished).toBeTruthy(); 26 | expect(actualClass.unfinished).toBeFalsy(); 27 | 28 | item.done = false; 29 | actualClass = ctrl.getDoneClass(item); 30 | expect(actualClass.finished).toBeFalsy(); 31 | expect(actualClass.unfinished).toBeTruthy(); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /chapter3/karma.conf.js: -------------------------------------------------------------------------------- 1 | // File: chapter3/karma.conf.js 2 | // Karma configuration 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'angular.min.js', 15 | 'angular-mocks.js', 16 | 'controller.js', 17 | 'simpleSpec.js', 18 | 'controllerSpec.js' 19 | ], 20 | 21 | // list of files / patterns to exclude 22 | exclude: [], 23 | 24 | // web server port 25 | port: 8080, 26 | 27 | // level of logging 28 | // possible values: LOG_DISABLE || LOG_ERROR || 29 | // LOG_WARN || LOG_INFO || LOG_DEBUG 30 | logLevel: config.LOG_INFO, 31 | 32 | 33 | // enable / disable watching file and executing tests 34 | // whenever any file changes 35 | autoWatch: true, 36 | 37 | // Start these browsers, currently available: 38 | // - Chrome 39 | // - ChromeCanary 40 | // - Firefox 41 | // - Opera 42 | // - Safari (only Mac) 43 | // - PhantomJS 44 | // - IE (only Windows) 45 | browsers: ['Chrome'], 46 | 47 | 48 | // Continuous Integration mode 49 | // if true, it capture browsers, run tests and exit 50 | singleRun: false 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /chapter3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajs-up-running-chapter-3", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "karma": "0.12.28", 6 | "karma-jasmine": "~0.2.0", 7 | "karma-chrome-launcher": "~0.1.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter3/simpleSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter3/simpleSpec.js 2 | // A Test Suite in Jasmine 3 | describe('My Function', function() { 4 | 5 | var t; 6 | // Similar to setup 7 | beforeEach(function() { 8 | t = true; 9 | }); 10 | 11 | afterEach(function() { 12 | t = null; 13 | }); 14 | 15 | it('should perform action 1', function() { 16 | expect(t).toBeTruthy(); 17 | }); 18 | it('should perform action 2', function() { 19 | var expectedValue = true; 20 | expect(t).toEqual(expectedValue); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /chapter4/checkbox-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 |
6 |

What are your favorite sports?

7 |
8 | 9 |
10 | With Binding: 11 | 15 |
16 |
17 | Using ng-checked: 18 | 20 |
21 |
22 | Current state: {{sport.selected}} 23 |
24 |
25 |
26 | 27 | 30 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /chapter4/form-error-messages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 12 | 13 | This is a required field 14 | 15 | 16 | Minimum length required is 4 17 | 18 | 19 | This field is invalid 20 | 21 | 25 | 26 | This is a required field 27 | 28 | 31 |
32 | 33 | 36 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /chapter4/form-styling.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Notes App 5 | 16 | 17 | 18 | 19 |
20 | 26 | 29 |
30 | 31 | 34 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /chapter4/form-validation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 11 | 14 | 17 |
18 | 19 | 22 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /chapter4/nested-forms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Notes App 5 | 6 | 7 | 8 |
9 |
10 | 17 | 23 |
24 | 25 | 26 | 31 | 35 | 40 | 44 | 45 | 46 | 47 | Please fill out the profile information 48 | 49 | 50 | 53 |
54 | 55 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /chapter4/ng-messages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 12 |
14 | 18 |
20 | 23 |
24 | 25 |
26 | 31 |
33 |
Please enter a username
34 |
35 | 39 |
40 |
Please enter a password
41 |
42 | 45 |
46 | 47 | 50 | 53 | 58 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /chapter4/ng-model-options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ng Model Options 4 | 5 | 6 |
7 | 10 | You typed {{ctrl.withoutModelOptions}} 11 |
12 |
13 | 17 | You typed {{ctrl.withModelOptions()}} 18 |
19 | 20 | 23 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /chapter4/select-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 10 | Selected Country ID : {{ctrl.selectedCountryId}} 11 |
12 | 13 |
14 | 17 | 18 | Selected Country : {{ctrl.selectedCountry}} 19 |
20 | 21 | 24 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter4/simple-form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 15 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /chapter4/simple-ng-model-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /chapter4/simple-ng-model.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 | 7 | You typed {{ctrl.username}} 8 | 9 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter4/two-forms-databinding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notes App 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | 21 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /chapter5/item-service-using-provider/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter5/item-service-using-provider/app.js 2 | 3 | function ItemService(opt_items) { 4 | var items = opt_items || []; 5 | 6 | this.list = function() { 7 | return items; 8 | }; 9 | this.add = function(item) { 10 | items.push(item); 11 | }; 12 | } 13 | 14 | angular.module('notesApp', []) 15 | .provider('ItemService', function() { 16 | var haveDefaultItems = true; 17 | 18 | this.disableDefaultItems = function() { 19 | haveDefaultItems = false; 20 | }; 21 | 22 | // This function gets our dependencies, not the 23 | // provider above 24 | this.$get = [function() { 25 | var optItems = []; 26 | if (haveDefaultItems) { 27 | optItems = [ 28 | {id: 1, label: 'Item 0'}, 29 | {id: 2, label: 'Item 1'} 30 | ]; 31 | } 32 | return new ItemService(optItems); 33 | 34 | }]; 35 | }) 36 | .config(['ItemServiceProvider', 37 | function(ItemServiceProvider) { 38 | // To see how the provider can change 39 | // configuration, change the value of 40 | // shouldHaveDefaults to true and try 41 | // running the example 42 | var shouldHaveDefaults = false; 43 | 44 | // Get configuration from server 45 | // Set shouldHaveDefaults somehow 46 | // Assume it magically changes for now 47 | if (!shouldHaveDefaults) { 48 | ItemServiceProvider.disableDefaultItems(); 49 | } 50 | }]) 51 | .controller('MainCtrl', [function() { 52 | var self = this; 53 | self.tab = 'first'; 54 | self.open = function(tab) { 55 | self.tab = tab; 56 | }; 57 | }]) 58 | .controller('SubCtrl', 59 | ['ItemService', function(ItemService) { 60 | var self = this; 61 | self.list = function() { 62 | return ItemService.list(); 63 | }; 64 | 65 | self.add = function() { 66 | ItemService.add({ 67 | id: self.list().length + 1, 68 | label: 'Item ' + self.list().length 69 | }); 70 | }; 71 | }]); 72 | -------------------------------------------------------------------------------- /chapter5/item-service-using-provider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hello Controllers!

5 | 6 | 7 |
8 | 9 |
10 |
11 |

First tab

12 |
    13 |
  • 14 | 15 |
  • 16 |
17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 |

Second tab

25 |
    26 |
  • 27 | 28 |
  • 29 |
30 | 31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter5/item-service-using-service/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter5/item-service-using-service/app.js 2 | 3 | function ItemService() { 4 | var items = [ 5 | {id: 1, label: 'Item 0'}, 6 | {id: 2, label: 'Item 1'} 7 | ]; 8 | this.list = function() { 9 | return items; 10 | }; 11 | this.add = function(item) { 12 | items.push(item); 13 | }; 14 | } 15 | 16 | angular.module('notesApp', []) 17 | .controller('MainCtrl', [function() { 18 | var self = this; 19 | self.tab = 'first'; 20 | self.open = function(tab) { 21 | self.tab = tab; 22 | }; 23 | }]) 24 | .controller('SubCtrl', 25 | ['ItemService', function(ItemService) { 26 | var self = this; 27 | self.list = function() { 28 | return ItemService.list(); 29 | }; 30 | 31 | self.add = function() { 32 | ItemService.add({ 33 | id: self.list().length + 1, 34 | label: 'Item ' + self.list().length 35 | }); 36 | }; 37 | }]) 38 | .service('ItemService', [ItemService]); 39 | -------------------------------------------------------------------------------- /chapter5/item-service-using-service/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hello Controllers!

5 | 6 | 7 |
8 | 9 |
10 |
11 |

First tab

12 |
    13 |
  • 14 | 15 |
  • 16 |
17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 |

Second tab

25 |
    26 |
  • 27 | 28 |
  • 29 |
30 | 31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter5/log-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hello Services!

5 | 6 | 7 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter5/need-for-service/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter5/need-for-service/app.js 2 | angular.module('notesApp', []) 3 | .controller('MainCtrl', [function() { 4 | var self = this; 5 | self.tab = 'first'; 6 | self.open = function(tab) { 7 | self.tab = tab; 8 | }; 9 | }]) 10 | .controller('SubCtrl', [function() { 11 | var self = this; 12 | self.list = [ 13 | {id: 1, label: 'Item 0'}, 14 | {id: 2, label: 'Item 1'} 15 | ]; 16 | 17 | self.add = function() { 18 | self.list.push({ 19 | id: self.list.length + 1, 20 | label: 'Item ' + self.list.length 21 | }); 22 | }; 23 | }]); 24 | -------------------------------------------------------------------------------- /chapter5/need-for-service/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 |

Hello Controllers!

13 | 14 | 15 |
16 | 17 |
18 |
19 |

First tab

20 |
    21 |
  • 22 | 23 |
  • 24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 |

Second tab

33 |
    34 |
  • 35 | 36 |
  • 37 |
38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /chapter5/simple-angularjs-service/app.js: -------------------------------------------------------------------------------- 1 | // File: chapter5/simple-angularjs-service/app.js 2 | 3 | angular.module('notesApp', []) 4 | .controller('MainCtrl', [function() { 5 | var self = this; 6 | self.tab = 'first'; 7 | self.open = function(tab) { 8 | self.tab = tab; 9 | }; 10 | }]) 11 | .controller('SubCtrl', ['ItemService', 12 | function(ItemService) { 13 | var self = this; 14 | self.list = function() { 15 | return ItemService.list(); 16 | }; 17 | 18 | self.add = function() { 19 | ItemService.add({ 20 | id: self.list().length + 1, 21 | label: 'Item ' + self.list().length 22 | }); 23 | }; 24 | }]) 25 | .factory('ItemService', [function() { 26 | var items = [ 27 | {id: 1, label: 'Item 0'}, 28 | {id: 2, label: 'Item 1'} 29 | ]; 30 | return { 31 | list: function() { 32 | return items; 33 | }, 34 | add: function(item) { 35 | items.push(item); 36 | } 37 | }; 38 | }]); 39 | -------------------------------------------------------------------------------- /chapter5/simple-angularjs-service/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hello Controllers!

5 | 8 | 11 |
12 |
13 |
14 |

First tab

15 |
    16 |
  • 17 | 18 |
  • 19 |
20 | 21 | 24 |
25 | 26 |
27 |
28 |
29 |

Second tab

30 |
    31 |
  • 32 | 33 |
  • 34 |
35 | 36 | 39 |
40 |
41 |
42 | 43 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /chapter6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-chapter6-server", 3 | "main": "server.js", 4 | "dependencies": { 5 | "express": "~4.0.0", 6 | "morgan": "~1.0.0", 7 | "body-parser": "~1.0.0", 8 | "method-override": "~1.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter6/promise-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter6/promise-chain.png -------------------------------------------------------------------------------- /chapter6/public/http-defaults.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTTP Default Example 5 | 6 | 7 | 8 |

Hello HTTP Defaults!

9 |
10 |
11 |
13 | 17 | 21 | 24 |
25 |
26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /chapter6/public/http-defaults.js: -------------------------------------------------------------------------------- 1 | // File: chapter6/public/http-defaults.js 2 | 3 | angular.module('notesApp', []) 4 | .controller('LoginCtrl', ['$http', function($http) { 5 | var self = this; 6 | self.user = {}; 7 | self.message = 'Please login'; 8 | self.login = function() { 9 | $http.post('/api/login', self.user).then( 10 | function(resp) { 11 | self.message = resp.data.msg; 12 | }); 13 | }; 14 | }]) 15 | .config(['$httpProvider', function($httpProvider) { 16 | // Every POST data becoms jQuery style 17 | $httpProvider.defaults.transformRequest.push( 18 | function(data) { 19 | var requestStr; 20 | if (data) { 21 | data = JSON.parse(data); 22 | for (var key in data) { 23 | if (requestStr) { 24 | requestStr += '&' + key + '=' + data[key]; 25 | } else { 26 | requestStr = key + '=' + data[key]; 27 | } 28 | } 29 | } 30 | 31 | return requestStr; 32 | }); 33 | // Set the content type to be FORM type for all post requests 34 | // This does not add it for GET requests. 35 | $httpProvider.defaults.headers.post['Content-Type'] = 36 | 'application/x-www-form-urlencoded'; 37 | }]); 38 | -------------------------------------------------------------------------------- /chapter6/public/http-get-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $http get example 6 | 11 | 12 | 13 | 14 |

Hello Servers!

15 |
16 |
17 |
- by
18 |
19 | 20 | 23 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /chapter6/public/http-post-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTTP Post Example 6 | 11 | 12 | 13 | 14 |

Hello Servers!

15 |
17 |
18 |
- by
19 |
20 | 21 |
22 |
24 | 28 | 32 | 35 |
36 |
37 | 38 | 41 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /chapter6/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Server Application Links 6 | 7 | 8 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /chapter6/public/logging-interceptor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTTP Interceptor Example 6 | 11 | 12 | 13 | 14 |

Hello Interceptors!

15 |
16 |
17 |
- by
18 |
19 | 20 |
21 |
22 | 26 | 30 | 33 |
34 |
35 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /chapter6/public/logging-interceptor.js: -------------------------------------------------------------------------------- 1 | // File: chapter6/public/logging-interceptor.js 2 | angular.module('notesApp', []) 3 | .controller('MainCtrl', ['$http', function($http) { 4 | var self = this; 5 | self.items = []; 6 | self.newTodo = {}; 7 | var fetchTodos = function() { 8 | return $http.get('/api/note').then(function(response) { 9 | self.items = response.data; 10 | }, function(errResponse) { 11 | console.error('Error while fetching notes'); 12 | }); 13 | }; 14 | 15 | fetchTodos(); 16 | 17 | self.add = function() { 18 | $http.post('/api/note', self.newTodo) 19 | .then(fetchTodos) 20 | .then(function(response) { 21 | self.newTodo = {}; 22 | }); 23 | }; 24 | 25 | }]).factory('MyLoggingInterceptor', ['$q', function($q) { 26 | return { 27 | request: function(config) { 28 | console.log('Request made with ', config); 29 | return config; 30 | // If an error, or not allowed, or my custom condition 31 | // return $q.reject('Not allowed'); 32 | }, 33 | requestError: function(rejection) { 34 | console.log('Request error due to ', rejection); 35 | // Continue to ensure that the next promise chain 36 | // sees an error 37 | return $q.reject(rejection); 38 | // Or handled successfully? 39 | // return someValue; 40 | }, 41 | response: function(response) { 42 | console.log('Response from server', response); 43 | // Return a promise 44 | return response || $q.when(response); 45 | }, 46 | responseError: function(rejection) { 47 | console.log('Error in response ', rejection); 48 | // Continue to ensure that the next promise chain 49 | // sees an error 50 | // Can check auth status code here if need to 51 | // if (rejection.status === 403) { 52 | // Show a login dialog 53 | // return a value to tell controllers it has 54 | // been handled 55 | // } 56 | // Or return a rejection to continue the 57 | // promise failure chain 58 | return $q.reject(rejection); 59 | } 60 | }; 61 | }]) 62 | .config(['$httpProvider', function($httpProvider) { 63 | $httpProvider.interceptors.push('MyLoggingInterceptor'); 64 | }]); 65 | -------------------------------------------------------------------------------- /chapter6/public/ng-resource-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ngResource Example 6 | 11 | 12 | 13 | 14 |

Hello Servers!

15 |
17 |
18 |
- by
19 |
20 |
21 | 22 |
23 |
25 | 29 | 33 | 36 |
37 |
38 | 39 | 42 | 45 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /chapter6/server.js: -------------------------------------------------------------------------------- 1 | // server.js (Express 4.0) 2 | var express = require('express'); 3 | var morgan = require('morgan'); 4 | var bodyParser = require('body-parser'); 5 | var methodOverride = require('method-override'); 6 | var app = express(); 7 | 8 | app.use(express.static(__dirname + '/public')); // set the static files location /public/img will be /img for users 9 | app.use(morgan('dev')); // log every request to the console 10 | app.use(bodyParser()); // pull information from html in POST 11 | app.use(methodOverride()); // simulate DELETE and PUT 12 | 13 | 14 | var router = express.Router(); 15 | 16 | var notes = [ 17 | {id: 1, label: 'First Note', author: 'Shyam'}, 18 | {id: 2, label: 'Second Note', author: 'Brad'}, 19 | {id: 3, label: 'Middle Note', author: 'Someone'}, 20 | {id: 4, label: 'Last Note', author: 'Shyam'}, 21 | {id: 5, label: 'Really the last Note', author: 'Shyam'} 22 | 23 | ]; 24 | var lastId = 6; 25 | 26 | router.get('/note', function(req, res) { 27 | res.send(notes); 28 | }); 29 | router.post('/note', function(req, res) { 30 | var note = req.body; 31 | note.id = lastId; 32 | lastId++; 33 | notes.push(note); 34 | res.send(note); 35 | }); 36 | 37 | 38 | router.post('/note/:id/done', function(req, res) { 39 | var noteId = req.params.id; 40 | var note = null; 41 | for (var i = 0; i < notes.length; i++) { 42 | if (notes[i].id == req.params.id) { 43 | note = notes[i]; 44 | break; 45 | } 46 | } 47 | note.label = 'Done - ' + note.label; 48 | res.send(notes); 49 | }); 50 | 51 | router.get('/note/:id', function(req, res) { 52 | for (var i = 0; i < notes.length; i++) { 53 | if (notes[i].id == req.params.id) { 54 | res.send(notes[i]); 55 | break; 56 | } 57 | } 58 | res.send({msg: 'Note not found'}, 404); 59 | }); 60 | router.post('/note/:id', function(req, res) { 61 | for (var i = 0; i < notes.length; i++) { 62 | if (notes[i].id == req.params.id) { 63 | notes[i] = req.body; 64 | notes[i].id = req.params.id; 65 | res.send(notes[i]); 66 | break; 67 | } 68 | } 69 | res.send({msg: 'Note not found'}, 404); 70 | }); 71 | 72 | router.post('/login', function(req, res) { 73 | console.log('API LOGIN FOR ', req.body); 74 | res.send({msg: 'Login successful for ' + req.body.username}); 75 | }); 76 | 77 | 78 | app.use('/api', router); 79 | 80 | 81 | 82 | app.listen(8000); 83 | console.log('Open http://localhost:8000 to access the files now'); // shoutout to the user 84 | -------------------------------------------------------------------------------- /chapter7/karma.conf.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/karma.conf.js 2 | // Karma configuration 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine'], 8 | files: [ 9 | 'angular.min.js', 10 | 'angular-mocks.js', 11 | '*.js' 12 | ], 13 | exclude: [], 14 | port: 8080, 15 | logLevel: config.LOG_INFO, 16 | autoWatch: true, 17 | browsers: ['Chrome'], 18 | singleRun: false 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /chapter7/notesApp1-mocks.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1-mocks.js 2 | 3 | angular.module('notesApp1Mocks', []) 4 | .factory('ItemService', [function() { 5 | return { 6 | list: function() { 7 | return [{id: 1, label: 'Mock'}]; 8 | } 9 | }; 10 | }]); 11 | -------------------------------------------------------------------------------- /chapter7/notesApp1.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1.js 2 | angular.module('notesApp1', []) 3 | .factory('ItemService', [function() { 4 | var items = [ 5 | {id: 1, label: 'Item 0'}, 6 | {id: 2, label: 'Item 1'} 7 | ]; 8 | return { 9 | list: function() { 10 | return items; 11 | }, 12 | add: function(item) { 13 | items.push(item); 14 | } 15 | }; 16 | }]) 17 | .controller('ItemCtrl', ['ItemService', function(ItemService) { 18 | var self = this; 19 | self.items = ItemService.list(); 20 | }]); 21 | -------------------------------------------------------------------------------- /chapter7/notesApp1ServiceSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1ServiceSpec.js 2 | 3 | describe('ItemCtrl with inline mock', function() { 4 | beforeEach(module('notesApp1')); 5 | 6 | var service; 7 | 8 | beforeEach(inject(function(ItemService) { 9 | service = ItemService; 10 | })); 11 | 12 | it('should return default items', function() { 13 | expect(service.list()).toEqual([ 14 | {id: 1, label: 'Item 0'}, 15 | {id: 2, label: 'Item 1'} 16 | ]); 17 | }); 18 | 19 | it('should add items', function() { 20 | var newItem = {id: 3, label: 'New Item'}; 21 | service.add(newItem); 22 | expect(service.list()).toEqual([ 23 | {id: 1, label: 'Item 0'}, 24 | {id: 2, label: 'Item 1'}, 25 | newItem 26 | ]); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /chapter7/notesApp1Spec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1Spec.js 2 | 3 | describe('ItemCtrl with inline mock', function() { 4 | beforeEach(module('notesApp1')); 5 | 6 | var ctrl, mockService; 7 | 8 | beforeEach(module(function($provide) { 9 | mockService = { 10 | list: function() { 11 | return [{id: 1, label: 'Mock'}]; 12 | } 13 | }; 14 | 15 | $provide.value('ItemService', mockService); 16 | })); 17 | 18 | beforeEach(inject(function($controller) { 19 | ctrl = $controller('ItemCtrl'); 20 | })); 21 | 22 | it('should load mocked out items', function() { 23 | expect(ctrl.items).toEqual([{id: 1, label: 'Mock'}]); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /chapter7/notesApp1SpecWithMock.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1SpecWithMock.js 2 | 3 | describe('ItemCtrl With global mock', function() { 4 | 5 | var ctrl; 6 | beforeEach(module('notesApp1')); 7 | beforeEach(module('notesApp1Mocks')); 8 | 9 | beforeEach(inject(function($controller) { 10 | ctrl = $controller('ItemCtrl'); 11 | })); 12 | 13 | it('should load mocked out items', function() { 14 | expect(ctrl.items).toEqual([{id: 1, label: 'Mock'}]); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /chapter7/notesApp1SpecWithSpies.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1SpecWithSpies.js 2 | 3 | describe('ItemCtrl with spies', function() { 4 | beforeEach(module('notesApp1')); 5 | 6 | var ctrl, itemService; 7 | 8 | beforeEach(inject(function($controller, ItemService) { 9 | spyOn(ItemService, 'list').and.callThrough(); 10 | itemService = ItemService; 11 | ctrl = $controller('ItemCtrl'); 12 | })); 13 | 14 | it('should load mocked out items', function() { 15 | expect(itemService.list).toHaveBeenCalled(); 16 | expect(itemService.list.calls.count()).toEqual(1); 17 | expect(ctrl.items).toEqual([ 18 | {id: 1, label: 'Item 0'}, 19 | {id: 2, label: 'Item 1'} 20 | ]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /chapter7/notesApp1SpecWithSpyReturn.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/notesApp1SpecWithSpyReturn.js 2 | 3 | describe('ItemCtrl with SpyReturn', function() { 4 | beforeEach(module('notesApp1')); 5 | 6 | var ctrl, itemService; 7 | 8 | beforeEach(inject(function($controller, ItemService) { 9 | 10 | spyOn(ItemService, 'list') 11 | .and.returnValue([{id: 1, label: 'Mock'}]); 12 | itemService = ItemService; 13 | ctrl = $controller('ItemCtrl'); 14 | })); 15 | 16 | it('should load mocked out items', function() { 17 | expect(itemService.list).toHaveBeenCalled(); 18 | expect(itemService.list.calls.count()).toEqual(1); 19 | expect(ctrl.items).toEqual([{id: 1, label: 'Mock'}]); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /chapter7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajs-up-running-chapter-7", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "karma": "0.12.28", 6 | "karma-jasmine": "~0.2.0", 7 | "karma-chrome-launcher": "~0.1.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter7/serverApp.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/serverApp.js 2 | 3 | angular.module('serverApp', []) 4 | .controller('MainCtrl', ['$http', function($http) { 5 | var self = this; 6 | self.items = []; 7 | self.errorMessage = ''; 8 | 9 | $http.get('/api/note').then(function(response) { 10 | self.items = response.data; 11 | }, function(errResponse) { 12 | self.errorMessage = errResponse.data.msg; 13 | }); 14 | }]); 15 | -------------------------------------------------------------------------------- /chapter7/serverAppSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/serverAppSpec.js 2 | 3 | describe('MainCtrl Server Calls', function() { 4 | beforeEach(module('serverApp')); 5 | 6 | var ctrl, mockBackend; 7 | 8 | beforeEach(inject(function($controller, $httpBackend) { 9 | 10 | mockBackend = $httpBackend; 11 | mockBackend.expectGET('/api/note') 12 | .respond([{id: 1, label: 'Mock'}]); 13 | ctrl = $controller('MainCtrl'); 14 | // At this point, a server request will have been made 15 | })); 16 | 17 | it('should load items from server', function() { 18 | // Initially, before the server responds, 19 | // the items should be empty 20 | expect(ctrl.items).toEqual([]); 21 | 22 | // Simulate a server response 23 | mockBackend.flush(); 24 | 25 | expect(ctrl.items).toEqual([{id: 1, label: 'Mock'}]); 26 | }); 27 | 28 | afterEach(function() { 29 | // Ensure that all expects set on the $httpBackend 30 | // were actually called 31 | mockBackend.verifyNoOutstandingExpectation(); 32 | 33 | // Ensure that all requests to the server 34 | // have actually responded (using flush()) 35 | mockBackend.verifyNoOutstandingRequest(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /chapter7/serverAppWithService.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/serverAppWithService.js 2 | 3 | angular.module('serverApp2', []) 4 | .controller('MainCtrl', ['NoteService', function(NoteService) { 5 | var self = this; 6 | self.items = []; 7 | self.errorMessage = ''; 8 | 9 | NoteService.query().then(function(response) { 10 | self.items = response.data; 11 | }, function(errResponse) { 12 | self.errorMessage = errResponse.data.msg; 13 | }); 14 | }]) 15 | .factory('NoteService', ['$http', function($http) { 16 | return { 17 | query: function() { 18 | return $http.get('/api/note'); 19 | } 20 | }; 21 | }]); 22 | -------------------------------------------------------------------------------- /chapter7/serverAppWithServiceSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/serverAppWithServiceSpec.js 2 | describe('Server App Integration', function() { 3 | beforeEach(module('serverApp2')); 4 | 5 | var ctrl, mockBackend; 6 | 7 | beforeEach(inject(function($controller, $httpBackend) { 8 | 9 | mockBackend = $httpBackend; 10 | mockBackend.expectGET('/api/note') 11 | .respond(404, {msg: 'Not Found'}); 12 | ctrl = $controller('MainCtrl'); 13 | // At this point, a server request will have been made 14 | })); 15 | 16 | it('should handle error while loading items', function() { 17 | // Initially, before the server responds, 18 | // the items should be empty 19 | expect(ctrl.items).toEqual([]); 20 | 21 | // Simulate a server response 22 | mockBackend.flush(); 23 | 24 | // No items from server, only an error 25 | // So items should be empty still 26 | expect(ctrl.items).toEqual([]); 27 | // and check the error message 28 | expect(ctrl.errorMessage).toEqual('Not Found'); 29 | }); 30 | 31 | afterEach(function() { 32 | // Ensure that all expects set on the $httpBackend 33 | // were actually called 34 | mockBackend.verifyNoOutstandingExpectation(); 35 | 36 | // Ensure that all requests to the server 37 | // have actually responded (using flush()) 38 | mockBackend.verifyNoOutstandingRequest(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /chapter7/simpleCtrl1.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/simpleCtrl1.js 2 | 3 | angular.module('simpleCtrl1App', []) 4 | .controller('SimpleCtrl', ['$location', function($location) { 5 | var self = this; 6 | self.navigate = function() { 7 | $location.path('/some/where/else'); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /chapter7/simpleCtrl1Spec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/simpleCtrl1Spec.js 2 | 3 | describe('SimpleCtrl', function() { 4 | beforeEach(module('simpleCtrl1App')); 5 | 6 | var ctrl, $loc; 7 | beforeEach(inject(function($controller, $location) { 8 | ctrl = $controller('SimpleCtrl'); 9 | $loc = $location; 10 | })); 11 | 12 | it('should navigate away from the current page', function() { 13 | $loc.path('/here'); 14 | ctrl.navigate(); 15 | expect($loc.path()).toEqual('/some/where/else'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /chapter7/simpleCtrl2.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/simpleCtrl2.js 2 | 3 | angular.module('simpleCtrl2App', []) 4 | .controller('SimpleCtrl2', ['$location', '$window', 5 | function($location, $window) { 6 | var self = this; 7 | self.navigate1 = function() { 8 | $location.path('/some/where'); 9 | }; 10 | self.navigate2 = function() { 11 | $location.path('/some/where/else'); 12 | }; 13 | }]); 14 | -------------------------------------------------------------------------------- /chapter7/simpleCtrl2Spec.js: -------------------------------------------------------------------------------- 1 | // File: chapter7/simpleCtrl2Spec.js 2 | 3 | describe('SimpleCtrl2', function() { 4 | beforeEach(module('simpleCtrl2App')); 5 | 6 | var ctrl, $loc; 7 | beforeEach(inject(function($controller, $location) { 8 | ctrl = $controller('SimpleCtrl2'); 9 | $loc = $location; 10 | })); 11 | 12 | it('should navigate away from the current page', function() { 13 | expect($loc.path()).toEqual(''); 14 | $loc.path('/here'); 15 | ctrl.navigate1(); 16 | expect($loc.path()).toEqual('/some/where'); 17 | }); 18 | 19 | it('should navigate away from the current page', function() { 20 | expect($loc.path()).toEqual(''); 21 | $loc.path('/there'); 22 | ctrl.navigate2(); 23 | expect($loc.path()).toEqual('/some/where/else'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /chapter8/custom-filters.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom Filters in Action 5 | 6 | 7 | 8 |
9 |
10 | Start Time (Timestamp): {{ctrl.startTime}} 11 |
12 |
13 | Start Time (DateTime): {{ctrl.startTime | date:'medium'}} 14 |
15 |
16 | Start Time (Our filter): {{ctrl.startTime | timeAgo}} 17 |
18 | 19 |
20 | someTimeAgo : {{ctrl.someTimeAgo | date:'short'}} 21 |
22 |
23 | someTimeAgo (Our filter): {{ctrl.someTimeAgo | timeAgo}} 24 |
25 |
26 | 27 | 30 | 31 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /chapter8/filter-arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Filters in Action 5 | 6 | 7 | 8 |
9 | 10 | 13 | 16 | 19 | Filter Text 20 | 22 | Show Done Only 23 | 25 |
    26 |
  • 30 | {{note.label}} - {{note.type}} - {{note.done}} 31 |
  • 32 |
33 |
34 | 35 | 38 | 39 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /chapter8/filter-example-1-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter8/filter-example-1-screenshot.png -------------------------------------------------------------------------------- /chapter8/filter-example-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Filters in Action 5 | 6 | 7 | 8 |
9 |
10 | Amount as a number: {{ctrl.amount | number}} 11 |
12 |
13 | Total Cost as a currency: {{ctrl.totalCost | currency}} 14 |
15 |
16 | 17 | Total Cost in INR: {{ctrl.totalCost | currency:'£ '}} 18 |
19 |
20 | Shouting the name: {{ctrl.name | uppercase}} 21 |
22 |
23 | Whispering the name: {{ctrl.name | lowercase}} 24 |
25 |
26 | Start Time: {{ctrl.startTime | date:'medium'}} 27 |
28 |
29 | 30 | 33 | 34 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /chapter8/filter-number-string-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyamseshadri/angularjs-up-and-running/9883211d97df2f089e43bf08c8eec676834a1a65/chapter8/filter-number-string-screenshot.png -------------------------------------------------------------------------------- /chapter8/filter-number-string.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Filters in Action 5 | 6 | 7 | 8 |
    9 |
  • 10 | Amount - {{ctrl.amount}} 11 |
  • 12 |
  • 13 | Amount - Default Currency: {{ctrl.amount | currency}} 14 |
  • 15 |
  • 16 | Amount - INR Currency: {{ctrl.amount | currency:'INR'}} 17 |
  • 18 | 19 |
  • 20 | Amount - Number: {{ctrl.amount | number}} 21 |
  • 22 |
  • 23 | Amount - No. with 4 decimals: {{ctrl.amount | number:4}} 24 |
  • 25 | 26 |
  • 27 | Name with no filters: {{ctrl.name}} 28 |
  • 29 |
  • 30 | Name - lowercase filter: {{ctrl.name | lowercase}} 31 |
  • 32 |
  • 33 | Name - uppercase filter: {{ctrl.name | uppercase}} 34 |
  • 35 | 36 |
  • 37 | The JSON Filter: {{ctrl.obj | json}} 38 |
  • 39 | 40 |
  • 41 | Timestamp: {{ctrl.startTime}} 42 |
  • 43 |
  • 44 | Default Date filter: {{ctrl.startTime | date}} 45 |
  • 46 |
  • 47 | Medium Date filter: {{ctrl.startTime | date:'medium'}} 48 |
  • 49 |
  • 50 | Custom Date filter: {{ctrl.startTime | date:'M/dd, yyyy'}} 51 |
  • 52 |
53 | 54 | 57 | 58 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /chapter9/karma.conf.js: -------------------------------------------------------------------------------- 1 | // File: chapter9/karma.conf.js 2 | // Karma configuration 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine'], 8 | files: [ 9 | 'angular.min.js', 10 | 'angular-mocks.js', 11 | '*.js' 12 | ], 13 | exclude: [], 14 | port: 8080, 15 | logLevel: config.LOG_INFO, 16 | autoWatch: true, 17 | browsers: ['Chrome'], 18 | singleRun: false 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /chapter9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajs-up-running-chapter-9", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "karma": "0.12.28", 6 | "karma-jasmine": "~0.2.0", 7 | "karma-chrome-launcher": "~0.1.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter9/timeAgoFilter.js: -------------------------------------------------------------------------------- 1 | // File: chapter9/timeAgoFilter.js 2 | angular.module('filtersApp', []) 3 | .filter('timeAgo', [function() { 4 | var ONE_MINUTE = 1000 * 60; 5 | var ONE_HOUR = ONE_MINUTE * 60; 6 | var ONE_DAY = ONE_HOUR * 24; 7 | var ONE_MONTH = ONE_DAY * 30; 8 | 9 | return function(ts, optShowSecondsMessage) { 10 | if (optShowSecondsMessage !== false) { 11 | optShowSecondsMessage = true; 12 | } 13 | 14 | var currentTime = new Date().getTime(); 15 | var diff = currentTime - ts; 16 | if (diff < ONE_MINUTE && optShowSecondsMessage) { 17 | return 'seconds ago'; 18 | } else if (diff < ONE_HOUR) { 19 | return 'minutes ago'; 20 | } else if (diff < ONE_DAY) { 21 | return 'hours ago'; 22 | } else if (diff < ONE_MONTH) { 23 | return 'days ago'; 24 | } else { 25 | return 'months ago'; 26 | } 27 | }; 28 | }]); 29 | -------------------------------------------------------------------------------- /chapter9/timeAgoFilterOptionalArgumentSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter9/timeAgoFilterOptionalArgumentSpec.js 2 | describe('timeAgo Filter', function() { 3 | beforeEach(module('filtersApp')); 4 | 5 | var filter; 6 | beforeEach(inject(function(timeAgoFilter) { 7 | filter = timeAgoFilter; 8 | })); 9 | 10 | it('should respond based on timestamp', function() { 11 | // The presence of new Date().getTime() makes it slightly 12 | // hard to unit test deterministicly. 13 | // Ideally, we would inject a dateProvider into the timeAgo 14 | // filter, but we are trying to keep it simple here 15 | // So going to assume that our tests are fast enough to 16 | // execute in mere milliseconds 17 | 18 | var currentTime = new Date().getTime(); 19 | currentTime -= 10000; 20 | expect(filter(currentTime, false)).toEqual('minutes ago'); 21 | var fewMinutesAgo = currentTime - 1000 * 60; 22 | expect(filter(fewMinutesAgo, false)).toEqual('minutes ago'); 23 | var fewHoursAgo = currentTime - 1000 * 60 * 68; 24 | expect(filter(fewHoursAgo, false)).toEqual('hours ago'); 25 | var fewDaysAgo = currentTime - 1000 * 60 * 60 * 26; 26 | expect(filter(fewDaysAgo, false)).toEqual('days ago'); 27 | var fewMonthsAgo = currentTime - 1000 * 60 * 60 * 24 * 32; 28 | expect(filter(fewMonthsAgo, false)).toEqual('months ago'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /chapter9/timeAgoFilterSpec.js: -------------------------------------------------------------------------------- 1 | // File: chapter9/timeAgoFilterSpec.js 2 | describe('timeAgo Filter', function() { 3 | beforeEach(module('filtersApp')); 4 | 5 | var filter; 6 | beforeEach(inject(function(timeAgoFilter) { 7 | filter = timeAgoFilter; 8 | })); 9 | 10 | it('should respond based on timestamp', function() { 11 | // The presence of new Date().getTime() makes it slightly 12 | // hard to unit test deterministicly. 13 | // Ideally, we would inject a dateProvider into the timeAgo 14 | // filter, but we are trying to keep it simple here 15 | // So going to assume that our tests are fast enough to 16 | // execute in mere milliseconds 17 | 18 | var currentTime = new Date().getTime(); 19 | currentTime -= 10000; 20 | expect(filter(currentTime)).toEqual('seconds ago'); 21 | var fewMinutesAgo = currentTime - 1000 * 60; 22 | expect(filter(fewMinutesAgo)).toEqual('minutes ago'); 23 | var fewHoursAgo = currentTime - 1000 * 60 * 68; 24 | expect(filter(fewHoursAgo)).toEqual('hours ago'); 25 | var fewDaysAgo = currentTime - 1000 * 60 * 60 * 26; 26 | expect(filter(fewDaysAgo)).toEqual('days ago'); 27 | var fewMonthsAgo = currentTime - 1000 * 60 * 60 * 24 * 32; 28 | expect(filter(fewMonthsAgo)).toEqual('months ago'); 29 | }); 30 | }); 31 | --------------------------------------------------------------------------------