├── src └── app │ ├── assets │ └── .gitkeep │ ├── app.scss │ ├── todo │ ├── todo.scss │ ├── todo.module.js │ ├── todo.html │ ├── todo.route.js │ └── todo.controller.js │ ├── styles │ ├── _material.scss │ ├── _base.scss │ └── _mixins.scss │ ├── core │ ├── 404.html │ ├── core.constants.js │ ├── core.module.js │ ├── dataservice.js │ ├── core.route.js │ └── core.config.js │ ├── chat │ ├── chat.module.js │ ├── chat.scss │ ├── chat.controller.js │ ├── chat.scroll.directive.js │ ├── chat.screen.directive.js │ ├── chat.html │ └── chat.route.js │ ├── about │ ├── about.module.js │ ├── about.controller.js │ ├── about.route.js │ └── about.html │ ├── blocks │ ├── logger │ │ ├── logger.module.js │ │ └── logger.js │ ├── exception │ │ ├── exception.module.js │ │ ├── exception.js │ │ └── exceptionHandler.provider.js │ └── router │ │ ├── router.module.js │ │ └── routerHelper.provider.js │ ├── config │ ├── config.json_example │ └── template.ejs │ ├── layout │ ├── layout.module.js │ ├── layout.footer.controller.js │ ├── footer.html │ ├── layout.route.js │ ├── layout.header-controller.js │ └── header.html │ ├── auth │ ├── login │ │ ├── login.module.js │ │ ├── login.scss │ │ ├── login.html │ │ ├── login.route.js │ │ └── login.controller.js │ ├── services │ │ ├── services.module.js │ │ └── auth.js │ ├── auth.module.js │ └── auth.route.js │ ├── app.module.js │ └── index.html ├── .bowerrc ├── qr.png ├── .gitignore ├── .csslintrc ├── .editorconfig ├── bower.json ├── LICENSE.md ├── .jshintrc ├── package.json ├── karma.conf.js ├── README.md └── gulpfile.js /src/app/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | @import "styles/_base"; 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarlepp/AngularJS-Firebase-Material-Demo/HEAD/qr.png -------------------------------------------------------------------------------- /src/app/todo/todo.scss: -------------------------------------------------------------------------------- 1 | .todo-done { 2 | .md-list-item-text { 3 | opacity: .3; 4 | text-decoration: line-through; 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/app/config/config.js 2 | src/app/config/config.json 3 | node_modules/ 4 | bower_components/ 5 | dist/* 6 | .tmp/* 7 | .idea 8 | -------------------------------------------------------------------------------- /src/app/styles/_material.scss: -------------------------------------------------------------------------------- 1 | header { 2 | .md-list-item-text { 3 | flex: none !important; 4 | 5 | .md-button { 6 | margin-top: 5px; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/core/404.html: -------------------------------------------------------------------------------- 1 |

404 - page not found

2 | 3 | 4 | Damn gerbils have stopped running again! Someone has been dispatched to poke them with a sharp stick. 5 | 6 | -------------------------------------------------------------------------------- /src/app/styles/_base.scss: -------------------------------------------------------------------------------- 1 | @import "mixins"; 2 | @import "material"; 3 | 4 | html { 5 | font-family: "RobotoDraft", "Roboto", sans-serif; 6 | overflow-x: hidden; 7 | } 8 | 9 | body { 10 | @include noSelect(); 11 | } -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "box-model": false, 3 | "fallback-colors": false, 4 | "box-sizing": false, 5 | "compatible-vendor-prefixes": false, 6 | "gradients": false, 7 | "adjoining-classes": false, 8 | "outline-none" : false 9 | } 10 | -------------------------------------------------------------------------------- /src/app/chat/chat.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.chat module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.chat', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/todo/todo.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.todo module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.todo', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/about/about.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.about module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.about', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/blocks/logger/logger.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of blocks.logger module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('blocks.logger', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/config/config.json_example: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy this file to 'config.json' and define _your_ firebaseUrl + remember remove this comment block too :D 3 | */ 4 | { 5 | "config": { 6 | "firebaseUrl": "https://CHANGETHISTOMATCHYOURFIREBASEURL.firebaseio.com/" 7 | } 8 | } -------------------------------------------------------------------------------- /src/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.layout module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.layout', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/auth/login/login.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.auth.login module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.auth.login', []) 11 | ; 12 | }()); 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/app/auth/services/services.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.auth.services module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.auth.services', []) 11 | ; 12 | })(); 13 | -------------------------------------------------------------------------------- /src/app/blocks/exception/exception.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of blocks.exception module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('blocks.exception', [ 11 | 'blocks.logger' 12 | ]) 13 | ; 14 | })(); 15 | -------------------------------------------------------------------------------- /src/app/blocks/router/router.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of blocks.router module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('blocks.router', [ 11 | 'ui.router', 12 | 'blocks.logger' 13 | ]) 14 | ; 15 | })(); 16 | -------------------------------------------------------------------------------- /src/app/auth/auth.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.auth module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.auth', [ 11 | 'firebaseDemo.auth.login', 12 | 'firebaseDemo.auth.services' 13 | ]) 14 | ; 15 | })(); 16 | -------------------------------------------------------------------------------- /src/app/app.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('firebaseDemo', [ 6 | 'firebaseDemo.about', 7 | 'firebaseDemo.auth', 8 | 'firebaseDemo.chat', 9 | 'firebaseDemo.config', 10 | 'firebaseDemo.core', 11 | 'firebaseDemo.layout', 12 | 'firebaseDemo.todo' 13 | ]) 14 | ; 15 | })(); 16 | -------------------------------------------------------------------------------- /src/app/core/core.constants.js: -------------------------------------------------------------------------------- 1 | /* global moment:false, Firebase: false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | /** 6 | * Specify core constant values 7 | * 8 | * @namespace Constants 9 | * @memberOf Core 10 | */ 11 | angular 12 | .module('firebaseDemo.core') 13 | .constant('moment', moment) 14 | .constant('Firebase', Firebase) 15 | ; 16 | })(); 17 | -------------------------------------------------------------------------------- /src/app/auth/login/login.scss: -------------------------------------------------------------------------------- 1 | .form-login { 2 | form { 3 | width: 300px; 4 | margin: 4em auto; 5 | padding: 0 2em 2em 2em; 6 | background: #fafafa; 7 | border: 1px solid #ebebeb; 8 | box-shadow: rgba(0, 0, 0, 0.14902) 0 1px 1px 0, rgba(0, 0, 0, 0.09804) 0 1px 2px 0; 9 | text-align: center; 10 | 11 | button { 12 | font-size: 30px; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/config/template.ejs: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE (config.js), THIS IS CREATED BY GULP TASK 2 | // jshint ignore: start 3 | (function() { 4 | 'use strict'; 5 | 6 | angular.module("<%- moduleName %>"<% if (deps) { %>, <%= JSON.stringify(deps) %><% } %>) 7 | <% constants.forEach(function(constant) { %> 8 | .constant("<%- constant.name %>", <%= constant.value %>) 9 | <% }) %> 10 | ; 11 | })(); 12 | -------------------------------------------------------------------------------- /src/app/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Initialization of firebaseDemo.core module. 6 | * 7 | * @namespace Modules 8 | */ 9 | angular 10 | .module('firebaseDemo.core', [ 11 | 'ngAnimate', 'ngMaterial', 'ngMessages', 'ngSanitize', 12 | 'ui.router', 13 | 'firebase', 'toastr', 14 | 'firebase-demo-templates', 15 | 'blocks.exception', 'blocks.logger', 'blocks.router' 16 | ]); 17 | })(); 18 | -------------------------------------------------------------------------------- /src/app/about/about.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.about module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.about') 11 | .controller('AboutController', AboutController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation for /about route. 16 | * @namespace About 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @constructor 21 | */ 22 | function AboutController() {} 23 | })(); 24 | -------------------------------------------------------------------------------- /src/app/layout/layout.footer.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.layout module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.layout') 11 | .controller('FooterController', FooterController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation for all routes 16 | * @namespace Layout 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @constructor 21 | */ 22 | function FooterController() {} 23 | })(); 24 | -------------------------------------------------------------------------------- /src/app/auth/services/auth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Auth factory. 6 | * 7 | * @namespace Factories 8 | */ 9 | angular 10 | .module('firebaseDemo.auth.services') 11 | .factory('Auth', Auth) 12 | ; 13 | 14 | /** 15 | * @desc Service class to return a firebaseAuth service to configured Firebase instance. 16 | * @namespace Auth 17 | * @memberOf Factories 18 | * @ngInject 19 | * 20 | * @param {Factories.Dataservice} dataservice 21 | * @param {AngularFireAuthService} $firebaseAuth 22 | * @returns {AngularFireAuth} 23 | * @constructor 24 | */ 25 | function Auth(dataservice, $firebaseAuth) { 26 | return $firebaseAuth(dataservice.getReference()); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /src/app/layout/footer.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 6 | 7 | 8 | GitHub 9 | 10 | 11 | 14 | 15 | 16 | Issues 17 | 18 | 19 | 22 | 23 | 24 | Tarmo Leppänen 25 | 26 |
27 |
-------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-demo", 3 | "main": [ 4 | "/dist/firebase-demo.min.js", 5 | "/dist/firebase-demo.min.css" 6 | ], 7 | "version": "0.1.0", 8 | "description": "TODO", 9 | "authors": [ 10 | "Tarmo Leppänen" 11 | ], 12 | "ignore": [ 13 | "**/.*", 14 | "package.json", 15 | "klei.json", 16 | "gulpfile.js", 17 | "node_modules", 18 | "bower_components", 19 | "src" 20 | ], 21 | "dependencies": { 22 | "angular": "1.4.1", 23 | "angular-animate": "1.4.1", 24 | "angular-loading-bar": "0.7.1", 25 | "angular-material": "0.9.8", 26 | "angular-messages": "1.4.1", 27 | "angular-sanitize": "1.4.1", 28 | "angular-ui-router": "0.2.15", 29 | "angularfire": "1.1.1", 30 | "mdi": "1.1.34", 31 | "moment": "2.10.3", 32 | "angular-toastr": "1.4.1" 33 | }, 34 | "devDependencies": {}, 35 | "resolutions": { 36 | "angular": "1.4.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/chat/chat.scss: -------------------------------------------------------------------------------- 1 | .chat-container { 2 | main.md-padding { 3 | padding: 0; 4 | } 5 | 6 | md-input-container { 7 | padding-bottom: 0; 8 | padding-left: 10px; 9 | } 10 | } 11 | 12 | .chat { 13 | margin: 0; 14 | padding: 0; 15 | 16 | .messages { 17 | border-top: none; 18 | border-bottom: none; 19 | overflow-y: scroll; 20 | overflow-x: hidden; 21 | height: 100vh; 22 | padding: 0 10px; 23 | 24 | code { 25 | background-color: transparent; 26 | } 27 | 28 | .time { 29 | position: absolute; 30 | left: 5px; 31 | } 32 | 33 | .message { 34 | margin-left: 155px; 35 | } 36 | } 37 | 38 | .input-group { 39 | .form-control, 40 | .input-group-addon { 41 | &:first-child { 42 | border-top-left-radius: 0; 43 | } 44 | } 45 | 46 | .input-group-btn:last-child > .btn { 47 | border-top-right-radius: 0; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/auth/auth.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.auth module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.auth') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.auth module. 16 | * @namespace Auth 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for firebaseDemo.auth module route definitions. 29 | * @memberOf Routes.Auth 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'auth', 37 | config: { 38 | abstract: true, 39 | parent: 'firebaseDemo' 40 | } 41 | } 42 | ]; 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /src/app/blocks/exception/exception.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Exception factory. 6 | * 7 | * @namespace Factories 8 | */ 9 | angular 10 | .module('blocks.exception') 11 | .factory('exception', exception) 12 | ; 13 | 14 | /** 15 | * @desc Application wide exception handler. 16 | * @namespace Exception 17 | * @memberOf Factories 18 | * @ngInject 19 | * 20 | * @param {Factories.Logger} logger 21 | * @returns {{ 22 | * catcher: Factories.Exception.catcher 23 | * }} 24 | */ 25 | function exception(logger) { 26 | return { 27 | catcher: catcher 28 | }; 29 | 30 | //////////////////// 31 | 32 | /** 33 | * @name catcher 34 | * @desc Catcher method for exception factory. 35 | * @memberOf Factories.Exception 36 | * 37 | * @param {string} message 38 | * @returns {function} 39 | */ 40 | function catcher(message) { 41 | return function(reason) { 42 | logger.error(message, reason); 43 | }; 44 | } 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /src/app/core/dataservice.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Dataservice factory. 6 | * 7 | * @namespace Factories 8 | */ 9 | angular 10 | .module('firebaseDemo.core') 11 | .factory('dataservice', dataservice) 12 | ; 13 | 14 | /** 15 | * @desc Application wide dataservice. 16 | * @namespace Dataservice 17 | * @memberOf Factories 18 | * @ngInject 19 | * 20 | * @param {Firebase} Firebase 21 | * @param {object} config 22 | * @returns {{ 23 | * getReference: Factories.Dataservice.getReference 24 | * }} 25 | */ 26 | function dataservice(Firebase, config) { 27 | return { 28 | getReference: getReference 29 | }; 30 | 31 | //////////////////// 32 | 33 | /** 34 | * @name getReference 35 | * @desc Getter method for Firebase reference. 36 | * @memberOf Factories.Dataservice 37 | * 38 | * @param {string} [identifier] 39 | * @returns {Firebase} 40 | */ 41 | function getReference(identifier) { 42 | identifier = identifier || ''; 43 | 44 | return new Firebase(config.firebaseUrl + identifier); 45 | } 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2015 Tarmo Leppänen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": false, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false, 59 | "$": false 60 | } 61 | } -------------------------------------------------------------------------------- /src/app/about/about.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.about module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.about') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.about module. 16 | * @namespace About 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for firebaseDemo.about module route definitions. 29 | * @memberOf Routes.About 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'about', 37 | config: { 38 | url: '/', 39 | parent: 'firebaseDemo', 40 | title: 'About', 41 | containerClass: 'about-container', 42 | views: { 43 | 'content@': { 44 | templateUrl: '/firebase-demo/about/about.html', 45 | controller: 'AboutController', 46 | controllerAs: 'vm' 47 | } 48 | } 49 | } 50 | } 51 | ]; 52 | } 53 | })(); 54 | -------------------------------------------------------------------------------- /src/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 |
26 | 29 | 30 |
31 |
32 |
33 | 34 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/app/layout/layout.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.layout module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.layout') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.layout module. 16 | * @namespace Layout 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for module route definitions. 29 | * @memberOf Routes.Layout 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'firebaseDemo', 37 | config: { 38 | abstract: true, 39 | views: { 40 | header: { 41 | templateUrl: '/firebase-demo/layout/header.html', 42 | controller: 'HeaderController', 43 | controllerAs: 'vm' 44 | }, 45 | footer: { 46 | templateUrl: '/firebase-demo/layout/footer.html', 47 | controller: 'FooterController', 48 | controllerAs: 'vm' 49 | } 50 | } 51 | } 52 | } 53 | ]; 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /src/app/auth/login/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Choose login provider

