├── .gitattributes ├── app ├── .buildignore ├── robots.txt ├── favicon.ico ├── images │ ├── angular.png │ ├── cordova.png │ └── yeoman.png ├── styles │ ├── material.css │ ├── main.css │ └── app.css ├── views │ ├── search.html │ ├── tabs.html │ ├── about.html │ ├── dialog.html │ ├── pulltorefresh.html │ ├── toolbar.html │ ├── main.html │ ├── form.html │ ├── list.html │ └── sidenav.html ├── scripts │ ├── controllers │ │ ├── about.js │ │ ├── main.js │ │ ├── search.js │ │ ├── dialog.js │ │ ├── pulltorefresh.js │ │ └── global.js │ ├── directives │ │ └── scrolltotoponnavclick.js │ ├── app.js │ └── rootScope.js ├── template │ ├── bottom-sheet-list-template.html │ └── bottom-sheet-grid-template.html ├── index.html ├── 404.html └── .htaccess ├── .bowerrc ├── .gitignore ├── .travis.yml ├── docs └── images │ ├── GruntForm.png │ ├── AndroidForm.png │ ├── iPhoneForm.png │ ├── GruntSlideNav.png │ ├── iPhoneAppSlideNav.png │ ├── AndroidAppSlideNav.png │ └── AndroidFormNumberInput.png ├── .jshintrc ├── test ├── spec │ ├── controllers │ │ ├── main.js │ │ ├── about.js │ │ ├── search.js │ │ └── global.js │ ├── directives │ │ └── scrolltotoponnavclick.js │ └── rootScope.js ├── .jshintrc └── karma.conf.js ├── .editorconfig ├── bower.json ├── package.json ├── README.md └── Gruntfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | .tmp 5 | .sass-cache 6 | bower_components 7 | cordova 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install -g grunt-cli' 6 | -------------------------------------------------------------------------------- /app/images/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/app/images/angular.png -------------------------------------------------------------------------------- /app/images/cordova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/app/images/cordova.png -------------------------------------------------------------------------------- /app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/app/images/yeoman.png -------------------------------------------------------------------------------- /docs/images/GruntForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/GruntForm.png -------------------------------------------------------------------------------- /docs/images/AndroidForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/AndroidForm.png -------------------------------------------------------------------------------- /docs/images/iPhoneForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/iPhoneForm.png -------------------------------------------------------------------------------- /docs/images/GruntSlideNav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/GruntSlideNav.png -------------------------------------------------------------------------------- /docs/images/iPhoneAppSlideNav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/iPhoneAppSlideNav.png -------------------------------------------------------------------------------- /docs/images/AndroidAppSlideNav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/AndroidAppSlideNav.png -------------------------------------------------------------------------------- /docs/images/AndroidFormNumberInput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infomofo/cordova-angular-md-template/HEAD/docs/images/AndroidFormNumberInput.png -------------------------------------------------------------------------------- /app/styles/material.css: -------------------------------------------------------------------------------- 1 | /* 2 | Suggestions for tweaks when using material design on mobile 3 | */ 4 | 5 | /* Several interacted elements get dotted borders after interaction, which is not expected on mobile devices */ 6 | md-radio-group { 7 | border:none 8 | } 9 | -------------------------------------------------------------------------------- /app/views/search.html: -------------------------------------------------------------------------------- 1 |
2 |

Searching for:

3 | 4 | 5 | 6 |
7 |

8 |

9 |
10 |
11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /app/scripts/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularCordovaApp.controller:AboutCtrl 6 | * @description 7 | * # AboutCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | angular.module('yoAngularCordovaApp') 11 | .controller('AboutCtrl', function ($scope) { 12 | $scope.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "globals": { 21 | "angular": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularCordovaApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | angular.module('yoAngularCordovaApp') 11 | .controller('MainCtrl', function ($scope) { 12 | console.log('test'); 13 | $scope.awesomeThings = [ 14 | 'HTML5 Boilerplate', 15 | 'AngularJS', 16 | 'Karma' 17 | ]; 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /app/scripts/controllers/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularCordovaApp.controller:SearchCtrl 6 | * @description 7 | * # SearchCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | angular.module('yoAngularCordovaApp') 11 | .controller('SearchCtrl', function ($scope, $routeParams) { 12 | 13 | $scope.searchModel = { 14 | query: null 15 | }; 16 | 17 | $scope.searchModel.query = $routeParams.query; 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /app/template/bottom-sheet-list-template.html: -------------------------------------------------------------------------------- 1 | 2 | Actions 3 | 4 | 5 | 6 | 7 | {{ item.name }} 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/controllers/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: SearchCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | var SearchCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | SearchCtrl = $controller('SearchCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /app/template/bottom-sheet-grid-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |
8 |

{{ item.name }}

9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | .noscroll { 2 | overflow: hidden; 3 | /*position: absolute;*/ 4 | top:0; 5 | } 6 | .toolbar-item { 7 | -webkit-transition: all linear 0.5s; 8 | transition: all linear 0.5s; 9 | max-width:100%; 10 | overflow-x: hidden; 11 | white-space: nowrap; 12 | } 13 | 14 | .toolbar-item.ng-hide { 15 | max-width:0; 16 | } 17 | 18 | .toolbar-item.ng-enter, .toolbar-item.ng-leave { 19 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21 | } 22 | 23 | .toolbar-item.ng-enter, 24 | .toolbar-item.ng-leave.ng-leave-active { 25 | max-width:0; 26 | } 27 | 28 | .toolbar-item.ng-leave, 29 | .toolbar-item.ng-enter.ng-enter-active { 30 | opacity:1; 31 | max-width:100%; 32 | } 33 | -------------------------------------------------------------------------------- /app/scripts/directives/scrolltotoponnavclick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @name yoAngularCordovaApp.directive:scrollToTopOnNavClick 6 | * @description 7 | * # scrollToTopOnNavClick 8 | */ 9 | angular.module('yoAngularCordovaApp') 10 | .directive('scrollToTopOnNavClick', function ($rootScope, $timeout) { 11 | return { 12 | restrict: 'A', 13 | link: function (scope, element) { 14 | $rootScope.$on('NavClicked', function () { 15 | $timeout(function () { 16 | //console.debug('NavClicked detected on ' + element[0].outerHTML + ' has scrollTop ' + element[0].scrollTop); 17 | element[0].scrollTop = 0; 18 | //$window.scrollTo(0,0); 19 | }); 20 | }); 21 | } 22 | }; 23 | }); 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yo-angular-cordova", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.3.15", 6 | "json3": "^3.3.0", 7 | "es5-shim": "^4.0.0", 8 | "angular-animate": "1.3.15", 9 | "angular-cookies": "1.3.15", 10 | "angular-resource": "1.3.15", 11 | "angular-route": "1.3.15", 12 | "angular-sanitize": "1.3.15", 13 | "angular-touch": "1.3.15", 14 | "angular-material": "0.8.3", 15 | "font-awesome": "4.3.0", 16 | "angular-md-pull-to-refresh": "infomofo/angular-md-pull-to-refresh" 17 | }, 18 | "devDependencies": { 19 | "angular-mocks": "~1.3.15", 20 | "angular-scenario": "~1.3.15" 21 | }, 22 | "appPath": "app", 23 | "resolutions": { 24 | "angular": "1.3.15", 25 | "angular-material": "0.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /test/spec/controllers/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: GlobalCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | var GlobalCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | GlobalCtrl = $controller('GlobalCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.searchModel.searchActive).toBe(false); 21 | scope.showSearch(); 22 | expect(scope.searchModel.searchActive).toBe(true); 23 | scope.disableSearch(); 24 | expect(scope.searchModel.searchActive).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/spec/directives/scrolltotoponnavclick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: scrollToTopOnNavClick', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | var scope, element; 9 | 10 | beforeEach(inject(function ($rootScope) { 11 | scope = $rootScope.$new(); 12 | })); 13 | 14 | it('should scroll to top when NavClicked is detected', inject(function ($compile) { 15 | element = angular.element('
div
'); 16 | element = $compile(element)(scope); 17 | //element.scrollTop(10); 18 | //expect(element[0].scrollTop()).toBe(10); 19 | 20 | inject(function($rootScope) { 21 | $rootScope.$emit('NavClicked'); 22 | $rootScope.$digest(); 23 | //expect(element[0].scrollTop()).toBe(0); 24 | }); 25 | })); 26 | }); 27 | -------------------------------------------------------------------------------- /app/styles/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | Styling for the app in general 3 | */ 4 | 5 | .toolbardemoScrollShrink md-list md-item .face { 6 | width: 48px; 7 | margin: 16px; 8 | border-radius: 48px; 9 | border: 1px solid #ddd; } 10 | 11 | /* The search overlay appears when the search bar is active to grey out the rest of the screen */ 12 | .search-overlay { 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | background-color: rgba(0,0,0,0.3); 19 | z-index:1; 20 | } 21 | 22 | md-input-container.search-box.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent label { 23 | padding-top:15px; 24 | } 25 | 26 | md-toolbar { 27 | z-index: 2; 28 | } 29 | 30 | .page { 31 | /*min-height:100%;*/ 32 | /*position:absolute;*/ 33 | z-index: 0; 34 | /*margin-top:63px;*/ 35 | top:0; 36 | bottom:0; 37 | /*padding-bottom: 63px;*/ 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/views/tabs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Tabs

4 |

Click the tab header change between tab contents

