├── .editorconfig ├── .eslintrc ├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── assets │ ├── css │ │ ├── angular-material-boilerplate.css │ │ └── angular-material.min.css │ ├── images │ │ ├── favicon.ico │ │ └── user-profile.png │ └── scripts │ │ ├── angular-material-boilerplate.js │ │ ├── bundle.js │ │ └── templates.js └── index.html ├── gulp_config.js ├── gulpfile.js ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── protractor.config.js ├── src ├── app │ ├── about │ │ ├── about.contoller.spec.js │ │ ├── about.controller.e2e.js │ │ ├── about.controller.js │ │ ├── about.module.js │ │ ├── about.style.scss │ │ └── about.view.html │ ├── app.js │ ├── layout │ │ ├── layout.controller.js │ │ ├── layout.module.js │ │ ├── layout.style.scss │ │ └── layout.view.html │ ├── shared │ │ ├── directives │ │ │ └── backButton.js │ │ ├── services │ │ │ └── Utils.js │ │ ├── shared.module.js │ │ └── styles │ │ │ ├── _color-palette.scss │ │ │ ├── _mixins.scss │ │ │ ├── overrides.scss │ │ │ └── shared.scss │ ├── sidenav │ │ ├── sidenav.controller.js │ │ ├── sidenav.module.js │ │ ├── sidenav.service.js │ │ ├── sidenav.style.scss │ │ └── sidenav.view.html │ ├── todo │ │ ├── todo.controller.e2e.js │ │ ├── todo.controller.js │ │ ├── todo.controller.spec.js │ │ ├── todo.module.js │ │ ├── todo.service.js │ │ ├── todo.style.scss │ │ └── todo.view.html │ └── toolbar │ │ ├── toolbar.controller.js │ │ ├── toolbar.module.js │ │ ├── toolbar.style.scss │ │ └── toolbar.view.html ├── assets │ └── images │ │ ├── favicon.ico │ │ └── user-profile.png └── index.html ├── vendor.config.js └── vendor_files.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | 4 | # No .editorconfig files above the root directory 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["angular"], 3 | 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "jasmine" : true, 8 | "phantomjs" : true 9 | }, 10 | 11 | "rules": { 12 | "strict": 0, 13 | "no-alert": 2, 14 | "no-caller": 2, 15 | "no-bitwise": 0, 16 | "no-console": 1, 17 | "no-debugger": 1, 18 | "no-empty": 1, 19 | "no-eval": 2, 20 | "no-ex-assign": 1, 21 | "no-floating-decimal": 0, 22 | "no-implied-eval": 1, 23 | "no-with": 1, 24 | "no-fallthrough": 1, 25 | "no-unreachable": 2, 26 | "no-underscore-dangle": 0, 27 | "no-undef-init": 1, 28 | "no-octal": 1, 29 | "no-obj-calls": 1, 30 | "no-new-wrappers": 1, 31 | "no-new": 1, 32 | "no-new-func": 1, 33 | "no-native-reassign": 1, 34 | "no-plusplus": 0, 35 | "no-delete-var": 1, 36 | "no-return-assign": 1, 37 | "no-new-object": 1, 38 | "no-label-var": 1, 39 | "no-ternary": 0, 40 | "no-self-compare": 0, 41 | "smarter-eqeqeq": 0, 42 | "brace-style": 0, 43 | "camelcase": 1, 44 | "curly": 1, 45 | "dot-notation": 1, 46 | "eqeqeq": 2, 47 | "new-parens": 1, 48 | "guard-for-in": 0, 49 | "radix": 0, 50 | "new-cap": 0, 51 | "quote-props": 0, 52 | "semi": 2, 53 | "use-isnan": 1, 54 | "quotes": [1, "single"], 55 | "max-params": [0, 3], 56 | "max-statements": [0, 10], 57 | "complexity": [0, 11], 58 | "wrap-iife": 1, 59 | "no-multi-str": 1, 60 | "no-multi-spaces": 1, 61 | "key-spacing": 0, 62 | "no-unused-vars": 2, 63 | "no-shadow": 0, 64 | "no-undef": 1, 65 | "comma-spacing": 1, 66 | "no-use-before-define": 0, 67 | 68 | // ANGULAR RULES 69 | "angular/controller-as": 0, 70 | "angular/typecheck-object": 0, 71 | "angular/timeout-service": 0, 72 | "angular/controller-as-vm": 1, 73 | "angular/controller-as-route": 0, 74 | "angular/controller-name": [1, "/[A-Z].*Controller|Ctrl$/"], //e.g HomeController or HomeCtrl 75 | "angular/on-watch": 0, 76 | "angular/di": 0, 77 | "angular/log": 0 78 | }, 79 | 80 | "globals": { 81 | "document": true, 82 | "window": true, 83 | "before": true, 84 | "beforeEach": true, 85 | "inject": true, 86 | "$log": false, 87 | "_": true, 88 | "after": true, 89 | "afterEach": true, 90 | "angular": true, 91 | "require": true, 92 | "module": true, 93 | "define": true, 94 | "brackets": true, 95 | "jQuery": true, 96 | "$": true, 97 | "Mustache": true, 98 | "CallManager": true, 99 | "getEncodedServerAddr": true, 100 | "log4javascript": true, 101 | "console": true, 102 | "atmosphere": true, 103 | "jasmine": true, 104 | "exports": true, 105 | "describe": true, 106 | "browser": true, 107 | "it": true, 108 | "by": true, 109 | "waits": true, 110 | "expect": true, 111 | "runs": true, 112 | "element": true, 113 | "waitsFor": true, 114 | "xit": false, 115 | "xdescribe": false, 116 | "spyOn": false 117 | } 118 | 119 | } 120 | 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | Thumbs.db 4 | *.log 5 | *.sass-cache 6 | *.orig 7 | *.diff 8 | junit-report.xml 9 | build/ 10 | public 11 | production/ 12 | node_modules/ 13 | reports/ 14 | coverage/ 15 | vendor 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Cathal Mac Donnacha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-material-boilerplate 2 | 3 | A straightforward and well structured boilerplate based on Google's Angular Material project. 4 | 5 | ## Features 6 | * Live reload 7 | * Organised folder structure 8 | * Minified CSS, HTML and JS build files 9 | * Minified images. 10 | * SASS support 11 | * Responsive layout 12 | * Mobile ready 13 | * [Material Design Icons](https://material.io/icons/) icons 14 | * Support for Unit & E2E Testing 15 | * Unit Test reporting 16 | * [ESLint](http://eslint.org/) code analysis tool. 17 | * [Jasmine](http://jasmine.github.io/2.3/introduction.html) testing framework 18 | * [Karma](http://karma-runner.github.io/0.13/index.html) test runner 19 | * [Protractor](https://angular.github.io/protractor/#/) end-to-end test framework 20 | 21 | ## Live Demo 22 | [Check out the live demo](http://cmacdonnacha.github.io/angular-material-boilerplate/) 23 | 24 | ## Setup 25 | 1. Install [Git](https://git-scm.com/downloads) and [NodeJS](http://nodejs.org/) 26 | 2. `git clone https://github.com/cmacdonnacha/angular-material-boilerplate.git myApp` 27 | 3. `cd myApp` 28 | 4. `npm install` 29 | 30 | ## Quick Usage 31 | * `npm start` : Creates a development build folder called `public` and serves it on: [`http://localhost:3000/`](http://localhost:3000/) 32 | * `npm start --production` : Creates a production ready folder called `production` and serves it on: [`http://localhost:4000/`](http://localhost:4000/) 33 | * `npm test` : Runs code checks, unit tests and E2E tests. 34 | * `npm run unit` : Runs unit tests only. 35 | * `npm run e2e` : Runs E2E tests only. 36 | * `npm run eslint` : Runs an ESLint code check only. 37 | * `npm run coverage` : Creates and serves the unit test coverage reports on: [`http://localhost:5000/`](http://localhost:5000/) 38 | * `npm run build` : Creates the `public` build folder but doesn't serve it. 39 | * `npm run build --production` : Creates the `production` build folder but doesn't serve it. 40 | 41 | ## Project Structure 42 | This project follows a **"Folders-by-Feature"** structure very similar to [John Papa's Styleguide](https://github.com/johnpapa/angular-styleguide#application-structure). From the screenshot below you can see that there are 6 separate features, a folder for each one. 43 | Each feature is treated as a mini Angular app. This structure allows us developers to easily locate code and identify what each file represents at a glance. 44 | By retaining this structure the project is much more manageable as it grows. 45 | 46 | ![alt text](http://i.imgur.com/9jYKIoi.png "Folders-by-Feature structure") 47 | 48 | * The `app` folder contains the following individual features: 49 | * `about`: Contains the about page related files. 50 | * `layout`: The high level layout container which stitches it all together. 51 | * `shared`: Contains all shared services, directives, styles etc. used across the entire app. 52 | * `sidenav`: Contains the sidenav related files. 53 | * `todo`: Contains the todo page related files. 54 | * `toolbar`: Contains the toolbar related files. 55 | 56 | * The `assets` folder contains all globally used images. 57 | 58 | #### Adding a new feature folder 59 | In the future I hope to automate the ability to create a new feature folder but for now, check out the [wiki](https://github.com/cmacdonnacha/angular-material-boilerplate/wiki/How-to-add-your-own-feature-folder) to see how you can go about adding one manually. 60 | 61 | ## Troubleshooting 62 | Even crème de menthe projects have their issues. Here are some problems you may face along with some suggestions on how to resolve them: 63 | 64 | #### 1. Issue: I'm getting the following error: ***"Error: EPERM or operation not permitted or permission denied"*** 65 | This can happen when trying to delete a folder that's already in use. For example when running `npm test` while the `npm start` task is already running. 66 | 67 | **Suggestion:** 68 | 1. Stop any tasks that are already running and try again. 69 | 70 |
71 | 72 | #### 2. Issue: I'm getting the following error when running the `npm test` task: ***"No selenium server jar found at the specified location"*** 73 | 74 | **Suggestion:** 75 | 1. Run the following command and try again: `npm run webdriver-update` 76 | 77 |
78 | 79 | #### 3. Issue: I'm getting the following error while running the `npm start` task: ***"Error: ENOENT: no such file or directory, scandir 'C:....node_modeules\node-sass\vendor"*** 80 | This can happen if you have changed your environment since first installing node-sass or if you are running an old version of node-sass. 81 | 82 | **Suggestion:** 83 | 84 | 1. Run this command: `npm rebuild node-sass` 85 | 86 |
87 | 88 | #### 4. Issue: None of the above have helped 89 | **Suggestion:** 90 | 91 | This project has been tested with the following tools: 92 | * **NodeJs:** 6.9.2 93 | * **Npm:** 3.10.9 94 | 95 | If you are running into issues while installing node packages then ensure you have the versions above installed. 96 | 97 |
98 | 99 | ## Contribute 100 | Believe it or not, **angular-material-boilerplate** is not perfect. If you want to improve it somehow then by all means go ahead and create a pull request :-) 101 | 102 | ## TODO 103 | - Add source maps 104 | - Automate ability to add new feature folder 105 | - Switch to Material SVG icons 106 | -------------------------------------------------------------------------------- /docs/assets/css/angular-material-boilerplate.css: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-material-boilerplate v1.0.0 3 | * Copyright (c) 2017 Cathal Mac Donnacha 4 | * Licensed under MIT 5 | */ 6 | body{background-color:#d3d3d3}.todo-page md-checkbox{max-width:20px}.sidenav md-sidenav{max-width:250px}.sidenav .profile-details{margin:10px 0 10px 0}.sidenav .profile-img{width:40%;height:40%;border-radius:50%}@media (min-width:600px){.sidenav .sidenav-profile{margin-top:10px}}.sidenav .menu-items .selected{color:#03a9f4}.img-circle{width:50%;height:50%;border-radius:50%} -------------------------------------------------------------------------------- /docs/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmacdonnacha/angular-material-boilerplate/3d769c517df271389b5e4362c935ab73e03f9331/docs/assets/images/favicon.ico -------------------------------------------------------------------------------- /docs/assets/images/user-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmacdonnacha/angular-material-boilerplate/3d769c517df271389b5e4362c935ab73e03f9331/docs/assets/images/user-profile.png -------------------------------------------------------------------------------- /docs/assets/scripts/angular-material-boilerplate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-material-boilerplate v1.0.0 3 | * Copyright (c) 2017 Cathal Mac Donnacha 4 | * Licensed under MIT 5 | */ 6 | angular.module("angular-material-boilerplate",["ngAnimate","ngAria","ngMaterial","ui.router","ngResource","templates","app.shared","app.layout","app.toolbar","app.sidenav","app.todo","app.about"]).config(["$mdThemingProvider",function(t){t.theme("default").primaryPalette("indigo").accentPalette("purple",{default:"200"})}]).run(["$state",function(t){t.go("todo")}]),angular.module("about.controllers.AboutCtrl",[]).controller("AboutCtrl",function(){var t=this;t.message="And here is where I tell you all about myself."}),angular.module("app.about",["ui.router","about.controllers.AboutCtrl"]).config(["$stateProvider",function(t){t.state("about",{url:"/about",templateUrl:"about/about.view.html"})}]),angular.module("app.shared",["shared.directives.backButton","shared.services.Utils"]),angular.module("layout.controllers.LayoutCtrl",[]).controller("LayoutCtrl",function(){}),angular.module("app.layout",["ui.router","layout.controllers.LayoutCtrl"]).config(["$stateProvider",function(t){t.state("layout",{url:"/layout",templateUrl:"layout/layout.view.html"})}]),angular.module("todo.controllers.ToDoCtrl",[]).controller("ToDoCtrl",["ToDo",function(t){var e=this;e.toDoItems=t.getItems}]),angular.module("app.todo",["ui.router","todo.controllers.ToDoCtrl","todo.services.ToDo"]).config(["$stateProvider",function(t){t.state("todo",{url:"/todo",templateUrl:"todo/todo.view.html"})}]),angular.module("todo.services.ToDo",[]).factory("ToDo",function(){var t=[{title:"Make a todo list",completed:!0},{title:"Check off first item on todo list",completed:!0},{title:"Realise you've have already accomplished two things on the list",completed:!0},{title:"Reward yourself with a nice long nap",completed:!1}],e={getItems:t};return e}),angular.module("sidenav.controllers.SideNavCtrl",[]).controller("SideNavCtrl",["$mdSidenav","$state","SideNav",function(t,e,o){function l(t){a.selected=t,r(),e.go(t.route)}function r(){t("left").close()}var a=this;a.selectMenuItem=l,a.closeSideNav=r,a.menuItems=o.getMenuItems,a.selected=a.menuItems[0]}]),angular.module("app.sidenav",["sidenav.controllers.SideNavCtrl","sidenav.services.SideNav"]),angular.module("sidenav.services.SideNav",[]).factory("SideNav",function(){var t=[{name:"To-Do",icon:"",route:"todo"},{name:"About",icon:"",route:"about"}],e={getMenuItems:t};return e}),angular.module("toolbar.controllers.ToolbarCtrl",[]).controller("ToolbarCtrl",["$state","$scope","$mdSidenav",function(t,e,o){e.openSideNav=function(){o("left").open()}}]),angular.module("app.toolbar",["toolbar.controllers.ToolbarCtrl"]),angular.module("shared.services.Utils",[]).factory("Utils",function(){function t(t){return!(null!==t&&!angular.isUndefined(t))}function e(t){return!!(angular.isUndefined(t)||t.trim().length<=0)}function o(t){return null===t||!!(angular.isUndefined(t)||t.trim().length<=0)}function l(t){return null===t||t.trim().length<=0}var r={isNullOrUndefined:t,isUndefinedOrWhitespace:e,isNullOrWhitespace:l,isNullOrUndefinedOrWhitespace:o};return r}),angular.module("shared.directives.backButton",[]).directive("backButton",["$window",function(t){return{restrict:"A",link:function(e,o){o.bind("click",function(){t.history.back()})}}}]); -------------------------------------------------------------------------------- /docs/assets/scripts/templates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-material-boilerplate v1.0.0 3 | * Copyright (c) 2017 Cathal Mac Donnacha 4 | * Licensed under MIT 5 | */ 6 | angular.module('templates', []).run(['$templateCache', function($templateCache) {$templateCache.put('about/about.view.html','
\n

