├── .gitignore ├── README.md ├── hot-towel-done ├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── README.md ├── bower.json ├── gulp.config.js ├── gulp.png ├── gulpfile.js ├── karma.conf.js ├── package.json └── src │ ├── client │ ├── app │ │ ├── admin │ │ │ ├── admin.controller.js │ │ │ ├── admin.controller.spec.js │ │ │ ├── admin.html │ │ │ ├── admin.module.js │ │ │ ├── admin.route.js │ │ │ ├── admin.route.spec.js │ │ │ └── i18n │ │ │ │ ├── en.json │ │ │ │ └── es.json │ │ ├── app.module.js │ │ ├── blocks │ │ │ ├── exception │ │ │ │ ├── exception-handler.provider.js │ │ │ │ ├── exception-handler.provider.spec.js │ │ │ │ ├── exception.js │ │ │ │ └── exception.module.js │ │ │ ├── logger │ │ │ │ ├── logger.js │ │ │ │ └── logger.module.js │ │ │ └── router │ │ │ │ ├── router-helper.provider.js │ │ │ │ └── router.module.js │ │ ├── core │ │ │ ├── 404.html │ │ │ ├── config.js │ │ │ ├── constants.js │ │ │ ├── core.module.js │ │ │ ├── core.route.js │ │ │ ├── core.route.spec.js │ │ │ ├── dataservice.js │ │ │ └── i18n │ │ │ │ ├── en.json │ │ │ │ └── es.json │ │ ├── dashboard │ │ │ ├── dashboard.controller.js │ │ │ ├── dashboard.controller.spec.js │ │ │ ├── dashboard.html │ │ │ ├── dashboard.module.js │ │ │ ├── dashboard.route.js │ │ │ ├── dashboard.route.spec.js │ │ │ └── i18n │ │ │ │ ├── en.json │ │ │ │ └── es.json │ │ ├── i18n │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── layout │ │ │ ├── ht-sidebar.directive.js │ │ │ ├── ht-sidebar.directive.spec.js │ │ │ ├── ht-top-nav.directive.js │ │ │ ├── ht-top-nav.html │ │ │ ├── layout.module.js │ │ │ ├── shell.controller.js │ │ │ ├── shell.controller.spec.js │ │ │ ├── shell.html │ │ │ ├── sidebar.controller.js │ │ │ ├── sidebar.controller.spec.js │ │ │ └── sidebar.html │ │ └── widgets │ │ │ ├── ht-img-person.directive.js │ │ │ ├── ht-widget-header.directive.js │ │ │ ├── ht-widget-towel.directive.js │ │ │ ├── widget-header.html │ │ │ ├── widget-ht-towel.html │ │ │ └── widgets.module.js │ ├── images │ │ ├── AngularJS-small.png │ │ ├── busy.gif │ │ ├── gulp-tiny.png │ │ └── icon.png │ ├── index.html │ ├── specs.html │ ├── styles │ │ └── styles.less │ └── test-helpers │ │ ├── bind-polyfill.js │ │ └── mock-data.js │ └── server │ ├── app.js │ ├── data.js │ ├── favicon.ico │ ├── routes.js │ └── utils │ └── 404.js └── hot-towel ├── .bowerrc ├── .gitignore ├── .jscsrc ├── .jshintrc ├── README.md ├── bower.json ├── gulp.config.js ├── gulp.png ├── gulpfile.js ├── karma.conf.js ├── package.json └── src ├── client ├── app │ ├── admin │ │ ├── admin.controller.js │ │ ├── admin.controller.spec.js │ │ ├── admin.html │ │ ├── admin.module.js │ │ ├── admin.route.js │ │ └── admin.route.spec.js │ ├── app.module.js │ ├── blocks │ │ ├── exception │ │ │ ├── exception-handler.provider.js │ │ │ ├── exception-handler.provider.spec.js │ │ │ ├── exception.js │ │ │ └── exception.module.js │ │ ├── logger │ │ │ ├── logger.js │ │ │ └── logger.module.js │ │ └── router │ │ │ ├── router-helper.provider.js │ │ │ └── router.module.js │ ├── core │ │ ├── 404.html │ │ ├── config.js │ │ ├── constants.js │ │ ├── core.module.js │ │ ├── core.route.js │ │ ├── core.route.spec.js │ │ └── dataservice.js │ ├── dashboard │ │ ├── dashboard.controller.js │ │ ├── dashboard.controller.spec.js │ │ ├── dashboard.html │ │ ├── dashboard.module.js │ │ ├── dashboard.route.js │ │ └── dashboard.route.spec.js │ ├── layout │ │ ├── ht-sidebar.directive.js │ │ ├── ht-sidebar.directive.spec.js │ │ ├── ht-top-nav.directive.js │ │ ├── ht-top-nav.html │ │ ├── layout.module.js │ │ ├── shell.controller.js │ │ ├── shell.controller.spec.js │ │ ├── shell.html │ │ ├── sidebar.controller.js │ │ ├── sidebar.controller.spec.js │ │ └── sidebar.html │ └── widgets │ │ ├── ht-img-person.directive.js │ │ ├── ht-widget-header.directive.js │ │ ├── ht-widget-towel.directive.js │ │ ├── widget-header.html │ │ ├── widget-ht-towel.html │ │ └── widgets.module.js ├── images │ ├── AngularJS-small.png │ ├── busy.gif │ ├── gulp-tiny.png │ └── icon.png ├── index.html ├── specs.html ├── styles │ └── styles.less └── test-helpers │ ├── bind-polyfill.js │ └── mock-data.js └── server ├── app.js ├── data.js ├── favicon.ico ├── routes.js └── utils └── 404.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 3 | node_modules/ 4 | bower_components/ 5 | 6 | # other 7 | .tmp 8 | /hot-towel/report/ 9 | /hot-towel-done/report/ 10 | hot-towel/report/ 11 | hot-towel-done/report/ 12 | /hot-towel/.editorconfig 13 | /hot-towel-done/.editorconfig -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-i18n 2 | Internationalization in Angular 3 | 4 | - `/hot-towel/` is a clean slate, generated hot-towel Angular SPA that can be used to follow along with my course: [Play by Play: Angular Internationalization with John Papa and Brian Clark](http://jpapa.me/pbpi18nclark) 5 | - `/hot-towel-done/` is the completed hot-towel Angular SPA that resulted from the Play by Play course. 6 | 7 | > Instructions to run each project are in their corresponding README.md file. 8 | -------------------------------------------------------------------------------- /hot-towel-done/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "scripts": { 4 | "postinstall": "gulp wiredep" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /hot-towel-done/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /hot-towel-done/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 3 | node_modules/ 4 | bower_components/ 5 | 6 | # other 7 | .tmp 8 | /hot-towel-done/report/ 9 | /build/ -------------------------------------------------------------------------------- /hot-towel-done/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["node_modules/**", "bower_components/**"], 3 | 4 | "requireCurlyBraces": [ 5 | "if", 6 | "else", 7 | "for", 8 | "while", 9 | "do", 10 | "try", 11 | "catch" 12 | ], 13 | "requireOperatorBeforeLineBreak": true, 14 | "requireCamelCaseOrUpperCaseIdentifiers": true, 15 | "maximumLineLength": { 16 | "value": 200, 17 | "allowComments": true, 18 | "allowRegex": true 19 | }, 20 | "validateIndentation": 4, 21 | "validateQuoteMarks": "'", 22 | 23 | "disallowMultipleLineStrings": true, 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": false, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowMultipleVarDecl": null, 28 | 29 | "requireSpaceAfterKeywords": [ 30 | "if", 31 | "else", 32 | "for", 33 | "while", 34 | "do", 35 | "switch", 36 | "return", 37 | "try", 38 | "catch" 39 | ], 40 | "requireSpaceBeforeBinaryOperators": [ 41 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 42 | "&=", "|=", "^=", "+=", 43 | 44 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 45 | "|", "^", "&&", "||", "===", "==", ">=", 46 | "<=", "<", ">", "!=", "!==" 47 | ], 48 | "requireSpaceAfterBinaryOperators": true, 49 | "requireSpacesInConditionalExpression": true, 50 | "requireSpaceBeforeBlockStatements": true, 51 | "requireLineFeedAtFileEnd": true, 52 | "disallowSpacesInsideObjectBrackets": "all", 53 | "disallowSpacesInsideArrayBrackets": "all", 54 | "disallowSpacesInsideParentheses": true, 55 | 56 | "jsDoc": { 57 | "checkAnnotations": true, 58 | "checkParamNames": true, 59 | "requireParamTypes": true, 60 | "checkReturnTypes": true, 61 | "checkTypes": true 62 | }, 63 | 64 | "disallowMultipleLineBreaks": true, 65 | 66 | "disallowCommaBeforeLineBreak": null, 67 | "disallowDanglingUnderscores": null, 68 | "disallowEmptyBlocks": null, 69 | "disallowTrailingComma": null, 70 | "requireCommaBeforeLineBreak": null, 71 | "requireDotNotation": null, 72 | "requireMultipleVarDecl": null, 73 | "requireParenthesesAroundIIFE": true 74 | } 75 | -------------------------------------------------------------------------------- /hot-towel-done/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": 50, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /hot-towel-done/README.md: -------------------------------------------------------------------------------- 1 | # hot-towel 2 | 3 | **Generated from HotTowel Angular** 4 | 5 | >*Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* 6 | 7 | >More details about the styles and patterns used in this app can be found in my [Angular Style Guide](https://github.com/johnpapa/angularjs-styleguide) and my [Angular Patterns: Clean Code](http://jpapa.me/ngclean) course at [Pluralsight](http://pluralsight.com/training/Authors/Details/john-papa) and working in teams. 8 | 9 | ## Prerequisites 10 | 11 | 1. Install [Node.js](http://nodejs.org) 12 | - on OSX use [homebrew](http://brew.sh) `brew install node` 13 | - on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` 14 | 15 | 2. Install Yeoman `npm install -g yo` 16 | 17 | 3. Install these NPM packages globally 18 | 19 | ```bash 20 | npm install -g bower gulp nodemon 21 | ``` 22 | 23 | >Refer to these [instructions on how to not require sudo](https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md) 24 | 25 | ## Running HotTowel 26 | 27 | ### Linting 28 | - Run code analysis using `gulp vet`. This runs jshint, jscs, and plato. 29 | 30 | ### Tests 31 | - Run the unit tests using `gulp test` (via karma, mocha, sinon). 32 | 33 | ### Running in dev mode 34 | - Run the project with `gulp serve-dev` 35 | 36 | - opens it in a browser and updates the browser with any files changes. 37 | 38 | ### Building the project 39 | - Build the optimized project using `gulp build` 40 | - This create the optimized code for the project and puts it in the build folder 41 | 42 | ### Running the optimized code 43 | - Run the optimize project from the build folder with `gulp serve-build` 44 | 45 | ## Exploring HotTowel 46 | HotTowel Angular starter project 47 | 48 | ### Structure 49 | The structure also contains a gulpfile.js and a server folder. The server is there just so we can serve the app using node. Feel free to use any server you wish. 50 | 51 | /src 52 | /client 53 | /app 54 | /content 55 | 56 | ### Installing Packages 57 | When you generate the project it should run these commands, but if you notice missing packages, run these again: 58 | 59 | - `npm install` 60 | - `bower install` 61 | 62 | ### The Modules 63 | The app has 4 feature modules and depends on a series of external modules and custom but cross-app modules 64 | 65 | ``` 66 | app --> [ 67 | app.admin --> [ 68 | app.core, 69 | app.widgets 70 | ], 71 | app.dashboard --> [ 72 | app.core, 73 | app.widgets 74 | ], 75 | app.layout --> [ 76 | app.core 77 | ], 78 | app.widgets, 79 | app.core --> [ 80 | ngAnimate, 81 | ngSanitize, 82 | ui.router, 83 | blocks.exception, 84 | blocks.logger, 85 | blocks.router 86 | ] 87 | ] 88 | ``` 89 | 90 | #### core Module 91 | Core modules are ones that are shared throughout the entire application and may be customized for the specific application. Example might be common data services. 92 | 93 | This is an aggregator of modules that the application will need. The `core` module takes the blocks, common, and Angular sub-modules as dependencies. 94 | 95 | #### blocks Modules 96 | Block modules are reusable blocks of code that can be used across projects simply by including them as dependencies. 97 | 98 | ##### blocks.logger Module 99 | The `blocks.logger` module handles logging across the Angular app. 100 | 101 | ##### blocks.exception Module 102 | The `blocks.exception` module handles exceptions across the Angular app. 103 | 104 | It depends on the `blocks.logger` module, because the implementation logs the exceptions. 105 | 106 | ##### blocks.router Module 107 | The `blocks.router` module contains a routing helper module that assists in adding routes to the $routeProvider. 108 | 109 | ## Gulp Tasks 110 | 111 | ### Task Listing 112 | 113 | - `gulp help` 114 | 115 | Displays all of the available gulp tasks. 116 | 117 | ### Code Analysis 118 | 119 | - `gulp vet` 120 | 121 | Performs static code analysis on all javascript files. Runs jshint and jscs. 122 | 123 | - `gulp vet --verbose` 124 | 125 | Displays all files affected and extended information about the code analysis. 126 | 127 | - `gulp plato` 128 | 129 | Performs code analysis using plato on all javascript files. Plato generates a report in the reports folder. 130 | 131 | ### Testing 132 | 133 | - `gulp serve-specs` 134 | 135 | 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`. 136 | 137 | - `gulp test` 138 | 139 | Runs all unit tests using karma runner, mocha, chai and sinon with phantomjs. Depends on vet task, for code analysis. 140 | 141 | - `gulp test --startServers` 142 | 143 | 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. 144 | 145 | - `gulp autotest` 146 | 147 | Runs a watch to run all unit tests. 148 | 149 | - `gulp autotest --startServers` 150 | 151 | 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. 152 | 153 | ### Cleaning Up 154 | 155 | - `gulp clean` 156 | 157 | Remove all files from the build and temp folders 158 | 159 | - `gulp clean-images` 160 | 161 | Remove all images from the build folder 162 | 163 | - `gulp clean-code` 164 | 165 | Remove all javascript and html from the build folder 166 | 167 | - `gulp clean-fonts` 168 | 169 | Remove all fonts from the build folder 170 | 171 | - `gulp clean-styles` 172 | 173 | Remove all styles from the build folder 174 | 175 | ### Fonts and Images 176 | 177 | - `gulp fonts` 178 | 179 | Copy all fonts from source to the build folder 180 | 181 | - `gulp images` 182 | 183 | Copy all images from source to the build folder 184 | 185 | ### Styles 186 | 187 | - `gulp styles` 188 | 189 | Compile less files to CSS, add vendor prefixes, and copy to the build folder 190 | 191 | ### Bower Files 192 | 193 | - `gulp wiredep` 194 | 195 | Looks up all bower components' main files and JavaScript source code, then adds them to the `index.html`. 196 | 197 | The `.bowerrc` file also runs this as a postinstall task whenever `bower install` is run. 198 | 199 | ### Angular HTML Templates 200 | 201 | - `gulp templatecache` 202 | 203 | 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. 204 | 205 | - `gulp templatecache --verbose` 206 | 207 | Displays all files affected by the task. 208 | 209 | ### Serving Development Code 210 | 211 | - `gulp serve-dev` 212 | 213 | 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. 214 | 215 | - `gulp serve-dev --nosync` 216 | 217 | Serves the development code without launching the browser. 218 | 219 | - `gulp serve-dev --debug` 220 | 221 | Launch debugger with node-inspector. 222 | 223 | - `gulp serve-dev --debug-brk` 224 | 225 | Launch debugger and break on 1st line with node-inspector. 226 | 227 | ### Building Production Code 228 | 229 | - `gulp optimize` 230 | 231 | Optimize all javascript and styles, move to a build folder, and inject them into the new index.html 232 | 233 | - `gulp build` 234 | 235 | Copies all fonts, copies images and runs `gulp optimize` to build the production code to the build folder. 236 | 237 | ### Serving Production Code 238 | 239 | - `gulp serve-build` 240 | 241 | Serve the optimized code from the build folder and launch it in a browser. 242 | 243 | - `gulp serve-build --nosync` 244 | 245 | Serve the optimized code from the build folder and manually launch the browser. 246 | 247 | - `gulp serve-build --debug` 248 | 249 | Launch debugger with node-inspector. 250 | 251 | - `gulp serve-build --debug-brk` 252 | 253 | Launch debugger and break on 1st line with node-inspector. 254 | 255 | ### Bumping Versions 256 | 257 | - `gulp bump` 258 | 259 | Bump the minor version using semver. 260 | --type=patch // default 261 | --type=minor 262 | --type=major 263 | --type=pre 264 | --ver=1.2.3 // specific version 265 | 266 | ## License 267 | 268 | MIT 269 | -------------------------------------------------------------------------------- /hot-towel-done/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hot-towel", 3 | "version": "0.0.1", 4 | "description": "hot-towel", 5 | "authors": [], 6 | "license": "MIT", 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests" 13 | ], 14 | "devDependencies": { 15 | "angular-mocks": "^1.4.5", 16 | "sinon": "http://sinonjs.org/releases/sinon-1.12.1.js", 17 | "bardjs": "^0.1.4" 18 | }, 19 | "dependencies": { 20 | "jquery": "^2.1.4", 21 | "angular": "^1.4.5", 22 | "angular-sanitize": "^1.4.5", 23 | "bootstrap": "~3.2.0", 24 | "extras.angular.plus": "^0.9.2", 25 | "font-awesome": "^4.3.0", 26 | "moment": "^2.10.3", 27 | "angular-ui-router": "^0.2.15", 28 | "toastr": "^2.1.1", 29 | "angular-animate": "^1.4.5", 30 | "angular-translate": "~2.8.1", 31 | "angular-translate-handler-log": "~2.8.1", 32 | "angular-translate-interpolation-messageformat": "~2.8.1", 33 | "angular-translate-loader-partial": "~2.8.1", 34 | "angular-translate-loader-static-files": "~2.8.1" 35 | }, 36 | "resolutions": { 37 | "angular": "1.3.17" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /hot-towel-done/gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var client = './src/client/'; 3 | var server = './src/server/'; 4 | var clientApp = client + 'app/'; 5 | var report = './report/'; 6 | var root = './'; 7 | var specRunnerFile = 'specs.html'; 8 | var temp = './.tmp/'; 9 | var wiredep = require('wiredep'); 10 | var bowerFiles = wiredep({devDependencies: true})['js']; 11 | var bower = { 12 | json: require('./bower.json'), 13 | directory: './bower_components/', 14 | ignorePath: '../..' 15 | }; 16 | var nodeModules = 'node_modules'; 17 | 18 | var config = { 19 | /** 20 | * File paths 21 | */ 22 | // all javascript that we want to vet 23 | alljs: [ 24 | './src/**/*.js', 25 | './*.js' 26 | ], 27 | build: './build/', 28 | client: client, 29 | css: temp + 'styles.css', 30 | fonts: bower.directory + 'font-awesome/fonts/**/*.*', 31 | html: client + '**/*.html', 32 | htmltemplates: clientApp + '**/*.html', 33 | images: client + 'images/**/*.*', 34 | index: client + 'index.html', 35 | // app js, with no specs 36 | js: [ 37 | clientApp + '**/*.module.js', 38 | clientApp + '**/*.js', 39 | '!' + clientApp + '**/*.spec.js' 40 | ], 41 | jsOrder: [ 42 | '**/app.module.js', 43 | '**/*.module.js', 44 | '**/*.js' 45 | ], 46 | less: client + 'styles/styles.less', 47 | report: report, 48 | root: root, 49 | server: server, 50 | source: 'src/', 51 | stubsjs: [ 52 | bower.directory + 'angular-mocks/angular-mocks.js', 53 | client + 'stubs/**/*.js' 54 | ], 55 | temp: temp, 56 | 57 | /** 58 | * optimized files 59 | */ 60 | optimized: { 61 | app: 'app.js', 62 | lib: 'lib.js' 63 | }, 64 | 65 | /** 66 | * plato 67 | */ 68 | plato: {js: clientApp + '**/*.js'}, 69 | 70 | /** 71 | * browser sync 72 | */ 73 | browserReloadDelay: 1000, 74 | 75 | /** 76 | * template cache 77 | */ 78 | templateCache: { 79 | file: 'templates.js', 80 | options: { 81 | module: 'app.core', 82 | root: 'app/', 83 | standalone: false 84 | } 85 | }, 86 | 87 | /** 88 | * Bower and NPM files 89 | */ 90 | bower: bower, 91 | packages: [ 92 | './package.json', 93 | './bower.json' 94 | ], 95 | 96 | /** 97 | * specs.html, our HTML spec runner 98 | */ 99 | specRunner: client + specRunnerFile, 100 | specRunnerFile: specRunnerFile, 101 | 102 | /** 103 | * The sequence of the injections into specs.html: 104 | * 1 testlibraries 105 | * mocha setup 106 | * 2 bower 107 | * 3 js 108 | * 4 spechelpers 109 | * 5 specs 110 | * 6 templates 111 | */ 112 | testlibraries: [ 113 | nodeModules + '/mocha/mocha.js', 114 | nodeModules + '/chai/chai.js', 115 | nodeModules + '/sinon-chai/lib/sinon-chai.js' 116 | ], 117 | specHelpers: [client + 'test-helpers/*.js'], 118 | specs: [clientApp + '**/*.spec.js'], 119 | serverIntegrationSpecs: [client + '/tests/server-integration/**/*.spec.js'], 120 | 121 | /** 122 | * Node settings 123 | */ 124 | nodeServer: server + 'app.js', 125 | defaultPort: '8001' 126 | }; 127 | 128 | /** 129 | * wiredep and bower settings 130 | */ 131 | config.getWiredepDefaultOptions = function() { 132 | var options = { 133 | bowerJson: config.bower.json, 134 | directory: config.bower.directory, 135 | ignorePath: config.bower.ignorePath 136 | }; 137 | return options; 138 | }; 139 | 140 | /** 141 | * karma settings 142 | */ 143 | config.karma = getKarmaOptions(); 144 | 145 | return config; 146 | 147 | //////////////// 148 | 149 | function getKarmaOptions() { 150 | var options = { 151 | files: [].concat( 152 | bowerFiles, 153 | config.specHelpers, 154 | clientApp + '**/*.module.js', 155 | clientApp + '**/*.js', 156 | temp + config.templateCache.file, 157 | config.serverIntegrationSpecs 158 | ), 159 | exclude: [], 160 | coverage: { 161 | dir: report + 'coverage', 162 | reporters: [ 163 | // reporters not supporting the `file` property 164 | {type: 'html', subdir: 'report-html'}, 165 | {type: 'lcov', subdir: 'report-lcov'}, 166 | {type: 'text-summary'} //, subdir: '.', file: 'text-summary.txt'} 167 | ] 168 | }, 169 | preprocessors: {} 170 | }; 171 | options.preprocessors[clientApp + '**/!(*.spec)+(.js)'] = ['coverage']; 172 | return options; 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /hot-towel-done/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/gulp.png -------------------------------------------------------------------------------- /hot-towel-done/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var gulpConfig = require('./gulp.config')(); 3 | 4 | config.set({ 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: './', 7 | 8 | // frameworks to use 9 | // some available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['mocha', 'chai', 'sinon', 'chai-sinon'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: gulpConfig.karma.files, 14 | 15 | // list of files to exclude 16 | exclude: gulpConfig.karma.exclude, 17 | 18 | proxies: { 19 | '/': 'http://localhost:8888/' 20 | }, 21 | 22 | // preprocess matching files before serving them to the browser 23 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 24 | preprocessors: gulpConfig.karma.preprocessors, 25 | 26 | // test results reporter to use 27 | // possible values: 'dots', 'progress', 'coverage' 28 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 29 | reporters: ['progress', 'coverage'], 30 | 31 | coverageReporter: { 32 | dir: gulpConfig.karma.coverage.dir, 33 | reporters: gulpConfig.karma.coverage.reporters 34 | }, 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || 44 | // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | // enable / disable watching file and executing tests whenever any file changes 48 | autoWatch: true, 49 | 50 | // start these browsers 51 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 52 | // browsers: ['Chrome', 'ChromeCanary', 'FirefoxAurora', 'Safari', 'PhantomJS'], 53 | browsers: ['PhantomJS'], 54 | 55 | // Continuous Integration mode 56 | // if true, Karma captures browsers, runs the tests and exits 57 | singleRun: false 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /hot-towel-done/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hot-towel", 3 | "description": "hot-towel Project Generated from HotTowel Angular", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "init": "npm install", 7 | "install": "bower install", 8 | "start": "node src/server/app.js", 9 | "test": "gulp test" 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.8.2", 13 | "express": "^4.9.3", 14 | "morgan": "^1.1.1", 15 | "serve-favicon": "^2.0.1" 16 | }, 17 | "devDependencies": { 18 | "browser-sync": "^2.7.13", 19 | "chai": "^3.1.0", 20 | "chai-as-promised": "^5.1.0", 21 | "chalk": "^1.1.0", 22 | "dateformat": "^1.0.8-1.2.3", 23 | "debug": "^2.0.0", 24 | "del": "^1.2.0", 25 | "glob": "^4.5.3", 26 | "gulp": "^3.8.10", 27 | "gulp-angular-templatecache": "^1.4.2", 28 | "gulp-autoprefixer": "^2.3.1", 29 | "gulp-bump": "^0.3.1", 30 | "gulp-bytediff": "^0.2.0", 31 | "gulp-concat": "^2.3.3", 32 | "gulp-filter": "^2.0.2", 33 | "gulp-header": "^1.2.2", 34 | "gulp-if": "^1.2.5", 35 | "gulp-imagemin": "^2.3.0", 36 | "gulp-inject": "^1.0.1", 37 | "gulp-jscs": "^2.0.0", 38 | "gulp-jshint": "^1.7.1", 39 | "gulp-less": "^3.0.1", 40 | "gulp-load-plugins": "^1.0.0-rc.1", 41 | "gulp-minify-css": "^1.1.1", 42 | "gulp-minify-html": "^1.0.4", 43 | "gulp-ng-annotate": "^1.0.0", 44 | "gulp-nodemon": "^2.0.3", 45 | "gulp-order": "^1.1.1", 46 | "gulp-plumber": "^1.0.1", 47 | "gulp-print": "^1.1.0", 48 | "gulp-rev": "^5.1.0", 49 | "gulp-rev-replace": "^0.4.2", 50 | "gulp-sourcemaps": "^1.1.5", 51 | "gulp-task-listing": "^1.0.0", 52 | "gulp-uglify": "^1.0.1", 53 | "gulp-useref": "^1.0.2", 54 | "gulp-util": "^3.0.1", 55 | "jshint-stylish": "^2.0.1", 56 | "karma": "^0.13.2", 57 | "karma-chai": "^0.1.0", 58 | "karma-chai-sinon": "^0.1.3", 59 | "karma-chrome-launcher": "^0.2.0", 60 | "karma-coverage": "^0.4.2", 61 | "karma-firefox-launcher": "^0.1.3", 62 | "karma-growl-reporter": "^0.1.1", 63 | "karma-mocha": "^0.2.0", 64 | "karma-phantomjs-launcher": "^0.2.0", 65 | "karma-safari-launcher": "^0.1.1", 66 | "karma-sinon": "^1.0.3", 67 | "lodash": "^3.10.0", 68 | "method-override": "^2.3.4", 69 | "minimist": "^1.1.0", 70 | "mocha": "^2.2.5", 71 | "node-notifier": "^4.0.3", 72 | "phantomjs": "^1.9.17", 73 | "plato": "^1.2.0", 74 | "q": "^1.0.1", 75 | "sinon": "^1.12.2", 76 | "sinon-chai": "^2.6.0", 77 | "wiredep": "^2.2.2", 78 | "yargs": "^3.15.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.admin') 6 | .controller('AdminController', AdminController); 7 | 8 | AdminController.$inject = ['logger', '$translate']; 9 | /* @ngInject */ 10 | function AdminController(logger, $translate) { 11 | var vm = this; 12 | vm.title = 'Admin'; 13 | 14 | activate(); 15 | 16 | function activate() { 17 | $translate('Activation_Admin').then(function (translation) { 18 | logger.info(translation); 19 | }); 20 | } 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('AdminController', function() { 3 | var controller; 4 | 5 | beforeEach(function() { 6 | bard.appModule('app.admin'); 7 | bard.inject('$controller', '$log', '$rootScope'); 8 | }); 9 | 10 | beforeEach(function () { 11 | controller = $controller('AdminController'); 12 | $rootScope.$apply(); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | describe('Admin controller', function() { 18 | it('should be created successfully', function () { 19 | expect(controller).to.be.defined; 20 | }); 21 | 22 | describe('after activate', function() { 23 | it('should have title of Admin', function() { 24 | expect(controller.title).to.equal('Admin'); 25 | }); 26 | 27 | it('should have logged "Activated"', function() { 28 | expect($log.info.logs).to.match(/Activated/); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |

