├── src ├── images │ ├── facebook.png │ ├── favicon.png │ ├── linkedin.png │ ├── twitter.png │ └── google-plus.png ├── framework │ ├── logger │ │ ├── logger.module.js │ │ └── logger.js │ └── exception │ │ ├── exception.module.js │ │ ├── exception.js │ │ └── exception-handler.provider.js ├── components │ ├── approot │ │ ├── approot.html │ │ ├── approot.module.js │ │ └── approot.directive.js │ ├── topnav │ │ ├── topnav.module.js │ │ ├── topnav.directive.js │ │ └── topnav.html │ ├── barchart │ │ ├── barchart.module.js │ │ ├── _barchart.scss │ │ ├── barchart.directive.js │ │ └── barchart.factory.js │ ├── profile │ │ ├── profile.module.js │ │ ├── profile.directive.js │ │ └── profile.html │ └── dashboard │ │ ├── dashboard.module.js │ │ ├── _dashboard.scss │ │ ├── dashboard.directive.js │ │ └── dashboard.html ├── app.scss ├── core │ ├── core.constants.js │ ├── core.module.js │ ├── styles │ │ ├── _core.scss │ │ └── _variables.scss │ ├── filters │ │ └── percentage.filter.js │ ├── core.router.js │ ├── services │ │ └── account.service.js │ └── core.config.js ├── app.module.js └── index.html ├── gulp-tasks ├── help.js ├── inject.js ├── styles.js ├── assets.js ├── vet.js ├── bump.js ├── plato.js ├── clean.js ├── template-cache.js ├── test.js ├── optimize.js └── serve.js ├── .editorconfig ├── .travis.yml ├── mock-server ├── routes │ ├── index.js │ └── utils │ │ ├── jsonfileservice.js │ │ └── errorHandler.js ├── app.js └── data │ └── account-summary.json ├── bower.json ├── test ├── helpers │ ├── mock-data.js │ └── bind-polyfill.js ├── server-integration │ └── account.service.spec.js ├── core │ └── services │ │ └── account.service.spec.js ├── components │ └── dashboard │ │ └── dashboard.controller.spec.js └── framework │ └── exception │ └── exception-handler.provider.spec.js ├── .gitignore ├── gulpfile.js ├── gulp.config.js ├── .jscsrc ├── package.json ├── karma.conf.js ├── .jshintrc └── README.md /src/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archfirst/angular-patterns/HEAD/src/images/facebook.png -------------------------------------------------------------------------------- /src/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archfirst/angular-patterns/HEAD/src/images/favicon.png -------------------------------------------------------------------------------- /src/images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archfirst/angular-patterns/HEAD/src/images/linkedin.png -------------------------------------------------------------------------------- /src/images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archfirst/angular-patterns/HEAD/src/images/twitter.png -------------------------------------------------------------------------------- /src/images/google-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archfirst/angular-patterns/HEAD/src/images/google-plus.png -------------------------------------------------------------------------------- /src/framework/logger/logger.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('fw.logger', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/framework/exception/exception.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('fw.exception', ['fw.logger']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/components/approot/approot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | -------------------------------------------------------------------------------- /gulp-tasks/help.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | gulp.task('help', config.$.taskListing); 6 | 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /src/components/topnav/topnav.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.topnav', [ 5 | 'app.core' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/components/approot/approot.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.approot', [ 5 | 'app.core' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/components/barchart/barchart.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.barchart', [ 5 | 'app.core' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/components/profile/profile.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.profile', [ 5 | 'app.core' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/components/dashboard/dashboard.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.dashboard', [ 5 | 'app.core', 6 | 'app.barchart' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | @import "core/styles/variables"; 2 | 3 | @import "../bower_components/bootstrap-sass/assets/stylesheets/bootstrap"; 4 | 5 | @import "core/styles/core"; 6 | 7 | @import "components/barchart/barchart"; 8 | @import "components/dashboard/dashboard"; 9 | 10 | -------------------------------------------------------------------------------- /src/core/core.constants.js: -------------------------------------------------------------------------------- 1 | /* global _, d3 */ 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('app.core') 8 | .constant('_', _) 9 | .constant('d3', d3) 10 | .constant('api', 'http://localhost:7203/api'); 11 | })(); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/app.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | // Common (everybody has access to these) 6 | 'app.core', 7 | 8 | // Features (listed alphabetically) 9 | 'app.approot', 10 | 'app.dashboard', 11 | 'app.profile', 12 | 'app.topnav' 13 | ]); 14 | })(); 15 | -------------------------------------------------------------------------------- /src/components/barchart/_barchart.scss: -------------------------------------------------------------------------------- 1 | /* ----- BarChart ----- */ 2 | 3 | ptrn-barchart { 4 | display: block; 5 | } 6 | 7 | .bar rect { 8 | fill: #F89F1B; 9 | stroke: white; 10 | } 11 | 12 | .axis text { 13 | font: 10px sans-serif; 14 | } 15 | 16 | .axis path, 17 | .axis line { 18 | fill: none; 19 | stroke: #000; 20 | shape-rendering: crispEdges; 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | before_install: 10 | - npm install bower -g 11 | - npm install gulp -g 12 | 13 | script: 14 | - npm install 15 | - bower install 16 | - gulp test --verbose 17 | 18 | cache: 19 | directories: 20 | - bower_components 21 | - node_modules 22 | 23 | notifications: 24 | email: 25 | - xxx@gmail.com 26 | 27 | -------------------------------------------------------------------------------- /src/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.core', [ 5 | // Angular modules (ngAnimate 1.4.x is not compatible with ui.bootstrap 0.13.0) 6 | /* 'ngAnimate', */ 'ngSanitize', 7 | 8 | // Our reusable framework 9 | 'fw.exception', 'fw.logger', 10 | 11 | // 3rd Party modules 12 | 'toastr', 'ui.bootstrap', 'ui.router' 13 | ]); 14 | })(); 15 | -------------------------------------------------------------------------------- /mock-server/routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | var api = '/api'; 3 | var data = '/../../data/'; 4 | var jsonfileservice = require('./utils/jsonfileservice')(); 5 | 6 | app.get(api + '/account', getAccountSummary); 7 | 8 | function getAccountSummary(req, res, next) { 9 | var json = jsonfileservice.getJsonFromFile(data + 'account-summary.json'); 10 | res.send(json); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/core/styles/_core.scss: -------------------------------------------------------------------------------- 1 | /* Move down content because we have a fixed navbar that is 50px tall */ 2 | body { 3 | padding-top: 50px; 4 | } 5 | 6 | /* Remove border to delete the 1px line */ 7 | .navbar-fixed-top { 8 | border: 0; 9 | } 10 | 11 | /* Workaround for unwanted route changes due to empty href attributes - see http://angular-ui.github.io/bootstrap/ */ 12 | .nav, .pagination, .carousel, .panel-title a { cursor: pointer; } 13 | -------------------------------------------------------------------------------- /src/components/approot/approot.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | 'use strict'; 4 | 5 | angular.module('app.approot') 6 | .directive('ptrnApproot', directiveFunction); 7 | 8 | 9 | // ----- directiveFunction ----- 10 | function directiveFunction() { 11 | 12 | var directive = { 13 | restrict: 'E', 14 | templateUrl: 'components/approot/approot.html', 15 | scope: { 16 | } 17 | }; 18 | 19 | return directive; 20 | } 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /src/core/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | * variables 3 | * -------------------------------------------------------------------------- */ 4 | 5 | /* ----- Colors ----- */ 6 | $color-border: #EEEEEE; 7 | $color-soft: #AAAAAA; 8 | 9 | // ------------------------- 10 | // Bootstrap Overrides 11 | // ------------------------- 12 | 13 | /* ----- Typography ----- */ 14 | $font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif !default; -------------------------------------------------------------------------------- /gulp-tasks/inject.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | gulp.task('inject', ['styles', 'templatecache'], function () { 6 | 7 | return gulp 8 | .src(config.sourceDir + 'index.html') 9 | .pipe(config.$.inject(gulp.src(config.js))) 10 | .pipe(config.$.inject(gulp.src(config.tempDir + 'templates.js'), {name: 'inject:templates', read: false})) 11 | .pipe(config.$.inject(gulp.src(config.tempDir + 'app.css'))) 12 | .pipe(gulp.dest(config.tempDir)); 13 | 14 | }); 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /src/framework/exception/exception.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('fw.exception') 6 | .factory('exception', exception); 7 | 8 | exception.$inject = ['logger']; 9 | 10 | /* @ngInject */ 11 | function exception(logger) { 12 | var service = { 13 | catcher: catcher 14 | }; 15 | return service; 16 | 17 | function catcher(message) { 18 | return function(reason) { 19 | logger.error(message, reason); 20 | }; 21 | } 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /src/components/profile/profile.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | 'use strict'; 4 | 5 | angular.module('app.profile') 6 | .directive('ptrnProfile', directiveFunction); 7 | 8 | 9 | // ----- directiveFunction ----- 10 | directiveFunction.$inject = []; 11 | 12 | /* @ngInject */ 13 | function directiveFunction() { 14 | 15 | var directive = { 16 | restrict: 'E', 17 | templateUrl: 'components/profile/profile.html', 18 | scope: { 19 | } 20 | }; 21 | 22 | return directive; 23 | } 24 | 25 | })(); 26 | -------------------------------------------------------------------------------- /src/core/filters/percentage.filter.js: -------------------------------------------------------------------------------- 1 | // Formats a number in decimal format to a percentage (i.e. 0.17 as 17%). 2 | // Usage: 3 | // {{0.17 | percentage:2}} 4 | // => 17% 5 | 6 | (function () { 7 | 'use strict'; 8 | 9 | angular 10 | .module('app.core') 11 | .filter('percentage', filterFunction); 12 | 13 | filterFunction.$inject = ['$filter']; 14 | 15 | /* @ngInject */ 16 | function filterFunction($filter) { 17 | return function(input, decimals) { 18 | return $filter('number')(input * 100, decimals) + '%'; 19 | }; 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /gulp-tasks/styles.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var watch = require('gulp-watch'); 3 | 4 | module.exports = function (config) { 5 | 6 | gulp.task('styles', ['clean-styles'], function () { 7 | config.log('Compiling Sass --> CSS'); 8 | 9 | return gulp 10 | .src(config.sourceDir + 'app.scss') 11 | .pipe(config.$.plumber()) // exit gracefully if something fails after this 12 | .pipe(config.$.sass()) 13 | .pipe(config.$.autoprefixer({browsers: ['last 2 version', '> 5%']})) 14 | .pipe(gulp.dest(config.tempDir)); 15 | 16 | }); 17 | 18 | gulp.task('sass-watcher', function () { 19 | watch(config.sass, function () { gulp.start('styles'); }); 20 | }); 21 | 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /gulp-tasks/assets.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | gulp.task('fonts', ['clean-fonts'], function () { 6 | config.log('Copying fonts'); 7 | 8 | var fontDir = './bower_components/bootstrap-sass/assets/fonts/**/*.*'; 9 | 10 | return gulp 11 | .src(fontDir) 12 | .pipe(gulp.dest(config.buildDir + 'fonts')); 13 | }); 14 | 15 | gulp.task('images', ['clean-images'], function () { 16 | config.log('Compressing and copying images'); 17 | 18 | return gulp 19 | .src(config.sourceDir + 'images/**/*.*') 20 | .pipe(config.$.imagemin({optimizationLevel: 4})) 21 | .pipe(gulp.dest(config.buildDir + 'images')); 22 | }); 23 | 24 | 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /mock-server/routes/utils/jsonfileservice.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var service = { 3 | getJsonFromFile: getJsonFromFile 4 | }; 5 | return service; 6 | 7 | function getJsonFromFile(file) { 8 | var fs = require('fs'); 9 | var json = getConfig(file); 10 | return json; 11 | 12 | function readJsonFileSync(filepath, encoding) { 13 | if (typeof (encoding) === 'undefined') { 14 | encoding = 'utf8'; 15 | } 16 | var file = fs.readFileSync(filepath, encoding); 17 | return JSON.parse(file); 18 | } 19 | 20 | function getConfig(file) { 21 | var filepath = __dirname + file; 22 | return readJsonFileSync(filepath); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /mock-server/routes/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var service = { 3 | init: init, 4 | logErrors: logErrors 5 | }; 6 | return service; 7 | 8 | function init(err, req, res, next) { 9 | var status = err.statusCode || 500; 10 | if (err.message) { 11 | res.send(status, err.message); 12 | } else { 13 | res.send(status, err); 14 | } 15 | next(); 16 | } 17 | 18 | /* Our fall through error logger and errorHandler */ 19 | function logErrors(err, req, res, next) { 20 | var status = err.statusCode || 500; 21 | console.error(status + ' ' + (err.message ? err.message : err)); 22 | if (err.stack) { 23 | console.error(err.stack); 24 | } 25 | next(err); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-template", 3 | "version": "0.1.0", 4 | "description": "A template to kick-start AngularJS projects", 5 | "author": "Naresh Bhatia", 6 | "license": "MIT", 7 | "homepage": "https://github.com/archfirst/angular-template", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "test", 13 | "tests" 14 | ], 15 | "devDependencies": { 16 | "angular-mocks": "~1.4.0", 17 | "bardjs": "~0.1.4" 18 | }, 19 | "dependencies": { 20 | "angular": "~1.4.0", 21 | "angular-animate": "~1.4.0", 22 | "angular-bootstrap": "~0.13.0", 23 | "angular-sanitize": "~1.4.0", 24 | "angular-toastr": "~1.4.1", 25 | "angular-ui-router": "~0.2.15", 26 | "bootstrap-sass": "~3.3.4", 27 | "d3": "~3.5.5", 28 | "lodash": "~3.9.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/core.router.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var core = angular.module('app.core'); 5 | 6 | core.config(configFunction); 7 | 8 | configFunction.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 9 | 10 | /* @ngInject */ 11 | function configFunction($locationProvider, $stateProvider, $urlRouterProvider) { 12 | 13 | $locationProvider.html5Mode(true); 14 | 15 | $urlRouterProvider.otherwise('/'); 16 | 17 | $stateProvider 18 | .state('dashboard', { 19 | url: '/', 20 | template: '' 21 | }) 22 | .state('profile', { 23 | url: '/profile', 24 | template: '' 25 | }); 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /gulp-tasks/vet.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | gulp.task('vet', vet); 6 | 7 | 8 | /** 9 | * vet the code and create coverage report 10 | * @return {Stream} 11 | */ 12 | function vet() { 13 | config.log('Analyzing source with JSHint and JSCS'); 14 | 15 | return gulp 16 | .src([ 17 | config.sourceDir + '**/*.js', 18 | config.testDir + '**/*.js', 19 | './*.js' 20 | ]) 21 | .pipe(config.$.if(config.args.verbose, config.$.print())) 22 | .pipe(config.$.jshint()) 23 | .pipe(config.$.jshint.reporter('jshint-stylish', {verbose: true})) 24 | .pipe(config.$.jshint.reporter('fail')) 25 | .pipe(config.$.jscs()); 26 | } 27 | }; 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/helpers/mock-data.js: -------------------------------------------------------------------------------- 1 | /* jshint -W079, -W098, -W126 */ 2 | var mockData = (function() { 3 | 'use strict'; 4 | 5 | return { 6 | getMockAccount: getMockAccount 7 | }; 8 | 9 | function getMockAccount() { 10 | return { 11 | 'market_value': 10000, 12 | 'investment': 9500, 13 | 'earnings': 500, 14 | 'cash': 1000, 15 | 'assets': [ 16 | { 17 | 'asset_class': 'US Stocks', 18 | 'market_value': 9000, 19 | 'percent_allocation': 0.90, 20 | 'percent_return': 0.0510 21 | }, 22 | { 23 | 'asset_class': 'Cash', 24 | 'market_value': 1000, 25 | 'percent_allocation': 0.10, 26 | 'percent_return': 0.0000 27 | } 28 | ] 29 | }; 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /test/server-integration/account.service.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W024, -W030, -W117 */ 2 | /** 3 | * Demonstrates use of bard's real $http and $q 4 | * restoring the ability to make AJAX calls to the server 5 | * while retaining all the goodness of ngMocks. 6 | * 7 | * An alternative to the ngMidwayTester 8 | */ 9 | 10 | describe('Server: accountService', function() { 11 | 'use strict'; 12 | var accountService; 13 | 14 | beforeEach(bard.asyncModule('app')); 15 | 16 | beforeEach(inject(function(_accountService_) { 17 | accountService = _accountService_; 18 | })); 19 | 20 | describe('when call getAccount', function() { 21 | 22 | it('should get 5 assets', function(done) { 23 | accountService.getAccount() 24 | .then(function(data) { 25 | expect(data.assets).to.have.length(5); 26 | }) 27 | .then(done, done); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/topnav/topnav.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.topnav') 6 | .directive('ptrnTopnav', directiveFunction) 7 | .controller('TopnavController', ControllerFunction); 8 | 9 | 10 | // ----- directiveFunction ----- 11 | directiveFunction.$inject = []; 12 | 13 | /* @ngInject */ 14 | function directiveFunction() { 15 | 16 | var directive = { 17 | restrict: 'E', 18 | templateUrl: 'components/topnav/topnav.html', 19 | scope: { 20 | }, 21 | controller: 'TopnavController', 22 | controllerAs: 'vm' 23 | }; 24 | 25 | return directive; 26 | } 27 | 28 | // ----- ControllerFunction ----- 29 | ControllerFunction.$inject = []; 30 | 31 | /* @ngInject */ 32 | function ControllerFunction() { 33 | var vm = this; 34 | vm.isCollapsed = true; 35 | } 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /src/core/services/account.service.js: -------------------------------------------------------------------------------- 1 | /* jshint -W024 */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .factory('accountService', serviceFunction); 8 | 9 | serviceFunction.$inject = ['$http', '$location', 'exception', 'api']; 10 | 11 | /* @ngInject */ 12 | function serviceFunction($http, $location, exception, api) { 13 | var service = { 14 | getAccount: getAccount 15 | }; 16 | 17 | return service; 18 | 19 | function getAccount() { 20 | return $http.get(api + '/account') 21 | .then(getAccountSuccess) 22 | .catch(function(message) { 23 | exception.catcher('XHR Failed for getAccount')(message); 24 | $location.url('/'); 25 | }); 26 | 27 | function getAccountSuccess(response) { 28 | return response.data; 29 | } 30 | } 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /mock-server/app.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var express = require('express'); 5 | var app = express(); 6 | var bodyParser = require('body-parser'); 7 | var compress = require('compression'); 8 | var cors = require('cors'); 9 | var errorHandler = require('./routes/utils/errorHandler')(); 10 | var logger = require('morgan'); 11 | var port = process.env.PORT || 7203; 12 | var routes; 13 | 14 | var environment = process.env.NODE_ENV; 15 | 16 | app.use(bodyParser.urlencoded({ 17 | extended: true 18 | })); 19 | app.use(bodyParser.json()); 20 | app.use(compress()); 21 | app.use(logger('dev')); 22 | app.use(cors()); 23 | app.use(errorHandler.init); 24 | 25 | routes = require('./routes/index')(app); 26 | 27 | app.get('/ping', function(req, res, next) { 28 | console.log(req.body); 29 | res.send('pong'); 30 | }); 31 | 32 | app.listen(port, function() { 33 | var url = [ 34 | 'http://', 35 | this.hostname 36 | ].join(''); 37 | console.log('Mock-Server started listening on: http://localhost:' + port); 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/dashboard/_dashboard.scss: -------------------------------------------------------------------------------- 1 | /* ----- Sidebar ----- */ 2 | 3 | /* Hide for mobile, show later */ 4 | .sidebar { 5 | display: none; 6 | } 7 | @media (min-width: 768px) { 8 | .sidebar { 9 | position: fixed; 10 | top: 140px; 11 | bottom: 0; 12 | left: 0; 13 | z-index: 1000; 14 | display: block; 15 | padding: 5px 20px; 16 | overflow-x: hidden; 17 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 18 | border-right: 1px solid $color-border; 19 | } 20 | } 21 | 22 | .sidebar dt { 23 | color: $color-soft; 24 | font-weight: normal; 25 | } 26 | 27 | .sidebar dd { 28 | font-size: 24px; 29 | border-bottom: 1px solid $color-border; 30 | margin-bottom: 20px; 31 | } 32 | 33 | .medialinks { 34 | margin-top: 30px; 35 | } 36 | 37 | /* ----- Main Content ----- */ 38 | .main { 39 | padding: 20px; 40 | } 41 | @media (min-width: 768px) { 42 | .main { 43 | padding-right: 40px; 44 | padding-left: 40px; 45 | } 46 | } 47 | 48 | /* ----- Chart ----- */ 49 | .chart-column { 50 | margin-top: 39px; 51 | } 52 | -------------------------------------------------------------------------------- /test/core/services/account.service.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W024, -W030, -W098, -W117 */ 2 | describe('accountService', function() { 3 | 'use strict'; 4 | var account = mockData.getMockAccount(); 5 | 6 | beforeEach(function() { 7 | bard.appModule('app.core'); 8 | bard.inject('$httpBackend', '$rootScope', 'accountService', 'api'); 9 | }); 10 | 11 | beforeEach(function() { 12 | $httpBackend.when('GET', api + '/account').respond(200, account); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | it('should be registered', function() { 18 | expect(accountService).not.to.equal(null); 19 | }); 20 | 21 | describe('getAccount function', function() { 22 | it('should exist', function() { 23 | expect(accountService.getAccount).not.to.equal(null); 24 | }); 25 | 26 | it('should return 2 assets', function(done) { 27 | accountService.getAccount().then(function(data) { 28 | expect(data.assets.length).to.equal(2); 29 | }).then(done, done); 30 | $httpBackend.flush(); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Visual Studio Project # 2 | ################### 3 | *.user 4 | *.gpState 5 | *.suo 6 | bin 7 | obj 8 | /packages 9 | 10 | # Ignore Node, Bower & Sass 11 | ################### 12 | node_modules 13 | bower_components 14 | build 15 | .sass-cache 16 | .tmp 17 | 18 | # Ignore Test reporters 19 | ################### 20 | **/test/coverage 21 | report 22 | 23 | 24 | # mongo db 25 | ################### 26 | #Don't commit Mongo Database files 27 | *.lock 28 | *.0 29 | *.1 30 | *.ns 31 | journal 32 | 33 | # Ignore Web Storm # 34 | .idea 35 | 36 | # Compiled source # 37 | ################### 38 | *.com 39 | *.class 40 | *.dll 41 | *.exe 42 | *.o 43 | *.so 44 | 45 | # Packages # 46 | ############ 47 | # it's better to unpack these files and commit the raw source 48 | # git has its own built in compression methods 49 | *.7z 50 | *.dmg 51 | *.gz 52 | *.iso 53 | *.jar 54 | *.rar 55 | *.tar 56 | *.xap 57 | *.zip 58 | 59 | # Logs and databases # 60 | ###################### 61 | *.log 62 | *.sql 63 | *.sqlite 64 | # *.sdf 65 | *.mdf 66 | *.ldf 67 | 68 | # OS generated files # 69 | ###################### 70 | .DS_Store* 71 | ehthumbs.db 72 | Icon? 73 | Thumbs.db 74 | 75 | -------------------------------------------------------------------------------- /mock-server/data/account-summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "market_value": 1956220, 3 | "investment": 1000000, 4 | "earnings": 956220, 5 | "cash": 39124, 6 | "assets": [ 7 | { 8 | "asset_class": "US Stocks", 9 | "market_value": 586866, 10 | "percent_allocation": 0.30, 11 | "percent_return": 0.0680 12 | }, 13 | { 14 | "asset_class": "Foreign Stocks", 15 | "market_value": 293433, 16 | "percent_allocation": 0.15, 17 | "percent_return": 0.0840 18 | }, 19 | { 20 | "asset_class": "Emerging Markets", 21 | "market_value": 293433, 22 | "percent_allocation": 0.15, 23 | "percent_return": 0.1270 24 | }, 25 | { 26 | "asset_class": "Bonds", 27 | "market_value": 743364, 28 | "percent_allocation": 0.38, 29 | "percent_return": 0.0220 30 | }, 31 | { 32 | "asset_class": "Cash", 33 | "market_value": 39124, 34 | "percent_allocation": 0.02, 35 | "percent_return": 0.0000 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true, -W024, -W040, -W098, -W126 */ 2 | 3 | 'use strict'; 4 | 5 | 6 | /** 7 | * yargs variables can be passed in to alter the behavior, when present. 8 | * Example: gulp serve-dev 9 | * 10 | * --verbose : Various tasks will produce more output to the console. 11 | * --nosync : Don't launch the browser with browser-sync when serving code. 12 | * --debug : Launch debugger with node-inspector. 13 | * --debug-brk: Launch debugger and break on 1st line with node-inspector. 14 | * --startServers: Will start servers for midway tests on the test task. 15 | */ 16 | 17 | var gulp = require('gulp'), 18 | $ = require('gulp-load-plugins')({lazy: true}), 19 | src = './src/', 20 | config = require('./gulp.config'), 21 | buildTask = (function (config, taskFile) { 22 | require('./gulp-tasks/' + taskFile)(config); 23 | }).bind(null, config); 24 | 25 | [ 26 | 'help', 27 | 'serve', 28 | 'vet', 29 | 'styles', 30 | 'clean', 31 | 'plato', 32 | 'assets', 33 | 'template-cache', 34 | 'inject', 35 | 'optimize', 36 | 'test', 37 | 'bump' 38 | ].forEach(buildTask); 39 | 40 | 41 | gulp.task('default', ['help']); 42 | 43 | module.exports = gulp; 44 | -------------------------------------------------------------------------------- /gulp-tasks/bump.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | var args = config.args, 6 | log = config.log, 7 | $ = config.$; 8 | 9 | /** 10 | * Bump the version 11 | * --type=pre will bump the prerelease version *.*.*-x 12 | * --type=patch or no flag will bump the patch version *.*.x 13 | * --type=minor will bump the minor version *.x.* 14 | * --type=major will bump the major version x.*.* 15 | * --version=1.2.3 will bump to a specific version and ignore other flags 16 | */ 17 | gulp.task('bump', function () { 18 | var msg = 'Bumping versions'; 19 | var type = args.type; 20 | var version = args.ver; 21 | var options = {}; 22 | if (version) { 23 | options.version = version; 24 | msg += ' to ' + version; 25 | } else { 26 | options.type = type; 27 | msg += ' for a ' + type; 28 | } 29 | log(msg); 30 | 31 | return gulp 32 | .src([ 33 | './package.json', 34 | './bower.json' 35 | ]) 36 | .pipe($.print()) 37 | .pipe($.bump(options)) 38 | .pipe(gulp.dest('../')); 39 | }); 40 | 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /gulp-tasks/plato.js: -------------------------------------------------------------------------------- 1 | var plato = require('plato'); 2 | var glob = require('glob'); 3 | var gulp = require('gulp'); 4 | 5 | module.exports = function (config) { 6 | 7 | gulp.task('plato', function (done) { 8 | config.log('Analyzing source with Plato'); 9 | config.log('Browse to /report/plato/index.html to see Plato results'); 10 | 11 | startPlatoVisualizer(done); 12 | }); 13 | 14 | /** 15 | * Start Plato inspector and visualizer 16 | */ 17 | function startPlatoVisualizer(done) { 18 | config.log('Running Plato'); 19 | 20 | var files = glob.sync(config.sourceDir + '**/*.js'); 21 | var excludeFiles = /.*\.spec\.js/; 22 | 23 | var options = { 24 | title: 'Plato Inspections Report', 25 | exclude: excludeFiles 26 | }; 27 | var outputDir = './report/plato'; 28 | 29 | plato.inspect(files, outputDir, options, platoCompleted); 30 | 31 | function platoCompleted(report) { 32 | var overview = plato.getOverviewReport(report); 33 | if (config.args.verbose) { 34 | config.log(overview.summary); 35 | } 36 | if (done) { 37 | done(); 38 | } 39 | } 40 | } 41 | 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/core/core.config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var core = angular.module('app.core'); 5 | 6 | // Application configuration values 7 | var config = { 8 | appErrorPrefix: '[Angular Template Error] ', 9 | appTitle: 'Angular Template' 10 | }; 11 | 12 | core.value('config', config); 13 | 14 | // Configure the app 15 | core.config(configFunction); 16 | 17 | configFunction.$inject = 18 | ['$compileProvider', '$logProvider', 'exceptionHandlerProvider']; 19 | 20 | /* @ngInject */ 21 | function configFunction( 22 | $compileProvider, $logProvider, exceptionHandlerProvider) { 23 | 24 | // During development, you may want to set debugInfoEnabled to true. This is required for tools like 25 | // Protractor, Batarang and ng-inspector to work correctly. However do not check in this change. 26 | // This flag must be set to false in production for a significant performance boost. 27 | $compileProvider.debugInfoEnabled(false); 28 | 29 | // turn debugging off/on (no info or warn) 30 | if ($logProvider.debugEnabled) { 31 | $logProvider.debugEnabled(true); 32 | } 33 | 34 | exceptionHandlerProvider.configure(config.appErrorPrefix); 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /gulp-tasks/clean.js: -------------------------------------------------------------------------------- 1 | var del = require('del'); 2 | var gulp = require('gulp'); 3 | 4 | module.exports = function (config) { 5 | 6 | gulp.task('clean', function (done) { 7 | var delconfig = [].concat(config.buildDir, './.sass-cache/', config.tempDir, './report/'); 8 | 9 | config.log('Cleaning: ' + config.$.util.colors.blue(delconfig)); 10 | 11 | del(delconfig, done); 12 | }); 13 | 14 | gulp.task('clean-fonts', function (done) { 15 | clean(config.buildDir + 'fonts/**/*.*', done); 16 | }); 17 | 18 | gulp.task('clean-images', function (done) { 19 | clean(config.buildDir + 'images/**/*.*', done); 20 | }); 21 | 22 | gulp.task('clean-code', function (done) { 23 | var files = [].concat( 24 | config.tempDir + '**/*.js', 25 | config.buildDir + '**/*.js', 26 | config.buildDir + '**/*.html' 27 | ); 28 | 29 | clean(files, done); 30 | }); 31 | 32 | gulp.task('clean-styles', function (done) { 33 | var files = [].concat( 34 | config.tempDir + '**/*.css', 35 | config.buildDir + '**/*.css' 36 | ); 37 | 38 | clean(files, done); 39 | }); 40 | 41 | function clean(path, done) { 42 | config.log('Cleaning: ' + config.$.util.colors.blue(path)); 43 | del(path, done); 44 | } 45 | 46 | 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /gulp.config.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true, -W024, -W040, -W098, -W126 */ 2 | 3 | 'use strict'; 4 | 5 | var $ = require('gulp-load-plugins')({lazy: true}), 6 | src = './src/'; 7 | 8 | module.exports = { 9 | 10 | // --- Configurables --- 11 | sourceDir: src, 12 | testDir: './test/', 13 | buildDir: './build/', 14 | tempDir: './.tmp/', 15 | proxyPort: 7203, 16 | port: 3000, 17 | browserReloadDelay: 1000, 18 | js: [ 19 | // module files in desired order 20 | src + '**/*.module.js', 21 | 22 | // remaining files in desired order 23 | src + 'core/**/*.js', 24 | src + 'framework/**/*.js', 25 | src + '**/*.js' 26 | ], 27 | html: src + '**/*.html', 28 | sass: src + '**/*.scss', 29 | $: $, 30 | args: require('yargs').argv, 31 | 32 | // --- Utilities --- 33 | log: function log(msg) { 34 | if (typeof(msg) === 'object') { 35 | for (var item in msg) { 36 | if (msg.hasOwnProperty(item)) { 37 | $.util.log($.util.colors.blue(msg[item])); 38 | } 39 | } 40 | } else { 41 | $.util.log($.util.colors.blue(msg)); 42 | } 43 | }, 44 | notify: function notify(options) { 45 | var notifier = require('node-notifier'); 46 | notifier.notify(options); 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/topnav/topnav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | -------------------------------------------------------------------------------- /test/components/dashboard/dashboard.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117 */ 2 | describe('Dashboard', function() { 3 | 'use strict'; 4 | 5 | var controller; 6 | 7 | beforeEach(function() { 8 | bard.appModule('app.dashboard'); 9 | bard.inject('$rootScope', '$controller', '$q', 'accountService'); 10 | }); 11 | 12 | beforeEach(function() { 13 | sinon.stub(accountService, 'getAccount').returns($q.when(mockData.getMockAccount())); 14 | 15 | controller = $controller('DashboardController'); 16 | $rootScope.$apply(); 17 | }); 18 | 19 | describe('Dashboard controller', function() { 20 | it('should have a market value of $10,000', function() { 21 | expect(controller.account.market_value).to.equal(10000); 22 | }); 23 | 24 | it('should have an investment amount of $9,500', function() { 25 | expect(controller.account.investment).to.equal(9500); 26 | }); 27 | 28 | it('should have an earnings amount of $500', function() { 29 | expect(controller.account.earnings).to.equal(500); 30 | }); 31 | 32 | it('should have $1,000 in cash', function() { 33 | expect(controller.account.cash).to.equal(1000); 34 | }); 35 | 36 | it('should have 2 assets', function() { 37 | expect(controller.account.assets.length).to.equal(2); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/helpers/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Phantom.js does not support Function.prototype.bind (at least not before v.2.0 3 | * That's just crazy. Everybody supports bind. 4 | * Read about it here: https://groups.google.com/forum/#!msg/phantomjs/r0hPOmnCUpc/uxusqsl2LNoJ 5 | * This polyfill is copied directly from MDN 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility 7 | */ 8 | /* jshint strict: false */ 9 | if (!Function.prototype.bind) { 10 | /*jshint freeze: false */ 11 | Function.prototype.bind = function (oThis) { 12 | if (typeof this !== 'function') { 13 | // closest thing possible to the ECMAScript 5 14 | // internal IsCallable function 15 | var msg = 'Function.prototype.bind - what is trying to be bound is not callable'; 16 | throw new TypeError(msg); 17 | } 18 | 19 | var aArgs = Array.prototype.slice.call(arguments, 1), 20 | fToBind = this, 21 | FuncNoOp = function () {}, 22 | fBound = function () { 23 | return fToBind.apply(this instanceof FuncNoOp && oThis ? this : oThis, 24 | aArgs.concat(Array.prototype.slice.call(arguments))); 25 | }; 26 | 27 | FuncNoOp.prototype = this.prototype; 28 | fBound.prototype = new FuncNoOp(); 29 | 30 | return fBound; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/framework/logger/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('fw.logger') 6 | .factory('logger', logger); 7 | 8 | logger.$inject = ['$log', 'toastr']; 9 | 10 | /* @ngInject */ 11 | function logger($log, toastr) { 12 | var service = { 13 | log : log, 14 | info : info, 15 | success : success, 16 | warn : warn, 17 | error : error, 18 | debug : debug 19 | }; 20 | 21 | return service; 22 | ///////////////////// 23 | 24 | function log(message) { 25 | $log.log('log: ' + message); 26 | } 27 | 28 | function info(message) { 29 | toastr.info(message, 'Information'); 30 | $log.info('info: ' + message); 31 | } 32 | 33 | function success(message) { 34 | toastr.success(message, 'Success'); 35 | $log.info('success: ' + message); 36 | } 37 | 38 | function warn(message) { 39 | toastr.warning(message, 'Warning'); 40 | $log.warn('warn: ' + message); 41 | } 42 | 43 | function error(message) { 44 | toastr.error(message, 'Error'); 45 | $log.error('error: ' + message); 46 | } 47 | 48 | function debug(message) { 49 | $log.debug('debug: ' + message); 50 | } 51 | } 52 | }()); 53 | -------------------------------------------------------------------------------- /src/components/profile/profile.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Profile

4 | 5 |
6 | 7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 | 24 |
25 | 26 |
27 | 28 | {{vm.user.firstname}} {{vm.user.lastname}} ({{vm.user.username}}) 29 |
30 | 31 |
32 |
-------------------------------------------------------------------------------- /gulp-tasks/template-cache.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | 5 | var templateCacheConfig = { 6 | templateFiles: [ 7 | config.sourceDir + '**/*.html', 8 | '!' + config.sourceDir + 'index.html' 9 | ], 10 | file: 'templates.js', 11 | destDir: config.tempDir, 12 | options: { 13 | module: 'app.core', 14 | root: '', 15 | standAlone: false 16 | } 17 | }; 18 | 19 | gulp.task('templatecache', ['clean-code'], function () { 20 | config.log('Creating an AngularJS $templateCache at ' + config.tempDir + templateCacheConfig.file); 21 | 22 | return gulp 23 | .src(templateCacheConfig.templateFiles) 24 | .pipe(config.$.if(config.args.verbose, config.$.bytediff.start())) 25 | .pipe(config.$.minifyHtml({empty: true})) 26 | .pipe(config.$.if(config.args.verbose, config.$.bytediff.stop(bytediffFormatter))) 27 | .pipe(config.$.angularTemplatecache( 28 | templateCacheConfig.file, 29 | templateCacheConfig.options 30 | )) 31 | .pipe(gulp.dest(templateCacheConfig.destDir)); 32 | }); 33 | 34 | 35 | function bytediffFormatter(data) { 36 | var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; 37 | return data.fileName + ' went from ' + 38 | (data.startSize / 1000).toFixed(2) + ' kB to ' + 39 | (data.endSize / 1000).toFixed(2) + ' kB and is ' + 40 | formatPercent(1 - data.percent, 2) + '%' + difference; 41 | } 42 | 43 | 44 | function formatPercent(num, precision) { 45 | return (num * 100).toFixed(precision); 46 | } 47 | 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /gulp-tasks/test.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | module.exports = function (config) { 4 | var log = config.log, 5 | args = config.args; 6 | 7 | gulp.task('test', [ 'vet', 'templatecache' ], function (done) { 8 | startTests(true /*singleRun*/, done); 9 | }); 10 | 11 | gulp.task('autotest', function (done) { 12 | startTests(false /*singleRun*/, done); 13 | }); 14 | 15 | 16 | function startTests(singleRun, done) { 17 | var child; 18 | var excludeFiles = []; 19 | var fork = require('child_process').fork; 20 | var karma = require('karma').server; 21 | var serverSpecs = [config.testDir + 'server-integration/**/*.spec.js']; 22 | 23 | if (args.startServers) { 24 | log('Starting servers'); 25 | var savedEnv = process.env; 26 | savedEnv.NODE_ENV = 'dev'; 27 | savedEnv.PORT = 3000; 28 | child = fork('../mock-server/app.js'); 29 | } else { 30 | if (serverSpecs && serverSpecs.length) { 31 | excludeFiles = serverSpecs; 32 | } 33 | } 34 | 35 | karma.start({ 36 | configFile: __dirname + '/../karma.conf.js', 37 | exclude: excludeFiles, 38 | singleRun: !!singleRun 39 | }, karmaCompleted); 40 | 41 | //////////////// 42 | 43 | function karmaCompleted(karmaResult) { 44 | log('Karma completed'); 45 | if (child) { 46 | log('shutting down the child process'); 47 | child.kill(); 48 | } 49 | if (karmaResult === 1) { 50 | done('karma: tests failed with code ' + karmaResult); 51 | } else { 52 | done(); 53 | } 54 | } 55 | } 56 | 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /src/components/dashboard/dashboard.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | 'use strict'; 4 | 5 | angular.module('app.dashboard') 6 | .directive('ptrnDashboard', directiveFunction) 7 | .controller('DashboardController', ControllerFunction); 8 | 9 | 10 | // ----- directiveFunction ----- 11 | directiveFunction.$inject = []; 12 | 13 | /* @ngInject */ 14 | function directiveFunction() { 15 | 16 | var directive = { 17 | restrict: 'E', 18 | templateUrl: 'components/dashboard/dashboard.html', 19 | scope: { 20 | }, 21 | controller: 'DashboardController', 22 | controllerAs: 'vm' 23 | }; 24 | 25 | return directive; 26 | } 27 | 28 | 29 | // ----- ControllerFunction ----- 30 | ControllerFunction.$inject = ['accountService', 'logger', '_']; 31 | 32 | /* @ngInject */ 33 | function ControllerFunction(accountService, logger, _) { 34 | var vm = this; 35 | 36 | vm.account = null; 37 | vm.chartdata = null; 38 | 39 | activate(); 40 | 41 | function activate() { 42 | return getAccount().then(function () { 43 | logger.log('Activated Dashboard View'); 44 | }); 45 | } 46 | 47 | function getAccount() { 48 | return accountService.getAccount().then(function (data) { 49 | 50 | // Convert assets to chart data 51 | var chartdata = _.map(data.assets, function (asset) { 52 | return { 53 | key: asset.asset_class, 54 | value: asset.percent_allocation * 100 55 | }; 56 | }); 57 | 58 | vm.account = data; 59 | vm.chartdata = chartdata; 60 | return vm.account; 61 | }); 62 | } 63 | } 64 | 65 | })(); 66 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Angular Patterns 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [ 3 | "node_modules/**", 4 | "bower_components/**" 5 | ], 6 | "requireCurlyBraces": [ 7 | "if", 8 | "else", 9 | "for", 10 | "while", 11 | "do", 12 | "try", 13 | "catch" 14 | ], 15 | "requireOperatorBeforeLineBreak": true, 16 | "maximumLineLength": { 17 | "value": 120, 18 | "allowComments": true, 19 | "allowRegex": true 20 | }, 21 | "validateIndentation": 4, 22 | "validateQuoteMarks": "'", 23 | "disallowMultipleLineStrings": true, 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": true, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowMultipleVarDecl": null, 28 | "requireSpaceAfterKeywords": [ 29 | "if", 30 | "else", 31 | "for", 32 | "while", 33 | "do", 34 | "switch", 35 | "return", 36 | "try", 37 | "catch" 38 | ], 39 | "requireSpaceBeforeBinaryOperators": [ 40 | "=", 41 | "+=", 42 | "-=", 43 | "*=", 44 | "/=", 45 | "%=", 46 | "<<=", 47 | ">>=", 48 | ">>>=", 49 | "&=", 50 | "|=", 51 | "^=", 52 | "+=", 53 | "+", 54 | "-", 55 | "*", 56 | "/", 57 | "%", 58 | "<<", 59 | ">>", 60 | ">>>", 61 | "&", 62 | "|", 63 | "^", 64 | "&&", 65 | "||", 66 | "===", 67 | "==", 68 | ">=", 69 | "<=", 70 | "<", 71 | ">", 72 | "!=", 73 | "!==" 74 | ], 75 | "requireSpaceAfterBinaryOperators": true, 76 | "requireSpacesInConditionalExpression": true, 77 | "requireSpaceBeforeBlockStatements": true, 78 | "validateJSDoc": { 79 | "checkParamNames": true, 80 | "requireParamTypes": true 81 | }, 82 | "disallowCommaBeforeLineBreak": null, 83 | "disallowDanglingUnderscores": null, 84 | "disallowEmptyBlocks": null, 85 | "disallowMultipleLineStrings": null, 86 | "disallowTrailingComma": null, 87 | "requireCommaBeforeLineBreak": null, 88 | "requireDotNotation": null, 89 | "requireMultipleVarDecl": null, 90 | "requireParenthesesAroundIIFE": true 91 | } 92 | -------------------------------------------------------------------------------- /gulp-tasks/optimize.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var del = require('del'); 3 | 4 | module.exports = function (config) { 5 | 6 | var $ = config.$, 7 | log = config.log; 8 | 9 | gulp.task('build', [ 'optimize', 'images', 'fonts' ], function () { 10 | log('Building everything'); 11 | del(config.tempDir); 12 | }); 13 | 14 | gulp.task('optimize', [ 'inject', 'test' ], function () { 15 | log('Optimizing the js, css, and html'); 16 | 17 | var assets = $.useref.assets({ searchPath: './' }); 18 | 19 | // Filters are named for the gulp-useref path 20 | var cssFilter = $.filter('**/*.css'); 21 | var jsAppFilter = $.filter('**/app.js'); 22 | var jslibFilter = $.filter('**/lib.js'); 23 | 24 | return gulp 25 | .src(config.tempDir + 'index.html') 26 | .pipe($.plumber()) 27 | .pipe(assets) // Gather all assets from the html with useref 28 | // Get the css 29 | .pipe(cssFilter) 30 | .pipe($.csso()) 31 | .pipe(cssFilter.restore()) 32 | // Get the custom javascript 33 | .pipe(jsAppFilter) 34 | .pipe($.ngAnnotate({ add: true })) 35 | .pipe($.uglify()) 36 | .pipe(getHeader()) 37 | .pipe(jsAppFilter.restore()) 38 | // Get the vendor javascript 39 | .pipe(jslibFilter) 40 | .pipe($.uglify()) 41 | .pipe(jslibFilter.restore()) 42 | // Take inventory of the file names for future rev numbers 43 | .pipe($.rev()) 44 | // Apply the concat and file replacement with useref 45 | .pipe(assets.restore()) 46 | .pipe($.useref()) 47 | // Replace the file names in the html with rev numbers 48 | .pipe($.revReplace()) 49 | .pipe(gulp.dest(config.buildDir)); 50 | }); 51 | 52 | 53 | function getHeader() { 54 | var pkg = require('../package.json'); 55 | var template = [ '/**', 56 | ' * <%= pkg.name %> - <%= pkg.description %>', 57 | ' * @authors <%= pkg.authors %>', 58 | ' * @version v<%= pkg.version %>', 59 | ' * @link <%= pkg.homepage %>', 60 | ' * @license <%= pkg.license %>', 61 | ' */', 62 | '' 63 | ].join('\n'); 64 | return $.header(template, { 65 | pkg: pkg 66 | }); 67 | } 68 | 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /test/framework/exception/exception-handler.provider.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W024, -W030, -W098, -W117 */ 2 | describe('fw.exception', function() { 3 | 'use strict'; 4 | var exceptionHandlerProvider; 5 | var mocks = { 6 | errorMessage: 'fake error', 7 | prefix: '[TEST]: ' 8 | }; 9 | 10 | beforeEach(function() { 11 | bard.appModule('fw.exception', function(_exceptionHandlerProvider_) { 12 | exceptionHandlerProvider = _exceptionHandlerProvider_; 13 | }); 14 | bard.inject('$rootScope'); 15 | }); 16 | 17 | bard.verifyNoOutstandingHttpRequests(); 18 | 19 | describe('$exceptionHandler', function() { 20 | it('should have a dummy test', inject(function() { 21 | expect(true).to.equal(true); 22 | })); 23 | 24 | it('should be defined', inject(function($exceptionHandler) { 25 | expect($exceptionHandler).to.be.defined; 26 | })); 27 | 28 | it('should have configuration', inject(function($exceptionHandler) { 29 | expect($exceptionHandler.config).to.be.defined; 30 | })); 31 | 32 | describe('with appErrorPrefix', function() { 33 | beforeEach(function() { 34 | exceptionHandlerProvider.configure(mocks.prefix); 35 | }); 36 | 37 | it('should have exceptionHandlerProvider defined', inject(function() { 38 | expect(exceptionHandlerProvider).to.be.defined; 39 | })); 40 | 41 | it('should have appErrorPrefix defined', inject(function() { 42 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix).to.be.defined; 43 | })); 44 | 45 | it('should have appErrorPrefix set properly', inject(function() { 46 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix) 47 | .to.equal(mocks.prefix); 48 | })); 49 | 50 | it('should throw an error when forced', inject(function() { 51 | expect(functionThatWillThrow).to.throw(); 52 | })); 53 | 54 | it('manual error is handled by decorator', function() { 55 | var exception; 56 | exceptionHandlerProvider.configure(mocks.prefix); 57 | try { 58 | $rootScope.$apply(functionThatWillThrow); 59 | } 60 | catch (ex) { 61 | exception = ex; 62 | expect(ex.message).to.equal(mocks.prefix + mocks.errorMessage); 63 | } 64 | }); 65 | }); 66 | }); 67 | 68 | function functionThatWillThrow() { 69 | throw new Error(mocks.errorMessage); 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /src/framework/exception/exception-handler.provider.js: -------------------------------------------------------------------------------- 1 | // Include in index.html so that app level exceptions are handled. 2 | // Exclude from testRunner.html which should run exactly what it wants to run 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fw.exception') 8 | .provider('exceptionHandler', exceptionHandlerProvider) 9 | .config(config); 10 | 11 | /** 12 | * Must configure the exception handling 13 | * @return {[type]} 14 | */ 15 | function exceptionHandlerProvider() { 16 | /* jshint validthis:true */ 17 | this.config = { 18 | appErrorPrefix: undefined 19 | }; 20 | 21 | this.configure = function (appErrorPrefix) { 22 | this.config.appErrorPrefix = appErrorPrefix; 23 | }; 24 | 25 | this.$get = function() { 26 | return {config: this.config}; 27 | }; 28 | } 29 | 30 | config.$inject = ['$provide']; 31 | 32 | /** 33 | * Configure by setting an optional string value for appErrorPrefix. 34 | * Accessible via config.appErrorPrefix (via config value). 35 | * @param {[type]} $provide 36 | * @return {[type]} 37 | * @ngInject 38 | */ 39 | function config($provide) { 40 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 41 | } 42 | 43 | extendExceptionHandler.$inject = ['$delegate', 'exceptionHandler', '$injector']; 44 | 45 | /** 46 | * Extend the $exceptionHandler service to log via the logger service. 47 | * @param {Object} $delegate 48 | * @param {Object} exceptionHandler 49 | * @param {Object} $injector 50 | * @return {Function} the decorated $exceptionHandler service 51 | */ 52 | function extendExceptionHandler($delegate, exceptionHandler, $injector) { 53 | return function(exception, cause) { 54 | // Need to load logger at runtime to avoid circular dependency with toastr 55 | var logger = $injector.get('logger'); 56 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 57 | var errorData = {exception: exception, cause: cause}; 58 | exception.message = appErrorPrefix + exception.message; 59 | $delegate(exception, cause); 60 | /** 61 | * Could add the error to a service's collection, 62 | * add errors to $rootScope, log errors to remote web server, 63 | * or log locally. Or throw hard. It is entirely up to you. 64 | * throw exception; 65 | * 66 | * @example 67 | * throw { message: 'error message we added' }; 68 | */ 69 | logger.error(exception.message, errorData); 70 | }; 71 | } 72 | })(); 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-patterns", 3 | "description": "A sample application to illustrate AngularJS patterns and best practices", 4 | "version": "0.1.0", 5 | "author": "Naresh Bhatia", 6 | "license": "MIT", 7 | "homepage": "https://github.com/archfirst/angular-patterns", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/archfirst/angular-patterns.git" 11 | }, 12 | "scripts": { 13 | "init": "npm install", 14 | "install": "bower install", 15 | "start": "node mock-server/app.js", 16 | "test": "gulp test" 17 | }, 18 | "dependencies": { 19 | "body-parser": "^1.13.1", 20 | "compression": "^1.5.0", 21 | "connect-modrewrite": "^0.8.1", 22 | "cors": "^2.7.1", 23 | "express": "^4.13.0", 24 | "morgan": "^1.6.0" 25 | }, 26 | "devDependencies": { 27 | "browser-sync": "^2.7.12", 28 | "chai": "^3.0.0", 29 | "chai-as-promised": "^5.1.0", 30 | "chalk": "^1.0.0", 31 | "dateformat": "^1.0.11", 32 | "debug": "^2.2.0", 33 | "del": "^1.2.0", 34 | "glob": "^5.0.10", 35 | "gulp": "^3.9.0", 36 | "gulp-angular-templatecache": "^1.6.0", 37 | "gulp-autoprefixer": "^2.3.1", 38 | "gulp-bump": "^0.3.1", 39 | "gulp-bytediff": "^0.2.1", 40 | "gulp-concat": "^2.6.0", 41 | "gulp-csso": "^1.0.0", 42 | "gulp-filter": "^2.0.2", 43 | "gulp-header": "^1.2.2", 44 | "gulp-if": "^1.2.5", 45 | "gulp-imagemin": "^2.3.0", 46 | "gulp-inject": "^1.3.1", 47 | "gulp-jscs": "^1.6.0", 48 | "gulp-jshint": "^1.11.0", 49 | "gulp-load-plugins": "^1.0.0-rc.1", 50 | "gulp-minify-html": "^1.0.3", 51 | "gulp-ng-annotate": "^1.0.0", 52 | "gulp-nodemon": "^2.0.3", 53 | "gulp-plumber": "^1.0.1", 54 | "gulp-print": "^1.1.0", 55 | "gulp-rev": "^5.0.1", 56 | "gulp-rev-replace": "^0.4.2", 57 | "gulp-sass": "^2.0.1", 58 | "gulp-sourcemaps": "^1.5.2", 59 | "gulp-task-listing": "^1.0.1", 60 | "gulp-uglify": "^1.2.0", 61 | "gulp-useref": "^1.2.0", 62 | "gulp-util": "^3.0.6", 63 | "gulp-watch": "^4.2.4", 64 | "jshint-stylish": "^2.0.1", 65 | "karma": "^0.12.37", 66 | "karma-chai": "^0.1.0", 67 | "karma-chai-sinon": "^0.1.5", 68 | "karma-chrome-launcher": "^0.2.0", 69 | "karma-coverage": "^0.4.2", 70 | "karma-firefox-launcher": "^0.1.6", 71 | "karma-growl-reporter": "^0.1.1", 72 | "karma-mocha": "^0.2.0", 73 | "karma-phantomjs-launcher": "^0.2.0", 74 | "karma-safari-launcher": "^0.1.1", 75 | "karma-sinon": "^1.0.4", 76 | "lodash": "^3.9.3", 77 | "method-override": "^2.3.3", 78 | "minimist": "^1.1.1", 79 | "mocha": "^2.2.5", 80 | "node-notifier": "^4.2.3", 81 | "phantomjs": "^1.9.17", 82 | "plato": "^1.5.0", 83 | "q": "^1.4.1", 84 | "sinon": "^1.15.3", 85 | "sinon-chai": "^2.8.0", 86 | "yargs": "^3.12.0" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/barchart/barchart.directive.js: -------------------------------------------------------------------------------- 1 | /* 2 | A directive to draw bar charts using D3. The directive itself takes 3 | care of the Angular side of the instrumentation, the D3 rendering 4 | is handled by the BarChart class provided by the barchart factory. 5 | 6 | Note: the prefix "ptrn" in "ptrn-barchart" stands for "patterns". 7 | You should choose a unique and descriptive prefix for your directives. 8 | 9 | Usage: 10 | 11 | 12 | Chart data should be in the following format: 13 | 14 | [ 15 | { 16 | 'key': 'US Stocks', 17 | 'value': 90, 18 | }, 19 | { 20 | 'key': 'Cash', 21 | 'value': 10, 22 | }, 23 | ... 24 | ]; 25 | */ 26 | 27 | (function () { 28 | 'use strict'; 29 | 30 | angular 31 | .module('app.barchart') 32 | .directive('ptrnBarchart', directiveFunction); 33 | 34 | 35 | // ----- directiveFunction ----- 36 | directiveFunction.$inject = ['BarChart', '$window', '_']; 37 | 38 | /* @ngInject */ 39 | function directiveFunction(BarChart, $window, _) { 40 | 41 | var directive = { 42 | link: link, 43 | restrict: 'E', 44 | scope: { 45 | chartdata: '=' 46 | } 47 | }; 48 | return directive; 49 | 50 | function link(scope, element) { 51 | var tableRowHeight = 37; // TODO: take out hard coding 52 | 53 | // initialize the chart 54 | var svgElement = element.html('').children()[0]; 55 | var barChart = new BarChart(svgElement); 56 | barChart.barHeight(tableRowHeight); 57 | 58 | // Redraw whenever chartdata changes 59 | scope.$watch('chartdata', drawChart); 60 | 61 | // Redraw whenever window resizes, adding some debounce 62 | var debouncedDrawChart = _.debounce(drawChart, 250); 63 | angular.element($window).on('resize', debouncedDrawChart); 64 | 65 | // Remove the redraw handler when the scope is destroyed 66 | // This prevents redrawing when the view containing the barchart is destroyed 67 | scope.$on('$destroy', function() { 68 | if (debouncedDrawChart) { 69 | angular.element($window).off('resize', debouncedDrawChart); 70 | debouncedDrawChart = undefined; 71 | } 72 | }); 73 | 74 | function drawChart() { 75 | var chartdata = scope.chartdata; 76 | 77 | // This can happen when the server has not yet returned the chartdata 78 | if (!chartdata) { return; } 79 | 80 | barChart 81 | .width(element[0].offsetWidth) 82 | .draw(chartdata); 83 | } 84 | } 85 | } 86 | 87 | })(); 88 | -------------------------------------------------------------------------------- /src/components/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Asset Allocation

4 | 5 |
6 | 7 | 34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
Asset ClassMarket ValueAllocationReturn
{{a.asset_class}}{{a.market_value | currency:undefined:0}}{{a.percent_allocation | percentage:0}}{{a.percent_return | percentage:2}}
62 |
63 |
64 |
65 |
66 | 67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (config) { 6 | 7 | config.set({ 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: './', 10 | 11 | // frameworks to use 12 | // some available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai', 'sinon', 'chai-sinon'], 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-animate/angular-animate.js', 19 | 'bower_components/angular-bootstrap/ui-bootstrap.js', 20 | 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', 21 | 'bower_components/angular-sanitize/angular-sanitize.js', 22 | 'bower_components/angular-toastr/dist/angular-toastr.js', 23 | 'bower_components/angular-toastr/dist/angular-toastr.tpls.js', 24 | 'bower_components/angular-ui-router/release/angular-ui-router.js', 25 | 'bower_components/angular-mocks/angular-mocks.js', 26 | 'bower_components/bardjs/dist/bard.js', 27 | 'bower_components/d3/d3.js', 28 | 'bower_components/lodash/lodash.js', 29 | 30 | 'test/helpers/*.js', 31 | 'src/**/*.module.js', 32 | 'src/**/*.js', 33 | '.tmp/templates.js', 34 | 'test/**/*.spec.js' 35 | ], 36 | 37 | // list of files to exclude 38 | exclude: [], 39 | 40 | proxies: { 41 | '/': 'http://localhost:8888/' 42 | }, 43 | 44 | // preprocess matching files before serving them to the browser 45 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 46 | preprocessors: { 47 | 'src/**/*.js': ['coverage'] 48 | }, 49 | 50 | // test results reporter to use 51 | // possible values: 'dots', 'progress', 'coverage' 52 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 53 | reporters: ['progress', 'coverage'], 54 | 55 | coverageReporter: { 56 | dir: 'report/coverage', 57 | reporters: [ 58 | // reporters not supporting the `file` property 59 | {type: 'html', subdir: 'report-html'}, 60 | {type: 'lcov', subdir: 'report-lcov'}, 61 | 62 | // reporters supporting the `file` property, use `subdir` to directly 63 | // output them in the `dir` directory. 64 | // omit `file` to output to the console. 65 | // {type: 'cobertura', subdir: '.', file: 'cobertura.txt'}, 66 | // {type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt'}, 67 | // {type: 'teamcity', subdir: '.', file: 'teamcity.txt'}, 68 | // {type: 'text'}, subdir: '.', file: 'text.txt'}, 69 | 70 | {type: 'text-summary'} //, subdir: '.', file: 'text-summary.txt'} 71 | ] 72 | }, 73 | 74 | // web server port 75 | port: 9876, 76 | 77 | // enable / disable colors in the output (reporters and logs) 78 | colors: true, 79 | 80 | // level of logging 81 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || 82 | // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 83 | logLevel: config.LOG_INFO, 84 | 85 | // enable / disable watching file and executing tests whenever any file changes 86 | autoWatch: true, 87 | 88 | // start these browsers 89 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 90 | // browsers: ['Chrome', 'ChromeCanary', 'FirefoxAurora', 'Safari', 'PhantomJS'], 91 | browsers: ['Chrome'], 92 | 93 | // Continuous Integration mode 94 | // if true, Karma captures browsers, runs the tests and exits 95 | singleRun: false 96 | } 97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /gulp-tasks/serve.js: -------------------------------------------------------------------------------- 1 | var browserSync = require('browser-sync'); 2 | var gulp = require('gulp'); 3 | var watch = require('gulp-watch'); 4 | var modRewrite = require('connect-modrewrite'); 5 | 6 | module.exports = function (config) { 7 | 8 | var args = config.args, 9 | log = config.log, 10 | notify = config.notify, 11 | $ = config.$; 12 | 13 | gulp.task('serve-dev', ['inject'], function () { 14 | serve(true /*isDev*/); 15 | }); 16 | 17 | /** 18 | * serve the build environment 19 | * --debug-brk or --debug 20 | * --nosync 21 | */ 22 | gulp.task('serve-build', ['build'], function () { 23 | var msg = { 24 | title: 'gulp build', 25 | subtitle: 'Deployed to the build folder', 26 | message: 'Running `gulp serve-build`' 27 | }; 28 | log(msg); 29 | notify(msg); 30 | serve(false /*isDev*/); 31 | }); 32 | 33 | function serve(isDev) { 34 | var debug = args.debug || args.debugBrk; 35 | var exec; 36 | var nodeOptions = { 37 | script: './mock-server/app.js', 38 | delayTime: 1, 39 | env: { 40 | 'PORT': config.proxyPort, 41 | 'NODE_ENV': isDev ? 'dev' : 'build' 42 | }, 43 | watch: ['./mock-server/'] 44 | }; 45 | 46 | if (debug) { 47 | log('Running node-inspector. Browse to http://localhost:8080/debug?port=5858'); 48 | exec = require('child_process').exec; 49 | exec('node-inspector'); 50 | nodeOptions.nodeArgs = ['--debug=5858']; 51 | } 52 | 53 | return $.nodemon(nodeOptions) 54 | .on('restart', ['vet'], function (ev) { 55 | log('*** nodemon restarted'); 56 | log('files changed:\n' + ev); 57 | setTimeout(function () { 58 | browserSync.notify('reloading now ...'); 59 | browserSync.reload({stream: false}); 60 | }, config.browserReloadDelay); 61 | }) 62 | .on('start', function () { 63 | log('*** nodemon started'); 64 | startBrowserSync(isDev); 65 | }) 66 | .on('crash', function () { 67 | log('*** nodemon crashed: script crashed for some reason'); 68 | }) 69 | .on('exit', function () { 70 | log('*** nodemon exited cleanly'); 71 | }); 72 | } 73 | 74 | /** 75 | * Start BrowserSync 76 | * --nosync will avoid browserSync 77 | */ 78 | function startBrowserSync(isDev) { 79 | 80 | if (args.nosync || browserSync.active) { 81 | return; 82 | } 83 | 84 | log('Starting BrowserSync on port ' + config.port); 85 | 86 | // If build: watches the files, builds, and restarts browser-sync. 87 | // If dev: watches sass, compiles it to css, browser-sync handles reload 88 | var files = [].concat(config.js, config.html, config.sass); 89 | if (isDev) { 90 | watch(files, function(){ gulp.start('inject', browserSync.reload); }); 91 | } else { 92 | watch(files, function(){ gulp.start('optimize', browserSync.reload); }); 93 | } 94 | 95 | var options = { 96 | server: { 97 | baseDir: isDev ? config.tempDir : config.buildDir, 98 | routes: isDev ? { 99 | '/bower_components': './bower_components', 100 | '/fonts': './bower_components/bootstrap-sass/assets/fonts', 101 | '/src': config.sourceDir, 102 | '/images': config.sourceDir + 'images', 103 | '/.tmp': config.tempDir 104 | } : {}, 105 | middleware: [ 106 | modRewrite([ '!\\.\\w+$ /index.html [L]' ]) 107 | ] 108 | }, 109 | port: config.port, 110 | ghostMode: { 111 | clicks: true, 112 | location: false, 113 | forms: true, 114 | scroll: true 115 | }, 116 | injectChanges: true, 117 | logFileChanges: true, 118 | logLevel: 'info', 119 | logPrefix: 'angular-patterns', 120 | notify: true, 121 | reloadDelay: 0 //1000 122 | }; 123 | 124 | browserSync(options); 125 | } 126 | 127 | }; 128 | 129 | -------------------------------------------------------------------------------- /src/components/barchart/barchart.factory.js: -------------------------------------------------------------------------------- 1 | /* 2 | The barchart factory simply returns a class called BarChart 3 | that is responsible for rendering the chart using D3. It knows 4 | nothing about AngularJS. 5 | */ 6 | 7 | (function () { 8 | 'use strict'; 9 | 10 | angular 11 | .module('app.barchart') 12 | .factory('BarChart', factoryFunction); 13 | 14 | factoryFunction.$inject = ['d3']; 15 | 16 | /* @ngInject */ 17 | function factoryFunction(d3) { 18 | 19 | function BarChart(svgElement) { 20 | this.base = d3.select(svgElement); 21 | 22 | this.margin = {top: 0, right: 7, bottom: 30, left: 5}; 23 | this.axisMargin = 5; 24 | 25 | this.x = d3.scale.linear(); 26 | 27 | this.y = d3.scale.ordinal(); 28 | 29 | this.xAxis = d3.svg.axis() 30 | .scale(this.x) 31 | .orient('bottom'); 32 | 33 | // chart base 34 | this.base 35 | .attr('class', 'chart'); 36 | 37 | // x-axis base 38 | this.xAxisBase = this.base.append('g') 39 | .attr('class', 'x axis'); 40 | 41 | // plot base 42 | this.plotBase = this.base.append('g') 43 | .attr('class', 'plot') 44 | .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); 45 | } 46 | 47 | BarChart.prototype.width = function(newWidth) { 48 | this.w = newWidth; 49 | this.plotWidth = this.w - this.margin.left - this.margin.right; 50 | this.base.attr('width', this.w); 51 | this.x.range([0, this.plotWidth]); 52 | return this; 53 | }; 54 | 55 | BarChart.prototype.barHeight = function(newBarHeight) { 56 | this.bh = newBarHeight; 57 | return this; 58 | }; 59 | 60 | BarChart.prototype.draw = function(data) { 61 | // Compute y-dimensions based on bar height 62 | this.plotHeight = this.bh * data.length; 63 | this.h = this.plotHeight + this.margin.top + this.margin.bottom; 64 | this.base.attr('height', this.h); 65 | this.y.rangeBands([0, this.plotHeight], 0.05, 0); 66 | this.xAxisBase.attr( 67 | 'transform', 68 | 'translate(' + this.margin.left + ',' + (this.margin.top + this.plotHeight + this.axisMargin) + ')' 69 | ); 70 | 71 | // Set the domains for the scales from the supplied data 72 | this.x.domain([0, d3.max(data.map(function(d) { return d.value; }))]); 73 | this.y.domain(data.map(function(d) { return d.key; })); 74 | 75 | // Draw the axes 76 | this.xAxis.tickValues(this.x.domain()); 77 | this.xAxisBase.call(this.xAxis); 78 | 79 | // Create the 'update selection' by selecting the bars and joining with data. 80 | // Update selection contains the DOM elements that were successfully bound to data 81 | // plus references to enter and exit selections. 82 | var updateSelection = this.plotBase.selectAll('.bar') 83 | .data(data, function(d) { return d.key; }); 84 | 85 | // Remove the exiting bars (this is the 'exit selection') 86 | updateSelection.exit() 87 | .remove(); 88 | 89 | // Get the 'enter selection' 90 | // Contains placeholder DOM nodes for each data element that was not bound 91 | var enterSelection = updateSelection.enter(); 92 | 93 | // Add a group for each entering element - these are the entering bars 94 | var barsEnter = enterSelection 95 | .append('g') 96 | .attr('class', 'bar'); 97 | 98 | // Add the rectangle for the bar 99 | barsEnter 100 | .append('rect') 101 | .attr('x', 0) 102 | .attr('width', 0) 103 | .attr('height', this.y.rangeBand()); 104 | 105 | // Draw the bars 106 | var self = this; 107 | updateSelection.select('rect') 108 | .attr('x', 0) 109 | .attr('y', function(d) { return self.y(d.key); }) 110 | .attr('height', this.y.rangeBand()) 111 | .transition() 112 | .duration(1000) 113 | .attr('width', function(d) { return self.x(d.value); }); 114 | }; 115 | 116 | return BarChart; 117 | } 118 | })(); 119 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Closely follows https://github.com/jshint/jshint/blob/master/examples/.jshintrc 3 | 4 | // ----- Enforcing options ----- 5 | 6 | // true: Prohibit bitwise operators (&, |, ^, etc.) 7 | "bitwise": true, 8 | 9 | // true: Identifiers must be in camelCase 10 | // Allow for snake_case identifiers 11 | "camelcase": false, 12 | 13 | // true: Require {} for every new block or scope 14 | "curly": true, 15 | 16 | // true: Require triple equals (===) for comparison 17 | "eqeqeq": true, 18 | 19 | // true: Require adherance to ECMAScript 3 specification 20 | // Set to true if you need your program to be executable in older browsers such as IE 6-9 21 | "es3": true, 22 | 23 | // true: Require filtering for..in loops with obj.hasOwnProperty() 24 | "forin": true, 25 | 26 | // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 27 | "freeze": true, 28 | 29 | // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 30 | "immed": true, 31 | 32 | // {int} Number of spaces to use for indentation 33 | "indent": 4, 34 | 35 | // true: Require variables/functions to be defined before being used 36 | // nofunc: Allow use of a function before it is defined (see https://github.com/johnpapa/angularjs-styleguide#style-y034) 37 | "latedef": "nofunc", 38 | 39 | // {int} Max cyclomatic complexity per function 40 | "maxcomplexity": 10, 41 | 42 | // {int} Max depth of nested blocks (within functions) 43 | "maxdepth": 5, 44 | 45 | // {int} Maximum error before stopping (default is 50) 46 | "maxerr": 50, 47 | 48 | // {int} Max number of characters per line 49 | "maxlen": 120, 50 | 51 | // {int} Max number of formal params allowed per function 52 | "maxparams": 10, 53 | 54 | // {int} Max number statements per function 55 | "maxstatements": 50, 56 | 57 | // true: Require capitalization of all constructor functions e.g. `new F()` 58 | "newcap": true, 59 | 60 | // true: Prohibit use of `arguments.caller` and `arguments.callee` 61 | "noarg": true, 62 | 63 | // true: prohibits the use of the comma operator 64 | // This is triggering false positives (see https://github.com/jshint/jshint/issues/2044) 65 | "nocomma": false, 66 | 67 | // true: Prohibit use of empty blocks 68 | "noempty": true, 69 | 70 | // true: Prohibit "non-breaking whitespace" characters 71 | "nonbsp": true, 72 | 73 | // true: Prohibit use of constructors for side-effects (without assignment) 74 | "nonew": true, 75 | 76 | // true: Prohibit use of `++` & `--` 77 | "plusplus": false, 78 | 79 | // Quotation mark consistency: 80 | // false : do nothing (default) 81 | // true : ensure whatever is used is consistent 82 | // "single" : require single quotes 83 | // "double" : require double quotes 84 | "quotmark": "single", 85 | 86 | // true: prohibits the use of the grouping operator for single-expression statements 87 | "singleGroups": false, 88 | 89 | // true: Requires all functions run in ES5 Strict Mode 90 | "strict": true, 91 | 92 | // true: Require all non-global variables to be declared (prevents global leaks) 93 | "undef": true, 94 | 95 | // true: Require all defined variables be used 96 | "unused": true, 97 | 98 | // ----- Relaxing options ----- 99 | 100 | // true: Tolerate Automatic Semicolon Insertion (no semicolons) 101 | "asi": false, 102 | 103 | // true: Tolerate assignments where comparisons would be expected 104 | "boss": false, 105 | 106 | // true: Allow debugger statements e.g. browser breakpoints 107 | "debug": false, 108 | 109 | // true: Tolerate use of `== null` 110 | "eqnull": false, 111 | 112 | // true: Allow ES5 syntax (ex: getters and setters) 113 | // Setting explicitly to true throws a warning: "ES5 option is now set per default" 114 | // "es5": true, 115 | 116 | // true: Allow ES.next (ES6) syntax (ex: `const`) 117 | "esnext": false, 118 | 119 | // true: Tolerate use of `eval` and `new Function()` 120 | "evil": false, 121 | 122 | // true: Tolerate `ExpressionStatement` as Programs 123 | "expr": false, 124 | 125 | // true: Tolerate defining variables inside control statements 126 | "funcscope": false, 127 | 128 | // true: Allow global "use strict" (also enables 'strict') 129 | "globalstrict": false, 130 | 131 | // true: Tolerate using the `__iterator__` property 132 | "iterator": false, 133 | 134 | // true: Tolerate omitting a semicolon for the last statement of a 1-line block 135 | "lastsemic": false, 136 | 137 | // true: Tolerate possibly unsafe line breakings 138 | "laxbreak": false, 139 | 140 | // true: Tolerate comma-first style coding 141 | "laxcomma": false, 142 | 143 | // true: Tolerate functions being defined in loops 144 | "loopfunc": false, 145 | 146 | // true: Allow Mozilla specific syntax (extends and overrides esnext features) 147 | // (ex: `for each`, multiple try/catch, function expression…) 148 | "moz": false, 149 | 150 | // true: Tolerate multi-line strings 151 | "multistr": false, 152 | 153 | // true: Tolerate invalid typeof operator values 154 | "notypeof": false, 155 | 156 | // true: Tolerate generator functions with no yield statement in them 157 | "noyield": false, 158 | 159 | // true: Tolerate using the `__proto__` property 160 | "proto": false, 161 | 162 | // true: Tolerate script-targeted URLs 163 | "scripturl": false, 164 | 165 | // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 166 | "shadow": false, 167 | 168 | // true: Tolerate using `[]` notation when it can still be expressed in dot notation 169 | "sub": false, 170 | 171 | // true: Tolerate `new function () { ... };` and `new Object;` 172 | "supernew": false, 173 | 174 | // true: Tolerate using this in a non-constructor function 175 | "validthis": false, 176 | 177 | // true: Suppresses warnings about the use of the with statement 178 | "withstmt": false, 179 | 180 | // ----- Environments ----- 181 | 182 | // Web Browser (window, document, etc) 183 | "browser": true, 184 | 185 | // Browserify (node.js code in the browser) 186 | "browserify": false, 187 | 188 | // CouchDB 189 | "couch": false, 190 | 191 | // Development/debugging (alert, confirm, etc) 192 | "devel": false, 193 | 194 | // Dojo Toolkit 195 | "dojo": false, 196 | 197 | // Jasmine 198 | "jasmine": false, 199 | 200 | // jQuery 201 | "jquery": false, 202 | 203 | // Mocha 204 | "mocha": false, 205 | 206 | // MooTools 207 | "mootools": false, 208 | 209 | // Node.js 210 | "node": false, 211 | 212 | // Widely adopted globals (escape, unescape, etc) 213 | "nonstandard": false, 214 | 215 | // PhantomJS 216 | "phantom": false, 217 | 218 | // Prototype and Scriptaculous 219 | "prototypejs": false, 220 | 221 | // QUnit 222 | "qunit": false, 223 | 224 | // Rhino 225 | "rhino": false, 226 | 227 | // ShellJS 228 | "shelljs": false, 229 | 230 | // defines globals for typed array constructors 231 | "typed": false, 232 | 233 | // Web Workers 234 | "worker": false, 235 | 236 | // Windows Scripting Host 237 | "wsh": false, 238 | 239 | // Yahoo User Interface 240 | "yui": false, 241 | 242 | // ----- Globals ----- 243 | // false: variable as read-only 244 | "globals": { 245 | "angular": false 246 | } 247 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Patterns 2 | 3 | ## This sample has been depricated! 4 | Please use the following projects as better examples of using the [Angular Template](https://github.com/archfirst/angular-template): 5 | 6 | - [Manage My Money Client](https://github.com/archfirst/manage-my-money-client) (AngularJS best practices) 7 | - [Manage My Money Server](https://github.com/archfirst/manage-my-money-server) (Node.js, REST and Bookshelf best practices) 8 | 9 | ## Description 10 | This project illustrate AngularJS patterns and best practices using a simple application. It uses the [angular-template](https://github.com/archfirst/angular-template) project as a base to provide the basic application structure and build system. 11 | 12 | ## Requirements 13 | - Install Node 14 | - on OSX, install [home brew](http://brew.sh/) and type `brew install node` 15 | - on Windows, use the installer available at [nodejs.org](http://nodejs.org/) 16 | - Open terminal 17 | - Type `npm install -g node-inspector bower gulp` 18 | 19 | ## Quick Start 20 | Clone this repo and run the content locally: 21 | ```bash 22 | $ npm install 23 | $ gulp serve-dev 24 | ``` 25 | - `npm install` will install the required node libraries under `node_modules` and then call `bower install` which will install the required client-side libraries under `bower_components`. 26 | - `gulp serve-dev` will serve up the Angular application in a browser window. It is designed for an efficient development process. As you make changes to the code, the browser will update to reflect the changes immediately. 27 | 28 | When you are ready to build the application for production, run the following command: 29 | ```bash 30 | $ gulp serve-build 31 | ``` 32 | 33 | This will build a production-ready package in the `/build` folder. 34 | 35 | ## Folder Structure 36 | 37 | The folder structure is somewhat simplified and flatter compared to John Papa's [Gulp Patterns](https://github.com/johnpapa/gulp-patterns) project. The description below includes reasons for some of my customizations. 38 | 39 | ### Highest Level Structure 40 | 41 | ``` 42 | /bower_components 43 | /build 44 | /mock-server 45 | /node_modules 46 | /src 47 | /test 48 | ``` 49 | 50 | - `bower_components:` Bower components downloaded by `bower install` (do not check in) 51 | 52 | - `build:` Production build (do not check in) 53 | 54 | - `mock-server:` Used to serve the application during development and also to provide mock data. The real server is intended to be developed as a separate project utilizing best practices for the chosen server-side technology (see the [Node REST Template](https://github.com/archfirst/node-rest-template) for an example). This approach decouples client and server development so that they can progress independently and forces them to define tighter APIs. 55 | 56 | - `node_modules:` Node.js modules downloaded by `npm install` (do not check in) 57 | 58 | - `src:` contains all the client source files including HTML, styles (in SASS format), JavaScript and images 59 | 60 | - `test:` contains client tests. This folder is intentionally kept separate from client source because I expect many different types of tests in this folder (unit, integration, acceptance). On real projects, the number of test files can easily exceed the number of source files, hence I like to keep the clutter away from the real source - just my preference! 61 | 62 | ### Source Folder Structure 63 | 64 | ``` 65 | /src 66 | /components 67 | /core 68 | /framework 69 | /images 70 | /app.module.js 71 | /app.scss 72 | /index.html 73 | ``` 74 | 75 | The `src` folder contains only the source for the AngularJS client application. It treats all 3 web technologies (HTML, CSS and JavaScript) as a first class citizens and arranges them into logical modules. At the highest level you will find the main html, css (well scss) and js files: 76 | 77 | - `index.html` 78 | - `app.scss` 79 | - `app.module.js` 80 | 81 | Below this level you will find various folders that arrange the application's functionality into logical modules. 82 | 83 | - `framework:` Container for reusable services such as logging, exception handling, routing, security, local storage etc. These services are expected to work out-of-the-box without any changes for most applications. The template provides sample implementations for the first three. (This folder is called `blocks` in the gulp-patterns project.) 84 | 85 | - `core:` Contains functionality that is shared across the application and will probably need customization for a specific application. This includes directives, filters and services and styles common to the entire application. 86 | 87 | - `components:` Contains all the components of the application. We recommend thinking of an Angular application as a tree of components, starting with the `app` component as the root of this tree. 88 | 89 | - `images:` Images used in the application. 90 | 91 | ## Tasks 92 | 93 | ### Task Listing 94 | 95 | - `gulp help` 96 | 97 | Displays all of the available gulp tasks. 98 | 99 | ### Code Analysis 100 | 101 | - `gulp vet` 102 | 103 | Performs static code analysis on all javascript files. Runs jshint and jscs. 104 | 105 | - `gulp vet --verbose` 106 | 107 | Displays all files affected and extended information about the code analysis. 108 | 109 | - `gulp plato` 110 | 111 | Performs code analysis using plato on all javascript files. Plato generates a report in the reports folder. 112 | 113 | ### Testing 114 | 115 | - `gulp serve-specs` 116 | 117 | Serves and browses to the spec runner html page and runs the unit tests in it. Injects any changes on the fly and re runs the tests. Quick and easy view of tests as an alternative to terminal via `gulp test`. 118 | 119 | - `gulp test` 120 | 121 | Runs all unit tests using karma runner, mocha, chai and sinon with phantomjs. Depends on vet task, for code analysis. 122 | 123 | - `gulp test --startServers` 124 | 125 | Runs all unit tests and midway tests. Cranks up a second node process to run a server for the midway tests to hit a web api. 126 | 127 | - `gulp autotest` 128 | 129 | Runs a watch to run all unit tests. 130 | 131 | - `gulp autotest --startServers` 132 | 133 | Runs a watch to run all unit tests and midway tests. Cranks up a second node process to run a server for the midway tests to hit a web api. 134 | 135 | ### Cleaning Up 136 | 137 | - `gulp clean` 138 | 139 | Remove all files from the build and temp folders 140 | 141 | - `gulp clean-images` 142 | 143 | Remove all images from the build folder 144 | 145 | - `gulp clean-code` 146 | 147 | Remove all javascript and html from the build folder 148 | 149 | - `gulp clean-fonts` 150 | 151 | Remove all fonts from the build folder 152 | 153 | - `gulp clean-styles` 154 | 155 | Remove all styles from the build folder 156 | 157 | ### Fonts and Images 158 | 159 | - `gulp fonts` 160 | 161 | Copy all fonts from source to the build folder 162 | 163 | - `gulp images` 164 | 165 | Copy all images from source to the build folder 166 | 167 | ### Styles 168 | 169 | - `gulp styles` 170 | 171 | Compile less files to CSS, add vendor prefixes, and copy to the build folder 172 | 173 | ### Angular HTML Templates 174 | 175 | - `gulp templatecache` 176 | 177 | Create an Angular module that adds all HTML templates to Angular's $templateCache. This pre-fetches all HTML templates saving XHR calls for the HTML. 178 | 179 | - `gulp templatecache --verbose` 180 | 181 | Displays all files affected by the task. 182 | 183 | ### Serving Development Code 184 | 185 | - `gulp serve-dev` 186 | 187 | Serves the development code and launches it in a browser. The goal of building for development is to do it as fast as possible, to keep development moving efficiently. This task serves all code from the source folders and compiles less to css in a temp folder. 188 | 189 | - `gulp serve-dev --nosync` 190 | 191 | Serves the development code without launching the browser. 192 | 193 | - `gulp serve-dev --debug` 194 | 195 | Launch debugger with node-inspector. 196 | 197 | - `gulp serve-dev --debug-brk` 198 | 199 | Launch debugger and break on 1st line with node-inspector. 200 | 201 | ### Building Production Code 202 | 203 | - `gulp html` 204 | 205 | Optimize all javascript and styles, move to a build folder, and inject them into the new index.html 206 | 207 | - `gulp build` 208 | 209 | Copies all fonts, copies images and runs `gulp html` to build the production code to the build folder. 210 | 211 | ### Serving Production Code 212 | 213 | - `gulp serve-build` 214 | 215 | Serve the optimized code from the build folder and launch it in a browser. 216 | 217 | - `gulp serve-build --nosync` 218 | 219 | Serve the optimized code from the build folder and manually launch the browser. 220 | 221 | - `gulp serve-build --debug` 222 | 223 | Launch debugger with node-inspector. 224 | 225 | - `gulp serve-build --debug-brk` 226 | 227 | Launch debugger and break on 1st line with node-inspector. 228 | --------------------------------------------------------------------------------