About

\n

{{ about.message }}

\n
\n\n'); 7 | $templateCache.put('layout/layout.view.html','\n
\n\n\n
\n\n \n
\n\n \n
\n
\n\n'); 8 | $templateCache.put('todo/todo.view.html','
\n

To-Do

\n \n \n \n {{ item.title }}\n \n \n
\n'); 9 | $templateCache.put('sidenav/sidenav.view.html','
\n \n\n \n \n
\n \n \n close\n \n
\n\n \n
\n \n
\n Joe Bloggs\n Director\n
\n
\n
\n\n \n \n \n

{{ menuItem.name }}

\n
\n
\n\n
\n
\n'); 10 | $templateCache.put('toolbar/toolbar.view.html','\n \n menu\n \n

Angular Material Boilerplate

\n
\n\n');}]); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular Material Boilerplate 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gulp_config.js: -------------------------------------------------------------------------------- 1 | var pkg = require('./package.json'); 2 | var isProduction = 'production' === process.env.NODE_ENV; 3 | 4 | module.exports = { 5 | scripts: { 6 | sources: [ 7 | 'src/app/**/*.js', 8 | '!src/app/**/*.spec.js', 9 | '!src/app/**/*.e2e.js' 10 | ], 11 | destinationFolder: isProduction ? 'production/assets/scripts' : 'public/assets/scripts', 12 | destinationName: pkg.name + '.js' 13 | }, 14 | sass: { 15 | sources : ['src/app/**/*.scss'], 16 | destinationFolder: isProduction ? 'production/assets/css' : 'public/assets/css', 17 | destinationName: pkg.name + '.css' 18 | }, 19 | browserify: { 20 | sources : ['./vendor_files.js'], 21 | destinationFolder: isProduction ? 'production/assets/scripts' : 'public/assets/scripts', 22 | destinationName: 'bundle.js' 23 | }, 24 | templates: { 25 | sources : ['src/app/**/*.html'], 26 | destinationFolder: isProduction ? 'production/assets/scripts' : 'public/assets/scripts', 27 | }, 28 | images: { 29 | sources : ['src/assets/images/*'], 30 | destinationFolder : isProduction ? 'production/assets/images' : 'public/assets/images' 31 | }, 32 | vendor: { 33 | sources : ['node_modules/angular-material/angular-material.min.css'], 34 | destinationFolder: isProduction ? 'production/assets/css' : 'public/assets/css', 35 | }, 36 | assets: { 37 | sources : ['src/assets/images/favicon.ico'], 38 | destinationFolder : isProduction ? 'production/assets/images' : 'public/assets/images' 39 | }, 40 | index: { 41 | sources : ['src/index.html'], 42 | destinationFolder : isProduction ? 'production' : 'public' 43 | }, 44 | serve: { 45 | baseDir : isProduction ? 'production' : 'public', 46 | port : isProduction ? 4000 : 3000 47 | }, 48 | coverage: { 49 | destinationFolder: 'reports/coverage/lcov-report', 50 | port: 5000 51 | }, 52 | protractor: { 53 | sources : ['src/app/**/*.e2e.js'] 54 | }, 55 | banner: ['/**', 56 | ' * <%= pkg.name %> v<%= pkg.version %>', 57 | ' * Copyright (c) <%= new Date().getFullYear() %> <%= pkg.author %>', 58 | ' * Licensed under <%= pkg.license %>', 59 | ' */', 60 | ''].join('\n') 61 | , 62 | isProduction : isProduction 63 | }; 64 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | browserify = require('browserify'), 3 | source = require('vinyl-source-stream'), 4 | buffer = require('vinyl-buffer'), 5 | browserSync = require('browser-sync'), 6 | del = require('del'), 7 | karma = require('karma').Server; 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | var config = require('./gulp_config.js'); 11 | var pkg = require('./package.json'); 12 | 13 | /** 14 | * Default tasks 15 | */ 16 | gulp.task('default', ['serve']); 17 | gulp.task('build', ['clean', 'scripts', 'sass', 'templates', 'minify:images', 'browserify', 'copy:vendor:css', 'copy:index', 'copy:assets']); 18 | gulp.task('test', ['eslint', 'unit', 'e2e']); 19 | 20 | /** 21 | * Tasks needed to update/run protractor. 22 | */ 23 | gulp.task('webdriver-update', $.protractor.webdriver_update); 24 | gulp.task('webdriver-start', $.protractor.webdriver_start); 25 | 26 | 27 | /** 28 | * Watches for file changes. Reloads the browser if a change is detected. 29 | */ 30 | gulp.task('watch', function() { 31 | gulp.watch('src/**/*.scss', ['sass']); 32 | gulp.watch('src/**/*.html', ['templates', browserSync.reload]); 33 | gulp.watch('src/**/*.js', ['scripts', 'unit', 'eslint', browserSync.reload]); 34 | gulp.watch('src/index.html', ['copy:index', browserSync.reload]); 35 | }); 36 | 37 | /** 38 | * Serves and watches the build folder. 39 | */ 40 | gulp.task('serve', ['build', 'watch'], function() { 41 | browserSync({ 42 | server: { 43 | baseDir: config.serve.baseDir // Serve the chosen folder. 44 | }, 45 | open: false, // Stop the browser from opening a new window automatically. 46 | port: config.serve.port // Decide which port to serve on. Default is 3000 for development and 4000 for production. 47 | }) 48 | }); 49 | 50 | /** 51 | * Builds, minifies and concatenates javascript files into one single file. 52 | */ 53 | gulp.task('scripts', function() { 54 | return gulp.src(config.scripts.sources) // Get all js files 55 | .pipe($.concat(config.scripts.destinationName)) // Concatenate them into one file 56 | .pipe($.ngAnnotate()) // Inject angular dependencies and prevent minfier from mangling them. 57 | .pipe(config.isProduction ? $.uglify() : $.util.noop()) // Minify the file if it's for production 58 | .pipe($.header(config.banner, { pkg : pkg } )) // Add a comment to the top of the file to include our package info (copyright etc.) 59 | .pipe(gulp.dest(config.scripts.destinationFolder)); // Output it to the destination folder 60 | }); 61 | 62 | /** 63 | * Builds, minifies and concatenates sass files into one single file. 64 | */ 65 | gulp.task('sass', function() { 66 | return gulp.src(config.sass.sources) // Get all sass files 67 | .pipe($.sass()) // Pass them through gulp-sass to convert it to css 68 | .pipe($.concat(config.sass.destinationName)) // Concatenate them into one file 69 | .pipe(config.isProduction ? $.cleanCss():$.util.noop()) // Minify the file if this is for production 70 | .pipe($.header(config.banner, { pkg : pkg } )) // Add a comment to the top of the file to include our package info (copyright etc.) 71 | .pipe(gulp.dest(config.sass.destinationFolder)) // Output it to the destination folder 72 | .pipe(browserSync.reload({ // Reload browser sync 73 | stream: true 74 | })); 75 | }); 76 | 77 | /** 78 | * Concatenates and registers AngularJS templates in the $templateCache. 79 | */ 80 | gulp.task('templates', function () { 81 | return gulp.src(config.templates.sources) // Get all html files 82 | .pipe($.angularTemplatecache({ // Pass them through angularTemplatecache 83 | standalone: true // Create a new AngularJS module dynamically so we don't have to 84 | })) 85 | .pipe($.header(config.banner, { pkg : pkg } )) // Add a comment to the top of the file to include our package info (copyright etc.) 86 | .pipe(gulp.dest(config.templates.destinationFolder)); // Output it to the destination folder. Default file name is 'templates.js'. 87 | }); 88 | 89 | /** 90 | * Minifies supported images found in the assets folder. 91 | */ 92 | gulp.task('minify:images', function() { 93 | return gulp.src(config.images.sources) // Get all images 94 | .pipe(config.isProduction ? $.imagemin():$.util.noop()) // Pass them imageMin in order to reduce their file sizes 95 | .pipe(gulp.dest(config.images.destinationFolder)); // Output them to the destination folder. 96 | }); 97 | 98 | /** 99 | * Performs an eslint code check analysis. 100 | */ 101 | gulp.task('eslint', function () { 102 | return gulp.src(config.scripts.sources) // Get all js files 103 | .pipe($.eslint()) // Run them through eslint to try and find error in the code 104 | .pipe($.eslint.format()) // Output the results to the console. 105 | .pipe($.eslint.failAfterError()); // Stop a stream if an ESLint error has been reported, but wait for all files to be processed first. 106 | }); 107 | 108 | /** 109 | * Runs unit tests using Karma 110 | */ 111 | gulp.task('unit', function(done) { 112 | karma.start({ // Start the Karma server 113 | configFile: __dirname + '/karma.conf.js' // Load the karma config file 114 | }, function() { 115 | done(); 116 | }); 117 | }); 118 | 119 | /** 120 | * Bundles node dependencies (Angular, ui-router, Angular Material etc.) into one single file. 121 | */ 122 | gulp.task('browserify', function() { 123 | return browserify(config.browserify.sources) // Get all node module files 124 | .bundle() // Bundle them all into one file 125 | .pipe(source(config.browserify.destinationName)) // Pass is through vinyl-source-stream to rename it 126 | .pipe(config.isProduction ? buffer() : $.util.noop()) // Pass is through vinyl-buffer so that 'uglify' can use it 127 | .pipe(config.isProduction ? $.uglify() : $.util.noop()) // Minify the file if it's for production 128 | .pipe(gulp.dest(config.browserify.destinationFolder)); // Output it to the destination folder 129 | }); 130 | 131 | /** 132 | * Copies the index.html file into the build folder 133 | */ 134 | gulp.task('copy:index', function() { 135 | return gulp.src(config.index.sources) // Get index.html file 136 | .pipe(gulp.dest(config.index.destinationFolder)); // Copy it to the destination folder 137 | }); 138 | 139 | /** 140 | * Copies the contents of assets folder into the build folder. 141 | */ 142 | gulp.task('copy:assets', function() { 143 | return gulp.src(config.assets.sources) // Get assets (e.g favicon, logo, shared images etc.) 144 | .pipe(gulp.dest(config.assets.destinationFolder)); // Copy them to the destination folder (e.g assets) 145 | }); 146 | 147 | /** 148 | * Copies the vendor css files (i.e angular-material.css) into the build folder 149 | */ 150 | gulp.task('copy:vendor:css', function() { 151 | return gulp.src(config.vendor.sources) // Get all vendor css files (e.g angular-material.css) 152 | .pipe(gulp.dest(config.vendor.destinationFolder)); // Copy it to the destination folder 153 | }); 154 | 155 | /** 156 | * Deletes the build folder. 157 | */ 158 | gulp.task('clean', function() { 159 | return del.sync([ 160 | config.serve.baseDir, // Delete the destination folder (may be 'public' or 'production') 161 | config.coverage.destinationFolder // Delete the code coverage 'reports' folder 162 | ]); 163 | }); 164 | 165 | /** 166 | * Runs unit test code coverage. 167 | */ 168 | gulp.task('coverage', ['unit'], function() { 169 | browserSync({ 170 | server: { 171 | baseDir: config.coverage.destinationFolder // Serve code coverage reports folder 172 | }, 173 | port: config.coverage.port, // On the selected port. 174 | open: false // Stop the browser from opening a new window automatically. 175 | }) 176 | }); 177 | 178 | /** 179 | * Runs end-to-end tests using protractor. 180 | */ 181 | gulp.task('e2e', ['serve:e2e'], function(done) { 182 | gulp.src(config.protractor.sources) // Get all e2e tests 183 | .pipe($.protractor.protractor({ 184 | configFile: 'protractor.conf.js' // Load the protractor config file 185 | })) 186 | .on('error', function (err) { // Stop the task if an error occurs 187 | // Stop the task 188 | process.exit(0); 189 | done(); 190 | }) 191 | .on('end', function () { // Stop the task if complete 192 | // Stop the task 193 | process.exit(0); 194 | done(); 195 | }); 196 | }); 197 | 198 | /** 199 | * Serves the app using browserSync so that end-to-end tests can run. 200 | */ 201 | gulp.task('serve:e2e', ['build', 'webdriver-start'], function() { 202 | browserSync({ 203 | server: { 204 | baseDir: config.serve.baseDir // Serve the chosen folder. Default port is 3000. 205 | }, 206 | open: false // Stop the browser from opening a new window automatically. 207 | }) 208 | }); 209 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | 4 | // Base path that will be used to resolve all patterns (eg. files, exclude) 5 | basePath: '', 6 | 7 | 8 | // Frameworks to use 9 | frameworks: ['jasmine'], 10 | 11 | 12 | // Plugins to use 13 | plugins : ['karma-jasmine', 'karma-phantomjs-launcher', 'karma-chrome-launcher', 'karma-coverage'], 14 | 15 | 16 | // List of files / patterns to load into the browser during testing. 17 | files: [ 18 | 'node_modules/angular/angular.js', 19 | 'node_modules/angular-mocks/angular-mocks.js', 20 | 'node_modules/angular-ui-router/release/angular-ui-router.js', 21 | 'src/app/**/*.js', 22 | 'src/app/**/*.spec.js' 23 | ], 24 | 25 | 26 | // List of files to exclude 27 | exclude: [ 28 | 'src/app/**/*.e2e.js' 29 | ], 30 | 31 | 32 | // Preprocessors matching files before serving them to the browser. 33 | // Available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | // Source files that you want to generate coverage for. 36 | // Do not include tests or libraries (these files will be instrumented by Istanbul) 37 | 'src/**/*.js': ['coverage'] 38 | }, 39 | 40 | 41 | // Configure the reporter 42 | coverageReporter: { 43 | type : 'lcov', 44 | dir : 'reports', 45 | subdir: 'coverage' 46 | }, 47 | 48 | 49 | // Test results reporter to use. 50 | // Possible values: 'dots', 'progress', 'coverage. 51 | // Available reporters: https://npmjs.org/browse/keyword/karma-reporter 52 | reporters: ['progress', 'coverage'], 53 | 54 | 55 | // Enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // Level of logging 60 | // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 61 | logLevel: config.LOG_DISABLE, 62 | 63 | 64 | // Enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: true, 66 | 67 | 68 | // The list of browsers to launch to test on: 69 | // Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 70 | browsers: ['PhantomJS'], 71 | 72 | // Continuous Integration mode 73 | // if true, Karma captures browsers, runs the tests and exits 74 | singleRun: true 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-boilerplate", 3 | "version": "1.0.0", 4 | "author": "Cathal Mac Donnacha", 5 | "description": "A straightforward and well structured boilerplate based on Google's Angular Material project.", 6 | "license": "MIT", 7 | "homepage": "https://github.com/cmacdonnacha/angular-material-boilerplate", 8 | "scripts": { 9 | "start": "gulp", 10 | "test": "gulp test", 11 | "unit": "gulp unit", 12 | "e2e": "gulp e2e", 13 | "eslint": "gulp eslint", 14 | "coverage": "gulp coverage", 15 | "build": "gulp build", 16 | "postinstall": "gulp webdriver-update" 17 | }, 18 | "dependencies": { 19 | "angular": "~1.8.0", 20 | "angular-animate": "~1.5.0", 21 | "angular-aria": "~1.5.0", 22 | "angular-local-storage": "^0.5.2", 23 | "angular-material": "^1.1.3", 24 | "angular-resource": "^1.6.2", 25 | "angular-ui-router": "^0.4.2", 26 | "material-design-icons": "^3.0.1", 27 | "underscore": "^1.8.3" 28 | }, 29 | "devDependencies": { 30 | "angular-mocks": "^1.6.2", 31 | "async": "^2.1.2", 32 | "browser-sync": "^2.18.8", 33 | "browserify": "^14.1.0", 34 | "del": "^2.2.2", 35 | "eslint-plugin-angular": "^1.5.0", 36 | "gulp": "^3.9.1", 37 | "gulp-angular-templatecache": "^2.0.0", 38 | "gulp-clean-css": "^3.0.2", 39 | "gulp-concat": "^2.6.1", 40 | "gulp-eslint": "^3.0.1", 41 | "gulp-header": "^1.8.8", 42 | "gulp-imagemin": "^3.1.1", 43 | "gulp-jshint": "^2.0.4", 44 | "gulp-load-plugins": "^1.5.0", 45 | "gulp-ng-annotate": "^2.0.0", 46 | "gulp-protractor": "^3.0.0", 47 | "gulp-rename": "^1.2.2", 48 | "gulp-sass": "^3.1.0", 49 | "gulp-sourcemaps": "^2.4.1", 50 | "gulp-uglify": "^2.0.0", 51 | "gulp-util": "^3.0.7", 52 | "jasmine-core": "^2.5.2", 53 | "jshint": "^2.9.4", 54 | "jshint-stylish": "^2.2.1", 55 | "karma": "^1.4.1", 56 | "karma-chrome-launcher": "^2.0.0", 57 | "karma-coverage": "^1.1.1", 58 | "karma-jasmine": "^1.1.0", 59 | "karma-phantomjs-launcher": "^1.0.2", 60 | "vinyl-buffer": "^1.0.0", 61 | "vinyl-source-stream": "^1.1.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('./gulp_config.js'); 2 | 3 | exports.config = { 4 | // The address of a running selenium server. Comment this out if you want the selenium server tp start automatically. 5 | //'seleniumAddress': 'http://localhost:4444/wd/hub', 6 | 7 | // The base url your app is served on. 8 | baseUrl: 'http://localhost:3000/', 9 | 10 | // Jasmine is fully supported as a test and assertion framework. 11 | framework: 'jasmine', 12 | 13 | // Capabilities to be passed to the webdriver instance. 14 | 'capabilities': { 15 | 'browserName': 'chrome' 16 | }, 17 | 18 | // Spec patterns are relative to the location of this config. 19 | specs: [ 20 | config.protractor.sources 21 | ], 22 | 23 | stackTrace: false, 24 | 25 | // Options to be passed to minijasminenode. 26 | // See the full list at https://github.com/juliemr/minijasminenode/tree/jasmine1 27 | jasmineNodeOpts: { 28 | // If true, display spec names. 29 | isVerbose: false, 30 | // If true, print colors to the terminal. 31 | showColors: true, 32 | // If true, include stack traces in failures. 33 | includeStackTrace: false, 34 | // Default time to wait in ms before a test fails. 35 | defaultTimeoutInterval: 30000, 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /protractor.config.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | // commenting this line auto-starts the selenium server 3 | //seleniumAddress: 'http://localhost:4444/wd/hub', 4 | 5 | // A base URL for your application under test. Calls to protractor.get() 6 | // with relative paths will be prepended with this. 7 | baseUrl: 'http://localhost:9001/', 8 | 9 | // Jasmine is fully supported as a test and assertion framework. 10 | framework: 'jasmine', 11 | 12 | // Options to be passed to minijasminenode. 13 | // See the full list at https://github.com/juliemr/minijasminenode/tree/jasmine1 14 | jasmineNodeOpts: { 15 | // If true, display spec names. 16 | isVerbose: true, 17 | // If true, print colors to the terminal. 18 | showColors: true, 19 | // If true, include stack traces in failures. 20 | includeStackTrace: false, 21 | // Default time to wait in ms before a test fails. 22 | defaultTimeoutInterval: 30000 23 | }, 24 | 25 | // Spec patterns are relative to the location of this config. 26 | specs: [ 27 | 'src/app/**/*.e2e.js' 28 | ] 29 | }; 30 | -------------------------------------------------------------------------------- /src/app/about/about.contoller.spec.js: -------------------------------------------------------------------------------- 1 | describe('AboutCtrl ', function () { 2 | 3 | var controller; 4 | 5 | beforeEach(module('app.about')); 6 | beforeEach(module('ui.router')); 7 | 8 | beforeEach(inject(function ($controller) { 9 | controller = $controller('AboutCtrl', {}); 10 | })); 11 | 12 | it('should set the correct about page message', function () { 13 | expect(controller.message).toBe('And here is where I tell you all about myself.'); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/about/about.controller.e2e.js: -------------------------------------------------------------------------------- 1 | describe('test about page', function () { 2 | 3 | beforeEach(function() { 4 | browser.get('#/about'); 5 | }); 6 | 7 | it('should ensure the page title is correct', function () { 8 | expect(browser.getTitle()).toEqual('Angular Material Boilerplate'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/app/about/about.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('about.controllers.AboutCtrl', []) 2 | .controller('AboutCtrl', function() { 3 | var vm = this; 4 | vm.message = 'And here is where I tell you all about myself.'; 5 | }); 6 | -------------------------------------------------------------------------------- /src/app/about/about.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.about', [ 2 | 'ui.router', 3 | 'about.controllers.AboutCtrl' 4 | ]) 5 | 6 | .config(function config($stateProvider) { 7 | $stateProvider.state('about', { 8 | url: '/about', 9 | templateUrl: 'about/about.view.html' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/about/about.style.scss: -------------------------------------------------------------------------------- 1 | .about-page { 2 | // Add all about page related styles in here 3 | } 4 | -------------------------------------------------------------------------------- /src/app/about/about.view.html: -------------------------------------------------------------------------------- 1 |
2 |