4 | 5 | 8 | 9 | Log in with Facebook account 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | Log in with Twitter account 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | Log in with GitHub account 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | Log in with Google account 40 | 41 | 42 | 43 | 44 |
45 |
46 | -------------------------------------------------------------------------------- /src/app/chat/chat.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.chat module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.chat') 11 | .controller('ChatController', ChatController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation for /chat route. 16 | * @namespace Chat 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @param {logger} logger 21 | * @param {{}} _user 22 | * @param {FirebaseArray} _messages 23 | * @constructor 24 | */ 25 | function ChatController( 26 | logger, 27 | _user, _messages 28 | ) { 29 | var vm = this; 30 | 31 | vm.messages = _messages; 32 | vm.message = ''; 33 | vm.form = {}; 34 | 35 | // Method to send new message 36 | vm.send = function() { 37 | // Define new item 38 | var message = { 39 | message: vm.message, 40 | stamp: new Date().getTime(), 41 | user: { 42 | name: _user[_user.provider].displayName, 43 | email: _user[_user.provider].email || '', 44 | image: _user[_user.provider].profileImageURL 45 | } 46 | }; 47 | 48 | // Add new message 49 | vm.messages 50 | .$add(message) 51 | .then(function onSuccess() { 52 | vm.message = ''; 53 | vm.form.$setUntouched(); 54 | 55 | logger.log('message stored'); 56 | }) 57 | ; 58 | }; 59 | } 60 | })(); 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "gulp watch", 7 | "test": "gulp test" 8 | }, 9 | "dependencies": { 10 | }, 11 | "devDependencies": { 12 | "connect-history-api-fallback": "1.1.0", 13 | "event-stream": "3.3.2", 14 | "gulp": "3.9.0", 15 | "gulp-angular-filesort": "1.1.1", 16 | "gulp-bower-files": "0.2.7", 17 | "gulp-cached": "1.1.0", 18 | "gulp-clean": "0.3.1", 19 | "gulp-concat": "2.6.0", 20 | "gulp-csslint": "0.2.0", 21 | "gulp-embedlr": "0.5.2", 22 | "gulp-filter": "3.0.1", 23 | "gulp-header": "1.7.1", 24 | "gulp-htmlmin": "1.2.0", 25 | "gulp-inject": "3.0.0", 26 | "gulp-jshint": "1.12.0", 27 | "gulp-karma": "0.0.5", 28 | "gulp-livereload": "3.8.1", 29 | "gulp-load-plugins": "1.0.0", 30 | "gulp-minify-css": "1.2.1", 31 | "gulp-ng-annotate": "1.1.0", 32 | "gulp-ng-constant": "1.1.0", 33 | "gulp-ng-html2js": "0.2.0", 34 | "gulp-rename": "1.2.2", 35 | "gulp-replace-task": "0.11.0", 36 | "gulp-sass": "2.1.0", 37 | "gulp-serve": "1.2.0", 38 | "gulp-uglify": "1.4.2", 39 | "gulp-util": "3.0.7", 40 | "jshint-stylish": "2.0.1", 41 | "karma": "0.13.14", 42 | "karma-chai": "0.1.0", 43 | "karma-chrome-launcher": "0.2.1", 44 | "karma-firefox-launcher": "0.1.6", 45 | "karma-mocha": "0.2.0", 46 | "karma-phantomjs-launcher": "0.2.1", 47 | "karma-script-launcher": "0.1.0", 48 | "lazypipe": "1.0.1", 49 | "sort-stream": "1.0.1", 50 | "streamqueue": "1.1.1", 51 | "tiny-lr": "0.2.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/chat/chat.scroll.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify 'chatScroll' directive for firebaseDemo.chat module. 6 | * 7 | * @namespace Directives 8 | */ 9 | angular 10 | .module('firebaseDemo.chat') 11 | .directive('chatScroll', chatScroll) 12 | ; 13 | 14 | /** 15 | * @desc 'chatScroll' directive implementation 16 | * @namespace chatScroll 17 | * @memberOf Directives 18 | * @ngInject 19 | * 20 | * @param {$window} $window 21 | * @param {$timeout} $timeout 22 | * @returns {{ 23 | * link: function, 24 | * restrict: string 25 | * }} 26 | */ 27 | function chatScroll($window, $timeout) { 28 | return { 29 | link: link, 30 | restrict: 'A' 31 | }; 32 | 33 | /** 34 | * Linker function for 'chatScroll' directive. 35 | * 36 | * @param {$scope} scope Current scope 37 | * @param {$element} element Element object 38 | * @param {object} attributes Element attributes 39 | */ 40 | function link(scope, element, attributes) { 41 | // Function to make actual scroll 42 | function scroll() { 43 | $timeout(function onTimeout() { 44 | element[0].scrollTop = element[0].scrollHeight; 45 | }); 46 | } 47 | 48 | // Watch message collection and whenever it changes scroll bottom 49 | scope.$watchCollection(attributes.chatScroll, function onEvent() { 50 | scroll(); 51 | }); 52 | 53 | // Also bind scroll to window resize event 54 | angular.element($window).bind('resize', function onEvent() { 55 | scroll(); 56 | }); 57 | } 58 | } 59 | })(); 60 | -------------------------------------------------------------------------------- /src/app/todo/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 11 | 12 | 13 | 17 | 18 | Add new item 19 | 20 | 21 | send 22 | 23 | 24 |
25 | 26 | 30 | 34 | 35 |
36 |