5 |
6 | 7 | 8 | 9 |
10 |

A tab of content

11 |
12 |
13 | 14 |
15 |

A third tab of content

16 |
17 |
18 | 19 |
20 |

A second tab of content

21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 |
2 |

About this app

3 |

4 | I'm Angular
5 | This app uses angular and material design! 6 |

7 |

8 | I'm Yeoman
9 | This app was scaffolded initially with Yeoman! 10 |

11 |

12 | 13 | This app uses Font-Awesome! 14 |

15 |

16 | I'm Cordova
17 | This app is built with the cordova framework to run on mobile devices! 18 |

19 |

20 | 21 | This app is hosted on github, where you can find all the source code! 22 |

23 | 24 |
25 | -------------------------------------------------------------------------------- /app/views/dialog.html: -------------------------------------------------------------------------------- 1 | 2 |

Ways to display various messages to a user

3 | 4 |
5 | Dialog 6 |
7 |
8 | Toast 9 |
10 | Toast with Action 11 |
12 |
13 | 14 | Bottom Sheet List 15 | 16 |
17 | 18 | Bottom Sheet Grid 19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /app/views/pulltorefresh.html: -------------------------------------------------------------------------------- 1 | 2 |

Pull to Refresh

3 |
refresh
4 |

5 | This pane demonstrates a pull-to-refresh gesture. Pulling down on this app will trigger a callback called 6 | "refreshFunction" that will wait for 2 seconds and then 7 | add a random quote to the top of this list. While it is refreshing, a material design progress bar will display 8 | at the top of the content area. 9 |

10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 |

19 |

20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /app/views/toolbar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 | 7 | Cordova Angular MD App 8 |
9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Hello!

4 |
5 | 6 |
7 |

Demo App

8 |

9 | This is a demo app testing the use of angular/material widgets in a cordova application container. 10 |

11 | 12 |

How to use this app

13 |

14 |

    15 |
  1. Clone the repo from infomofo/cordova-angular-md-template
  2. 16 |
  3. Run npm install
  4. 17 |
  5. Run grunt
  6. 18 |
  7. From there you can run this application in one of three ways: 19 |
      20 |
    • Run grunt serve to open the application as a standard webpage
    • 21 |
    • Run cd cordova; cordova emulate ios to emulate it on an ios simulator running on your machine
    • 22 |
    • Run cd cordova; cordova run android to emulate it on an android simulator running on your machine, or to a connected android device with debugging options turned on.
    • 23 |
    24 |
  8. 25 |
26 |

27 | 28 |

Application scaffolding and organization

29 |

New directives, filters, routes, and controllers, as well as their respective karma tests can be added with standard yeoman generator-angular commands

30 | 31 |

Other features

32 |

This app will also contain example grunt tests that can be run on an integration server, such as Travis.

33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yoangularcordova", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "bower": "^1.3.12", 7 | "cca": "^0.5.1", 8 | "cordova": "^4.2.0", 9 | "extend": "^2.0.0", 10 | "grunt": "^0.4.1", 11 | "grunt-autoprefixer": "^0.7.3", 12 | "grunt-cli": "^0.1.13", 13 | "grunt-concurrent": "^0.5.0", 14 | "grunt-contrib-clean": "^0.5.0", 15 | "grunt-contrib-concat": "^0.4.0", 16 | "grunt-contrib-connect": "^0.7.1", 17 | "grunt-contrib-copy": "^0.5.0", 18 | "grunt-contrib-cssmin": "^0.9.0", 19 | "grunt-contrib-htmlmin": "^0.3.0", 20 | "grunt-contrib-imagemin": "^0.8.1", 21 | "grunt-contrib-jshint": "^0.10.0", 22 | "grunt-contrib-uglify": "^0.4.0", 23 | "grunt-contrib-watch": "^0.6.1", 24 | "grunt-cordovacli": "^0.7.0", 25 | "grunt-filerev": "^0.2.1", 26 | "grunt-google-cdn": "^0.4.0", 27 | "grunt-karma": "^0.9.0", 28 | "grunt-newer": "^0.7.0", 29 | "grunt-ng-annotate": "^0.4.0", 30 | "grunt-shell": "^1.1.1", 31 | "grunt-svgmin": "^0.4.0", 32 | "grunt-usemin": "^2.1.1", 33 | "grunt-wiredep": "^1.7.0", 34 | "jshint-stylish": "^0.2.0", 35 | "karma": "^0.12.25", 36 | "karma-chrome-launcher": "^0.1.7", 37 | "karma-jasmine": "^0.2.3", 38 | "karma-phantomjs-launcher": "^0.1.4", 39 | "load-grunt-tasks": "^0.4.0", 40 | "time-grunt": "^0.3.1", 41 | "xmldom": "^0.1.19" 42 | }, 43 | "engines": { 44 | "node": ">=0.10.0" 45 | }, 46 | "scripts": { 47 | "test": "grunt test", 48 | "postinstall": "git clean -fdx bower_components && ./node_modules/bower/bin/bower install && ./node_modules/grunt-cli/bin/grunt build" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/views/form.html: -------------------------------------------------------------------------------- 1 | 2 |

Various form elements

3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | Switch 1: {{data.cb1}} 26 | 27 | 28 | 29 | Checkbox 1: {{ data.cb2 }} 30 | 31 |
32 |
33 |

Selected Value: {{ data.group1 }}

34 | 35 | Apple 36 | Banana 37 | Mango 38 | 39 |
40 |
41 |

Rating: {{rating}}/5

42 | 43 | 44 |
45 |
46 | -------------------------------------------------------------------------------- /app/views/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | Scroll-shrinking subheader 6 |

7 |
8 |
9 | 10 |

List

11 |

This pane demonstrates a common list pane. When you scroll down the inner toolbar disappears.

12 | 13 | 14 | who 15 | 16 |

Paracosm

17 |

18 | The titles of Washed Out's breakthrough song and the first single from Paracosm share the 19 | two most important words in Ernest Greene's musical language: feel it. It's a simple request, as well... 20 |

21 |
22 |
23 | 24 | 25 | 26 |
27 | who 28 |
29 |
30 |

1/5/2015 01:00:00 PM

31 |

infomofo

32 |

33 | this is a list item! 34 |

