p&&(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
2 |
3 |
4 | Stock Market App
5 |
6 |
7 |
8 |
9 |
List of Stocks
10 |
11 |
12 | Recommended Stock : {{s.name}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/chapter13/directive-transclusion/stock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Price:
6 |
8 |
9 | Percentage Change:
10 |
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 |
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 |
4 |
![Image Courtesy: Wikipedia]()
6 |
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 |
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 | {{note.label}} |
8 |
9 |
10 | Done: {{note.done}} |
11 |
12 |
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 |
32 |
33 |
36 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/chapter4/form-styling.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Notes App
5 |
16 |
17 |
18 |
19 |
30 |
31 |
34 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/chapter4/form-validation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Notes App
4 |
5 |
6 |
18 |
19 |
22 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/chapter4/nested-forms.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Notes App
5 |
6 |
7 |
8 |
54 |
55 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/chapter4/ng-messages.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Notes App
4 |
5 |
6 |
24 |
25 |
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 |
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 |
11 |
12 |
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 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
Second tab
25 |
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 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
Second tab
25 |
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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Second tab
33 |
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 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
Second tab
30 |
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 |
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 |
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 |
20 |
21 |
22 |
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 |
19 |
20 |
21 |
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 |
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 |
--------------------------------------------------------------------------------