{{todo.label}}

37 |
38 | 39 | 42 | 43 | Remove item 44 | 45 | 46 | clear 47 | 48 |
49 |
50 | -------------------------------------------------------------------------------- /src/app/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin border-radius($radius) { 2 | -webkit-border-radius: $radius; 3 | -moz-border-radius: $radius; 4 | -ms-border-radius: $radius; 5 | border-radius: $radius; 6 | } 7 | 8 | @mixin noSelect() { 9 | -webkit-touch-callout: none; 10 | -webkit-user-select: none; 11 | -khtml-user-select: none; 12 | -moz-user-select: none; 13 | -ms-user-select: none; 14 | user-select: none; 15 | } 16 | 17 | @mixin box-shadow($top, $left, $blur, $color: $colorBoxShadow, $inset:"") { 18 | -webkit-box-shadow: $top $left $blur $color #{$inset}; 19 | -moz-box-shadow: $top $left $blur $color #{$inset}; 20 | box-shadow: $top $left $blur $color #{$inset}; 21 | } 22 | 23 | @mixin text-shadow($top, $left, $blur, $color) { 24 | text-shadow: $top $left $blur $color; 25 | } 26 | 27 | @mixin linear-gradient($fromColor, $toColor) { 28 | background-color: $toColor; /* Fallback Color */ 29 | background-image: -webkit-gradient(linear, left top, left bottom, from($fromColor), to($toColor)); /* Saf4+, Chrome */ 30 | background-image: -webkit-linear-gradient(top, $fromColor, $toColor); /* Chrome 10+, Saf5.1+, iOS 5+ */ 31 | background-image: -moz-linear-gradient(top, $fromColor, $toColor); /* FF3.6 */ 32 | background-image: -ms-linear-gradient(top, $fromColor, $toColor); /* IE10 */ 33 | background-image: -o-linear-gradient(top, $fromColor, $toColor); /* Opera 11.10+ */ 34 | background-image: linear-gradient(top, $fromColor, $toColor); 35 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#{$fromColor}', EndColorStr='#{$toColor}'); 36 | } 37 | 38 | @mixin border-box() { 39 | -webkit-box-sizing: border-box; 40 | -moz-box-sizing: border-box; 41 | box-sizing: border-box; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/auth/login/login.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.auth.login module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.auth.login') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.auth.login module. 16 | * @namespace Auth.Login 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for firebaseDemo.auth.login module route definitions. 29 | * @memberOf Routes.Auth.Login 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'auth.login', 37 | config: { 38 | url: '/login', 39 | title: 'Login', 40 | containerClass: 'login-container', 41 | views: { 42 | 'content@': { 43 | templateUrl: '/firebase-demo/auth/login/login.html', 44 | controller: 'LoginController', 45 | controllerAs: 'vm', 46 | resolve: { 47 | _user: _user 48 | } 49 | } 50 | } 51 | } 52 | } 53 | ]; 54 | } 55 | 56 | /** 57 | * @name _user 58 | * @desc '_user' resolve implementation. 59 | * @memberOf Routes.Auth.Login 60 | * @ngInject 61 | * 62 | * @param {AngularFireAuth} Auth 63 | * @returns {ng.IPromise|*} 64 | * @private 65 | */ 66 | function _user(Auth) { 67 | return Auth.$waitForAuth(); 68 | } 69 | })(); 70 | -------------------------------------------------------------------------------- /src/app/chat/chat.screen.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify 'chatScreen' directive for firebaseDemo.chat module. 6 | * 7 | * @namespace Directives 8 | */ 9 | angular 10 | .module('firebaseDemo.chat') 11 | .directive('chatScreen', chatScreen) 12 | ; 13 | 14 | /** 15 | * @desc 'chatScreen' directive implementation 16 | * @namespace chatScroll 17 | * @memberOf Directives 18 | * @ngInject 19 | * 20 | * @param {$window} $window 21 | * @param {$timeout} $timeout 22 | * @returns {{ 23 | * link: function, 24 | * restrict: string 25 | * }} 26 | */ 27 | function chatScreen($window, $timeout) { 28 | return { 29 | link: link, 30 | restrict: 'A' 31 | }; 32 | 33 | //noinspection JSUnusedLocalSymbols 34 | /** 35 | * Linker function for 'chatScreen' directive. 36 | * 37 | * @param {$scope} scope 38 | * @param {$element} element 39 | */ 40 | function link(scope, element) { 41 | // Initialize height values 42 | var heightHeader = 0; 43 | var heightFooter = 0; 44 | var heightTotal = 0; 45 | 46 | // function to make actual chat screen resize 47 | function resize() { 48 | $timeout(function onTimeout() { 49 | heightHeader = document.getElementById('header').offsetHeight; 50 | heightFooter = document.getElementById('footer').offsetHeight; 51 | heightTotal = $window.innerHeight - heightHeader - heightFooter - 70; 52 | 53 | angular.element(element).css('height', heightTotal + 'px'); 54 | }); 55 | } 56 | 57 | // Bind resize to window resize event 58 | angular.element($window).bind('resize', function onEvent() { 59 | resize(); 60 | }); 61 | 62 | // And on initialize resize screen 63 | resize(); 64 | } 65 | } 66 | })(); 67 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function ( karma ) { 3 | process.env.PHANTOMJS_BIN = 'node_modules/karma-phantomjs-launcher/node_modules/.bin/phantomjs'; 4 | 5 | karma.set({ 6 | /** 7 | * From where to look for files, starting with the location of this file. 8 | */ 9 | basePath: './', 10 | 11 | /** 12 | * Filled by the task `gulp karma-conf` 13 | */ 14 | files: [ 15 | ], 16 | 17 | frameworks: [ 'mocha', 'chai' ], 18 | plugins: [ 'karma-mocha', 'karma-chai', 'karma-phantomjs-launcher' ], 19 | 20 | /** 21 | * How to report, by default. 22 | */ 23 | reporters: 'progress', 24 | 25 | /** 26 | * Show colors in output? 27 | */ 28 | colors: true, 29 | 30 | /** 31 | * On which port should the browser connect, on which port is the test runner 32 | * operating, and what is the URL path for the browser to use. 33 | */ 34 | port: 9099, 35 | runnerPort: 9100, 36 | urlRoot: '/', 37 | 38 | /** 39 | * Disable file watching by default. 40 | */ 41 | autoWatch: false, 42 | 43 | /** 44 | * The list of browsers to launch to test on. This includes only "Firefox" by 45 | * default, but other browser names include: 46 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 47 | * 48 | * Note that you can also use the executable name of the browser, like "chromium" 49 | * or "firefox", but that these vary based on your operating system. 50 | * 51 | * You may also leave this blank and manually navigate your browser to 52 | * http://localhost:9099/ when you're running tests. The window/tab can be left 53 | * open and the tests will automatically occur there during the build. This has 54 | * the aesthetic advantage of not launching a browser every time you save. 55 | */ 56 | browsers: [ 57 | 'PhantomJS' 58 | ] 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /src/app/layout/layout.header-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.layout module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.layout') 11 | .controller('HeaderController', HeaderController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation. 16 | * @namespace Layout 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @param {ui.router.state.$state} $state 21 | * @param {AngularFireAuth} Auth 22 | * @constructor 23 | * @ngInject 24 | */ 25 | function HeaderController( 26 | $state, 27 | Auth 28 | ) { 29 | var vm = this; 30 | 31 | // Initialize user object 32 | vm.user = {}; 33 | 34 | /** 35 | * Method to get provider class for currently authenticated user. 36 | * 37 | * @param {string} provider 38 | * @returns {string} 39 | */ 40 | vm.getProviderClass = function getProviderClass(provider) { 41 | var output = ''; 42 | 43 | switch (provider) { 44 | case 'facebook': 45 | output = 'mdi-facebook-box'; 46 | break; 47 | case 'twitter': 48 | output = 'mdi-twitter-box'; 49 | break; 50 | case 'github': 51 | output = 'mdi-github-box'; 52 | break; 53 | case 'google': 54 | output = 'mdi-google-plus-box'; 55 | break; 56 | } 57 | 58 | return output; 59 | }; 60 | 61 | /** 62 | * Method to make logout action. 63 | * 64 | * @param {Event} $event 65 | */ 66 | vm.logout = function logout($event) { 67 | $event.preventDefault(); 68 | $event.stopPropagation(); 69 | 70 | Auth.$unauth(); 71 | 72 | $state.go('about'); 73 | }; 74 | 75 | // Watcher for auth status 76 | Auth.$onAuth(function onAuth(user) { 77 | vm.user = user; 78 | }); 79 | } 80 | })(); 81 | -------------------------------------------------------------------------------- /src/app/core/core.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for module 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.core') 11 | .run(appRun) 12 | ; 13 | 14 | /** 15 | * @desc Actual run block. 16 | * @namespace Core 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {ng.IRootScopeService|{containerClass: string}} $rootScope 21 | * @param {Providers.RouterHelper} routerHelper 22 | * @constructor 23 | */ 24 | function appRun( 25 | $rootScope, 26 | routerHelper 27 | ) { 28 | // Set default state to be 404 page 29 | routerHelper.configureStates(getStates(), '/404'); 30 | 31 | // Add success handler for route change 32 | $rootScope.$on('$stateChangeSuccess', stateChangeSuccess); 33 | 34 | //noinspection JSUnusedLocalSymbols 35 | /** 36 | * Success state change helper function 37 | * 38 | * @param {object} event 39 | * @param {IState|{containerClass: string}} toState 40 | * @param {object} toParams 41 | * @param {IState|{containerClass: string}} fromState 42 | * @param {object} fromParams 43 | */ 44 | function stateChangeSuccess(event, toState, toParams, fromState, fromParams) { 45 | $rootScope.containerClass = toState.containerClass; 46 | } 47 | } 48 | 49 | /** 50 | * @name getStates 51 | * @desc Getter method for module route definitions. 52 | * @memberOf Routes.Core 53 | * 54 | * @returns {*[]} 55 | */ 56 | function getStates() { 57 | return [ 58 | { 59 | state: '404', 60 | config: { 61 | url: '/404', 62 | title: '404', 63 | parent: 'firebaseDemo', 64 | views: { 65 | 'content@': { 66 | templateUrl: '/firebase-demo/core/404.html' 67 | } 68 | } 69 | } 70 | } 71 | ]; 72 | } 73 | })(); 74 | -------------------------------------------------------------------------------- /src/app/todo/todo.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.todo module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.todo') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.todo module. 16 | * @namespace Todo 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for module route definitions. 29 | * @memberOf Routes.Todo 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'todo', 37 | config: { 38 | url: '/todo', 39 | parent: 'firebaseDemo', 40 | title: 'Todo', 41 | containerClass: 'todo-container', 42 | views: { 43 | 'content@': { 44 | templateUrl: '/firebase-demo/todo/todo.html', 45 | controller: 'TodoController', 46 | controllerAs: 'vm', 47 | resolve: { 48 | _todos: _todos 49 | } 50 | } 51 | } 52 | } 53 | } 54 | ]; 55 | } 56 | 57 | /** 58 | * @name _todos 59 | * @desc '_todos' resolve implementation. 60 | * @memberOf Routes.Todo 61 | * @ngInject 62 | * 63 | * @param {AngularFireArrayService} $firebaseArray 64 | * @param {AngularFireAuth} Auth 65 | * @param {Factories.Dataservice} dataservice 66 | * @returns {ng.IPromise} 67 | * @private 68 | */ 69 | function _todos($firebaseArray, Auth, dataservice) { 70 | return Auth 71 | .$requireAuth() 72 | .then(getItems) 73 | ; 74 | 75 | function getItems(user) { 76 | return $firebaseArray(dataservice.getReference('todos/' + user.auth.uid)); 77 | } 78 | } 79 | })(); 80 | -------------------------------------------------------------------------------- /src/app/chat/chat.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | 8 |
9 |

10 | {{message.user.name}} 11 |

12 |

{{message.message}}

13 |
14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 28 | 29 | 30 | 34 | 35 | Send messagejoi 36 | 37 | 38 | send 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 | 63 | -------------------------------------------------------------------------------- /src/app/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Angular/Firebase/Material - demo 4 | 5 | 8 | 9 | 10 | about 11 | 12 | 13 | 17 | 18 | 19 | todo 20 | 21 | 22 | 26 | 27 | 28 | chat 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | Login 42 | 43 | 44 | 47 | {{vm.user[vm.user.provider].displayName}} 50 | 51 |
52 |

53 | 54 | 55 | 56 |

57 | 58 |

59 |
60 | 61 |
62 | 63 | 64 | 65 | Logout 66 | 67 |
68 |
69 |
70 |
71 | -------------------------------------------------------------------------------- /src/app/chat/chat.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify run block for firebaseDemo.chat module. 6 | * 7 | * @namespace Routes 8 | */ 9 | angular 10 | .module('firebaseDemo.chat') 11 | .run(moduleRun) 12 | ; 13 | 14 | /** 15 | * @desc Run block for firebaseDemo.chat module. 16 | * @namespace Chat 17 | * @memberOf Routes 18 | * @ngInject 19 | * 20 | * @param {Providers.RouterHelper} routerHelper 21 | */ 22 | function moduleRun(routerHelper) { 23 | routerHelper.configureStates(getStates()); 24 | } 25 | 26 | /** 27 | * @name getStates 28 | * @desc Getter method for firebaseDemo.chat module route definitions. 29 | * @memberOf Routes.Chat 30 | * 31 | * @returns {*[]} 32 | */ 33 | function getStates() { 34 | return [ 35 | { 36 | state: 'chat', 37 | config: { 38 | url: '/chat', 39 | parent: 'firebaseDemo', 40 | title: 'Chat', 41 | containerClass: 'chat-container', 42 | views: { 43 | 'content@': { 44 | templateUrl: '/firebase-demo/chat/chat.html', 45 | controller: 'ChatController', 46 | controllerAs: 'vm', 47 | resolve: { 48 | _user: _user, 49 | _messages: _messages 50 | } 51 | } 52 | } 53 | } 54 | } 55 | ]; 56 | } 57 | 58 | /** 59 | * @name _user 60 | * @desc '_user' resolve implementation. 61 | * @memberOf Routes.Chat 62 | * @ngInject 63 | * 64 | * @param {AngularFireAuth} Auth 65 | * @returns {ng.IPromise|*} 66 | * @private 67 | */ 68 | function _user(Auth) { 69 | return Auth.$requireAuth(); 70 | } 71 | 72 | /** 73 | * @name _messages 74 | * @desc '_messages' resolve function. 75 | * @memberOf Routes.Chat 76 | * @ngInject 77 | * 78 | * @param {AngularFireArrayService} $firebaseArray 79 | * @param {Factories.Dataservice} dataservice 80 | * @returns {ng.IPromise} 81 | * @private 82 | */ 83 | function _messages($firebaseArray, dataservice) { 84 | return $firebaseArray(dataservice.getReference('messages')); 85 | } 86 | })(); 87 | -------------------------------------------------------------------------------- /src/app/todo/todo.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.todo module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.todo') 11 | .controller('TodoController', TodoController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation. 16 | * @namespace Todo 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @param {Factories.Logger} logger 21 | * @param {FirebaseArray} _todos 22 | * @constructor 23 | */ 24 | function TodoController( 25 | logger, 26 | _todos 27 | ) { 28 | var vm = this; 29 | 30 | vm.todos = _todos; 31 | vm.label = ''; 32 | vm.form = {}; 33 | 34 | // Method to add new item 35 | vm.add = function() { 36 | // Define new item 37 | var todo = { 38 | label: vm.label, 39 | dateAdded: new Date().getTime(), 40 | dateDone: null, 41 | isDone: false 42 | }; 43 | 44 | // Add new todo item 45 | vm.todos 46 | .$add(todo) 47 | .then(function() { 48 | logger.success('New todo item added!'); 49 | 50 | vm.label = ''; 51 | vm.form.$setUntouched(); 52 | }) 53 | ; 54 | }; 55 | 56 | /** 57 | * Method to save specified todo item. 58 | * 59 | * @param {{}} todo 60 | */ 61 | vm.save = function(todo) { 62 | todo.dateDone = !todo.isDone ? null : new Date().getTime(); 63 | 64 | // Save actual item 65 | vm.todos 66 | .$save(todo) 67 | .then(function() { 68 | logger.success('Todo item saved'); 69 | }) 70 | ; 71 | }; 72 | 73 | /** 74 | * Method to remove specified todo item. 75 | * 76 | * @param {Event} event 77 | * @param {{}} todo 78 | */ 79 | vm.remove = function(event, todo) { 80 | // We need to stop anything else to happen in this case... 81 | event.preventDefault(); 82 | event.stopPropagation(); 83 | 84 | // Remove actual item 85 | vm.todos 86 | .$remove(todo) 87 | .then(function() { 88 | logger.success('Todo item removed'); 89 | }) 90 | ; 91 | }; 92 | } 93 | })(); 94 | -------------------------------------------------------------------------------- /src/app/auth/login/login.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify controller for firebaseDemo.auth.login module. 6 | * 7 | * @namespace Controllers 8 | */ 9 | angular 10 | .module('firebaseDemo.auth.login') 11 | .controller('LoginController', LoginController) 12 | ; 13 | 14 | /** 15 | * @desc Controller implementation for /login route. 16 | * @namespace Login 17 | * @memberOf Controllers 18 | * @ngInject 19 | * 20 | * @param {ui.router.state.$state} $state 21 | * @param {Factories.Dataservice} dataservice 22 | * @param {Factories.Logger} logger 23 | * @param {object|undefined} _user 24 | * @constructor 25 | */ 26 | function LoginController( 27 | $state, 28 | dataservice, logger, 29 | _user 30 | ) { 31 | var vm = this; 32 | 33 | // User has already logged in, so redirect to proper page 34 | if (_user) { 35 | $state.go('todo'); 36 | } 37 | 38 | /** 39 | * Method to make actual login via specified provider to Firebase backend. 40 | * 41 | * @param {string} provider Name of the used provider, this is one of following: 42 | * - facebook 43 | * - twitter 44 | * - github 45 | * - google 46 | */ 47 | vm.login = function(provider) { 48 | var ref = dataservice.getReference(); 49 | 50 | /** 51 | * Login callback function which handles possible login errors and redirection if all is ok. 52 | * 53 | * @param {object} error 54 | * @param {object} authData 55 | */ 56 | var callback = function(error, authData) { 57 | if (error) { 58 | logger.error('Login Failed!', error); 59 | } else { 60 | logger.log('auth data', authData); 61 | logger.success('Login successfully!'); 62 | 63 | $state.go('todo'); 64 | } 65 | }; 66 | 67 | // Specify used options for Firebase auth 68 | var options = { 69 | remember: 'sessionOnly', 70 | scope: (provider !== 'github') ? 'email' : 'user:email' 71 | }; 72 | 73 | // And make actual user authentication 74 | ref.authWithOAuthPopup(provider, callback, options); 75 | }; 76 | } 77 | }()); 78 | -------------------------------------------------------------------------------- /src/app/about/about.html: -------------------------------------------------------------------------------- 1 |