35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 |
44 | 45 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * app.js 5 | * 6 | * This file will contain the module definition, routing, and themeing for the application 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name yoAngularCordovaApp 12 | * @description 13 | * # yoAngularCordovaApp 14 | * 15 | * Main module of the application. 16 | */ 17 | var app = angular 18 | .module('yoAngularCordovaApp', [ 19 | 'ngAnimate', 20 | 'ngCookies', 21 | 'ngResource', 22 | 'ngRoute', 23 | 'ngSanitize', 24 | 'ngTouch', 25 | 'ngMaterial', 26 | 'infomofo.angularMdPullToRefresh' 27 | ]); 28 | 29 | app 30 | .config(function($compileProvider){ 31 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|chrome-extension):/); 32 | }) 33 | .config(function ($routeProvider) { 34 | $routeProvider 35 | .when('/', { 36 | templateUrl: 'views/main.html', 37 | controller: 'MainCtrl' 38 | }) 39 | .when('/pulltorefresh', { 40 | templateUrl: 'views/pulltorefresh.html', 41 | controller: 'PullToRefreshCtrl' 42 | }) 43 | .when('/about', { 44 | templateUrl: 'views/about.html', 45 | controller: 'AboutCtrl' 46 | }) 47 | .when('/list', { 48 | templateUrl: 'views/list.html' 49 | }) 50 | .when('/form', { 51 | templateUrl: 'views/form.html' 52 | }) 53 | .when('/dialog', { 54 | templateUrl: 'views/dialog.html', 55 | controller: 'DialogCtrl' 56 | }) 57 | .when('/tabs', { 58 | templateUrl: 'views/tabs.html' 59 | }) 60 | .when('/search/:query', { 61 | templateUrl: 'views/search.html', 62 | controller: 'SearchCtrl' 63 | }) 64 | .otherwise({ 65 | redirectTo: '/' 66 | }); 67 | }); 68 | 69 | app 70 | .config(function($mdThemingProvider) { 71 | $mdThemingProvider.theme('default') 72 | .primaryPalette('green') 73 | .accentPalette('orange'); 74 | }); 75 | -------------------------------------------------------------------------------- /test/spec/rootScope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('rootScope', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yoAngularCordovaApp')); 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($httpBackend) { 10 | $httpBackend.when('GET', 'views/about.html').respond('about'); 11 | $httpBackend.when('GET', 'views/list.html').respond('list'); 12 | $httpBackend.when('GET', 'views/main.html').respond('main'); 13 | })); 14 | 15 | 16 | it('should obey history logic', function () { 17 | 18 | inject(function($route, $location, $rootScope) { 19 | expect($rootScope.isHistoryEmpty()).toBe(true); 20 | expect($rootScope.isHistory()).toBe(false); 21 | $rootScope.go('/about'); 22 | $rootScope.$digest(); 23 | expect($route.current.controller).toBe('AboutCtrl'); 24 | expect($route.current.templateUrl).toBe('views/about.html'); 25 | expect($route.current.originalPath).toBe('/about'); 26 | expect($rootScope.isHistoryEmpty()).toBe(true); 27 | expect($rootScope.isHistory()).toBe(false); 28 | 29 | $rootScope.go('/list'); 30 | $rootScope.$digest(); 31 | expect($route.current.templateUrl).toBe('views/list.html'); 32 | expect($route.current.originalPath).toBe('/list'); 33 | 34 | $rootScope.backFunction(); 35 | $rootScope.$digest(); 36 | expect($route.current.controller).toBe('MainCtrl'); 37 | expect($route.current.templateUrl).toBe('views/main.html'); 38 | expect($route.current.originalPath).toBe('/'); 39 | expect($rootScope.isHistoryEmpty()).toBe(true); 40 | expect($rootScope.isHistory()).toBe(false); 41 | 42 | $rootScope.go('/list'); 43 | $rootScope.$digest(); 44 | expect($route.current.templateUrl).toBe('views/list.html'); 45 | expect($route.current.originalPath).toBe('/list'); 46 | 47 | $rootScope.clearHistory(); 48 | $rootScope.$digest(); 49 | }); 50 | 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /app/scripts/controllers/dialog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularChromeApp.controller:DialogCtrl 6 | * @description 7 | * # DialogCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | console.log('test'); 11 | angular.module('yoAngularCordovaApp') 12 | .controller('DialogCtrl', function ($scope, $mdBottomSheet) { 13 | console.log('initialized dialog ctrl'); 14 | $scope.showListBottomSheet = function($event) { 15 | console.log('show list'); 16 | $scope.alert = ''; 17 | $mdBottomSheet.show({ 18 | templateUrl: 'template/bottom-sheet-list-template.html', 19 | controller: 'ListBottomSheetCtrl', 20 | targetEvent: $event 21 | }).then(function(clickedItem) { 22 | $scope.alert = clickedItem.name + ' clicked!'; 23 | }); 24 | }; 25 | $scope.showGridBottomSheet = function($event) { 26 | $scope.alert = ''; 27 | $mdBottomSheet.show({ 28 | templateUrl: 'template/bottom-sheet-grid-template.html', 29 | controller: 'GridBottomSheetCtrl', 30 | targetEvent: $event 31 | }).then(function(clickedItem) { 32 | $scope.alert = clickedItem.name + ' clicked!'; 33 | }); 34 | }; 35 | }) 36 | .controller('ListBottomSheetCtrl', function($scope, $mdBottomSheet) { 37 | $scope.items = [ 38 | { name: 'Share', icon: 'share' }, 39 | { name: 'Upload', icon: 'upload' }, 40 | { name: 'Copy', icon: 'copy' }, 41 | { name: 'Print this page', icon: 'print' }, 42 | ]; 43 | $scope.listItemClick = function($index) { 44 | var clickedItem = $scope.items[$index]; 45 | $mdBottomSheet.hide(clickedItem); 46 | }; 47 | }) 48 | .controller('GridBottomSheetCtrl', function($scope, $mdBottomSheet) { 49 | $scope.items = [ 50 | { name: 'Hangout', icon: 'google' }, 51 | { name: 'Mail', icon: 'envelope' }, 52 | { name: 'Message', icon: 'whatsapp' }, 53 | { name: 'Copy', icon: 'copy' }, 54 | { name: 'Facebook', icon: 'facebook' }, 55 | { name: 'Twitter', icon: 'twitter' }, 56 | ]; 57 | $scope.listItemClick = function($index) { 58 | var clickedItem = $scope.items[$index]; 59 | $mdBottomSheet.hide(clickedItem); 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /app/scripts/rootScope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * rootScope.js 5 | * 6 | * This file is for all functions that need to be globally accessible or access shared memory between routes. 7 | */ 8 | angular.module('yoAngularCordovaApp') 9 | .run(function ($rootScope, $location, $mdToast, $mdSidenav) { 10 | 11 | var history = []; 12 | 13 | $rootScope.$on('$routeChangeSuccess', function() { 14 | 15 | var path = $location.$$path; 16 | //tracker.sendAppView(path); 17 | if (path !== '/') { 18 | history.push($location.$$path); 19 | } 20 | }); 21 | 22 | var backFunction = function() { 23 | var prevUrl = history.length > 1 ? history.splice(-2)[0] : '/'; 24 | $location.path(prevUrl); 25 | }; 26 | 27 | $rootScope.backFunction = function () { 28 | backFunction(); 29 | }; 30 | 31 | 32 | var onResume = function() { 33 | $rootScope.$emit('Resumed'); 34 | }; 35 | 36 | //document.addEventListener("deviceready", function() { 37 | console.log('deviceready'); 38 | document.addEventListener('resume', onResume, false); 39 | document.addEventListener('online', onResume, false); 40 | // detect application touches and emit an event on rootscope: 41 | window.addEventListener('statusTap', function () { 42 | $rootScope.$emit('NavClicked'); 43 | }); 44 | //}); 45 | 46 | /** 47 | * Clears the known history 48 | */ 49 | $rootScope.clearHistory = function() { 50 | history = []; 51 | }; 52 | 53 | /** 54 | * Returns if there is no history left to return to 55 | * @returns {boolean} 56 | */ 57 | $rootScope.isHistoryEmpty = function() { 58 | return history.length <= 1; 59 | }; 60 | 61 | /** 62 | * Returns if there is eligible history to go back to 63 | * @returns {boolean} 64 | */ 65 | $rootScope.isHistory = function() { 66 | return history.length > 1; 67 | }; 68 | 69 | /** 70 | * This function should be called to change views- this will retain history and encapsulate any other behaviors. 71 | * @param url 72 | */ 73 | $rootScope.go = function (url) { 74 | // Hide any active toasts when the route changes 75 | $mdToast.hide(); 76 | 77 | $mdSidenav('left').close() 78 | .then(function(){ 79 | }); 80 | $location.path(url); 81 | }; 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-11-16 using 4 | // generator-karma 0.8.3 5 | 6 | module.exports = function(config) { 7 | 'use strict'; 8 | 9 | config.set({ 10 | // enable / disable watching file and executing tests whenever any file changes 11 | autoWatch: true, 12 | 13 | // base path, that will be used to resolve files and exclude 14 | basePath: '../', 15 | 16 | // testing framework to use (jasmine/mocha/qunit/...) 17 | frameworks: ['jasmine'], 18 | 19 | // list of files / patterns to load in the browser 20 | files: [ 21 | 'bower_components/angular/angular.js', 22 | 'bower_components/angular-mocks/angular-mocks.js', 23 | 'bower_components/angular-animate/angular-animate.js', 24 | 'bower_components/angular-cookies/angular-cookies.js', 25 | 'bower_components/angular-resource/angular-resource.js', 26 | 'bower_components/angular-route/angular-route.js', 27 | 'bower_components/angular-sanitize/angular-sanitize.js', 28 | 'bower_components/angular-touch/angular-touch.js', 29 | 'bower_components/angular-material/angular-material.js', 30 | 'bower_components/angular-aria/angular-aria.js', 31 | 'bower_components/angular-md-pull-to-refresh/angular-md-pull-to-refresh.js', 32 | 'app/scripts/**/*.js', 33 | 'test/mock/**/*.js', 34 | 'test/spec/**/*.js' 35 | ], 36 | 37 | // list of files / patterns to exclude 38 | exclude: [], 39 | 40 | // web server port 41 | port: 8080, 42 | 43 | // Start these browsers, currently available: 44 | // - Chrome 45 | // - ChromeCanary 46 | // - Firefox 47 | // - Opera 48 | // - Safari (only Mac) 49 | // - PhantomJS 50 | // - IE (only Windows) 51 | browsers: [ 52 | //'Chrome', 53 | 'PhantomJS' 54 | ], 55 | 56 | // Which plugins to enable 57 | plugins: [ 58 | //'karma-chrome-launcher', 59 | 'karma-phantomjs-launcher', 60 | 'karma-jasmine' 61 | ], 62 | 63 | // Continuous Integration mode 64 | // if true, it capture browsers, run tests and exit 65 | singleRun: false, 66 | 67 | colors: true, 68 | 69 | // level of logging 70 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 71 | logLevel: config.LOG_INFO, 72 | 73 | // Uncomment the following lines if you are using grunt's server to run the tests 74 | // proxies: { 75 | // '/': 'http://localhost:9000/' 76 | // }, 77 | // URL root prevent conflicts with the site root 78 | // urlRoot: '_karma_' 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 |
30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

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

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/views/sidenav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | About 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 |

Home

14 |

15 | The application's landing screen 16 |

17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 |

About

29 |

30 | About this app 31 |

32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 | Demo 55 | 56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 |

Dialog

64 |

65 | Various methods of alerting users to information 66 |

67 |
68 |
69 |
70 |
71 | 72 | 73 | 74 |
75 | 76 |
77 |
78 |

Forms

79 |

80 | Demonstrations of input elements 81 |

82 |
83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 | 91 |
92 |
93 |

List

94 |

95 | Demonstration of a list of content 96 |

97 |
98 |
99 |
100 |
101 | 102 | 103 | 104 |
105 | 106 |
107 |
108 |

Tabs

109 |

110 | Demonstration of a pane with several tabs 111 |

112 |
113 |
114 |
115 |
116 | 117 | 118 | 119 |
120 | 121 |
122 |
123 |

Pull To Refresh

124 |

125 | Demonstration of pull-to-refresh functionality 126 |

