├── .jshintignore ├── .bowerrc ├── .temp └── styles │ ├── main.css │ └── style.css ├── app ├── styles │ ├── main.css │ └── style.css ├── images │ └── ionic.png ├── templates │ ├── top.html │ ├── home.html │ ├── login.html │ └── menu.html ├── scripts │ ├── configuration.js │ ├── home.js │ ├── user_services.js │ ├── auth.js │ └── app.js └── index.html ├── ionic.project ├── resources └── ios │ ├── icons │ ├── icon.png │ ├── icon-40.png │ ├── icon-50.png │ ├── icon-60.png │ ├── icon-72.png │ ├── icon-76.png │ ├── icon@2x.png │ ├── icon-40@2x.png │ ├── icon-50@2x.png │ ├── icon-60@2x.png │ ├── icon-60@3x.png │ ├── icon-72@2x.png │ ├── icon-76@2x.png │ ├── icon-small.png │ └── icon-small@2x.png │ └── splash │ ├── Default-667h.png │ ├── Default-736h.png │ ├── Default~iphone.png │ ├── Default@2x~iphone.png │ ├── Default-Portrait~ipad.png │ ├── Default-568h@2x~iphone.png │ ├── Default-Landscape-736h.png │ ├── Default-Landscape~ipad.png │ ├── Default-Portrait@2x~ipad.png │ └── Default-Landscape@2x~ipad.png ├── .gitignore ├── .editorconfig ├── bower.json ├── .jshintrc ├── config.xml ├── hooks ├── after_plugin_rm │ ├── deregister_plugins.js │ └── 010_deregister_plugin.js ├── before_platform_add │ └── init_directories.js ├── after_plugin_add │ ├── register_plugins.js │ └── 010_register_plugin.js ├── after_platform_add │ ├── install_plugins.js │ └── 010_install_plugins.js ├── after_prepare │ ├── 020_remove_sass_from_platforms.js │ ├── icons_and_splashscreens.js │ ├── 010_add_platform_class.js │ └── update_platform_config.js └── README.md ├── test └── .jshintrc ├── README.md ├── LICENSE ├── package.json └── Gruntfile.js /.jshintignore: -------------------------------------------------------------------------------- 1 | app/scripts/config.js 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.temp/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Your application styles go here */ 2 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Your application styles go here */ 2 | -------------------------------------------------------------------------------- /.temp/styles/style.css: -------------------------------------------------------------------------------- 1 | /* Empty. Add your own CSS if you like */ 2 | -------------------------------------------------------------------------------- /app/styles/style.css: -------------------------------------------------------------------------------- 1 | /* Empty. Add your own CSS if you like */ 2 | -------------------------------------------------------------------------------- /ionic.project: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic_rails_sample", 3 | "app_id": "" 4 | } -------------------------------------------------------------------------------- /app/images/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/app/images/ionic.png -------------------------------------------------------------------------------- /resources/ios/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icons/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon@2x.png -------------------------------------------------------------------------------- /app/templates/top.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Top