2 | This is a small demo about 3 | Angular.js, 4 | Angular Material and 5 | Firebase. 6 | Main purpose of this demo application is to show how powerful these components are together. 7 |

8 | 9 |
10 |
11 | 12 | 13 | Used libraries 14 | 15 | 16 | Angular.js 17 | 18 | 19 | Angular Material 20 | 21 | 22 | AngularFire 23 | 24 | 25 | Material Design Icons 26 | 27 | 28 | Moment.js 29 | 30 | 31 |
32 | 33 |
34 | 35 | External links 36 | 37 | 38 | Firebase 39 | 40 | 41 | 42 | Github 43 | 44 | 45 | 46 | Angular Style Guide 47 | 48 | 49 | 50 | Airbnb JavaScript Style Guide 51 | 52 | 53 | 54 | Material design 55 | 56 | 57 |
58 | 59 |
60 | -------------------------------------------------------------------------------- /src/app/blocks/exception/exceptionHandler.provider.js: -------------------------------------------------------------------------------- 1 | // Include in index.html so that app level exceptions are handled. 2 | // Exclude from testRunner.html which should run exactly what it wants to run 3 | (function() { 4 | 'use strict'; 5 | 6 | /** 7 | * Specify provider and configure it for blocks.exception module 8 | * 9 | * @namespace Providers 10 | */ 11 | angular 12 | .module('blocks.exception') 13 | .provider('exceptionHandler', exceptionHandlerProvider) 14 | .config(moduleConfig) 15 | ; 16 | 17 | /** 18 | * @desc Must configure the exception handling. 19 | * @namespace ExceptionHandler 20 | * @memberOf Providers 21 | */ 22 | function exceptionHandlerProvider() { 23 | /* jshint validthis:true */ 24 | this.config = { 25 | appErrorPrefix: undefined 26 | }; 27 | 28 | this.configure = function configure(appErrorPrefix) { 29 | this.config.appErrorPrefix = appErrorPrefix; 30 | }; 31 | 32 | this.$get = function $get() { 33 | return {config: this.config}; 34 | }; 35 | } 36 | 37 | /** 38 | * @desc Configure by setting an optional string value for appErrorPrefix. Accessible via config.appErrorPrefix 39 | * (via config value). 40 | * @namespace ExceptionHandler 41 | * @memberOf Providers 42 | * @ngInject 43 | * 44 | * @param {$provide} $provide 45 | */ 46 | function moduleConfig($provide) { 47 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 48 | } 49 | 50 | /** 51 | * @desc Extend the $exceptionHandler service to also display a toast. 52 | * @namespace ExceptionHandler 53 | * @memberOf Providers 54 | * @ngInject 55 | * 56 | * @param {$delegate|*} $delegate 57 | * @param {Providers.ExceptionHandler} exceptionHandler 58 | * @param {Factories.Logger} logger 59 | * @return {function} the decorated $exceptionHandler service 60 | */ 61 | function extendExceptionHandler($delegate, exceptionHandler, logger) { 62 | return function(exception, cause) { 63 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 64 | var errorData = { 65 | exception: exception, 66 | cause: cause 67 | }; 68 | 69 | // Create exception message 70 | exception.message = appErrorPrefix + exception.message; 71 | 72 | $delegate(exception, cause); 73 | 74 | /** 75 | * Could add the error to a service's collection, add errors to $rootScope, log errors to remote web server, 76 | * or log locally. Or throw hard. It is entirely up to you. throw exception; 77 | * 78 | * @example 79 | * throw { message: 'error message we added' }; 80 | */ 81 | logger.error(exception.message, errorData); 82 | }; 83 | } 84 | })(); 85 | -------------------------------------------------------------------------------- /src/app/blocks/logger/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Logger factory. 6 | * 7 | * @namespace Factories 8 | */ 9 | angular 10 | .module('blocks.logger') 11 | .factory('logger', logger) 12 | ; 13 | 14 | /** 15 | * @desc Application wide logger handler. 16 | * @namespace Logger 17 | * @memberOf Factories 18 | * @ngInject 19 | * 20 | * @param {$log} $log 21 | * @param {$injector} $injector 22 | * @returns {{ 23 | * error: Factories.Logger.error, 24 | * info: Factories.Logger.info, 25 | * success: Factories.Logger.success, 26 | * warning: Factories.Logger.warning, 27 | * log: Factories.Logger.log 28 | * }} 29 | */ 30 | function logger($log, $injector) { 31 | return { 32 | // toastr implementations 33 | error: error, 34 | info: info, 35 | success: success, 36 | warning: warning, 37 | 38 | // straight to console; bypass toastr 39 | log: log 40 | }; 41 | 42 | //////////////////// 43 | 44 | /** 45 | * @name error 46 | * @desc Error method for logger factory. 47 | * @memberOf Factories.Logger 48 | * 49 | * @param {string} message 50 | * @param {object} [data] 51 | * @param {string} [title] 52 | */ 53 | function error(message, data, title) { 54 | data = data || {}; 55 | title = title || ''; 56 | 57 | $injector.get('toastr').error(message, title); 58 | 59 | $log.error('Error: ' + message, data); 60 | } 61 | 62 | /** 63 | * @name info 64 | * @desc Info method for logger factory. 65 | * @memberOf Factories.Logger 66 | * 67 | * @param {string} message 68 | * @param {object} [data] 69 | * @param {string} [title] 70 | */ 71 | function info(message, data, title) { 72 | data = data || {}; 73 | title = title || ''; 74 | 75 | $injector.get('toastr').info(message, title); 76 | 77 | $log.info('Info: ' + message, data); 78 | } 79 | 80 | /** 81 | * @name success 82 | * @desc Success method for logger factory. 83 | * @memberOf Factories.Logger 84 | * 85 | * @param {string} message 86 | * @param {object} [data] 87 | * @param {string} [title] 88 | */ 89 | function success(message, data, title) { 90 | data = data || {}; 91 | title = title || ''; 92 | 93 | $injector.get('toastr').success(message, title); 94 | 95 | $log.info('Success: ' + message, data); 96 | } 97 | 98 | /** 99 | * @name warning 100 | * @desc Warning method for logger factory. 101 | * @memberOf Factories.Logger 102 | * 103 | * @param {string} message 104 | * @param {object} [data] 105 | * @param {string} [title] 106 | */ 107 | function warning(message, data, title) { 108 | data = data || {}; 109 | title = title || ''; 110 | 111 | $injector.get('toastr').warning(message, title); 112 | 113 | $log.warn('Warning: ' + message, data, title); 114 | } 115 | 116 | /** 117 | * @name log 118 | * @desc Default log function. 119 | * @memberOf Factories.Logger 120 | */ 121 | function log() { 122 | $log.log(arguments); 123 | } 124 | } 125 | }()); 126 | -------------------------------------------------------------------------------- /src/app/core/core.config.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify application configure values 6 | * 7 | * @type {{ 8 | * appErrorPrefix: string, 9 | * appTitle: string 10 | * }} 11 | */ 12 | var config = { 13 | appErrorPrefix: 'Angular/Firebase/Material - demo - Error', 14 | appTitle: 'Angular/Firebase/Material - demo' 15 | }; 16 | 17 | /** 18 | * Module initialization 19 | * 20 | * @namespace Core 21 | */ 22 | angular 23 | .module('firebaseDemo.core') 24 | .value('config', config) 25 | .config(moduleConfig) 26 | ; 27 | 28 | /** 29 | * @desc Actual configure implementation for module. 30 | * @namespace Configure 31 | * @memberOf Core 32 | * @ngInject 33 | * 34 | * @param {$provide} $provide 35 | * @param {$logProvider} $logProvider 36 | * @param {$mdThemingProvider} $mdThemingProvider 37 | * @param {Providers.RouterHelperProvider} routerHelperProvider 38 | * @param {Providers.ExceptionHandler} exceptionHandlerProvider 39 | * @constructor 40 | */ 41 | function moduleConfig( 42 | $provide, $logProvider, $mdThemingProvider, 43 | routerHelperProvider, exceptionHandlerProvider 44 | ) { 45 | // Add filename + line number feature to $log component 46 | $provide.decorator('$log', function decorator($delegate) { 47 | var originalFunctions = {}; 48 | 49 | // Store the original log functions 50 | angular.forEach($delegate, function iterator(originalFunction, functionName) { 51 | originalFunctions[functionName] = originalFunction; 52 | }); 53 | 54 | var functionsToDecorate = ['log', 'info', 'warn', 'error', 'debug']; 55 | 56 | // Apply the decorations 57 | angular.forEach(functionsToDecorate, function iterator(functionName) { 58 | $delegate[functionName] = logDecorator(originalFunctions[functionName]); 59 | }); 60 | 61 | return $delegate; 62 | }); 63 | 64 | if ($logProvider.debugEnabled) { 65 | $logProvider.debugEnabled(true); 66 | } 67 | 68 | // Configure material design palettes 69 | $mdThemingProvider 70 | .theme('default') 71 | .primaryPalette('blue-grey') 72 | .accentPalette('blue') 73 | ; 74 | 75 | // Configure exception handler provider 76 | exceptionHandlerProvider.configure(config.appErrorPrefix); 77 | 78 | // Configure router helper provider 79 | routerHelperProvider.configure({docTitle: config.appTitle + ': '}); 80 | } 81 | 82 | /** 83 | * $log decorator function, this is needed to add filename and line number to each $log command. 84 | * 85 | * @param {function} func 86 | * @returns {function} 87 | */ 88 | function logDecorator(func) { 89 | return function anon() { 90 | var args = [].slice.call(arguments); 91 | 92 | // Insert a separator between the existing log message(s) and what we're adding. 93 | args.push(' - '); 94 | 95 | // Use (instance of Error)'s stack to get the current line. 96 | var stack = (new Error()).stack.split('\n').slice(1); 97 | 98 | // Throw away the first item because it is the `$log.fn()` function, 99 | // but we want the code that called `$log.fn()`. 100 | stack.shift(); 101 | 102 | // We only want the top line, thanks. 103 | stack = stack.slice(1, 2); 104 | 105 | // Put it on the args stack. 106 | args.push(stack); 107 | 108 | // Call the original function with the new args. 109 | func.apply(func, args); 110 | }; 111 | } 112 | })(); 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS/Firebase/Material - demo 2 | ============ 3 | [![Dependency Status](https://david-dm.org/tarlepp/Angular-Firebase-Material-Demo.svg)](https://david-dm.org/tarlepp/Angular-Firebase-Material-Demo) 4 | [![devDependency Status](https://david-dm.org/tarlepp/Angular-Firebase-Material-Demo/dev-status.svg)](https://david-dm.org/tarlepp/Angular-Firebase-Material-Demo#info=devDependencies) 5 | 6 | ## What is this? 7 | 8 | Just a small demo to show how to use [Angular](https://angularjs.org/) + [Firebase](https://www.firebase.com/) + 9 | [Google Material Design](https://www.google.com/design/spec/material-design/introduction.html) together. Currently 10 | this demo application contains following features: 11 | 12 | * Social media login (Facebook, Twitter, Google+ and GitHub) 13 | * Personal 'Todo' item list 14 | * Chat with other users 15 | 16 | ## Demo 17 | 18 | Demo of this application can be found from [https://boiling-fire-2804.firebaseapp.com/](https://boiling-fire-2804.firebaseapp.com/) 19 | 20 | ![QR code to demo application](https://raw.github.com/tarlepp/Angular-Firebase-Material-Demo/master/qr.png) 21 | 22 | ## Used libraries, guides, etc. 23 | 24 | ### Libraries 25 | 26 | * [AngularJS — Superheroic JavaScript MVW Framework](https://angularjs.org/) 27 | * [Angular Material](https://material.angularjs.org/) 28 | * [AngularFire](https://www.firebase.com/docs/web/libraries/angular/) 29 | * [Material Design icons By Google](https://github.com/google/material-design-icons) 30 | * [Moment.js](http://momentjs.com/) 31 | 32 | ### Guides 33 | 34 | * [Angular Style Guide](https://github.com/johnpapa/angular-styleguide) 35 | * [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript/tree/master/es5) 36 | 37 | ### Other resources 38 | 39 | * [Firebase](https://www.firebase.com/) 40 | * [Material design](https://www.google.com/design/spec/material-design/) 41 | 42 | ## Installation 43 | 44 | First of all you have to install npm and node.js to your box. Installation instructions can 45 | be found [here](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager). 46 | 47 | After that you need to install bower and gulp main packages to make all things to happen. 48 | These can be installed with following commands on your *nix box. 49 |
 50 | sudo npm install bower -g
 51 | sudo npm install gulp -g
 52 | 