127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | YO ANGULAR CORDOVA 2 | ================= 3 | 4 | Follow the commit history of this project to learn how to build a yeoman angular cordova app. 5 | 6 | [![Build Status](https://travis-ci.org/infomofo/cordova-angular-md-template.svg?branch=master)](https://travis-ci.org/infomofo/cordova-angular-md-template) 7 | 8 | Sample screenshots 9 | ------------------ 10 | 11 | ### Grunt 12 | ![A Demo Material-Angular App running in Grunt](docs/images/GruntSlideNav.png) 13 | ![A Form running in Grunt](docs/images/GruntForm.png) 14 | 15 | ### iPhone 16 | ![A Demo Material-Angular App running on iOS](docs/images/iPhoneAppSlideNav.png) 17 | ![A Form running on iOS](docs/images/iPhoneForm.png) 18 | 19 | ### Android 20 | ![A Demo Material-Angular App running on Android](docs/images/AndroidAppSlideNav.png) 21 | ![A Form running in Android](docs/images/AndroidFormNumberInput.png) 22 | 23 | Dependencies 24 | ------------ 25 | 26 | You may need to run some of the following to get all the dependencies for this application 27 | 28 | * npm install -g grunt-cli bower cordova 29 | 30 | For ios development you will need the following 31 | 32 | * npm install -g ios-deploy 33 | * npm install -g ios-sim 34 | 35 | For android development you will need 36 | 37 | * brew install android 38 | * android 39 | * download packages and set up an android device for emulation 40 | 41 | To build a cordova app 42 | --------------------- 43 | 44 | 1. npm install 45 | 2. grunt 46 | 47 | To run on android 48 | ----------------- 49 | 50 | grunt cordovacli:runAndroid 51 | 52 | To emulate on ios 53 | ----------------- 54 | 55 | grunt cordovacli:emulateIos 56 | 57 | To run on ios 58 | ------------- 59 | 60 | grunt cordovacli:runIos 61 | 62 | How to use this repository 63 | -------------------------- 64 | 65 | This repo is meant to demo material design application patterns on a mobile device and a browser. You can also build 66 | an application of your own using this repository as the backbone. One suggested workflow for this would be: 67 | 68 | 1. Fork this repository 69 | - alternately, duplicate this repository and add this repo as an upstream ref 70 | 2. Modify the section of Gruntfile.js that looks like this: 71 | 72 | var customConfig = { 73 | // The following variables can be customized for an application that forks this repo 74 | appName: 'YoAngularCordova', 75 | appPackage: 'com.sample.YoAngularCordova', 76 | plugins: [ 77 | 'https://github.com/j-mcnally/cordova-statusTap', 78 | 'org.apache.cordova.statusbar' 79 | ], 80 | platforms: ['ios', 'android'], 81 | statusBarBackgroundColor: '#388E3C' // Should be the 700 color for your main color http://www.google.com/design/spec/style/color.html#color-color-palette 82 | }; 83 | 84 | Specifically, it is important to have a unique appPackage and recommended to have a unique appName. You will also 85 | want to replace any references to application name in package.json and bower.json with your application's name. 86 | 87 | 3. Modify package.json and bower.json to replace the generic application name "yoangularcordova" with your application's name. 88 | 89 | 4. Modify the application scaffolding in app/views/sidenav.html, app/scripts/app.js, and the relevant controllers. 90 | 91 | 5. Periodically, you will want to pull any changes made from the upstream repository into your application. [Here](https://stackoverflow.com/questions/3903817/pull-new-updates-from-original-github-repository-into-forked-github-repository/3903835#3903835) 92 | is a detailed guide on various ways to do that. I'd recommend using: 93 | 94 | git rebase upstream/master 95 | 96 | to replay your application's changes onto the template. 97 | 98 | 6. If you make any core functionality changes or improvements to the app that you believe others would benefit from, 99 | I recommend forking the repo and making a pull request into the original so that others may benefit from it. 100 | 101 | Directory Structure 102 | ------------------- 103 | 104 | * /app - 105 | * /scripts 106 | * app.js - contains the basic angular application, angular modules loaded, routing information, theme and 107 | configuration 108 | * rootScope.js - contains any functions or variables bound to rootScope (in general this should be avoided and 109 | these should be isolated to services 110 | * /controllers 111 | * global.js - contains the controller for the global application, controlling behavior that is shared across 112 | application panes (i.e. search, sidenav, toasts) 113 | * main.js - contains the controller for the main landing page 114 | * about.js - contains the controller for the about page 115 | * /directives 116 | * scrolltotoponnavclick.js - contains a directive for instructing a scrollable html container to scroll 117 | to top when a "NavClicked" event is emitted on rootScope 118 | * /styles - contains css (this will all be merged and minified when you build the app) 119 | * /views - contains all the application panes and module html 120 | * /images - contains any images used in the application itself 121 | * /docs - contains any images or extra data needed for the README documentation 122 | * /test - contains karma tests for your angular application 123 | * package.json - contains the node dependencies 124 | * .bower.json - contains the bower dependencies 125 | * Gruntfile.js - contains the build process for this application 126 | 127 | Modifications 128 | ------------- 129 | 130 | Additions to the app can be made with the standard [yeoman generator-angular](https://github.com/yeoman/generator-angular) 131 | commands. For example, a new route, tab, and controller can be added with the command yo angular:route myroute 132 | 133 | Generated files not in git 134 | -------------------------- 135 | 136 | * node_modules - node modules - these are generated by npm install 137 | * bower_components - bower dependencies - these are generated by grunt 138 | * dist - the generated application 139 | * cordova - the generated cordova mobile applications for ios and android 140 | -------------------------------------------------------------------------------- /app/scripts/controllers/pulltorefresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularCordovaApp.controller:PullToRefreshCtrl 6 | * @description 7 | * # PullToRefreshCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | angular.module('yoAngularCordovaApp') 11 | .controller('PullToRefreshCtrl', function ($scope, $q) { 12 | 13 | 14 | $scope.listItems = []; 15 | 16 | var icons = [ 17 | 'anchor', 18 | 'archive', 19 | 'area-chart', 20 | 'arrows', 21 | 'arrows-h', 22 | 'arrows-v', 23 | 'asterisk', 24 | 'at', 25 | 'ban', 26 | 'bar-chart', 27 | 'barcode', 28 | 'bars', 29 | 'bed', 30 | 'beer', 31 | 'bell', 32 | 'bell-o', 33 | 'bell-slash', 34 | 'bell-slash-o', 35 | 'bicycle', 36 | 'binoculars', 37 | 'birthday-cake', 38 | 'bolt', 39 | 'bomb', 40 | 'book', 41 | 'bookmark', 42 | 'bookmark-o', 43 | 'briefcase', 44 | 'bug', 45 | 'building', 46 | 'building-o', 47 | 'bullhorn', 48 | 'bullseye', 49 | 'bus', 50 | 'cab', 51 | 'calculator', 52 | 'calendar', 53 | 'calendar-o', 54 | 'camera', 55 | 'camera-retro', 56 | 'car', 57 | 'caret-square-o-down', 58 | 'caret-square-o-left', 59 | 'caret-square-o-right', 60 | 'caret-square-o-up', 61 | 'cart-arrow-down', 62 | 'cart-plus', 63 | 'cc', 64 | 'certificate', 65 | 'check', 66 | 'check-circle', 67 | 'check-circle-o', 68 | 'check-square', 69 | 'check-square-o', 70 | 'child', 71 | 'circle', 72 | 'circle-o', 73 | 'circle-o-notch', 74 | 'circle-thin', 75 | 'clock-o', 76 | 'close', 77 | 'cloud', 78 | 'cloud-download', 79 | 'cloud-upload', 80 | 'code', 81 | 'code-fork', 82 | 'coffee', 83 | 'cog', 84 | 'cogs', 85 | 'comment', 86 | 'comment-o', 87 | 'comments', 88 | 'comments-o', 89 | 'compass', 90 | 'copyright', 91 | 'credit-card', 92 | 'crop', 93 | 'crosshairs', 94 | 'cube', 95 | 'cubes', 96 | 'cutlery', 97 | 'dashboard', 98 | 'database', 99 | 'desktop', 100 | 'diamond', 101 | 'dot-circle-o', 102 | 'download', 103 | 'edit', 104 | 'ellipsis-h', 105 | 'ellipsis-v', 106 | 'envelope', 107 | 'envelope-o', 108 | 'envelope-square', 109 | 'eraser', 110 | 'exchange', 111 | 'exclamation', 112 | 'exclamation-circle', 113 | 'exclamation-triangle', 114 | 'external-link', 115 | 'external-link-square', 116 | 'eye', 117 | 'eye-slash', 118 | 'eyedropper', 119 | 'fax', 120 | 'female', 121 | 'fighter-jet', 122 | 'file-archive-o', 123 | 'file-audio-o', 124 | 'file-code-o', 125 | 'file-excel-o', 126 | 'file-image-o', 127 | 'file-movie-o', 128 | 'file-pdf-o', 129 | 'file-photo-o', 130 | 'file-picture-o', 131 | 'file-powerpoint-o', 132 | 'file-sound-o', 133 | 'file-video-o', 134 | 'file-word-o', 135 | 'file-zip-o', 136 | 'film', 137 | 'filter', 138 | 'fire', 139 | 'fire-extinguisher', 140 | 'flag', 141 | 'flag-checkered', 142 | 'flag-o', 143 | 'flash', 144 | 'flask', 145 | 'folder', 146 | 'folder-o', 147 | 'folder-open', 148 | 'folder-open-o', 149 | 'frown-o', 150 | 'futbol-o', 151 | 'gamepad', 152 | 'gavel', 153 | 'gear', 154 | 'gears', 155 | 'genderless', 156 | 'gift', 157 | 'glass', 158 | 'globe', 159 | 'graduation-cap', 160 | 'group', 161 | 'hdd-o', 162 | 'headphones', 163 | 'heart', 164 | 'heart-o', 165 | 'heartbeat', 166 | 'history', 167 | 'home', 168 | 'hotel', 169 | 'image', 170 | 'inbox', 171 | 'info', 172 | 'info-circle', 173 | 'institution', 174 | 'key', 175 | 'keyboard-o', 176 | 'language', 177 | 'laptop', 178 | 'leaf', 179 | 'legal', 180 | 'lemon-o', 181 | 'level-down', 182 | 'level-up', 183 | 'life-bouy', 184 | 'life-buoy', 185 | 'life-ring', 186 | 'life-saver', 187 | 'lightbulb-o', 188 | 'line-chart', 189 | 'location-arrow', 190 | 'lock', 191 | 'magic', 192 | 'magnet', 193 | 'mail-forward', 194 | 'mail-reply', 195 | 'mail-reply-all', 196 | 'male', 197 | 'map-marker', 198 | 'meh-o', 199 | 'microphone', 200 | 'microphone-slash', 201 | 'minus', 202 | 'minus-circle', 203 | 'minus-square', 204 | 'minus-square-o', 205 | 'mobile', 206 | 'mobile-phone', 207 | 'money', 208 | 'moon-o', 209 | 'mortar-board', 210 | 'motorcycle', 211 | 'music', 212 | 'navicon', 213 | 'newspaper-o', 214 | 'paint-brush', 215 | 'paper-plane', 216 | 'paper-plane-o', 217 | 'paw', 218 | 'pencil', 219 | 'pencil-square', 220 | 'pencil-square-o', 221 | 'phone', 222 | 'phone-square', 223 | 'photo', 224 | 'picture-o', 225 | 'pie-chart', 226 | 'plane', 227 | 'plug', 228 | 'plus', 229 | 'plus-circle', 230 | 'plus-square', 231 | 'plus-square-o', 232 | 'power-off', 233 | 'print', 234 | 'puzzle-piece', 235 | 'qrcode', 236 | 'question', 237 | 'question-circle', 238 | 'quote-left', 239 | 'quote-right', 240 | 'random', 241 | 'recycle', 242 | 'refresh', 243 | 'remove', 244 | 'reorder', 245 | 'reply', 246 | 'reply-all', 247 | 'retweet', 248 | 'road', 249 | 'rocket', 250 | 'rss', 251 | 'rss-square', 252 | 'search', 253 | 'search-minus', 254 | 'search-plus', 255 | 'send', 256 | 'send-o', 257 | 'server', 258 | 'share', 259 | 'share-alt', 260 | 'share-alt-square', 261 | 'share-square', 262 | 'share-square-o', 263 | 'shield', 264 | 'ship', 265 | 'shopping-cart', 266 | 'sign-in', 267 | 'sign-out', 268 | 'signal', 269 | 'sitemap', 270 | 'sliders', 271 | 'smile-o', 272 | 'soccer-ball-o', 273 | 'sort', 274 | 'sort-alpha-asc', 275 | 'sort-alpha-desc', 276 | 'sort-amount-asc', 277 | 'sort-amount-desc', 278 | 'sort-asc', 279 | 'sort-desc', 280 | 'sort-down', 281 | 'sort-numeric-asc', 282 | 'sort-numeric-desc', 283 | 'sort-up', 284 | 'space-shuttle', 285 | 'spinner', 286 | 'spoon', 287 | 'square', 288 | 'square-o', 289 | 'star', 290 | 'star-half', 291 | 'star-half-empty', 292 | 'star-half-full', 293 | 'star-half-o', 294 | 'star-o', 295 | 'street-view', 296 | 'suitcase', 297 | 'sun-o', 298 | 'support', 299 | 'tablet', 300 | 'tachometer', 301 | 'tag', 302 | 'tags', 303 | 'tasks', 304 | 'taxi', 305 | 'terminal', 306 | 'thumb-tack', 307 | 'thumbs-down', 308 | 'thumbs-o-down', 309 | 'thumbs-o-up', 310 | 'thumbs-up', 311 | 'ticket', 312 | 'times', 313 | 'times-circle', 314 | 'times-circle-o', 315 | 'tint', 316 | 'toggle-down', 317 | 'toggle-left', 318 | 'toggle-off', 319 | 'toggle-on', 320 | 'toggle-right', 321 | 'toggle-up', 322 | 'trash', 323 | 'trash-o', 324 | 'tree', 325 | 'trophy', 326 | 'truck', 327 | 'tty', 328 | 'umbrella', 329 | 'university', 330 | 'unlock', 331 | 'unlock-alt', 332 | 'unsorted', 333 | 'upload', 334 | 'user', 335 | 'user-plus', 336 | 'user-secret', 337 | 'user-times', 338 | 'users', 339 | 'video-camera', 340 | 'volume-down', 341 | 'volume-off', 342 | 'volume-up', 343 | 'warning', 344 | 'wheelchair', 345 | 'wifi', 346 | 'wrench' 347 | ]; 348 | 349 | 350 | var random = function(elementArray) { 351 | return elementArray[Math.floor(Math.random()*elementArray.length)]; 352 | }; 353 | $scope.randomItem = function() { 354 | $scope.listItems.unshift({ 355 | icon: random(icons), 356 | text: random($scope.quotes), 357 | time: new Date() 358 | }); 359 | }; 360 | 361 | for (var i = 0; i < 20; i++) { 362 | $scope.randomItem(); 363 | } 364 | 365 | $scope.refreshFunction = function() { 366 | var deferred = $q.defer(); 367 | setTimeout(function() { 368 | $scope.randomItem(); 369 | deferred.resolve(true); 370 | }, 2000); 371 | return deferred.promise; 372 | }; 373 | 374 | }); 375 | -------------------------------------------------------------------------------- /app/scripts/controllers/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yoAngularCordovaApp.controller:GlobalCtrl 6 | * @description 7 | * # GlobalCtrl 8 | * Controller of the yoAngularCordovaApp 9 | */ 10 | angular.module('yoAngularCordovaApp') 11 | .controller('GlobalCtrl', function ($scope, $rootScope, $mdSidenav, $mdToast, $mdDialog){ 12 | 13 | $scope.searchModel = { 14 | searchActive: false, 15 | searchQuery: null 16 | }; 17 | 18 | $scope.clickNav = function() { 19 | // Publish an event that can be handled by directives 20 | $rootScope.$emit('NavClicked'); 21 | }; 22 | 23 | /** 24 | * Closes the mdSidenav and handles any related behavior 25 | */ 26 | $scope.closeSideNav = function(){ 27 | $mdSidenav('left').close() 28 | .then(function(){ 29 | }); 30 | }; 31 | 32 | /** 33 | * A replacement for the sidenav toggle button if it is replaced with a hamburger action. 34 | */ 35 | $scope.handleHamburger = function() { 36 | if ($rootScope.isHistoryEmpty()) { 37 | $scope.toggleSideNav(); 38 | } else { 39 | $rootScope.backFunction(true); 40 | } 41 | }; 42 | 43 | $scope.openSidenav = function() { 44 | $mdToast.hide(); 45 | $mdSidenav('left').open() 46 | .then(function(){ 47 | }); 48 | }; 49 | 50 | $scope.executeSearch = function() { 51 | var path = '/search/' + $scope.searchModel.searchQuery; 52 | console.debug('go to path ' + path); 53 | $scope.disableSearch(); 54 | // lose focus on search bar 55 | $rootScope.go(path); 56 | }; 57 | 58 | $scope.disableSearch = function() { 59 | //var searchBox = angular.element('#searchBox'); 60 | //searchBox.blur(); 61 | $scope.searchModel.searchActive = false; 62 | $scope.searchModel.searchQuery = null; 63 | }; 64 | 65 | /** 66 | * Closes the mdSidenav and handles any related behavior 67 | */ 68 | $scope.closeSideNav = function(){ 69 | $mdSidenav('left').close() 70 | .then(function(){ 71 | }); 72 | }; 73 | 74 | /** 75 | * A replacement for the sidenav toggle button if it is replaced with a hamburger action. 76 | */ 77 | $scope.handleHamburger = function() { 78 | if ($rootScope.isHistoryEmpty()) { 79 | $scope.toggleSideNav(); 80 | } else { 81 | $rootScope.backFunction(true); 82 | } 83 | }; 84 | 85 | /** 86 | * Handles the event of clicking on the nav bar- this is a common application convention that will make the active 87 | * scrollable container scroll to the top. 88 | */ 89 | $scope.$on('NavClicked', function() { 90 | var domElement = document.getElementById('scrollcontainer'); 91 | domElement.style.overflow = 'hidden'; 92 | // wait for any current momentum scrolling to finish and then jump to top 93 | //$('#scrollcontainer').animate({scrollTop: 0}, 'fast'); 94 | domElement.style.overflow = ''; 95 | }); 96 | 97 | /** 98 | * Displays the search box element on the toolbar 99 | */ 100 | $scope.showSearch = function() { 101 | //var searchBox = angular.element('#searchBox'); 102 | //searchBox.focus(); 103 | $scope.searchModel.searchActive = true; 104 | }; 105 | 106 | $scope.toggleSearch = function() { 107 | if ($scope.searchModel.searchActive) { 108 | $scope.disableSearch(); 109 | } else { 110 | $scope.showSearch(); 111 | } 112 | }; 113 | 114 | /** 115 | * Displays an alert toast in the bottom right that disappears after 3 seconds 116 | * 117 | * Suitable for displaying short unactionable messages to the user 118 | * 119 | * @param message The alert message to display to the user 120 | */ 121 | $scope.showAlertToast = function(message) { 122 | var toast = $mdToast.simple() 123 | .content(message) 124 | .highlightAction(false) 125 | .position('bottom right') 126 | .hideDelay(2000); 127 | $mdToast.show(toast); 128 | }; 129 | 130 | /** 131 | * Displays an alert toast in the bottom right that disappears when dismissed by the user 132 | * 133 | * Suitable for displaying short unactionable messages to the user 134 | * 135 | * @param message The alert message to display to the user 136 | */ 137 | $scope.showAlertToastPersistent = function(message) { 138 | var toast = $mdToast.simple() 139 | .content(message) 140 | .highlightAction(false) 141 | .position('bottom right') 142 | .hideDelay(0); 143 | $mdToast.show(toast); 144 | }; 145 | 146 | /** 147 | * Displays an undoable toast in the bottom right that disappears after 3 seconds 148 | * 149 | * @param message the message to display to the user 150 | * @param callback the function to call when the undo action is clicked 151 | */ 152 | $scope.showUndoToast = function(message, callback) { 153 | var toast = $mdToast.simple() 154 | .content(message) 155 | .action('undo') 156 | .highlightAction(false) 157 | .position('bottom right') 158 | .hideDelay(2000); 159 | $mdToast.show(toast).then(function() { 160 | callback(true); 161 | }); 162 | }; 163 | 164 | /** 165 | * Displays an undoable toast in the bottom right that disappears when dismissed by the user 166 | * 167 | * @param message the message to display to the user 168 | * @param callback the function to call when the undo action is clicked 169 | */ 170 | $scope.showUndoToastPersistent = function(message, callback) { 171 | var toast = $mdToast.simple() 172 | .content(message) 173 | .action('undo') 174 | .highlightAction(false) 175 | .position('bottom right') 176 | .hideDelay(0); 177 | $mdToast.show(toast).then(function() { 178 | callback(true); 179 | }); 180 | }; 181 | 182 | /** 183 | * Displays an alert modal dialog 184 | * 185 | * Suitable for displaying short messages to the user 186 | * 187 | * @param title The title of the dialog box 188 | * @param message The alert message to display to the user 189 | * @param ev An event to animate the dialog box from 190 | */ 191 | $scope.showAlertDialog = function(title, message, ev) { 192 | console.log(ev); 193 | $mdDialog.show( 194 | $mdDialog.alert() 195 | .title(title) 196 | .content(message) 197 | .ariaLabel('A modal dialog box: ' + message) 198 | .ok('close') 199 | //.targetEvent(ev) 200 | ); 201 | }; 202 | 203 | var backButton = function() { 204 | console.debug('back button'); 205 | $rootScope.backFunction(); 206 | $rootScope.$apply(); 207 | }; 208 | document.addEventListener('backbutton', backButton, false); 209 | 210 | $scope.quotes = [ 211 | 'Michael: You\'re wearing ostrich-skin boots.', 212 | 'Lindsay: Well, I don\'t care about ostriches.', 213 | 'Lucille Bluth: I\'ll be in the hospital bar.', 214 | 'Michael: Uh, you know there isn\'t a hospital bar, Mother.', 215 | 'Lucille: Well, this is why people hate hospitals. [Cackles at her own wit]', 216 | 'Buster: She\'s just wigged out because I have a girlfriend.', 217 | 'Lucille: A waiter hands him a note and suddenly he\'s Steve McQueen. He doesn\'t even know what she looks like.', 218 | 'Buster: I know that she\'s a brownish area with points.', 219 | 'Michael: You\'re just jumping right into this, huh?', 220 | 'Buster: That\'s what you do when life hands you a chance to be with someone special. You just grab that brownish area by its points and you don\'t let go no matter what your mom says.', 221 | 'Prison warden: You really think you can break out of my prison?', 222 | 'Gob: You won\'t even know I was here.', 223 | 'Narrator: The warden was intrigued. Less about the stunt, and more about the prison beatings this brash magician was sure to receive.', 224 | 'Michael: Hey, speaking of kidding, How serious are you about Marta? I get the sense that there’s not much of a future there. Am I reading that right?', 225 | 'Gob: [from inside the prison] Let me ask you something. How would you feel if I came down on you hard?', 226 | 'Michael: You\'re saying I\'m not reading this right.', 227 | 'Gob: No, I\'m saying move the bike. I need to jump on you to break my fall.', 228 | 'Marta: I just couldn\'t find my keys.', 229 | 'Michael: Well, my brother may have eaten them.', 230 | 231 | 'Gob: Take off your glasses. Oh... wait, wait. Let down your hair. No: glasses on, hair back up. Let\'s just get that hair right back up.', 232 | 'Kitty: Let me turn the lights off.', 233 | 'Gob: Yes, yes, please.', 234 | 'Kitty: How\'s that? Is that better?', 235 | 'Gob: It just seems like there\'s still light coming in from under the door.', 236 | 'George Sr.: I just haven\'t had sex in a month.', 237 | 'Michael: You know, you\'ve been here two months.', 238 | 'George Sr.: It\'s hard to gauge time.', 239 | 'Michael: Yeah, I\'ll bet.', 240 | 'Gob: I [bleep]ed Kitty!', 241 | 'Michael: Gob! I just wanted you to get the information.', 242 | 'Gob: I got the information.', 243 | 'Michael: You did, huh? About the international accounts?', 244 | 'Gob: Oh, I see what you\'re getting at. No, I didn\'t get any information.', 245 | 'Kitty: Gob? I wish I would have know you were coming. I am a mess.', 246 | 'Gob: I don\'t know if a call from me would\'ve changed that.', 247 | 'Kitty: Do you like my outfit?', 248 | 'Gob: Not as much as I like what\'s underneath it.', 249 | 'Kitty: Gob!', 250 | 'Gob: No, I need your chair. Get up.', 251 | 252 | 'Michael: Come on, face it. You just do all this charity crap just to stroke your ego. You don\'t even know what the auction\'s for tonight.', 253 | 'Lindsay: The wetlands.', 254 | 'Michael: To do what with them?', 255 | 'Lindsay: Dry them.', 256 | 'Michael: Save them.', 257 | 'Lindsay: From drying.', 258 | 'Lindsay: Look, I screwed up, ok? I\'m lost, and I hate them. I hate the Wetlands. They\'re stupid and wet, and there are bugs everywhere, and I think I maced a crane, Michael.', 259 | 'Narrator: Tobias had intended to park the family’s only vehicle at the airport parking lot, but was waved onto the tarmac instead, where he found a spot close to his gate.', 260 | 'Lucille: Supposedly, Luz had to take her daughter to the hospital. That’s Lupe, her sister.', 261 | 'Michael: I hope she’s okay.', 262 | 'Lucille: She’s awful. Can barely wash a dish.', 263 | 'Lindsay: (loudly) Well, how embarrassing. My own brother buying me? I\'d rather die. (whispers to Michael) Thank you. Maybe you\'re not that selfish.', 264 | 265 | '[at the prison]', 266 | 'Barry Zuckerkorn: Are all the guys in here... you know? [referencing gay inmates]', 267 | 'George Sr.: Oh, no, no. No, not all of them.', 268 | 'Barry Zuckerkorn: Yeah. It\'s never the ones you hope.', 269 | 'George Sr.: Hope?', 270 | 'Barry Zuckerkorn: Think.', 271 | 'Lucille: What\'d she do, get you drunk?', 272 | 'Michael: No, we just, uh, well... we — we did drink a little bit. How\'d you know that?', 273 | 'Lucille: Because that\'s what she said she\'d do. I said you wouldn\'t give her the money, and she said, "He will if I get him drunk." Probably because she thinks you\'re a cheap bastard. Oh... her words.', 274 | 'Wayne Jarvis: I use one adjective to describe myself, what is it?', 275 | 'Michael: Professional.', 276 | '[Jarvis shakes head and walks out]', 277 | 'Michael: Did I just wake you up? I didn’t even know you were home.', 278 | 'Lindsay: No, Michael, I don’t just sleep all day.', 279 | 'Narrator: Actually, Lindsay was so upset at Michael that she tried meditating to calm herself but ended up taking a two-hour angry nap.', 280 | 'Michael: I love Marta.', 281 | 'Lindsay: Mom\'s housekeeper?', 282 | 'Wayne Jarvis: I shall duck behind the couch.', 283 | 'Michael: What a pro.', 284 | 'Wayne Jarvis: I shall duck behind that little garbage can.', 285 | 'Michael: Guy\'s a pro.', 286 | 'Michael: [to Lucille] Oh, um... there\'s a big bowl of candy in my office. Why don\'t you go eat it?', 287 | 'Wayne Jarvis: Wayne Jarvis, attorney at law. I have a responsibility to tell you that there is no candy in this office.', 288 | 'Gob: [to Michael] Well, if it isn\'t the boy who lives under the stairs.', 289 | 290 | 'Michael: Hey, Mom. Remember we had that conversation about trying to cut back on things that aren\'t necessities?', 291 | 'Lucille: Like it was yesterday.', 292 | 'Michael: It was this morning.', 293 | 'Lucille: You\'re my third least favorite child.', 294 | 'Michael: I can live with that.', 295 | '[Speaking of the surprise party for Lucille]', 296 | 'Maeby: We [George Michael and Maeby] don\'t have to go, do we?', 297 | 'Michael: This is a Bluth family celebration. It\'s no place for children.', 298 | 'Michael: I can\'t believe she got that driver\'s license renewed.', 299 | 'Gob: She didn\'t. I dummied her up a new one. Not my best work, though. She wanted to look 48. I nearly airbrushed her into oblivion. Ended up checking “albino” in the form.', 300 | 'Lindsay: It would just give Dad one more reason to think that I\'ve got nothing to offer but my looks.', 301 | 'Gob: Yeah, I got some of that. Except he also didn\'t like my looks.', 302 | 303 | 'Buster: And I\'m going to continue dating, Mom.', 304 | 'Michael: It sounds a little bit like "dating Mom."', 305 | 'Buster: It\'s starting to feel a little like it.', 306 | 'Michael: Since when are you against leather?', 307 | 'Maeby: Yeah, you\'re not even a vegetarian.', 308 | 'Lindsay: Well, I\'m not against the insides. I mean, people need meat to survive.', 309 | 'Michael: You are aware they don\'t remove it from the cow surgically, right?', 310 | 'Man at the store: May I help you?', 311 | 'Tobias: Oh, I hope so. Um, I\'m looking for something that says, "Dad likes leather."', 312 | 'Man: Something that says, "leather daddy"?', 313 | 'Tobias: Oh, is there such a thing?', 314 | 'Michael: I\'m a saint, you know. I\'m a living saint, and I get absolutely nothing out of it.', 315 | 'Lindsay: Well, you get a false feeling of superiority', 316 | 'Michael: That is nice, but this time it\'s not enough.', 317 | 'Cab driver: Where to, mate?', 318 | 'Tobias: The Gothic Castle.', 319 | 'Cab driver: Gothic Asshole?', 320 | 'Tobias: That\'s what I said.', 321 | 'Rollo: If you care about your brother, you\'ll get in this car.', 322 | 'Michael: Which brother?', 323 | 'Rollo: Gob.', 324 | '[Michael leaves]', 325 | 'Lucille: You\'re the only one who chose a spouse I liked and she had to die.', 326 | 'Michael: I know, that\'s rough for you.' 327 | 328 | ]; 329 | }); 330 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-11-16 using generator-angular 0.10.0 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | var extend = require('extend'); 19 | 20 | var customConfig = { 21 | // The following variables can be customized for an application that forks this repo 22 | appName: 'YoAngularCordova', 23 | appPackage: 'com.sample.YoAngularCordova', 24 | plugins: [ 25 | 'https://github.com/j-mcnally/cordova-statusTap', 26 | 'org.apache.cordova.statusbar' 27 | ], 28 | platforms: ['ios', 'android'], 29 | statusBarBackgroundColor: '#388E3C' // Should be the 700 color for your main color http://www.google.com/design/spec/style/color.html#color-color-palette 30 | }; 31 | 32 | var appConfig = extend (true, 33 | { 34 | app: require('./bower.json').appPath || 'app', 35 | dist: 'dist', 36 | cordova: 'cordova' 37 | }, customConfig); 38 | 39 | // Define the configuration for all the tasks 40 | grunt.initConfig({ 41 | 42 | // Project settings 43 | yeoman: appConfig, 44 | 45 | // Watches files for changes and runs tasks based on the changed files 46 | watch: { 47 | bower: { 48 | files: ['bower.json'], 49 | tasks: ['wiredep'] 50 | }, 51 | js: { 52 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 53 | tasks: ['newer:jshint:all'], 54 | options: { 55 | livereload: '<%= connect.options.livereload %>' 56 | } 57 | }, 58 | jsTest: { 59 | files: ['test/spec/{,*/}*.js'], 60 | tasks: ['newer:jshint:test', 'karma'] 61 | }, 62 | styles: { 63 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 64 | tasks: ['newer:copy:styles', 'autoprefixer'] 65 | }, 66 | gruntfile: { 67 | files: ['Gruntfile.js'] 68 | }, 69 | livereload: { 70 | options: { 71 | livereload: '<%= connect.options.livereload %>' 72 | }, 73 | files: [ 74 | '<%= yeoman.app %>/{,*/}*.html', 75 | '.tmp/styles/{,*/}*.css', 76 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 77 | ] 78 | } 79 | }, 80 | 81 | // The actual grunt server settings 82 | connect: { 83 | options: { 84 | port: 9000, 85 | // Change this to '0.0.0.0' to access the server from outside. 86 | hostname: 'localhost', 87 | livereload: 35729 88 | }, 89 | livereload: { 90 | options: { 91 | open: true, 92 | middleware: function (connect) { 93 | return [ 94 | connect.static('.tmp'), 95 | connect().use( 96 | '/bower_components', 97 | connect.static('./bower_components') 98 | ), 99 | connect.static(appConfig.app) 100 | ]; 101 | } 102 | } 103 | }, 104 | test: { 105 | options: { 106 | port: 9001, 107 | middleware: function (connect) { 108 | return [ 109 | connect.static('.tmp'), 110 | connect.static('test'), 111 | connect().use( 112 | '/bower_components', 113 | connect.static('./bower_components') 114 | ), 115 | connect.static(appConfig.app) 116 | ]; 117 | } 118 | } 119 | }, 120 | dist: { 121 | options: { 122 | open: true, 123 | base: '<%= yeoman.dist %>' 124 | } 125 | } 126 | }, 127 | 128 | // Make sure code styles are up to par and there are no obvious mistakes 129 | jshint: { 130 | options: { 131 | jshintrc: '.jshintrc', 132 | reporter: require('jshint-stylish') 133 | }, 134 | all: { 135 | src: [ 136 | 'Gruntfile.js', 137 | '<%= yeoman.app %>/scripts/{,*/}*.js' 138 | ] 139 | }, 140 | test: { 141 | options: { 142 | jshintrc: 'test/.jshintrc' 143 | }, 144 | src: ['test/spec/{,*/}*.js'] 145 | } 146 | }, 147 | 148 | // Empties folders to start fresh 149 | clean: { 150 | dist: { 151 | files: [{ 152 | dot: true, 153 | src: [ 154 | '.tmp', 155 | '<%= yeoman.dist %>/{,*/}*', 156 | '!<%= yeoman.dist %>/.git{,*/}*' 157 | ] 158 | }] 159 | }, 160 | server: '.tmp' 161 | }, 162 | 163 | // Add vendor prefixed styles 164 | autoprefixer: { 165 | options: { 166 | browsers: ['last 1 version'] 167 | }, 168 | dist: { 169 | files: [{ 170 | expand: true, 171 | cwd: '.tmp/styles/', 172 | src: '{,*/}*.css', 173 | dest: '.tmp/styles/' 174 | }] 175 | } 176 | }, 177 | 178 | // Automatically inject Bower components into the app 179 | wiredep: { 180 | app: { 181 | src: ['<%= yeoman.app %>/index.html'], 182 | exclude: [ 183 | 'bower_components/bootstrap/dist/js/bootstrap.js' 184 | //'bower_components/jquery/dist/jquery.js' 185 | ], 186 | ignorePath: /\.\.\// 187 | } 188 | }, 189 | 190 | // Renames files for browser caching purposes 191 | filerev: { 192 | dist: { 193 | src: [ 194 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 195 | '<%= yeoman.dist %>/styles/{,*/}*.css', 196 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 197 | '<%= yeoman.dist %>/styles/fonts/*' 198 | ] 199 | } 200 | }, 201 | 202 | // Reads HTML for usemin blocks to enable smart builds that automatically 203 | // concat, minify and revision files. Creates configurations in memory so 204 | // additional tasks can operate on them 205 | useminPrepare: { 206 | html: '<%= yeoman.app %>/index.html', 207 | options: { 208 | dest: '<%= yeoman.dist %>', 209 | flow: { 210 | html: { 211 | steps: { 212 | js: ['concat', 'uglifyjs'], 213 | css: ['cssmin'] 214 | }, 215 | post: {} 216 | } 217 | } 218 | } 219 | }, 220 | 221 | // Performs rewrites based on filerev and the useminPrepare configuration 222 | usemin: { 223 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 224 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 225 | options: { 226 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] 227 | } 228 | }, 229 | 230 | // The following *-min tasks will produce minified files in the dist folder 231 | // By default, your `index.html`'s will take care of 232 | // minification. These next options are pre-configured if you do not wish 233 | // to use the Usemin blocks. 234 | // cssmin: { 235 | // dist: { 236 | // files: { 237 | // '<%= yeoman.dist %>/styles/main.css': [ 238 | // '.tmp/styles/{,*/}*.css' 239 | // ] 240 | // } 241 | // } 242 | // }, 243 | // uglify: { 244 | // dist: { 245 | // files: { 246 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 247 | // '<%= yeoman.dist %>/scripts/scripts.js' 248 | // ] 249 | // } 250 | // } 251 | // }, 252 | // concat: { 253 | // dist: {} 254 | // }, 255 | 256 | imagemin: { 257 | dist: { 258 | files: [{ 259 | expand: true, 260 | cwd: '<%= yeoman.app %>/images', 261 | src: '{,*/}*.{png,jpg,jpeg,gif}', 262 | dest: '<%= yeoman.dist %>/images' 263 | }] 264 | } 265 | }, 266 | 267 | svgmin: { 268 | dist: { 269 | files: [{ 270 | expand: true, 271 | cwd: '<%= yeoman.app %>/images', 272 | src: '{,*/}*.svg', 273 | dest: '<%= yeoman.dist %>/images' 274 | }] 275 | } 276 | }, 277 | 278 | htmlmin: { 279 | dist: { 280 | options: { 281 | collapseWhitespace: true, 282 | conservativeCollapse: true, 283 | collapseBooleanAttributes: true, 284 | removeCommentsFromCDATA: true, 285 | removeOptionalTags: true 286 | }, 287 | files: [{ 288 | expand: true, 289 | cwd: '<%= yeoman.dist %>', 290 | src: ['*.html', 'views/{,*/}*.html'], 291 | dest: '<%= yeoman.dist %>' 292 | }] 293 | } 294 | }, 295 | 296 | // ng-annotate tries to make the code safe for minification automatically 297 | // by using the Angular long form for dependency injection. 298 | ngAnnotate: { 299 | dist: { 300 | files: [{ 301 | expand: true, 302 | cwd: '.tmp/concat/scripts', 303 | src: ['*.js', '!oldieshim.js'], 304 | dest: '.tmp/concat/scripts' 305 | }] 306 | } 307 | }, 308 | 309 | // Replace Google CDN references 310 | cdnify: { 311 | dist: { 312 | html: ['<%= yeoman.dist %>/*.html'] 313 | } 314 | }, 315 | 316 | // Copies remaining files to places other tasks can use 317 | copy: { 318 | dist: { 319 | files: [{ 320 | expand: true, 321 | dot: true, 322 | cwd: '<%= yeoman.app %>', 323 | dest: '<%= yeoman.dist %>', 324 | src: [ 325 | '*.{ico,png,txt}', 326 | '.htaccess', 327 | '*.html', 328 | 'views/{,*/}*.html', 329 | 'template/{,*/}*.html', 330 | 'images/{,*/}*.{webp}', 331 | 'fonts/{,*/}*.*' 332 | ] 333 | }, { 334 | expand: true, 335 | cwd: '.tmp/images', 336 | dest: '<%= yeoman.dist %>/images', 337 | src: ['generated/*'] 338 | }, { 339 | expand: true, 340 | cwd: 'bower_components/font-awesome', 341 | src: 'fonts/*', 342 | dest: '<%= yeoman.dist %>' 343 | }] 344 | }, 345 | styles: { 346 | expand: true, 347 | cwd: '<%= yeoman.app %>/styles', 348 | dest: '.tmp/styles/', 349 | src: '{,*/}*.css' 350 | } 351 | }, 352 | 353 | // Run some tasks in parallel to speed up the build process 354 | concurrent: { 355 | server: [ 356 | 'copy:styles' 357 | ], 358 | test: [ 359 | 'copy:styles' 360 | ], 361 | dist: [ 362 | 'copy:styles', 363 | 'imagemin', 364 | 'svgmin' 365 | ] 366 | }, 367 | 368 | // Test settings 369 | karma: { 370 | unit: { 371 | configFile: 'test/karma.conf.js', 372 | singleRun: true 373 | } 374 | }, 375 | cordovacli: { 376 | options: { 377 | path: '<%= yeoman.cordova %>', 378 | id: '<%= yeoman.appPackage %>', 379 | name: '<%= yeoman.appName %>', 380 | platforms: appConfig.platforms 381 | }, 382 | cordova: { 383 | options: { 384 | command: ['create','platform','plugin','build'], 385 | } 386 | }, 387 | create: { 388 | options: { 389 | command: 'create', 390 | args: ['--copy-from=<%= yeoman.dist %>'] 391 | } 392 | }, 393 | addPlatforms: { 394 | options: { 395 | command: 'platform', 396 | action: 'add' 397 | } 398 | }, 399 | addPlugins: { 400 | options: { 401 | command: 'plugin', 402 | action: 'add', 403 | plugins: appConfig.plugins 404 | } 405 | }, 406 | build: { 407 | options: { 408 | command: 'build' 409 | } 410 | }, 411 | emulateAndroid: { 412 | options: { 413 | command: 'emulate', 414 | platforms: ['android'] 415 | } 416 | }, 417 | emulateIos: { 418 | options: { 419 | command: 'emulate', 420 | platforms: ['ios'] 421 | } 422 | }, 423 | runAndroid: { 424 | options: { 425 | command: 'run', 426 | platforms: ['android'] 427 | } 428 | }, 429 | runIos: { 430 | options: { 431 | command: 'run', 432 | platforms: ['ios'] 433 | } 434 | } 435 | }, 436 | shell: { 437 | cordovaClean: { 438 | command: 'rm -Rf <%= yeoman.cordova %>' 439 | } 440 | } 441 | }); 442 | grunt.loadNpmTasks('grunt-cordovacli'); 443 | 444 | //var xpath = require('xpath'); 445 | var DOMParser = require('xmldom').DOMParser; 446 | grunt.registerTask('addCordovaPreferences', function(){ 447 | var cordovaConfig = appConfig.cordova + '/config.xml'; 448 | var xml = grunt.file.read(cordovaConfig); 449 | var doc = new DOMParser().parseFromString(xml); 450 | 451 | var node = doc.getElementsByTagName('widget')[0]; 452 | 453 | //var author = node.getElementsByTagName("author")[0]; 454 | //author.setAttribute('email','contact@example.com'); 455 | //author.setAttribute('href','http://example.com'); 456 | //author.nodeValue='contact@example.com'; 457 | 458 | // grunt.log.writeln('Adding to: ' + node); 459 | var KeyboardShrinksView = doc.createElement('preference'); 460 | KeyboardShrinksView.setAttribute('name', 'KeyboardShrinksView'); 461 | KeyboardShrinksView.setAttribute('value', true); 462 | grunt.log.writeln('Adding: ' + KeyboardShrinksView); 463 | node.appendChild(KeyboardShrinksView); 464 | 465 | var StatusBarOverlaysWebView = doc.createElement('preference'); 466 | StatusBarOverlaysWebView.setAttribute('name', 'StatusBarOverlaysWebView'); 467 | StatusBarOverlaysWebView.setAttribute('value', false); 468 | grunt.log.writeln('Adding: ' + StatusBarOverlaysWebView); 469 | node.appendChild(StatusBarOverlaysWebView); 470 | 471 | var StatusBarBackgroundColor = doc.createElement('preference'); 472 | StatusBarBackgroundColor.setAttribute('name', 'StatusBarBackgroundColor'); 473 | StatusBarBackgroundColor.setAttribute('value', appConfig.statusBarBackgroundColor); 474 | grunt.log.writeln('Adding: ' + StatusBarBackgroundColor); 475 | node.appendChild(StatusBarBackgroundColor); 476 | 477 | var KeyboardDisplayRequiresUserAction = doc.createElement('preference'); 478 | KeyboardDisplayRequiresUserAction.setAttribute('name', 'KeyboardDisplayRequiresUserAction'); 479 | KeyboardDisplayRequiresUserAction.setAttribute('value', false); 480 | grunt.log.writeln('Adding: ' + KeyboardDisplayRequiresUserAction); 481 | node.appendChild(KeyboardDisplayRequiresUserAction); 482 | 483 | var DisallowOverscroll = doc.createElement('preference'); 484 | DisallowOverscroll.setAttribute('name', 'DisallowOverscroll'); 485 | DisallowOverscroll.setAttribute('value', true); 486 | grunt.log.writeln('Adding: ' + DisallowOverscroll); 487 | node.appendChild(DisallowOverscroll); 488 | 489 | grunt.file.write(cordovaConfig, doc); 490 | }); 491 | 492 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 493 | if (target === 'dist') { 494 | return grunt.task.run(['build', 'connect:dist:keepalive']); 495 | } 496 | 497 | grunt.task.run([ 498 | 'clean:server', 499 | 'wiredep', 500 | 'concurrent:server', 501 | 'autoprefixer', 502 | 'connect:livereload', 503 | 'watch' 504 | ]); 505 | }); 506 | 507 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 508 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 509 | grunt.task.run(['serve:' + target]); 510 | }); 511 | 512 | grunt.registerTask('test', [ 513 | 'clean:server', 514 | 'concurrent:test', 515 | 'autoprefixer', 516 | 'connect:test', 517 | 'karma' 518 | ]); 519 | 520 | grunt.registerTask('build', [ 521 | 'clean:dist', 522 | 'wiredep', 523 | 'useminPrepare', 524 | 'concurrent:dist', 525 | 'autoprefixer', 526 | 'concat', 527 | 'ngAnnotate', 528 | 'copy:dist', 529 | 'cdnify', 530 | 'cssmin', 531 | 'uglify', 532 | 'filerev', 533 | 'usemin', 534 | 'htmlmin' 535 | ]); 536 | 537 | grunt.registerTask('default', [ 538 | 'newer:jshint', 539 | // 'test', 540 | 'build', 541 | 'cordova' 542 | ]); 543 | 544 | grunt.registerTask('cordova', 545 | [ 546 | 'shell:cordovaClean', 547 | 'cordovacli:create', 548 | 'addCordovaPreferences', 549 | 'cordovacli:addPlatforms', 550 | 'cordovacli:addPlugins', 551 | 'cordovacli:build' 552 | ]); 553 | }; 554 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | --------------------------------------------------------------------------------