Test

9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app.admin', [ 5 | 'app.core', 6 | 'app.widgets' 7 | ]); 8 | 9 | })(); 10 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.admin') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'admin', 18 | config: { 19 | url: '/admin', 20 | templateUrl: 'app/admin/admin.html', 21 | controller: 'AdminController', 22 | controllerAs: 'vm', 23 | title: 'Admin', 24 | settings: { 25 | nav: 2, 26 | content: ' Admin' 27 | } 28 | } 29 | } 30 | ]; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/admin.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('admin routes', function () { 3 | describe('state', function () { 4 | var view = 'app/admin/admin.html'; 5 | 6 | beforeEach(function() { 7 | module('app.admin', bard.fakeToastr); 8 | bard.inject('$httpBackend', '$location', '$rootScope', '$state', '$templateCache'); 9 | }); 10 | 11 | beforeEach(function() { 12 | $templateCache.put(view, ''); 13 | }); 14 | 15 | it('should map state admin to url /admin ', function() { 16 | expect($state.href('admin', {})).to.equal('/admin'); 17 | }); 18 | 19 | it('should map /admin route to admin View template', function () { 20 | expect($state.get('admin').templateUrl).to.equal(view); 21 | }); 22 | 23 | it('of admin should work with $state.go', function () { 24 | $state.go('admin'); 25 | $rootScope.$apply(); 26 | expect($state.is('admin')); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Admin": "Admin", 3 | "Admin_Message": "The quick brown fox jumped over the lazy dog", 4 | "Activation_Admin": "Activated Admin View" 5 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/admin/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Admin": "Administración", 3 | "Admin_Message": "El zorro marrón rápido saltó sobre el perro perezoso", 4 | "Activation_Admin": "Activado Administración Ver" 5 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/app.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | 'app.core', 6 | 'app.widgets', 7 | 'app.admin', 8 | 'app.dashboard', 9 | 'app.layout' 10 | ]); 11 | 12 | })(); 13 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/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('blocks.exception') 8 | .provider('exceptionHandler', exceptionHandlerProvider) 9 | .config(config); 10 | 11 | /** 12 | * Must configure the exception handling 13 | */ 14 | function exceptionHandlerProvider() { 15 | /* jshint validthis:true */ 16 | this.config = { 17 | appErrorPrefix: undefined 18 | }; 19 | 20 | this.configure = function (appErrorPrefix) { 21 | this.config.appErrorPrefix = appErrorPrefix; 22 | }; 23 | 24 | this.$get = function() { 25 | return {config: this.config}; 26 | }; 27 | } 28 | 29 | config.$inject = ['$provide']; 30 | 31 | /** 32 | * Configure by setting an optional string value for appErrorPrefix. 33 | * Accessible via config.appErrorPrefix (via config value). 34 | * @param {Object} $provide 35 | */ 36 | /* @ngInject */ 37 | function config($provide) { 38 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 39 | } 40 | 41 | extendExceptionHandler.$inject = ['$delegate', 'exceptionHandler', 'logger']; 42 | 43 | /** 44 | * Extend the $exceptionHandler service to also display a toast. 45 | * @param {Object} $delegate 46 | * @param {Object} exceptionHandler 47 | * @param {Object} logger 48 | * @return {Function} the decorated $exceptionHandler service 49 | */ 50 | function extendExceptionHandler($delegate, exceptionHandler, logger) { 51 | return function(exception, cause) { 52 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 53 | var errorData = {exception: exception, cause: cause}; 54 | exception.message = appErrorPrefix + exception.message; 55 | $delegate(exception, cause); 56 | /** 57 | * Could add the error to a service's collection, 58 | * add errors to $rootScope, log errors to remote web server, 59 | * or log locally. Or throw hard. It is entirely up to you. 60 | * throw exception; 61 | * 62 | * @example 63 | * throw { message: 'error message we added' }; 64 | */ 65 | logger.error(exception.message, errorData); 66 | }; 67 | } 68 | })(); 69 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/exception/exception-handler.provider.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('blocks.exception', function() { 3 | var exceptionHandlerProvider; 4 | var mocks = { 5 | errorMessage: 'fake error', 6 | prefix: '[TEST]: ' 7 | }; 8 | 9 | beforeEach(function() { 10 | bard.appModule('blocks.exception', function(_exceptionHandlerProvider_) { 11 | exceptionHandlerProvider = _exceptionHandlerProvider_; 12 | }); 13 | bard.inject('$rootScope'); 14 | }); 15 | 16 | bard.verifyNoOutstandingHttpRequests(); 17 | 18 | describe('exceptionHandlerProvider', function() { 19 | it('should have a dummy test', inject(function() { 20 | expect(true).to.equal(true); 21 | })); 22 | 23 | it('should have exceptionHandlerProvider defined', inject(function() { 24 | expect(exceptionHandlerProvider).to.be.defined; 25 | })); 26 | 27 | it('should have configuration', inject(function() { 28 | expect(exceptionHandlerProvider.config).to.be.defined; 29 | })); 30 | 31 | it('should have configuration', inject(function() { 32 | expect(exceptionHandlerProvider.configure).to.be.defined; 33 | })); 34 | 35 | describe('with appErrorPrefix', function() { 36 | beforeEach(function() { 37 | exceptionHandlerProvider.configure(mocks.prefix); 38 | }); 39 | 40 | it('should have appErrorPrefix defined', inject(function() { 41 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix).to.be.defined; 42 | })); 43 | 44 | it('should have appErrorPrefix set properly', inject(function() { 45 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix) 46 | .to.equal(mocks.prefix); 47 | })); 48 | 49 | it('should throw an error when forced', inject(function() { 50 | expect(functionThatWillThrow).to.throw(); 51 | })); 52 | 53 | it('manual error is handled by decorator', function() { 54 | var exception; 55 | exceptionHandlerProvider.configure(mocks.prefix); 56 | try { 57 | $rootScope.$apply(functionThatWillThrow); 58 | } 59 | catch (ex) { 60 | exception = ex; 61 | expect(ex.message).to.equal(mocks.prefix + mocks.errorMessage); 62 | } 63 | }); 64 | }); 65 | }); 66 | 67 | function functionThatWillThrow() { 68 | throw new Error(mocks.errorMessage); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/exception/exception.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.exception') 6 | .factory('exception', exception); 7 | 8 | /* @ngInject */ 9 | function exception($q, logger) { 10 | var service = { 11 | catcher: catcher 12 | }; 13 | return service; 14 | 15 | function catcher(message) { 16 | return function(e) { 17 | var thrownDescription; 18 | var newMessage; 19 | if (e.data && e.data.description) { 20 | thrownDescription = '\n' + e.data.description; 21 | newMessage = message + thrownDescription; 22 | } 23 | e.data.description = newMessage; 24 | logger.error(newMessage); 25 | return $q.reject(e); 26 | }; 27 | } 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/exception/exception.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.exception', ['blocks.logger']); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/logger/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.logger') 6 | .factory('logger', logger); 7 | 8 | logger.$inject = ['$log', 'toastr']; 9 | 10 | /* @ngInject */ 11 | function logger($log, toastr) { 12 | var service = { 13 | showToasts: true, 14 | 15 | error : error, 16 | info : info, 17 | success : success, 18 | warning : warning, 19 | 20 | // straight to console; bypass toastr 21 | log : $log.log 22 | }; 23 | 24 | return service; 25 | ///////////////////// 26 | 27 | function error(message, data, title) { 28 | toastr.error(message, title); 29 | $log.error('Error: ' + message, data); 30 | } 31 | 32 | function info(message, data, title) { 33 | toastr.info(message, title); 34 | $log.info('Info: ' + message, data); 35 | } 36 | 37 | function success(message, data, title) { 38 | toastr.success(message, title); 39 | $log.info('Success: ' + message, data); 40 | } 41 | 42 | function warning(message, data, title) { 43 | toastr.warning(message, title); 44 | $log.warn('Warning: ' + message, data); 45 | } 46 | } 47 | }()); 48 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/logger/logger.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.logger', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/router/router-helper.provider.js: -------------------------------------------------------------------------------- 1 | /* Help configure the state-base ui.router */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('blocks.router') 7 | .provider('routerHelper', routerHelperProvider); 8 | 9 | routerHelperProvider.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 10 | /* @ngInject */ 11 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 12 | /* jshint validthis:true */ 13 | var config = { 14 | docTitle: undefined, 15 | resolveAlways: {} 16 | }; 17 | 18 | $locationProvider.html5Mode(true); 19 | 20 | this.configure = function(cfg) { 21 | angular.extend(config, cfg); 22 | }; 23 | 24 | this.$get = RouterHelper; 25 | RouterHelper.$inject = ['$location', '$rootScope', '$state', 'logger']; 26 | /* @ngInject */ 27 | function RouterHelper($location, $rootScope, $state, logger) { 28 | var handlingStateChangeError = false; 29 | var hasOtherwise = false; 30 | var stateCounts = { 31 | errors: 0, 32 | changes: 0 33 | }; 34 | 35 | var service = { 36 | configureStates: configureStates, 37 | getStates: getStates, 38 | stateCounts: stateCounts 39 | }; 40 | 41 | init(); 42 | 43 | return service; 44 | 45 | /////////////// 46 | 47 | function configureStates(states, otherwisePath) { 48 | states.forEach(function(state) { 49 | state.config.resolve = 50 | angular.extend(state.config.resolve || {}, config.resolveAlways); 51 | $stateProvider.state(state.state, state.config); 52 | }); 53 | if (otherwisePath && !hasOtherwise) { 54 | hasOtherwise = true; 55 | $urlRouterProvider.otherwise(otherwisePath); 56 | } 57 | } 58 | 59 | function handleRoutingErrors() { 60 | // Route cancellation: 61 | // On routing error, go to the dashboard. 62 | // Provide an exit clause if it tries to do it twice. 63 | $rootScope.$on('$stateChangeError', 64 | function(event, toState, toParams, fromState, fromParams, error) { 65 | if (handlingStateChangeError) { 66 | return; 67 | } 68 | stateCounts.errors++; 69 | handlingStateChangeError = true; 70 | var destination = (toState && 71 | (toState.title || toState.name || toState.loadedTemplateUrl)) || 72 | 'unknown target'; 73 | var msg = 'Error routing to ' + destination + '. ' + 74 | (error.data || '') + '.
' + (error.statusText || '') + 75 | ': ' + (error.status || ''); 76 | logger.warning(msg, [toState]); 77 | $location.path('/'); 78 | } 79 | ); 80 | } 81 | 82 | function init() { 83 | handleRoutingErrors(); 84 | updateDocTitle(); 85 | } 86 | 87 | function getStates() { return $state.get(); } 88 | 89 | function updateDocTitle() { 90 | $rootScope.$on('$stateChangeSuccess', 91 | function(event, toState, toParams, fromState, fromParams) { 92 | stateCounts.changes++; 93 | handlingStateChangeError = false; 94 | var title = config.docTitle + ' ' + (toState.title || ''); 95 | $rootScope.title = title; // data bind to 96 | } 97 | ); 98 | } 99 | } 100 | } 101 | })(); 102 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/blocks/router/router.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.router', [ 5 | 'ui.router', 6 | 'blocks.logger' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/404.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="bred"> 8 | <div class="pull-left"><i class="fa fa-warning"></i></div> 9 | <div class="datas-text pull-right"> 10 | <a><span class="bold">404</span></a>Page Not Found 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | </ul> 15 | </div> 16 | </div> 17 | <div class="row"> 18 | <div class="widget wblue"> 19 | <div ht-widget-header title="Page Not Found" 20 | allow-collapse="true"></div> 21 | <div class="widget-content text-center text-info"> 22 | <div class="container"> 23 | No soup for you! 24 | </div> 25 | </div> 26 | <div class="widget-foot"> 27 | <div class="clearfix"></div> 28 | </div> 29 | </div> 30 | </div> 31 | </div> 32 | </section> 33 | </section> 34 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var core = angular.module('app.core'); 5 | 6 | core.config(toastrConfig); 7 | 8 | toastrConfig.$inject = ['toastr']; 9 | /* @ngInject */ 10 | function toastrConfig(toastr) { 11 | toastr.options.timeOut = 4000; 12 | toastr.options.positionClass = 'toast-bottom-right'; 13 | } 14 | 15 | var config = { 16 | appErrorPrefix: '[hot-towel Error] ', 17 | appTitle: 'hot-towel' 18 | }; 19 | 20 | core.value('config', config); 21 | 22 | core.config(configure); 23 | 24 | configure.$inject = ['$logProvider', 'routerHelperProvider', 'exceptionHandlerProvider', '$translateProvider']; 25 | /* @ngInject */ 26 | function configure($logProvider, routerHelperProvider, exceptionHandlerProvider, $translateProvider) { 27 | 28 | $translateProvider 29 | .addInterpolation('$translateMessageFormatInterpolation') 30 | .preferredLanguage('en') 31 | .fallbackLanguage('en') 32 | .useStaticFilesLoader({ 33 | prefix: '/app/i18n/', 34 | suffix: '.json' 35 | }) 36 | .useSanitizeValueStrategy('sanitize'); 37 | 38 | if ($logProvider.debugEnabled) { 39 | $logProvider.debugEnabled(true); 40 | } 41 | exceptionHandlerProvider.configure(config.appErrorPrefix); 42 | routerHelperProvider.configure({docTitle: config.appTitle + ': '}); 43 | } 44 | 45 | core.run(function ($rootScope) { 46 | $rootScope.$on('$translateChangeSuccess', function () { 47 | console.log('Translation Change Success!'); 48 | }); 49 | $rootScope.$on('$translateChangeError', function () { 50 | console.log('Translation Change Error!'); 51 | }); 52 | }); 53 | 54 | })(); 55 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/constants.js: -------------------------------------------------------------------------------- 1 | /* global toastr:false, moment:false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .constant('toastr', toastr) 8 | .constant('moment', moment); 9 | })(); 10 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core', [ 6 | 'ngAnimate', 'ngSanitize', 7 | 'blocks.exception', 'blocks.logger', 'blocks.router', 8 | 'ui.router', 'ngplus', 'pascalprecht.translate' 9 | ]); 10 | })(); 11 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/core.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .run(appRun); 7 | 8 | /* @ngInject */ 9 | function appRun(routerHelper) { 10 | var otherwise = '/404'; 11 | routerHelper.configureStates(getStates(), otherwise); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: '404', 18 | config: { 19 | url: '/404', 20 | templateUrl: 'app/core/404.html', 21 | title: '404' 22 | } 23 | } 24 | ]; 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/core.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('core', function() { 3 | describe('state', function() { 4 | var views = { 5 | four0four: 'app/core/404.html' 6 | }; 7 | 8 | beforeEach(function() { 9 | module('app.core', bard.fakeToastr); 10 | bard.inject('$location', '$rootScope', '$state', '$templateCache'); 11 | $templateCache.put(views.core, ''); 12 | }); 13 | 14 | it('should map /404 route to 404 View template', function() { 15 | expect($state.get('404').templateUrl).to.equal(views.four0four); 16 | }); 17 | 18 | it('of dashboard should work with $state.go', function() { 19 | $state.go('404'); 20 | $rootScope.$apply(); 21 | expect($state.is('404')); 22 | }); 23 | 24 | it('should route /invalid to the otherwise (404) route', function() { 25 | $location.path('/invalid'); 26 | $rootScope.$apply(); 27 | expect($state.current.templateUrl).to.equal(views.four0four); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/dataservice.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('dataservice', dataservice); 7 | 8 | dataservice.$inject = ['$http', '$q', 'exception', 'logger']; 9 | /* @ngInject */ 10 | function dataservice($http, $q, exception, logger) { 11 | var service = { 12 | getPeople: getPeople, 13 | getMessageCount: getMessageCount 14 | }; 15 | 16 | return service; 17 | 18 | function getMessageCount() { return $q.when(72); } 19 | 20 | function getPeople() { 21 | return $http.get('/api/people') 22 | .then(success) 23 | .catch(fail); 24 | 25 | function success(response) { 26 | return response.data; 27 | } 28 | 29 | function fail(e) { 30 | return exception.catcher('XHR Failed for getPeople')(e); 31 | } 32 | } 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Internationalization", 3 | "Language": "Language", 4 | "Languages": { 5 | "English": "English", 6 | "Spanish": "Spanish", 7 | "French": "French" 8 | }, 9 | "Created_By": "Created by John Papa", 10 | "Splash_Msg": "Loading . . .", 11 | "Dashboard": "Dashboard", 12 | "Admin": "Admin" 13 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/core/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Internacionalización", 3 | "Language": "Idioma", 4 | "Languages": { 5 | "English": "Inglés", 6 | "Spanish": "Español", 7 | "French": "Francés" 8 | }, 9 | "Created_By": "Creado por Juan Padre", 10 | "Splash_Msg": "Cargando . . .", 11 | "Dashboard": "Tablero", 12 | "Admin": "Administración" 13 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .controller('DashboardController', DashboardController); 7 | 8 | DashboardController.$inject = ['$q', 'dataservice', 'logger', '$translate']; 9 | /* @ngInject */ 10 | function DashboardController($q, dataservice, logger, $translate) { 11 | 12 | var vm = this; 13 | vm.news = { 14 | title: 'hot-towel', 15 | description: 'Hot Towel Angular is a SPA template for Angular developers.' 16 | }; 17 | vm.name = 'Mike'; 18 | vm.messageCount = 0; 19 | vm.people = []; 20 | vm.title = 'Dashboard'; 21 | 22 | activate(); 23 | 24 | function activate() { 25 | var promises = [getMessageCount(), getPeople()]; 26 | return $q.all(promises).then(function () { 27 | $translate('Activation_Dash').then(function (translation) { 28 | logger.info(translation); 29 | }); 30 | }); 31 | } 32 | 33 | function getMessageCount() { 34 | return dataservice.getMessageCount().then(function (data) { 35 | vm.messageCount = data; 36 | return vm.messageCount; 37 | }); 38 | } 39 | 40 | function getPeople() { 41 | return dataservice.getPeople().then(function (data) { 42 | vm.people = data; 43 | return vm.people; 44 | }); 45 | } 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('DashboardController', function() { 3 | var controller; 4 | var people = mockData.getMockPeople(); 5 | 6 | beforeEach(function() { 7 | bard.appModule('app.dashboard'); 8 | bard.inject('$controller', '$log', '$q', '$rootScope', 'dataservice'); 9 | }); 10 | 11 | beforeEach(function () { 12 | sinon.stub(dataservice, 'getPeople').returns($q.when(people)); 13 | controller = $controller('DashboardController'); 14 | $rootScope.$apply(); 15 | }); 16 | 17 | bard.verifyNoOutstandingHttpRequests(); 18 | 19 | describe('Dashboard controller', function() { 20 | it('should be created successfully', function () { 21 | expect(controller).to.be.defined; 22 | }); 23 | 24 | describe('after activate', function() { 25 | it('should have title of Dashboard', function () { 26 | expect(controller.title).to.equal('Dashboard'); 27 | }); 28 | 29 | it('should have logged "Activated"', function() { 30 | expect($log.info.logs).to.match(/Activated/); 31 | }); 32 | 33 | it('should have news', function () { 34 | expect(controller.news).to.not.be.empty; 35 | }); 36 | 37 | it('should have at least 1 person', function () { 38 | expect(controller.people).to.have.length.above(0); 39 | }); 40 | 41 | it('should have people count of 5', function () { 42 | expect(controller.people).to.have.length(7); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="blightblue"> 8 | <div class="pull-left"><i class="fa fa-plane"></i></div> 9 | <div class="datas-text pull-right"> 10 | <span class="bold" translate="Conference_Date">May 18 - 19, 2015</span> Castle Resort, Neverland 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | <li class="borange"> 15 | <div class="pull-left"><i class="fa fa-envelope"></i></div> 16 | <div class="datas-text pull-right"> 17 | <span class="bold" translate="Message_Count" 18 | translate-values="{messageCount: vm.messageCount}" 19 | translate-interpolation="messageformat">72 Messages</span> 20 | </div> 21 | <div class="clearfix"></div> 22 | </li> 23 | <li class="bgreen"> 24 | <div class="pull-left"><i class="fa fa-envelope"></i></div> 25 | <div class="datas-text pull-right"> 26 | <span class="bold" translate="Greeting" translate-value-name="John">Mike is logged in</span> 27 | </div> 28 | <div class="clearfix"></div> 29 | </li> 30 | 31 | </ul> 32 | </div> 33 | </div> 34 | <div class="row"> 35 | <div class="col-md-6"> 36 | <div class="widget wviolet"> 37 | <div ht-widget-header title="People" 38 | allow-collapse="true"></div> 39 | <div class="widget-content text-center text-info"> 40 | <table class="table table-condensed table-striped"> 41 | <thead> 42 | <tr> 43 | <th translate>First_Name</th> 44 | <th translate="Last_Name">Last Name</th> 45 | <th>{{ 'Age' | translate }}</th> 46 | <th translate>Location</th> 47 | </tr> 48 | </thead> 49 | <tbody> 50 | <tr ng-repeat="p in vm.people"> 51 | <td>{{p.firstName}}</td> 52 | <td>{{p.lastName}}</td> 53 | <td>{{p.age}}</td> 54 | <td>{{p.location}}</td> 55 | </tr> 56 | </tbody> 57 | </table> 58 | </div> 59 | <div class="widget-foot"> 60 | <div class="clearfix"></div> 61 | </div> 62 | </div> 63 | </div> 64 | </div> 65 | </div> 66 | </section> 67 | </section> -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.dashboard', [ 5 | 'app.core', 6 | 'app.widgets' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'dashboard', 18 | config: { 19 | url: '/', 20 | templateUrl: 'app/dashboard/dashboard.html', 21 | controller: 'DashboardController', 22 | controllerAs: 'vm', 23 | title: 'Dashboard', 24 | settings: { 25 | nav: 1, 26 | content: '<i class="fa fa-dashboard"></i> Dashboard' 27 | } 28 | } 29 | } 30 | ]; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/dashboard.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('dashboard routes', function () { 3 | describe('state', function () { 4 | var view = 'app/dashboard/dashboard.html'; 5 | 6 | beforeEach(function() { 7 | module('app.dashboard', bard.fakeToastr); 8 | bard.inject('$httpBackend', '$location', '$rootScope', '$state', '$templateCache'); 9 | }); 10 | 11 | beforeEach(function() { 12 | $templateCache.put(view, ''); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | it('should map state dashboard to url / ', function() { 18 | expect($state.href('dashboard', {})).to.equal('/'); 19 | }); 20 | 21 | it('should map /dashboard route to dashboard View template', function () { 22 | expect($state.get('dashboard').templateUrl).to.equal(view); 23 | }); 24 | 25 | it('of dashboard should work with $state.go', function () { 26 | $state.go('dashboard'); 27 | $rootScope.$apply(); 28 | expect($state.is('dashboard')); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "First_Name": "First Name", 3 | "Last_Name": "Last Name", 4 | "Age": "Age", 5 | "Location": "Location", 6 | "Messages": "Messages", 7 | "People": "People", 8 | "Message_Count": "{messageCount, plural, =0{No Messages} one{1 Message} other{# Messages}}", 9 | "Conference_Date": "May 18 - 19, 2015", 10 | "Dashboard": "Dashboard", 11 | "Greeting": "{{name}} is logged in", 12 | "Activation_Dash": "Activated Dashboard View" 13 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/dashboard/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "First_Name": "Nombre De Pila", 3 | "Last_Name": "Apellido", 4 | "Age": "Edad", 5 | "Location": "Ubicación", 6 | "Messages": "Mensajes", 7 | "People": "Gente", 8 | "Message_Count": "{messageCount, plural, =0{No Hay Mensajes} one{1 Mensaje} other{# Mensajes}}", 9 | "Conference_Date": "18 a 19 mayo, 2015", 10 | "Dashboard": "Tablero", 11 | "Greeting": "{{name}} se registra en", 12 | "Activation_Dash": "Activado Tablero Ver" 13 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Internationalization", 3 | "Language": "Language", 4 | "Languages": { 5 | "English": "English", 6 | "Spanish": "Spanish", 7 | "French": "French" 8 | }, 9 | "Created_By": "Created by John Papa", 10 | "First_Name": "First Name", 11 | "Last_Name": "Last Name", 12 | "Age": "Age", 13 | "Location": "Location", 14 | "Messages": "Messages", 15 | "People": "People", 16 | "Splash_Msg": "Loading . . .", 17 | "Message_Count": "{messageCount, plural, =0{No Messages} one{1 Message} other{# Messages}}", 18 | "Conference_Date": "May 18 - 19, 2015", 19 | "Dashboard": "Dashboard", 20 | "Admin": "Admin", 21 | "Greeting": "{{name}} is logged in", 22 | "Admin_Message": "The quick brown fox jumped over the lazy dog", 23 | "Activation_Dash": "Activated Dashboard View", 24 | "Activation_Admin": "Activated Admin View" 25 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Internacionalización", 3 | "Language": "Idioma", 4 | "Languages": { 5 | "English": "Inglés", 6 | "Spanish": "Español", 7 | "French": "Francés" 8 | }, 9 | "Created_By": "Creado por Juan Padre", 10 | "First_Name": "Nombre De Pila", 11 | "Last_Name": "Apellido", 12 | "Age": "Edad", 13 | "Location": "Ubicación", 14 | "Messages": "Mensajes", 15 | "People": "Gente", 16 | "Splash_Msg": "Cargando . . .", 17 | "Message_Count": "{messageCount, plural, =0{No Hay Mensajes} one{1 Mensaje} other{# Mensajes}}", 18 | "Conference_Date": "18 a 19 mayo, 2015", 19 | "Dashboard": "Tablero", 20 | "Admin": "Administración", 21 | "Greeting": "{{name}} se registra en", 22 | "Admin_Message": "El zorro marrón rápido saltó sobre el perro perezoso", 23 | "Activation_Dash": "Activado Tablero Ver", 24 | "Activation_Admin": "Activado Administración Ver" 25 | } -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/ht-sidebar.directive.js: -------------------------------------------------------------------------------- 1 | 2 | (function () { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.layout') 7 | .directive('htSidebar', htSidebar); 8 | 9 | /* @ngInject */ 10 | function htSidebar () { 11 | // Opens and closes the sidebar menu. 12 | // Usage: 13 | // <div ht-sidebar"> 14 | // <div ht-sidebar whenDoneAnimating="vm.sidebarReady()"> 15 | // Creates: 16 | // <div ht-sidebar class="sidebar"> 17 | var directive = { 18 | link: link, 19 | restrict: 'EA', 20 | scope: { 21 | whenDoneAnimating: '&?' 22 | } 23 | }; 24 | return directive; 25 | 26 | function link(scope, element, attrs) { 27 | var $sidebarInner = element.find('.sidebar-inner'); 28 | var $dropdownElement = element.find('.sidebar-dropdown a'); 29 | element.addClass('sidebar'); 30 | $dropdownElement.click(dropdown); 31 | 32 | function dropdown(e) { 33 | var dropClass = 'dropy'; 34 | e.preventDefault(); 35 | if (!$dropdownElement.hasClass(dropClass)) { 36 | $sidebarInner.slideDown(350, scope.whenDoneAnimating); 37 | $dropdownElement.addClass(dropClass); 38 | } else if ($dropdownElement.hasClass(dropClass)) { 39 | $dropdownElement.removeClass(dropClass); 40 | $sidebarInner.slideUp(350, scope.whenDoneAnimating); 41 | } 42 | } 43 | } 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/ht-sidebar.directive.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | /* jshint multistr:true */ 3 | describe('htSidebar directive: ', function () { 4 | var dropdownElement; 5 | var el; 6 | var innerElement; 7 | var isOpenClass = 'dropy'; 8 | var scope; 9 | 10 | beforeEach(module('app.layout')); 11 | 12 | beforeEach(inject(function($compile, $rootScope) { 13 | // The minimum necessary template HTML for this spec. 14 | // Simulates a menu link that opens and closes a dropdown of menu items 15 | // The `when-done-animating` attribute is optional (as is the vm's implementation) 16 | // 17 | // N.B.: the attribute value is supposed to be an expression that invokes a $scope method 18 | // so make sure the expression includes '()', e.g., "vm.sidebarReady(42)" 19 | // no harm if the expression fails ... but then scope.sidebarReady will be undefined. 20 | // All parameters in the expression are passed to vm.sidebarReady ... if it exists 21 | // 22 | // N.B.: We do NOT add this element to the browser DOM (although we could). 23 | // spec runs faster if we don't touch the DOM (even the PhantomJS DOM). 24 | el = angular.element( 25 | '<ht-sidebar when-done-animating="vm.sidebarReady(42)">' + 26 | '<div class="sidebar-dropdown"><a href="">Menu</a></div>' + 27 | '<div class="sidebar-inner" style="display: none"></div>' + 28 | '</ht-sidebar>'); 29 | 30 | // The spec examines changes to these template parts 31 | dropdownElement = el.find('.sidebar-dropdown a'); // the link to click 32 | innerElement = el.find('.sidebar-inner'); // container of menu items 33 | 34 | // ng's $compile service resolves nested directives (there are none in this example) 35 | // and binds the element to the scope (which must be a real ng scope) 36 | scope = $rootScope; 37 | $compile(el)(scope); 38 | 39 | // tell angular to look at the scope values right now 40 | scope.$digest(); 41 | })); 42 | 43 | /// tests /// 44 | describe('the isOpenClass', function () { 45 | it('is absent for a closed menu', function () { 46 | hasIsOpenClass(false); 47 | }); 48 | 49 | it('is added to a closed menu after clicking', function () { 50 | clickIt(); 51 | hasIsOpenClass(true); 52 | }); 53 | 54 | it('is present for an open menu', function () { 55 | openDropdown(); 56 | hasIsOpenClass(true); 57 | }); 58 | 59 | it('is removed from a closed menu after clicking', function () { 60 | openDropdown(); 61 | clickIt(); 62 | hasIsOpenClass(false); 63 | }); 64 | }); 65 | 66 | describe('when animating w/ jQuery fx off', function () { 67 | beforeEach(function () { 68 | // remember current state of jQuery's global FX duration switch 69 | this.oldFxOff = $.fx.off; 70 | // when jQuery fx are of, there is zero animation time; no waiting for animation to complete 71 | $.fx.off = true; 72 | // must add to DOM when testing jQuery animation result 73 | el.appendTo(document.body); 74 | }); 75 | 76 | afterEach(function () { 77 | $.fx.off = this.oldFxOff; 78 | el.remove(); 79 | }); 80 | 81 | it('dropdown is visible after opening a closed menu', function () { 82 | dropdownIsVisible(false); // hidden before click 83 | clickIt(); 84 | dropdownIsVisible(true); // visible after click 85 | }); 86 | 87 | it('dropdown is hidden after closing an open menu', function () { 88 | openDropdown(); 89 | dropdownIsVisible(true); // visible before click 90 | clickIt(); 91 | dropdownIsVisible(false); // hidden after click 92 | }); 93 | 94 | it('click triggers "when-done-animating" expression', function () { 95 | // spy on directive's callback when the animation is done 96 | var spy = sinon.spy(); 97 | 98 | // Recall the pertinent tag in the template ... 99 | // ' <div ht-sidebar when-done-animating="vm.sidebarReady(42)" > 100 | // therefore, the directive looks for scope.vm.sidebarReady 101 | // and should call that method with the value '42' 102 | scope.vm = {sidebarReady: spy}; 103 | 104 | // tell angular to look again for that vm.sidebarReady property 105 | scope.$digest(); 106 | 107 | // spy not called until after click which triggers the animation 108 | expect(spy).not.to.have.been.called; 109 | 110 | // this click triggers an animation 111 | clickIt(); 112 | 113 | // verify that the vm's method (sidebarReady) was called with '42' 114 | // FYI: spy.args[0] is the array of args passed to sidebarReady() 115 | expect(spy).to.have.been.called; 116 | expect(spy).to.have.been.calledWith(42); 117 | }); 118 | }); 119 | 120 | /////// helpers ////// 121 | 122 | // put the dropdown in the 'menu open' state 123 | function openDropdown() { 124 | dropdownElement.addClass(isOpenClass); 125 | innerElement.css('display', 'block'); 126 | } 127 | 128 | // click the "menu" link 129 | function clickIt() { 130 | dropdownElement.trigger('click'); 131 | } 132 | 133 | // assert whether the "menu" link has the class that means 'is open' 134 | function hasIsOpenClass(isTrue) { 135 | var hasClass = dropdownElement.hasClass(isOpenClass); 136 | expect(hasClass).equal(!!isTrue, 137 | 'dropdown has the "is open" class is ' + hasClass); 138 | } 139 | 140 | // assert whether the dropdown container is 'block' (visible) or 'none' (hidden) 141 | function dropdownIsVisible(isTrue) { 142 | var display = innerElement.css('display'); 143 | expect(display).to.equal(isTrue ? 'block' : 'none', 144 | 'innerElement display value is ' + display); 145 | } 146 | }); 147 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/ht-top-nav.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .directive('htTopNav', htTopNav); 7 | 8 | /* @ngInject */ 9 | function htTopNav () { 10 | var directive = { 11 | bindToController: true, 12 | controller: TopNavController, 13 | controllerAs: 'vm', 14 | restrict: 'EA', 15 | scope: { 16 | 'navline': '=', 17 | 'author': '=' 18 | }, 19 | templateUrl: 'app/layout/ht-top-nav.html' 20 | }; 21 | 22 | /* @ngInject */ 23 | function TopNavController($translate) { 24 | var vm = this; 25 | vm.changeLanguage = changeLanguage; 26 | 27 | function changeLanguage(langKey) { 28 | $translate.use(langKey); 29 | } 30 | } 31 | 32 | return directive; 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/ht-top-nav.html: -------------------------------------------------------------------------------- 1 | <nav class="navbar navbar-fixed-top navbar-inverse"> 2 | <div class="navbar-header"> 3 | <a href="/" class="navbar-brand"><span class="brand-title" translate="Title">{{vm.navline.title}}</span></a> 4 | <a class="btn navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> 5 | <span class="icon-bar"></span> 6 | <span class="icon-bar"></span> 7 | <span class="icon-bar"></span> 8 | </a> 9 | </div> 10 | 11 | 12 | <div class="navbar-collapse collapse"> 13 | <div class="pull-right navbar-logo"> 14 | <div class="dropdown nav navbar-nav pull-left"> 15 | <button id="dLabel" type="button" data-toggle="dropdown" translate="Language"> 16 | <span class="caret"></span> 17 | </button> 18 | <ul class="dropdown-menu" role="menu"> 19 | <li> 20 | <a ng-click="vm.changeLanguage('en')" translate>Languages.English</a> 21 | </li> 22 | <li> 23 | <a ng-click="vm.changeLanguage('es')" translate>Languages.Spanish</a> 24 | </li> 25 | <li> 26 | <a ng-click="vm.changeLanguage('xx')">Language X</a> 27 | </li> 28 | </ul> 29 | </div> 30 | <ul class="nav navbar-nav pull-right"> 31 | <li> 32 | <a ng-href="{{vm.navline.link}}" target="_blank" translate="Created_By"> 33 | {{vm.navline.text}} 34 | </a> 35 | </li> 36 | <li class="dropdown dropdown-big"> 37 | <a href="http://www.angularjs.org" target="_blank"> 38 | <img src="images/AngularJS-small.png" /> 39 | </a> 40 | </li> 41 | <li> 42 | <a href="http://www.gulpjs.com/" target="_blank"> 43 | <img src="images/gulp-tiny.png" /> 44 | </a> 45 | </li> 46 | </ul> 47 | </div> 48 | </div> 49 | </nav> 50 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.layout', ['app.core']); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/shell.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('ShellController', ShellController); 7 | 8 | ShellController.$inject = ['$rootScope', '$timeout', 'config', 'logger', '$http']; 9 | /* @ngInject */ 10 | function ShellController($rootScope, $timeout, config, logger, $http) { 11 | var vm = this; 12 | vm.busyMessage = 'Please wait ...'; 13 | vm.isBusy = true; 14 | $rootScope.showSplash = true; 15 | vm.navline = { 16 | title: config.appTitle, 17 | text: 'Created by John Papa', 18 | link: 'http://twitter.com/john_papa' 19 | }; 20 | vm.author = 'Brian Clark'; 21 | 22 | activate(); 23 | 24 | function activate() { 25 | logger.success(config.appTitle + ' loaded!', null); 26 | hideSplash(); 27 | } 28 | 29 | function hideSplash() { 30 | //Force a 1 second delay so we can see the splash. 31 | $timeout(function() { 32 | $rootScope.showSplash = false; 33 | }, 1000); 34 | } 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/shell.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('ShellController', function() { 3 | var controller; 4 | 5 | beforeEach(function() { 6 | bard.appModule('app.layout'); 7 | bard.inject('$controller', '$q', '$rootScope', '$timeout', 'dataservice'); 8 | }); 9 | 10 | beforeEach(function () { 11 | controller = $controller('ShellController'); 12 | $rootScope.$apply(); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | describe('Shell controller', function() { 18 | it('should be created successfully', function () { 19 | expect(controller).to.be.defined; 20 | }); 21 | 22 | it('should show splash screen', function () { 23 | expect($rootScope.showSplash).to.be.true; 24 | }); 25 | 26 | it('should hide splash screen after timeout', function (done) { 27 | $timeout(function() { 28 | expect($rootScope.showSplash).to.be.false; 29 | done(); 30 | }, 1000); 31 | $timeout.flush(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/shell.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="ShellController as vm"> 2 | <header class="clearfix"> 3 | <ht-top-nav navline="vm.navline"></ht-top-nav> 4 | </header> 5 | <section id="content" class="content"> 6 | <div ng-include="'app/layout/sidebar.html'"></div> 7 | 8 | <div ui-view class="shuffle-animation"></div> 9 | 10 | <div ngplus-overlay 11 | ngplus-overlay-delay-in="50" 12 | ngplus-overlay-delay-out="700" 13 | ngplus-overlay-animation="dissolve-animation"> 14 | <img src="images/busy.gif"/> 15 | 16 | <div class="page-spinner-message overlay-message">{{vm.busyMessage}}</div> 17 | </div> 18 | </section> 19 | </div> 20 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/sidebar.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('SidebarController', SidebarController); 7 | 8 | SidebarController.$inject = ['$state', 'routerHelper']; 9 | /* @ngInject */ 10 | function SidebarController($state, routerHelper) { 11 | var vm = this; 12 | var states = routerHelper.getStates(); 13 | vm.isCurrent = isCurrent; 14 | 15 | activate(); 16 | 17 | function activate() { getNavRoutes(); } 18 | 19 | function getNavRoutes() { 20 | vm.navRoutes = states.filter(function(r) { 21 | return r.settings && r.settings.nav; 22 | }).sort(function(r1, r2) { 23 | return r1.settings.nav - r2.settings.nav; 24 | }); 25 | } 26 | 27 | function isCurrent(route) { 28 | if (!route.title || !$state.current || !$state.current.title) { 29 | return ''; 30 | } 31 | var menuName = route.title; 32 | return $state.current.title.substr(0, menuName.length) === menuName ? 'current' : ''; 33 | } 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/sidebar.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('layout', function() { 3 | describe('sidebar', function() { 4 | var controller; 5 | var views = { 6 | dashboard: 'app/dashboard/dashboard.html', 7 | customers: 'app/customers/customers.html' 8 | }; 9 | 10 | beforeEach(function() { 11 | module('app.layout', bard.fakeToastr); 12 | bard.inject('$controller', '$httpBackend', '$location', 13 | '$rootScope', '$state', 'routerHelper'); 14 | }); 15 | 16 | beforeEach(function() { 17 | routerHelper.configureStates(mockData.getMockStates(), '/'); 18 | controller = $controller('SidebarController'); 19 | $rootScope.$apply(); 20 | }); 21 | 22 | bard.verifyNoOutstandingHttpRequests(); 23 | 24 | it('should have isCurrent() for / to return `current`', function() { 25 | $location.path('/'); 26 | expect(controller.isCurrent($state.current)).to.equal('current'); 27 | }); 28 | 29 | it('should have isCurrent() for /customers to return `current`', function() { 30 | $location.path('/customers'); 31 | expect(controller.isCurrent($state.current)).to.equal('current'); 32 | }); 33 | 34 | it('should have isCurrent() for non route not return `current`', function() { 35 | $location.path('/invalid'); 36 | expect(controller.isCurrent({title: 'invalid'})).not.to.equal('current'); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/layout/sidebar.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="SidebarController as vm"> 2 | <ht-sidebar when-done-animating="vm.sidebarReady()"> 3 | <div class="sidebar-filler"></div> 4 | <div class="sidebar-dropdown"><a href="#">Menu</a></div> 5 | <div class="sidebar-inner"> 6 | <div class="sidebar-widget"></div> 7 | <ul class="navi"> 8 | <li class="nlightblue fade-selection-animation" ng-class="vm.isCurrent(r)" 9 | ng-repeat="r in vm.navRoutes"> 10 | <a ui-sref="{{r.name}}" 11 | translate="{{r.title}}"></a> 12 | </li> 13 | </ul> 14 | </div> 15 | </ht-sidebar> 16 | </div> 17 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/ht-img-person.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htImgPerson', htImgPerson); 7 | 8 | htImgPerson.$inject = ['config']; 9 | /* @ngInject */ 10 | function htImgPerson (config) { 11 | //Usage: 12 | //<img ht-img-person="{{person.imageSource}}"/> 13 | var basePath = config.imageBasePath; 14 | var unknownImage = config.unknownPersonImageSource; 15 | var directive = { 16 | link: link, 17 | restrict: 'A' 18 | }; 19 | return directive; 20 | 21 | function link(scope, element, attrs) { 22 | attrs.$observe('htImgPerson', function (value) { 23 | value = basePath + (value || unknownImage); 24 | attrs.$set('src', value); 25 | }); 26 | } 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/ht-widget-header.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htWidgetHeader', htWidgetHeader); 7 | 8 | /* @ngInject */ 9 | function htWidgetHeader() { 10 | //Usage: 11 | //<div ht-widget-header title="vm.map.title"></div> 12 | // Creates: 13 | // <div ht-widget-header="" 14 | // title="Movie" 15 | // allow-collapse="true" </div> 16 | var directive = { 17 | scope: { 18 | 'title': '@', 19 | 'subtitle': '@', 20 | 'rightText': '@', 21 | 'allowCollapse': '@' 22 | }, 23 | templateUrl: 'app/widgets/widget-header.html', 24 | restrict: 'EA' 25 | }; 26 | return directive; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/ht-widget-towel.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htWidgetTowel', htWidgetTowel); 7 | 8 | /* @ngInject */ 9 | function htWidgetTowel() { 10 | //Usage: 11 | //<div ht-widget-towel name="vm.map.name"></div> 12 | // Creates: 13 | // <div ht-widget-towel="" 14 | // name="Beach Towel" 15 | // price="10.00" 16 | // allow-collapse="true" </div> 17 | var directive = { 18 | scope: { 19 | name: '@', 20 | price: '@' 21 | }, 22 | templateUrl: 'app/widgets/widget-ht-towel.html', 23 | restrict: 'EA' 24 | }; 25 | return directive; 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/widget-header.html: -------------------------------------------------------------------------------- 1 | <div class="widget-head"> 2 | <div class="page-title pull-left" translate>{{title}}</div> 3 | <small class="page-title-subtle" ng-show="subtitle">({{subtitle}})</small> 4 | <div class="widget-icons pull-right"></div> 5 | <small class="pull-right page-title-subtle" ng-show="rightText">{{rightText}}</small> 6 | <div class="clearfix"></div> 7 | </div> -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/widget-ht-towel.html: -------------------------------------------------------------------------------- 1 | <div class="widget-head"> 2 | <div> 3 | <small class="pull-left">{{price}}</small> 4 | <div class="pull-left">{{name}}</div> 5 | </div> 6 | <br> 7 | <img src="images/AngularJS-small.png"></img> 8 | <div class="widget-icons pull-right"></div> 9 | <div class="clearfix"></div> 10 | </div> -------------------------------------------------------------------------------- /hot-towel-done/src/client/app/widgets/widgets.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.widgets', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/images/AngularJS-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/src/client/images/AngularJS-small.png -------------------------------------------------------------------------------- /hot-towel-done/src/client/images/busy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/src/client/images/busy.gif -------------------------------------------------------------------------------- /hot-towel-done/src/client/images/gulp-tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/src/client/images/gulp-tiny.png -------------------------------------------------------------------------------- /hot-towel-done/src/client/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/src/client/images/icon.png -------------------------------------------------------------------------------- /hot-towel-done/src/client/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html ng-app="app"> 3 | 4 | <head> 5 | <style> 6 | /* This helps the ng-show/ng-hide animations start at the right place. */ 7 | /* Since Angular has this but needs to load, this gives us the class early. */ 8 | 9 | .ng-hide { 10 | display: none!important; 11 | } 12 | </style> 13 | <title ng-bind="title">hot-towel 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 | Loading . . . 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 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/specs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Spec Runner 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Spec Runner

24 |

Make sure the REMOTE server is running
25 | Click on a description title to narrow the scope to just its specs 26 | (see " 27 | ?grep" in address bar).
28 | Click on a spec title to see the test implementation.
29 | Click on page title to start over. 30 |

31 | 32 |
33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/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 | if (!Function.prototype.bind) { 9 | /*jshint freeze: false */ 10 | Function.prototype.bind = function (oThis) { 11 | if (typeof this !== 'function') { 12 | // closest thing possible to the ECMAScript 5 13 | // internal IsCallable function 14 | var msg = 'Function.prototype.bind - what is trying to be bound is not callable'; 15 | throw new TypeError(msg); 16 | } 17 | 18 | var aArgs = Array.prototype.slice.call(arguments, 1), 19 | fToBind = this, 20 | FuncNoOp = function () {}, 21 | fBound = function () { 22 | return fToBind.apply(this instanceof FuncNoOp && oThis ? this : oThis, 23 | aArgs.concat(Array.prototype.slice.call(arguments))); 24 | }; 25 | 26 | FuncNoOp.prototype = this.prototype; 27 | fBound.prototype = new FuncNoOp(); 28 | 29 | return fBound; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /hot-towel-done/src/client/test-helpers/mock-data.js: -------------------------------------------------------------------------------- 1 | /* jshint -W079 */ 2 | var mockData = (function() { 3 | return { 4 | getMockPeople: getMockPeople, 5 | getMockStates: getMockStates 6 | }; 7 | 8 | function getMockStates() { 9 | return [ 10 | { 11 | state: 'dashboard', 12 | config: { 13 | url: '/', 14 | templateUrl: 'app/dashboard/dashboard.html', 15 | title: 'dashboard', 16 | settings: { 17 | nav: 1, 18 | content: ' Dashboard' 19 | } 20 | } 21 | } 22 | ]; 23 | } 24 | 25 | function getMockPeople() { 26 | return [ 27 | {firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 28 | {firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 29 | {firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 30 | {firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 31 | {firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 32 | {firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 33 | {firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'} 34 | ]; 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /hot-towel-done/src/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 favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | var port = process.env.PORT || 8001; 10 | var four0four = require('./utils/404')(); 11 | 12 | var environment = process.env.NODE_ENV; 13 | 14 | app.use(favicon(__dirname + '/favicon.ico')); 15 | app.use(bodyParser.urlencoded({extended: true})); 16 | app.use(bodyParser.json()); 17 | app.use(logger('dev')); 18 | 19 | app.use('/api', require('./routes')); 20 | 21 | console.log('About to crank up node'); 22 | console.log('PORT=' + port); 23 | console.log('NODE_ENV=' + environment); 24 | 25 | switch (environment){ 26 | case 'build': 27 | console.log('** BUILD **'); 28 | app.use(express.static('./build/')); 29 | // Any invalid calls for templateUrls are under app/* and should return 404 30 | app.use('/app/*', function(req, res, next) { 31 | four0four.send404(req, res); 32 | }); 33 | // Any deep link calls should return index.html 34 | app.use('/*', express.static('./build/index.html')); 35 | break; 36 | default: 37 | console.log('** DEV **'); 38 | app.use(express.static('./src/client/')); 39 | app.use(express.static('./')); 40 | app.use(express.static('./tmp')); 41 | // Any invalid calls for templateUrls are under app/* and should return 404 42 | app.use('/app/*', function(req, res, next) { 43 | four0four.send404(req, res); 44 | }); 45 | // Any deep link calls should return index.html 46 | app.use('/*', express.static('./src/client/index.html')); 47 | break; 48 | } 49 | 50 | app.listen(port, function() { 51 | console.log('Express server listening on port ' + port); 52 | console.log('env = ' + app.get('env') + 53 | '\n__dirname = ' + __dirname + 54 | '\nprocess.cwd = ' + process.cwd()); 55 | }); 56 | -------------------------------------------------------------------------------- /hot-towel-done/src/server/data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | people: getPeople() 3 | }; 4 | 5 | function getPeople() { 6 | return [ 7 | {id: 1, firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 8 | {id: 2, firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 9 | {id: 3, firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 10 | {id: 4, firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 11 | {id: 5, firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 12 | {id: 6, firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 13 | {id: 7, firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'}, 14 | {id: 8, firstName: 'Aaron', lastName: 'Jinglehiemer', age: 22, location: 'Utah'} 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /hot-towel-done/src/server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel-done/src/server/favicon.ico -------------------------------------------------------------------------------- /hot-towel-done/src/server/routes.js: -------------------------------------------------------------------------------- 1 | var router = require('express').Router(); 2 | var four0four = require('./utils/404')(); 3 | var data = require('./data'); 4 | 5 | router.get('/people', getPeople); 6 | router.get('/person/:id', getPerson); 7 | router.get('/*', four0four.notFoundMiddleware); 8 | 9 | module.exports = router; 10 | 11 | ////////////// 12 | 13 | function getPeople(req, res, next) { 14 | res.status(200).send(data.people); 15 | } 16 | 17 | function getPerson(req, res, next) { 18 | var id = +req.params.id; 19 | var person = data.people.filter(function(p) { 20 | return p.id === id; 21 | })[0]; 22 | 23 | if (person) { 24 | res.status(200).send(person); 25 | } else { 26 | four0four.send404(req, res, 'person ' + id + ' not found'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hot-towel-done/src/server/utils/404.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var service = { 3 | notFoundMiddleware: notFoundMiddleware, 4 | send404: send404 5 | }; 6 | return service; 7 | 8 | function notFoundMiddleware(req, res, next) { 9 | send404(req, res, 'API endpoint not found'); 10 | } 11 | 12 | function send404(req, res, description) { 13 | var data = { 14 | status: 404, 15 | message: 'Not Found', 16 | description: description, 17 | url: req.url 18 | }; 19 | res.status(404) 20 | .send(data) 21 | .end(); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /hot-towel/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "scripts": { 4 | "postinstall": "gulp wiredep" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /hot-towel/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 3 | node_modules/ 4 | bower_components/ 5 | 6 | # other 7 | .tmp 8 | /hot-towel/report/ 9 | /build/ -------------------------------------------------------------------------------- /hot-towel/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["node_modules/**", "bower_components/**"], 3 | 4 | "requireCurlyBraces": [ 5 | "if", 6 | "else", 7 | "for", 8 | "while", 9 | "do", 10 | "try", 11 | "catch" 12 | ], 13 | "requireOperatorBeforeLineBreak": true, 14 | "requireCamelCaseOrUpperCaseIdentifiers": true, 15 | "maximumLineLength": { 16 | "value": 200, 17 | "allowComments": true, 18 | "allowRegex": true 19 | }, 20 | "validateIndentation": 4, 21 | "validateQuoteMarks": "'", 22 | 23 | "disallowMultipleLineStrings": true, 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": false, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowMultipleVarDecl": null, 28 | 29 | "requireSpaceAfterKeywords": [ 30 | "if", 31 | "else", 32 | "for", 33 | "while", 34 | "do", 35 | "switch", 36 | "return", 37 | "try", 38 | "catch" 39 | ], 40 | "requireSpaceBeforeBinaryOperators": [ 41 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 42 | "&=", "|=", "^=", "+=", 43 | 44 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 45 | "|", "^", "&&", "||", "===", "==", ">=", 46 | "<=", "<", ">", "!=", "!==" 47 | ], 48 | "requireSpaceAfterBinaryOperators": true, 49 | "requireSpacesInConditionalExpression": true, 50 | "requireSpaceBeforeBlockStatements": true, 51 | "requireLineFeedAtFileEnd": true, 52 | "disallowSpacesInsideObjectBrackets": "all", 53 | "disallowSpacesInsideArrayBrackets": "all", 54 | "disallowSpacesInsideParentheses": true, 55 | 56 | "jsDoc": { 57 | "checkAnnotations": true, 58 | "checkParamNames": true, 59 | "requireParamTypes": true, 60 | "checkReturnTypes": true, 61 | "checkTypes": true 62 | }, 63 | 64 | "disallowMultipleLineBreaks": true, 65 | 66 | "disallowCommaBeforeLineBreak": null, 67 | "disallowDanglingUnderscores": null, 68 | "disallowEmptyBlocks": null, 69 | "disallowTrailingComma": null, 70 | "requireCommaBeforeLineBreak": null, 71 | "requireDotNotation": null, 72 | "requireMultipleVarDecl": null, 73 | "requireParenthesesAroundIIFE": true 74 | } 75 | -------------------------------------------------------------------------------- /hot-towel/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": 50, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /hot-towel/README.md: -------------------------------------------------------------------------------- 1 | # hot-towel 2 | 3 | **Generated from HotTowel Angular** 4 | 5 | >*Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* 6 | 7 | >More details about the styles and patterns used in this app can be found in my [Angular Style Guide](https://github.com/johnpapa/angularjs-styleguide) and my [Angular Patterns: Clean Code](http://jpapa.me/ngclean) course at [Pluralsight](http://pluralsight.com/training/Authors/Details/john-papa) and working in teams. 8 | 9 | ## Prerequisites 10 | 11 | 1. Install [Node.js](http://nodejs.org) 12 | - on OSX use [homebrew](http://brew.sh) `brew install node` 13 | - on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` 14 | 15 | 2. Install Yeoman `npm install -g yo` 16 | 17 | 3. Install these NPM packages globally 18 | 19 | ```bash 20 | npm install -g bower gulp nodemon 21 | ``` 22 | 23 | >Refer to these [instructions on how to not require sudo](https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md) 24 | 25 | ## Running HotTowel 26 | 27 | ### Linting 28 | - Run code analysis using `gulp vet`. This runs jshint, jscs, and plato. 29 | 30 | ### Tests 31 | - Run the unit tests using `gulp test` (via karma, mocha, sinon). 32 | 33 | ### Running in dev mode 34 | - Run the project with `gulp serve-dev` 35 | 36 | - opens it in a browser and updates the browser with any files changes. 37 | 38 | ### Building the project 39 | - Build the optimized project using `gulp build` 40 | - This create the optimized code for the project and puts it in the build folder 41 | 42 | ### Running the optimized code 43 | - Run the optimize project from the build folder with `gulp serve-build` 44 | 45 | ## Exploring HotTowel 46 | HotTowel Angular starter project 47 | 48 | ### Structure 49 | The structure also contains a gulpfile.js and a server folder. The server is there just so we can serve the app using node. Feel free to use any server you wish. 50 | 51 | /src 52 | /client 53 | /app 54 | /content 55 | 56 | ### Installing Packages 57 | When you generate the project it should run these commands, but if you notice missing packages, run these again: 58 | 59 | - `npm install` 60 | - `bower install` 61 | 62 | ### The Modules 63 | The app has 4 feature modules and depends on a series of external modules and custom but cross-app modules 64 | 65 | ``` 66 | app --> [ 67 | app.admin --> [ 68 | app.core, 69 | app.widgets 70 | ], 71 | app.dashboard --> [ 72 | app.core, 73 | app.widgets 74 | ], 75 | app.layout --> [ 76 | app.core 77 | ], 78 | app.widgets, 79 | app.core --> [ 80 | ngAnimate, 81 | ngSanitize, 82 | ui.router, 83 | blocks.exception, 84 | blocks.logger, 85 | blocks.router 86 | ] 87 | ] 88 | ``` 89 | 90 | #### core Module 91 | Core modules are ones that are shared throughout the entire application and may be customized for the specific application. Example might be common data services. 92 | 93 | This is an aggregator of modules that the application will need. The `core` module takes the blocks, common, and Angular sub-modules as dependencies. 94 | 95 | #### blocks Modules 96 | Block modules are reusable blocks of code that can be used across projects simply by including them as dependencies. 97 | 98 | ##### blocks.logger Module 99 | The `blocks.logger` module handles logging across the Angular app. 100 | 101 | ##### blocks.exception Module 102 | The `blocks.exception` module handles exceptions across the Angular app. 103 | 104 | It depends on the `blocks.logger` module, because the implementation logs the exceptions. 105 | 106 | ##### blocks.router Module 107 | The `blocks.router` module contains a routing helper module that assists in adding routes to the $routeProvider. 108 | 109 | ## Gulp Tasks 110 | 111 | ### Task Listing 112 | 113 | - `gulp help` 114 | 115 | Displays all of the available gulp tasks. 116 | 117 | ### Code Analysis 118 | 119 | - `gulp vet` 120 | 121 | Performs static code analysis on all javascript files. Runs jshint and jscs. 122 | 123 | - `gulp vet --verbose` 124 | 125 | Displays all files affected and extended information about the code analysis. 126 | 127 | - `gulp plato` 128 | 129 | Performs code analysis using plato on all javascript files. Plato generates a report in the reports folder. 130 | 131 | ### Testing 132 | 133 | - `gulp serve-specs` 134 | 135 | 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`. 136 | 137 | - `gulp test` 138 | 139 | Runs all unit tests using karma runner, mocha, chai and sinon with phantomjs. Depends on vet task, for code analysis. 140 | 141 | - `gulp test --startServers` 142 | 143 | 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. 144 | 145 | - `gulp autotest` 146 | 147 | Runs a watch to run all unit tests. 148 | 149 | - `gulp autotest --startServers` 150 | 151 | 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. 152 | 153 | ### Cleaning Up 154 | 155 | - `gulp clean` 156 | 157 | Remove all files from the build and temp folders 158 | 159 | - `gulp clean-images` 160 | 161 | Remove all images from the build folder 162 | 163 | - `gulp clean-code` 164 | 165 | Remove all javascript and html from the build folder 166 | 167 | - `gulp clean-fonts` 168 | 169 | Remove all fonts from the build folder 170 | 171 | - `gulp clean-styles` 172 | 173 | Remove all styles from the build folder 174 | 175 | ### Fonts and Images 176 | 177 | - `gulp fonts` 178 | 179 | Copy all fonts from source to the build folder 180 | 181 | - `gulp images` 182 | 183 | Copy all images from source to the build folder 184 | 185 | ### Styles 186 | 187 | - `gulp styles` 188 | 189 | Compile less files to CSS, add vendor prefixes, and copy to the build folder 190 | 191 | ### Bower Files 192 | 193 | - `gulp wiredep` 194 | 195 | Looks up all bower components' main files and JavaScript source code, then adds them to the `index.html`. 196 | 197 | The `.bowerrc` file also runs this as a postinstall task whenever `bower install` is run. 198 | 199 | ### Angular HTML Templates 200 | 201 | - `gulp templatecache` 202 | 203 | 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. 204 | 205 | - `gulp templatecache --verbose` 206 | 207 | Displays all files affected by the task. 208 | 209 | ### Serving Development Code 210 | 211 | - `gulp serve-dev` 212 | 213 | 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. 214 | 215 | - `gulp serve-dev --nosync` 216 | 217 | Serves the development code without launching the browser. 218 | 219 | - `gulp serve-dev --debug` 220 | 221 | Launch debugger with node-inspector. 222 | 223 | - `gulp serve-dev --debug-brk` 224 | 225 | Launch debugger and break on 1st line with node-inspector. 226 | 227 | ### Building Production Code 228 | 229 | - `gulp optimize` 230 | 231 | Optimize all javascript and styles, move to a build folder, and inject them into the new index.html 232 | 233 | - `gulp build` 234 | 235 | Copies all fonts, copies images and runs `gulp optimize` to build the production code to the build folder. 236 | 237 | ### Serving Production Code 238 | 239 | - `gulp serve-build` 240 | 241 | Serve the optimized code from the build folder and launch it in a browser. 242 | 243 | - `gulp serve-build --nosync` 244 | 245 | Serve the optimized code from the build folder and manually launch the browser. 246 | 247 | - `gulp serve-build --debug` 248 | 249 | Launch debugger with node-inspector. 250 | 251 | - `gulp serve-build --debug-brk` 252 | 253 | Launch debugger and break on 1st line with node-inspector. 254 | 255 | ### Bumping Versions 256 | 257 | - `gulp bump` 258 | 259 | Bump the minor version using semver. 260 | --type=patch // default 261 | --type=minor 262 | --type=major 263 | --type=pre 264 | --ver=1.2.3 // specific version 265 | 266 | ## License 267 | 268 | MIT 269 | -------------------------------------------------------------------------------- /hot-towel/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hot-towel", 3 | "version": "0.0.1", 4 | "description": "hot-towel", 5 | "authors": [], 6 | "license": "MIT", 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests" 13 | ], 14 | "devDependencies": { 15 | "angular-mocks": "^1.4.5", 16 | "sinon": "http://sinonjs.org/releases/sinon-1.12.1.js", 17 | "bardjs": "^0.1.4" 18 | }, 19 | "dependencies": { 20 | "jquery": "^2.1.4", 21 | "angular": "^1.4.5", 22 | "angular-sanitize": "^1.4.5", 23 | "bootstrap": "~3.2.0", 24 | "extras.angular.plus": "^0.9.2", 25 | "font-awesome": "^4.3.0", 26 | "moment": "^2.10.3", 27 | "angular-ui-router": "^0.2.15", 28 | "toastr": "^2.1.1", 29 | "angular-animate": "^1.4.5", 30 | "angular-translate": "~2.8.1", 31 | "angular-translate-handler-log": "~2.8.1", 32 | "angular-translate-interpolation-messageformat": "~2.8.1", 33 | "angular-translate-loader-partial": "~2.8.1", 34 | "angular-translate-loader-static-files": "~2.8.1" 35 | }, 36 | "resolutions": { 37 | "angular": "1.3.17" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /hot-towel/gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var client = './src/client/'; 3 | var server = './src/server/'; 4 | var clientApp = client + 'app/'; 5 | var report = './report/'; 6 | var root = './'; 7 | var specRunnerFile = 'specs.html'; 8 | var temp = './.tmp/'; 9 | var wiredep = require('wiredep'); 10 | var bowerFiles = wiredep({devDependencies: true})['js']; 11 | var bower = { 12 | json: require('./bower.json'), 13 | directory: './bower_components/', 14 | ignorePath: '../..' 15 | }; 16 | var nodeModules = 'node_modules'; 17 | 18 | var config = { 19 | /** 20 | * File paths 21 | */ 22 | // all javascript that we want to vet 23 | alljs: [ 24 | './src/**/*.js', 25 | './*.js' 26 | ], 27 | build: './build/', 28 | client: client, 29 | css: temp + 'styles.css', 30 | fonts: bower.directory + 'font-awesome/fonts/**/*.*', 31 | html: client + '**/*.html', 32 | htmltemplates: clientApp + '**/*.html', 33 | images: client + 'images/**/*.*', 34 | index: client + 'index.html', 35 | // app js, with no specs 36 | js: [ 37 | clientApp + '**/*.module.js', 38 | clientApp + '**/*.js', 39 | '!' + clientApp + '**/*.spec.js' 40 | ], 41 | jsOrder: [ 42 | '**/app.module.js', 43 | '**/*.module.js', 44 | '**/*.js' 45 | ], 46 | less: client + 'styles/styles.less', 47 | report: report, 48 | root: root, 49 | server: server, 50 | source: 'src/', 51 | stubsjs: [ 52 | bower.directory + 'angular-mocks/angular-mocks.js', 53 | client + 'stubs/**/*.js' 54 | ], 55 | temp: temp, 56 | 57 | /** 58 | * optimized files 59 | */ 60 | optimized: { 61 | app: 'app.js', 62 | lib: 'lib.js' 63 | }, 64 | 65 | /** 66 | * plato 67 | */ 68 | plato: {js: clientApp + '**/*.js'}, 69 | 70 | /** 71 | * browser sync 72 | */ 73 | browserReloadDelay: 1000, 74 | 75 | /** 76 | * template cache 77 | */ 78 | templateCache: { 79 | file: 'templates.js', 80 | options: { 81 | module: 'app.core', 82 | root: 'app/', 83 | standalone: false 84 | } 85 | }, 86 | 87 | /** 88 | * Bower and NPM files 89 | */ 90 | bower: bower, 91 | packages: [ 92 | './package.json', 93 | './bower.json' 94 | ], 95 | 96 | /** 97 | * specs.html, our HTML spec runner 98 | */ 99 | specRunner: client + specRunnerFile, 100 | specRunnerFile: specRunnerFile, 101 | 102 | /** 103 | * The sequence of the injections into specs.html: 104 | * 1 testlibraries 105 | * mocha setup 106 | * 2 bower 107 | * 3 js 108 | * 4 spechelpers 109 | * 5 specs 110 | * 6 templates 111 | */ 112 | testlibraries: [ 113 | nodeModules + '/mocha/mocha.js', 114 | nodeModules + '/chai/chai.js', 115 | nodeModules + '/sinon-chai/lib/sinon-chai.js' 116 | ], 117 | specHelpers: [client + 'test-helpers/*.js'], 118 | specs: [clientApp + '**/*.spec.js'], 119 | serverIntegrationSpecs: [client + '/tests/server-integration/**/*.spec.js'], 120 | 121 | /** 122 | * Node settings 123 | */ 124 | nodeServer: server + 'app.js', 125 | defaultPort: '8001' 126 | }; 127 | 128 | /** 129 | * wiredep and bower settings 130 | */ 131 | config.getWiredepDefaultOptions = function() { 132 | var options = { 133 | bowerJson: config.bower.json, 134 | directory: config.bower.directory, 135 | ignorePath: config.bower.ignorePath 136 | }; 137 | return options; 138 | }; 139 | 140 | /** 141 | * karma settings 142 | */ 143 | config.karma = getKarmaOptions(); 144 | 145 | return config; 146 | 147 | //////////////// 148 | 149 | function getKarmaOptions() { 150 | var options = { 151 | files: [].concat( 152 | bowerFiles, 153 | config.specHelpers, 154 | clientApp + '**/*.module.js', 155 | clientApp + '**/*.js', 156 | temp + config.templateCache.file, 157 | config.serverIntegrationSpecs 158 | ), 159 | exclude: [], 160 | coverage: { 161 | dir: report + 'coverage', 162 | reporters: [ 163 | // reporters not supporting the `file` property 164 | {type: 'html', subdir: 'report-html'}, 165 | {type: 'lcov', subdir: 'report-lcov'}, 166 | {type: 'text-summary'} //, subdir: '.', file: 'text-summary.txt'} 167 | ] 168 | }, 169 | preprocessors: {} 170 | }; 171 | options.preprocessors[clientApp + '**/!(*.spec)+(.js)'] = ['coverage']; 172 | return options; 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /hot-towel/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/gulp.png -------------------------------------------------------------------------------- /hot-towel/gulpfile.js: -------------------------------------------------------------------------------- 1 | var args = require('yargs').argv; 2 | var browserSync = require('browser-sync'); 3 | var config = require('./gulp.config')(); 4 | var del = require('del'); 5 | var glob = require('glob'); 6 | var gulp = require('gulp'); 7 | var path = require('path'); 8 | var _ = require('lodash'); 9 | var $ = require('gulp-load-plugins')({lazy: true}); 10 | 11 | var colors = $.util.colors; 12 | var envenv = $.util.env; 13 | var port = process.env.PORT || config.defaultPort; 14 | 15 | /** 16 | * yargs variables can be passed in to alter the behavior, when present. 17 | * Example: gulp serve-dev 18 | * 19 | * --verbose : Various tasks will produce more output to the console. 20 | * --nosync : Don't launch the browser with browser-sync when serving code. 21 | * --debug : Launch debugger with node-inspector. 22 | * --debug-brk: Launch debugger and break on 1st line with node-inspector. 23 | * --startServers: Will start servers for midway tests on the test task. 24 | */ 25 | 26 | /** 27 | * List the available gulp tasks 28 | */ 29 | gulp.task('help', $.taskListing); 30 | gulp.task('default', ['help']); 31 | 32 | /** 33 | * vet the code and create coverage report 34 | * @return {Stream} 35 | */ 36 | gulp.task('vet', function() { 37 | log('Analyzing source with JSHint and JSCS'); 38 | 39 | return gulp 40 | .src(config.alljs) 41 | .pipe($.if(args.verbose, $.print())) 42 | .pipe($.jshint()) 43 | .pipe($.jshint.reporter('jshint-stylish', {verbose: true})) 44 | .pipe($.jshint.reporter('fail')) 45 | .pipe($.jscs()); 46 | }); 47 | 48 | /** 49 | * Create a visualizer report 50 | */ 51 | gulp.task('plato', function(done) { 52 | log('Analyzing source with Plato'); 53 | log('Browse to /report/plato/index.html to see Plato results'); 54 | 55 | startPlatoVisualizer(done); 56 | }); 57 | 58 | /** 59 | * Compile less to css 60 | * @return {Stream} 61 | */ 62 | gulp.task('styles', ['clean-styles'], function() { 63 | log('Compiling Less --> CSS'); 64 | 65 | return gulp 66 | .src(config.less) 67 | .pipe($.plumber()) // exit gracefully if something fails after this 68 | .pipe($.less()) 69 | // .on('error', errorLogger) // more verbose and dupe output. requires emit. 70 | .pipe($.autoprefixer({browsers: ['last 2 version', '> 5%']})) 71 | .pipe(gulp.dest(config.temp)); 72 | }); 73 | 74 | /** 75 | * Copy fonts 76 | * @return {Stream} 77 | */ 78 | gulp.task('fonts', ['clean-fonts'], function() { 79 | log('Copying fonts'); 80 | 81 | return gulp 82 | .src(config.fonts) 83 | .pipe(gulp.dest(config.build + 'fonts')); 84 | }); 85 | 86 | /** 87 | * Compress images 88 | * @return {Stream} 89 | */ 90 | gulp.task('images', ['clean-images'], function() { 91 | log('Compressing and copying images'); 92 | 93 | return gulp 94 | .src(config.images) 95 | .pipe($.imagemin({optimizationLevel: 4})) 96 | .pipe(gulp.dest(config.build + 'images')); 97 | }); 98 | 99 | gulp.task('less-watcher', function() { 100 | gulp.watch([config.less], ['styles']); 101 | }); 102 | 103 | /** 104 | * Create $templateCache from the html templates 105 | * @return {Stream} 106 | */ 107 | gulp.task('templatecache', ['clean-code'], function() { 108 | log('Creating an AngularJS $templateCache'); 109 | 110 | return gulp 111 | .src(config.htmltemplates) 112 | .pipe($.if(args.verbose, $.bytediff.start())) 113 | .pipe($.minifyHtml({empty: true})) 114 | .pipe($.if(args.verbose, $.bytediff.stop(bytediffFormatter))) 115 | .pipe($.angularTemplatecache( 116 | config.templateCache.file, 117 | config.templateCache.options 118 | )) 119 | .pipe(gulp.dest(config.temp)); 120 | }); 121 | 122 | /** 123 | * Wire-up the bower dependencies 124 | * @return {Stream} 125 | */ 126 | gulp.task('wiredep', function() { 127 | log('Wiring the bower dependencies into the html'); 128 | 129 | var wiredep = require('wiredep').stream; 130 | var options = config.getWiredepDefaultOptions(); 131 | 132 | // Only include stubs if flag is enabled 133 | var js = args.stubs ? [].concat(config.js, config.stubsjs) : config.js; 134 | 135 | return gulp 136 | .src(config.index) 137 | .pipe(wiredep(options)) 138 | .pipe(inject(js, '', config.jsOrder)) 139 | .pipe(gulp.dest(config.client)); 140 | }); 141 | 142 | gulp.task('inject', ['wiredep', 'styles', 'templatecache'], function() { 143 | log('Wire up css into the html, after files are ready'); 144 | 145 | return gulp 146 | .src(config.index) 147 | .pipe(inject(config.css)) 148 | .pipe(gulp.dest(config.client)); 149 | }); 150 | 151 | /** 152 | * Run the spec runner 153 | * @return {Stream} 154 | */ 155 | gulp.task('serve-specs', ['build-specs'], function(done) { 156 | log('run the spec runner'); 157 | serve(true /* isDev */, true /* specRunner */); 158 | done(); 159 | }); 160 | 161 | /** 162 | * Inject all the spec files into the specs.html 163 | * @return {Stream} 164 | */ 165 | gulp.task('build-specs', ['templatecache'], function(done) { 166 | log('building the spec runner'); 167 | 168 | var wiredep = require('wiredep').stream; 169 | var templateCache = config.temp + config.templateCache.file; 170 | var options = config.getWiredepDefaultOptions(); 171 | var specs = config.specs; 172 | 173 | if (args.startServers) { 174 | specs = [].concat(specs, config.serverIntegrationSpecs); 175 | } 176 | options.devDependencies = true; 177 | 178 | return gulp 179 | .src(config.specRunner) 180 | .pipe(wiredep(options)) 181 | .pipe(inject(config.js, '', config.jsOrder)) 182 | .pipe(inject(config.testlibraries, 'testlibraries')) 183 | .pipe(inject(config.specHelpers, 'spechelpers')) 184 | .pipe(inject(specs, 'specs', ['**/*'])) 185 | .pipe(inject(templateCache, 'templates')) 186 | .pipe(gulp.dest(config.client)); 187 | }); 188 | 189 | /** 190 | * Build everything 191 | * This is separate so we can run tests on 192 | * optimize before handling image or fonts 193 | */ 194 | gulp.task('build', ['optimize', 'images', 'fonts'], function() { 195 | log('Building everything'); 196 | 197 | var msg = { 198 | title: 'gulp build', 199 | subtitle: 'Deployed to the build folder', 200 | message: 'Running `gulp serve-build`' 201 | }; 202 | del(config.temp); 203 | log(msg); 204 | notify(msg); 205 | }); 206 | 207 | /** 208 | * Optimize all files, move to a build folder, 209 | * and inject them into the new index.html 210 | * @return {Stream} 211 | */ 212 | gulp.task('optimize', ['inject', 'test'], function() { 213 | log('Optimizing the js, css, and html'); 214 | 215 | var assets = $.useref.assets({searchPath: './'}); 216 | // Filters are named for the gulp-useref path 217 | var cssFilter = $.filter('**/*.css'); 218 | var jsAppFilter = $.filter('**/' + config.optimized.app); 219 | var jslibFilter = $.filter('**/' + config.optimized.lib); 220 | 221 | var templateCache = config.temp + config.templateCache.file; 222 | 223 | return gulp 224 | .src(config.index) 225 | .pipe($.plumber()) 226 | .pipe(inject(templateCache, 'templates')) 227 | .pipe(assets) // Gather all assets from the html with useref 228 | // Get the css 229 | .pipe(cssFilter) 230 | .pipe($.minifyCss()) 231 | .pipe(cssFilter.restore()) 232 | // Get the custom javascript 233 | .pipe(jsAppFilter) 234 | .pipe($.ngAnnotate({add: true})) 235 | .pipe($.uglify()) 236 | .pipe(getHeader()) 237 | .pipe(jsAppFilter.restore()) 238 | // Get the vendor javascript 239 | .pipe(jslibFilter) 240 | .pipe($.uglify()) // another option is to override wiredep to use min files 241 | .pipe(jslibFilter.restore()) 242 | // Take inventory of the file names for future rev numbers 243 | .pipe($.rev()) 244 | // Apply the concat and file replacement with useref 245 | .pipe(assets.restore()) 246 | .pipe($.useref()) 247 | // Replace the file names in the html with rev numbers 248 | .pipe($.revReplace()) 249 | .pipe(gulp.dest(config.build)); 250 | }); 251 | 252 | /** 253 | * Remove all files from the build, temp, and reports folders 254 | * @param {Function} done - callback when complete 255 | */ 256 | gulp.task('clean', function(done) { 257 | var delconfig = [].concat(config.build, config.temp, config.report); 258 | log('Cleaning: ' + $.util.colors.blue(delconfig)); 259 | del(delconfig, done); 260 | }); 261 | 262 | /** 263 | * Remove all fonts from the build folder 264 | * @param {Function} done - callback when complete 265 | */ 266 | gulp.task('clean-fonts', function(done) { 267 | clean(config.build + 'fonts/**/*.*', done); 268 | }); 269 | 270 | /** 271 | * Remove all images from the build folder 272 | * @param {Function} done - callback when complete 273 | */ 274 | gulp.task('clean-images', function(done) { 275 | clean(config.build + 'images/**/*.*', done); 276 | }); 277 | 278 | /** 279 | * Remove all styles from the build and temp folders 280 | * @param {Function} done - callback when complete 281 | */ 282 | gulp.task('clean-styles', function(done) { 283 | var files = [].concat( 284 | config.temp + '**/*.css', 285 | config.build + 'styles/**/*.css' 286 | ); 287 | clean(files, done); 288 | }); 289 | 290 | /** 291 | * Remove all js and html from the build and temp folders 292 | * @param {Function} done - callback when complete 293 | */ 294 | gulp.task('clean-code', function(done) { 295 | var files = [].concat( 296 | config.temp + '**/*.js', 297 | config.build + 'js/**/*.js', 298 | config.build + '**/*.html' 299 | ); 300 | clean(files, done); 301 | }); 302 | 303 | /** 304 | * Run specs once and exit 305 | * To start servers and run midway specs as well: 306 | * gulp test --startServers 307 | * @return {Stream} 308 | */ 309 | gulp.task('test', ['vet', 'templatecache'], function(done) { 310 | startTests(true /*singleRun*/ , done); 311 | }); 312 | 313 | /** 314 | * Run specs and wait. 315 | * Watch for file changes and re-run tests on each change 316 | * To start servers and run midway specs as well: 317 | * gulp autotest --startServers 318 | */ 319 | gulp.task('autotest', function(done) { 320 | startTests(false /*singleRun*/ , done); 321 | }); 322 | 323 | /** 324 | * serve the dev environment 325 | * --debug-brk or --debug 326 | * --nosync 327 | */ 328 | gulp.task('serve-dev', ['inject'], function() { 329 | serve(true /*isDev*/); 330 | }); 331 | 332 | /** 333 | * serve the build environment 334 | * --debug-brk or --debug 335 | * --nosync 336 | */ 337 | gulp.task('serve-build', ['build'], function() { 338 | serve(false /*isDev*/); 339 | }); 340 | 341 | /** 342 | * Bump the version 343 | * --type=pre will bump the prerelease version *.*.*-x 344 | * --type=patch or no flag will bump the patch version *.*.x 345 | * --type=minor will bump the minor version *.x.* 346 | * --type=major will bump the major version x.*.* 347 | * --version=1.2.3 will bump to a specific version and ignore other flags 348 | */ 349 | gulp.task('bump', function() { 350 | var msg = 'Bumping versions'; 351 | var type = args.type; 352 | var version = args.ver; 353 | var options = {}; 354 | if (version) { 355 | options.version = version; 356 | msg += ' to ' + version; 357 | } else { 358 | options.type = type; 359 | msg += ' for a ' + type; 360 | } 361 | log(msg); 362 | 363 | return gulp 364 | .src(config.packages) 365 | .pipe($.print()) 366 | .pipe($.bump(options)) 367 | .pipe(gulp.dest(config.root)); 368 | }); 369 | 370 | /** 371 | * Optimize the code and re-load browserSync 372 | */ 373 | gulp.task('browserSyncReload', ['optimize'], browserSync.reload); 374 | 375 | //////////////// 376 | 377 | /** 378 | * When files change, log it 379 | * @param {Object} event - event that fired 380 | */ 381 | function changeEvent(event) { 382 | var srcPattern = new RegExp('/.*(?=/' + config.source + ')/'); 383 | log('File ' + event.path.replace(srcPattern, '') + ' ' + event.type); 384 | } 385 | 386 | /** 387 | * Delete all files in a given path 388 | * @param {Array} path - array of paths to delete 389 | * @param {Function} done - callback when complete 390 | */ 391 | function clean(path, done) { 392 | log('Cleaning: ' + $.util.colors.blue(path)); 393 | del(path, done); 394 | } 395 | 396 | /** 397 | * Inject files in a sorted sequence at a specified inject label 398 | * @param {Array} src glob pattern for source files 399 | * @param {String} label The label name 400 | * @param {Array} order glob pattern for sort order of the files 401 | * @returns {Stream} The stream 402 | */ 403 | function inject(src, label, order) { 404 | var options = {read: false}; 405 | if (label) { 406 | options.name = 'inject:' + label; 407 | } 408 | 409 | return $.inject(orderSrc(src, order), options); 410 | } 411 | 412 | /** 413 | * Order a stream 414 | * @param {Stream} src The gulp.src stream 415 | * @param {Array} order Glob array pattern 416 | * @returns {Stream} The ordered stream 417 | */ 418 | function orderSrc (src, order) { 419 | //order = order || ['**/*']; 420 | return gulp 421 | .src(src) 422 | .pipe($.if(order, $.order(order))); 423 | } 424 | 425 | /** 426 | * serve the code 427 | * --debug-brk or --debug 428 | * --nosync 429 | * @param {Boolean} isDev - dev or build mode 430 | * @param {Boolean} specRunner - server spec runner html 431 | */ 432 | function serve(isDev, specRunner) { 433 | var debugMode = '--debug'; 434 | var nodeOptions = getNodeOptions(isDev); 435 | 436 | nodeOptions.nodeArgs = [debugMode + '=5858']; 437 | 438 | if (args.verbose) { 439 | console.log(nodeOptions); 440 | } 441 | 442 | return $.nodemon(nodeOptions) 443 | .on('restart', ['vet'], function(ev) { 444 | log('*** nodemon restarted'); 445 | log('files changed:\n' + ev); 446 | setTimeout(function() { 447 | browserSync.notify('reloading now ...'); 448 | browserSync.reload({stream: false}); 449 | }, config.browserReloadDelay); 450 | }) 451 | .on('start', function () { 452 | log('*** nodemon started'); 453 | startBrowserSync(isDev, specRunner); 454 | }) 455 | .on('crash', function () { 456 | log('*** nodemon crashed: script crashed for some reason'); 457 | }) 458 | .on('exit', function () { 459 | log('*** nodemon exited cleanly'); 460 | }); 461 | } 462 | 463 | function getNodeOptions(isDev) { 464 | return { 465 | script: config.nodeServer, 466 | delayTime: 1, 467 | env: { 468 | 'PORT': port, 469 | 'NODE_ENV': isDev ? 'dev' : 'build' 470 | }, 471 | watch: [config.server] 472 | }; 473 | } 474 | 475 | //function runNodeInspector() { 476 | // log('Running node-inspector.'); 477 | // log('Browse to http://localhost:8080/debug?port=5858'); 478 | // var exec = require('child_process').exec; 479 | // exec('node-inspector'); 480 | //} 481 | 482 | /** 483 | * Start BrowserSync 484 | * --nosync will avoid browserSync 485 | */ 486 | function startBrowserSync(isDev, specRunner) { 487 | if (args.nosync || browserSync.active) { 488 | return; 489 | } 490 | 491 | log('Starting BrowserSync on port ' + port); 492 | 493 | // If build: watches the files, builds, and restarts browser-sync. 494 | // If dev: watches less, compiles it to css, browser-sync handles reload 495 | if (isDev) { 496 | gulp.watch([config.less], ['styles']) 497 | .on('change', changeEvent); 498 | } else { 499 | gulp.watch([config.less, config.js, config.html], ['browserSyncReload']) 500 | .on('change', changeEvent); 501 | } 502 | 503 | var options = { 504 | proxy: 'localhost:' + port, 505 | port: 3000, 506 | files: isDev ? [ 507 | config.client + '**/*.*', 508 | '!' + config.less, 509 | config.temp + '**/*.css' 510 | ] : [], 511 | ghostMode: { // these are the defaults t,f,t,t 512 | clicks: true, 513 | location: false, 514 | forms: true, 515 | scroll: true 516 | }, 517 | injectChanges: true, 518 | logFileChanges: true, 519 | logLevel: 'info', 520 | logPrefix: 'hottowel', 521 | notify: true, 522 | reloadDelay: 0 //1000 523 | } ; 524 | if (specRunner) { 525 | options.startPath = config.specRunnerFile; 526 | } 527 | 528 | browserSync(options); 529 | } 530 | 531 | /** 532 | * Start Plato inspector and visualizer 533 | */ 534 | function startPlatoVisualizer(done) { 535 | log('Running Plato'); 536 | 537 | var files = glob.sync(config.plato.js); 538 | var excludeFiles = /.*\.spec\.js/; 539 | var plato = require('plato'); 540 | 541 | var options = { 542 | title: 'Plato Inspections Report', 543 | exclude: excludeFiles 544 | }; 545 | var outputDir = config.report + '/plato'; 546 | 547 | plato.inspect(files, outputDir, options, platoCompleted); 548 | 549 | function platoCompleted(report) { 550 | var overview = plato.getOverviewReport(report); 551 | if (args.verbose) { 552 | log(overview.summary); 553 | } 554 | if (done) { done(); } 555 | } 556 | } 557 | 558 | /** 559 | * Start the tests using karma. 560 | * @param {boolean} singleRun - True means run once and end (CI), or keep running (dev) 561 | * @param {Function} done - Callback to fire when karma is done 562 | * @return {undefined} 563 | */ 564 | function startTests(singleRun, done) { 565 | var child; 566 | var excludeFiles = []; 567 | var fork = require('child_process').fork; 568 | var karma = require('karma').server; 569 | var serverSpecs = config.serverIntegrationSpecs; 570 | 571 | if (args.startServers) { 572 | log('Starting servers'); 573 | var savedEnv = process.env; 574 | savedEnv.NODE_ENV = 'dev'; 575 | savedEnv.PORT = 8888; 576 | child = fork(config.nodeServer); 577 | } else { 578 | if (serverSpecs && serverSpecs.length) { 579 | excludeFiles = serverSpecs; 580 | } 581 | } 582 | 583 | karma.start({ 584 | configFile: __dirname + '/karma.conf.js', 585 | exclude: excludeFiles, 586 | singleRun: !!singleRun 587 | }, karmaCompleted); 588 | 589 | //////////////// 590 | 591 | function karmaCompleted(karmaResult) { 592 | log('Karma completed'); 593 | if (child) { 594 | log('shutting down the child process'); 595 | child.kill(); 596 | } 597 | if (karmaResult === 1) { 598 | done('karma: tests failed with code ' + karmaResult); 599 | } else { 600 | done(); 601 | } 602 | } 603 | } 604 | 605 | /** 606 | * Formatter for bytediff to display the size changes after processing 607 | * @param {Object} data - byte data 608 | * @return {String} Difference in bytes, formatted 609 | */ 610 | function bytediffFormatter(data) { 611 | var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; 612 | return data.fileName + ' went from ' + 613 | (data.startSize / 1000).toFixed(2) + ' kB to ' + 614 | (data.endSize / 1000).toFixed(2) + ' kB and is ' + 615 | formatPercent(1 - data.percent, 2) + '%' + difference; 616 | } 617 | 618 | /** 619 | * Log an error message and emit the end of a task 620 | */ 621 | //function errorLogger(error) { 622 | // log('*** Start of Error ***'); 623 | // log(error); 624 | // log('*** End of Error ***'); 625 | // this.emit('end'); 626 | //} 627 | 628 | /** 629 | * Format a number as a percentage 630 | * @param {Number} num Number to format as a percent 631 | * @param {Number} precision Precision of the decimal 632 | * @return {String} Formatted perentage 633 | */ 634 | function formatPercent(num, precision) { 635 | return (num * 100).toFixed(precision); 636 | } 637 | 638 | /** 639 | * Format and return the header for files 640 | * @return {String} Formatted file header 641 | */ 642 | function getHeader() { 643 | var pkg = require('./package.json'); 644 | var template = ['/**', 645 | ' * <%= pkg.name %> - <%= pkg.description %>', 646 | ' * @authors <%= pkg.authors %>', 647 | ' * @version v<%= pkg.version %>', 648 | ' * @link <%= pkg.homepage %>', 649 | ' * @license <%= pkg.license %>', 650 | ' */', 651 | '' 652 | ].join('\n'); 653 | return $.header(template, { 654 | pkg: pkg 655 | }); 656 | } 657 | 658 | /** 659 | * Log a message or series of messages using chalk's blue color. 660 | * Can pass in a string, object or array. 661 | */ 662 | function log(msg) { 663 | if (typeof(msg) === 'object') { 664 | for (var item in msg) { 665 | if (msg.hasOwnProperty(item)) { 666 | $.util.log($.util.colors.blue(msg[item])); 667 | } 668 | } 669 | } else { 670 | $.util.log($.util.colors.blue(msg)); 671 | } 672 | } 673 | 674 | /** 675 | * Show OS level notification using node-notifier 676 | */ 677 | function notify(options) { 678 | var notifier = require('node-notifier'); 679 | var notifyOptions = { 680 | sound: 'Bottle', 681 | contentImage: path.join(__dirname, 'gulp.png'), 682 | icon: path.join(__dirname, 'gulp.png') 683 | }; 684 | _.assign(notifyOptions, options); 685 | notifier.notify(notifyOptions); 686 | } 687 | 688 | module.exports = gulp; 689 | -------------------------------------------------------------------------------- /hot-towel/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var gulpConfig = require('./gulp.config')(); 3 | 4 | config.set({ 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: './', 7 | 8 | // frameworks to use 9 | // some available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['mocha', 'chai', 'sinon', 'chai-sinon'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: gulpConfig.karma.files, 14 | 15 | // list of files to exclude 16 | exclude: gulpConfig.karma.exclude, 17 | 18 | proxies: { 19 | '/': 'http://localhost:8888/' 20 | }, 21 | 22 | // preprocess matching files before serving them to the browser 23 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 24 | preprocessors: gulpConfig.karma.preprocessors, 25 | 26 | // test results reporter to use 27 | // possible values: 'dots', 'progress', 'coverage' 28 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 29 | reporters: ['progress', 'coverage'], 30 | 31 | coverageReporter: { 32 | dir: gulpConfig.karma.coverage.dir, 33 | reporters: gulpConfig.karma.coverage.reporters 34 | }, 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || 44 | // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | // enable / disable watching file and executing tests whenever any file changes 48 | autoWatch: true, 49 | 50 | // start these browsers 51 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 52 | // browsers: ['Chrome', 'ChromeCanary', 'FirefoxAurora', 'Safari', 'PhantomJS'], 53 | browsers: ['PhantomJS'], 54 | 55 | // Continuous Integration mode 56 | // if true, Karma captures browsers, runs the tests and exits 57 | singleRun: false 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /hot-towel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hot-towel", 3 | "description": "hot-towel Project Generated from HotTowel Angular", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "init": "npm install", 7 | "install": "bower install", 8 | "start": "node src/server/app.js", 9 | "test": "gulp test" 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.8.2", 13 | "express": "^4.9.3", 14 | "morgan": "^1.1.1", 15 | "serve-favicon": "^2.0.1" 16 | }, 17 | "devDependencies": { 18 | "browser-sync": "^2.7.13", 19 | "chai": "^3.1.0", 20 | "chai-as-promised": "^5.1.0", 21 | "chalk": "^1.1.0", 22 | "dateformat": "^1.0.8-1.2.3", 23 | "debug": "^2.0.0", 24 | "del": "^1.2.0", 25 | "glob": "^4.5.3", 26 | "gulp": "^3.8.10", 27 | "gulp-angular-templatecache": "^1.4.2", 28 | "gulp-autoprefixer": "^2.3.1", 29 | "gulp-bump": "^0.3.1", 30 | "gulp-bytediff": "^0.2.0", 31 | "gulp-concat": "^2.3.3", 32 | "gulp-filter": "^2.0.2", 33 | "gulp-header": "^1.2.2", 34 | "gulp-if": "^1.2.5", 35 | "gulp-imagemin": "^2.3.0", 36 | "gulp-inject": "^1.0.1", 37 | "gulp-jscs": "^2.0.0", 38 | "gulp-jshint": "^1.7.1", 39 | "gulp-less": "^3.0.1", 40 | "gulp-load-plugins": "^1.0.0-rc.1", 41 | "gulp-minify-css": "^1.1.1", 42 | "gulp-minify-html": "^1.0.4", 43 | "gulp-ng-annotate": "^1.0.0", 44 | "gulp-nodemon": "^2.0.3", 45 | "gulp-order": "^1.1.1", 46 | "gulp-plumber": "^1.0.1", 47 | "gulp-print": "^1.1.0", 48 | "gulp-rev": "^5.1.0", 49 | "gulp-rev-replace": "^0.4.2", 50 | "gulp-sourcemaps": "^1.1.5", 51 | "gulp-task-listing": "^1.0.0", 52 | "gulp-uglify": "^1.0.1", 53 | "gulp-useref": "^1.0.2", 54 | "gulp-util": "^3.0.1", 55 | "jshint-stylish": "^2.0.1", 56 | "karma": "^0.13.2", 57 | "karma-chai": "^0.1.0", 58 | "karma-chai-sinon": "^0.1.3", 59 | "karma-chrome-launcher": "^0.2.0", 60 | "karma-coverage": "^0.4.2", 61 | "karma-firefox-launcher": "^0.1.3", 62 | "karma-growl-reporter": "^0.1.1", 63 | "karma-mocha": "^0.2.0", 64 | "karma-phantomjs-launcher": "^0.2.0", 65 | "karma-safari-launcher": "^0.1.1", 66 | "karma-sinon": "^1.0.3", 67 | "lodash": "^3.10.0", 68 | "method-override": "^2.3.4", 69 | "minimist": "^1.1.0", 70 | "mocha": "^2.2.5", 71 | "node-notifier": "^4.0.3", 72 | "phantomjs": "^1.9.17", 73 | "plato": "^1.2.0", 74 | "q": "^1.0.1", 75 | "sinon": "^1.12.2", 76 | "sinon-chai": "^2.6.0", 77 | "wiredep": "^2.2.2", 78 | "yargs": "^3.15.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.admin') 6 | .controller('AdminController', AdminController); 7 | 8 | AdminController.$inject = ['logger']; 9 | /* @ngInject */ 10 | function AdminController(logger) { 11 | var vm = this; 12 | vm.title = 'Admin'; 13 | 14 | activate(); 15 | 16 | function activate() { 17 | logger.info('Activated Admin View'); 18 | } 19 | } 20 | })(); 21 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('AdminController', function() { 3 | var controller; 4 | 5 | beforeEach(function() { 6 | bard.appModule('app.admin'); 7 | bard.inject('$controller', '$log', '$rootScope'); 8 | }); 9 | 10 | beforeEach(function () { 11 | controller = $controller('AdminController'); 12 | $rootScope.$apply(); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | describe('Admin controller', function() { 18 | it('should be created successfully', function () { 19 | expect(controller).to.be.defined; 20 | }); 21 | 22 | describe('after activate', function() { 23 | it('should have title of Admin', function() { 24 | expect(controller.title).to.equal('Admin'); 25 | }); 26 | 27 | it('should have logged "Activated"', function() { 28 | expect($log.info.logs).to.match(/Activated/); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |

The quick brown fox jumped over the lazy dog

9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app.admin', [ 5 | 'app.core', 6 | 'app.widgets' 7 | ]); 8 | 9 | })(); 10 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.admin') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'admin', 18 | config: { 19 | url: '/admin', 20 | templateUrl: 'app/admin/admin.html', 21 | controller: 'AdminController', 22 | controllerAs: 'vm', 23 | title: 'Admin', 24 | settings: { 25 | nav: 2, 26 | content: ' Admin' 27 | } 28 | } 29 | } 30 | ]; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/admin/admin.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('admin routes', function () { 3 | describe('state', function () { 4 | var view = 'app/admin/admin.html'; 5 | 6 | beforeEach(function() { 7 | module('app.admin', bard.fakeToastr); 8 | bard.inject('$httpBackend', '$location', '$rootScope', '$state', '$templateCache'); 9 | }); 10 | 11 | beforeEach(function() { 12 | $templateCache.put(view, ''); 13 | }); 14 | 15 | it('should map state admin to url /admin ', function() { 16 | expect($state.href('admin', {})).to.equal('/admin'); 17 | }); 18 | 19 | it('should map /admin route to admin View template', function () { 20 | expect($state.get('admin').templateUrl).to.equal(view); 21 | }); 22 | 23 | it('of admin should work with $state.go', function () { 24 | $state.go('admin'); 25 | $rootScope.$apply(); 26 | expect($state.is('admin')); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/app.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | 'app.core', 6 | 'app.widgets', 7 | 'app.admin', 8 | 'app.dashboard', 9 | 'app.layout' 10 | ]); 11 | 12 | })(); 13 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/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('blocks.exception') 8 | .provider('exceptionHandler', exceptionHandlerProvider) 9 | .config(config); 10 | 11 | /** 12 | * Must configure the exception handling 13 | */ 14 | function exceptionHandlerProvider() { 15 | /* jshint validthis:true */ 16 | this.config = { 17 | appErrorPrefix: undefined 18 | }; 19 | 20 | this.configure = function (appErrorPrefix) { 21 | this.config.appErrorPrefix = appErrorPrefix; 22 | }; 23 | 24 | this.$get = function() { 25 | return {config: this.config}; 26 | }; 27 | } 28 | 29 | config.$inject = ['$provide']; 30 | 31 | /** 32 | * Configure by setting an optional string value for appErrorPrefix. 33 | * Accessible via config.appErrorPrefix (via config value). 34 | * @param {Object} $provide 35 | */ 36 | /* @ngInject */ 37 | function config($provide) { 38 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 39 | } 40 | 41 | extendExceptionHandler.$inject = ['$delegate', 'exceptionHandler', 'logger']; 42 | 43 | /** 44 | * Extend the $exceptionHandler service to also display a toast. 45 | * @param {Object} $delegate 46 | * @param {Object} exceptionHandler 47 | * @param {Object} logger 48 | * @return {Function} the decorated $exceptionHandler service 49 | */ 50 | function extendExceptionHandler($delegate, exceptionHandler, logger) { 51 | return function(exception, cause) { 52 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 53 | var errorData = {exception: exception, cause: cause}; 54 | exception.message = appErrorPrefix + exception.message; 55 | $delegate(exception, cause); 56 | /** 57 | * Could add the error to a service's collection, 58 | * add errors to $rootScope, log errors to remote web server, 59 | * or log locally. Or throw hard. It is entirely up to you. 60 | * throw exception; 61 | * 62 | * @example 63 | * throw { message: 'error message we added' }; 64 | */ 65 | logger.error(exception.message, errorData); 66 | }; 67 | } 68 | })(); 69 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/exception/exception-handler.provider.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('blocks.exception', function() { 3 | var exceptionHandlerProvider; 4 | var mocks = { 5 | errorMessage: 'fake error', 6 | prefix: '[TEST]: ' 7 | }; 8 | 9 | beforeEach(function() { 10 | bard.appModule('blocks.exception', function(_exceptionHandlerProvider_) { 11 | exceptionHandlerProvider = _exceptionHandlerProvider_; 12 | }); 13 | bard.inject('$rootScope'); 14 | }); 15 | 16 | bard.verifyNoOutstandingHttpRequests(); 17 | 18 | describe('exceptionHandlerProvider', function() { 19 | it('should have a dummy test', inject(function() { 20 | expect(true).to.equal(true); 21 | })); 22 | 23 | it('should have exceptionHandlerProvider defined', inject(function() { 24 | expect(exceptionHandlerProvider).to.be.defined; 25 | })); 26 | 27 | it('should have configuration', inject(function() { 28 | expect(exceptionHandlerProvider.config).to.be.defined; 29 | })); 30 | 31 | it('should have configuration', inject(function() { 32 | expect(exceptionHandlerProvider.configure).to.be.defined; 33 | })); 34 | 35 | describe('with appErrorPrefix', function() { 36 | beforeEach(function() { 37 | exceptionHandlerProvider.configure(mocks.prefix); 38 | }); 39 | 40 | it('should have appErrorPrefix defined', inject(function() { 41 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix).to.be.defined; 42 | })); 43 | 44 | it('should have appErrorPrefix set properly', inject(function() { 45 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix) 46 | .to.equal(mocks.prefix); 47 | })); 48 | 49 | it('should throw an error when forced', inject(function() { 50 | expect(functionThatWillThrow).to.throw(); 51 | })); 52 | 53 | it('manual error is handled by decorator', function() { 54 | var exception; 55 | exceptionHandlerProvider.configure(mocks.prefix); 56 | try { 57 | $rootScope.$apply(functionThatWillThrow); 58 | } 59 | catch (ex) { 60 | exception = ex; 61 | expect(ex.message).to.equal(mocks.prefix + mocks.errorMessage); 62 | } 63 | }); 64 | }); 65 | }); 66 | 67 | function functionThatWillThrow() { 68 | throw new Error(mocks.errorMessage); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/exception/exception.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.exception') 6 | .factory('exception', exception); 7 | 8 | /* @ngInject */ 9 | function exception($q, logger) { 10 | var service = { 11 | catcher: catcher 12 | }; 13 | return service; 14 | 15 | function catcher(message) { 16 | return function(e) { 17 | var thrownDescription; 18 | var newMessage; 19 | if (e.data && e.data.description) { 20 | thrownDescription = '\n' + e.data.description; 21 | newMessage = message + thrownDescription; 22 | } 23 | e.data.description = newMessage; 24 | logger.error(newMessage); 25 | return $q.reject(e); 26 | }; 27 | } 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/exception/exception.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.exception', ['blocks.logger']); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/logger/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.logger') 6 | .factory('logger', logger); 7 | 8 | logger.$inject = ['$log', 'toastr']; 9 | 10 | /* @ngInject */ 11 | function logger($log, toastr) { 12 | var service = { 13 | showToasts: true, 14 | 15 | error : error, 16 | info : info, 17 | success : success, 18 | warning : warning, 19 | 20 | // straight to console; bypass toastr 21 | log : $log.log 22 | }; 23 | 24 | return service; 25 | ///////////////////// 26 | 27 | function error(message, data, title) { 28 | toastr.error(message, title); 29 | $log.error('Error: ' + message, data); 30 | } 31 | 32 | function info(message, data, title) { 33 | toastr.info(message, title); 34 | $log.info('Info: ' + message, data); 35 | } 36 | 37 | function success(message, data, title) { 38 | toastr.success(message, title); 39 | $log.info('Success: ' + message, data); 40 | } 41 | 42 | function warning(message, data, title) { 43 | toastr.warning(message, title); 44 | $log.warn('Warning: ' + message, data); 45 | } 46 | } 47 | }()); 48 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/logger/logger.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.logger', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/router/router-helper.provider.js: -------------------------------------------------------------------------------- 1 | /* Help configure the state-base ui.router */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('blocks.router') 7 | .provider('routerHelper', routerHelperProvider); 8 | 9 | routerHelperProvider.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 10 | /* @ngInject */ 11 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 12 | /* jshint validthis:true */ 13 | var config = { 14 | docTitle: undefined, 15 | resolveAlways: {} 16 | }; 17 | 18 | $locationProvider.html5Mode(true); 19 | 20 | this.configure = function(cfg) { 21 | angular.extend(config, cfg); 22 | }; 23 | 24 | this.$get = RouterHelper; 25 | RouterHelper.$inject = ['$location', '$rootScope', '$state', 'logger']; 26 | /* @ngInject */ 27 | function RouterHelper($location, $rootScope, $state, logger) { 28 | var handlingStateChangeError = false; 29 | var hasOtherwise = false; 30 | var stateCounts = { 31 | errors: 0, 32 | changes: 0 33 | }; 34 | 35 | var service = { 36 | configureStates: configureStates, 37 | getStates: getStates, 38 | stateCounts: stateCounts 39 | }; 40 | 41 | init(); 42 | 43 | return service; 44 | 45 | /////////////// 46 | 47 | function configureStates(states, otherwisePath) { 48 | states.forEach(function(state) { 49 | state.config.resolve = 50 | angular.extend(state.config.resolve || {}, config.resolveAlways); 51 | $stateProvider.state(state.state, state.config); 52 | }); 53 | if (otherwisePath && !hasOtherwise) { 54 | hasOtherwise = true; 55 | $urlRouterProvider.otherwise(otherwisePath); 56 | } 57 | } 58 | 59 | function handleRoutingErrors() { 60 | // Route cancellation: 61 | // On routing error, go to the dashboard. 62 | // Provide an exit clause if it tries to do it twice. 63 | $rootScope.$on('$stateChangeError', 64 | function(event, toState, toParams, fromState, fromParams, error) { 65 | if (handlingStateChangeError) { 66 | return; 67 | } 68 | stateCounts.errors++; 69 | handlingStateChangeError = true; 70 | var destination = (toState && 71 | (toState.title || toState.name || toState.loadedTemplateUrl)) || 72 | 'unknown target'; 73 | var msg = 'Error routing to ' + destination + '. ' + 74 | (error.data || '') + '.
' + (error.statusText || '') + 75 | ': ' + (error.status || ''); 76 | logger.warning(msg, [toState]); 77 | $location.path('/'); 78 | } 79 | ); 80 | } 81 | 82 | function init() { 83 | handleRoutingErrors(); 84 | updateDocTitle(); 85 | } 86 | 87 | function getStates() { return $state.get(); } 88 | 89 | function updateDocTitle() { 90 | $rootScope.$on('$stateChangeSuccess', 91 | function(event, toState, toParams, fromState, fromParams) { 92 | stateCounts.changes++; 93 | handlingStateChangeError = false; 94 | var title = config.docTitle + ' ' + (toState.title || ''); 95 | $rootScope.title = title; // data bind to 96 | } 97 | ); 98 | } 99 | } 100 | } 101 | })(); 102 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/blocks/router/router.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.router', [ 5 | 'ui.router', 6 | 'blocks.logger' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/404.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="bred"> 8 | <div class="pull-left"><i class="fa fa-warning"></i></div> 9 | <div class="datas-text pull-right"> 10 | <a><span class="bold">404</span></a>Page Not Found 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | </ul> 15 | </div> 16 | </div> 17 | <div class="row"> 18 | <div class="widget wblue"> 19 | <div ht-widget-header title="Page Not Found" 20 | allow-collapse="true"></div> 21 | <div class="widget-content text-center text-info"> 22 | <div class="container"> 23 | No soup for you! 24 | </div> 25 | </div> 26 | <div class="widget-foot"> 27 | <div class="clearfix"></div> 28 | </div> 29 | </div> 30 | </div> 31 | </div> 32 | </section> 33 | </section> 34 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var core = angular.module('app.core'); 5 | 6 | core.config(toastrConfig); 7 | 8 | toastrConfig.$inject = ['toastr']; 9 | /* @ngInject */ 10 | function toastrConfig(toastr) { 11 | toastr.options.timeOut = 4000; 12 | toastr.options.positionClass = 'toast-bottom-right'; 13 | } 14 | 15 | var config = { 16 | appErrorPrefix: '[hot-towel Error] ', 17 | appTitle: 'hot-towel' 18 | }; 19 | 20 | core.value('config', config); 21 | 22 | core.config(configure); 23 | 24 | configure.$inject = ['$logProvider', 'routerHelperProvider', 'exceptionHandlerProvider']; 25 | /* @ngInject */ 26 | function configure($logProvider, routerHelperProvider, exceptionHandlerProvider) { 27 | 28 | if ($logProvider.debugEnabled) { 29 | $logProvider.debugEnabled(true); 30 | } 31 | exceptionHandlerProvider.configure(config.appErrorPrefix); 32 | routerHelperProvider.configure({docTitle: config.appTitle + ': '}); 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/constants.js: -------------------------------------------------------------------------------- 1 | /* global toastr:false, moment:false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .constant('toastr', toastr) 8 | .constant('moment', moment); 9 | })(); 10 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core', [ 6 | 'ngAnimate', 'ngSanitize', 7 | 'blocks.exception', 'blocks.logger', 'blocks.router', 8 | 'ui.router', 'ngplus' 9 | ]); 10 | })(); 11 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/core.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .run(appRun); 7 | 8 | /* @ngInject */ 9 | function appRun(routerHelper) { 10 | var otherwise = '/404'; 11 | routerHelper.configureStates(getStates(), otherwise); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: '404', 18 | config: { 19 | url: '/404', 20 | templateUrl: 'app/core/404.html', 21 | title: '404' 22 | } 23 | } 24 | ]; 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/core.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('core', function() { 3 | describe('state', function() { 4 | var views = { 5 | four0four: 'app/core/404.html' 6 | }; 7 | 8 | beforeEach(function() { 9 | module('app.core', bard.fakeToastr); 10 | bard.inject('$location', '$rootScope', '$state', '$templateCache'); 11 | $templateCache.put(views.core, ''); 12 | }); 13 | 14 | it('should map /404 route to 404 View template', function() { 15 | expect($state.get('404').templateUrl).to.equal(views.four0four); 16 | }); 17 | 18 | it('of dashboard should work with $state.go', function() { 19 | $state.go('404'); 20 | $rootScope.$apply(); 21 | expect($state.is('404')); 22 | }); 23 | 24 | it('should route /invalid to the otherwise (404) route', function() { 25 | $location.path('/invalid'); 26 | $rootScope.$apply(); 27 | expect($state.current.templateUrl).to.equal(views.four0four); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/core/dataservice.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('dataservice', dataservice); 7 | 8 | dataservice.$inject = ['$http', '$q', 'exception', 'logger']; 9 | /* @ngInject */ 10 | function dataservice($http, $q, exception, logger) { 11 | var service = { 12 | getPeople: getPeople, 13 | getMessageCount: getMessageCount 14 | }; 15 | 16 | return service; 17 | 18 | function getMessageCount() { return $q.when(72); } 19 | 20 | function getPeople() { 21 | return $http.get('/api/people') 22 | .then(success) 23 | .catch(fail); 24 | 25 | function success(response) { 26 | return response.data; 27 | } 28 | 29 | function fail(e) { 30 | return exception.catcher('XHR Failed for getPeople')(e); 31 | } 32 | } 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .controller('DashboardController', DashboardController); 7 | 8 | DashboardController.$inject = ['$q', 'dataservice', 'logger']; 9 | /* @ngInject */ 10 | function DashboardController($q, dataservice, logger) { 11 | 12 | var vm = this; 13 | vm.news = { 14 | title: 'hot-towel', 15 | description: 'Hot Towel Angular is a SPA template for Angular developers.' 16 | }; 17 | vm.name = 'Mike'; 18 | vm.messageCount = 0; 19 | vm.people = []; 20 | vm.title = 'Dashboard'; 21 | 22 | activate(); 23 | 24 | function activate() { 25 | var promises = [getMessageCount(), getPeople()]; 26 | return $q.all(promises).then(function () { 27 | logger.info('Activated Dashboard View'); 28 | }); 29 | } 30 | 31 | function getMessageCount() { 32 | return dataservice.getMessageCount().then(function (data) { 33 | vm.messageCount = data; 34 | return vm.messageCount; 35 | }); 36 | } 37 | 38 | function getPeople() { 39 | return dataservice.getPeople().then(function (data) { 40 | vm.people = data; 41 | return vm.people; 42 | }); 43 | } 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('DashboardController', function() { 3 | var controller; 4 | var people = mockData.getMockPeople(); 5 | 6 | beforeEach(function() { 7 | bard.appModule('app.dashboard'); 8 | bard.inject('$controller', '$log', '$q', '$rootScope', 'dataservice'); 9 | }); 10 | 11 | beforeEach(function () { 12 | sinon.stub(dataservice, 'getPeople').returns($q.when(people)); 13 | controller = $controller('DashboardController'); 14 | $rootScope.$apply(); 15 | }); 16 | 17 | bard.verifyNoOutstandingHttpRequests(); 18 | 19 | describe('Dashboard controller', function() { 20 | it('should be created successfully', function () { 21 | expect(controller).to.be.defined; 22 | }); 23 | 24 | describe('after activate', function() { 25 | it('should have title of Dashboard', function () { 26 | expect(controller.title).to.equal('Dashboard'); 27 | }); 28 | 29 | it('should have logged "Activated"', function() { 30 | expect($log.info.logs).to.match(/Activated/); 31 | }); 32 | 33 | it('should have news', function () { 34 | expect(controller.news).to.not.be.empty; 35 | }); 36 | 37 | it('should have at least 1 person', function () { 38 | expect(controller.people).to.have.length.above(0); 39 | }); 40 | 41 | it('should have people count of 5', function () { 42 | expect(controller.people).to.have.length(7); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="blightblue"> 8 | <div class="pull-left"><i class="fa fa-plane"></i></div> 9 | <div class="datas-text pull-right"> 10 | <span class="bold">May 18 - 19, 2015</span> Castle Resort, Neverland 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | <li class="borange"> 15 | <div class="pull-left"><i class="fa fa-envelope"></i></div> 16 | <div class="datas-text pull-right"> 17 | <span class="bold">72 Messages</span> 18 | </div> 19 | <div class="clearfix"></div> 20 | </li> 21 | <li class="bgreen"> 22 | <div class="pull-left"><i class="fa fa-envelope"></i></div> 23 | <div class="datas-text pull-right"> 24 | <span class="bold">Mike is logged in</span> 25 | </div> 26 | <div class="clearfix"></div> 27 | </li> 28 | 29 | </ul> 30 | </div> 31 | </div> 32 | <div class="row"> 33 | <div class="col-md-6"> 34 | <div class="widget wviolet"> 35 | <div ht-widget-header title="People" 36 | allow-collapse="true"></div> 37 | <div class="widget-content text-center text-info"> 38 | <table class="table table-condensed table-striped"> 39 | <thead> 40 | <tr> 41 | <th>First Name</th> 42 | <th>Last Name</th> 43 | <th>Age</th> 44 | <th>Location</th> 45 | </tr> 46 | </thead> 47 | <tbody> 48 | <tr ng-repeat="p in vm.people"> 49 | <td>{{p.firstName}}</td> 50 | <td>{{p.lastName}}</td> 51 | <td>{{p.age}}</td> 52 | <td>{{p.location}}</td> 53 | </tr> 54 | </tbody> 55 | </table> 56 | </div> 57 | <div class="widget-foot"> 58 | <div class="clearfix"></div> 59 | </div> 60 | </div> 61 | </div> 62 | </div> 63 | </div> 64 | </section> 65 | </section> -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.dashboard', [ 5 | 'app.core', 6 | 'app.widgets' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'dashboard', 18 | config: { 19 | url: '/', 20 | templateUrl: 'app/dashboard/dashboard.html', 21 | controller: 'DashboardController', 22 | controllerAs: 'vm', 23 | title: 'dashboard', 24 | settings: { 25 | nav: 1, 26 | content: '<i class="fa fa-dashboard"></i> Dashboard' 27 | } 28 | } 29 | } 30 | ]; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/dashboard/dashboard.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('dashboard routes', function () { 3 | describe('state', function () { 4 | var view = 'app/dashboard/dashboard.html'; 5 | 6 | beforeEach(function() { 7 | module('app.dashboard', bard.fakeToastr); 8 | bard.inject('$httpBackend', '$location', '$rootScope', '$state', '$templateCache'); 9 | }); 10 | 11 | beforeEach(function() { 12 | $templateCache.put(view, ''); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | it('should map state dashboard to url / ', function() { 18 | expect($state.href('dashboard', {})).to.equal('/'); 19 | }); 20 | 21 | it('should map /dashboard route to dashboard View template', function () { 22 | expect($state.get('dashboard').templateUrl).to.equal(view); 23 | }); 24 | 25 | it('of dashboard should work with $state.go', function () { 26 | $state.go('dashboard'); 27 | $rootScope.$apply(); 28 | expect($state.is('dashboard')); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/ht-sidebar.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .directive('htSidebar', htSidebar); 7 | 8 | /* @ngInject */ 9 | function htSidebar () { 10 | // Opens and closes the sidebar menu. 11 | // Usage: 12 | // <div ht-sidebar"> 13 | // <div ht-sidebar whenDoneAnimating="vm.sidebarReady()"> 14 | // Creates: 15 | // <div ht-sidebar class="sidebar"> 16 | var directive = { 17 | link: link, 18 | restrict: 'EA', 19 | scope: { 20 | whenDoneAnimating: '&?' 21 | } 22 | }; 23 | return directive; 24 | 25 | function link(scope, element, attrs) { 26 | var $sidebarInner = element.find('.sidebar-inner'); 27 | var $dropdownElement = element.find('.sidebar-dropdown a'); 28 | element.addClass('sidebar'); 29 | $dropdownElement.click(dropdown); 30 | 31 | function dropdown(e) { 32 | var dropClass = 'dropy'; 33 | e.preventDefault(); 34 | if (!$dropdownElement.hasClass(dropClass)) { 35 | $sidebarInner.slideDown(350, scope.whenDoneAnimating); 36 | $dropdownElement.addClass(dropClass); 37 | } else if ($dropdownElement.hasClass(dropClass)) { 38 | $dropdownElement.removeClass(dropClass); 39 | $sidebarInner.slideUp(350, scope.whenDoneAnimating); 40 | } 41 | } 42 | } 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/ht-sidebar.directive.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | /* jshint multistr:true */ 3 | describe('htSidebar directive: ', function () { 4 | var dropdownElement; 5 | var el; 6 | var innerElement; 7 | var isOpenClass = 'dropy'; 8 | var scope; 9 | 10 | beforeEach(module('app.layout')); 11 | 12 | beforeEach(inject(function($compile, $rootScope) { 13 | // The minimum necessary template HTML for this spec. 14 | // Simulates a menu link that opens and closes a dropdown of menu items 15 | // The `when-done-animating` attribute is optional (as is the vm's implementation) 16 | // 17 | // N.B.: the attribute value is supposed to be an expression that invokes a $scope method 18 | // so make sure the expression includes '()', e.g., "vm.sidebarReady(42)" 19 | // no harm if the expression fails ... but then scope.sidebarReady will be undefined. 20 | // All parameters in the expression are passed to vm.sidebarReady ... if it exists 21 | // 22 | // N.B.: We do NOT add this element to the browser DOM (although we could). 23 | // spec runs faster if we don't touch the DOM (even the PhantomJS DOM). 24 | el = angular.element( 25 | '<ht-sidebar when-done-animating="vm.sidebarReady(42)">' + 26 | '<div class="sidebar-dropdown"><a href="">Menu</a></div>' + 27 | '<div class="sidebar-inner" style="display: none"></div>' + 28 | '</ht-sidebar>'); 29 | 30 | // The spec examines changes to these template parts 31 | dropdownElement = el.find('.sidebar-dropdown a'); // the link to click 32 | innerElement = el.find('.sidebar-inner'); // container of menu items 33 | 34 | // ng's $compile service resolves nested directives (there are none in this example) 35 | // and binds the element to the scope (which must be a real ng scope) 36 | scope = $rootScope; 37 | $compile(el)(scope); 38 | 39 | // tell angular to look at the scope values right now 40 | scope.$digest(); 41 | })); 42 | 43 | /// tests /// 44 | describe('the isOpenClass', function () { 45 | it('is absent for a closed menu', function () { 46 | hasIsOpenClass(false); 47 | }); 48 | 49 | it('is added to a closed menu after clicking', function () { 50 | clickIt(); 51 | hasIsOpenClass(true); 52 | }); 53 | 54 | it('is present for an open menu', function () { 55 | openDropdown(); 56 | hasIsOpenClass(true); 57 | }); 58 | 59 | it('is removed from a closed menu after clicking', function () { 60 | openDropdown(); 61 | clickIt(); 62 | hasIsOpenClass(false); 63 | }); 64 | }); 65 | 66 | describe('when animating w/ jQuery fx off', function () { 67 | beforeEach(function () { 68 | // remember current state of jQuery's global FX duration switch 69 | this.oldFxOff = $.fx.off; 70 | // when jQuery fx are of, there is zero animation time; no waiting for animation to complete 71 | $.fx.off = true; 72 | // must add to DOM when testing jQuery animation result 73 | el.appendTo(document.body); 74 | }); 75 | 76 | afterEach(function () { 77 | $.fx.off = this.oldFxOff; 78 | el.remove(); 79 | }); 80 | 81 | it('dropdown is visible after opening a closed menu', function () { 82 | dropdownIsVisible(false); // hidden before click 83 | clickIt(); 84 | dropdownIsVisible(true); // visible after click 85 | }); 86 | 87 | it('dropdown is hidden after closing an open menu', function () { 88 | openDropdown(); 89 | dropdownIsVisible(true); // visible before click 90 | clickIt(); 91 | dropdownIsVisible(false); // hidden after click 92 | }); 93 | 94 | it('click triggers "when-done-animating" expression', function () { 95 | // spy on directive's callback when the animation is done 96 | var spy = sinon.spy(); 97 | 98 | // Recall the pertinent tag in the template ... 99 | // ' <div ht-sidebar when-done-animating="vm.sidebarReady(42)" > 100 | // therefore, the directive looks for scope.vm.sidebarReady 101 | // and should call that method with the value '42' 102 | scope.vm = {sidebarReady: spy}; 103 | 104 | // tell angular to look again for that vm.sidebarReady property 105 | scope.$digest(); 106 | 107 | // spy not called until after click which triggers the animation 108 | expect(spy).not.to.have.been.called; 109 | 110 | // this click triggers an animation 111 | clickIt(); 112 | 113 | // verify that the vm's method (sidebarReady) was called with '42' 114 | // FYI: spy.args[0] is the array of args passed to sidebarReady() 115 | expect(spy).to.have.been.called; 116 | expect(spy).to.have.been.calledWith(42); 117 | }); 118 | }); 119 | 120 | /////// helpers ////// 121 | 122 | // put the dropdown in the 'menu open' state 123 | function openDropdown() { 124 | dropdownElement.addClass(isOpenClass); 125 | innerElement.css('display', 'block'); 126 | } 127 | 128 | // click the "menu" link 129 | function clickIt() { 130 | dropdownElement.trigger('click'); 131 | } 132 | 133 | // assert whether the "menu" link has the class that means 'is open' 134 | function hasIsOpenClass(isTrue) { 135 | var hasClass = dropdownElement.hasClass(isOpenClass); 136 | expect(hasClass).equal(!!isTrue, 137 | 'dropdown has the "is open" class is ' + hasClass); 138 | } 139 | 140 | // assert whether the dropdown container is 'block' (visible) or 'none' (hidden) 141 | function dropdownIsVisible(isTrue) { 142 | var display = innerElement.css('display'); 143 | expect(display).to.equal(isTrue ? 'block' : 'none', 144 | 'innerElement display value is ' + display); 145 | } 146 | }); 147 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/ht-top-nav.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .directive('htTopNav', htTopNav); 7 | 8 | /* @ngInject */ 9 | function htTopNav () { 10 | var directive = { 11 | bindToController: true, 12 | controller: TopNavController, 13 | controllerAs: 'vm', 14 | restrict: 'EA', 15 | scope: { 16 | 'navline': '=', 17 | 'author': '=' 18 | }, 19 | templateUrl: 'app/layout/ht-top-nav.html' 20 | }; 21 | 22 | /* @ngInject */ 23 | function TopNavController() { 24 | var vm = this; 25 | } 26 | 27 | return directive; 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/ht-top-nav.html: -------------------------------------------------------------------------------- 1 | <nav class="navbar navbar-fixed-top navbar-inverse"> 2 | <div class="navbar-header"> 3 | <a href="/" class="navbar-brand"><span class="brand-title">{{vm.navline.title}}</span></a> 4 | <a class="btn navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> 5 | <span class="icon-bar"></span> 6 | <span class="icon-bar"></span> 7 | <span class="icon-bar"></span> 8 | </a> 9 | </div> 10 | <div class="navbar-collapse collapse"> 11 | <div class="pull-right navbar-logo"> 12 | <ul class="nav navbar-nav pull-right"> 13 | <li> 14 | <a ng-href="{{vm.navline.link}}" target="_blank"> 15 | {{vm.navline.text}} 16 | </a> 17 | </li> 18 | <li class="dropdown dropdown-big"> 19 | <a href="http://www.angularjs.org" target="_blank"> 20 | <img src="images/AngularJS-small.png" /> 21 | </a> 22 | </li> 23 | <li> 24 | <a href="http://www.gulpjs.com/" target="_blank"> 25 | <img src="images/gulp-tiny.png" /> 26 | </a> 27 | </li> 28 | </ul> 29 | </div> 30 | </div> 31 | </nav> 32 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.layout', ['app.core']); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/shell.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('ShellController', ShellController); 7 | 8 | ShellController.$inject = ['$rootScope', '$timeout', 'config', 'logger', '$http']; 9 | /* @ngInject */ 10 | function ShellController($rootScope, $timeout, config, logger, $http) { 11 | var vm = this; 12 | vm.busyMessage = 'Please wait ...'; 13 | vm.isBusy = true; 14 | $rootScope.showSplash = true; 15 | vm.navline = { 16 | title: config.appTitle, 17 | text: 'Created by John Papa', 18 | link: 'http://twitter.com/john_papa' 19 | }; 20 | vm.author = 'Brian Clark'; 21 | 22 | activate(); 23 | 24 | function activate() { 25 | logger.success(config.appTitle + ' loaded!', null); 26 | hideSplash(); 27 | } 28 | 29 | function hideSplash() { 30 | //Force a 1 second delay so we can see the splash. 31 | $timeout(function() { 32 | $rootScope.showSplash = false; 33 | }, 1000); 34 | } 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/shell.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('ShellController', function() { 3 | var controller; 4 | 5 | beforeEach(function() { 6 | bard.appModule('app.layout'); 7 | bard.inject('$controller', '$q', '$rootScope', '$timeout', 'dataservice'); 8 | }); 9 | 10 | beforeEach(function () { 11 | controller = $controller('ShellController'); 12 | $rootScope.$apply(); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | describe('Shell controller', function() { 18 | it('should be created successfully', function () { 19 | expect(controller).to.be.defined; 20 | }); 21 | 22 | it('should show splash screen', function () { 23 | expect($rootScope.showSplash).to.be.true; 24 | }); 25 | 26 | it('should hide splash screen after timeout', function (done) { 27 | $timeout(function() { 28 | expect($rootScope.showSplash).to.be.false; 29 | done(); 30 | }, 1000); 31 | $timeout.flush(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/shell.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="ShellController as vm"> 2 | <header class="clearfix"> 3 | <ht-top-nav navline="vm.navline"></ht-top-nav> 4 | </header> 5 | <section id="content" class="content"> 6 | <div ng-include="'app/layout/sidebar.html'"></div> 7 | 8 | <div ui-view class="shuffle-animation"></div> 9 | 10 | <div ngplus-overlay 11 | ngplus-overlay-delay-in="50" 12 | ngplus-overlay-delay-out="700" 13 | ngplus-overlay-animation="dissolve-animation"> 14 | <img src="images/busy.gif"/> 15 | 16 | <div class="page-spinner-message overlay-message">{{vm.busyMessage}}</div> 17 | </div> 18 | </section> 19 | </div> 20 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/sidebar.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('SidebarController', SidebarController); 7 | 8 | SidebarController.$inject = ['$state', 'routerHelper']; 9 | /* @ngInject */ 10 | function SidebarController($state, routerHelper) { 11 | var vm = this; 12 | var states = routerHelper.getStates(); 13 | vm.isCurrent = isCurrent; 14 | 15 | activate(); 16 | 17 | function activate() { getNavRoutes(); } 18 | 19 | function getNavRoutes() { 20 | vm.navRoutes = states.filter(function(r) { 21 | return r.settings && r.settings.nav; 22 | }).sort(function(r1, r2) { 23 | return r1.settings.nav - r2.settings.nav; 24 | }); 25 | } 26 | 27 | function isCurrent(route) { 28 | if (!route.title || !$state.current || !$state.current.title) { 29 | return ''; 30 | } 31 | var menuName = route.title; 32 | return $state.current.title.substr(0, menuName.length) === menuName ? 'current' : ''; 33 | } 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/sidebar.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('layout', function() { 3 | describe('sidebar', function() { 4 | var controller; 5 | var views = { 6 | dashboard: 'app/dashboard/dashboard.html', 7 | customers: 'app/customers/customers.html' 8 | }; 9 | 10 | beforeEach(function() { 11 | module('app.layout', bard.fakeToastr); 12 | bard.inject('$controller', '$httpBackend', '$location', 13 | '$rootScope', '$state', 'routerHelper'); 14 | }); 15 | 16 | beforeEach(function() { 17 | routerHelper.configureStates(mockData.getMockStates(), '/'); 18 | controller = $controller('SidebarController'); 19 | $rootScope.$apply(); 20 | }); 21 | 22 | bard.verifyNoOutstandingHttpRequests(); 23 | 24 | it('should have isCurrent() for / to return `current`', function() { 25 | $location.path('/'); 26 | expect(controller.isCurrent($state.current)).to.equal('current'); 27 | }); 28 | 29 | it('should have isCurrent() for /customers to return `current`', function() { 30 | $location.path('/customers'); 31 | expect(controller.isCurrent($state.current)).to.equal('current'); 32 | }); 33 | 34 | it('should have isCurrent() for non route not return `current`', function() { 35 | $location.path('/invalid'); 36 | expect(controller.isCurrent({title: 'invalid'})).not.to.equal('current'); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/layout/sidebar.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="SidebarController as vm"> 2 | <ht-sidebar when-done-animating="vm.sidebarReady()"> 3 | <div class="sidebar-filler"></div> 4 | <div class="sidebar-dropdown"><a href="#">Menu</a></div> 5 | <div class="sidebar-inner"> 6 | <div class="sidebar-widget"></div> 7 | <ul class="navi"> 8 | <li class="nlightblue fade-selection-animation" ng-class="vm.isCurrent(r)" 9 | ng-repeat="r in vm.navRoutes"> 10 | <a ui-sref="{{r.name}}" 11 | ng-bind-html="r.settings.content"></a> 12 | </li> 13 | </ul> 14 | </div> 15 | </ht-sidebar> 16 | </div> 17 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/ht-img-person.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htImgPerson', htImgPerson); 7 | 8 | htImgPerson.$inject = ['config']; 9 | /* @ngInject */ 10 | function htImgPerson (config) { 11 | //Usage: 12 | //<img ht-img-person="{{person.imageSource}}"/> 13 | var basePath = config.imageBasePath; 14 | var unknownImage = config.unknownPersonImageSource; 15 | var directive = { 16 | link: link, 17 | restrict: 'A' 18 | }; 19 | return directive; 20 | 21 | function link(scope, element, attrs) { 22 | attrs.$observe('htImgPerson', function (value) { 23 | value = basePath + (value || unknownImage); 24 | attrs.$set('src', value); 25 | }); 26 | } 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/ht-widget-header.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htWidgetHeader', htWidgetHeader); 7 | 8 | /* @ngInject */ 9 | function htWidgetHeader() { 10 | //Usage: 11 | //<div ht-widget-header title="vm.map.title"></div> 12 | // Creates: 13 | // <div ht-widget-header="" 14 | // title="Movie" 15 | // allow-collapse="true" </div> 16 | var directive = { 17 | scope: { 18 | 'title': '@', 19 | 'subtitle': '@', 20 | 'rightText': '@', 21 | 'allowCollapse': '@' 22 | }, 23 | templateUrl: 'app/widgets/widget-header.html', 24 | restrict: 'EA' 25 | }; 26 | return directive; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/ht-widget-towel.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htWidgetTowel', htWidgetTowel); 7 | 8 | /* @ngInject */ 9 | function htWidgetTowel() { 10 | //Usage: 11 | //<div ht-widget-towel name="vm.map.name"></div> 12 | // Creates: 13 | // <div ht-widget-towel="" 14 | // name="Beach Towel" 15 | // price="10.00" 16 | // allow-collapse="true" </div> 17 | var directive = { 18 | scope: { 19 | name: '@', 20 | price: '@' 21 | }, 22 | templateUrl: 'app/widgets/widget-ht-towel.html', 23 | restrict: 'EA' 24 | }; 25 | return directive; 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/widget-header.html: -------------------------------------------------------------------------------- 1 | <div class="widget-head"> 2 | <div class="page-title pull-left">{{title}}</div> 3 | <small class="page-title-subtle" ng-show="subtitle">({{subtitle}})</small> 4 | <div class="widget-icons pull-right"></div> 5 | <small class="pull-right page-title-subtle" ng-show="rightText">{{rightText}}</small> 6 | <div class="clearfix"></div> 7 | </div> -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/widget-ht-towel.html: -------------------------------------------------------------------------------- 1 | <div class="widget-head"> 2 | <div> 3 | <small class="pull-left">{{price}}</small> 4 | <div class="pull-left">{{name}}</div> 5 | </div> 6 | <br> 7 | <img src="images/AngularJS-small.png"></img> 8 | <div class="widget-icons pull-right"></div> 9 | <div class="clearfix"></div> 10 | </div> -------------------------------------------------------------------------------- /hot-towel/src/client/app/widgets/widgets.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.widgets', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /hot-towel/src/client/images/AngularJS-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/src/client/images/AngularJS-small.png -------------------------------------------------------------------------------- /hot-towel/src/client/images/busy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/src/client/images/busy.gif -------------------------------------------------------------------------------- /hot-towel/src/client/images/gulp-tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/src/client/images/gulp-tiny.png -------------------------------------------------------------------------------- /hot-towel/src/client/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/src/client/images/icon.png -------------------------------------------------------------------------------- /hot-towel/src/client/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html ng-app="app"> 3 | 4 | <head> 5 | <style> 6 | /* This helps the ng-show/ng-hide animations start at the right place. */ 7 | /* Since Angular has this but needs to load, this gives us the class early. */ 8 | 9 | .ng-hide { 10 | display: none!important; 11 | } 12 | </style> 13 | <title ng-bind="title">hot-towel 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 | Loading . . . 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 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /hot-towel/src/client/specs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Spec Runner 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Spec Runner

24 |

Make sure the REMOTE server is running
25 | Click on a description title to narrow the scope to just its specs 26 | (see " 27 | ?grep" in address bar).
28 | Click on a spec title to see the test implementation.
29 | Click on page title to start over. 30 |

31 | 32 |
33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /hot-towel/src/client/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 | if (!Function.prototype.bind) { 9 | /*jshint freeze: false */ 10 | Function.prototype.bind = function (oThis) { 11 | if (typeof this !== 'function') { 12 | // closest thing possible to the ECMAScript 5 13 | // internal IsCallable function 14 | var msg = 'Function.prototype.bind - what is trying to be bound is not callable'; 15 | throw new TypeError(msg); 16 | } 17 | 18 | var aArgs = Array.prototype.slice.call(arguments, 1), 19 | fToBind = this, 20 | FuncNoOp = function () {}, 21 | fBound = function () { 22 | return fToBind.apply(this instanceof FuncNoOp && oThis ? this : oThis, 23 | aArgs.concat(Array.prototype.slice.call(arguments))); 24 | }; 25 | 26 | FuncNoOp.prototype = this.prototype; 27 | fBound.prototype = new FuncNoOp(); 28 | 29 | return fBound; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /hot-towel/src/client/test-helpers/mock-data.js: -------------------------------------------------------------------------------- 1 | /* jshint -W079 */ 2 | var mockData = (function() { 3 | return { 4 | getMockPeople: getMockPeople, 5 | getMockStates: getMockStates 6 | }; 7 | 8 | function getMockStates() { 9 | return [ 10 | { 11 | state: 'dashboard', 12 | config: { 13 | url: '/', 14 | templateUrl: 'app/dashboard/dashboard.html', 15 | title: 'dashboard', 16 | settings: { 17 | nav: 1, 18 | content: ' Dashboard' 19 | } 20 | } 21 | } 22 | ]; 23 | } 24 | 25 | function getMockPeople() { 26 | return [ 27 | {firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 28 | {firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 29 | {firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 30 | {firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 31 | {firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 32 | {firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 33 | {firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'} 34 | ]; 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /hot-towel/src/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 favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | var port = process.env.PORT || 8001; 10 | var four0four = require('./utils/404')(); 11 | 12 | var environment = process.env.NODE_ENV; 13 | 14 | app.use(favicon(__dirname + '/favicon.ico')); 15 | app.use(bodyParser.urlencoded({extended: true})); 16 | app.use(bodyParser.json()); 17 | app.use(logger('dev')); 18 | 19 | app.use('/api', require('./routes')); 20 | 21 | console.log('About to crank up node'); 22 | console.log('PORT=' + port); 23 | console.log('NODE_ENV=' + environment); 24 | 25 | switch (environment){ 26 | case 'build': 27 | console.log('** BUILD **'); 28 | app.use(express.static('./build/')); 29 | // Any invalid calls for templateUrls are under app/* and should return 404 30 | app.use('/app/*', function(req, res, next) { 31 | four0four.send404(req, res); 32 | }); 33 | // Any deep link calls should return index.html 34 | app.use('/*', express.static('./build/index.html')); 35 | break; 36 | default: 37 | console.log('** DEV **'); 38 | app.use(express.static('./src/client/')); 39 | app.use(express.static('./')); 40 | app.use(express.static('./tmp')); 41 | // Any invalid calls for templateUrls are under app/* and should return 404 42 | app.use('/app/*', function(req, res, next) { 43 | four0four.send404(req, res); 44 | }); 45 | // Any deep link calls should return index.html 46 | app.use('/*', express.static('./src/client/index.html')); 47 | break; 48 | } 49 | 50 | app.listen(port, function() { 51 | console.log('Express server listening on port ' + port); 52 | console.log('env = ' + app.get('env') + 53 | '\n__dirname = ' + __dirname + 54 | '\nprocess.cwd = ' + process.cwd()); 55 | }); 56 | -------------------------------------------------------------------------------- /hot-towel/src/server/data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | people: getPeople() 3 | }; 4 | 5 | function getPeople() { 6 | return [ 7 | {id: 1, firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 8 | {id: 2, firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 9 | {id: 3, firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 10 | {id: 4, firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 11 | {id: 5, firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 12 | {id: 6, firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 13 | {id: 7, firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'}, 14 | {id: 8, firstName: 'Aaron', lastName: 'Jinglehiemer', age: 22, location: 'Utah'} 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /hot-towel/src/server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkio/angular-i18n/6ddf392c770ed885d6147faab9bd6a60c4e25437/hot-towel/src/server/favicon.ico -------------------------------------------------------------------------------- /hot-towel/src/server/routes.js: -------------------------------------------------------------------------------- 1 | var router = require('express').Router(); 2 | var four0four = require('./utils/404')(); 3 | var data = require('./data'); 4 | 5 | router.get('/people', getPeople); 6 | router.get('/person/:id', getPerson); 7 | router.get('/*', four0four.notFoundMiddleware); 8 | 9 | module.exports = router; 10 | 11 | ////////////// 12 | 13 | function getPeople(req, res, next) { 14 | res.status(200).send(data.people); 15 | } 16 | 17 | function getPerson(req, res, next) { 18 | var id = +req.params.id; 19 | var person = data.people.filter(function(p) { 20 | return p.id === id; 21 | })[0]; 22 | 23 | if (person) { 24 | res.status(200).send(person); 25 | } else { 26 | four0four.send404(req, res, 'person ' + id + ' not found'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hot-towel/src/server/utils/404.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var service = { 3 | notFoundMiddleware: notFoundMiddleware, 4 | send404: send404 5 | }; 6 | return service; 7 | 8 | function notFoundMiddleware(req, res, next) { 9 | send404(req, res, 'API endpoint not found'); 10 | } 11 | 12 | function send404(req, res, description) { 13 | var data = { 14 | status: 404, 15 | message: 'Not Found', 16 | description: description, 17 | url: req.url 18 | }; 19 | res.status(404) 20 | .send(data) 21 | .end(); 22 | } 23 | }; 24 | --------------------------------------------------------------------------------