53 | 54 | And when you have npm and node.js installed to your box, just navigate yourself to root folder 55 | of the app and run following commands: 56 | 57 |
 58 | npm install
 59 | bower install
 60 | 
61 | 62 | ### Configuration 63 | 64 | See ```/src/app/config/config.json_example``` file and copy it to ```/src/app/config/config.json``` file and make 65 | necessary changes to it. Note that you need a Firebase account to get that url. 66 | 67 | ### Firebase 68 | 69 | To get Firebase running as it should first you need to make new Firebase application. Which you can create easily from 70 | their website [https://www.firebase.com/](https://www.firebase.com/). 71 | 72 | After you have created new application you need to make some [security rules](https://www.firebase.com/docs/security/quickstart.html) 73 | for the used data storage. Below is configuration that my demo application uses, so you can use the same within your 74 | application. 75 | 76 | ``` 77 | { 78 | "rules": { 79 | "messages": { 80 | ".write": "auth !== null", 81 | ".read": "auth !== null" 82 | }, 83 | "todos": { 84 | "$uid": { 85 | // grants write access to the owner of this user account whose uid must exactly match the key ($uid) 86 | ".write": "auth !== null && auth.uid === $uid", 87 | // grants read access to any user who is logged in with Facebook 88 | ".read": "auth !== null && auth.uid === $uid" 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | These rules ensure that 'todo' items are show only to user who made those. Also chat messages requires that user is 96 | logged in to read / write those. 97 | 98 | ## Development 99 | 100 | To start developing in the project run: 101 | 102 | ```bash 103 | gulp serve 104 | ``` 105 | 106 | Then head to `http://localhost:3002` in your browser. 107 | 108 | The `serve` tasks starts a static file server, which serves the AngularJS application, and a watch task which watches 109 | all files for changes and lints, builds and injects them into the index.html accordingly. 110 | 111 | ## Tests 112 | 113 | To run tests run: 114 | 115 | ```bash 116 | gulp test 117 | ``` 118 | 119 | **Or** first inject all test files into `karma.conf.js` with: 120 | 121 | ```bash 122 | gulp karma-conf 123 | ``` 124 | 125 | Then you're able to run Karma directly. Example: 126 | 127 | ```bash 128 | karma start --single-run 129 | ``` 130 | 131 | ## Production ready build - a.k.a. dist 132 | 133 | To make the app ready for deploy to production run: 134 | 135 | ```bash 136 | gulp dist 137 | ``` 138 | 139 | Now there's a `./dist` folder with all scripts and stylesheets concatenated and minified, also third party libraries 140 | installed with bower will be concatenated and minified into `vendors.min.js` and `vendors.min.css` respectively. 141 | 142 | ### Running production ready build 143 | 144 | To start production ready build in the project run: 145 | 146 | ```bash 147 | gulp production 148 | ``` 149 | 150 | ## Author 151 | 152 | Tarmo Leppänen 153 | 154 | ## License 155 | 156 | The MIT License (MIT) 157 | 158 | Copyright (c) 2015 Tarmo Leppänen 159 | -------------------------------------------------------------------------------- /src/app/blocks/router/routerHelper.provider.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Specify provider blocks.router module 6 | * 7 | * @namespace Providers 8 | */ 9 | angular 10 | .module('blocks.router') 11 | .provider('routerHelper', routerHelperProvider) 12 | ; 13 | 14 | /** 15 | * @desc Implementation for RouterHelperProvider to help configure the state-base ui.router 16 | * @namespace RouterHelperProvider 17 | * @memberOf Providers 18 | * @ngInject 19 | * 20 | * @param {$locationProvider} $locationProvider 21 | * @param {ng.ui.IStateProvider} $stateProvider 22 | * @param {angular.ui.IUrlRouterProvider} $urlRouterProvider 23 | */ 24 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 25 | var _this = this; 26 | 27 | // Default config for routerHelper 28 | var config = { 29 | docTitle: undefined, 30 | resolveAlways: {} 31 | }; 32 | 33 | // We want to use HTML5 mode with routing 34 | $locationProvider.html5Mode(true); 35 | 36 | // Specify configure method 37 | _this.configure = function configure(configOverride) { 38 | angular.extend(config, configOverride); 39 | }; 40 | 41 | // Getter for provider service 42 | _this.$get = routerHelper; 43 | 44 | /** 45 | * @desc routerHelper service. 46 | * @namespace RouterHelper 47 | * @memberOf Providers 48 | * @ngInject 49 | * 50 | * @param {$location} $location 51 | * @param {ng.IRootScopeService|{title: string}} $rootScope 52 | * @param {ui.router.state.$state} $state 53 | * @param {Factories.Logger} logger 54 | * @returns {{ 55 | * configureStates: Providers.RouterHelper.configureStates, 56 | * getStates: Providers.RouterHelper.getStates, 57 | * stateCounts: { 58 | * errors: number, 59 | * changes: number 60 | * } 61 | * }} 62 | * @constructor 63 | */ 64 | function routerHelper( 65 | $location, $rootScope, $state, 66 | logger 67 | ) { 68 | // Initialize used default variables 69 | var handlingStateChangeError = false; 70 | var hasOtherwise = false; 71 | var stateCounts = { 72 | errors: 0, 73 | changes: 0 74 | }; 75 | 76 | // Specify service methods 77 | var service = { 78 | configureStates: configureStates, 79 | getStates: getStates, 80 | stateCounts: stateCounts 81 | }; 82 | 83 | // Initialize service 84 | _init(); 85 | 86 | return service; 87 | 88 | //////////////////// 89 | 90 | /** 91 | * @name configureStates 92 | * @desc Implementation for configureStates method. 93 | * @memberOf Providers.RouterHelper 94 | * 95 | * @param {object[]} states 96 | * @param {string} [otherwisePath] 97 | */ 98 | function configureStates(states, otherwisePath) { 99 | // Iterate specified states, add resolves to each one and attach state to router 100 | states.forEach(stateIterator); 101 | 102 | // Set otherwise path 103 | if (otherwisePath && !hasOtherwise) { 104 | hasOtherwise = true; 105 | 106 | $urlRouterProvider.otherwise(otherwisePath); 107 | } 108 | 109 | /** 110 | * State iterator helper function. 111 | * 112 | * @param {*} state 113 | */ 114 | function stateIterator(state) { 115 | state.config.resolve = angular.extend(state.config.resolve || {}, config.resolveAlways); 116 | 117 | $stateProvider.state(state.state, state.config); 118 | } 119 | } 120 | 121 | /** 122 | * @name getStates 123 | * @desc Implementation for getStates method. 124 | * @memberOf Providers.RouterHelper 125 | */ 126 | function getStates() { 127 | return $state.get(); 128 | } 129 | 130 | //////////////////// Private functions for service 131 | 132 | /** 133 | * Service initialize method. This will activate state change error listener and updates current page title to 134 | * match with state. 135 | * 136 | * @private 137 | */ 138 | function _init() { 139 | _handleRoutingErrors(); 140 | _updateDocumentTitle(); 141 | } 142 | 143 | /** 144 | * Route cancellation: 145 | * 1) On routing error, go to the default location (/). 146 | * 2) Provide an exit clause if it tries to do it twice. 147 | * 148 | * @private 149 | */ 150 | function _handleRoutingErrors() { 151 | $rootScope.$on('$stateChangeError', onEvent); 152 | 153 | //noinspection JSUnusedLocalSymbols 154 | 155 | /** 156 | * Callback for $stateChangeError event. 157 | * 158 | * @param {object} event 159 | * @param {IState} toState 160 | * @param {object} toParams 161 | * @param {IState} fromState 162 | * @param {object} fromParams 163 | * @param {Error|string} error 164 | */ 165 | function onEvent(event, toState, toParams, fromState, fromParams, error) { 166 | // Oh noes error is already activated 167 | if (handlingStateChangeError) { 168 | return; 169 | } 170 | 171 | stateCounts.errors++; 172 | handlingStateChangeError = true; 173 | 174 | // State requires authenticated user. 175 | if (error === 'AUTH_REQUIRED') { 176 | $state.go('auth.login'); 177 | 178 | logger.error('Login required'); 179 | } else { // Otherwise show error message and redirect user to root (/) 180 | var message = _getErrorMessage(error, toState); 181 | 182 | logger.warning(message, toState); 183 | 184 | $location.path('/'); 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Method that will update current document title to match with state specification. 191 | * 192 | * @private 193 | */ 194 | function _updateDocumentTitle() { 195 | $rootScope.$on('$stateChangeSuccess', onEvent); 196 | 197 | //noinspection JSUnusedLocalSymbols 198 | 199 | /** 200 | * Callback for $stateChangeSuccess event. 201 | * 202 | * @param {object} event 203 | * @param {IState|{title: string}} toState 204 | * @param {object} toParams 205 | * @param {IState} fromState 206 | * @param {object} fromParams 207 | * @param {Error} error 208 | */ 209 | function onEvent(event, toState, toParams, fromState, fromParams, error) { 210 | stateCounts.changes++; 211 | handlingStateChangeError = false; 212 | 213 | // data bind to 214 | $rootScope.title = config.docTitle + ' ' + (toState.title || ''); 215 | } 216 | } 217 | 218 | /** 219 | * Method to determine error message that is shown to user if router error happens. 220 | * 221 | * @param {object} error 222 | * @param {IState} toState 223 | * @returns {string} 224 | * @private 225 | */ 226 | function _getErrorMessage(error, toState) { 227 | var destination = _getDestination(toState); 228 | 229 | return 'Error routing to ' + destination + '. ' + 230 | (error.data || '') + '. <br />' + (error.statusText || '') + 231 | ': ' + (error.status || '') 232 | ; 233 | } 234 | 235 | /** 236 | * Method to get toState destination name. 237 | * 238 | * @param {IState|{title: string, name: string, loadedTemplateUrl: string}} toState 239 | * @returns {*|string} 240 | * @private 241 | */ 242 | function _getDestination(toState) { 243 | return (toState && (toState.title || toState.name || toState.loadedTemplateUrl)) || 'unknown target'; 244 | } 245 | } 246 | } 247 | })(); 248 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var gulp = require('gulp'); 5 | var fs = require('fs'); 6 | var g = require('gulp-load-plugins')({lazy: false}); 7 | var ngConstant = require('gulp-ng-constant'); 8 | var noop = g.util.noop; 9 | var es = require('event-stream'); 10 | var queue = require('streamqueue'); 11 | var lazypipe = require('lazypipe'); 12 | var stylish = require('jshint-stylish'); 13 | var bower = require('./bower'); 14 | var historyApiFallback = require('connect-history-api-fallback'); 15 | var isWatching = false; 16 | 17 | var htmlminOpts = { 18 | removeComments: true, 19 | collapseWhitespace: true, 20 | removeEmptyAttributes: false, 21 | collapseBooleanAttributes: true, 22 | removeRedundantAttributes: true 23 | }; 24 | 25 | /** 26 | * JS Hint 27 | */ 28 | gulp.task('jshint', function() { 29 | return gulp.src([ 30 | './gulpfile.js', 31 | './src/app/**/*.js' 32 | ]) 33 | .pipe(g.cached('jshint')) 34 | .pipe(jshint('./.jshintrc')) 35 | .pipe(livereload()); 36 | }); 37 | 38 | /** 39 | * CSS 40 | */ 41 | gulp.task('clean-css', function() { 42 | return gulp.src('./.tmp/css').pipe(g.clean()); 43 | }); 44 | 45 | gulp.task('styles', ['clean-css'], function() { 46 | return gulp.src([ 47 | './src/app/**/*.scss', 48 | '!./src/app/**/_*.scss' 49 | ]) 50 | .pipe(g.sass()) 51 | .pipe(gulp.dest('./.tmp/css/')) 52 | .pipe(g.cached('built-css')) 53 | .pipe(livereload()); 54 | }); 55 | 56 | gulp.task('styles-dist', ['styles'], function() { 57 | return cssFiles().pipe(dist('css', bower.name)); 58 | }); 59 | 60 | gulp.task('csslint', ['styles'], function() { 61 | return cssFiles() 62 | .pipe(g.cached('csslint')) 63 | .pipe(g.csslint('./.csslintrc')) 64 | .pipe(g.csslint.reporter()) 65 | ; 66 | }); 67 | 68 | /** 69 | * Scripts 70 | */ 71 | gulp.task('scripts-dist', ['templates-dist'], function() { 72 | return appFiles().pipe(dist('js', bower.name, {ngAnnotate: true})); 73 | }); 74 | 75 | /** 76 | * Templates 77 | */ 78 | gulp.task('templates', function() { 79 | return templateFiles().pipe(buildTemplates()); 80 | }); 81 | 82 | gulp.task('templates-dist', function() { 83 | return templateFiles({min: true}).pipe(buildTemplates()); 84 | }); 85 | 86 | /** 87 | * Vendors 88 | */ 89 | gulp.task('vendors', function() { 90 | var bowerStream = g.bowerFiles(); 91 | 92 | return es.merge( 93 | bowerStream.pipe(g.filter('**/*.css')).pipe(dist('css', 'vendors')), 94 | bowerStream.pipe(g.filter('**/*.js')).pipe(dist('js', 'vendors')) 95 | ); 96 | }); 97 | 98 | /** 99 | * Index 100 | */ 101 | gulp.task('index', index); 102 | gulp.task('build-all', ['styles', 'templates'], index); 103 | 104 | function index() { 105 | var opt = {read: false}; 106 | 107 | return gulp.src('./src/app/index.html') 108 | .pipe(g.inject(g.bowerFiles(opt), {ignorePath: 'bower_components', starttag: '<!-- inject:vendor:{{ext}} -->'})) 109 | .pipe(g.inject(es.merge(appFiles(), cssFiles(opt)), {ignorePath: ['.tmp', 'src/app']})) 110 | .pipe(g.embedlr()) 111 | .pipe(gulp.dest('./.tmp/')) 112 | .pipe(livereload()) 113 | ; 114 | } 115 | 116 | /** 117 | * Assets 118 | */ 119 | gulp.task('assets', function() { 120 | return gulp.src('./src/app/assets/**') 121 | .pipe(gulp.dest('./dist/assets')) 122 | ; 123 | }); 124 | 125 | /** 126 | * Partials 127 | */ 128 | gulp.task('partials', function() { 129 | return gulp.src('./src/app/partials/**') 130 | .pipe(gulp.dest('./dist/partials')) 131 | ; 132 | }); 133 | 134 | /** 135 | * Fonts 136 | */ 137 | gulp.task('fonts', function() { 138 | return gulp.src('./bower_components/mdi/fonts/**') 139 | .pipe(gulp.dest('./dist/fonts')) 140 | ; 141 | }); 142 | 143 | /** 144 | * Dist 145 | */ 146 | gulp.task('dist', ['vendors', 'assets', 'fonts', 'styles-dist', 'scripts-dist'], function() { 147 | return gulp.src('./src/app/index.html') 148 | .pipe(g.inject(gulp.src('./dist/vendors.min.{js,css}'), { 149 | ignorePath: 'dist', 150 | starttag: '<!-- inject:vendor:{{ext}} -->' 151 | })) 152 | .pipe(g.inject(gulp.src('./dist/' + bower.name + '.min.{js,css}'), {ignorePath: 'dist'})) 153 | .pipe(g.htmlmin(htmlminOpts)) 154 | .pipe(gulp.dest('./dist/')) 155 | ; 156 | }); 157 | 158 | /** 159 | * Static file server 160 | */ 161 | gulp.task('statics', g.serve({ 162 | port: 3002, 163 | root: ['./.tmp', './src/app', './bower_components'], 164 | middleware: historyApiFallback({}) 165 | })); 166 | 167 | /** 168 | * Production file server, note remember to run 'gulp dist' first! 169 | */ 170 | gulp.task('production', g.serve({ 171 | port: 3000, 172 | root: ['./dist'], 173 | middleware: historyApiFallback({}) 174 | })); 175 | 176 | /** 177 | * Watch 178 | */ 179 | gulp.task('serve', ['config', 'watch']); 180 | 181 | gulp.task('watch', ['statics', 'default'], function() { 182 | isWatching = true; 183 | 184 | // Initiate livereload server: 185 | g.livereload({start: true}); 186 | 187 | gulp.watch('./src/app/**/*.js', ['jshint']).on('change', function(evt) { 188 | if (evt.type !== 'changed') { 189 | gulp.start('index'); 190 | } 191 | }); 192 | 193 | gulp.watch('./src/app/index.html', ['index']); 194 | gulp.watch(['./src/app/**/*.html', '!./src/app/index.html'], ['templates']); 195 | gulp.watch(['./src/app/**/*.scss'], ['csslint']).on('change', function(evt) { 196 | if (evt.type !== 'changed') { 197 | gulp.start('index'); 198 | } 199 | }); 200 | }); 201 | 202 | gulp.task('config', function() { 203 | gulp.src('./src/app/config/config.json') 204 | .pipe(ngConstant({ 205 | name: 'firebaseDemo.config', 206 | templatePath: './src/app/config/template.ejs', 207 | space: ' ' 208 | })) 209 | // Writes config.js to dist/ folder 210 | .pipe(gulp.dest('./src/app/config/')); 211 | }); 212 | 213 | /** 214 | * Default task 215 | */ 216 | gulp.task('default', ['lint', 'build-all']); 217 | 218 | /** 219 | * Lint everything 220 | */ 221 | gulp.task('lint', ['jshint', 'csslint']); 222 | 223 | /** 224 | * Test 225 | */ 226 | gulp.task('test', ['templates'], function() { 227 | return testFiles() 228 | .pipe(g.karma({ 229 | configFile: 'karma.conf.js', 230 | action: 'run' 231 | })) 232 | ; 233 | }); 234 | 235 | /** 236 | * Inject all files for tests into karma.conf.js 237 | * to be able to run `karma` without gulp. 238 | */ 239 | gulp.task('karma-conf', ['templates'], function() { 240 | return gulp.src('./karma.conf.js') 241 | .pipe(g.inject(testFiles(), { 242 | starttag: 'files: [', 243 | endtag: ']', 244 | addRootSlash: false, 245 | transform: function(filepath, file, i, length) { 246 | return ' \'' + filepath + '\'' + (i + 1 < length ? ',' : ''); 247 | } 248 | })) 249 | .pipe(gulp.dest('./')) 250 | ; 251 | }); 252 | 253 | /** 254 | * Test files 255 | */ 256 | function testFiles() { 257 | return new queue({objectMode: true}) 258 | .queue(g.bowerFiles().pipe(g.filter('**/*.js'))) 259 | .queue(gulp.src('./bower_components/angular-mocks/angular-mocks.js')) 260 | .queue(appFiles()) 261 | .queue(gulp.src('./src/app/**/*.spec.js')) 262 | .done() 263 | ; 264 | } 265 | 266 | /** 267 | * All CSS files as a stream 268 | */ 269 | function cssFiles(opt) { 270 | return gulp.src('./.tmp/css/**/*.css', opt); 271 | } 272 | 273 | /** 274 | * All AngularJS application files as a stream 275 | */ 276 | function appFiles() { 277 | var files = [ 278 | './.tmp/' + bower.name + '-templates.js', 279 | './src/app/**/*.js', 280 | '!./src/app/**/*.spec.js' 281 | ]; 282 | 283 | return gulp.src(files) 284 | .pipe(g.angularFilesort()) 285 | ; 286 | } 287 | 288 | /** 289 | * All AngularJS templates/partials as a stream 290 | */ 291 | function templateFiles(opt) { 292 | return gulp.src(['./src/app/**/*.html', '!./src/app/index.html'], opt) 293 | .pipe(opt && opt.min ? g.htmlmin(htmlminOpts) : noop()) 294 | ; 295 | } 296 | 297 | /** 298 | * Build AngularJS templates/partials 299 | */ 300 | function buildTemplates() { 301 | return lazypipe() 302 | .pipe(g.ngHtml2js, { 303 | moduleName: bower.name + '-templates', 304 | prefix: '/' + bower.name + '/', 305 | stripPrefix: '/src/app' 306 | }) 307 | .pipe(g.concat, bower.name + '-templates.js') 308 | .pipe(gulp.dest, './.tmp') 309 | .pipe(livereload)() 310 | ; 311 | } 312 | 313 | /** 314 | * Concat, rename, minify 315 | * 316 | * @param {String} ext 317 | * @param {String} name 318 | * @param {Object} opt 319 | */ 320 | function dist(ext, name, opt) { 321 | opt = opt || {}; 322 | 323 | return lazypipe() 324 | .pipe(g.concat, name + '.' + ext) 325 | .pipe(gulp.dest, './dist') 326 | .pipe(opt.ngAnnotate ? g.ngAnnotate : noop) 327 | .pipe(opt.ngAnnotate ? g.rename : noop, name + '.annotated.' + ext) 328 | .pipe(opt.ngAnnotate ? gulp.dest : noop, './dist') 329 | .pipe(ext === 'js' ? g.uglify : g.minifyCss) 330 | .pipe(g.rename, name + '.min.' + ext) 331 | .pipe(gulp.dest, './dist')() 332 | ; 333 | } 334 | 335 | /** 336 | * Livereload (or noop if not run by watch) 337 | */ 338 | function livereload() { 339 | return lazypipe() 340 | .pipe(isWatching ? g.livereload : noop)() 341 | ; 342 | } 343 | 344 | /** 345 | * Jshint with stylish reporter 346 | */ 347 | function jshint(jshintfile) { 348 | // Read JSHint settings, for some reason jshint-stylish won't work on initial load of files 349 | var jshintSettings = JSON.parse(fs.readFileSync(jshintfile, 'utf8')); 350 | 351 | return lazypipe() 352 | .pipe(g.jshint, jshintSettings) 353 | .pipe(g.jshint.reporter, stylish)() 354 | ; 355 | } 356 | --------------------------------------------------------------------------------