├── 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 |
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('