4 |
5 |
-------------------------------------------------------------------------------- /resources/ios/icons/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/icons/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwako/ionic_rails_sample/HEAD/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /app/scripts/configuration.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('config', []) 4 | 5 | .constant('ENV', {name:'development',apiEndpoint:'http://localhost:3000/api/v1'}) 6 | 7 | ; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | www 3 | .idea 4 | .tmp 5 | .sass-cache 6 | app/bower_components 7 | coverage 8 | platforms 9 | plugins 10 | *.swp 11 | *.swo 12 | *.log 13 | *.DS_Store 14 | 15 | app/scripts/config.js 16 | -------------------------------------------------------------------------------- /app/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{user.email}} 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/scripts/home.js: -------------------------------------------------------------------------------- 1 | angular.module('sample.home', []) 2 | 3 | .controller('HomeCtrl', function($scope, User) { 4 | this.userService = new User(serverErrorHandler); 5 | this.userService.all().$promise.then(function(result) { 6 | return $scope.users = result.users; 7 | }); 8 | 9 | var serverErrorHandler = function() { 10 | return console.log("There was a server error."); 11 | }; 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IonicRailsSample", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "ionic": "v1.0.0-beta.14", 6 | "ngCordova": "0.1.12-alpha", 7 | "ng-token-auth": "latest", 8 | "angular-resource": "latest" 9 | }, 10 | "devDependencies": { 11 | "angular-mocks": "~1.3.6", 12 | "angular-scenario": "~1.3.6" 13 | }, 14 | "resolutions": { 15 | "angular": "1.3.6" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false, 23 | "cordova": false, 24 | "StatusBar": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | IonicRailsSample 4 | 5 | A sample Apache Cordova application that responds to the deviceready event. 6 | 7 | 8 | Apache Cordova Team 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /hooks/after_plugin_rm/deregister_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Remove plugins from cordovaPlugins array after_plugin_rm 5 | */ 6 | var fs = require('fs'); 7 | var _ = require('lodash'); 8 | var packageJSON = require('../../package.json'); 9 | 10 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 11 | _.each(process.env.CORDOVA_PLUGINS.split(','), function (plugin) { 12 | _.remove(packageJSON.cordovaPlugins, function (p) { return p === plugin; }); 13 | }); 14 | 15 | fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2)); 16 | -------------------------------------------------------------------------------- /hooks/before_platform_add/init_directories.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * On a fresh clone, the local platforms/ and plugins/ directories will be 5 | * missing, so ensure they get created before the first platform is added. 6 | */ 7 | var mkdirp = require('mkdirp'); 8 | var path = require('path'); 9 | 10 | var platformsDir = path.resolve(__dirname, '../../platforms'); 11 | var pluginsDir = path.resolve(__dirname, '../../plugins'); 12 | 13 | mkdirp(platformsDir, function (err) { 14 | if (err) { console.error(err); } 15 | }); 16 | 17 | mkdirp(pluginsDir, function (err) { 18 | if (err) { console.error(err); } 19 | }); 20 | -------------------------------------------------------------------------------- /hooks/after_plugin_add/register_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Push plugins to cordovaPlugins array after_plugin_add 5 | */ 6 | var fs = require('fs'); 7 | var packageJSON = require('../../package.json'); 8 | 9 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 10 | 11 | var fromEnv = process.env.CORDOVA_PLUGINS.split(','); 12 | for (var i = 0; i < fromEnv.length; i++) { 13 | var plugin = fromEnv[i]; 14 | 15 | if (packageJSON.cordovaPlugins.indexOf(plugin) !== -1) { 16 | packageJSON.cordovaPlugins.push(plugin); 17 | } 18 | } 19 | 20 | fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); 21 | -------------------------------------------------------------------------------- /app/scripts/user_services.js: -------------------------------------------------------------------------------- 1 | angular.module('sample').factory('User', ['$resource', 'ENV', function($resource, ENV) { 2 | var User; 3 | return User = (function() { 4 | function User(errorHandler) { 5 | this.service = $resource(ENV.apiEndpoint + '/users.json', {}, { 6 | 'query': { 7 | method: 'GET' 8 | } 9 | }); 10 | this.errorHandler = errorHandler; 11 | } 12 | 13 | User.prototype.all = function() { 14 | return this.service.query((function() { 15 | return null; 16 | }), this.errorHandler); 17 | }; 18 | 19 | return User; 20 | 21 | })(); 22 | } 23 | ]); 24 | 25 | -------------------------------------------------------------------------------- /hooks/after_platform_add/install_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Install all plugins listed in package.json 5 | */ 6 | var exec = require('child_process').exec; 7 | var path = require('path'); 8 | var sys = require('sys'); 9 | 10 | var packageJSON = require('../../package.json'); 11 | var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 12 | var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd); 13 | 14 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 15 | packageJSON.cordovaPlugins.forEach(function (plugin) { 16 | exec(script + ' plugin add ' + plugin, function (error, stdout, stderr) { 17 | sys.puts(stdout); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ionic + ng-token-auth Demo 2 | ================ 3 | 4 | This is a [Ionic](http://ionicframework.com/) application fully configured to work with the [ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth). 5 | If you use [devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth) gem as a server-side authentication, 6 | you can integrate client-side application with server-side seamlessly. 7 | 8 | Getting Started 9 | --------------- 10 | ## Ionic 11 | Run a local development server with built in filesystem. 12 | 13 | ``` 14 | $grunt serve 15 | ``` 16 | 17 | ## Server side 18 | You can use a rails application with the [devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth) gem as a server side. 19 | 20 | [jwako/devise_token_auth_demo](https://github.com/jwako/devise_token_auth_demo) 21 | 22 | -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Login

4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 | 15 | 19 | 22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /app/templates/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Left

18 |
19 | 20 | 21 | 22 | Login 23 | 24 | 25 | Home 26 | 27 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /hooks/after_plugin_rm/010_deregister_plugin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Remove plugins from cordovaPlugins array after_plugin_rm 5 | */ 6 | var fs = require('fs'); 7 | var packageJSON = require('../../package.json'); 8 | 9 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 10 | 11 | process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) { 12 | var index = packageJSON.cordovaPlugins.indexOf(plugin); 13 | if (index > -1) { 14 | packageJSON.cordovaPlugins.splice(index, 1); 15 | } else { 16 | //If it didnt find a match, it may be listed as {id,locator} 17 | for(var i = 0, j = packageJSON.cordovaPlugins.length; i < j; i++) { 18 | var packagePlugin = packageJSON.cordovaPlugins[i]; 19 | if(typeof packagePlugin == 'object' && packagePlugin.id == plugin) { 20 | packageJSON.cordovaPlugins.splice(index, 1); 21 | break; 22 | } 23 | } 24 | } 25 | }); 26 | 27 | fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2)); 28 | -------------------------------------------------------------------------------- /app/scripts/auth.js: -------------------------------------------------------------------------------- 1 | angular.module('sample.auth', []) 2 | 3 | .controller('AuthCtrl', function($scope, $ionicModal, $timeout, $state, $auth) { 4 | // Form data for the login modal 5 | $scope.loginData = {}; 6 | 7 | // Create the login modal that we will use later 8 | $ionicModal.fromTemplateUrl('templates/login.html', { 9 | scope: $scope 10 | }).then(function(modal) { 11 | $scope.modal = modal; 12 | }); 13 | 14 | // Triggered in the login modal to close it 15 | $scope.closeLogin = function() { 16 | $scope.modal.hide(); 17 | }; 18 | 19 | // Open the login modal 20 | $scope.login = function() { 21 | $scope.modal.show(); 22 | }; 23 | 24 | // Perform the login action when the user submits the login form 25 | $scope.doLogin = function() { 26 | $auth.submitLogin($scope.loginData) 27 | .then(function(resp) { 28 | $scope.closeLogin(); 29 | $state.go('app.home'); 30 | }) 31 | .catch(function(resp) { 32 | console.log(resp); 33 | }); 34 | }; 35 | }); 36 | 37 | 38 | -------------------------------------------------------------------------------- /hooks/after_platform_add/010_install_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Install all plugins listed in package.json 5 | * https://raw.githubusercontent.com/diegonetto/generator-ionic/master/templates/hooks/after_platform_add/install_plugins.js 6 | */ 7 | var exec = require('child_process').exec; 8 | var path = require('path'); 9 | var sys = require('sys'); 10 | 11 | var packageJSON = null; 12 | 13 | try { 14 | packageJSON = require('../../package.json'); 15 | } catch(ex) { 16 | console.log('\nThere was an error fetching your package.json file.') 17 | console.log('\nPlease ensure a valid package.json is in the root of this project\n') 18 | return; 19 | } 20 | 21 | var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 22 | // var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd); 23 | 24 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 25 | packageJSON.cordovaPlugins.forEach(function (plugin) { 26 | exec('cordova plugin add ' + plugin, function (error, stdout, stderr) { 27 | sys.puts(stdout); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /hooks/after_prepare/020_remove_sass_from_platforms.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * After prepare, files are copied to the platforms/ios and platforms/android folders. 5 | * Lets clean up some of those files that arent needed with this hook. 6 | */ 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var deleteFolderRecursive = function(removePath) { 11 | if( fs.existsSync(removePath) ) { 12 | fs.readdirSync(removePath).forEach(function(file,index){ 13 | var curPath = path.join(removePath, file); 14 | if(fs.lstatSync(curPath).isDirectory()) { // recurse 15 | deleteFolderRecursive(curPath); 16 | } else { // delete file 17 | fs.unlinkSync(curPath); 18 | } 19 | }); 20 | fs.rmdirSync(removePath); 21 | } 22 | }; 23 | 24 | var iosPlatformsDir = path.resolve(__dirname, '../../platforms/ios/www/lib/ionic/scss'); 25 | var androidPlatformsDir = path.resolve(__dirname, '../../platforms/android/assets/www/lib/ionic/scss'); 26 | 27 | deleteFolderRecursive(iosPlatformsDir); 28 | deleteFolderRecursive(androidPlatformsDir); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Junya Wako 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | angular.module('sample', ['ionic', 2 | 'config', 3 | 'sample.auth', 4 | 'sample.home', 5 | 'ng-token-auth', 6 | 'ngResource']) 7 | 8 | .run(function($ionicPlatform) { 9 | $ionicPlatform.ready(function() { 10 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 11 | // for form inputs) 12 | if (window.cordova && window.cordova.plugins.Keyboard) { 13 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 14 | } 15 | if (window.StatusBar) { 16 | // org.apache.cordova.statusbar required 17 | StatusBar.styleDefault(); 18 | } 19 | }); 20 | }) 21 | 22 | .config(function($authProvider, ENV) { 23 | $authProvider.configure({ 24 | apiUrl: ENV.apiEndpoint 25 | }); 26 | }) 27 | 28 | .config(function($stateProvider, $urlRouterProvider) { 29 | $stateProvider 30 | 31 | .state('app', { 32 | url: "/app", 33 | abstract: true, 34 | templateUrl: "templates/menu.html", 35 | controller: 'AuthCtrl' 36 | }) 37 | 38 | .state('app.top', { 39 | url: "/top", 40 | views: { 41 | 'menuContent': { 42 | templateUrl: "templates/top.html" 43 | } 44 | } 45 | }) 46 | 47 | .state('app.home', { 48 | url: "/home", 49 | views: { 50 | 'menuContent': { 51 | templateUrl: "templates/home.html", 52 | controller: 'HomeCtrl' 53 | } 54 | } 55 | }); 56 | 57 | // if none of the above states are matched, use this as the fallback 58 | $urlRouterProvider.otherwise('/app/top'); 59 | }); 60 | -------------------------------------------------------------------------------- /hooks/after_plugin_add/010_register_plugin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Push plugins to cordovaPlugins array after_plugin_add 5 | */ 6 | var fs = require('fs'), 7 | packageJSON = require('../../package.json'), 8 | path = require('path'); 9 | 10 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 11 | process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) { 12 | var configString, 13 | idRegEx, 14 | id, 15 | pluginXmlPath, 16 | pluginToAdd; 17 | 18 | if(plugin.indexOf('https') != -1 || plugin.indexOf('git') != -1) { 19 | console.log('Installing plugin from url'); 20 | } 21 | 22 | if(plugin.indexOf('/') != -1) { 23 | try { 24 | pluginXmlPath = path.resolve(plugin, 'plugin.xml'); 25 | console.log('got pluginXmlPath:', pluginXmlPath); 26 | if (!fs.existsSync(pluginXmlPath)) { 27 | var errorMessage = ['There was no plugin.xml file found for path: ', pluginXmlPath].join(''); 28 | return; 29 | } 30 | 31 | configString = fs.readFileSync(pluginXmlPath,{encoding: 'utf8'}); 32 | idRegEx = new RegExp(']*id="(.*)"', 'i'); 33 | id = idRegEx.exec(configString)[1] 34 | pluginToAdd = {id: id, locator: plugin}; 35 | } catch(ex) { 36 | console.log('There was an error retrieving the plugin.xml filr from the 010_register_plugin.js hook', ex); 37 | } 38 | } else { 39 | pluginToAdd = plugin; 40 | } 41 | 42 | if(typeof pluginToAdd == 'string' && packageJSON.cordovaPlugins.indexOf(pluginToAdd) == -1) { 43 | packageJSON.cordovaPlugins.push(pluginToAdd); 44 | } else if (typeof pluginToAdd == 'object') { 45 | var pluginExists = false; 46 | packageJSON.cordovaPlugins.forEach(function(checkPlugin) { 47 | if(typeof checkPlugin == 'object' && checkPlugin.id == pluginToAdd.id) { 48 | pluginExists = true; 49 | } 50 | }) 51 | if(!pluginExists) { 52 | packageJSON.cordovaPlugins.push(pluginToAdd); 53 | } 54 | } 55 | }); 56 | 57 | fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); 58 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IonicRailsSample", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "grunt": "~0.4.5", 8 | "glob": "~4.3.5", 9 | "grunt-autoprefixer": "~2.2.0", 10 | "grunt-wiredep": "^2.0.0", 11 | "ionic": "^1.3.7", 12 | "grunt-concurrent": "~1.0.0", 13 | "grunt-contrib-clean": "~0.6.0", 14 | "grunt-contrib-concat": "~0.5.0", 15 | "grunt-contrib-connect": "~0.9.0", 16 | "grunt-contrib-copy": "~0.7.0", 17 | "grunt-contrib-cssmin": "~0.11.0", 18 | "grunt-contrib-htmlmin": "~0.3.0", 19 | "grunt-contrib-jshint": "~0.11.0", 20 | "grunt-contrib-uglify": "~0.7.0", 21 | "grunt-contrib-watch": "~0.6.1", 22 | "grunt-newer": "~1.1.0", 23 | "grunt-usemin": "~3.0.0", 24 | "grunt-ng-annotate": "~0.9.2", 25 | "grunt-ng-constant": "^1.0.0", 26 | "grunt-karma": "~0.10.1", 27 | "karma": "~0.12.31", 28 | "karma-mocha": "~0.1.10", 29 | "karma-chai": "~0.1.0", 30 | "karma-chrome-launcher": "~0.1.7", 31 | "karma-phantomjs-launcher": "~0.1.4", 32 | "karma-coverage": "~0.2.7", 33 | "jshint-stylish": "~1.0.0", 34 | "load-grunt-tasks": "~3.1.0", 35 | "time-grunt": "~1.0.0", 36 | "cordova": "~4.2.0", 37 | "lodash": "~3.1.0", 38 | "mkdirp": "~0.5.0", 39 | "ncp": "~1.0.1", 40 | "orchestrator": "~0.3.7", 41 | "ripple-emulator": "~0.9.24", 42 | "elementtree": "0.1.6", 43 | "plist": "1.1.0", 44 | "win-spawn": "^2.0.0" 45 | }, 46 | "engines": { 47 | "node": ">=0.10.0" 48 | }, 49 | "scripts": { 50 | "test": "grunt test" 51 | }, 52 | "cordovaPlugins": [ 53 | "org.apache.cordova.device", 54 | "org.apache.cordova.console", 55 | "com.ionic.keyboard", 56 | "org.apache.cordova.device", 57 | "org.apache.cordova.console", 58 | "com.ionic.keyboard", 59 | "org.apache.cordova.console", 60 | "org.apache.cordova.device", 61 | "org.apache.cordova.console", 62 | "org.apache.cordova.device", 63 | "com.ionic.keyboard", 64 | "com.ionic.keyboard", 65 | "org.apache.cordova.device", 66 | "org.apache.cordova.device", 67 | "org.apache.cordova.device", 68 | "org.apache.cordova.console", 69 | "org.apache.cordova.console", 70 | "org.apache.cordova.console", 71 | "org.apache.cordova.console", 72 | "com.ionic.keyboard", 73 | "com.ionic.keyboard", 74 | "com.ionic.keyboard", 75 | "org.apache.cordova.console", 76 | "org.apache.cordova.console", 77 | "org.apache.cordova.device", 78 | "org.apache.cordova.device", 79 | "org.apache.cordova.device", 80 | "org.apache.cordova.console", 81 | "org.apache.cordova.device", 82 | "org.apache.cordova.console", 83 | "com.ionic.keyboard", 84 | "com.ionic.keyboard", 85 | "com.ionic.keyboard", 86 | "com.ionic.keyboard" 87 | ] 88 | } -------------------------------------------------------------------------------- /hooks/after_prepare/icons_and_splashscreens.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Algorithm 4 | * [1] Look at all installed platforms 5 | * [2] Copy (non-destructive) icons and splash screens from platform to local RESOURCE_DIR 6 | * [3] Copy (destructive) matching icons and splash screens from RESOURCE_DIR to platform 7 | * 8 | * This ensures that local RESOURCE_DIR will be pre-scaffolded with icons and splash 9 | * screens generated by Cordova as placeholder ONLY for installed platforms and that 10 | * any modifications to local assets are reflected in the CORRECT Cordova platform 11 | * locations, without having to hardcode file paths. 12 | */ 13 | var fs = require('fs'); 14 | var path = require('path'); 15 | var _ = require('lodash'); 16 | var ncp = require('ncp'); 17 | var mkdirp = require('mkdirp'); 18 | var glob = require('glob'); 19 | var Orchestrator = require('orchestrator'); 20 | 21 | var BASES = { 22 | android: 'res', 23 | ios: 'IonicRailsSample/Resources' 24 | }; 25 | var RESOURCE_DIR = 'resources'; 26 | 27 | // Helper function for file copying that ensures directory existence. 28 | function copyFile (src, dest, ncpOpts, callback) { 29 | var orchestrator = new Orchestrator(); 30 | var parts = dest.split(path.sep); 31 | var fileName = parts.pop(); 32 | var destDir = parts.join(path.sep); 33 | var destFile = path.resolve(destDir, fileName); 34 | orchestrator.add('ensureDir', function (done) { 35 | mkdirp(destDir, function (err) { 36 | done(err); 37 | }); 38 | }); 39 | orchestrator.add('copyFile', ['ensureDir'], function (done) { 40 | ncp(src, destFile, ncpOpts, function (err) { 41 | done(err); 42 | }); 43 | }); 44 | orchestrator.start('copyFile', function (err) { 45 | callback(err); 46 | }); 47 | } 48 | 49 | // Main 50 | var platforms = _.filter(fs.readdirSync('platforms'), function (file) { 51 | return fs.statSync(path.resolve('platforms', file)).isDirectory(); 52 | }); 53 | _.each(platforms, function (platform) { 54 | var base = path.resolve('platforms', platform, BASES[platform]); 55 | glob(base + '/**/*.png', function (err, files) { 56 | _.each(files, function (cordovaFile) { 57 | var orchestrator = new Orchestrator(); 58 | var parts = cordovaFile.split('/'); 59 | var fileName = parts.pop(); 60 | var localDir = path.resolve(RESOURCE_DIR, platform, _.last(parts)); 61 | var localFile = path.resolve(localDir, fileName); 62 | 63 | orchestrator.add('copyFromCordova', function (done) { 64 | copyFile(cordovaFile, localFile, { clobber: false }, function (err) { 65 | done(err); 66 | }); 67 | }); 68 | orchestrator.add('copyToCordova', ['copyFromCordova'], function (done) { 69 | copyFile(localFile, cordovaFile, { clobber: true }, function (err) { 70 | done(err); 71 | }); 72 | }); 73 | orchestrator.start('copyToCordova', function (err) { 74 | if (err) { console.error(err); } 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /hooks/after_prepare/010_add_platform_class.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Add Platform Class 4 | // v1.0 5 | // Automatically adds the platform class to the body tag 6 | // after the `prepare` command. By placing the platform CSS classes 7 | // directly in the HTML built for the platform, it speeds up 8 | // rendering the correct layout/style for the specific platform 9 | // instead of waiting for the JS to figure out the correct classes. 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | 14 | var rootdir = process.argv[2]; 15 | 16 | function addPlatformBodyTag(indexPath, platform) { 17 | // add the platform class to the body tag 18 | try { 19 | var platformClass = 'platform-' + platform; 20 | var cordovaClass = 'platform-cordova platform-webview'; 21 | 22 | var html = fs.readFileSync(indexPath, 'utf8'); 23 | 24 | var bodyTag = findBodyTag(html); 25 | if(!bodyTag) return; // no opening body tag, something's wrong 26 | 27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x 21 | # Cordova Hooks 22 | 23 | Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system to customize cordova commands. Hook scripts could be defined by adding them to the special predefined folder (`/hooks`) or via configuration files (`config.xml` and `plugin.xml`) and run serially in the following order: 24 | * Application hooks from `/hooks`; 25 | * Application hooks from `config.xml`; 26 | * Plugin hooks from `plugins/.../plugin.xml`. 27 | 28 | __Remember__: Make your scripts executable. 29 | 30 | __Note__: `.cordova/hooks` directory is also supported for backward compatibility, but we don't recommend using it as it is deprecated. 31 | 32 | ## Supported hook types 33 | The following hook types are supported: 34 | 35 | after_build/ 36 | after_compile/ 37 | after_docs/ 38 | after_emulate/ 39 | after_platform_add/ 40 | after_platform_rm/ 41 | after_platform_ls/ 42 | after_plugin_add/ 43 | after_plugin_ls/ 44 | after_plugin_rm/ 45 | after_plugin_search/ 46 | after_plugin_install/ <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed 47 | after_prepare/ 48 | after_run/ 49 | after_serve/ 50 | before_build/ 51 | before_compile/ 52 | before_docs/ 53 | before_emulate/ 54 | before_platform_add/ 55 | before_platform_rm/ 56 | before_platform_ls/ 57 | before_plugin_add/ 58 | before_plugin_ls/ 59 | before_plugin_rm/ 60 | before_plugin_search/ 61 | before_plugin_install/ <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed 62 | before_plugin_uninstall/ <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being uninstalled 63 | before_prepare/ 64 | before_run/ 65 | before_serve/ 66 | pre_package/ <-- Windows 8 and Windows Phone only. 67 | 68 | ## Ways to define hooks 69 | ### Via '/hooks' directory 70 | To execute custom action when corresponding hook type is fired, use hook type as a name for a subfolder inside 'hooks' directory and place you script file here, for example: 71 | 72 | # script file will be automatically executed after each build 73 | hooks/after_build/after_build_custom_action.js 74 | 75 | 76 | ### Config.xml 77 | 78 | Hooks can be defined in project's `config.xml` using `` elements, for example: 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ... 89 | 90 | 91 | 92 | 93 | 94 | 95 | ... 96 | 97 | 98 | ### Plugin hooks (plugin.xml) 99 | 100 | As a plugin developer you can define hook scripts using `` elements in a `plugin.xml` like that: 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ... 109 | 110 | 111 | `before_plugin_install`, `after_plugin_install`, `before_plugin_uninstall` plugin hooks will be fired exclusively for the plugin being installed/uninstalled. 112 | 113 | ## Script Interface 114 | 115 | ### Javascript 116 | 117 | If you are writing hooks in Javascript you should use the following module definition: 118 | ```javascript 119 | module.exports = function(context) { 120 | ... 121 | } 122 | ``` 123 | 124 | You can make your scipts async using Q: 125 | ```javascript 126 | module.exports = function(context) { 127 | var Q = context.requireCordovaModule('q'); 128 | var deferral = new Q.defer(); 129 | 130 | setTimeout(function(){ 131 | console.log('hook.js>> end'); 132 | deferral.resolve(); 133 | }, 1000); 134 | 135 | return deferral.promise; 136 | } 137 | ``` 138 | 139 | `context` object contains hook type, executed script full path, hook options, command-line arguments passed to Cordova and top-level "cordova" object: 140 | ```json 141 | { 142 | "hook": "before_plugin_install", 143 | "scriptLocation": "c:\\script\\full\\path\\appBeforePluginInstall.js", 144 | "cmdLine": "The\\exact\\command\\cordova\\run\\with arguments", 145 | "opts": { 146 | "projectRoot":"C:\\path\\to\\the\\project", 147 | "cordova": { 148 | "platforms": ["wp8"], 149 | "plugins": ["com.plugin.withhooks"], 150 | "version": "0.21.7-dev" 151 | }, 152 | "plugin": { 153 | "id": "com.plugin.withhooks", 154 | "pluginInfo": { 155 | ... 156 | }, 157 | "platform": "wp8", 158 | "dir": "C:\\path\\to\\the\\project\\plugins\\com.plugin.withhooks" 159 | } 160 | }, 161 | "cordova": {...} 162 | } 163 | 164 | ``` 165 | `context.opts.plugin` object will only be passed to plugin hooks scripts. 166 | 167 | You can also require additional Cordova modules in your script using `context.requireCordovaModule` in the following way: 168 | ```javascript 169 | var Q = context.requireCordovaModule('q'); 170 | ``` 171 | 172 | __Note__: new module loader script interface is used for the `.js` files defined via `config.xml` or `plugin.xml` only. 173 | For compatibility reasons hook files specified via `/hooks` folders are run via Node child_process spawn, see 'Non-javascript' section below. 174 | 175 | ### Non-javascript 176 | 177 | Non-javascript scripts are run via Node child_process spawn from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 178 | 179 | * CORDOVA_VERSION - The version of the Cordova-CLI. 180 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 181 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 182 | * CORDOVA_HOOK - Path to the hook that is being executed. 183 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 184 | 185 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 186 | 187 | ## Writing hooks 188 | 189 | We highly recommend writing your hooks using Node.js so that they are 190 | cross-platform. Some good examples are shown here: 191 | 192 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 193 | 194 | Also, note that even if you are working on Windows, and in case your hook scripts aren't bat files (which is recommended, if you want your scripts to work in non-Windows operating systems) Cordova CLI will expect a shebang line as the first line for it to know the interpreter it needs to use to launch the script. The shebang line should match the following example: 195 | 196 | #!/usr/bin/env [name_of_interpreter_executable] 197 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2015-03-13 using generator-ionic 0.7.1 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var path = require('path'); 6 | var cordovaCli = require('cordova'); 7 | var spawn = process.platform === 'win32' ? require('win-spawn') : require('child_process').spawn; 8 | 9 | module.exports = function (grunt) { 10 | 11 | // Load grunt tasks automatically 12 | require('load-grunt-tasks')(grunt); 13 | 14 | // Time how long tasks take. Can help when optimizing build times 15 | require('time-grunt')(grunt); 16 | 17 | // Define the configuration for all the tasks 18 | grunt.initConfig({ 19 | 20 | // Project settings 21 | yeoman: { 22 | // configurable paths 23 | app: 'app', 24 | scripts: 'scripts', 25 | styles: 'styles', 26 | images: 'images', 27 | test: 'test', 28 | dist: 'www' 29 | }, 30 | 31 | // Environment Variables for Angular App 32 | // This creates an Angular Module that can be injected via ENV 33 | // Add any desired constants to the ENV objects below. 34 | // https://github.com/diegonetto/generator-ionic#environment-specific-configuration 35 | ngconstant: { 36 | options: { 37 | space: ' ', 38 | wrap: '"use strict";\n\n {%= __ngModule %}', 39 | name: 'config', 40 | dest: '<%= yeoman.app %>/<%= yeoman.scripts %>/configuration.js' 41 | }, 42 | development: { 43 | constants: { 44 | ENV: { 45 | name: 'development', 46 | apiEndpoint: 'http://localhost:3000/api/v1' 47 | } 48 | } 49 | }, 50 | production: { 51 | constants: { 52 | ENV: { 53 | name: 'production', 54 | apiEndpoint: 'https://rails-angular-auth-demo.herokuapp.com/api/v1' 55 | } 56 | } 57 | } 58 | }, 59 | 60 | // Watches files for changes and runs tasks based on the changed files 61 | watch: { 62 | bower: { 63 | files: ['bower.json'], 64 | tasks: ['wiredep', 'newer:copy:app'] 65 | }, 66 | html: { 67 | files: ['<%= yeoman.app %>/**/*.html'], 68 | tasks: ['newer:copy:app'] 69 | }, 70 | js: { 71 | files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js'], 72 | tasks: ['newer:copy:app', 'newer:jshint:all'] 73 | }, 74 | styles: { 75 | files: ['<%= yeoman.app %>/<%= yeoman.styles %>/**/*.css'], 76 | tasks: ['newer:copy:styles', 'autoprefixer', 'newer:copy:tmp'] 77 | }, 78 | gruntfile: { 79 | files: ['Gruntfile.js'], 80 | tasks: ['ngconstant:development', 'newer:copy:app'] 81 | } 82 | }, 83 | 84 | // The actual grunt server settings 85 | connect: { 86 | options: { 87 | port: 9000, 88 | // Change this to '0.0.0.0' to access the server from outside. 89 | hostname: 'localhost' 90 | }, 91 | dist: { 92 | options: { 93 | base: '<%= yeoman.dist %>' 94 | } 95 | }, 96 | coverage: { 97 | options: { 98 | port: 9002, 99 | open: true, 100 | base: ['coverage'] 101 | } 102 | } 103 | }, 104 | 105 | // Make sure code styles are up to par and there are no obvious mistakes 106 | jshint: { 107 | options: { 108 | jshintrc: '.jshintrc', 109 | reporter: require('jshint-stylish') 110 | }, 111 | all: [ 112 | 'Gruntfile.js', 113 | '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js' 114 | ], 115 | test: { 116 | options: { 117 | jshintrc: 'test/.jshintrc' 118 | }, 119 | src: ['test/unit/**/*.js'] 120 | } 121 | }, 122 | 123 | // Empties folders to start fresh 124 | clean: { 125 | dist: { 126 | files: [{ 127 | dot: true, 128 | src: [ 129 | '.temp', 130 | '<%= yeoman.dist %>/*', 131 | '!<%= yeoman.dist %>/.git*' 132 | ] 133 | }] 134 | }, 135 | server: '.temp' 136 | }, 137 | 138 | autoprefixer: { 139 | options: { 140 | browsers: ['last 1 version'] 141 | }, 142 | dist: { 143 | files: [{ 144 | expand: true, 145 | cwd: '.temp/<%= yeoman.styles %>/', 146 | src: '{,*/}*.css', 147 | dest: '.temp/<%= yeoman.styles %>/' 148 | }] 149 | } 150 | }, 151 | 152 | // Automatically inject Bower components into the app 153 | wiredep: { 154 | app: { 155 | src: ['<%= yeoman.app %>/index.html'], 156 | ignorePath: /\.\.\// 157 | } 158 | }, 159 | 160 | 161 | 162 | // Reads HTML for usemin blocks to enable smart builds that automatically 163 | // concat, minify and revision files. Creates configurations in memory so 164 | // additional tasks can operate on them 165 | useminPrepare: { 166 | html: '<%= yeoman.app %>/index.html', 167 | options: { 168 | dest: '<%= yeoman.dist %>', 169 | staging: '.temp', 170 | flow: { 171 | html: { 172 | steps: { 173 | js: ['concat', 'uglifyjs'], 174 | css: ['cssmin'] 175 | }, 176 | post: {} 177 | } 178 | } 179 | } 180 | }, 181 | 182 | // Performs rewrites based on the useminPrepare configuration 183 | usemin: { 184 | html: ['<%= yeoman.dist %>/**/*.html'], 185 | css: ['<%= yeoman.dist %>/<%= yeoman.styles %>/**/*.css'], 186 | options: { 187 | assetsDirs: ['<%= yeoman.dist %>'] 188 | } 189 | }, 190 | 191 | // The following *-min tasks produce minified files in the dist folder 192 | cssmin: { 193 | options: { 194 | //root: '<%= yeoman.app %>', 195 | noRebase: true 196 | } 197 | }, 198 | htmlmin: { 199 | dist: { 200 | options: { 201 | collapseWhitespace: true, 202 | collapseBooleanAttributes: true, 203 | removeCommentsFromCDATA: true, 204 | removeOptionalTags: true 205 | }, 206 | files: [{ 207 | expand: true, 208 | cwd: '<%= yeoman.dist %>', 209 | src: ['*.html', 'templates/**/*.html'], 210 | dest: '<%= yeoman.dist %>' 211 | }] 212 | } 213 | }, 214 | 215 | // Copies remaining files to places other tasks can use 216 | copy: { 217 | dist: { 218 | files: [{ 219 | expand: true, 220 | dot: true, 221 | cwd: '<%= yeoman.app %>', 222 | dest: '<%= yeoman.dist %>', 223 | src: [ 224 | '<%= yeoman.images %>/**/*.{png,jpg,jpeg,gif,webp,svg}', 225 | '*.html', 226 | 'templates/**/*.html', 227 | 'fonts/*' 228 | ] 229 | }, { 230 | expand: true, 231 | cwd: '.temp/<%= yeoman.images %>', 232 | dest: '<%= yeoman.dist %>/<%= yeoman.images %>', 233 | src: ['generated/*'] 234 | }] 235 | }, 236 | styles: { 237 | expand: true, 238 | cwd: '<%= yeoman.app %>/<%= yeoman.styles %>', 239 | dest: '.temp/<%= yeoman.styles %>/', 240 | src: '{,*/}*.css' 241 | }, 242 | fonts: { 243 | expand: true, 244 | cwd: 'app/lib/ionic/release/fonts/', 245 | dest: '<%= yeoman.app %>/fonts/', 246 | src: '*' 247 | }, 248 | vendor: { 249 | expand: true, 250 | cwd: '<%= yeoman.app %>/vendor', 251 | dest: '.temp/<%= yeoman.styles %>/', 252 | src: '{,*/}*.css' 253 | }, 254 | app: { 255 | expand: true, 256 | cwd: '<%= yeoman.app %>', 257 | dest: '<%= yeoman.dist %>/', 258 | src: [ 259 | '**/*', 260 | '!**/*.(scss,sass,css)', 261 | ] 262 | }, 263 | tmp: { 264 | expand: true, 265 | cwd: '.temp', 266 | dest: '<%= yeoman.dist %>/', 267 | src: '**/*' 268 | } 269 | }, 270 | 271 | concurrent: { 272 | ionic: { 273 | tasks: [], 274 | options: { 275 | logConcurrentOutput: true 276 | } 277 | }, 278 | server: [ 279 | 'copy:styles', 280 | 'copy:vendor', 281 | 'copy:fonts' 282 | ], 283 | test: [ 284 | 'copy:styles', 285 | 'copy:vendor', 286 | 'copy:fonts' 287 | ], 288 | dist: [ 289 | 'copy:styles', 290 | 'copy:vendor', 291 | 'copy:fonts' 292 | ] 293 | }, 294 | 295 | // By default, your `index.html`'s will take care of 296 | // minification. These next options are pre-configured if you do not wish 297 | // to use the Usemin blocks. 298 | // cssmin: { 299 | // dist: { 300 | // files: { 301 | // '<%= yeoman.dist %>/<%= yeoman.styles %>/main.css': [ 302 | // '.temp/<%= yeoman.styles %>/**/*.css', 303 | // '<%= yeoman.app %>/<%= yeoman.styles %>/**/*.css' 304 | // ] 305 | // } 306 | // } 307 | // }, 308 | // uglify: { 309 | // dist: { 310 | // files: { 311 | // '<%= yeoman.dist %>/<%= yeoman.scripts %>/scripts.js': [ 312 | // '<%= yeoman.dist %>/<%= yeoman.scripts %>/scripts.js' 313 | // ] 314 | // } 315 | // } 316 | // }, 317 | // concat: { 318 | // dist: {} 319 | // }, 320 | 321 | // Test settings 322 | // These will override any config options in karma.conf.js if you create it. 323 | karma: { 324 | options: { 325 | basePath: '', 326 | frameworks: ['mocha', 'chai'], 327 | files: [ 328 | '<%= yeoman.app %>/bower_components/angular/angular.js', 329 | '<%= yeoman.app %>/bower_components/angular-mocks/angular-mocks.js', 330 | '<%= yeoman.app %>/bower_components/angular-animate/angular-animate.js', 331 | '<%= yeoman.app %>/bower_components/angular-sanitize/angular-sanitize.js', 332 | '<%= yeoman.app %>/bower_components/angular-ui-router/release/angular-ui-router.js', 333 | '<%= yeoman.app %>/bower_components/ionic/release/js/ionic.js', 334 | '<%= yeoman.app %>/bower_components/ionic/release/js/ionic-angular.js', 335 | '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js', 336 | '<%= yeoman.test %>/mock/**/*.js', 337 | '<%= yeoman.test %>/spec/**/*.js' 338 | ], 339 | autoWatch: false, 340 | reporters: ['dots', 'coverage'], 341 | port: 8080, 342 | singleRun: false, 343 | preprocessors: { 344 | // Update this if you change the yeoman config path 345 | '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js': ['coverage'] 346 | }, 347 | coverageReporter: { 348 | reporters: [ 349 | { type: 'html', dir: 'coverage/' }, 350 | { type: 'text-summary' } 351 | ] 352 | } 353 | }, 354 | unit: { 355 | // Change this to 'Chrome', 'Firefox', etc. Note that you will need 356 | // to install a karma launcher plugin for browsers other than Chrome. 357 | browsers: ['PhantomJS'], 358 | background: true 359 | }, 360 | continuous: { 361 | browsers: ['PhantomJS'], 362 | singleRun: true, 363 | } 364 | }, 365 | 366 | // ngAnnotate tries to make the code safe for minification automatically by 367 | // using the Angular long form for dependency injection. 368 | ngAnnotate: { 369 | dist: { 370 | files: [{ 371 | expand: true, 372 | cwd: '.temp/concat/<%= yeoman.scripts %>', 373 | src: '*.js', 374 | dest: '.temp/concat/<%= yeoman.scripts %>' 375 | }] 376 | } 377 | } 378 | 379 | }); 380 | 381 | // Register tasks for all Cordova commands 382 | _.functions(cordovaCli).forEach(function (name) { 383 | grunt.registerTask(name, function () { 384 | this.args.unshift(name.replace('cordova:', '')); 385 | // Handle URL's being split up by Grunt because of `:` characters 386 | if (_.contains(this.args, 'http') || _.contains(this.args, 'https')) { 387 | this.args = this.args.slice(0, -2).concat(_.last(this.args, 2).join(':')); 388 | } 389 | var done = this.async(); 390 | var exec = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 391 | var cmd = path.resolve('./node_modules/cordova/bin', exec); 392 | var flags = process.argv.splice(3); 393 | var child = spawn(cmd, this.args.concat(flags)); 394 | child.stdout.on('data', function (data) { 395 | grunt.log.writeln(data); 396 | }); 397 | child.stderr.on('data', function (data) { 398 | grunt.log.error(data); 399 | }); 400 | child.on('close', function (code) { 401 | code = code ? false : true; 402 | done(code); 403 | }); 404 | }); 405 | }); 406 | 407 | // Since Apache Ripple serves assets directly out of their respective platform 408 | // directories, we watch all registered files and then copy all un-built assets 409 | // over to <%= yeoman.dist %>/. Last step is running cordova prepare so we can refresh the ripple 410 | // browser tab to see the changes. Technically ripple runs `cordova prepare` on browser 411 | // refreshes, but at this time you would need to re-run the emulator to see changes. 412 | grunt.registerTask('ripple', ['wiredep', 'newer:copy:app', 'ripple-emulator']); 413 | grunt.registerTask('ripple-emulator', function () { 414 | grunt.config.set('watch', { 415 | all: { 416 | files: _.flatten(_.pluck(grunt.config.get('watch'), 'files')), 417 | tasks: ['newer:copy:app', 'prepare'] 418 | } 419 | }); 420 | 421 | var cmd = path.resolve('./node_modules/ripple-emulator/bin', 'ripple'); 422 | var child = spawn(cmd, ['emulate']); 423 | child.stdout.on('data', function (data) { 424 | grunt.log.writeln(data); 425 | }); 426 | child.stderr.on('data', function (data) { 427 | grunt.log.error(data); 428 | }); 429 | process.on('exit', function (code) { 430 | child.kill('SIGINT'); 431 | process.exit(code); 432 | }); 433 | 434 | return grunt.task.run(['watch']); 435 | }); 436 | 437 | // Dynamically configure `karma` target of `watch` task so that 438 | // we don't have to run the karma test server as part of `grunt serve` 439 | grunt.registerTask('watch:karma', function () { 440 | var karma = { 441 | files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js', '<%= yeoman.test %>/spec/**/*.js'], 442 | tasks: ['newer:jshint:test', 'karma:unit:run'] 443 | }; 444 | grunt.config.set('watch', karma); 445 | return grunt.task.run(['watch']); 446 | }); 447 | 448 | // Wrap ionic-cli commands 449 | grunt.registerTask('ionic', function() { 450 | var done = this.async(); 451 | var script = path.resolve('./node_modules/ionic/bin/', 'ionic'); 452 | var flags = process.argv.splice(3); 453 | var child = spawn(script, this.args.concat(flags), { stdio: 'inherit' }); 454 | child.on('close', function (code) { 455 | code = code ? false : true; 456 | done(code); 457 | }); 458 | }); 459 | 460 | grunt.registerTask('test', [ 461 | 'wiredep', 462 | 'clean', 463 | 'concurrent:test', 464 | 'autoprefixer', 465 | 'karma:unit:start', 466 | 'watch:karma' 467 | ]); 468 | 469 | grunt.registerTask('serve', function (target) { 470 | if (target === 'compress') { 471 | return grunt.task.run(['compress', 'ionic:serve']); 472 | } 473 | 474 | grunt.config('concurrent.ionic.tasks', ['ionic:serve', 'watch']); 475 | grunt.task.run(['wiredep', 'init', 'concurrent:ionic']); 476 | }); 477 | grunt.registerTask('emulate', function() { 478 | grunt.config('concurrent.ionic.tasks', ['ionic:emulate:' + this.args.join(), 'watch']); 479 | return grunt.task.run(['init', 'concurrent:ionic']); 480 | }); 481 | grunt.registerTask('run', function() { 482 | grunt.config('concurrent.ionic.tasks', ['ionic:run:' + this.args.join(), 'watch']); 483 | return grunt.task.run(['init', 'concurrent:ionic']); 484 | }); 485 | grunt.registerTask('build', function() { 486 | return grunt.task.run(['init', 'ionic:build:' + this.args.join()]); 487 | }); 488 | 489 | grunt.registerTask('init', [ 490 | 'clean', 491 | 'ngconstant:development', 492 | 'wiredep', 493 | 'concurrent:server', 494 | 'autoprefixer', 495 | 'newer:copy:app', 496 | 'newer:copy:tmp' 497 | ]); 498 | 499 | 500 | grunt.registerTask('compress', [ 501 | 'clean', 502 | 'ngconstant:production', 503 | 'wiredep', 504 | 'useminPrepare', 505 | 'concurrent:dist', 506 | 'autoprefixer', 507 | 'concat', 508 | 'ngAnnotate', 509 | 'copy:dist', 510 | 'cssmin', 511 | 'uglify', 512 | 'usemin', 513 | 'htmlmin' 514 | ]); 515 | 516 | grunt.registerTask('coverage', 517 | ['karma:continuous', 518 | 'connect:coverage:keepalive' 519 | ]); 520 | 521 | grunt.registerTask('default', [ 522 | 'wiredep', 523 | 'newer:jshint', 524 | 'karma:continuous', 525 | 'compress' 526 | ]); 527 | }; 528 | -------------------------------------------------------------------------------- /hooks/after_prepare/update_platform_config.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** This hook updates platform configuration files based on preferences and config-file data defined in config.xml. 4 | Currently only the AndroidManifest.xml and IOS *-Info.plist file are supported. 5 | 6 | Preferences: 7 | 1. Preferences defined outside of the platform element will apply to all platforms 8 | 2. Preferences defined inside a platform element will apply only to the specified platform 9 | 3. Platform preferences take precedence over common preferences 10 | 4. The preferenceMappingData object contains all of the possible custom preferences to date including the 11 | target file they belong to, parent element, and destination element or attribute 12 | 13 | Config Files 14 | 1. config-file elements MUST be defined inside a platform element, otherwise they will be ignored. 15 | 2. config-file target attributes specify the target file to update. (AndroidManifest.xml or *-Info.plist) 16 | 3. config-file parent attributes specify the parent element (AndroidManifest.xml) or parent key (*-Info.plist) 17 | that the child data will replace or be appended to. 18 | 4. config-file elements are uniquely indexed by target AND parent for each platform. 19 | 5. If there are multiple config-file's defined with the same target AND parent, the last config-file will be used 20 | 6. Elements defined WITHIN a config-file will replace or be appended to the same elements relative to the parent element 21 | 7. If a unique config-file contains multiples of the same elements (other than uses-permssion elements which are 22 | selected by by the uses-permission name attribute), the last defined element will be retrieved. 23 | 24 | Examples: 25 | 26 | AndroidManifest.xml 27 | NOTE: For possible manifest values see http://developer.android.com/guide/topics/manifest/manifest-intro.html 28 | 29 | 30 | //These preferences are actually available in Cordova by default although not currently documented 31 | 32 | 33 | 34 | 35 | //custom preferences examples 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | *-Info.plist 56 | 57 | 58 | 59 | 60 | UIInterfaceOrientationLandscapeOmg 61 | 62 | 63 | 64 | 65 | someValue 66 | 67 | 68 | 69 | NOTE: Currently, items aren't removed from the platform config files if you remove them from config.xml. 70 | For example, if you add a custom permission, build the remove it, it will still be in the manifest. 71 | If you make a mistake, for example adding an element to the wrong parent, you may need to remove and add your platform, 72 | or revert to your previous manifest/plist file. 73 | 74 | TODO: We may need to capture all default manifest/plist elements/keys created by Cordova along with any plugin elements/keys to compare against custom elements to remove. 75 | */ 76 | 77 | // global vars 78 | var fs = require('fs'); 79 | var path = require('path'); 80 | var _ = require('lodash'); 81 | var et = require('elementtree'); 82 | var plist = require('plist'); 83 | 84 | var rootdir = path.resolve(__dirname, '../../'); 85 | 86 | var platformConfig = (function(){ 87 | /* Global object that defines the available custom preferences for each platform. 88 | Maps a config.xml preference to a specific target file, parent element, and destination attribute or element 89 | */ 90 | var preferenceMappingData = { 91 | 'android': { 92 | 'android-manifest-hardwareAccelerated': {target: 'AndroidManifest.xml', parent: './', destination: 'android:hardwareAccelerated'}, 93 | 'android-installLocation': {target: 'AndroidManifest.xml', parent: './', destination: 'android:installLocation'}, 94 | 'android-activity-hardwareAccelerated': {target: 'AndroidManifest.xml', parent: 'application', destination: 'android:hardwareAccelerated'}, 95 | 'android-configChanges': {target: 'AndroidManifest.xml', parent: 'application/activity[@android:name=\'CordovaApp\']', destination: 'android:configChanges'}, 96 | 'android-launchMode': {target: 'AndroidManifest.xml', parent: 'application/activity[@android:name=\'CordovaApp\']', destination: 'android:launchMode'}, 97 | 'android-theme': {target: 'AndroidManifest.xml', parent: 'application/activity[@android:name=\'CordovaApp\']', destination: 'android:theme'}, 98 | 'android-windowSoftInputMode': {target: 'AndroidManifest.xml', parent: 'application/activity[@android:name=\'CordovaApp\']', destination: 'android:windowSoftInputMode'} 99 | }, 100 | 'ios': {} 101 | }; 102 | var configXmlData, preferencesData; 103 | 104 | return { 105 | // Parses a given file into an elementtree object 106 | parseElementtreeSync: function (filename) { 107 | var contents = fs.readFileSync(filename, 'utf-8'); 108 | if(contents) { 109 | //Windows is the BOM. Skip the Byte Order Mark. 110 | contents = contents.substring(contents.indexOf('<')); 111 | } 112 | return new et.ElementTree(et.XML(contents)); 113 | }, 114 | 115 | // Converts an elementtree object to an xml string. Since this is used for plist values, we don't care about attributes 116 | eltreeToXmlString: function (data) { 117 | var tag = data.tag; 118 | var el = '<' + tag + '>'; 119 | 120 | if(data.text && data.text.trim()) { 121 | el += data.text.trim(); 122 | } else { 123 | _.each(data.getchildren(), function (child) { 124 | el += platformConfig.eltreeToXmlString(child); 125 | }); 126 | } 127 | 128 | el += ''; 129 | return el; 130 | }, 131 | 132 | // Parses the config.xml into an elementtree object and stores in the config object 133 | getConfigXml: function () { 134 | if(!configXmlData) { 135 | configXmlData = this.parseElementtreeSync(path.join(rootdir, 'config.xml')); 136 | } 137 | 138 | return configXmlData; 139 | }, 140 | 141 | /* Retrieves all from config.xml and returns a map of preferences with platform as the key. 142 | If a platform is supplied, common prefs + platform prefs will be returned, otherwise just common prefs are returned. 143 | */ 144 | getPreferences: function (platform) { 145 | var configXml = this.getConfigXml(); 146 | 147 | //init common config.xml prefs if we haven't already 148 | if(!preferencesData) { 149 | preferencesData = { 150 | common: configXml.findall('preference') 151 | }; 152 | } 153 | 154 | var prefs = preferencesData.common || []; 155 | if(platform) { 156 | if(!preferencesData[platform]) { 157 | preferencesData[platform] = configXml.findall('platform[@name=\'' + platform + '\']/preference'); 158 | } 159 | prefs = prefs.concat(preferencesData[platform]); 160 | } 161 | 162 | return prefs; 163 | }, 164 | 165 | /* Retrieves all configured xml for a specific platform/target/parent element nested inside a platforms config-file 166 | element within the config.xml. The config-file elements are then indexed by target|parent so if there are 167 | any config-file elements per platform that have the same target and parent, the last config-file element is used. 168 | */ 169 | getConfigFilesByTargetAndParent: function (platform) { 170 | var configFileData = this.getConfigXml().findall('platform[@name=\'' + platform + '\']/config-file'); 171 | 172 | return _.indexBy(configFileData, function(item) { 173 | var parent = item.attrib.parent; 174 | //if parent attribute is undefined /* or */, set parent to top level elementree selector 175 | if(!parent || parent === '/*' || parent === '*/') { 176 | parent = './'; 177 | } 178 | return item.attrib.target + '|' + parent; 179 | }); 180 | }, 181 | 182 | // Parses the config.xml's preferences and config-file elements for a given platform 183 | parseConfigXml: function (platform) { 184 | var configData = {}; 185 | this.parsePreferences(configData, platform); 186 | this.parseConfigFiles(configData, platform); 187 | 188 | return configData; 189 | }, 190 | 191 | // Retrieves the config.xml's pereferences for a given platform and parses them into JSON data 192 | parsePreferences: function (configData, platform) { 193 | var preferences = this.getPreferences(platform), 194 | type = 'preference'; 195 | 196 | _.each(preferences, function (preference) { 197 | var prefMappingData = preferenceMappingData[platform][preference.attrib.name], 198 | target, 199 | prefData; 200 | 201 | if (prefMappingData) { 202 | prefData = { 203 | parent: prefMappingData.parent, 204 | type: type, 205 | destination: prefMappingData.destination, 206 | data: preference 207 | }; 208 | 209 | target = prefMappingData.target; 210 | if(!configData[target]) { 211 | configData[target] = []; 212 | } 213 | configData[target].push(prefData); 214 | } 215 | }); 216 | }, 217 | 218 | // Retrieves the config.xml's config-file elements for a given platform and parses them into JSON data 219 | parseConfigFiles: function (configData, platform) { 220 | var configFiles = this.getConfigFilesByTargetAndParent(platform), 221 | type = 'configFile'; 222 | 223 | _.each(configFiles, function (configFile, key) { 224 | var keyParts = key.split('|'); 225 | var target = keyParts[0]; 226 | var parent = keyParts[1]; 227 | var items = configData[target] || []; 228 | 229 | _.each(configFile.getchildren(), function (element) { 230 | items.push({ 231 | parent: parent, 232 | type: type, 233 | destination: element.tag, 234 | data: element 235 | }); 236 | }); 237 | 238 | configData[target] = items; 239 | }); 240 | }, 241 | 242 | // Parses config.xml data, and update each target file for a specified platform 243 | updatePlatformConfig: function (platform) { 244 | var configData = this.parseConfigXml(platform), 245 | platformPath = path.join(rootdir, 'platforms', platform); 246 | 247 | _.each(configData, function (configItems, targetFileName) { 248 | var projectName, targetFile; 249 | 250 | if (platform === 'ios' && targetFileName.indexOf("Info.plist") > -1) { 251 | projectName = platformConfig.getConfigXml().findtext('name'); 252 | targetFile = path.join(platformPath, projectName, projectName + '-Info.plist'); 253 | platformConfig.updateIosPlist(targetFile, configItems); 254 | } else if (platform === 'android' && targetFileName === 'AndroidManifest.xml') { 255 | targetFile = path.join(platformPath, targetFileName); 256 | platformConfig.updateAndroidManifest(targetFile, configItems); 257 | } 258 | }); 259 | }, 260 | 261 | // Updates the AndroidManifest.xml target file with data from config.xml 262 | updateAndroidManifest: function (targetFile, configItems) { 263 | var tempManifest = platformConfig.parseElementtreeSync(targetFile), 264 | root = tempManifest.getroot(); 265 | 266 | _.each(configItems, function (item) { 267 | // if parent is not found on the root, child/grandchild nodes are searched 268 | var parentEl = root.find(item.parent) || root.find('*/' + item.parent), 269 | data = item.data, 270 | childSelector = item.destination, 271 | childEl; 272 | 273 | if(!parentEl) { 274 | return; 275 | } 276 | 277 | if(item.type === 'preference') { 278 | parentEl.attrib[childSelector] = data.attrib['value']; 279 | } else { 280 | // since there can be multiple uses-permission elements, we need to select them by unique name 281 | if(childSelector === 'uses-permission') { 282 | childSelector += '[@android:name=\'' + data.attrib['android:name'] + '\']'; 283 | } 284 | 285 | childEl = parentEl.find(childSelector); 286 | // if child element doesnt exist, create new element 287 | if(!childEl) { 288 | childEl = new et.Element(item.destination); 289 | parentEl.append(childEl); 290 | } 291 | 292 | // copy all config.xml data except for the generated _id property 293 | _.each(data, function (prop, propName) { 294 | if(propName !== '_id') { 295 | childEl[propName] = prop; 296 | } 297 | }); 298 | } 299 | }); 300 | 301 | fs.writeFileSync(targetFile, tempManifest.write({indent: 4}), 'utf-8'); 302 | }, 303 | 304 | /* Updates the *-Info.plist file with data from config.xml by parsing to an xml string, then using the plist 305 | module to convert the data to a map. The config.xml data is then replaced or appended to the original plist file 306 | */ 307 | updateIosPlist: function (targetFile, configItems) { 308 | var infoPlist = plist.parse(fs.readFileSync(targetFile, 'utf-8')), 309 | tempInfoPlist; 310 | 311 | _.each(configItems, function (item) { 312 | var key = item.parent; 313 | var plistXml = '' + key + ''; 314 | plistXml += platformConfig.eltreeToXmlString(item.data) + ''; 315 | 316 | var configPlistObj = plist.parse(plistXml); 317 | infoPlist[key] = configPlistObj[key]; 318 | }); 319 | 320 | tempInfoPlist = plist.build(infoPlist); 321 | tempInfoPlist = tempInfoPlist.replace(/[\s\r\n]*<\/string>/g,''); 322 | fs.writeFileSync(targetFile, tempInfoPlist, 'utf-8'); 323 | } 324 | }; 325 | })(); 326 | 327 | // Main 328 | (function () { 329 | if (rootdir) { 330 | // go through each of the platform directories that have been prepared 331 | var platforms = _.filter(fs.readdirSync('platforms'), function (file) { 332 | return fs.statSync(path.resolve('platforms', file)).isDirectory(); 333 | }); 334 | 335 | _.each(platforms, function (platform) { 336 | try { 337 | platform = platform.trim().toLowerCase(); 338 | platformConfig.updatePlatformConfig(platform); 339 | } catch (e) { 340 | process.stdout.write(e); 341 | } 342 | }); 343 | } 344 | })(); --------------------------------------------------------------------------------