About

3 |

{{ about.message }}

4 |
5 | 6 | -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('angular-material-boilerplate', [ 2 | /** 3 | * Dependencies must be injected in specific order: 4 | * 1) AngularJS dependencies 5 | * 2) Compiled HTML templates 6 | * 3) Common Services, Directives, Filters and Utilities 7 | * 4) Main App Layout 8 | * 5) Other App components (e.g. Toolbar, About, etc) 9 | */ 10 | 11 | // AngularJS dependencies 12 | 'ngAnimate', 13 | 'ngAria', 14 | 'ngMaterial', 15 | 'ui.router', 16 | 'ngResource', 17 | 18 | // Include compiled HTML templates 19 | 'templates', 20 | 21 | // Common/shared code 22 | 'app.shared', 23 | 24 | // Layout 25 | 'app.layout', 26 | 27 | // Components 28 | 'app.toolbar', 29 | 'app.sidenav', 30 | 'app.todo', 31 | 'app.about' 32 | ]) 33 | 34 | .config(function($mdThemingProvider) { 35 | $mdThemingProvider.theme('default') 36 | .primaryPalette('indigo') 37 | .accentPalette('purple', { 38 | 'default': '200' 39 | }); 40 | }) 41 | 42 | .run(['$state', function ($state) { 43 | $state.go('todo'); 44 | }]); 45 | -------------------------------------------------------------------------------- /src/app/layout/layout.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('layout.controllers.LayoutCtrl', []) 2 | .controller('LayoutCtrl', function() { 3 | 4 | }); 5 | -------------------------------------------------------------------------------- /src/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.layout', [ 2 | 'ui.router', 3 | 'layout.controllers.LayoutCtrl' 4 | ]) 5 | 6 | .config(function config($stateProvider) { 7 | $stateProvider.state('layout', { 8 | url: '/layout', 9 | templateUrl: 'layout/layout.view.html' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/layout/layout.style.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightgrey; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/layout/layout.view.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 |
12 |
13 | 14 | -------------------------------------------------------------------------------- /src/app/shared/directives/backButton.js: -------------------------------------------------------------------------------- 1 | angular.module('shared.directives.backButton', []) 2 | .directive('backButton', ['$window', function($window) { 3 | return { 4 | restrict: 'A', 5 | link: function (scope, elem) { 6 | elem.bind('click', function () { 7 | $window.history.back(); 8 | }); 9 | } 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /src/app/shared/services/Utils.js: -------------------------------------------------------------------------------- 1 | angular.module('shared.services.Utils', []) 2 | .factory('Utils', function Utils() { 3 | 4 | var service = { 5 | isNullOrUndefined: isNullOrUndefined, 6 | isUndefinedOrWhitespace: isUndefinedOrWhitespace, 7 | isNullOrWhitespace: isNullOrWhitespace, 8 | isNullOrUndefinedOrWhitespace: isNullorUndefinedOrWhitespace 9 | }; 10 | return service; 11 | 12 | function isNullOrUndefined(object) { 13 | return object === null || angular.isUndefined(object) ? true : false; 14 | } 15 | 16 | function isUndefinedOrWhitespace(stringText) { 17 | return angular.isUndefined(stringText) || stringText.trim().length <= 0 ? true : false; 18 | } 19 | 20 | function isNullorUndefinedOrWhitespace(stringText) { 21 | if(stringText !== null) { 22 | return angular.isUndefined(stringText) || stringText.trim().length <= 0 ? true : false; 23 | } else { 24 | return true; 25 | } 26 | } 27 | 28 | function isNullOrWhitespace(stringText) { 29 | return stringText === null || stringText.trim().length <= 0 ? true : false; 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.shared', [ 2 | 'shared.directives.backButton', 3 | 'shared.services.Utils' 4 | ]); 5 | -------------------------------------------------------------------------------- /src/app/shared/styles/_color-palette.scss: -------------------------------------------------------------------------------- 1 | $white: #ffffff; 2 | $black: #000000; 3 | $red50: #ffebee; 4 | $red100: #ffcdd2; 5 | $red200: #ef9a9a; 6 | $red300: #e57373; 7 | $red400: #ef5350; 8 | $red500: #f44336; 9 | $red600: #e53935; 10 | $red700: #d32f2f; 11 | $red800: #c62828; 12 | $red900: #b71c1c; 13 | $redA100: #ff8a80; 14 | $redA200: #ff5252; 15 | $redA400: #ff1744; 16 | $redA700: #d50000; 17 | $pink50: #fce4ec; 18 | $pink100: #f8bbd0; 19 | $pink200: #f48fb1; 20 | $pink300: #f06292; 21 | $pink400: #ec407a; 22 | $pink500: #e91e63; 23 | $pink600: #d81b60; 24 | $pink700: #c2185b; 25 | $pink800: #ad1457; 26 | $pink900: #880e4f; 27 | $pinkA100: #ff80ab; 28 | $pinkA200: #ff4081; 29 | $pinkA400: #f50057; 30 | $pinkA700: #c51162; 31 | $purple50: #f3e5f5; 32 | $purple100: #e1bee7; 33 | $purple200: #ce93d8; 34 | $purple300: #ba68c8; 35 | $purple400: #ab47bc; 36 | $purple500: #9c27b0; 37 | $purple600: #8e24aa; 38 | $purple700: #7b1fa2; 39 | $purple800: #6a1b9a; 40 | $purple900: #4a148c; 41 | $purpleA100: #ea80fc; 42 | $purpleA200: #e040fb; 43 | $purpleA400: #d500f9; 44 | $purpleA700: #aa00ff; 45 | $deep-purple50: #ede7f6; 46 | $deep-purple100: #d1c4e9; 47 | $deep-purple200: #b39ddb; 48 | $deep-purple300: #9575cd; 49 | $deep-purple400: #7e57c2; 50 | $deep-purple500: #673ab7; 51 | $deep-purple600: #5e35b1; 52 | $deep-purple700: #512da8; 53 | $deep-purple800: #4527a0; 54 | $deep-purple900: #311b92; 55 | $deep-purpleA100: #b388ff; 56 | $deep-purpleA200: #7c4dff; 57 | $deep-purpleA400: #651fff; 58 | $deep-purpleA700: #6200ea; 59 | $indigo50: #e8eaf6; 60 | $indigo100: #c5cae9; 61 | $indigo200: #9fa8da; 62 | $indigo300: #7986cb; 63 | $indigo400: #5c6bc0; 64 | $indigo500: #3f51b5; 65 | $indigo600: #3949ab; 66 | $indigo700: #303f9f; 67 | $indigo800: #283593; 68 | $indigo900: #1a237e; 69 | $indigoA100: #8c9eff; 70 | $indigoA200: #536dfe; 71 | $indigoA400: #3d5afe; 72 | $indigoA700: #304ffe; 73 | $blue50: #e3f2fd; 74 | $blue100: #bbdefb; 75 | $blue200: #90caf9; 76 | $blue300: #64b5f6; 77 | $blue400: #42a5f5; 78 | $blue500: #2196f3; 79 | $blue600: #1e88e5; 80 | $blue700: #1976d2; 81 | $blue800: #1565c0; 82 | $blue900: #0d47a1; 83 | $blueA100: #82b1ff; 84 | $blueA200: #448aff; 85 | $blueA400: #2979ff; 86 | $blueA700: #2962ff; 87 | $light-blue50: #e1f5fe; 88 | $light-blue100: #b3e5fc; 89 | $light-blue200: #81d4fa; 90 | $light-blue300: #4fc3f7; 91 | $light-blue400: #29b6f6; 92 | $light-blue500: #03a9f4; 93 | $light-blue600: #039be5; 94 | $light-blue700: #0288d1; 95 | $light-blue800: #0277bd; 96 | $light-blue900: #01579b; 97 | $light-blueA100: #80d8ff; 98 | $light-blueA200: #40c4ff; 99 | $light-blueA400: #00b0ff; 100 | $light-blueA700: #0091ea; 101 | $cyan50: #e0f7fa; 102 | $cyan100: #b2ebf2; 103 | $cyan200: #80deea; 104 | $cyan300: #4dd0e1; 105 | $cyan400: #26c6da; 106 | $cyan500: #00bcd4; 107 | $cyan600: #00acc1; 108 | $cyan700: #0097a7; 109 | $cyan800: #00838f; 110 | $cyan900: #006064; 111 | $cyanA100: #84ffff; 112 | $cyanA200: #18ffff; 113 | $cyanA400: #00e5ff; 114 | $cyanA700: #00b8d4; 115 | $teal50: #e0f2f1; 116 | $teal100: #b2dfdb; 117 | $teal200: #80cbc4; 118 | $teal300: #4db6ac; 119 | $teal400: #26a69a; 120 | $teal500: #009688; 121 | $teal600: #00897b; 122 | $teal700: #00796b; 123 | $teal800: #00695c; 124 | $teal900: #004d40; 125 | $tealA100: #a7ffeb; 126 | $tealA200: #64ffda; 127 | $tealA400: #1de9b6; 128 | $tealA700: #00bfa5; 129 | $green50: #e8f5e9; 130 | $green100: #c8e6c9; 131 | $green200: #a5d6a7; 132 | $green300: #81c784; 133 | $green400: #66bb6a; 134 | $green500: #4caf50; 135 | $green600: #43a047; 136 | $green700: #388e3c; 137 | $green800: #2e7d32; 138 | $green900: #1b5e20; 139 | $greenA100: #b9f6ca; 140 | $greenA200: #69f0ae; 141 | $greenA400: #00e676; 142 | $greenA700: #00c853; 143 | $light-green50: #f1f8e9; 144 | $light-green100: #dcedc8; 145 | $light-green200: #c5e1a5; 146 | $light-green300: #aed581; 147 | $light-green400: #9ccc65; 148 | $light-green500: #8bc34a; 149 | $light-green600: #7cb342; 150 | $light-green700: #689f38; 151 | $light-green800: #558b2f; 152 | $light-green900: #33691e; 153 | $light-greenA100: #ccff90; 154 | $light-greenA200: #b2ff59; 155 | $light-greenA400: #76ff03; 156 | $light-greenA700: #64dd17; 157 | $lime50: #f9fbe7; 158 | $lime100: #f0f4c3; 159 | $lime200: #e6ee9c; 160 | $lime300: #dce775; 161 | $lime400: #d4e157; 162 | $lime500: #cddc39; 163 | $lime600: #c0ca33; 164 | $lime700: #afb42b; 165 | $lime800: #9e9d24; 166 | $lime900: #827717; 167 | $limeA100: #f4ff81; 168 | $limeA200: #eeff41; 169 | $limeA400: #c6ff00; 170 | $limeA700: #aeea00; 171 | $yellow50: #fffde7; 172 | $yellow100: #fff9c4; 173 | $yellow200: #fff59d; 174 | $yellow300: #fff176; 175 | $yellow400: #ffee58; 176 | $yellow500: #ffeb3b; 177 | $yellow600: #fdd835; 178 | $yellow700: #fbc02d; 179 | $yellow800: #f9a825; 180 | $yellow900: #f57f17; 181 | $yellowA100: #ffff8d; 182 | $yellowA200: #ffff00; 183 | $yellowA400: #ffea00; 184 | $yellowA700: #ffd600; 185 | $amber50: #fff8e1; 186 | $amber100: #ffecb3; 187 | $amber200: #ffe082; 188 | $amber300: #ffd54f; 189 | $amber400: #ffca28; 190 | $amber500: #ffc107; 191 | $amber600: #ffb300; 192 | $amber700: #ffa000; 193 | $amber800: #ff8f00; 194 | $amber900: #ff6f00; 195 | $amberA100: #ffe57f; 196 | $amberA200: #ffd740; 197 | $amberA400: #ffc400; 198 | $amberA700: #ffab00; 199 | $orange50: #fff3e0; 200 | $orange100: #ffe0b2; 201 | $orange200: #ffcc80; 202 | $orange300: #ffb74d; 203 | $orange400: #ffa726; 204 | $orange500: #ff9800; 205 | $orange600: #fb8c00; 206 | $orange700: #f57c00; 207 | $orange800: #ef6c00; 208 | $orange900: #e65100; 209 | $orangeA100: #ffd180; 210 | $orangeA200: #ffab40; 211 | $orangeA400: #ff9100; 212 | $orangeA700: #ff6d00; 213 | $deep-orange50: #fbe9e7; 214 | $deep-orange100: #ffccbc; 215 | $deep-orange200: #ffab91; 216 | $deep-orange300: #ff8a65; 217 | $deep-orange400: #ff7043; 218 | $deep-orange500: #ff5722; 219 | $deep-orange600: #f4511e; 220 | $deep-orange700: #e64a19; 221 | $deep-orange800: #d84315; 222 | $deep-orange900: #bf360c; 223 | $deep-orangeA100: #ff9e80; 224 | $deep-orangeA200: #ff6e40; 225 | $deep-orangeA400: #ff3d00; 226 | $deep-orangeA700: #dd2c00; 227 | $brown50: #efebe9; 228 | $brown100: #d7ccc8; 229 | $brown200: #bcaaa4; 230 | $brown300: #a1887f; 231 | $brown400: #8d6e63; 232 | $brown500: #795548; 233 | $brown600: #6d4c41; 234 | $brown700: #5d4037; 235 | $brown800: #4e342e; 236 | $brown900: #3e2723; 237 | $brownA100: #d7ccc8; 238 | $brownA200: #bcaaa4; 239 | $brownA400: #8d6e63; 240 | $brownA700: #5d4037; 241 | $grey50: #fafafa; 242 | $grey100: #f5f5f5; 243 | $grey200: #eeeeee; 244 | $grey300: #e0e0e0; 245 | $grey400: #bdbdbd; 246 | $grey500: #9e9e9e; 247 | $grey600: #757575; 248 | $grey700: #616161; 249 | $grey800: #424242; 250 | $grey900: #212121; 251 | $grey1000: #000000; 252 | $greyA100: #ffffff; 253 | $greyA200: #eeeeee; 254 | $greyA400: #bdbdbd; 255 | $greyA700: #616161; 256 | $blue-grey50: #eceff1; 257 | $blue-grey100: #cfd8dc; 258 | $blue-grey200: #b0bec5; 259 | $blue-grey300: #90a4ae; 260 | $blue-grey400: #78909c; 261 | $blue-grey500: #607d8b; 262 | $blue-grey600: #546e7a; 263 | $blue-grey700: #455a64; 264 | $blue-grey800: #37474f; 265 | $blue-grey900: #263238; 266 | $blue-greyA100: #cfd8dc; 267 | $blue-greyA200: #b0bec5; 268 | $blue-greyA400: #78909c; 269 | $blue-greyA700: #455a64; 270 | -------------------------------------------------------------------------------- /src/app/shared/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // ** This is where you can put your very own put Sass Mixins that can be used globally. ** 2 | 3 | // Mixin: circle ($width:50%, $height:50%) 4 | // Description: Used to make a circular element. (e.g Change a square to a circle) 5 | // @params $width: [ number ]: Specify the width of the element. Set to 50% if an argument is not passed. 6 | // @params $height: [ number ]: Specify the height of the element. Set to 50% if an argument is not passed. 7 | @mixin circle ($width:50%, $height:50%) { 8 | width: $width; 9 | height: $height; 10 | border-radius: 50%; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/styles/overrides.scss: -------------------------------------------------------------------------------- 1 | // ** This is where you can override the default Angular Material styles ** 2 | -------------------------------------------------------------------------------- /src/app/shared/styles/shared.scss: -------------------------------------------------------------------------------- 1 | // ** This is where you define styles that are used globally across the entire app ** // 2 | @import "mixins"; 3 | 4 | // Class: img-circle 5 | // Description: Used to make a circular element. (e.g Change a square to a circle) 6 | .img-circle { 7 | @include circle; 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('sidenav.controllers.SideNavCtrl', []) 2 | .controller('SideNavCtrl', function($mdSidenav, $state, SideNav) { 3 | var vm = this; 4 | vm.selectMenuItem = selectMenuItem; 5 | vm.closeSideNav = closeSideNav; 6 | vm.menuItems = SideNav.getMenuItems; 7 | vm.selected = vm.menuItems[0]; 8 | 9 | function selectMenuItem(menuItem) { 10 | vm.selected = menuItem; 11 | closeSideNav(); 12 | $state.go(menuItem.route); 13 | } 14 | 15 | function closeSideNav() { 16 | $mdSidenav('left').close(); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.sidenav', [ 2 | 'sidenav.controllers.SideNavCtrl', 3 | 'sidenav.services.SideNav' 4 | ]); 5 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.service.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('sidenav.services.SideNav', []) 3 | .factory('SideNav', function() { 4 | 5 | var menuItems = [ 6 | { 7 | name: 'To-Do', 8 | icon: '', 9 | route: 'todo' 10 | }, 11 | { 12 | name: 'About', 13 | icon: '', 14 | route: 'about' 15 | } 16 | ]; 17 | 18 | var service = { 19 | getMenuItems: menuItems 20 | }; 21 | return service; 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.style.scss: -------------------------------------------------------------------------------- 1 | @import "../shared/styles/_mixins"; 2 | 3 | .sidenav { 4 | md-sidenav { 5 | max-width: 250px; 6 | } 7 | 8 | .profile-details { 9 | margin: 10px 0 10px 0; 10 | } 11 | 12 | .profile-img { 13 | @include circle(40%, 40%); 14 | } 15 | 16 | // Add a margin above the profile when the width is greater than 600px 17 | @media (min-width: 600px) { 18 | .sidenav-profile { 19 | margin-top: 10px; 20 | } 21 | } 22 | 23 | .menu-items .selected { 24 | color: #03a9f4; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | close 10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 | Joe Bloggs 18 | Director 19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 |

{{ menuItem.name }}

27 |
28 |
29 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /src/app/todo/todo.controller.e2e.js: -------------------------------------------------------------------------------- 1 | describe('test todo page', function () { 2 | 3 | beforeEach(function() { 4 | browser.get('#/todo'); 5 | }); 6 | 7 | it('should ensure the page title is correct', function () { 8 | expect(browser.getTitle()).toEqual('Angular Material Boilerplate'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/app/todo/todo.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('todo.controllers.ToDoCtrl', []) 2 | .controller('ToDoCtrl', function(ToDo) { 3 | var vm = this; 4 | vm.toDoItems = ToDo.getItems; 5 | }); 6 | -------------------------------------------------------------------------------- /src/app/todo/todo.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('ToDoCtrl ', function () { 2 | 3 | var controller; 4 | 5 | beforeEach(module('app.todo')); 6 | beforeEach(module('ui.router')); 7 | 8 | beforeEach(inject(function ($controller) { 9 | controller = $controller('ToDoCtrl', {}); 10 | })); 11 | 12 | it('should set the correct about page message', function () { 13 | expect(controller.toDoItems.length).toBeGreaterThan(0); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/todo/todo.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.todo', [ 2 | 'ui.router', 3 | 'todo.controllers.ToDoCtrl', 4 | 'todo.services.ToDo' 5 | ]) 6 | 7 | .config(function config($stateProvider) { 8 | $stateProvider.state('todo', { 9 | url: '/todo', 10 | templateUrl: 'todo/todo.view.html' 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/todo/todo.service.js: -------------------------------------------------------------------------------- 1 | angular.module('todo.services.ToDo', []) 2 | .factory('ToDo', function() { 3 | 4 | var toDoItems = [ 5 | { 6 | title: 'Make a todo list', 7 | completed: true 8 | }, 9 | { 10 | title: 'Check off first item on todo list', 11 | completed: true 12 | }, 13 | { 14 | title: 'Realise you\'ve have already accomplished two things on the list', 15 | completed: true 16 | }, 17 | { 18 | title: 'Reward yourself with a nice long nap', 19 | completed: false 20 | } 21 | ]; 22 | 23 | var service = { 24 | getItems: toDoItems 25 | }; 26 | return service; 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/todo/todo.style.scss: -------------------------------------------------------------------------------- 1 | .todo-page { 2 | 3 | // Fixes issue with to-do items not aligned correctly on small screens 4 | md-checkbox { 5 | max-width: 20px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/todo/todo.view.html: -------------------------------------------------------------------------------- 1 |
2 |

To-Do

3 | 4 | 5 | 6 | {{ item.title }} 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/app/toolbar/toolbar.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('toolbar.controllers.ToolbarCtrl', []) 2 | .controller('ToolbarCtrl', function($state, $scope, $mdSidenav) { 3 | $scope.openSideNav = function () { 4 | $mdSidenav('left').open(); 5 | }; 6 | }); 7 | -------------------------------------------------------------------------------- /src/app/toolbar/toolbar.module.js: -------------------------------------------------------------------------------- 1 | angular.module('app.toolbar', [ 2 | 'toolbar.controllers.ToolbarCtrl' 3 | ]); 4 | -------------------------------------------------------------------------------- /src/app/toolbar/toolbar.style.scss: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | // Add all toolbar related styles in here 3 | } 4 | -------------------------------------------------------------------------------- /src/app/toolbar/toolbar.view.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | menu 5 | 6 |

Angular Material Boilerplate

7 |
8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmacdonnacha/angular-material-boilerplate/3d769c517df271389b5e4362c935ab73e03f9331/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/user-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmacdonnacha/angular-material-boilerplate/3d769c517df271389b5e4362c935ab73e03f9331/src/assets/images/user-profile.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular Material Boilerplate 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vendor.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The `vendor_files` property in Gruntfile.js holds files to be automatically 3 | * concatenated and minified with our project source files. 4 | * 5 | * NOTE: Use the *.min.js version when compiling for production. 6 | * Otherwise, use the normal *.js version for development 7 | * 8 | */ 9 | 10 | module.exports = { 11 | js: [ 12 | // utility libraries 13 | 'vendor/jquery/dist/jquery.min.js', 14 | 15 | // Angular components 16 | 'vendor/angular/angular.js', 17 | 'vendor/angular-ui-router/release/angular-ui-router.min.js', 18 | 'vendor/angular-resource/angular-resource.min.js', 19 | 'vendor/angular-mocks/angular-mocks.js', 20 | 21 | // Angular Material 22 | 'vendor/angular-animate/angular-animate.min.js', 23 | 'vendor/angular-aria/angular-aria.min.js', 24 | 'vendor/angular-material/angular-material.min.js', 25 | 26 | // Local storage 27 | 'vendor/angular-local-storage/dist/angular-local-storage.min.js', 28 | 29 | // Modernizer 30 | 'vendor/modernizr/modernizr.js' 31 | ], 32 | css: [ ], 33 | assets: [ ] 34 | }; 35 | -------------------------------------------------------------------------------- /vendor_files.js: -------------------------------------------------------------------------------- 1 | require('angular'); 2 | require('angular-animate'); 3 | require('angular-aria'); 4 | require('angular-material'); 5 | require('angular-ui-router'); 6 | require('angular-resource'); 7 | require('angular-local-storage'); 8 | --------------------------------------------------------------------------------