├── .babelrc
├── src
├── sass
│ ├── _layout.scss
│ ├── _utils.scss
│ ├── _vendors.scss
│ ├── vendors
│ │ └── _vendor.scss
│ ├── utils
│ │ └── _mixins.scss
│ ├── _base.scss
│ ├── components
│ │ ├── _contacts.scss
│ │ ├── _root.scss
│ │ ├── _auth.scss
│ │ ├── _header.scss
│ │ ├── _sidebar.scss
│ │ ├── _contact-card.scss
│ │ └── _contact.scss
│ ├── _components.scss
│ ├── base
│ │ ├── _forms.scss
│ │ ├── _scaffolding.scss
│ │ ├── _colors.scss
│ │ ├── _typography.scss
│ │ └── _buttons.scss
│ ├── style.scss
│ └── layout
│ │ └── _loading.scss
├── app
│ ├── root.html
│ ├── root.module.js
│ ├── root.component.js
│ ├── components
│ │ ├── contact
│ │ │ ├── contact-new
│ │ │ │ ├── contact-new.html
│ │ │ │ ├── contact-new.component.js
│ │ │ │ ├── contact-new.controller.js
│ │ │ │ └── contact-new.spec.js
│ │ │ ├── contact-edit
│ │ │ │ ├── contact-edit.html
│ │ │ │ ├── contact-edit.component.js
│ │ │ │ ├── contact-edit.controller.js
│ │ │ │ └── contact-edit.spec.js
│ │ │ ├── contact
│ │ │ │ ├── contact.component.js
│ │ │ │ ├── contact.controller.js
│ │ │ │ ├── contact.html
│ │ │ │ └── contact.spec.js
│ │ │ ├── contact-tag
│ │ │ │ ├── contact-tag.component.js
│ │ │ │ ├── contact-tag.html
│ │ │ │ ├── contact-tag.controller.js
│ │ │ │ └── contact-tag.spec.js
│ │ │ ├── contact.module.js
│ │ │ ├── contact-detail
│ │ │ │ ├── contact-detail.component.js
│ │ │ │ ├── contact-detail.controller.js
│ │ │ │ ├── contact-detail.spec.js
│ │ │ │ └── contact-detail.html
│ │ │ ├── contacts
│ │ │ │ ├── contacts.filter.js
│ │ │ │ ├── contacts.html
│ │ │ │ ├── contacts.controller.js
│ │ │ │ ├── contacts.component.js
│ │ │ │ └── contacts.spec.js
│ │ │ ├── length-check
│ │ │ │ ├── length-check.spec.js
│ │ │ │ └── length-check.directive.js
│ │ │ ├── contact.service.js
│ │ │ └── contacts.spec.js
│ │ ├── auth
│ │ │ ├── auth-form
│ │ │ │ ├── auth-form.component.js
│ │ │ │ ├── auth-form.controller.js
│ │ │ │ ├── auth-form.html
│ │ │ │ └── auth-form.spec.js
│ │ │ ├── login
│ │ │ │ ├── login.html
│ │ │ │ ├── login.component.js
│ │ │ │ ├── login.controller.js
│ │ │ │ └── login.spec.js
│ │ │ ├── register
│ │ │ │ ├── register.html
│ │ │ │ ├── register.component.js
│ │ │ │ ├── register.controller.js
│ │ │ │ └── register.spec.js
│ │ │ ├── auth.module.js
│ │ │ ├── auth.service.js
│ │ │ └── auth.spec.js
│ │ └── components.module.js
│ └── common
│ │ ├── app.html
│ │ ├── app-sidebar.component.js
│ │ ├── app-nav.component.js
│ │ ├── app.module.js
│ │ ├── app-nav.html
│ │ ├── app-sidebar.html
│ │ ├── app.component.js
│ │ ├── app.controller.js
│ │ ├── app-sidebar.controller.js
│ │ └── app.spec.js
├── img
│ ├── logo.png
│ └── favicon.ico
├── fonts
│ ├── MaterialIcons-Regular.eot
│ ├── MaterialIcons-Regular.ttf
│ ├── MaterialIcons-Regular.woff
│ └── MaterialIcons-Regular.woff2
└── index.html
├── .firebaserc
├── docs
├── app
│ ├── app.module.js
│ ├── api.js
│ ├── guide.js
│ └── app.config.js
├── build
│ ├── partials
│ │ ├── api.html
│ │ ├── guide.html
│ │ ├── guide
│ │ │ └── howTo.html
│ │ └── api
│ │ │ ├── components.html
│ │ │ ├── components.auth
│ │ │ ├── service.html
│ │ │ └── service
│ │ │ │ └── AuthService.html
│ │ │ ├── components.contact
│ │ │ ├── service.html
│ │ │ ├── directive.html
│ │ │ ├── type.html
│ │ │ ├── service
│ │ │ │ └── ContactService.html
│ │ │ ├── directive
│ │ │ │ └── lengthCheck.html
│ │ │ └── type
│ │ │ │ └── ContactEditController.html
│ │ │ ├── common
│ │ │ ├── directive.html
│ │ │ ├── type
│ │ │ │ ├── AppSidebarController.html
│ │ │ │ └── AppController.html
│ │ │ ├── type.html
│ │ │ └── directive
│ │ │ │ └── app.html
│ │ │ ├── components.auth.html
│ │ │ ├── common.html
│ │ │ └── components.contact.html
│ ├── src
│ │ ├── app.module.js
│ │ ├── api.js
│ │ ├── guide.js
│ │ ├── guide-data.js
│ │ ├── api-data.js
│ │ └── app.config.js
│ └── index.html
├── config
│ ├── templates
│ │ ├── content.template.html
│ │ ├── constant-data.template.js
│ │ ├── module.template.html
│ │ └── indexPage.template.html
│ ├── processors
│ │ ├── index-page.js
│ │ ├── guide-data.js
│ │ └── api-data.js
│ └── index.js
└── content
│ ├── api
│ └── index.md
│ └── guide
│ ├── howTo.md
│ └── index.md
├── .gitignore
├── database.rules.json
├── firebase.json
├── .editorconfig
├── package.json
├── README.md
├── mocks
└── firebase.mock.js
├── karma.conf.js
└── gulpfile.babel.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
4 |
--------------------------------------------------------------------------------
/src/sass/_layout.scss:
--------------------------------------------------------------------------------
1 | @import "layout/loading";
2 |
--------------------------------------------------------------------------------
/src/sass/_utils.scss:
--------------------------------------------------------------------------------
1 | @import "utils/mixins.scss";
2 |
--------------------------------------------------------------------------------
/src/sass/_vendors.scss:
--------------------------------------------------------------------------------
1 | @import "vendors/vendor";
2 |
--------------------------------------------------------------------------------
/src/sass/vendors/_vendor.scss:
--------------------------------------------------------------------------------
1 | // Vendor placeholder file
2 |
--------------------------------------------------------------------------------
/src/app/root.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "contacts-manager-e486f"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/img/logo.png
--------------------------------------------------------------------------------
/docs/app/app.module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs', [
5 | 'ui.router'
6 | ]);
7 |
--------------------------------------------------------------------------------
/docs/build/partials/api.html:
--------------------------------------------------------------------------------
1 |
2 | API
3 | Select a link in side menu.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/img/favicon.ico
--------------------------------------------------------------------------------
/docs/build/src/app.module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs', [
5 | 'ui.router'
6 | ]);
7 |
--------------------------------------------------------------------------------
/docs/config/templates/content.template.html:
--------------------------------------------------------------------------------
1 | {% block content %}
2 | {$ doc.description | marked $}
3 | {% endblock %}
4 |
--------------------------------------------------------------------------------
/src/app/root.module.js:
--------------------------------------------------------------------------------
1 | angular
2 | .module('root', [
3 | 'common',
4 | 'components',
5 | 'templates'
6 | ]);
7 |
--------------------------------------------------------------------------------
/docs/build/partials/guide.html:
--------------------------------------------------------------------------------
1 |
2 | Guide
3 | This would be the base page for your guide section
4 |
5 |
6 |
--------------------------------------------------------------------------------
/docs/build/partials/guide/howTo.html:
--------------------------------------------------------------------------------
1 |
2 | How to
3 | Describe how to write your documentation
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/fonts/MaterialIcons-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.eot
--------------------------------------------------------------------------------
/src/fonts/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/docs/config/templates/constant-data.template.js:
--------------------------------------------------------------------------------
1 | angular
2 | .module('docs')
3 | .constant('{$ doc.name $}', {$ doc.items | json $});
4 |
--------------------------------------------------------------------------------
/src/fonts/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/src/fonts/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.woff2
--------------------------------------------------------------------------------
/src/app/root.component.js:
--------------------------------------------------------------------------------
1 | var root = {
2 | templateUrl: './root.html'
3 | };
4 |
5 | angular
6 | .module('root')
7 | .component('root', root);
8 |
--------------------------------------------------------------------------------
/src/sass/utils/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixin placeholder file
2 | // Here you should follow the same pattern as the other folders and separate your files
3 |
--------------------------------------------------------------------------------
/docs/content/api/index.md:
--------------------------------------------------------------------------------
1 | @ngdoc content
2 | @module api
3 | @name Overview
4 |
5 | @description
6 |
7 | # API
8 |
9 | Select a link in side menu.
10 |
--------------------------------------------------------------------------------
/docs/content/guide/howTo.md:
--------------------------------------------------------------------------------
1 | @ngdoc content
2 | @module guide
3 | @name How To
4 | @description
5 |
6 | # How to
7 | Describe how to write your documentation
8 |
--------------------------------------------------------------------------------
/src/sass/_base.scss:
--------------------------------------------------------------------------------
1 | @import "base/colors";
2 | @import "base/scaffolding";
3 | @import "base/forms";
4 | @import "base/buttons";
5 | @import "base/typography";
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .DS_Store
4 | .tmp
5 | .sass-cache
6 | *.sublime-*
7 | .jekyll-metadata
8 | .idea
9 | npm-debug.log
10 | templates.js
11 |
--------------------------------------------------------------------------------
/docs/content/guide/index.md:
--------------------------------------------------------------------------------
1 | @ngdoc content
2 | @module guide
3 | @name Overview
4 |
5 | @description
6 |
7 | # Guide
8 | This would be the base page for your guide section
9 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-new/contact-new.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/common/app.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-edit/contact-edit.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
--------------------------------------------------------------------------------
/src/sass/components/_contacts.scss:
--------------------------------------------------------------------------------
1 | .contacts__empty {
2 | text-align: center;
3 | padding: 30px 0;
4 | font-size: 24px;
5 | }
6 | .contacts__empty .material-icons {
7 | position: relative;
8 | top: 2px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/common/app-sidebar.component.js:
--------------------------------------------------------------------------------
1 | var appSidebar = {
2 | templateUrl: './app-sidebar.html',
3 | controller: 'AppSidebarController'
4 | };
5 |
6 | angular
7 | .module('common')
8 | .component('appSidebar', appSidebar);
9 |
--------------------------------------------------------------------------------
/database.rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | ".read": true,
4 | "contacts": {
5 | "$uid": {
6 | ".read": "$uid === auth.uid",
7 | ".write": "$uid === auth.uid"
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/common/app-nav.component.js:
--------------------------------------------------------------------------------
1 | var appNav = {
2 | bindings: {
3 | user: '<',
4 | onLogout: '&'
5 | },
6 | templateUrl: './app-nav.html'
7 | };
8 |
9 | angular
10 | .module('common')
11 | .component('appNav', appNav);
12 |
--------------------------------------------------------------------------------
/src/sass/_components.scss:
--------------------------------------------------------------------------------
1 | @import "components/root";
2 | @import "components/header";
3 | @import "components/sidebar";
4 | @import "components/auth";
5 | @import "components/contacts";
6 | @import "components/contact";
7 | @import "components/contact-card";
8 |
--------------------------------------------------------------------------------
/src/sass/components/_root.scss:
--------------------------------------------------------------------------------
1 | root {
2 | display: block;
3 | }
4 | root .loader {
5 | position: absolute;
6 | top: 40%;
7 | left: 45%;
8 | font-size: 24px;
9 | display: block;
10 | text-align: center;
11 | font-weight: 900;
12 | }
13 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": {
3 | "rules": "database.rules.json"
4 | },
5 | "hosting": {
6 | "public": "dist",
7 | "rewrites": [
8 | {
9 | "source": "**",
10 | "destination": "/index.html"
11 | }
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/docs/app/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .controller('ApiController', ApiController);
6 |
7 | function ApiController(API_DATA) {
8 |
9 | var ctrl = this;
10 | ctrl.allPages = API_DATA;
11 |
12 | }
13 |
14 | ApiController.$inject = ["API_DATA"];
15 |
--------------------------------------------------------------------------------
/docs/build/src/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .controller('ApiController', ApiController);
6 |
7 | function ApiController(API_DATA) {
8 |
9 | var ctrl = this;
10 | ctrl.allPages = API_DATA;
11 |
12 | }
13 |
14 | ApiController.$inject = ["API_DATA"];
15 |
--------------------------------------------------------------------------------
/docs/app/guide.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .controller('GuideController', GuideController);
6 |
7 | function GuideController(GUIDE_DATA) {
8 |
9 | var ctrl = this;
10 | ctrl.allPages = GUIDE_DATA;
11 |
12 | }
13 |
14 | GuideController.$inject = ["GUIDE_DATA"];
15 |
--------------------------------------------------------------------------------
/docs/build/src/guide.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .controller('GuideController', GuideController);
6 |
7 | function GuideController(GUIDE_DATA) {
8 |
9 | var ctrl = this;
10 | ctrl.allPages = GUIDE_DATA;
11 |
12 | }
13 |
14 | GuideController.$inject = ["GUIDE_DATA"];
15 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact/contact.component.js:
--------------------------------------------------------------------------------
1 | var contact = {
2 | bindings: {
3 | contact: '<',
4 | onSelect: '&'
5 | },
6 | templateUrl: './contact.html',
7 | controller: 'ContactController'
8 | };
9 |
10 | angular
11 | .module('components.contact')
12 | .component('contact', contact);
13 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-tag/contact-tag.component.js:
--------------------------------------------------------------------------------
1 | var contactTag = {
2 | bindings: {
3 | tag: '<',
4 | onChange: '&'
5 | },
6 | templateUrl: './contact-tag.html',
7 | controller: 'ContactTagController'
8 | };
9 |
10 | angular
11 | .module('components.contact')
12 | .component('contactTag', contactTag);
13 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth-form/auth-form.component.js:
--------------------------------------------------------------------------------
1 | var authForm = {
2 | bindings: {
3 | user: '<',
4 | button: '@',
5 | message: '@',
6 | onSubmit: '&'
7 | },
8 | templateUrl: './auth-form.html',
9 | controller: 'AuthFormController'
10 | };
11 |
12 | angular
13 | .module('components.auth')
14 | .component('authForm', authForm);
15 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact.module.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | *
4 | * @ngdoc module
5 | * @name components.contact
6 | *
7 | * @requires ui.router
8 | *
9 | * @description
10 | *
11 | * This is the contact module. It includes all of our components for the contact feature.
12 | *
13 | **/
14 | angular
15 | .module('components.contact', [
16 | 'ui.router'
17 | ]);
18 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact/contact.controller.js:
--------------------------------------------------------------------------------
1 | function ContactController() {
2 | var ctrl = this;
3 | ctrl.selectContact = function () {
4 | ctrl.onSelect({
5 | $event: {
6 | contactId: ctrl.contact.$id
7 | }
8 | });
9 | };
10 | }
11 |
12 | angular
13 | .module('components.contact')
14 | .controller('ContactController', ContactController);
15 |
--------------------------------------------------------------------------------
/docs/build/src/guide-data.js:
--------------------------------------------------------------------------------
1 | angular
2 | .module('docs')
3 | .constant('GUIDE_DATA', [
4 | {
5 | "name": "Overview",
6 | "type": "content",
7 | "outputPath": "partials/guide.html",
8 | "url": "guide"
9 | },
10 | {
11 | "name": "How To",
12 | "type": "content",
13 | "outputPath": "partials/guide/howTo.html",
14 | "url": "guide/howTo"
15 | }
16 | ]);
17 |
--------------------------------------------------------------------------------
/src/app/components/auth/login/login.html:
--------------------------------------------------------------------------------
1 |
10 |
15 |
--------------------------------------------------------------------------------
/src/app/components/auth/register/register.html:
--------------------------------------------------------------------------------
1 |
10 |
15 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-detail/contact-detail.component.js:
--------------------------------------------------------------------------------
1 | var contactDetail = {
2 | bindings: {
3 | contact: '<',
4 | onSave: '&',
5 | onUpdate: '&',
6 | onDelete: '&'
7 | },
8 | templateUrl: './contact-detail.html',
9 | controller: 'ContactDetailController'
10 | };
11 |
12 | angular
13 | .module('components.contact')
14 | .component('contactDetail', contactDetail);
15 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | components
4 |
5 |
6 | This is the components module. It includes all of our components.
7 |
8 |
9 | components
10 |
11 | Requires components.contact,components.auth
12 |
13 |
14 |
Module Components
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/components/components.module.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | *
4 | * @ngdoc module
5 | * @name components
6 | *
7 | * @requires components.contact
8 | * @requires components.auth
9 | *
10 | * @description
11 | *
12 | * This is the components module. It includes all of our components.
13 | *
14 | **/
15 |
16 | angular
17 | .module('components', [
18 | 'components.contact',
19 | 'components.auth'
20 | ]);
21 |
--------------------------------------------------------------------------------
/src/app/components/contact/contacts/contacts.filter.js:
--------------------------------------------------------------------------------
1 | function contactsFilter() {
2 | return function (collection, params) {
3 | return collection.filter(function (item) {
4 | return item.tag === (
5 | params.filter === 'none' ? item.tag : params.filter
6 | );
7 | });
8 | };
9 | }
10 |
11 | angular
12 | .module('components.contact')
13 | .filter('contactsFilter', contactsFilter);
14 |
--------------------------------------------------------------------------------
/src/app/components/auth/register/register.component.js:
--------------------------------------------------------------------------------
1 | var register = {
2 | templateUrl: './register.html',
3 | controller: 'RegisterController'
4 | };
5 |
6 | angular
7 | .module('components.auth')
8 | .component('register', register)
9 | .config(function ($stateProvider) {
10 | $stateProvider
11 | .state('auth.register', {
12 | url: '/register',
13 | component: 'register'
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-new/contact-new.component.js:
--------------------------------------------------------------------------------
1 | var contactNew = {
2 | templateUrl: './contact-new.html',
3 | controller: 'ContactNewController'
4 | };
5 |
6 | angular
7 | .module('components.contact')
8 | .component('contactNew', contactNew)
9 | .config(function ($stateProvider) {
10 | $stateProvider
11 | .state('new', {
12 | parent: 'app',
13 | url: '/new',
14 | component: 'contactNew'
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Contacts Manager
6 |
7 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/components/contact/contacts/contacts.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth-form/auth-form.controller.js:
--------------------------------------------------------------------------------
1 | function AuthFormController() {
2 | var ctrl = this;
3 | ctrl.$onChanges = function (changes) {
4 | if (changes.user) {
5 | ctrl.user = angular.copy(ctrl.user);
6 | }
7 | };
8 | ctrl.submitForm = function () {
9 | ctrl.onSubmit({
10 | $event: {
11 | user: ctrl.user
12 | }
13 | });
14 | };
15 | }
16 |
17 | angular
18 | .module('components.auth')
19 | .controller('AuthFormController', AuthFormController);
20 |
--------------------------------------------------------------------------------
/src/app/components/contact/contacts/contacts.controller.js:
--------------------------------------------------------------------------------
1 | function ContactsController($filter, $state) {
2 | var ctrl = this;
3 |
4 | ctrl.$onInit = function() {
5 | ctrl.filteredContacts = $filter('contactsFilter')(ctrl.contacts, ctrl.filter);
6 | };
7 |
8 | ctrl.goToContact = function (event) {
9 | $state.go('contact', {
10 | id: event.contactId
11 | });
12 | };
13 | }
14 |
15 | angular
16 | .module('components.contact')
17 | .controller('ContactsController', ContactsController);
18 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-tag/contact-tag.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/docs/config/processors/index-page.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function indexPageProcessor() {
4 | return {
5 | $runAfter: ['adding-extra-docs'],
6 | $runBefore: ['extra-docs-added'],
7 | $process: process
8 | };
9 |
10 | function process(docs) {
11 |
12 | docs.push({
13 | docType: 'indexPage',
14 | template: 'indexPage.template.html',
15 | outputPath: 'index.html',
16 | path: 'index.html',
17 | id: 'index'
18 | });
19 |
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/src/app/common/app.module.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | *
4 | * @ngdoc module
5 | * @name common
6 | *
7 | * @requires ui.router
8 | * @requires angular-loading-bar
9 | *
10 | * @description
11 | *
12 | * This is the common module. It includes a run method that setups the loading bar.
13 | *
14 | **/
15 | angular
16 | .module('common', [
17 | 'ui.router',
18 | 'angular-loading-bar'
19 | ])
20 | .run(function ($transitions, cfpLoadingBar) {
21 | $transitions.onStart({}, cfpLoadingBar.start);
22 | $transitions.onSuccess({}, cfpLoadingBar.complete);
23 | });
24 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.auth/service.html:
--------------------------------------------------------------------------------
1 |
2 | Service components in components.auth
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | AuthService
16 | Provides HTTP methods for our firebase authentification.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact/contact.html:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/service.html:
--------------------------------------------------------------------------------
1 |
2 | Service components in components.contact
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | ContactService
16 | Provides HTTP methods for our firebase connection.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/components/auth/login/login.component.js:
--------------------------------------------------------------------------------
1 | var login = {
2 | templateUrl: './login.html',
3 | controller: 'LoginController'
4 | };
5 |
6 | angular
7 | .module('components.auth')
8 | .component('login', login)
9 | .config(function ($stateProvider, $urlRouterProvider) {
10 | $stateProvider
11 | .state('auth', {
12 | redirectTo: 'auth.login',
13 | url: '/auth',
14 | template: '
'
15 | })
16 | .state('auth.login', {
17 | url: '/login',
18 | component: 'login'
19 | });
20 | $urlRouterProvider.otherwise('/auth/login');
21 | });
22 |
--------------------------------------------------------------------------------
/src/app/components/auth/login/login.controller.js:
--------------------------------------------------------------------------------
1 | function LoginController(AuthService, $state) {
2 | var ctrl = this;
3 | ctrl.$onInit = function () {
4 | ctrl.error = null;
5 | ctrl.user = {
6 | email: '',
7 | password: ''
8 | };
9 | };
10 | ctrl.loginUser = function (event) {
11 | return AuthService
12 | .login(event.user)
13 | .then(function () {
14 | $state.go('app');
15 | }, function (reason) {
16 | ctrl.error = reason.message;
17 | });
18 | };
19 | }
20 |
21 | angular
22 | .module('components.auth')
23 | .controller('LoginController', LoginController);
24 |
--------------------------------------------------------------------------------
/src/app/components/auth/register/register.controller.js:
--------------------------------------------------------------------------------
1 | function RegisterController(AuthService, $state) {
2 | var ctrl = this;
3 | ctrl.$onInit = function () {
4 | ctrl.error = null;
5 | ctrl.user = {
6 | email: '',
7 | password: ''
8 | };
9 | };
10 | ctrl.createUser = function (event) {
11 | return AuthService
12 | .register(event.user)
13 | .then(function () {
14 | $state.go('app');
15 | }, function (reason) {
16 | ctrl.error = reason.message;
17 | });
18 | };
19 | }
20 |
21 | angular
22 | .module('components.auth')
23 | .controller('RegisterController', RegisterController);
24 |
--------------------------------------------------------------------------------
/src/sass/base/_forms.scss:
--------------------------------------------------------------------------------
1 | input {
2 | font-size: 16px;
3 | padding: 10px 15px;
4 | border: 1px solid rgba(0, 0, 0, 0.1);
5 | border-radius: 3px;
6 |
7 | &:hover {
8 | border-color: colors(button);
9 | }
10 | }
11 |
12 | input:focus {
13 | border-color: colors(button, hover);
14 | outline: none;
15 | }
16 |
17 |
18 |
19 |
20 | .dynamic-input {
21 | border-color: transparent;
22 | }
23 |
24 | .dynamic-input--no-contents {
25 | border-color: rgba(0, 0, 0, 0.1);
26 | }
27 |
28 | .dynamic-input:hover {
29 | border-color: rgba(0, 0, 0, 0.1);
30 | }
31 |
32 | .dynamic-input:focus {
33 | border-color: rgba(0, 133, 215, 1);
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-tag/contact-tag.controller.js:
--------------------------------------------------------------------------------
1 | function ContactTagController() {
2 | var ctrl = this;
3 | ctrl.$onInit = function () {
4 | ctrl.tags = [
5 | 'friends', 'family', 'acquaintances', 'following'
6 | ];
7 | };
8 | ctrl.$onChanges = function (changes) {
9 | if (changes.tag) {
10 | ctrl.tag = angular.copy(ctrl.tag);
11 | }
12 | };
13 | ctrl.updateTag = function (tag) {
14 | ctrl.onChange({
15 | $event: {
16 | tag: tag
17 | }
18 | });
19 | };
20 | }
21 |
22 | angular
23 | .module('components.contact')
24 | .controller('ContactTagController', ContactTagController);
25 |
--------------------------------------------------------------------------------
/src/sass/components/_auth.scss:
--------------------------------------------------------------------------------
1 | .auth {
2 | background: #fff;
3 | border: 1px solid rgba(0, 0, 0, 0.1);
4 | box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
5 | padding: 20px 30px;
6 | width: 600px;
7 | margin: 50px auto 0;
8 | }
9 |
10 | .auth__info {
11 | text-align: center;
12 | margin-top: 20px;
13 | }
14 |
15 | .auth__info a {
16 | font-size: 12px;
17 | }
18 |
19 | .auth__info a:hover {
20 | text-decoration: underline;
21 | }
22 |
23 | .auth h1 {
24 | margin: 0 0 10px;
25 | }
26 |
27 | .auth label {
28 | margin-bottom: 10px;
29 | display: block;
30 | }
31 |
32 | .auth input {
33 | width: 100%;
34 | }
35 |
36 | .auth-button {
37 | text-align: center;
38 | }
39 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common/directive.html:
--------------------------------------------------------------------------------
1 |
2 | Directive components in common
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | app
16 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
17 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/common/app-nav.html:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/src/app/common/app-sidebar.html:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/src/sass/base/_scaffolding.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | -webkit-box-sizing: border-box;
4 | -moz-box-sizing: border-box;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | body {
10 | font: 300 14px/1.4 'Helvetica Neue', Helvetica, Arial, sans-serif;
11 | color: colors(scaffold);
12 | background: #FFFFFF;
13 | }
14 | a {
15 | color: colors(scaffold);
16 | text-decoration: none;
17 | }
18 | ul {
19 | list-style: none;
20 | }
21 | img {
22 | max-width: 100%;
23 | }
24 |
25 |
26 |
27 |
28 | .app {
29 | margin-left: 250px;
30 | }
31 |
32 |
33 |
34 | .message {
35 | &:not(:first-child) {
36 | margin-top: 15px;
37 | }
38 | &.error {
39 | color: colors(scaffold, error);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/sass/style.scss:
--------------------------------------------------------------------------------
1 |
2 | // Utils
3 | // Contains all mixins, functions and helpers
4 | @import "utils";
5 |
6 | // Base
7 | // Contains all the global styles for the application as well as the resets, typography, colors, buttons, etc...
8 | // You should also define a _variables.scss to define all app variables
9 | @import "base";
10 |
11 |
12 | // Components
13 | // Contains each isolated component. Should ideally mimic the AngularJS component structure
14 | @import "components";
15 |
16 |
17 | // Layout
18 | // Contains styling elements for the larger 'core' modules like loading bars / indicators, wrappers, containers, etc...
19 | @import "layout";
20 |
21 |
22 | // Vendors
23 | // Contains all the 3rd party styles
24 | @import "vendors";
25 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/directive.html:
--------------------------------------------------------------------------------
1 |
2 | Directive components in components.contact
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | lengthCheck
16 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
17 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-edit/contact-edit.component.js:
--------------------------------------------------------------------------------
1 | var contactEdit = {
2 | bindings: {
3 | contact: '<'
4 | },
5 | templateUrl: './contact-edit.html',
6 | controller: 'ContactEditController'
7 | };
8 |
9 | angular
10 | .module('components.contact')
11 | .component('contactEdit', contactEdit)
12 | .config(function ($stateProvider) {
13 | $stateProvider
14 | .state('contact', {
15 | parent: 'app',
16 | url: '/contact/:id',
17 | component: 'contactEdit',
18 | resolve: {
19 | contact: function ($transition$, ContactService) {
20 | var key = $transition$.params().id;
21 | return ContactService.getContactById(key).$loaded();
22 | }
23 | }
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/type.html:
--------------------------------------------------------------------------------
1 |
2 | Type components in components.contact
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | ContactEditController
16 | Lorem Ipsum 1
17 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth-form/auth-form.html:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-new/contact-new.controller.js:
--------------------------------------------------------------------------------
1 | function ContactNewController(ContactService, $state) {
2 | var ctrl = this;
3 | ctrl.$onInit = function () {
4 | ctrl.contact = {
5 | name: '',
6 | email: '',
7 | job: '',
8 | location: '',
9 | social: {
10 | facebook: '',
11 | github: '',
12 | twitter: '',
13 | linkedin: ''
14 | },
15 | tag: 'none'
16 | };
17 | };
18 | ctrl.createNewContact = function (event) {
19 | return ContactService
20 | .createNewContact(event.contact)
21 | .then(function (contact) {
22 | $state.go('contact', {
23 | id: contact.key
24 | });
25 | });
26 | };
27 | }
28 |
29 | angular
30 | .module('components.contact')
31 | .controller('ContactNewController', ContactNewController);
32 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-detail/contact-detail.controller.js:
--------------------------------------------------------------------------------
1 | function ContactDetailController() {
2 | var ctrl = this;
3 | ctrl.$onInit = function () {
4 | ctrl.isNewContact = !ctrl.contact.$id;
5 | };
6 | ctrl.saveContact = function () {
7 | ctrl.onSave({
8 | $event: {
9 | contact: ctrl.contact
10 | }
11 | });
12 | };
13 | ctrl.updateContact = function () {
14 | ctrl.onUpdate({
15 | $event: {
16 | contact: ctrl.contact
17 | }
18 | });
19 | };
20 | ctrl.deleteContact = function () {
21 | ctrl.onDelete({
22 | $event: {
23 | contact: ctrl.contact
24 | }
25 | });
26 | };
27 | ctrl.tagChange = function (event) {
28 | ctrl.contact.tag = event.tag;
29 | ctrl.updateContact();
30 | }
31 | }
32 |
33 | angular
34 | .module('components.contact')
35 | .controller('ContactDetailController', ContactDetailController);
36 |
--------------------------------------------------------------------------------
/src/app/components/contact/contacts/contacts.component.js:
--------------------------------------------------------------------------------
1 | var contacts = {
2 | bindings: {
3 | contacts: '<',
4 | filter: '<'
5 | },
6 | templateUrl: './contacts.html',
7 | controller: 'ContactsController'
8 | };
9 |
10 | angular
11 | .module('components.contact')
12 | .component('contacts', contacts)
13 | .config(function ($stateProvider) {
14 | $stateProvider
15 | .state('contacts', {
16 | parent: 'app',
17 | url: '/contacts?filter',
18 | component: 'contacts',
19 | params: {
20 | filter: {
21 | value: 'none'
22 | }
23 | },
24 | resolve: {
25 | contacts: function (ContactService) {
26 | return ContactService.getContactList().$loaded();
27 | },
28 | filter: function ($transition$) {
29 | return $transition$.params();
30 | }
31 | }
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth-form/auth-form.spec.js:
--------------------------------------------------------------------------------
1 | describe('Auth', function () {
2 | beforeEach(module('components.auth'));
3 |
4 | describe('AuthFormController', function () {
5 | var $componentController,
6 | controller,
7 | mockUser = { $id: 1 },
8 | mockSubmit = angular.noop;
9 |
10 | beforeEach(inject(function ($injector) {
11 | $componentController = $injector.get('$componentController');
12 |
13 | controller = $componentController('authForm',
14 | { $scope: {} },
15 | { user: mockUser, button: '', message: '', onSubmit: mockSubmit }
16 | );
17 | }));
18 |
19 | it('should call onSelect with the correct payload', function () {
20 | var payload = { $event: { user: mockUser } };
21 |
22 | spyOn(controller, 'onSubmit');
23 | controller.submitForm();
24 | expect(controller.onSubmit).toHaveBeenCalledWith(payload);
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/sass/base/_colors.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | // Purpose: These maps are meant to group color variables together and to help simplify the variable names when calling them.
3 |
4 | $brandColors: (
5 | scaffold: (
6 | base: #5c5c5c,
7 | error: #B72B2C,
8 | newContact: #50D749,
9 | logout: #E32E30
10 | ),
11 | button: (
12 | base: rgba(0, 133, 215, 0.6),
13 | shadow: rgba(0, 133, 215, 1),
14 | hover: #0085d7,
15 | hoverShadow: darken(#0085d7, 8%)
16 | ),
17 | buttonDelete: (
18 | base: rgba(227, 46, 48, 0.6),
19 | shadow: rgba(227, 46, 48, 1),
20 | hover: #e32e30,
21 | hoverShadow: darken(#e32e30, 10%)
22 | ),
23 | pills: (
24 | base: #008FED,
25 | family: #FF387E,
26 | acquaintances: #B5D158,
27 | following: #DB5A3D
28 | )
29 | );
30 |
31 | @function colors($color, $tone: "base") {
32 | @return map-get(map-get($brandColors, $color), $tone);
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/common/app.component.js:
--------------------------------------------------------------------------------
1 | var app = {
2 | templateUrl: './app.html',
3 | controller: 'AppController'
4 | };
5 |
6 | /**
7 | * @ngdoc directive
8 | * @name app
9 | * @module common
10 | *
11 | * @description
12 | *
13 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
14 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
15 | *
16 | * @usage
17 | *
18 | * ### How to use
19 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
20 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
21 | **/
22 | angular
23 | .module('common')
24 | .component('app', app)
25 | .config(function ($stateProvider) {
26 | $stateProvider
27 | .state('app', {
28 | redirectTo: 'contacts',
29 | url: '/app',
30 | data: {
31 | requiredAuth: true
32 | },
33 | component: 'app'
34 | })
35 | });
36 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact/contact.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact'));
3 |
4 | describe('Controller', function () {
5 | var $componentController,
6 | controller,
7 | mockContact = { $id: 1 },
8 | mockSelect = angular.noop;
9 |
10 | beforeEach(inject(function ($injector) {
11 | $componentController = $injector.get('$componentController');
12 | controller = $componentController('contact',
13 | { $scope: {} },
14 | { contact: mockContact, onSelect: mockSelect }
15 | );
16 | }));
17 |
18 | it('should bind to the correct contact', function () {
19 | expect(controller.contact.$id).toEqual(mockContact.$id);
20 | });
21 |
22 | it('should call onSelect with the correct payload', function () {
23 | var payload = { $event: { contactId: mockContact.$id } };
24 |
25 | spyOn(controller, 'onSelect');
26 | controller.selectContact();
27 | expect(controller.onSelect).toHaveBeenCalledWith(payload);
28 | });
29 | });
30 | });
--------------------------------------------------------------------------------
/src/sass/components/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | height: 60px;
3 | }
4 | .header__fixed {
5 | background: #fff;
6 | height: inherit;
7 | position: fixed;
8 | top: 0;
9 | left: 250px;
10 | right: 0;
11 | border-bottom: 1px solid #E9EEF3;
12 | padding: 13px 20px;
13 | font-size: 16px;
14 | }
15 | .header__brand {
16 | font-size: 24px;
17 | float: left;
18 | }
19 | .header__logout {
20 | float: right;
21 | }
22 | .header__logout a {
23 | display: inline-block;
24 | color: colors(scaffold, logout);
25 | padding: 5px 10px;
26 | margin-left: 10px;
27 | &:hover {
28 | color: darken(colors(scaffold, logout), 15%);
29 | }
30 | }
31 | .header__button {
32 | font-size: 16px;
33 | }
34 | .header__button .material-icons {
35 | font-size: inherit;
36 | position: relative;
37 | top: 2px;
38 | margin-right: 3px;
39 | }
40 | .header__button--new-contact {
41 | color: colors(scaffold, newContact);
42 | margin-left: 30px;
43 | position: relative;
44 | top: -2px;
45 | &:hover {
46 | color: darken(colors(scaffold, newContact), 15%);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/common/app.controller.js:
--------------------------------------------------------------------------------
1 | function AppController(AuthService, $state) {
2 | var ctrl = this;
3 | ctrl.user = AuthService.getUser();
4 |
5 | /**
6 | * @ngdoc method
7 | * @name AppController#logout
8 | *
9 | * @description Logout :)
10 | */
11 | ctrl.logout = function () {
12 | AuthService.logout().then(function () {
13 | $state.go('auth.login');
14 | });
15 | };
16 | }
17 |
18 | /**
19 | * @ngdoc type
20 | * @module common
21 | * @name AppController
22 | *
23 | * @description
24 | *
25 | * ## Lorem Ipsum 1
26 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
27 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
28 | *
29 | * ## Lorem Ipsum 2
30 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
31 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
32 | */
33 | angular
34 | .module('common')
35 | .controller('AppController', AppController);
36 |
--------------------------------------------------------------------------------
/docs/config/processors/guide-data.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash');
3 |
4 |
5 | function buildDocData(doc) {
6 | return _.assign({
7 | name: doc.name,
8 | type: doc.docType,
9 | outputPath: doc.outputPath,
10 | url: doc.path,
11 | });
12 | }
13 |
14 | module.exports = function guidePagesProcessor(moduleMap) {
15 | return {
16 | $runAfter: ['paths-computed'],
17 | $runBefore: ['rendering-docs'],
18 | $process: process
19 | };
20 |
21 | function process(docs) {
22 |
23 | var guides = _(docs).filter(function(doc) {
24 | return doc.docType == 'content' && doc.module == 'guide';
25 | })
26 |
27 | .sortBy(function(page) {
28 | return page.sortOrder || page.path;
29 | })
30 |
31 | .map(buildDocData)
32 |
33 | .value();
34 |
35 | docs.push({
36 | name: 'GUIDE_DATA',
37 | template: 'constant-data.template.js',
38 | outputPath: 'src/guide-data.js',
39 | items: guides
40 | });
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.auth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | components.auth
4 |
5 |
6 | This is the auth module. It includes our auth components
7 |
8 |
9 | componentsAuth
10 |
11 | Requires ui.router,firebase
12 |
13 |
14 |
Module Components
15 |
16 |
17 |
Service
18 |
19 |
20 | Name
21 | Description
22 |
23 |
24 |
25 | AuthService
26 | Provides HTTP methods for our firebase authentification.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/sass/components/_sidebar.scss:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | background: #F7FAFC;
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | bottom: 0;
7 | width: 250px;
8 | border-right: 1px solid #DEE8EF;
9 | }
10 | .sidebar__logo {
11 | margin-top: 15px;
12 | }
13 | .sidebar__logo a {
14 | display: block;
15 | width: 160px;
16 | margin: 0 auto;
17 | }
18 | .sidebar__list {
19 | margin-top: 20px;
20 | }
21 | .sidebar__item {
22 |
23 | }
24 | .sidebar__link {
25 | display: block;
26 | color: #909090;
27 | font-size: 14px;
28 | padding: 5px 20px;
29 | margin-bottom: 5px;
30 | font-weight: 500;
31 | }
32 | .sidebar__link:hover {
33 | color: #2F3030;
34 | }
35 | .sidebar__link--active {
36 | color: #2F3030;
37 | }
38 | .sidebar__link .material-icons {
39 | width: 24px;
40 | margin-right: 7px;
41 | font-size: 18px;
42 | text-align: center;
43 | color: #74A7DC;
44 | position: relative;
45 | top: 3px;
46 | }
47 | .sidebar__header {
48 | font-size: 12px;
49 | color: #A4B4C4;
50 | font-weight: 500;
51 | text-transform: uppercase;
52 | padding: 5px 20px;
53 | margin: 0 0
54 | }
55 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common/type/AppSidebarController.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
Lorem Ipsum 1
18 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
20 |
Lorem Ipsum 2
21 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-tag/contact-tag.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact'));
3 |
4 | describe('ContactController', function () {
5 | var $componentController,
6 | controller,
7 | mockTag = 'friends',
8 | mockChange = angular.noop;
9 |
10 | beforeEach(inject(function ($injector) {
11 | $componentController = $injector.get('$componentController');
12 | controller = $componentController('contactTag',
13 | { $scope: {} },
14 | { tag: mockTag, onChange: mockChange }
15 | );
16 | }));
17 |
18 | it('should bind to the correct tag', function () {
19 | var mockTag = 'football';
20 | controller.tag = mockTag;
21 | expect(controller.tag).toEqual(mockTag);
22 | });
23 |
24 | it('should call onSelect with the correct payload', function () {
25 | var tag = 'mate',
26 | payload = { $event: { tag: tag }};
27 |
28 | spyOn(controller, 'onChange');
29 | controller.updateTag(tag);
30 | expect(controller.onChange).toHaveBeenCalledWith(payload);
31 | });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/sass/components/_contact-card.scss:
--------------------------------------------------------------------------------
1 | .contact-card {
2 | background: #fff;
3 | border-bottom: 1px solid #e5e5e5;
4 | }
5 | .contact-card__link {
6 | display: block;
7 | padding: 15px 20px;
8 | font-size: 16px;
9 | overflow: hidden;
10 | }
11 | .contact-card__column {
12 | float: left;
13 | }
14 | .contact-card__column--name {
15 | width: 30%;
16 | }
17 | .contact-card__pill {
18 | opacity: 0.4;
19 | display: inline-block;
20 | font-size: 12px;
21 | border-radius: 3px;
22 | text-transform: uppercase;
23 | padding: 2px 5px 1px;
24 | font-weight: 500;
25 | margin-left: 10px;
26 | color: #fff;
27 | &:hover {
28 | opacity: 0.65;
29 | }
30 | }
31 | .contact-card__column .contact-card__pill {
32 | opacity: 1
33 | }
34 | .contact-card__pill a {
35 | color: #fff;
36 | }
37 | .contact-card__pill--friends {
38 | background: colors(pills);
39 | }
40 | .contact-card__pill--family {
41 | background: colors(pills, family);
42 | }
43 | .contact-card__pill--acquaintances {
44 | background: colors(pills, acquaintances);
45 | }
46 | .contact-card__pill--following {
47 | background: colors(pills, following);
48 | }
49 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common/type.html:
--------------------------------------------------------------------------------
1 |
2 | Type components in common
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Name
11 | Description
12 |
13 |
14 |
15 | AppSidebarController
16 | Lorem Ipsum 1
17 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
19 |
20 |
21 |
22 |
23 | AppController
24 | Lorem Ipsum 1
25 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
26 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.auth/service/AuthService.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 |
Provides HTTP methods for our firebase authentification.
20 |
Lorem Ipsum 1
21 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
23 |
Lorem Ipsum 2
24 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
25 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/service/ContactService.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 |
Provides HTTP methods for our firebase connection.
20 |
Lorem Ipsum 1
21 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
23 |
Lorem Ipsum 2
24 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
25 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/sass/base/_typography.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Material Icons';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: url(../../fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
6 | src: local('Material Icons'),
7 | local('MaterialIcons-Regular'),
8 | url(../../fonts/MaterialIcons-Regular.woff2) format('woff2'),
9 | url(../../fonts/MaterialIcons-Regular.woff) format('woff'),
10 | url(../../fonts/MaterialIcons-Regular.ttf) format('truetype');
11 | }
12 |
13 | .material-icons {
14 | font-family: 'Material Icons';
15 | font-weight: normal;
16 | font-style: normal;
17 | font-size: 24px; /* Preferred icon size */
18 | display: inline-block;
19 | width: 1em;
20 | height: 1em;
21 | line-height: 1;
22 | text-transform: none;
23 | letter-spacing: normal;
24 | word-wrap: normal;
25 | white-space: nowrap;
26 | direction: ltr;
27 |
28 | /* Support for all WebKit browsers. */
29 | -webkit-font-smoothing: antialiased;
30 | /* Support for Safari and Chrome. */
31 | text-rendering: optimizeLegibility;
32 |
33 | /* Support for Firefox. */
34 | -moz-osx-font-smoothing: grayscale;
35 |
36 | /* Support for IE. */
37 | font-feature-settings: 'liga';
38 | }
39 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common/directive/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Directive Info
32 |
33 |
34 | This directive executes at priority level 0.
35 |
36 |
37 |
38 |
39 |
Usage
40 |
41 |
42 |
How to use
43 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
44 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/directive/lengthCheck.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Directive Info
32 |
33 |
34 | This directive executes at priority level 0.
35 |
36 |
37 |
38 |
39 |
Usage
40 |
41 |
42 |
How to use
43 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
44 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/app/common/app-sidebar.controller.js:
--------------------------------------------------------------------------------
1 | function AppSidebarController() {
2 | var ctrl = this;
3 | ctrl.contactTags = [{
4 | label: 'All contacts',
5 | icon: 'star',
6 | state: 'none'
7 | }, {
8 | label: 'Friends',
9 | icon: 'people',
10 | state: 'friends'
11 | }, {
12 | label: 'Family',
13 | icon: 'child_care',
14 | state: 'family'
15 | }, {
16 | label: 'Acquaintances',
17 | icon: 'accessibility',
18 | state: 'acquaintances'
19 | }, {
20 | label: 'Following',
21 | icon: 'remove_red_eye',
22 | state: 'following'
23 | }];
24 | }
25 |
26 | /**
27 | * @ngdoc type
28 | * @module common
29 | * @name AppSidebarController
30 | *
31 | * @description
32 | *
33 | * ## Lorem Ipsum 1
34 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
35 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
36 | *
37 | * ## Lorem Ipsum 2
38 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
39 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
40 | */
41 | angular
42 | .module('common')
43 | .controller('AppSidebarController', AppSidebarController);
44 |
--------------------------------------------------------------------------------
/src/sass/base/_buttons.scss:
--------------------------------------------------------------------------------
1 |
2 | button {
3 | background: colors(button);
4 | color: #fff;
5 | border-radius: 3px;
6 | box-shadow: 0 3px 0px 0px colors(button, shadow);
7 | font-size: 16px;
8 | border: none;
9 | cursor: pointer;
10 | padding: 10px 15px;
11 | margin-bottom: 3px;
12 |
13 | &:hover {
14 | background: colors(button, hover);
15 | box-shadow: 0 3px 0 0 colors(button, hoverShadow);
16 | }
17 | }
18 |
19 | button:focus {
20 | outline: none;
21 | }
22 |
23 | button:active,
24 | button:focus {
25 | outline: none;
26 | background: colors(button, hoverShadow);
27 | box-shadow: 0 3px 0 0 darken(colors(button, hoverShadow), 8%);
28 | margin-bottom: 1px;
29 | }
30 | button[disabled] {
31 | background: rgba(0, 0, 0, 0.05);
32 | color: rgba(0, 0, 0, 0.4);
33 | cursor: not-allowed;
34 | box-shadow: 0 3px 0px 0px rgba(0, 0, 0, 0.1);
35 | }
36 | button.delete {
37 | background: colors(buttonDelete);
38 | box-shadow: 0 3px 0px 0px colors(buttonDelete, shadow);
39 |
40 | &:hover {
41 | background: colors(buttonDelete, hover);
42 | box-shadow: 0 3px 0 0 colors(buttonDelete, hoverShadow);
43 | }
44 | }
45 | button.delete:active,
46 | button.delete:focus {
47 | background: colors(buttonDelete, hoverShadow);
48 | box-shadow: 0 3px 0 0 darken(colors(buttonDelete, hoverShadow), 8%);
49 | }
50 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common/type/AppController.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
Lorem Ipsum 1
18 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
20 |
Lorem Ipsum 2
21 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Methods
40 |
41 |
42 | logout();
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/app/components/contact/length-check/length-check.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact'));
3 |
4 | describe('lengthCheck', function () {
5 | var $rootScope,
6 | $compile,
7 | element;
8 |
9 | beforeEach(inject(function ($injector) {
10 | $rootScope = $injector.get('$rootScope');
11 | $compile = $injector.get('$compile');
12 |
13 | $rootScope.contact = { name: '' };
14 | element = angular.element(' ');
15 | $compile(element)($rootScope);
16 | $rootScope.$digest();
17 | }));
18 |
19 | it('should contain dynamic-input class', function() {
20 | expect(element.hasClass('dynamic-input')).toEqual(true);
21 | });
22 |
23 | it('should dynamically add dynamic-input--no-contents class', function() {
24 | var scope = element.scope();
25 |
26 | element.val('John Doe').triggerHandler('input');
27 | scope.$apply();
28 |
29 | expect(scope.contact.name).toBe('John Doe');
30 | expect(element.hasClass('dynamic-input--no-contents')).toEqual(false);
31 |
32 | element.val('').triggerHandler('input');
33 | scope.$apply();
34 |
35 | expect(scope.contact.name).toBe('');
36 | expect(element.hasClass('dynamic-input--no-contents')).toEqual(true);
37 | });
38 | });
39 | });
--------------------------------------------------------------------------------
/src/app/components/contact/length-check/length-check.directive.js:
--------------------------------------------------------------------------------
1 | function lengthCheck() {
2 | return {
3 | restrict: 'A',
4 | require: 'ngModel',
5 | compile: function ($element) {
6 | $element.addClass('dynamic-input');
7 | return function ($scope, $element, $attrs, $ctrl) {
8 | var dynamicClass = 'dynamic-input--no-contents';
9 | $scope.$watch(function () {
10 | return $ctrl.$viewValue;
11 | }, function (newValue) {
12 | if (newValue) {
13 | $element.removeClass(dynamicClass);
14 | } else {
15 | $element.addClass(dynamicClass);
16 | }
17 | });
18 | };
19 | }
20 | };
21 | }
22 |
23 | /**
24 | * @ngdoc directive
25 | * @name lengthCheck
26 | * @module components.contact
27 | *
28 | * @description
29 | *
30 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
31 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
32 | *
33 | * @usage
34 | *
35 | * ### How to use
36 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
37 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
38 | **/
39 | angular
40 | .module('components.contact')
41 | .directive('lengthCheck', lengthCheck);
42 |
--------------------------------------------------------------------------------
/docs/config/templates/module.template.html:
--------------------------------------------------------------------------------
1 | {% extends "base.template.html" %}
2 |
3 | {% block content %}
4 |
5 | {% if doc.title %}{$ doc.title | marked $}{% else %}{$ doc.name | code $}{% endif %}
6 |
7 |
8 | {$ doc.description | marked $}
9 |
10 | {$ doc.stateName $}
11 |
12 | Requires {$ doc.requires $}
13 |
14 |
15 |
Module Components
16 | {% for componentGroup in doc.componentGroups %}
17 |
18 |
{$ componentGroup.groupType | title $}
19 |
20 |
21 | Name
22 | Description
23 |
24 | {% for component in componentGroup.components %}
25 |
26 | {$ component.id | link(component.name, component) $}
27 | {$ component.description | firstParagraph | marked $}
28 |
29 | {% endfor %}
30 |
31 |
32 | {% endfor %}
33 |
34 |
35 | {% if doc.usage %}
36 | Usage
37 | {$ doc.usage | marked $}
38 | {% endif %}
39 |
40 | {% endblock %}
41 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact.service.js:
--------------------------------------------------------------------------------
1 | function ContactService(AuthService, $firebaseRef, $firebaseArray, $firebaseObject) {
2 | var ref = $firebaseRef.contacts;
3 | var uid = AuthService.getUser().uid;
4 | return {
5 | createNewContact: function (contact) {
6 | return $firebaseArray(ref.child(uid)).$add(contact);
7 | },
8 | getContactById: function (id) {
9 | return $firebaseObject(ref.child(uid).child(id));
10 | },
11 | getContactList: function () {
12 | return $firebaseArray(ref.child(uid));
13 | },
14 | updateContact: function (contact) {
15 | return contact.$save();
16 | },
17 | deleteContact: function (contact) {
18 | return contact.$remove();
19 | }
20 | };
21 | }
22 |
23 | /**
24 | * @ngdoc service
25 | * @name ContactService
26 | * @module components.contact
27 | *
28 | * @description Provides HTTP methods for our firebase connection.
29 | *
30 | * ## Lorem Ipsum 1
31 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
32 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
33 | *
34 | * ## Lorem Ipsum 2
35 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
36 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
37 | */
38 |
39 | angular
40 | .module('components.contact')
41 | .factory('ContactService', ContactService);
42 |
--------------------------------------------------------------------------------
/src/sass/components/_contact.scss:
--------------------------------------------------------------------------------
1 | .contact {
2 | padding: 10px 15px;
3 | }
4 | .contact__saving {
5 | position: absolute;
6 | left: 50%;
7 | transform: translate(-50%, 0);
8 | background: rgba(0, 0, 0, 0.1);
9 | border: 1px solid rgba(0, 0, 0, 0.1);
10 | padding: 10px 15px;
11 | font-size: 16px;
12 | border-radius: 3px;
13 | }
14 | .contact__field {
15 | margin-bottom: 15px;
16 | padding-right: 30px;
17 | }
18 | .contact button {
19 | float: right;
20 | }
21 | .contact__required {
22 | color: colors(scaffold, error);
23 | font-size: 16px;
24 | line-height: 0px;
25 | position: relative;
26 | top: 4px;
27 | margin-left: 5px;
28 | }
29 | .contact__field--long {
30 | width: 600px;
31 | }
32 | .contact__error {
33 | float: right;
34 | color: colors(scaffold, error);
35 | }
36 | .contact__field label {
37 | font-size: 12px;
38 | color: #A4B4C4;
39 | font-weight: 500;
40 | text-transform: uppercase;
41 | padding: 5px 15px;
42 | display: block;
43 | margin-bottom: 3px;
44 | }
45 | .contact__field input {
46 | width: 100%;
47 | }
48 | .contact__box {
49 | float: left;
50 | width: 50%;
51 | max-width: 770px;
52 | }
53 | .contact__box--no-margin {
54 | margin-right: 0;
55 | }
56 | .contact__pill {
57 | opacity: 0.4;
58 | cursor: pointer;
59 | font-size: 16px;
60 | }
61 | .contact__pill--active {
62 | opacity: 1;
63 | &:hover {
64 | opacity: 1;
65 | }
66 | }
67 | .contact__sub-title {
68 | font-size: 24px;
69 | font-weight: 300;
70 | margin: 10px 15px;
71 | }
72 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth.module.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @ngdoc module
4 | * @name components.auth
5 | *
6 | * @requires ui.router
7 | * @requires firebase
8 | *
9 | * @description
10 | *
11 | * This is the auth module. It includes our auth components
12 | *
13 | **/
14 | angular
15 | .module('components.auth', [
16 | 'ui.router',
17 | 'firebase'
18 | ])
19 | .config(function ($firebaseRefProvider) {
20 |
21 | var config = {
22 | apiKey: "AIzaSyCsNISt3dFx7dy5AImIIk62jDDd0OLvZK0",
23 | authDomain: "contacts-manager-e486f.firebaseapp.com",
24 | databaseURL: "https://contacts-manager-e486f.firebaseio.com",
25 | storageBucket: "contacts-manager-e486f.appspot.com",
26 | };
27 |
28 | $firebaseRefProvider
29 | .registerUrl({
30 | default: config.databaseURL,
31 | contacts: config.databaseURL + '/contacts'
32 | });
33 |
34 | firebase.initializeApp(config);
35 | })
36 | .run(function ($transitions, $state, AuthService) {
37 | $transitions.onStart({
38 | to: function (state) {
39 | return !!(state.data && state.data.requiredAuth);
40 | }
41 | }, function() {
42 | return AuthService
43 | .requireAuthentication()
44 | .catch(function () {
45 | return $state.target('auth.login');
46 | });
47 | });
48 | $transitions.onStart({
49 | to: 'auth.*'
50 | }, function () {
51 | if (AuthService.isAuthenticated()) {
52 | return $state.target('app');
53 | }
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contacts",
3 | "version": "1.0.0",
4 | "author": "@toddmotto",
5 | "description": "Contacts manager app for Ultimate AngularJS",
6 | "scripts": {
7 | "start": "gulp",
8 | "test": "karma start",
9 | "deploy": "gulp production --deploy"
10 | },
11 | "license": "MIT",
12 | "repository": "https://github.com/toddmotto/angular-1-5-components-app",
13 | "dependencies": {
14 | "angular": "^1.5.8",
15 | "angular-loading-bar": "^0.9.0",
16 | "angular-messages": "^1.5.8",
17 | "angular-ui-router": "1.0.0-beta.2",
18 | "angularfire": "^2.0.2",
19 | "firebase": "3.3.0"
20 | },
21 | "devDependencies": {
22 | "angular-mocks": "^1.5.8",
23 | "babel-core": "^6.11.4",
24 | "babel-preset-es2015": "^6.9.0",
25 | "browser-sync": "^2.14.0",
26 | "canonical-path": "0.0.2",
27 | "del": "^2.2.1",
28 | "dgeni": "^0.4.2",
29 | "dgeni-packages": "^0.16.0",
30 | "gulp": "^3.9.1",
31 | "gulp-angular-templatecache": "^2.0.0",
32 | "gulp-concat": "^2.6.0",
33 | "gulp-htmlmin": "^2.0.0",
34 | "gulp-if": "^2.0.1",
35 | "gulp-ng-annotate": "^2.0.0",
36 | "gulp-sass": "^2.3.2",
37 | "gulp-uglify": "^2.0.0",
38 | "gulp-wrap": "^0.13.0",
39 | "jasmine-core": "^2.4.1",
40 | "karma": "^0.13.19",
41 | "karma-chrome-launcher": "^0.2.2",
42 | "karma-jasmine": "^0.3.6",
43 | "karma-phantomjs-launcher": "^1.0.1",
44 | "karma-spec-reporter": "0.0.23",
45 | "lodash": "^4.16.2",
46 | "path": "^0.12.7",
47 | "yargs": "^5.0.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 |
4 |
5 |
6 | ---
7 |
8 | # AngularJS 1.5 component architecture app
9 |
10 | > Try the [Contacts Manager](https://contacts-manager-e486f.firebaseapp.com) app! You'll need to register and create an account.
11 |
12 | ---
13 |
14 | > Want the ES2015 version? [Check it out here](https://github.com/toddmotto/angular-1-5-components-app/tree/ES2015).
15 |
16 | ---
17 |
18 | A [Contacts Manager](https://contacts-manager-e486f.firebaseapp.com) application built on AngularJS 1.5 components, ui-router 1.0.0, Firebase. Want to build it? Jump on my [AngularJS 1.5 Pro course](https://ultimateangular.com).
19 |
20 | ### List of features:
21 |
22 | - AngularJS 1.5 `.component()` method
23 | - Stateful/stateless and routed components
24 | - One-way dataflow
25 | - Lifecycle hooks
26 | - ui-router 1.0.0
27 | - Firebase auth and database/hosting
28 | - Fully tested, including spec files
29 | - Built against my component architecture [styleguide](https://github.com/toddmotto/angular-styleguide)
30 | - Proper SCSS architecture to provide maintainable, scalable and well-organized code
31 |
32 | ### Setup and install
33 |
34 | To run this app, follow the [instructions here](https://github.com/toddmotto/ultimate-angular-master-seed). Basically `npm install` and `npm start`. Run tests with `npm test`. Have fun!
35 |
36 | 
37 |
--------------------------------------------------------------------------------
/docs/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth.service.js:
--------------------------------------------------------------------------------
1 | function AuthService($firebaseAuth) {
2 | var auth = $firebaseAuth();
3 | var authData = null;
4 | function storeAuthData(response) {
5 | authData = response;
6 | return authData;
7 | }
8 | function onSignIn(user) {
9 | authData = user;
10 | return auth.$requireSignIn();
11 | }
12 | function clearAuthData() {
13 | authData = null;
14 | }
15 | this.login = function (user) {
16 | return auth
17 | .$signInWithEmailAndPassword(user.email, user.password)
18 | .then(storeAuthData);
19 | };
20 | this.register = function (user) {
21 | return auth
22 | .$createUserWithEmailAndPassword(user.email, user.password)
23 | .then(storeAuthData);
24 | };
25 | this.logout = function () {
26 | return auth
27 | .$signOut()
28 | .then(clearAuthData);
29 | };
30 | this.requireAuthentication = function () {
31 | return auth
32 | .$waitForSignIn().then(onSignIn);
33 | };
34 | this.isAuthenticated = function () {
35 | return !!authData;
36 | };
37 | this.getUser = function () {
38 | if (authData) {
39 | return authData;
40 | }
41 | };
42 | }
43 |
44 | /**
45 | * @ngdoc service
46 | * @name AuthService
47 | * @module components.auth
48 | *
49 | * @description Provides HTTP methods for our firebase authentification.
50 | *
51 | * ## Lorem Ipsum 1
52 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
53 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
54 | *
55 | * ## Lorem Ipsum 2
56 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
57 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
58 | */
59 | angular
60 | .module('components.auth')
61 | .service('AuthService', AuthService);
62 |
--------------------------------------------------------------------------------
/docs/config/templates/indexPage.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-edit/contact-edit.controller.js:
--------------------------------------------------------------------------------
1 | function ContactEditController($state, ContactService, cfpLoadingBar, $window) {
2 | var ctrl = this;
3 | /**
4 | * @ngdoc method
5 | * @name ContactEditController#updateContact
6 | *
7 | * @param {event} event Receive the emitted event
8 | * Updates the contact information
9 | *
10 | * @return {method} ContactService returns the updateContact method and a promise
11 | */
12 | ctrl.updateContact = function (event) {
13 | cfpLoadingBar.start();
14 | return ContactService
15 | .updateContact(event.contact)
16 | .then(cfpLoadingBar.complete, cfpLoadingBar.complete);
17 | };
18 | /**
19 | * @ngdoc method
20 | * @name ContactEditController#deleteContact
21 | *
22 | * @param {event} event Delete the contact
23 | *
24 | * @return {method} ContactService returns the deleteContact method and a promise
25 | */
26 | ctrl.deleteContact = function (event) {
27 | var message = 'Delete ' + event.contact.name + ' from contacts?';
28 | if ($window.confirm(message)) {
29 | return ContactService
30 | .deleteContact(event.contact)
31 | .then(function () {
32 | $state.go('contacts');
33 | });
34 | }
35 | };
36 | }
37 |
38 | /**
39 | * @ngdoc type
40 | * @module components.contact
41 | * @name ContactEditController
42 | *
43 | * @description
44 | *
45 | * ## Lorem Ipsum 1
46 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
47 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
48 | *
49 | * ## Lorem Ipsum 2
50 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
51 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
52 | */
53 | angular
54 | .module('components.contact')
55 | .controller('ContactEditController', ContactEditController);
56 |
--------------------------------------------------------------------------------
/mocks/firebase.mock.js:
--------------------------------------------------------------------------------
1 | function MockFirebase () {}
2 | MockFirebase.prototype.initializeApp = function () {};
3 | window.firebase = new MockFirebase();
4 |
5 | angular
6 | .module('firebase', [])
7 | .provider('$firebaseRef', function () {
8 | this.registerUrl = function (urls) {
9 | for (var key in urls) {
10 | this[key] = urls[key];
11 | }
12 | };
13 | this.$get = function () {
14 | return angular.noop;
15 | };
16 | })
17 | .factory('$firebaseArray', function ($q) {
18 | function FirebaseArray(ref) {}
19 |
20 | FirebaseArray.prototype = {
21 | constructor: FirebaseArray,
22 | $add: function (newData) {
23 | return $q.resolve({ key: 1 });
24 | }
25 | };
26 |
27 | return function () {
28 | return new FirebaseArray();
29 | };
30 | })
31 | .factory('$firebaseObject', function ($q) {
32 | function FirebaseObject() {
33 | return $q.when({ key: 1 });
34 | }
35 |
36 | return function () {
37 | return new FirebaseObject();
38 | };
39 | })
40 | .factory('$firebaseAuth', function ($q) {
41 |
42 | var fakeUser = { $id: 1 };
43 |
44 | function FirebaseAuth() {
45 | this.auth = null;
46 | }
47 |
48 | FirebaseAuth.prototype = {
49 | constructor: FirebaseAuth,
50 | $requireSignIn: function () {
51 | this.auth = fakeUser;
52 | return $q.resolve(this.auth);
53 | },
54 | $signInWithEmailAndPassword: function (email, password) {
55 | this.auth = fakeUser;
56 | return $q.resolve(this.auth);
57 | },
58 | $createUserWithEmailAndPassword: function () {
59 | this.auth = fakeUser;
60 | return $q.resolve(this.auth);
61 | },
62 | $waitForSignIn: function () {
63 | this.auth = fakeUser;
64 | return $q.resolve(this.auth);
65 | },
66 | $signOut: function () {
67 | this.auth = null;
68 | return $q.resolve(this.auth);
69 | }
70 | };
71 |
72 | return function () {
73 | return new FirebaseAuth();
74 | };
75 | });
76 |
--------------------------------------------------------------------------------
/src/sass/layout/_loading.scss:
--------------------------------------------------------------------------------
1 | #loading-bar,#loading-bar-spinner{pointer-events:none;-webkit-pointer-events:none;-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar-spinner.ng-enter,#loading-bar-spinner.ng-leave.ng-leave-active,#loading-bar.ng-enter,#loading-bar.ng-leave.ng-leave-active{opacity:0}#loading-bar-spinner.ng-enter.ng-enter-active,#loading-bar-spinner.ng-leave,#loading-bar.ng-enter.ng-enter-active,#loading-bar.ng-leave{opacity:1}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:10002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px;opacity:.45;-moz-box-shadow:#29d 1px 0 6px 1px;-ms-box-shadow:#29d 1px 0 6px 1px;-webkit-box-shadow:#29d 1px 0 6px 1px;box-shadow:#29d 1px 0 6px 1px;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}#loading-bar-spinner{display:block;position:fixed;z-index:10002;top:10px;left:10px}#loading-bar-spinner .spinner-icon{width:14px;height:14px;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:loading-bar-spinner 400ms linear infinite;-moz-animation:loading-bar-spinner 400ms linear infinite;-ms-animation:loading-bar-spinner 400ms linear infinite;-o-animation:loading-bar-spinner 400ms linear infinite;animation:loading-bar-spinner 400ms linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
2 |
--------------------------------------------------------------------------------
/src/app/components/contact/contacts.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.auth', function($provide){
3 | $provide.value('AuthService', {
4 | getUser: function() {
5 | return { uid: 1 }
6 | },
7 | isAuthenticated: function() {
8 | return true;
9 | }
10 | });
11 |
12 | var child = function(record) {
13 | return { child: child }
14 | };
15 |
16 | $provide.value('$firebaseRef', {
17 | contacts: {
18 | child: child
19 | }
20 | })
21 | }));
22 |
23 | beforeEach(module(function ($stateProvider) {
24 | $stateProvider.state('app', { url: '/' });
25 | }));
26 |
27 | beforeEach(module('components.contact'));
28 |
29 | describe('ContactService', function () {
30 | var ContactService,
31 | $rootScope;
32 |
33 | beforeEach(inject(function ($injector) {
34 | ContactService = $injector.get('ContactService');
35 | $rootScope = $injector.get('$rootScope');
36 | }));
37 |
38 | it('should get contacts', function () {
39 | // Pending
40 | });
41 |
42 | it('should get a contact', function () {
43 | var id = 1,
44 | promise = ContactService.getContactById(id);
45 |
46 | promise.then(function(ref){
47 | expect(ref.key).toEqual(id);
48 | });
49 |
50 | $rootScope.$digest();
51 | });
52 |
53 | it('should create a contact', function () {
54 | var contact = { email: 'test@test.com', password: 'insecure' }
55 | promise = ContactService.createNewContact(contact);
56 |
57 | promise.then(function(ref){
58 | expect(ref.key).toEqual(1);
59 | });
60 |
61 | $rootScope.$digest();
62 | });
63 |
64 | it('should update a contact', function () {
65 | var contact = { $save: angular.noop };
66 |
67 | spyOn(contact, '$save');
68 | ContactService.updateContact(contact);
69 | expect(contact.$save).toHaveBeenCalled();
70 | });
71 |
72 | it('should delete a contact', function () {
73 | var contact = { $remove: angular.noop };
74 |
75 | spyOn(contact, '$remove');
76 | ContactService.deleteContact(contact);
77 | expect(contact.$remove).toHaveBeenCalled();
78 | });
79 | });
80 | });
--------------------------------------------------------------------------------
/docs/config/processors/api-data.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 |
3 |
4 | function buildDocData(doc, extraData) {
5 |
6 | // So that we can create states even though our module names contain dots(.)
7 | // in UI-Router dotted notation means it's a child state, so this is problematic
8 | // if we are following AngularJS styleguides and conventions regarding
9 | // naming of our Modules
10 | // #hack #lazy
11 | var splitName = doc.name.split('.');
12 | doc.stateName = _.camelCase(splitName);
13 |
14 | return _.assign({
15 | name: doc.name,
16 | stateName: doc.stateName,
17 | type: doc.docType,
18 | outputPath: doc.outputPath,
19 | url: doc.path,
20 | }, extraData);
21 | }
22 |
23 | module.exports = function apiPagesProcessor(moduleMap) {
24 | return {
25 | $runAfter: ['paths-computed'],
26 | $runBefore: ['rendering-docs'],
27 | $process: process
28 | };
29 |
30 | function process(docs) {
31 |
32 | var apiPages = _(docs)
33 |
34 | .filter(function(doc) {
35 | // We are not interested in docs that are not in a module
36 | // We are only interested in pages that are not landing pages
37 | return doc.docType !== 'componentGroup';
38 | })
39 |
40 | .filter('module')
41 |
42 | .groupBy('module')
43 |
44 | .map(function(moduleDocs, moduleName) {
45 |
46 | var moduleDoc = _.find(docs, {
47 | docType: 'module',
48 | name: moduleName
49 | });
50 |
51 | if (!moduleDoc) return;
52 |
53 | return buildDocData(moduleDoc, {
54 | docs: moduleDocs
55 |
56 | .filter(function(doc) {
57 | // Private isn't set to true, just to an empty string if @private is supplied
58 | return doc.docType !== 'module';
59 | })
60 |
61 | .map(buildDocData)
62 | });
63 |
64 | })
65 |
66 | .filter() //remove null items
67 |
68 | .value();
69 |
70 | docs.push({
71 | name: 'API_DATA',
72 | template: 'constant-data.template.js',
73 | outputPath: 'src/api-data.js',
74 | items: apiPages
75 | });
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/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 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
10 | frameworks: ['jasmine'],
11 |
12 |
13 | // list of files / patterns to load in the browser
14 | files: [
15 | 'node_modules/angular/angular.js',
16 | 'node_modules/angular-ui-router/release/angular-ui-router.js',
17 | 'node_modules/angular-loading-bar/build/loading-bar.min.js',
18 | // 'node_modules/firebase/firebase.js',
19 | // 'node_modules/angularfire/dist/angularfire.js',
20 | 'node_modules/angular-mocks/angular-mocks.js',
21 | 'mocks/firebase.mock.js',
22 | 'src/app/**/*.spec.js',
23 | 'dist/js/bundle.js',
24 | ],
25 |
26 | // list of files to exclude
27 | exclude: [
28 | ],
29 |
30 | plugins: [
31 | require('karma-jasmine'),
32 | require('karma-chrome-launcher'),
33 | require('karma-phantomjs-launcher'),
34 | require('karma-spec-reporter')
35 | ],
36 |
37 | // preprocess matching files before serving them to the browser
38 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
39 | preprocessors: {
40 | },
41 |
42 |
43 | // test results reporter to use
44 | // possible values: 'dots', 'progress'
45 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
46 | reporters: ['spec'],
47 |
48 |
49 | // web server port
50 | port: 9876,
51 |
52 |
53 | // enable / disable colors in the output (reporters and logs)
54 | colors: true,
55 |
56 |
57 | // level of logging
58 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
59 | logLevel: config.LOG_INFO,
60 |
61 |
62 | // enable / disable watching file and executing tests whenever any file changes
63 | autoWatch: true,
64 |
65 | // start these browsers
66 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
67 | browsers: ['Chrome'],
68 |
69 |
70 | // Continuous Integration mode
71 | // if true, Karma captures browsers, runs the tests and exits
72 | singleRun: false,
73 |
74 | // Concurrency level
75 | // how many browser should be started simultaneous
76 | concurrency: Infinity
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/docs/build/partials/api/common.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | common
4 |
5 |
6 | This is the common module. It includes a run method that setups the loading bar.
7 |
8 |
9 | common
10 |
11 | Requires ui.router,angular-loading-bar
12 |
13 |
14 |
Module Components
15 |
16 |
17 |
Type
18 |
19 |
20 | Name
21 | Description
22 |
23 |
24 |
25 | AppSidebarController
26 | Lorem Ipsum 1
27 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
28 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
29 |
30 |
31 |
32 |
33 | AppController
34 | Lorem Ipsum 1
35 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
36 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Directive
45 |
46 |
47 | Name
48 | Description
49 |
50 |
51 |
52 | app
53 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
54 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-detail/contact-detail.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact'));
3 |
4 | describe('ContactDetailController', function () {
5 | var $componentController,
6 | controller,
7 | mockContact = { $id: 1 },
8 | mockSave = angular.noop,
9 | mockUpdate = angular.noop,
10 | mockDelete = angular.noop;
11 |
12 | beforeEach(inject(function ($injector) {
13 | $componentController = $injector.get('$componentController');
14 | controller = $componentController('contactDetail',
15 | { $scope: {} },
16 | { contact: mockContact, onSave: mockSave, onUpdate: mockUpdate, onDelete: mockDelete }
17 | );
18 | }));
19 |
20 | it('should bind to the correct contact', function () {
21 | expect(controller.contact.$id).toEqual(mockContact.$id);
22 | controller.$onInit();
23 |
24 | expect(controller.isNewContact).toBe(false);
25 | });
26 |
27 | it('should initialize isNewContact if no $id is present', function() {
28 | controller = $componentController('contactDetail',
29 | { $scope: {} },
30 | { contact: {}, onSave: mockSave, onUpdate: mockUpdate, onDelete: mockDelete }
31 | );
32 | controller.$onInit();
33 |
34 | expect(controller.isNewContact).toBe(true);
35 | });
36 |
37 | it('should call onSave when saveContact is called', function () {
38 | var payload = { $event: { contact: mockContact } };
39 |
40 | spyOn(controller, 'onSave');
41 | controller.saveContact();
42 | expect(controller.onSave).toHaveBeenCalledWith(payload);
43 | });
44 |
45 | it('should call onUpdate when updateContact is called', function () {
46 | var payload = { $event: { contact: mockContact } };
47 |
48 | spyOn(controller, 'onUpdate');
49 | controller.updateContact();
50 | expect(controller.onUpdate).toHaveBeenCalledWith(payload);
51 | });
52 |
53 | it('should call onDelete when deleteContact is called', function () {
54 | var payload = { $event: { contact: mockContact } };
55 |
56 | spyOn(controller, 'onDelete');
57 | controller.deleteContact();
58 | expect(controller.onDelete).toHaveBeenCalledWith(payload);
59 | });
60 |
61 | it('should save tag when tagChange is called', function () {
62 | var event = { tag: 'friend' };
63 |
64 | spyOn(controller, 'updateContact');
65 | controller.tagChange(event);
66 | expect(controller.updateContact).toHaveBeenCalled();
67 | expect(controller.contact.tag).toEqual(event.tag);
68 | });
69 | });
70 | });
--------------------------------------------------------------------------------
/src/app/components/contact/contact-new/contact-new.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact', function ($provide) {
3 | $provide.value('cfpLoadingBar', {
4 | start: angular.noop,
5 | complete: angular.noop
6 | })
7 |
8 | $provide.value('ContactService', {
9 | createNewContact: angular.noop
10 | });
11 | }));
12 |
13 | beforeEach(module('components.auth'));
14 |
15 | beforeEach(module(function ($stateProvider) {
16 | $stateProvider.state('app', {
17 | redirectTo: 'contacts',
18 | url: '/app',
19 | data: {
20 | requiredAuth: true
21 | }
22 | });
23 | }));
24 |
25 | describe('Routes', function () {
26 | var $state, $location, $rootScope, AuthService;
27 |
28 | function goTo(url) {
29 | $location.url(url);
30 | $rootScope.$digest();
31 | }
32 |
33 | beforeEach(inject(function ($injector) {
34 | $state = $injector.get('$state');
35 | $location = $injector.get('$location');
36 | $rootScope = $injector.get('$rootScope');
37 | AuthService = $injector.get('AuthService');
38 | }));
39 |
40 | it('should go to the contact state', function() {
41 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
42 |
43 | goTo('/app/new');
44 |
45 | expect($state.current.name).toEqual('new')
46 | });
47 | });
48 |
49 | describe('ContactNewController', function () {
50 | var $componentController,
51 | controller,
52 | $state,
53 | ContactService,
54 | $rootScope,
55 | $q;
56 |
57 | beforeEach(inject(function ($injector) {
58 | $componentController = $injector.get('$componentController');
59 | $state = $injector.get('$state');
60 | ContactService = $injector.get('ContactService');
61 | $rootScope = $injector.get('$rootScope');
62 | $q = $injector.get('$q');
63 |
64 | controller = $componentController('contactNew',
65 | { $scope: {}, $state: $state, ContactService: ContactService }
66 | );
67 | }));
68 |
69 | it('should create a contact', function () {
70 | var event = { contact: { email: 'test@test.com', password: 'insecure' } };
71 | spyOn(ContactService, 'createNewContact').and.callFake(
72 | function () {
73 | return $q.when({ key: 1})
74 | }
75 | );
76 | spyOn($state, 'go');
77 |
78 | var promise = controller.createNewContact(event);
79 |
80 | promise.then(function () {
81 | expect(ContactService.createNewContact).toHaveBeenCalled();
82 | expect($state.go).toHaveBeenCalledWith('contact', {id: 1});
83 | });
84 |
85 | $rootScope.$digest();
86 | });
87 | });
88 | });
--------------------------------------------------------------------------------
/src/app/components/contact/contacts/contacts.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.contact', function($provide){
3 | $provide.value('ContactService', {
4 | getContactList: function() {
5 | return {
6 | $loaded: angular.noop
7 | }
8 | }
9 | });
10 | }));
11 |
12 | beforeEach(module('components.auth'));
13 |
14 | beforeEach(module(function ($stateProvider) {
15 | $stateProvider.state('app', {
16 | redirectTo: 'contacts',
17 | url: '/app',
18 | data: {
19 | requiredAuth: true
20 | }
21 | });
22 | }));
23 |
24 | describe('Routes', function () {
25 | var $state, $location, $rootScope, AuthService;
26 |
27 | function goTo(url) {
28 | $location.url(url);
29 | $rootScope.$digest();
30 | }
31 |
32 | beforeEach(inject(function ($injector) {
33 | $state = $injector.get('$state');
34 | $location = $injector.get('$location');
35 | $rootScope = $injector.get('$rootScope');
36 | AuthService = $injector.get('AuthService');
37 | }));
38 |
39 | it('should go to the contact state', function() {
40 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
41 |
42 | goTo('/app/contacts?friends');
43 |
44 | expect($state.current.name).toEqual('contacts')
45 | });
46 | });
47 |
48 | describe('ContactsController', function () {
49 | var $componentController,
50 | controller,
51 | $filter,
52 | $state,
53 | mockFilter = { filter: 'friends'},
54 | mockContacts = [
55 | {
56 | name: 'John Doe',
57 | tag: 'friends'
58 | },
59 | {
60 | name: 'Jane Smith',
61 | tag: 'family'
62 | }
63 | ];
64 |
65 | beforeEach(inject(function ($injector) {
66 | $componentController = $injector.get('$componentController');
67 | $filter = $injector.get('$filter');
68 | $state = $injector.get('$state');
69 | controller = $componentController('contacts',
70 | { $scope: {}, $filter: $filter, $state: $state },
71 | { filter: mockFilter, contacts: mockContacts }
72 | );
73 | controller.$onInit();
74 | }));
75 |
76 | it('should filter contacts', function() {
77 | expect(controller.filteredContacts).toEqual([{
78 | name: 'John Doe',
79 | tag: 'friends'
80 | }]);
81 | });
82 |
83 | it('should route on goToContact call', function () {
84 | var event = { contactId: 1 };
85 |
86 | spyOn($state, 'go');
87 | controller.goToContact(event);
88 | expect($state.go).toHaveBeenCalledWith('contact', { id: event.contactId });
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/src/app/common/app.spec.js:
--------------------------------------------------------------------------------
1 | describe('App', function () {
2 | beforeEach(module('ui.router'));
3 |
4 | beforeEach(module('common', function ($provide) {
5 | $provide.value('AuthService', {
6 | getUser: angular.noop,
7 | logout: angular.noop
8 | });
9 | }));
10 |
11 | beforeEach(module('components.auth'));
12 |
13 | beforeEach(module(function ($stateProvider) {
14 | $stateProvider.state('contacts', { url: '/app/contacts' });
15 | }));
16 |
17 | describe('Routes', function () {
18 | var $state, $location, $rootScope, AuthService;
19 |
20 | function goTo(url) {
21 | $location.url(url);
22 | $rootScope.$digest();
23 | }
24 |
25 | beforeEach(inject(function ($injector) {
26 | $state = $injector.get('$state');
27 | $location = $injector.get('$location');
28 | $rootScope = $injector.get('$rootScope');
29 | AuthService = $injector.get('AuthService');
30 | }));
31 |
32 | it('should redirect to contacts state', function () {
33 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
34 |
35 | goTo('/app');
36 |
37 | expect($state.current.name).toEqual('contacts');
38 | });
39 | });
40 |
41 | describe('AppController', function () {
42 | var $rootScope, $q, $componentController, controller, AuthService, $state;
43 |
44 | beforeEach(inject(function ($injector) {
45 | $rootScope = $injector.get('$rootScope');
46 | $q = $injector.get('$q');
47 | $componentController = $injector.get('$componentController');
48 | AuthService = $injector.get('AuthService');
49 | $state = $injector.get('$state');
50 | }));
51 |
52 | it('should get user on instantiated', function () {
53 | var user = { $id: 1 }
54 | spyOn(AuthService, 'getUser').and.returnValue(user);
55 |
56 | controller = $componentController('app',
57 | { $scope: {}, AuthService: AuthService, $state: $state }
58 | );
59 |
60 | expect(AuthService.getUser).toHaveBeenCalled();
61 | expect(controller.user).toEqual(user);
62 | });
63 |
64 | it('should go to the login state on logout', function () {
65 | spyOn(AuthService, 'logout')
66 | .and.callFake(function () {
67 | var deferred = $q.defer();
68 | deferred.resolve();
69 | return deferred.promise;
70 | });
71 | spyOn($state, 'go').and.callThrough();
72 |
73 | controller = $componentController('app',
74 | { $scope: {}, AuthService: AuthService, $state: $state }
75 | );
76 |
77 | controller.logout();
78 | $rootScope.$digest();
79 |
80 | expect(AuthService.logout).toHaveBeenCalled();
81 | expect($state.go).toHaveBeenCalledWith('auth.login');
82 | });
83 | });
84 |
85 | });
86 |
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | components.contact
4 |
5 |
6 | This is the contact module. It includes all of our components for the contact feature.
7 |
8 |
9 | componentsContact
10 |
11 | Requires ui.router
12 |
13 |
14 |
Module Components
15 |
16 |
17 |
Type
18 |
19 |
20 | Name
21 | Description
22 |
23 |
24 |
25 | ContactEditController
26 | Lorem Ipsum 1
27 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
28 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Service
37 |
38 |
39 | Name
40 | Description
41 |
42 |
43 |
44 | ContactService
45 | Provides HTTP methods for our firebase connection.
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Directive
54 |
55 |
56 | Name
57 | Description
58 |
59 |
60 |
61 | lengthCheck
62 | Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
63 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/docs/build/src/api-data.js:
--------------------------------------------------------------------------------
1 | angular
2 | .module('docs')
3 | .constant('API_DATA', [
4 | {
5 | "name": "common",
6 | "stateName": "common",
7 | "type": "module",
8 | "outputPath": "partials/api/common.html",
9 | "url": "api/common",
10 | "docs": [
11 | {
12 | "name": "AppSidebarController",
13 | "stateName": "appSidebarController",
14 | "type": "type",
15 | "outputPath": "partials/api/common/type/AppSidebarController.html",
16 | "url": "api/common/type/AppSidebarController"
17 | },
18 | {
19 | "name": "app",
20 | "stateName": "app",
21 | "type": "directive",
22 | "outputPath": "partials/api/common/directive/app.html",
23 | "url": "api/common/directive/app"
24 | },
25 | {
26 | "name": "AppController",
27 | "stateName": "appController",
28 | "type": "type",
29 | "outputPath": "partials/api/common/type/AppController.html",
30 | "url": "api/common/type/AppController"
31 | }
32 | ]
33 | },
34 | {
35 | "name": "components",
36 | "stateName": "components",
37 | "type": "module",
38 | "outputPath": "partials/api/components.html",
39 | "url": "api/components",
40 | "docs": []
41 | },
42 | {
43 | "name": "components.auth",
44 | "stateName": "componentsAuth",
45 | "type": "module",
46 | "outputPath": "partials/api/components.auth.html",
47 | "url": "api/components.auth",
48 | "docs": [
49 | {
50 | "name": "AuthService",
51 | "stateName": "authService",
52 | "type": "service",
53 | "outputPath": "partials/api/components.auth/service/AuthService.html",
54 | "url": "api/components.auth/service/AuthService"
55 | }
56 | ]
57 | },
58 | {
59 | "name": "components.contact",
60 | "stateName": "componentsContact",
61 | "type": "module",
62 | "outputPath": "partials/api/components.contact.html",
63 | "url": "api/components.contact",
64 | "docs": [
65 | {
66 | "name": "ContactEditController",
67 | "stateName": "contactEditController",
68 | "type": "type",
69 | "outputPath": "partials/api/components.contact/type/ContactEditController.html",
70 | "url": "api/components.contact/type/ContactEditController"
71 | },
72 | {
73 | "name": "ContactService",
74 | "stateName": "contactService",
75 | "type": "service",
76 | "outputPath": "partials/api/components.contact/service/ContactService.html",
77 | "url": "api/components.contact/service/ContactService"
78 | },
79 | {
80 | "name": "lengthCheck",
81 | "stateName": "lengthCheck",
82 | "type": "directive",
83 | "outputPath": "partials/api/components.contact/directive/lengthCheck.html",
84 | "url": "api/components.contact/directive/lengthCheck"
85 | }
86 | ]
87 | }
88 | ]);
89 |
--------------------------------------------------------------------------------
/docs/app/app.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .config(config);
6 |
7 | function config($locationProvider, $stateProvider, API_DATA, GUIDE_DATA, $urlRouterProvider) {
8 |
9 | $locationProvider.html5Mode(true);
10 |
11 | $urlRouterProvider.otherwise('/api');
12 |
13 | var sidebarTemplate = 'Contents ' +
14 | '' +
15 | '' +
16 | '{{page.name}} ' +
17 | '' +
18 | '' +
19 | '{{child.name}} ' +
20 | ' ' +
21 | ' ' +
22 | ' ' +
23 | ' ';
24 |
25 | var apiState = {
26 | name: 'api',
27 | url: '/api',
28 | views: {
29 | 'main': {
30 | templateUrl: 'partials/api.html',
31 | },
32 | 'sidebar': {
33 | template: sidebarTemplate,
34 | controller: 'ApiController as ctrl',
35 | }
36 | }
37 | }
38 |
39 | var guideState = {
40 | name: 'guide',
41 | url: '/guide',
42 | views: {
43 | 'main': {
44 | templateUrl: 'partials/guide.html'
45 | },
46 | 'sidebar': {
47 | template: sidebarTemplate,
48 | controller: 'GuideController as ctrl',
49 | }
50 | }
51 | }
52 |
53 | $stateProvider.state(apiState);
54 | $stateProvider.state(guideState);
55 |
56 | angular.forEach(API_DATA, function(parent) {
57 |
58 | var newState = {
59 | name: parent.stateName,
60 | url: '/' + parent.url,
61 | views: {
62 | 'main': {
63 | templateUrl: parent.outputPath
64 | },
65 | 'sidebar': {
66 | template: sidebarTemplate,
67 | controller: 'ApiController as ctrl'
68 | }
69 | }
70 | };
71 |
72 | $stateProvider.state(newState);
73 |
74 | angular.forEach(parent.docs, function(doc) {
75 |
76 | var newState = {
77 | name: doc.stateName,
78 | url: '/' + doc.url,
79 | views: {
80 | 'main': {
81 | templateUrl: doc.outputPath
82 | },
83 | 'sidebar': {
84 | template: sidebarTemplate,
85 | controller: 'ApiController as ctrl'
86 | }
87 | }
88 | };
89 |
90 | $stateProvider.state(newState);
91 | });
92 | });
93 |
94 | angular.forEach(GUIDE_DATA, function(parent) {
95 |
96 | var newState = {
97 | name: parent.name,
98 | url: '/' + parent.url,
99 | views: {
100 | 'main': {
101 | templateUrl: parent.outputPath
102 | },
103 | 'sidebar': {
104 | template: sidebarTemplate,
105 | controller: 'GuideController as ctrl'
106 | }
107 | }
108 | };
109 |
110 | $stateProvider.state(newState);
111 |
112 | });
113 |
114 |
115 | }
116 | config.$inject = ["$locationProvider", "$stateProvider", "API_DATA", "GUIDE_DATA", "$urlRouterProvider"];
117 |
--------------------------------------------------------------------------------
/docs/build/src/app.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular
4 | .module('docs')
5 | .config(config);
6 |
7 | function config($locationProvider, $stateProvider, API_DATA, GUIDE_DATA, $urlRouterProvider) {
8 |
9 | $locationProvider.html5Mode(true);
10 |
11 | $urlRouterProvider.otherwise('/api');
12 |
13 | var sidebarTemplate = 'Contents ' +
14 | '' +
15 | '' +
16 | '{{page.name}} ' +
17 | '' +
18 | '' +
19 | '{{child.name}} ' +
20 | ' ' +
21 | ' ' +
22 | ' ' +
23 | ' ';
24 |
25 | var apiState = {
26 | name: 'api',
27 | url: '/api',
28 | views: {
29 | 'main': {
30 | templateUrl: 'partials/api.html',
31 | },
32 | 'sidebar': {
33 | template: sidebarTemplate,
34 | controller: 'ApiController as ctrl',
35 | }
36 | }
37 | }
38 |
39 | var guideState = {
40 | name: 'guide',
41 | url: '/guide',
42 | views: {
43 | 'main': {
44 | templateUrl: 'partials/guide.html'
45 | },
46 | 'sidebar': {
47 | template: sidebarTemplate,
48 | controller: 'GuideController as ctrl',
49 | }
50 | }
51 | }
52 |
53 | $stateProvider.state(apiState);
54 | $stateProvider.state(guideState);
55 |
56 | angular.forEach(API_DATA, function(parent) {
57 |
58 | var newState = {
59 | name: parent.stateName,
60 | url: '/' + parent.url,
61 | views: {
62 | 'main': {
63 | templateUrl: parent.outputPath
64 | },
65 | 'sidebar': {
66 | template: sidebarTemplate,
67 | controller: 'ApiController as ctrl'
68 | }
69 | }
70 | };
71 |
72 | $stateProvider.state(newState);
73 |
74 | angular.forEach(parent.docs, function(doc) {
75 |
76 | var newState = {
77 | name: doc.stateName,
78 | url: '/' + doc.url,
79 | views: {
80 | 'main': {
81 | templateUrl: doc.outputPath
82 | },
83 | 'sidebar': {
84 | template: sidebarTemplate,
85 | controller: 'ApiController as ctrl'
86 | }
87 | }
88 | };
89 |
90 | $stateProvider.state(newState);
91 | });
92 | });
93 |
94 | angular.forEach(GUIDE_DATA, function(parent) {
95 |
96 | var newState = {
97 | name: parent.name,
98 | url: '/' + parent.url,
99 | views: {
100 | 'main': {
101 | templateUrl: parent.outputPath
102 | },
103 | 'sidebar': {
104 | template: sidebarTemplate,
105 | controller: 'GuideController as ctrl'
106 | }
107 | }
108 | };
109 |
110 | $stateProvider.state(newState);
111 |
112 | });
113 |
114 |
115 | }
116 | config.$inject = ["$locationProvider", "$stateProvider", "API_DATA", "GUIDE_DATA", "$urlRouterProvider"];
117 |
--------------------------------------------------------------------------------
/src/app/components/auth/login/login.spec.js:
--------------------------------------------------------------------------------
1 | describe('Auth', function () {
2 | beforeEach(module('components.auth'));
3 |
4 | beforeEach(module(function ($stateProvider) {
5 | $stateProvider.state('app', { url: '/' });
6 | }));
7 |
8 | describe('Routes', function () {
9 | var $state, $location, $rootScope;
10 |
11 | function goTo(url) {
12 | $location.url(url);
13 | $rootScope.$digest();
14 | }
15 |
16 | beforeEach(inject(function ($injector) {
17 | $state = $injector.get('$state');
18 | $location = $injector.get('$location');
19 | $rootScope = $injector.get('$rootScope');
20 | }));
21 |
22 | it('should redirect to auth.login state', function() {
23 | goTo('/auth');
24 | expect($state.current.name).toEqual('auth.login')
25 | });
26 |
27 | it('should go to auth.login state', function() {
28 | goTo('/login');
29 | expect($state.current.name).toEqual('auth.login')
30 | });
31 | });
32 |
33 | describe('LoginController', function () {
34 | var $componentController,
35 | controller,
36 | AuthService,
37 | $state,
38 | $rootScope,
39 | $q;
40 |
41 | beforeEach(inject(function ($injector) {
42 | $componentController = $injector.get('$componentController');
43 | AuthService = $injector.get('AuthService');
44 | $state = $injector.get('$state');
45 | $rootScope = $injector.get('$rootScope');
46 | $q = $injector.get('$q');
47 |
48 | controller = $componentController('login',
49 | { $scope: {}, AuthService: AuthService, $state: $state }
50 | );
51 | }));
52 |
53 | it('should initialize with correct properties', function () {
54 | controller.$onInit();
55 |
56 | expect(controller.error).toBeNull();
57 | expect(controller.user.email).toEqual('');
58 | expect(controller.user.password).toEqual('');
59 | });
60 |
61 | it('should redirect on successful login ', function () {
62 | var mockUser = { email: 'test@test.com', password: 'insecure' },
63 | mockEvent = { $event: { user: mockUser } };
64 |
65 | spyOn(AuthService, 'login').and.callFake(function() {
66 | return $q.when({$id: 1});
67 | });
68 |
69 | spyOn($state, 'go');
70 |
71 | var promise = controller.loginUser(mockEvent);
72 |
73 | promise.then(function(result){
74 | expect(AuthService.login).toHaveBeenCalledWith(mockEvent.user);
75 | expect($state.go).toHaveBeenCalledWith('app');
76 | });
77 |
78 | $rootScope.$digest();
79 | });
80 |
81 | it('should set error on failed login ', function () {
82 | var mockUser = { email: 'test@test.com', password: 'insecure' },
83 | mockEvent = { $event: { user: mockUser } },
84 | mockMessage = 'wrong username or password';
85 |
86 | spyOn(AuthService, 'login').and.callFake(function() {
87 | return $q.reject({ message: mockMessage});
88 | });
89 |
90 | spyOn($state, 'go');
91 |
92 | var promise = controller.loginUser({});
93 |
94 | promise.then(function(result){
95 | expect(AuthService.login).toHaveBeenCalledWith(mockEvent.user);
96 | expect(controller.error).toEqual(mockMessage);
97 | expect($state.go).not.toHaveBeenCalled();
98 | });
99 |
100 | $rootScope.$digest();
101 | });
102 | });
103 | });
--------------------------------------------------------------------------------
/docs/build/partials/api/components.contact/type/ContactEditController.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
17 |
Lorem Ipsum 1
18 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
20 |
Lorem Ipsum 2
21 |
Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper.
22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Methods
40 |
41 |
42 | updateContact(event);
43 |
44 |
45 |
46 |
47 |
48 | Parameters
49 |
50 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Returns
86 |
87 |
88 | method
89 | ContactService returns the updateContact method and a promise
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | deleteContact(event);
99 |
100 |
101 |
102 |
103 |
104 | Parameters
105 |
106 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | Returns
141 |
142 |
143 | method
144 | ContactService returns the deleteContact method and a promise
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
--------------------------------------------------------------------------------
/src/app/components/auth/register/register.spec.js:
--------------------------------------------------------------------------------
1 | describe('Auth', function () {
2 | beforeEach(module('components.auth'));
3 |
4 | beforeEach(module(function ($stateProvider) {
5 | $stateProvider.state('app', { url: '/' });
6 | }));
7 |
8 | describe('Routes', function () {
9 | var $state, $location, $rootScope, AuthService;
10 |
11 | function goTo(url) {
12 | $location.url(url);
13 | $rootScope.$digest();
14 | }
15 |
16 | beforeEach(inject(function ($injector) {
17 | $state = $injector.get('$state');
18 | $location = $injector.get('$location');
19 | $rootScope = $injector.get('$rootScope');
20 | AuthService = $injector.get('AuthService');
21 | }));
22 |
23 | it('should go to auth.register state', function() {
24 | spyOn(AuthService, 'requireAuthentication').and.callFake(
25 | function () {
26 | return $q.when('is authenticated');
27 | }
28 | );
29 | goTo('/auth/register');
30 | expect($state.current.name).toEqual('auth.register')
31 | });
32 |
33 | it('should redirect to app state', function() {
34 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
35 | goTo('/auth/register');
36 | expect($state.current.name).toEqual('app')
37 | });
38 | });
39 |
40 | describe('RegisterController', function () {
41 | var $componentController,
42 | controller,
43 | AuthService,
44 | $state,
45 | $rootScope,
46 | $q;
47 |
48 | beforeEach(inject(function ($injector) {
49 | $componentController = $injector.get('$componentController');
50 | AuthService = $injector.get('AuthService');
51 | $state = $injector.get('$state');
52 | $rootScope = $injector.get('$rootScope');
53 | $q = $injector.get('$q');
54 |
55 | controller = $componentController('register',
56 | { $scope: {}, AuthService: AuthService, $state: $state }
57 | );
58 | }));
59 |
60 | it('should initialize with correct properties', function () {
61 | controller.$onInit();
62 |
63 | expect(controller.error).toBeNull();
64 | expect(controller.user.email).toEqual('');
65 | expect(controller.user.password).toEqual('');
66 | });
67 |
68 | it('should redirect on successful registration ', function () {
69 | var mockUser = { email: 'test@test.com', password: 'insecure' },
70 | mockEvent = { $event: { user: mockUser } };
71 |
72 | spyOn(AuthService, 'register').and.callFake(function() {
73 | return $q.when({$id: 1});
74 | });
75 |
76 | spyOn($state, 'go');
77 |
78 | var promise = controller.createUser(mockEvent);
79 |
80 | promise.then(function(result){
81 | expect(AuthService.register).toHaveBeenCalledWith(mockEvent.user);
82 | expect($state.go).toHaveBeenCalledWith('app');
83 | });
84 |
85 | $rootScope.$digest();
86 | });
87 |
88 | it('should set error on failed login ', function () {
89 | var mockUser = { email: 'test@test.com', password: 'insecure' },
90 | mockEvent = { $event: { user: mockUser } },
91 | mockMessage = 'Oh bollocks!';
92 |
93 | spyOn(AuthService, 'register').and.callFake(function() {
94 | return $q.reject({ message: mockMessage});
95 | });
96 |
97 | spyOn($state, 'go');
98 |
99 | var promise = controller.createUser({});
100 |
101 | promise.then(function(result){
102 | expect(AuthService.register).toHaveBeenCalledWith(mockEvent.user);
103 | expect(controller.error).toEqual(mockMessage);
104 | expect($state.go).not.toHaveBeenCalled();
105 | });
106 |
107 | $rootScope.$digest();
108 | });
109 | });
110 | });
--------------------------------------------------------------------------------
/docs/config/index.js:
--------------------------------------------------------------------------------
1 | // Canonical path provides a consistent path (i.e. always forward slashes) across different OSes
2 | var path = require('canonical-path');
3 |
4 | var Package = require('dgeni').Package;
5 | var packagePath = __dirname;
6 |
7 | // Create and export a new Dgeni package
8 | // We will use Gulp later on to generate that package
9 | // Think of packages as containers, our 'myDoc' package contains other packages
10 | // which themselves include processors, services, templates...
11 | module.exports = new Package('cma-docs', [
12 | require('dgeni-packages/ngdoc'),
13 | require('dgeni-packages/nunjucks')
14 | ])
15 |
16 |
17 | .processor(require('./processors/index-page'))
18 | .processor(require('./processors/guide-data'))
19 | .processor(require('./processors/api-data'))
20 |
21 |
22 | .config(function(log, readFilesProcessor, templateFinder, writeFilesProcessor) {
23 |
24 | // Set the log level to 'info', switch to 'debug' when troubleshooting
25 | log.level = 'info';
26 |
27 | // Specify the base path used when resolving relative paths to source and output files
28 | readFilesProcessor.basePath = path.resolve(packagePath, '../..');
29 |
30 | // Specify our source files that we want to extract
31 | readFilesProcessor.sourceFiles = [
32 | { include: 'src/app/**/**/*.js', basePath: 'src/app' }, // All of our application files
33 |
34 | // Our static Markdown documents
35 | // We are specifying the path and telling Dgeni to use the ngdocFileReader
36 | // to parse the Markdown files to HTMLs
37 | { include: 'docs/content/**/*.md', basePath: 'docs/content', fileReader: 'ngdocFileReader' }
38 | ];
39 |
40 | // Use the writeFilesProcessor to specify the output folder for the extracted files
41 | writeFilesProcessor.outputFolder = 'docs/build';
42 |
43 | })
44 |
45 | .config(function(templateFinder) {
46 | // Specify where the templates are located
47 | templateFinder.templateFolders.unshift(path.resolve(packagePath, 'templates'));
48 | })
49 |
50 | .config(function(computePathsProcessor, computeIdsProcessor) {
51 |
52 | computeIdsProcessor.idTemplates.push({
53 | docTypes: ['content', 'indexPage'],
54 | getId: function(doc) { return doc.fileInfo.baseName; },
55 | getAliases: function(doc) { return [doc.id]; }
56 | });
57 |
58 | // Build custom paths and outputPaths for "content" pages (theming and CSS).
59 | computePathsProcessor.pathTemplates.push({
60 | docTypes: ['content'],
61 | getPath: function(doc) {
62 | var docPath = path.dirname(doc.fileInfo.relativePath);
63 | if (doc.fileInfo.baseName !== 'index') {
64 | docPath = path.join(docPath, doc.fileInfo.baseName);
65 | }
66 | return docPath;
67 | },
68 | outputPathTemplate: 'partials/${path}.html'
69 | });
70 |
71 |
72 | // Here we are defining our docType Module
73 | //
74 | // Each angular module will be extracted to it's own partial
75 | // and will act as a container for the various Components, Controllers, Services in that Module
76 | // We are basically specifying where we want the output files to be located
77 | computePathsProcessor.pathTemplates.push({
78 | docTypes: ['module'],
79 | pathTemplate: '${area}/${name}',
80 | outputPathTemplate: 'partials/${area}/${name}.html'
81 | });
82 |
83 | // Doing the same thing but for regular types like Services, Controllers, etc...
84 | // By default they are grouped in a componentGroup and processed via the generateComponentGroupsProcessor internally in Dgeni
85 | computePathsProcessor.pathTemplates.push({
86 | docTypes: ['componentGroup'],
87 | pathTemplate: '${area}/${moduleName}/${groupType}',
88 | outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html'
89 | });
90 |
91 | })
92 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-edit/contact-edit.spec.js:
--------------------------------------------------------------------------------
1 | describe('Contact', function () {
2 | beforeEach(module('components.auth'));
3 |
4 | beforeEach(module('components.contact', function ($provide) {
5 | $provide.value('ContactService', {
6 | updateContact: angular.noop,
7 | deleteContact: angular.noop,
8 | getContactById: function() {
9 | return {
10 | $loaded: angular.noop
11 | }
12 | }
13 | });
14 |
15 | $provide.value('cfpLoadingBar', {
16 | start: angular.noop,
17 | complete: angular.noop
18 | })
19 |
20 | $provide.value('$window', {
21 | confirm: function() { return true; }
22 | })
23 | }));
24 |
25 | beforeEach(module(function ($stateProvider) {
26 | $stateProvider.state('app', {
27 | redirectTo: 'contacts',
28 | url: '/app',
29 | data: {
30 | requiredAuth: true
31 | }
32 | });
33 | }));
34 |
35 | describe('Routes', function () {
36 | var $state, $location, $rootScope, AuthService;
37 |
38 | function goTo(url) {
39 | $location.url(url);
40 | $rootScope.$digest();
41 | }
42 |
43 | beforeEach(inject(function ($injector) {
44 | $state = $injector.get('$state');
45 | $location = $injector.get('$location');
46 | $rootScope = $injector.get('$rootScope');
47 | AuthService = $injector.get('AuthService');
48 | }));
49 |
50 | it('should go to the contact state', function() {
51 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
52 |
53 | goTo('/app/contact/1');
54 |
55 | expect($state.current.name).toEqual('contact');
56 | });
57 | });
58 |
59 | describe('ContactDetailController', function () {
60 | var $componentController,
61 | controller,
62 | $state,
63 | ContactService,
64 | cfpLoadingBar,
65 | $rootScope,
66 | $q,
67 | mockContact = { $id: 1 };
68 |
69 | beforeEach(inject(function ($injector) {
70 | $componentController = $injector.get('$componentController');
71 | $state = $injector.get('$state');
72 | ContactService = $injector.get('ContactService');
73 | cfpLoadingBar = $injector.get('cfpLoadingBar');
74 | $window = $injector.get('$window');
75 | $rootScope = $injector.get('$rootScope');
76 | $q = $injector.get('$q');
77 |
78 | controller = $componentController('contactEdit',
79 | { $scope: {}, $state: $state, ContactService: ContactService, cfpLoadingBar: cfpLoadingBar },
80 | { contact: mockContact }
81 | );
82 | }));
83 |
84 | it('should update contact', function () {
85 | var event = { contact: { $id: 1 }};
86 | spyOn(cfpLoadingBar, 'start');
87 | spyOn(cfpLoadingBar, 'complete');
88 | spyOn(ContactService, 'updateContact').and.callFake(
89 | function () {
90 | return $q.when({})
91 | }
92 | );
93 |
94 | var promise = controller.updateContact(event);
95 |
96 | expect(cfpLoadingBar.start).toHaveBeenCalled();
97 |
98 | promise.then(function () {
99 | expect(ContactService.updateContact).toHaveBeenCalled();
100 | expect(cfpLoadingBar.complete).toHaveBeenCalled();
101 | });
102 |
103 | $rootScope.$digest();
104 | });
105 |
106 | it('should delete contact', function () {
107 | var event = { contact: { $id: 1, name: 'John Smith' }};
108 | spyOn($state, 'go');
109 |
110 | spyOn(ContactService, 'deleteContact').and.callFake(
111 | function () {
112 | return $q.when({})
113 | }
114 | );
115 |
116 | var promise = controller.deleteContact(event);
117 |
118 | promise.then(function () {
119 | expect($state.go).toHaveBeenCalledWith('contacts');
120 | });
121 |
122 | $rootScope.$digest();
123 | });
124 | });
125 | });
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import concat from 'gulp-concat';
3 | import wrap from 'gulp-wrap';
4 | import uglify from 'gulp-uglify';
5 | import htmlmin from 'gulp-htmlmin';
6 | import gulpif from 'gulp-if';
7 | import sass from 'gulp-sass';
8 | import yargs from 'yargs';
9 | import ngAnnotate from 'gulp-ng-annotate';
10 | import templateCache from 'gulp-angular-templatecache';
11 | import server from 'browser-sync';
12 | import del from 'del';
13 | import path from 'path';
14 | import child from 'child_process';
15 | import Dgeni from 'dgeni';
16 |
17 | const exec = child.exec;
18 | const argv = yargs.argv;
19 | const root = 'src/';
20 | const paths = {
21 | dist: './dist/',
22 | distDocs: './docs/build',
23 | docs: './docs/app/*.js',
24 | scripts: [`${root}/app/**/*.js`, `!${root}/app/**/*.spec.js`],
25 | tests: `${root}/app/**/*.spec.js`,
26 | styles: `${root}/sass/*.scss`,
27 | templates: `${root}/app/**/*.html`,
28 | modules: [
29 | 'angular/angular.js',
30 | 'angular-ui-router/release/angular-ui-router.js',
31 | 'firebase/firebase.js',
32 | 'angularfire/dist/angularfire.js',
33 | 'angular-loading-bar/build/loading-bar.min.js'
34 | ],
35 | static: [
36 | `${root}/index.html`,
37 | `${root}/fonts/**/*`,
38 | `${root}/img/**/*`
39 | ]
40 | };
41 |
42 | server.create();
43 |
44 | gulp.task('clean', cb => del(paths.dist + '**/*', cb));
45 |
46 | gulp.task('cleanDocs', cb => del(paths.distDocs + '**/**/*', cb));
47 |
48 | gulp.task('templates', () => {
49 | return gulp.src(paths.templates)
50 | .pipe(htmlmin({ collapseWhitespace: true }))
51 | .pipe(templateCache({
52 | root: 'app',
53 | standalone: true,
54 | transformUrl: function (url) {
55 | return url.replace(path.dirname(url), '.');
56 | }
57 | }))
58 | .pipe(gulp.dest('./'));
59 | });
60 |
61 | gulp.task('modules', ['templates'], () => {
62 | return gulp.src(paths.modules.map(item => 'node_modules/' + item))
63 | .pipe(concat('vendor.js'))
64 | .pipe(gulpif(argv.deploy, uglify()))
65 | .pipe(gulp.dest(paths.dist + 'js/'));
66 | });
67 |
68 | gulp.task('styles', () => {
69 | return gulp.src(paths.styles)
70 | .pipe(sass({outputStyle: 'compressed'}))
71 | .pipe(gulp.dest(paths.dist + 'css/'));
72 | });
73 |
74 | gulp.task('scripts', ['modules'], () => {
75 | return gulp.src([
76 | `!${root}/app/**/*.spec.js`,
77 | `${root}/app/**/*.module.js`,
78 | ...paths.scripts,
79 | './templates.js'
80 | ])
81 | .pipe(wrap('(function(angular){\n\'use strict\';\n<%= contents %>})(window.angular);'))
82 | .pipe(concat('bundle.js'))
83 | .pipe(ngAnnotate())
84 | .pipe(gulpif(argv.deploy, uglify()))
85 | .pipe(gulp.dest(paths.dist + 'js/'));
86 | });
87 |
88 | gulp.task('serve', () => {
89 | return server.init({
90 | files: [`${paths.dist}/**`],
91 | port: 4000,
92 | server: {
93 | baseDir: paths.dist
94 | }
95 | });
96 | });
97 |
98 | gulp.task('copy', ['clean'], () => {
99 | return gulp.src(paths.static, { base: 'src' })
100 | .pipe(gulp.dest(paths.dist));
101 | });
102 |
103 | gulp.task('watch', ['serve', 'scripts'], () => {
104 | gulp.watch([paths.scripts, paths.templates], ['scripts']);
105 | gulp.watch(paths.styles, ['styles']);
106 | });
107 |
108 | gulp.task('firebase', ['styles', 'scripts'], cb => {
109 | return exec('firebase deploy', function (err, stdout, stderr) {
110 | console.log(stdout);
111 | console.log(stderr);
112 | cb(err);
113 | });
114 | });
115 |
116 | gulp.task('default', [
117 | 'copy',
118 | 'styles',
119 | 'serve',
120 | 'watch'
121 | ]);
122 |
123 | gulp.task('production', [
124 | 'copy',
125 | 'scripts',
126 | 'firebase'
127 | ]);
128 |
129 | gulp.task('copyDocs', () => {
130 | return gulp.src(paths.docs)
131 | .pipe(gulp.dest(paths.distDocs + '/src'));
132 | });
133 |
134 | gulp.task('dgeni', ['cleanDocs', 'copyDocs'], () => {
135 | var dgeni = new Dgeni([require('./docs/config')]);
136 | return dgeni.generate();
137 | });
138 |
--------------------------------------------------------------------------------
/src/app/components/auth/auth.spec.js:
--------------------------------------------------------------------------------
1 | describe('Auth', function () {
2 | beforeEach(module('components.auth'));
3 |
4 | beforeEach(module(function ($stateProvider) {
5 | $stateProvider.state('app', { url: '/' });
6 | }));
7 |
8 | describe('Routes', function () {
9 | var $state, AuthService, $location, $rootScope, $q;
10 |
11 | function goTo(url) {
12 | $location.url(url);
13 | $rootScope.$digest();
14 | }
15 |
16 | beforeEach(inject(function ($injector) {
17 | $state = $injector.get('$state');
18 | AuthService = $injector.get('AuthService');
19 | $location = $injector.get('$location');
20 | $rootScope = $injector.get('$rootScope');
21 | $q = $injector.get('$q');
22 | }));
23 |
24 | it('should redirect to auth.login state', function() {
25 | spyOn(AuthService, 'requireAuthentication').and.callFake(
26 | function () {
27 | return $q.reject('Not authenticated!');
28 | }
29 | );
30 |
31 | goTo('/app');
32 |
33 | expect($state.current.name).toEqual('auth.login')
34 | });
35 |
36 | it('should redirect to app state', function() {
37 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true);
38 |
39 | goTo('/auth');
40 |
41 | expect($state.current.name).toEqual('app')
42 | });
43 | });
44 |
45 | describe('AuthService', function () {
46 | var AuthService, $firebaseAuth, $rootScope;
47 |
48 | beforeEach(inject(function ($injector) {
49 | AuthService = $injector.get('AuthService');
50 | $firebaseAuth = $injector.get('$firebaseAuth')();
51 | $rootScope = $injector.get('$rootScope');
52 | }));
53 |
54 | it('should return undefined for initial user', function () {
55 | expect(AuthService.getUser()).toBeUndefined();
56 | });
57 |
58 | it('should login and store the authenticated user', function () {
59 | var user = { email: 'test@test.com', password: 'insecure' },
60 | response = { $id: 1 },
61 | promise = AuthService.login(user);
62 |
63 | promise.then(function (result) {
64 | expect(result).toEqual(response);
65 | expect(AuthService.isAuthenticated()).toBe(true);
66 | expect(AuthService.getUser()).toEqual(response);
67 | });
68 |
69 | $rootScope.$digest();
70 | });
71 |
72 | it('should properly store auth data when registering a user', function () {
73 | var user = { email: 'test@test.com', password: 'insecure' },
74 | response = { $id: 1 },
75 | promise = AuthService.register(user);
76 |
77 | promise.then(function (result) {
78 | expect(result).toEqual(response);
79 | expect(AuthService.isAuthenticated()).toBe(true);
80 | expect(AuthService.getUser()).toEqual(response);
81 | });
82 |
83 | $rootScope.$digest();
84 | });
85 |
86 | it('should clear auth data when logout is called', function () {
87 | var promise = AuthService.logout();
88 |
89 | promise.then(function (result) {
90 | expect(AuthService.isAuthenticated()).toBe(false);
91 | expect(AuthService.getUser()).toBeUndefined();
92 | });
93 |
94 | $rootScope.$digest();
95 | });
96 |
97 | it('should should return correct auth data on getUser', function () {
98 | var user = { email: 'test@test.com', password: 'insecure' },
99 | response = { $id: 1 },
100 | promise = AuthService.login(user);
101 |
102 | promise.then(function (result) {
103 | expect(AuthService.getUser()).toEqual(response);
104 | });
105 |
106 | $rootScope.$digest();
107 |
108 | promise = AuthService.logout();
109 |
110 | promise.then(function (result) {
111 | expect(AuthService.getUser()).toBeUndefined();
112 | });
113 |
114 | $rootScope.$digest();
115 | });
116 |
117 | it('should should return correct response on requireAuthentication', function () {
118 | var response = { $id: 1 },
119 | promise = AuthService.requireAuthentication();
120 |
121 | promise.then(function (result) {
122 | expect(result).toEqual(response);
123 | });
124 |
125 | $rootScope.$digest();
126 | });
127 |
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/src/app/components/contact/contact-detail/contact-detail.html:
--------------------------------------------------------------------------------
1 |
188 |
--------------------------------------------------------------------------------