├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── README.md ├── app ├── .buildignore ├── .htaccess ├── 404.html ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── images │ └── yeoman.png ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ ├── controllers │ │ ├── about.js │ │ ├── contact.js │ │ ├── main.js │ │ └── master.js │ ├── data │ │ ├── sample1.json │ │ └── success.json │ ├── directives │ │ └── youtube.js │ ├── filters │ │ └── reverse.js │ └── services │ │ ├── request.js │ │ └── validators.js ├── styles │ ├── bootstrap.css │ ├── index.less │ └── main.css └── views │ ├── about.html │ ├── contact.html │ ├── fragments │ ├── footer.html │ └── header.html │ ├── main.html │ └── modals │ ├── splendid.html │ └── video.html ├── bower.json ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test ├── .jshintrc ├── runner.html └── spec ├── controllers ├── about.js ├── contact.js ├── main.js └── master.js ├── directives └── youtube.js ├── filters └── palindrome.js └── services └── request.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/bower_components 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | before_script: 6 | - 'npm install -g bower grunt-cli' 7 | - 'bower install' 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-04-01 using generator-angular 0.6.0-rc.2 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Define the configuration for all the tasks 19 | grunt.initConfig({ 20 | 21 | // Project settings 22 | yeoman: { 23 | // configurable paths 24 | app: require('./bower.json').appPath || 'app', 25 | dist: 'dist' 26 | }, 27 | 28 | // Watches files for changes and runs tasks based on the changed files 29 | watch: { 30 | js: { 31 | files: ['{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js'], 32 | tasks: ['newer:jshint:all'] 33 | }, 34 | jsTest: { 35 | files: ['test/spec/{,*/}*.js'], 36 | tasks: ['newer:jshint:test', 'karma'] 37 | }, 38 | styles: { 39 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 40 | tasks: ['newer:copy:styles', 'autoprefixer'] 41 | }, 42 | gruntfile: { 43 | files: ['Gruntfile.js'] 44 | }, 45 | less: { 46 | files: ['<%= yeoman.app %>/styles/{,*/}*.less'], 47 | tasks: ['recess:app'] 48 | }, 49 | livereload: { 50 | options: { 51 | livereload: '<%= connect.options.livereload %>' 52 | }, 53 | files: [ 54 | '<%= yeoman.app %>/{,*/}*.html', 55 | '.tmp/styles/{,*/}*.css', 56 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 57 | ] 58 | } 59 | }, 60 | 61 | // The actual grunt server settings 62 | connect: { 63 | options: { 64 | port: 9000, 65 | // Change this to '0.0.0.0' to access the server from outside. 66 | hostname: 'localhost', 67 | livereload: 35729 68 | }, 69 | livereload: { 70 | options: { 71 | open: true, 72 | base: [ 73 | '.tmp', 74 | '<%= yeoman.app %>' 75 | ] 76 | } 77 | }, 78 | test: { 79 | options: { 80 | port: 9001, 81 | base: [ 82 | '.tmp', 83 | 'test', 84 | '<%= yeoman.app %>' 85 | ] 86 | } 87 | }, 88 | dist: { 89 | options: { 90 | base: '<%= yeoman.dist %>' 91 | } 92 | } 93 | }, 94 | 95 | // Make sure code styles are up to par and there are no obvious mistakes 96 | jshint: { 97 | options: { 98 | jshintrc: '.jshintrc', 99 | reporter: require('jshint-stylish') 100 | }, 101 | all: [ 102 | 'Gruntfile.js', 103 | '<%= yeoman.app %>/scripts/{,*/}*.js' 104 | ], 105 | test: { 106 | options: { 107 | jshintrc: 'test/.jshintrc' 108 | }, 109 | src: ['test/spec/{,*/}*.js'] 110 | } 111 | }, 112 | 113 | // Empties folders to start fresh 114 | clean: { 115 | dist: { 116 | files: [{ 117 | dot: true, 118 | src: [ 119 | '.tmp', 120 | '<%= yeoman.dist %>/*', 121 | '!<%= yeoman.dist %>/.git*' 122 | ] 123 | }] 124 | }, 125 | server: '.tmp' 126 | }, 127 | 128 | // Add vendor prefixed styles 129 | autoprefixer: { 130 | options: { 131 | browsers: ['last 1 version'] 132 | }, 133 | dist: { 134 | files: [{ 135 | expand: true, 136 | cwd: '.tmp/styles/', 137 | src: '{,*/}*.css', 138 | dest: '.tmp/styles/' 139 | }] 140 | } 141 | }, 142 | 143 | 144 | 145 | 146 | 147 | // Renames files for browser caching purposes 148 | rev: { 149 | dist: { 150 | files: { 151 | src: [ 152 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 153 | '<%= yeoman.dist %>/styles/{,*/}*.css', 154 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 155 | '<%= yeoman.dist %>/styles/fonts/*' 156 | ] 157 | } 158 | } 159 | }, 160 | 161 | // Reads HTML for usemin blocks to enable smart builds that automatically 162 | // concat, minify and revision files. Creates configurations in memory so 163 | // additional tasks can operate on them 164 | useminPrepare: { 165 | html: '<%= yeoman.app %>/index.html', 166 | options: { 167 | dest: '<%= yeoman.dist %>' 168 | } 169 | }, 170 | 171 | // Performs rewrites based on rev and the useminPrepare configuration 172 | usemin: { 173 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 174 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 175 | options: { 176 | assetsDirs: ['<%= yeoman.dist %>'] 177 | } 178 | }, 179 | 180 | // The following *-min tasks produce minified files in the dist folder 181 | imagemin: { 182 | dist: { 183 | files: [{ 184 | expand: true, 185 | cwd: '<%= yeoman.app %>/images', 186 | src: '{,*/}*.{png,jpg,jpeg,gif}', 187 | dest: '<%= yeoman.dist %>/images' 188 | }] 189 | } 190 | }, 191 | svgmin: { 192 | dist: { 193 | files: [{ 194 | expand: true, 195 | cwd: '<%= yeoman.app %>/images', 196 | src: '{,*/}*.svg', 197 | dest: '<%= yeoman.dist %>/images' 198 | }] 199 | } 200 | }, 201 | htmlmin: { 202 | dist: { 203 | options: { 204 | // Optional configurations that you can uncomment to use 205 | // removeCommentsFromCDATA: true, 206 | // collapseBooleanAttributes: true, 207 | // removeAttributeQuotes: true, 208 | // removeRedundantAttributes: true, 209 | // useShortDoctype: true, 210 | // removeEmptyAttributes: true, 211 | // removeOptionalTags: true*/ 212 | }, 213 | files: [{ 214 | expand: true, 215 | cwd: '<%= yeoman.app %>', 216 | src: ['*.html', 'views/*.html'], 217 | dest: '<%= yeoman.dist %>' 218 | }] 219 | } 220 | }, 221 | 222 | // Allow the use of non-minsafe AngularJS files. Automatically makes it 223 | // minsafe compatible so Uglify does not destroy the ng references 224 | ngmin: { 225 | dist: { 226 | files: [{ 227 | expand: true, 228 | cwd: '.tmp/concat/scripts', 229 | src: '*.js', 230 | dest: '.tmp/concat/scripts' 231 | }] 232 | } 233 | }, 234 | 235 | // Replace Google CDN references 236 | cdnify: { 237 | dist: { 238 | html: ['<%= yeoman.dist %>/*.html'] 239 | } 240 | }, 241 | 242 | // Copies remaining files to places other tasks can use 243 | copy: { 244 | dist: { 245 | files: [{ 246 | expand: true, 247 | dot: true, 248 | cwd: '<%= yeoman.app %>', 249 | dest: '<%= yeoman.dist %>', 250 | src: [ 251 | '*.{ico,png,txt}', 252 | '.htaccess', 253 | 'bower_components/**/*', 254 | 'images/{,*/}*.{webp}', 255 | 'fonts/*' 256 | ] 257 | }, { 258 | expand: true, 259 | cwd: '.tmp/images', 260 | dest: '<%= yeoman.dist %>/images', 261 | src: [ 262 | 'generated/*' 263 | ] 264 | }] 265 | }, 266 | styles: { 267 | expand: true, 268 | cwd: '<%= yeoman.app %>/styles', 269 | dest: '.tmp/styles/', 270 | src: '{,*/}*.css' 271 | }, 272 | less: { 273 | expand: true, 274 | cwd: '<%= yeoman.app %>/styles', 275 | dest: '.tmp/styles/', 276 | src: '{,*/}*.less' 277 | } 278 | }, 279 | 280 | // Run some tasks in parallel to speed up the build process 281 | concurrent: { 282 | server: [ 283 | 'copy:styles', 284 | 'copy:less' 285 | ], 286 | test: [ 287 | 'copy:styles', 288 | 'copy:less' 289 | ], 290 | dist: [ 291 | 'copy:styles', 292 | 'copy:less', 293 | 'imagemin', 294 | 'svgmin', 295 | 'htmlmin' 296 | ] 297 | }, 298 | 299 | // By default, your `index.html`'s will take care of 300 | // minification. These next options are pre-configured if you do not wish 301 | // to use the Usemin blocks. 302 | // cssmin: { 303 | // dist: { 304 | // files: { 305 | // '<%= yeoman.dist %>/styles/main.css': [ 306 | // '.tmp/styles/{,*/}*.css', 307 | // '<%= yeoman.app %>/styles/{,*/}*.css' 308 | // ] 309 | // } 310 | // } 311 | // }, 312 | // uglify: { 313 | // dist: { 314 | // files: { 315 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 316 | // '<%= yeoman.dist %>/scripts/scripts.js' 317 | // ] 318 | // } 319 | // } 320 | // }, 321 | // concat: { 322 | // dist: {} 323 | // }, 324 | 325 | // Test settings 326 | karma: { 327 | unit: { 328 | configFile: 'karma.conf.js', 329 | singleRun: true 330 | } 331 | }, 332 | 333 | recess: { 334 | app: { 335 | options: { 336 | compile: true, 337 | compress: false 338 | }, 339 | files: [{ 340 | expand: true, 341 | cwd: '<%= yeoman.app %>/styles', 342 | src: 'index.less', 343 | dest: '.tmp/styles/', 344 | ext: '.css' 345 | }] 346 | }, 347 | dist: { 348 | options: { 349 | compile: true, 350 | compress: true 351 | }, 352 | files: [{ 353 | expand: true, 354 | cwd: '.tmp/styles', 355 | src: 'index.less', 356 | dest: '.tmp/styles/', 357 | ext: '.css' 358 | }] 359 | } 360 | } 361 | }); 362 | 363 | 364 | grunt.registerTask('serve', function (target) { 365 | if (target === 'dist') { 366 | return grunt.task.run(['build', 'connect:dist:keepalive']); 367 | } 368 | 369 | grunt.task.run([ 370 | 'clean:server', 371 | 'concurrent:server', 372 | 'autoprefixer', 373 | 'recess:app', 374 | 'connect:livereload', 375 | 'watch' 376 | ]); 377 | }); 378 | 379 | grunt.registerTask('server', function () { 380 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 381 | grunt.task.run(['serve']); 382 | }); 383 | 384 | grunt.registerTask('test', [ 385 | 'clean:server', 386 | 'concurrent:test', 387 | 'autoprefixer', 388 | 'connect:test', 389 | 'karma' 390 | ]); 391 | 392 | grunt.registerTask('build', [ 393 | 'clean:dist', 394 | 'useminPrepare', 395 | 'concurrent:dist', 396 | 'autoprefixer', 397 | 'recess:dist', 398 | 'concat', 399 | 'ngmin', 400 | 'copy:dist', 401 | 'cdnify', 402 | 'cssmin', 403 | 'uglify', 404 | 'rev', 405 | 'usemin' 406 | ]); 407 | 408 | grunt.registerTask('default', [ 409 | 'newer:jshint', 410 | 'test', 411 | 'build' 412 | ]); 413 | 414 | grunt.loadNpmTasks('grunt-recess'); 415 | }; 416 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS Practices 2 | ==================== 3 | 4 | A collection of examples and best practices for AngularJS projects. 5 | 6 | More introductory info can be found here: 7 | https://docs.google.com/a/tivix.com/document/d/1TbSmSOTqYwxzEMFbVp2XGRNPiMCcWOuf_EM8MjRXdQQ/edit?usp=sharing 8 | 9 | Installation 10 | ============ 11 | 12 | Note: I am assuming that you already have node.js installed as well as grunt CLI, etc. Please install the following components before proceeding. 13 | - Node: http://nodejs.org/ 14 | - Yeoman: http://yeoman.io/gettingstarted.html 15 | - Grunt: http://gruntjs.com/getting-started 16 | - Batarang: https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en 17 | 18 | Go to your work directory by running `cd ~/work` 19 | 20 | Run `git clone git@github.com:Tivix/AngularJSPractices.git AngularJSPractices` 21 | 22 | Then run `npm install` 23 | 24 | And finally, run `bower install` 25 | 26 | Run Locally 27 | =========== 28 | 29 | From your working directory, run `grunt serve` 30 | 31 | Reference 32 | ========= 33 | 34 | To bootstrap a project with Yeoman, run `yo angular` 35 | 36 | To create a new route, run `yo angular:route ` 37 | 38 | To create a new controller, run `yo angular:controller ` 39 | 40 | To create a new service, run `yo angular:service ` 41 | 42 | To create a new directive, run `yo angular:directive ` 43 | 44 | To create a new filter, run `yo angular:filter ` 45 | 46 | Search for Javascript libraries by running `bower search ` 47 | 48 | Install a Javascript library and save it to the project by running `bower install --save` 49 | -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 |
    148 |
  • a mistyped address
  • 149 |
  • an out-of-date link
  • 150 |
151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tivix/AngularJSPractices/e3759176e7434dd470c807a5330be32dcc828529/app/favicon.ico -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tivix/AngularJSPractices/e3759176e7434dd470c807a5330be32dcc828529/app/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 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 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tivix/AngularJSPractices/e3759176e7434dd470c807a5330be32dcc828529/app/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tivix/AngularJSPractices/e3759176e7434dd470c807a5330be32dcc828529/app/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tivix/AngularJSPractices/e3759176e7434dd470c807a5330be32dcc828529/app/images/yeoman.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | AngularJS Example App 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 | 39 | 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 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Here, we define our app/module. The list following the 4 | // name of the module represents all of the apps dependencies. 5 | // Yeoman will include the first four based on your input in 6 | // the generator wizard. ui.bootstrap was added after the fact 7 | // in order to give our app access to the AngularJS flavored 8 | // bootstrap functions. 9 | angular.module('angularJspracticesApp', [ 10 | 'ngCookies', 11 | 'ngResource', 12 | 'ngSanitize', 13 | 'ngRoute', 14 | 'ui.bootstrap' 15 | ]) 16 | .config(function ($routeProvider) { 17 | // Here we define all of the app routes we'll support. 18 | $routeProvider 19 | // Home page 20 | .when('/', { 21 | templateUrl: 'views/main.html', 22 | controller: 'MainCtrl' 23 | }) 24 | // About list view 25 | .when('/about', { 26 | templateUrl: 'views/about.html', 27 | controller: 'AboutCtrl', 28 | resolve: { 29 | names: AboutCtrl.loadData 30 | } 31 | }) 32 | // About details view. 33 | // Adding a section to the url prefixed with a colon 34 | // will indicate that this is a variable. :who will 35 | // be replaced with someone's name. 36 | .when('/about/:who', { 37 | templateUrl: 'views/about.html', 38 | controller: 'AboutCtrl' 39 | }) 40 | // Our contact view. 41 | .when('/contact', { 42 | templateUrl: 'views/contact.html', 43 | controller: 'ContactCtrl' 44 | }) 45 | // Our contact view. 46 | .when('/404', { 47 | templateUrl: '404.html', 48 | controller: 'MainCtrl' 49 | }) 50 | // Our catch-all view. 51 | .otherwise({ 52 | redirectTo: '/404' 53 | }); 54 | }) 55 | 56 | // Cache templates. When the app loads, Angular will have the 57 | // browser make a series of requests for these files and store 58 | // then in the $templateCache service. 59 | .run(function($templateCache,$http){ 60 | $http.get('views/main.html', {cache:$templateCache}); 61 | $http.get('views/about.html', {cache:$templateCache}); 62 | $http.get('views/contact.html', {cache:$templateCache}); 63 | $http.get('views/fragments/header.html', {cache:$templateCache}); 64 | $http.get('views/fragments/footer.html', {cache:$templateCache}); 65 | }); -------------------------------------------------------------------------------- /app/scripts/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Okay, this is different from what Yeoman sets up by default. 4 | // We are going to assign the controller to a variable so that we 5 | // can later assign methods to it. Calling the controller method 6 | // of the module `angular.module('angularJspracticesApp')` is 7 | // sufficient to register the controller and make it available to 8 | // routes. 9 | var AboutCtrl = angular.module('angularJspracticesApp') 10 | .controller('AboutCtrl', function ($scope, $route, $routeParams, Request) { 11 | // Since the data we resolved loaded by the route service, we 12 | // need to retrieve it and hand it off to the $scope service, 13 | // which exposes it to the template. 14 | $scope.names = $route.current.locals.names; 15 | // Now, let's check and see if the who variable is set as part 16 | // of the route. The $routeParams service will give us insight 17 | // into this. Check out app.js for more info on how to setup 18 | // route parameters. 19 | $scope.who = $routeParams['who'] || ""; 20 | // If we're looking someone up, let's get a picture of them. 21 | // Google can help us out, right? 22 | if($scope.who != ""){ 23 | // Let's query google and handle the promise. 24 | var myPromise = Request.jsonp({ 25 | url: 'http://ajax.googleapis.com/ajax/services/search/images?v=1.0&q='+$scope.who, 26 | method: 'GET' 27 | }); 28 | // The query has been sent, and sometime later it will be 29 | // fulfilled. Let's define what happens when that is the case. 30 | // The then method takes two arguments: a function to call when 31 | // the promise is fulfilled and one to call when it's rejected. 32 | myPromise.then(function(data){ 33 | $scope.photo = data.responseData.results[0].tbUrl; 34 | },function(data){ 35 | $scope.error = "Well, it looks like there's been a problem. Sorry about that."; 36 | }); 37 | } 38 | //http://ajax.googleapis.com/ajax/services/search/images?v=1.0&q= 39 | }); 40 | 41 | // Now, let's define a method against the controller. This method 42 | // will be exposed to other parts of the app/module. In this 43 | // instance, we want to expose it to app.js and the $routeProvider 44 | // service. This is because we are going to configure the /about 45 | // route to resolve some data before the route change is complete. 46 | // The method returns the promise generated by the Request service 47 | // which will resolve once it's fetched sample1.json from the 48 | // server. 49 | AboutCtrl.loadData = function(Request){ 50 | return Request.sync({ 51 | url: '/scripts/data/sample1.json', 52 | method: 'GET' 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /app/scripts/controllers/contact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .controller('ContactCtrl', function ($scope, $log, Validators, Request) { 5 | // Here we'll setup some containers and flags, right off the bat. 6 | // This represents our form model. Each property will be mapped 7 | // to a form field. 8 | $scope.mymodel = {name:""}; 9 | // This is our errors container, which will be used by the 10 | // validators service. 11 | $scope.errors = []; 12 | // The submitted flag will update the UI based on the state of the 13 | // submission. 14 | $scope.submitted = false; 15 | // The sending flag will indicate when to disable the submit button. 16 | $scope.sending = false; 17 | // Let's define a function to be triggered when the form is submitted. 18 | $scope.sendMessage = function(){ 19 | // First things first, if there are any validation errors, post 20 | // messages back to the screen. 21 | Validators.validate_form($scope.contactus,$scope.errors); 22 | // If the form is invalid, short-circuit it. 23 | if($scope.contactus.$invalid){ 24 | // Do not continue. 25 | return false; 26 | } 27 | // Setup a POST request that includes our model. 28 | // This will be stored as a promise. 29 | // NOTE: This will fail because you can't post to a file in the grunt 30 | // server. It will return 404. For the purposes of demonstration, 31 | // we will treat that result as a success result. 32 | var post_request = Request.sync({ 33 | url: "/scripts/data/success.json", 34 | method: 'POST', 35 | data: $scope.mymodel 36 | }); 37 | // The request has been sent, let's prevent the submit button from 38 | // being pressed again. 39 | $scope.sending = true; 40 | // Now let's figure out what to do with the promise once it's 41 | // fulfilled. 42 | post_request.then(function(data){ 43 | // Success case! Set submitted flag and undo sending flag. 44 | $scope.submitted = true; 45 | $scope.sending = false; 46 | // Let's log this result. 47 | $log.info("Server fulfilled request with result " + data.result); 48 | },function(data){ 49 | // Problem case! Set submitted flag and undo sending flag. 50 | $scope.submitted = true; 51 | $scope.sending = false; 52 | // Let's log this result. 53 | $log.error("Oh geez. Looks like there was a problem submitting this to the server."); 54 | }); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .controller('MainCtrl', function ($scope, $modal) { 5 | // Some example data for our page. 6 | $scope.now = new Date(); 7 | $scope.price = 1001.92; 8 | $scope.message = "Hello World"; 9 | // Video modal function 10 | $scope.videoWindow = function(){ 11 | $modal.open({ 12 | templateUrl: 'views/modals/video.html', 13 | controller: 'VideoModalCtrl' 14 | }); 15 | } 16 | }); 17 | 18 | // Setup a controller for the modal used to display a video. 19 | angular.module('angularJspracticesApp') 20 | .controller('VideoModalCtrl', function ($scope, $modalInstance) { 21 | // Handle OK button 22 | $scope.ok = function () { 23 | $modalInstance.close("OKAY"); 24 | }; 25 | // Handle cancel button 26 | $scope.cancel = function () { 27 | $modalInstance.dismiss('cancel'); 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /app/scripts/controllers/master.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // master.js is a controller that we've setup above the ng-view section 4 | // of the page. This means that this controller will be continuously 5 | // active for the lifetime of the app. Each time we change routes, this 6 | // one will persist. It also means that all scope variables in master.js 7 | // will be accessible in child controller. We use this to manipulate 8 | // elements that aren't related to the route - like the header and footer 9 | // of the page. 10 | angular.module('angularJspracticesApp') 11 | .controller('MasterCtrl', function ($scope, $location, $modal, $log, Validators) { 12 | // Let's setup a listener. The event we want to listen for is 13 | // $locationChangeStart. This occurs when the user has changed routes 14 | // by clicking a link and activating a method that changes the route 15 | // for them. Let's tell the controller what to do when this happens. 16 | $scope.$on("$locationChangeStart", function(event){ 17 | // We're going to update the where variable with the new path. 18 | // This will be used in the header to mark the right link as active. 19 | $scope.where = $location.path(); 20 | }); 21 | // Let's expose some validation methods 22 | $scope.validate_field = function(name,form,errors){ 23 | Validators.validate_field(name,form,errors); 24 | }; // Now we want to setup a method that invokes a modal window, and it 25 | // will be accessible to all child controllers. Basically, every page 26 | // in the app can call this. 27 | $scope.splendid = function(){ 28 | // We open the modal from the $modal service. We provide a template 29 | // url to be used in the modal, and a controller to use. We receive 30 | // an object that contains a promise that will be fulfilled when the 31 | // modal window is closed. In this example we also resolve the 32 | // where variable so that the modal has access to this information. 33 | var splendid_modal = $modal.open({ 34 | templateUrl: 'views/modals/splendid.html', 35 | controller: 'SplendidModalCtrl', 36 | resolve: { 37 | "where": function(){ return $scope.where; } 38 | } 39 | }); 40 | // Let's tell the controller what to do when the promise is fulfilled. 41 | // NOTE: ui.bootstrap modals store the promise in the result property. 42 | splendid_modal.result.then(function(result){ 43 | // Log an info level event indicating what the success value was. 44 | $log.info("The modal window was closed with a result of " + result); 45 | },function(result){ 46 | // Log a warning level event indicating what the rejection value was. 47 | $log.warn("Looks like the modal window was cancelled with a result of " + (result || "none")); 48 | }); 49 | } 50 | }); 51 | 52 | // ProcessingModalCtrl is used to control the modal that appears while app is processing. 53 | // Notice that the variable where is being injected into this controller. It 54 | // was configured as part of the resolve property when setting up the modal. 55 | // You can make it accessible to the template by assigning it to the scope. 56 | angular.module('angularJspracticesApp') 57 | .controller('SplendidModalCtrl', function ($scope, $modalInstance, where) { 58 | // Handle OK button 59 | $scope.ok = function () { 60 | $modalInstance.close("OKAY"); 61 | }; 62 | // Handle cancel button 63 | $scope.cancel = function () { 64 | $modalInstance.dismiss('cancel'); 65 | }; 66 | // Assign the resolved where property to the scope. 67 | $scope.where = where; 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /app/scripts/data/sample1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Bert", 3 | "Ernie", 4 | "Oscar the Grouch" 5 | ] -------------------------------------------------------------------------------- /app/scripts/data/success.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": "okay" 3 | } -------------------------------------------------------------------------------- /app/scripts/directives/youtube.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .directive('youtube', function () { 5 | return { 6 | restrict: 'A', 7 | link: function postLink(scope, element, attrs) { 8 | // When the directive is linked into the DOM, let's setup the content. 9 | // element gives us access to the DOM where the directive is applied. 10 | // We can use it in ways similar to how you manipulate elements in jQuery. 11 | // attrs provides a list of properties that are set to the same element as 12 | // the directive. So we can retrieve the youtube id set to it and use it 13 | // to generate the appropriate video embed. 14 | element.html(''); 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/filters/reverse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .filter('reverse', function () { 5 | return function (input) { 6 | return input.split('').reverse().join(''); 7 | }; 8 | }); 9 | -------------------------------------------------------------------------------- /app/scripts/services/request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .service('Request', function Request($http, $q, $location, $window) { 5 | // AngularJS will instantiate a singleton by calling "new" on this function 6 | var loading = false; 7 | var service = { 8 | 9 | // JSONP functionality allows requests to be made across domains. 10 | // This has not been updated with enhancements for this project. 11 | jsonp: function(args){ 12 | // Default the arguments list 13 | args = args || {}; 14 | // Determine which separator to use for URL encoding. 15 | var x = "?"; 16 | if(args.url.indexOf("?") != -1){ 17 | x = "&"; 18 | } 19 | // Append callback param so AngularJS can process the response. 20 | args.url += x+"callback=JSON_CALLBACK"; 21 | // Setup containers and flags used by request. 22 | var deferred = $q.defer(), 23 | url = args.url || this.url, 24 | method = args.method || "GET", 25 | params = args.params || {}, 26 | data = args.data || {}; 27 | // Fire the request 28 | $http.jsonp(url, { 29 | method: method.toUpperCase(), 30 | params: params, 31 | data: data 32 | }) 33 | .success(angular.bind(this, function(data, status, headers, config) { 34 | // Resolve promise. 35 | deferred.resolve(data, status); 36 | })) 37 | .error(angular.bind(this, function(data, status, headers, config) { 38 | // Handle error case. 39 | console.log("error syncing with: " + url + "\nHTTP Error " + status); 40 | // Set request status 41 | if(data){ 42 | data.status = status; 43 | } 44 | // If server responds with FORBIDDEN 45 | if(status == "403"){ 46 | } 47 | // If the server responds with SERVICE UNAVAILABLE 48 | if(status == "503"){ 49 | } 50 | // If there is no response... 51 | if(status == 0){ 52 | // and data is empty, then there was a connection error. 53 | if(data == ""){ 54 | // Inject a non field error alerting the user 55 | // that there's been a connection error. 56 | data = {}; 57 | data['status'] = 0; 58 | data['non_field_errors'] = ["Could not connect. Please try again."]; 59 | } 60 | // or if the data is null, then there was a timeout. 61 | if(data == null){ 62 | // Inject a non field error alerting the user 63 | // that there's been a timeout error. 64 | data = {}; 65 | data['status'] = 0; 66 | data['non_field_errors'] = ["Server timed out. Please try again."]; 67 | } 68 | } 69 | // Resolve the promise 70 | deferred.reject(data, status, headers, config); 71 | })); 72 | // Return the promise for the request 73 | return deferred.promise; 74 | }, 75 | 76 | // sync handles GET and POST requests using the browser's 77 | // AJAX functionality. Cross domain requests are only possible 78 | // with a server that's enabled CORS. 79 | // ALL REQUESTS TIMEOUT AFTER 10s 80 | sync: function(args) { 81 | // Default the arguments list 82 | params = args.params || {} 83 | args = args || {}; 84 | // Configure containers and flags 85 | var deferred = $q.defer(), 86 | url = args.url || this.url, 87 | method = args.method || "GET", 88 | params = params, 89 | data = args.data || {}, 90 | timeout = args.timeout || 10000; 91 | // IMPORTANT: withcred must be set to true in order to 92 | // use the cookies set by Django. Otherwise all requests 93 | // will be made without authorization. 94 | var withcred = false; 95 | // Fire the request, as configured. 96 | $http({ 97 | url: url, 98 | withCredentials: withcred, 99 | method: method.toUpperCase(), 100 | params: params, 101 | data: data, 102 | timeout: timeout 103 | }) 104 | .success(angular.bind(this,function(data, status, headers, config) { 105 | // Resolve promise. 106 | deferred.resolve(data, status); 107 | })) 108 | .error(angular.bind(this, function(data, status, headers, config) { 109 | // Handle error case. 110 | console.log("error syncing with: " + url + "\nHTTP Error " + status); 111 | // Set request status 112 | if(data){ 113 | data.status = status; 114 | } 115 | // If server responds with FORBIDDEN 116 | if(status == "403"){ 117 | } 118 | // If the server responds with SERVICE UNAVAILABLE 119 | if(status == "503"){ 120 | } 121 | // If there is no response... 122 | if(status == 0){ 123 | // and data is empty, then there was a connection error. 124 | if(data == ""){ 125 | // Inject a non field error alerting the user 126 | // that there's been a connection error. 127 | data = {}; 128 | data['status'] = 0; 129 | data['non_field_errors'] = ["Could not connect. Please try again."]; 130 | } 131 | // or if the data is null, then there was a timeout. 132 | if(data == null){ 133 | // Inject a non field error alerting the user 134 | // that there's been a timeout error. 135 | data = {}; 136 | data['status'] = 0; 137 | data['non_field_errors'] = ["Server timed out. Please try again."]; 138 | } 139 | } 140 | // Resolve the promise 141 | deferred.reject(data, status, headers, config); 142 | })); 143 | // Return promise to calling function. 144 | return deferred.promise; 145 | } 146 | 147 | }; 148 | // The service is returned as a singleton. 149 | return service; 150 | }); 151 | 152 | -------------------------------------------------------------------------------- /app/scripts/services/validators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularJspracticesApp') 4 | .service('Validators', function Validators() { 5 | // AngularJS will instantiate a singleton by calling "new" on this function 6 | return { 7 | // Global validation error messages that are used if not overridden. 8 | // Key matches validation error key used by AngularJS API. 9 | 'errors': { 10 | 'required': "This field is required.", 11 | 'email': "Please enter a valid email address.", 12 | 'minlength': "Value is not long enough.", 13 | 'maxlength': "Value is too long." 14 | }, 15 | // Custom validation error messages can be specified on a per 16 | // field basis, and override the default error messages. 17 | 'custom_errors': { 18 | // Custom errors must be formatted as follows 19 | //'field_name': {'error_key':'Error Message'}, 20 | }, 21 | // get_custom_error retrieves a custom error message 22 | // if it exists 23 | 'get_custom_error': function(name,error){ 24 | if(!this.custom_errors[name]){ 25 | return false; 26 | } 27 | if(!this.custom_errors[name][error]){ 28 | return false; 29 | } 30 | return this.custom_errors[name][error]; 31 | }, 32 | // validate_field is called by templates/controllers to 33 | // collect all validation error messages for display on the page. 34 | // It is usually called by validate_form or from a blur event 35 | // on a field. 36 | 'validate_field': function(name,form,errors,custom_messages){ 37 | // Setup containers 38 | var report_errors = []; 39 | // Proceed only if field is invalid 40 | if(form[name].$invalid){ 41 | // Loop through all potential field errors 42 | for(var e in form[name].$error){ 43 | // If error is activated 44 | if(form[name].$error[e]){ 45 | // Attempt to get a custom error message 46 | var custom_error = this.get_custom_error(name,e); 47 | if(custom_error){ 48 | // Use custom error message if it exists 49 | report_errors.push(custom_error); 50 | }else if(this.errors[e]){ 51 | // Otherwise use the default error message 52 | // if it exists. 53 | report_errors.push(this.errors[e]); 54 | }else{ 55 | // If no default or custom error message 56 | // exists for this error, use error key 57 | // as the message. 58 | report_errors.push("Error: " + e) 59 | } 60 | } 61 | } 62 | } 63 | // Setup another container 64 | var clean_errors = []; 65 | // De-duplicate error messages 66 | angular.forEach(report_errors, function(el, i){ 67 | if(clean_errors.indexOf(el) == -1) clean_errors.push(el); 68 | }); 69 | // If requesting function specified an existing errors 70 | // container, dump all error messages into it. 71 | if(errors){ 72 | errors[name] = clean_errors; 73 | } 74 | }, 75 | // validate_distinct checks two fields to see if they 76 | // are distinct. Then returns validation errors based 77 | // on the provided message if they match. 78 | 'validate_distinct': function(name_1, name_2, form, errors, message){ 79 | // Get the fields from the form 80 | var field1 = form[name_1]; 81 | var field2 = form[name_2]; 82 | // Check if the field values are set, and match 83 | if(field1.$viewValue == field2.$viewValue && !!field1.$viewValue){ 84 | // Trigger validation error for distinct on first field 85 | field1.$setValidity("distinct", false); 86 | }else{ 87 | // Otherwise unset any distinct validation errors. 88 | field1.$setValidity("distinct", true); 89 | } 90 | // If first field has a value, perform other validation checks. 91 | if(!!field1.$viewValue){ 92 | this.validate_field(name_1, form, errors); 93 | } 94 | }, 95 | // validate_identical checks two fields to see if they 96 | // are identical. Then returns validation errors based 97 | // on the provided message if they don't match. 98 | 'validate_identical': function(name_1, name_2, form, errors, message){ 99 | // Get the fields from the form 100 | var field1 = form[name_1]; 101 | var field2 = form[name_2]; 102 | // Check if the field values are set, and don't match 103 | if(field1.$modelValue != field2.$modelValue && !!field1.$modelValue){ 104 | // Trigger validation error for identical on first field 105 | field1.$setValidity("identical", false); 106 | }else{ 107 | // Otherwise unset any identical validation errors. 108 | field1.$setValidity("identical", true); 109 | } 110 | // If first field has a value, perform other validation checks. 111 | if(!!field1.$viewValue){ 112 | this.validate_field(name_1, form, errors); 113 | } 114 | }, 115 | // validate_form is usually called when submit button is pressed. It 116 | // will loop through all form fields and collect error messages based 117 | // on running validate_field against each of them. 118 | 'validate_form': function(form,errors){ 119 | // Loop through all form properties (fields being a subset) 120 | for(var field in form){ 121 | // Check that property doesn't start with $ 122 | // If it doesn't, then it is a field as stored by AngularJS 123 | if(field.substr(0,1) != "$"){ 124 | // Trigger validate_field against it. 125 | this.validate_field(field,form,errors); 126 | } 127 | } 128 | } 129 | } 130 | }); 131 | -------------------------------------------------------------------------------- /app/styles/index.less: -------------------------------------------------------------------------------- 1 | @header-font: Impact; 2 | h1{ 3 | font-family: @header-font; 4 | } -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | } 6 | 7 | /* Everything but the jumbotron gets side spacing for mobile first views */ 8 | .header, 9 | .marketing, 10 | .footer { 11 | padding-left: 15px; 12 | padding-right: 15px; 13 | } 14 | 15 | /* Custom page header */ 16 | .header { 17 | border-bottom: 1px solid #e5e5e5; 18 | } 19 | /* Make the masthead heading the same height as the navigation */ 20 | .header h3 { 21 | margin-top: 0; 22 | margin-bottom: 0; 23 | line-height: 40px; 24 | padding-bottom: 19px; 25 | } 26 | 27 | /* Custom page footer */ 28 | .footer { 29 | padding-top: 19px; 30 | color: #777; 31 | border-top: 1px solid #e5e5e5; 32 | } 33 | 34 | /* Customize container */ 35 | @media (min-width: 768px) { 36 | .container { 37 | max-width: 730px; 38 | } 39 | } 40 | .container-narrow > hr { 41 | margin: 30px 0; 42 | } 43 | 44 | /* Main marketing message and sign up button */ 45 | .jumbotron { 46 | text-align: center; 47 | border-bottom: 1px solid #e5e5e5; 48 | } 49 | .jumbotron .btn { 50 | font-size: 21px; 51 | padding: 14px 24px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | .marketing p + h4 { 59 | margin-top: 28px; 60 | } 61 | 62 | /* Responsive: Portrait tablets and up */ 63 | @media screen and (min-width: 768px) { 64 | /* Remove the padding we set earlier */ 65 | .header, 66 | .marketing, 67 | .footer { 68 | padding-left: 0; 69 | padding-right: 0; 70 | } 71 | /* Space out the masthead */ 72 | .header { 73 | margin-bottom: 30px; 74 | } 75 | /* Remove the bottom border on the jumbotron for visual effect */ 76 | .jumbotron { 77 | border-bottom: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 | 16 |
17 |

About Us

18 |

The following has been loaded from sample1.json in the /app/scripts/data directory.

19 |

It's an example of how to use the resolve property of the $routeProvider service to load 20 | data before the route is changed.

21 |
    22 | 28 |
  • 29 | 37 | {{name}} 38 |
  • 39 |
40 |

There are a few things that happen differently in the controller (about.js) to make this 41 | happen. Check out the comments for more info.

42 |
43 | 49 |
50 |

About {{who}}

51 | 52 |

So you're interested in {{who}}, are you? Sorry, I've got nothin'. Better go back 53 | and try someone else.

54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /app/views/contact.html: -------------------------------------------------------------------------------- 1 |
2 |

Contact Us

3 |
4 | 5 |
6 |

The following is a sample form. It demonstrates how to wire up a model 7 | property to a form field and configure validation. The following field has 8 | the following validation rules:

9 |
    10 |
  • It is required
  • 11 |
  • It must be 3 characters long
  • 12 |
  • It must be less than 20 characters long
  • 13 |
14 | 20 |
21 |
22 | 23 | 32 | 37 |
38 | 41 |

{{error}}

42 | 45 |

46 |
47 | 50 |

Thanks

51 |
52 | -------------------------------------------------------------------------------- /app/views/fragments/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/fragments/header.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |

AngularJSPractices

8 |
9 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |

'Allo, 'Allo!

3 |

4 | I'm Yeoman
5 | Always a pleasure scaffolding your apps. 6 |

7 | 8 |

9 |
10 | 11 |
12 |

Filter Examples

13 |

14 | Here's an example of how AngularJS filters work. I've created a date object. If I present it in its raw format, we get {{now}}. If I apply a filter, we get {{now | date : "MM/dd/yyyy"}}. Similarly, with currency, I might have a value like {{price}}. If I wanted to properly format it, I could apply the currency filter and get {{price | currency : "$"}} instead. 15 |

16 |

You can also define your own custom filters. An example I've prepared is reverse.js. Let's say I have the message {{message}}. When I apply the reverse filter, it becomes {{message | reverse}}.

17 |

Directive Example

18 |

19 | Try out a directive in this modal window. 20 |

21 |

HTML5 Boilerplate

22 |

23 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. 24 |

25 | 26 |

Angular

27 |

28 | AngularJS is a toolset for building the framework most suited to your application development. 29 |

30 | 31 |

Karma

32 |

Spectacular Test Runner for JavaScript.

33 |
34 | -------------------------------------------------------------------------------- /app/views/modals/splendid.html: -------------------------------------------------------------------------------- 1 | 5 | 9 | 12 | -------------------------------------------------------------------------------- /app/views/modals/video.html: -------------------------------------------------------------------------------- 1 | 5 | 10 | 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-jspractices", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.0", 6 | "json3": "~3.2.4", 7 | "es5-shim": "~2.1.0", 8 | "jquery": "~1.10.2", 9 | "angular-resource": "~1.2.0", 10 | "angular-cookies": "~1.2.0", 11 | "angular-sanitize": "~1.2.0", 12 | "angular-route": "~1.2.0", 13 | "angular-bootstrap": "~0.10.0" 14 | }, 15 | "devDependencies": { 16 | "angular-mocks": "~1.2.0", 17 | "angular-scenario": "~1.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['ng-scenario'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/e2e/**/*.js' 15 | ], 16 | 17 | // list of files / patterns to exclude 18 | exclude: [], 19 | 20 | // web server port 21 | port: 8080, 22 | 23 | // level of logging 24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 25 | logLevel: config.LOG_INFO, 26 | 27 | 28 | // enable / disable watching file and executing tests whenever any file changes 29 | autoWatch: false, 30 | 31 | 32 | // Start these browsers, currently available: 33 | // - Chrome 34 | // - ChromeCanary 35 | // - Firefox 36 | // - Opera 37 | // - Safari (only Mac) 38 | // - PhantomJS 39 | // - IE (only Windows) 40 | browsers: ['Chrome'], 41 | 42 | 43 | // Continuous Integration mode 44 | // if true, it capture browsers, run tests and exit 45 | singleRun: false 46 | 47 | // Uncomment the following lines if you are using grunt's server to run the tests 48 | // proxies: { 49 | // '/': 'http://localhost:9000/' 50 | // }, 51 | // URL root prevent conflicts with the site root 52 | // urlRoot: '_karma_' 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'app/bower_components/angular/angular.js', 15 | 'app/bower_components/angular-mocks/angular-mocks.js', 16 | 'app/bower_components/angular-resource/angular-resource.js', 17 | 'app/bower_components/angular-cookies/angular-cookies.js', 18 | 'app/bower_components/angular-sanitize/angular-sanitize.js', 19 | 'app/bower_components/angular-route/angular-route.js', 20 | 'app/scripts/*.js', 21 | 'app/scripts/**/*.js', 22 | 'test/mock/**/*.js', 23 | 'test/spec/**/*.js' 24 | ], 25 | 26 | // list of files / patterns to exclude 27 | exclude: [], 28 | 29 | // web server port 30 | port: 8080, 31 | 32 | // level of logging 33 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 34 | logLevel: config.LOG_INFO, 35 | 36 | 37 | // enable / disable watching file and executing tests whenever any file changes 38 | autoWatch: false, 39 | 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari (only Mac) 47 | // - PhantomJS 48 | // - IE (only Windows) 49 | browsers: ['Chrome'], 50 | 51 | 52 | // Continuous Integration mode 53 | // if true, it capture browsers, run tests and exit 54 | singleRun: false 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjspractices", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "grunt-recess": "~0.5.0" 6 | }, 7 | "devDependencies": { 8 | "grunt": "~0.4.1", 9 | "grunt-autoprefixer": "~0.4.0", 10 | "grunt-concurrent": "~0.4.1", 11 | "grunt-contrib-clean": "~0.5.0", 12 | "grunt-contrib-coffee": "~0.7.0", 13 | "grunt-contrib-compass": "~0.6.0", 14 | "grunt-contrib-concat": "~0.3.0", 15 | "grunt-contrib-connect": "~0.5.0", 16 | "grunt-contrib-copy": "~0.4.1", 17 | "grunt-contrib-cssmin": "~0.7.0", 18 | "grunt-contrib-htmlmin": "~0.1.3", 19 | "grunt-contrib-imagemin": "~0.3.0", 20 | "grunt-contrib-jshint": "~0.7.1", 21 | "grunt-contrib-uglify": "~0.2.0", 22 | "grunt-contrib-watch": "~0.5.2", 23 | "grunt-google-cdn": "~0.2.0", 24 | "grunt-newer": "~0.5.4", 25 | "grunt-ngmin": "~0.0.2", 26 | "grunt-rev": "~0.1.0", 27 | "grunt-svgmin": "~0.2.0", 28 | "grunt-usemin": "~2.0.0", 29 | "jshint-stylish": "~0.1.3", 30 | "load-grunt-tasks": "~0.2.0", 31 | "time-grunt": "~0.2.1", 32 | "karma-ng-scenario": "~0.1.0", 33 | "grunt-karma": "~0.8.2", 34 | "karma": "~0.12.2", 35 | "karma-ng-html2js-preprocessor": "~0.1.0" 36 | }, 37 | "engines": { 38 | "node": ">=0.8.0" 39 | }, 40 | "scripts": { 41 | "test": "grunt test" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "spyOn": false 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/contact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ContactCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | var ContactCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ContactCtrl = $controller('ContactCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/master.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MasterCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | var MasterCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MasterCtrl = $controller('MasterCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/directives/youtube.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: youtube', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the youtube directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /test/spec/filters/palindrome.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Filter: palindrome', function () { 4 | 5 | // load the filter's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | // initialize a new instance of the filter before each test 9 | var palindrome; 10 | beforeEach(inject(function ($filter) { 11 | palindrome = $filter('palindrome'); 12 | })); 13 | 14 | it('should return the input prefixed with "palindrome filter:"', function () { 15 | var text = 'angularjs'; 16 | expect(palindrome(text)).toBe('palindrome filter: ' + text); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/services/request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: Request', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('angularJspracticesApp')); 7 | 8 | // instantiate service 9 | var Request; 10 | beforeEach(inject(function (_Request_) { 11 | Request = _Request_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!Request).toBe(true); 16 | }); 17 | 18 | }); 19 | --------------------------------------------------------------------------------