├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── ion-wizard.js └── ion-wizard.min.js ├── example-storage ├── .bowerrc ├── .gitignore ├── bower.json ├── config.xml ├── gulpfile.js ├── hooks │ ├── README.md │ └── after_prepare │ │ └── 010_add_platform_class.js ├── ionic.project ├── package.json ├── scss │ └── ionic.app.scss └── www │ ├── css │ ├── ionic.app.css │ ├── ionic.app.min.css │ └── style.css │ ├── img │ └── ionic.png │ ├── index.html │ ├── js │ ├── app.js │ ├── controllers.js │ ├── ion-wizard.js │ ├── ion-wizard.min.js │ └── services.js │ └── templates │ ├── chat-detail.html │ ├── intro.html │ ├── tab-account.html │ ├── tab-chats.html │ ├── tab-dash.html │ └── tabs.html ├── example ├── .bowerrc ├── .gitignore ├── bower.json ├── config.xml ├── gulpfile.js ├── hooks │ ├── README.md │ └── after_prepare │ │ └── 010_add_platform_class.js ├── ionic.config.json ├── package.json ├── scss │ └── ionic.app.scss └── www │ ├── css │ ├── ionic.app.css │ ├── ionic.app.min.css │ └── style.css │ ├── img │ └── ionic.png │ ├── index.html │ ├── js │ ├── app.js │ ├── controllers.js │ ├── ion-wizard.js │ ├── ion-wizard.min.js │ └── services.js │ └── templates │ ├── chat-detail.html │ ├── intro.html │ ├── tab-account.html │ ├── tab-chats.html │ ├── tab-dash.html │ └── tabs.html ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src └── ion-wizard.js └── tests └── ion-wizard_test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | node_modules/ 4 | bower_components/ 5 | tmp 6 | .DS_Store 7 | .idea 8 | www/lib/ 9 | resources/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "globals": { 4 | "angular": false, 5 | "describe": false, 6 | "it": false, 7 | "expect": false, 8 | "beforeEach": false, 9 | "afterEach": false, 10 | "module": false, 11 | "inject": false 12 | } 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm start > /dev/null & 9 | - npm run update-webdriver 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. http://angularjs.org 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ionic-wizard 2 | Visit the project page for demo and documentation. 3 | 4 | http://arielfaur.github.io/ionic-wizard 5 | 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-wizard", 3 | "description": "A set of Angular/Ionic directives to create a wizard using Ionic's slide box component", 4 | "version": "2.0.1", 5 | "homepage": "https://github.com/arielfaur/ionic-wizard", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "ionic": "driftyco/ionic-bower#1.2.4" 9 | }, 10 | "dependencies": { 11 | "angular": "~1.4.x", 12 | "angular-loader": "~1.4.x", 13 | "angular-mocks": "~1.4.x" 14 | }, 15 | "authors": [ 16 | "Ariel Faur " 17 | ], 18 | "main": "dist/ion-wizard.js", 19 | "keywords": [ 20 | "angular", 21 | "ionic", 22 | "wizard" 23 | ], 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /dist/ion-wizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ionic Wizard v2.0.1 3 | 4 | 2016-02-13 5 | Updated to work with Ionic 1.2 6 | */ 7 | angular.module('ionic.wizard', []) 8 | .directive('ionWizard', ['$rootScope', '$timeout', function($rootScope, $timeout) { 9 | return{ 10 | restrict: 'EA', 11 | controller: [function() { 12 | var conditions = []; 13 | 14 | this.addCondition = function(condition) { 15 | conditions.push(condition); 16 | }; 17 | 18 | this.getCondition = function(index) { 19 | return conditions[index]; 20 | }; 21 | 22 | this.checkNextCondition = function(index) { 23 | return index > (conditions.length - 1) 24 | ? false 25 | : conditions[index].next(); 26 | }; 27 | 28 | this.checkPreviousCondition = function(index) { 29 | return index > (conditions.length - 1) 30 | ? false 31 | : conditions[index].prev(); 32 | }; 33 | 34 | }], 35 | link: function (scope, element, attrs, controller) { 36 | var currentIndex = 0; 37 | 38 | scope.swiperOptions = angular.extend(scope.swiperOptions || {}, { 39 | initialSlide: 0, 40 | autoHeight: true, 41 | onInit: function(swiper){ 42 | scope.swiper = swiper; 43 | } 44 | }); 45 | 46 | scope.$on("wizard:Previous", function() { 47 | scope.swiper.slidePrev(true); 48 | }); 49 | scope.$on("wizard:Next", function() { 50 | scope.swiper.slideNext(true); 51 | }); 52 | 53 | scope.$watch('swiper', function(swiper) { 54 | if (!swiper) return; 55 | 56 | swiper.on('onTransitionStart', function(e){ 57 | $timeout(function() { 58 | currentIndex = e.activeIndex; 59 | }); 60 | $rootScope.$broadcast("wizard:IndexChanged", e.activeIndex, swiper.slides.length); 61 | }); 62 | 63 | // watch the current index's condition for changes and broadcast the new condition state on change 64 | scope.$watch(function() { 65 | return controller.checkNextCondition(currentIndex) && controller.checkPreviousCondition(currentIndex); 66 | }, function() { 67 | if (!scope.swiper) return; 68 | 69 | var allowNext = controller.checkNextCondition(currentIndex), 70 | allowPrev = controller.checkPreviousCondition(currentIndex); 71 | 72 | if (allowNext) 73 | scope.swiper.unlockSwipeToNext() 74 | else 75 | scope.swiper.lockSwipeToNext(); 76 | if (allowPrev) 77 | scope.swiper.unlockSwipeToPrev() 78 | else 79 | scope.swiper.lockSwipeToPrev(); 80 | 81 | $rootScope.$broadcast("wizard:NextCondition", allowNext); 82 | $rootScope.$broadcast("wizard:PreviousCondition", allowPrev); 83 | }); 84 | }); 85 | } 86 | } 87 | 88 | }]) 89 | .directive('ionWizardStep', ['$q', function($q) { 90 | return { 91 | restrict: 'EA', 92 | scope: { 93 | nextConditionFn: '&nextCondition', 94 | prevConditionFn: "&prevCondition" 95 | }, 96 | require: '^^ionWizard', 97 | link: function(scope, element, attrs, controller) { 98 | var nextFn = function() { 99 | // if there's no condition, just set the condition to true, otherwise evaluate 100 | return angular.isUndefined(attrs.nextCondition) 101 | ? true 102 | : scope.nextConditionFn(); 103 | }; 104 | 105 | var prevFn = function() { 106 | return angular.isUndefined(attrs.prevCondition) 107 | ? true 108 | : scope.prevConditionFn(); 109 | }; 110 | 111 | var conditions = { 112 | next: nextFn, 113 | prev: prevFn 114 | }; 115 | 116 | controller.addCondition(conditions); 117 | } 118 | } 119 | }]) 120 | .directive('ionWizardPrevious', ['$rootScope', function($rootScope) { 121 | return{ 122 | restrict: 'EA', 123 | scope: {}, 124 | link: function(scope, element, attrs, controller) { 125 | element.addClass('ng-hide'); 126 | 127 | element.on('click', function() { 128 | $rootScope.$broadcast("wizard:Previous"); 129 | }); 130 | 131 | scope.$on("wizard:IndexChanged", function(e, index) { 132 | element.toggleClass('ng-hide', index == 0); 133 | }); 134 | 135 | scope.$on("wizard:PreviousCondition", function(e, condition) { 136 | element.attr("disabled", !condition); 137 | }); 138 | } 139 | } 140 | }]) 141 | .directive('ionWizardNext', ['$rootScope', function($rootScope) { 142 | return{ 143 | restrict: 'EA', 144 | scope: {}, 145 | link: function(scope, element, attrs, controller) { 146 | element.on('click', function() { 147 | $rootScope.$broadcast("wizard:Next"); 148 | }); 149 | 150 | scope.$on("wizard:IndexChanged", function(e, index, count) { 151 | element.toggleClass('ng-hide', index == count - 1); 152 | }); 153 | 154 | scope.$on("wizard:NextCondition", function(e, condition) { 155 | element.attr("disabled", !condition); 156 | }); 157 | } 158 | } 159 | }]) 160 | .directive('ionWizardStart', [function() { 161 | return{ 162 | restrict: 'EA', 163 | scope: { 164 | startFn: '&ionWizardStart', 165 | startCondition: '&condition' 166 | }, 167 | link: function(scope, element, attrs) { 168 | element.addClass('ng-hide'); 169 | 170 | function checkCondition() { 171 | return (angular.isUndefined(attrs.condition)) ? true : scope.startCondition(); 172 | } 173 | 174 | element.on('click', function() { 175 | scope.startFn(); 176 | }); 177 | 178 | scope.$watch(function() { 179 | return checkCondition() 180 | }, function(result) { 181 | element.attr('disabled', !result); 182 | }); 183 | 184 | scope.$on("wizard:IndexChanged", function(e, index, count) { 185 | element.toggleClass('ng-hide', index < count - 1); 186 | }); 187 | } 188 | } 189 | }]); 190 | -------------------------------------------------------------------------------- /dist/ion-wizard.min.js: -------------------------------------------------------------------------------- 1 | angular.module("ionic.wizard",[]).directive("ionWizard",["$rootScope","$timeout",function(n,i){return{restrict:"EA",controller:[function(){var n=[];this.addCondition=function(i){n.push(i)},this.getCondition=function(i){return n[i]},this.checkNextCondition=function(i){return i>n.length-1?!1:n[i].next()},this.checkPreviousCondition=function(i){return i>n.length-1?!1:n[i].prev()}}],link:function(t,o,e,r){var d=0;t.swiperOptions=angular.extend(t.swiperOptions||{},{initialSlide:0,autoHeight:!0,onInit:function(n){t.swiper=n}}),t.$on("wizard:Previous",function(){t.swiper.slidePrev(!0)}),t.$on("wizard:Next",function(){t.swiper.slideNext(!0)}),t.$watch("swiper",function(o){o&&(o.on("onTransitionStart",function(t){i(function(){d=t.activeIndex}),n.$broadcast("wizard:IndexChanged",t.activeIndex,o.slides.length)}),t.$watch(function(){return r.checkNextCondition(d)&&r.checkPreviousCondition(d)},function(){if(t.swiper){var i=r.checkNextCondition(d),o=r.checkPreviousCondition(d);i?t.swiper.unlockSwipeToNext():t.swiper.lockSwipeToNext(),o?t.swiper.unlockSwipeToPrev():t.swiper.lockSwipeToPrev(),n.$broadcast("wizard:NextCondition",i),n.$broadcast("wizard:PreviousCondition",o)}}))})}}}]).directive("ionWizardStep",["$q",function(n){return{restrict:"EA",scope:{nextConditionFn:"&nextCondition",prevConditionFn:"&prevCondition"},require:"^^ionWizard",link:function(n,i,t,o){var e=function(){return angular.isUndefined(t.nextCondition)?!0:n.nextConditionFn()},r=function(){return angular.isUndefined(t.prevCondition)?!0:n.prevConditionFn()},d={next:e,prev:r};o.addCondition(d)}}}]).directive("ionWizardPrevious",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.addClass("ng-hide"),t.on("click",function(){n.$broadcast("wizard:Previous")}),i.$on("wizard:IndexChanged",function(n,i){t.toggleClass("ng-hide",0==i)}),i.$on("wizard:PreviousCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardNext",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.on("click",function(){n.$broadcast("wizard:Next")}),i.$on("wizard:IndexChanged",function(n,i,o){t.toggleClass("ng-hide",i==o-1)}),i.$on("wizard:NextCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardStart",[function(){return{restrict:"EA",scope:{startFn:"&ionWizardStart",startCondition:"&condition"},link:function(n,i,t){function o(){return angular.isUndefined(t.condition)?!0:n.startCondition()}i.addClass("ng-hide"),i.on("click",function(){n.startFn()}),n.$watch(function(){return o()},function(n){i.attr("disabled",!n)}),n.$on("wizard:IndexChanged",function(n,t,o){i.toggleClass("ng-hide",o-1>t)})}}}]); -------------------------------------------------------------------------------- /example-storage/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /example-storage/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | node_modules/ 5 | platforms/ 6 | plugins/ 7 | -------------------------------------------------------------------------------- /example-storage/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-storage", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.2.4" 6 | }, 7 | "dependencies": { 8 | "ngstorage": "*" 9 | } 10 | } -------------------------------------------------------------------------------- /example-storage/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | example-storage 4 | 5 | An Ionic Framework and Cordova project. 6 | 7 | 8 | Ionic Framework Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example-storage/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (!sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /example-storage/hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run 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: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /example-storage/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 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example-storage/www/js/app.js: -------------------------------------------------------------------------------- 1 | // Ionic Starter App 2 | 3 | // angular.module is a global place for creating, registering and retrieving Angular modules 4 | // 'starter' is the name of this angular module example (also set in a attribute in index.html) 5 | // the 2nd parameter is an array of 'requires' 6 | // 'starter.services' is found in services.js 7 | // 'starter.controllers' is found in controllers.js 8 | angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ionic.wizard', 'ngStorage']) 9 | 10 | .run(function($ionicPlatform) { 11 | $ionicPlatform.ready(function() { 12 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 13 | // for form inputs) 14 | if (window.cordova && window.cordova.plugins.Keyboard) { 15 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 16 | } 17 | if (window.StatusBar) { 18 | // org.apache.cordova.statusbar required 19 | StatusBar.styleDefault(); 20 | } 21 | }); 22 | }) 23 | 24 | .config(function($stateProvider, $urlRouterProvider) { 25 | 26 | // set default route to wizard 27 | var defaultRoute = '/start/intro'; 28 | 29 | // check whether wizard has been run in order to change default route 30 | // we cannot inject ngStorage dependency in a config module, so we need to use plain localStorage object 31 | if (localStorage.getItem('ngStorage-myAppRun')) { 32 | console.log('wizard has been run - skip!'); 33 | defaultRoute = '/tab/dash'; 34 | } 35 | 36 | 37 | // Ionic uses AngularUI Router which uses the concept of states 38 | // Learn more here: https://github.com/angular-ui/ui-router 39 | // Set up the various states which the app can be in. 40 | // Each state's controller can be found in controllers.js 41 | $stateProvider 42 | 43 | .state('main', { 44 | url: '/start', 45 | abstract: true, 46 | template: '' 47 | }) 48 | 49 | .state('main.intro', { 50 | url: '/intro', 51 | templateUrl: 'templates/intro.html', 52 | controller: 'IntroCtrl' 53 | }) 54 | 55 | // setup an abstract state for the tabs directive 56 | .state('tab', { 57 | url: "/tab", 58 | abstract: true, 59 | templateUrl: "templates/tabs.html" 60 | }) 61 | 62 | // Each tab has its own nav history stack: 63 | 64 | .state('tab.dash', { 65 | url: '/dash', 66 | views: { 67 | 'tab-dash': { 68 | templateUrl: 'templates/tab-dash.html', 69 | controller: 'DashCtrl' 70 | } 71 | } 72 | }) 73 | 74 | .state('tab.chats', { 75 | url: '/chats', 76 | views: { 77 | 'tab-chats': { 78 | templateUrl: 'templates/tab-chats.html', 79 | controller: 'ChatsCtrl' 80 | } 81 | } 82 | }) 83 | .state('tab.chat-detail', { 84 | url: '/chats/:chatId', 85 | views: { 86 | 'tab-chats': { 87 | templateUrl: 'templates/chat-detail.html', 88 | controller: 'ChatDetailCtrl' 89 | } 90 | } 91 | }) 92 | 93 | .state('tab.account', { 94 | url: '/account', 95 | views: { 96 | 'tab-account': { 97 | templateUrl: 'templates/tab-account.html', 98 | controller: 'AccountCtrl' 99 | } 100 | } 101 | }); 102 | 103 | // if none of the above states are matched, use this as the fallback 104 | $urlRouterProvider.otherwise(defaultRoute); 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /example-storage/www/js/controllers.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.controllers', []) 2 | 3 | .controller('IntroCtrl', ['$scope', '$state', '$localStorage', function($scope, $state, $localStorage) { 4 | // here we store wizard data 5 | $scope.step2 = {}; 6 | $scope.step3 = {}; 7 | 8 | function persistWizardData() { 9 | // set flag to indicate wizard has been run 10 | $localStorage.myAppRun = true; 11 | 12 | // save additional data 13 | $localStorage.myAppData = { 14 | something: $scope.wizard.something, 15 | someOtherData: 'test data' 16 | }; 17 | } 18 | 19 | $scope.startCondition = function() { 20 | return angular.isDefined($scope.step3.something); 21 | }; 22 | 23 | $scope.start = function() { 24 | // save whatever data we need and then redirect to main app 25 | persistWizardData(); 26 | 27 | $state.go('tab.dash'); 28 | }; 29 | 30 | }]) 31 | 32 | .controller('DashCtrl', function($scope) {}) 33 | 34 | .controller('ChatsCtrl', function($scope, Chats) { 35 | $scope.chats = Chats.all(); 36 | $scope.remove = function(chat) { 37 | Chats.remove(chat); 38 | } 39 | }) 40 | 41 | .controller('ChatDetailCtrl', function($scope, $stateParams, Chats) { 42 | $scope.chat = Chats.get($stateParams.chatId); 43 | }) 44 | 45 | .controller('AccountCtrl', function($scope) { 46 | $scope.settings = { 47 | enableFriends: true 48 | }; 49 | }); 50 | -------------------------------------------------------------------------------- /example-storage/www/js/ion-wizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ionic Wizard v2.0.1 3 | 4 | 2016-02-13 5 | Updated to work with Ionic 1.2 6 | */ 7 | angular.module('ionic.wizard', []) 8 | .directive('ionWizard', ['$rootScope', '$timeout', function($rootScope, $timeout) { 9 | return{ 10 | restrict: 'EA', 11 | controller: [function() { 12 | var conditions = []; 13 | 14 | this.addCondition = function(condition) { 15 | conditions.push(condition); 16 | }; 17 | 18 | this.getCondition = function(index) { 19 | return conditions[index]; 20 | }; 21 | 22 | this.checkNextCondition = function(index) { 23 | return index > (conditions.length - 1) 24 | ? false 25 | : conditions[index].next(); 26 | }; 27 | 28 | this.checkPreviousCondition = function(index) { 29 | return index > (conditions.length - 1) 30 | ? false 31 | : conditions[index].prev(); 32 | }; 33 | 34 | }], 35 | link: function (scope, element, attrs, controller) { 36 | var currentIndex = 0; 37 | 38 | scope.swiperOptions = angular.extend(scope.swiperOptions || {}, { 39 | initialSlide: 0, 40 | autoHeight: true, 41 | onInit: function(swiper){ 42 | scope.swiper = swiper; 43 | } 44 | }); 45 | 46 | scope.$on("wizard:Previous", function() { 47 | scope.swiper.slidePrev(true); 48 | }); 49 | scope.$on("wizard:Next", function() { 50 | scope.swiper.slideNext(true); 51 | }); 52 | 53 | scope.$watch('swiper', function(swiper) { 54 | if (!swiper) return; 55 | 56 | swiper.on('onTransitionStart', function(e){ 57 | $timeout(function() { 58 | currentIndex = e.activeIndex; 59 | }); 60 | $rootScope.$broadcast("wizard:IndexChanged", e.activeIndex, swiper.slides.length); 61 | }); 62 | 63 | // watch the current index's condition for changes and broadcast the new condition state on change 64 | scope.$watch(function() { 65 | return controller.checkNextCondition(currentIndex) && controller.checkPreviousCondition(currentIndex); 66 | }, function() { 67 | if (!scope.swiper) return; 68 | 69 | var allowNext = controller.checkNextCondition(currentIndex), 70 | allowPrev = controller.checkPreviousCondition(currentIndex); 71 | 72 | if (allowNext) 73 | scope.swiper.unlockSwipeToNext() 74 | else 75 | scope.swiper.lockSwipeToNext(); 76 | if (allowPrev) 77 | scope.swiper.unlockSwipeToPrev() 78 | else 79 | scope.swiper.lockSwipeToPrev(); 80 | 81 | $rootScope.$broadcast("wizard:NextCondition", allowNext); 82 | $rootScope.$broadcast("wizard:PreviousCondition", allowPrev); 83 | }); 84 | }); 85 | } 86 | } 87 | 88 | }]) 89 | .directive('ionWizardStep', ['$q', function($q) { 90 | return { 91 | restrict: 'EA', 92 | scope: { 93 | nextConditionFn: '&nextCondition', 94 | prevConditionFn: "&prevCondition" 95 | }, 96 | require: '^^ionWizard', 97 | link: function(scope, element, attrs, controller) { 98 | var nextFn = function() { 99 | // if there's no condition, just set the condition to true, otherwise evaluate 100 | return angular.isUndefined(attrs.nextCondition) 101 | ? true 102 | : scope.nextConditionFn(); 103 | }; 104 | 105 | var prevFn = function() { 106 | return angular.isUndefined(attrs.prevCondition) 107 | ? true 108 | : scope.prevConditionFn(); 109 | }; 110 | 111 | var conditions = { 112 | next: nextFn, 113 | prev: prevFn 114 | }; 115 | 116 | controller.addCondition(conditions); 117 | } 118 | } 119 | }]) 120 | .directive('ionWizardPrevious', ['$rootScope', function($rootScope) { 121 | return{ 122 | restrict: 'EA', 123 | scope: {}, 124 | link: function(scope, element, attrs, controller) { 125 | element.addClass('ng-hide'); 126 | 127 | element.on('click', function() { 128 | $rootScope.$broadcast("wizard:Previous"); 129 | }); 130 | 131 | scope.$on("wizard:IndexChanged", function(e, index) { 132 | element.toggleClass('ng-hide', index == 0); 133 | }); 134 | 135 | scope.$on("wizard:PreviousCondition", function(e, condition) { 136 | element.attr("disabled", !condition); 137 | }); 138 | } 139 | } 140 | }]) 141 | .directive('ionWizardNext', ['$rootScope', function($rootScope) { 142 | return{ 143 | restrict: 'EA', 144 | scope: {}, 145 | link: function(scope, element, attrs, controller) { 146 | element.on('click', function() { 147 | $rootScope.$broadcast("wizard:Next"); 148 | }); 149 | 150 | scope.$on("wizard:IndexChanged", function(e, index, count) { 151 | element.toggleClass('ng-hide', index == count - 1); 152 | }); 153 | 154 | scope.$on("wizard:NextCondition", function(e, condition) { 155 | element.attr("disabled", !condition); 156 | }); 157 | } 158 | } 159 | }]) 160 | .directive('ionWizardStart', [function() { 161 | return{ 162 | restrict: 'EA', 163 | scope: { 164 | startFn: '&ionWizardStart', 165 | startCondition: '&condition' 166 | }, 167 | link: function(scope, element, attrs) { 168 | element.addClass('ng-hide'); 169 | 170 | function checkCondition() { 171 | return (angular.isUndefined(attrs.condition)) ? true : scope.startCondition(); 172 | } 173 | 174 | element.on('click', function() { 175 | scope.startFn(); 176 | }); 177 | 178 | scope.$watch(function() { 179 | return checkCondition() 180 | }, function(result) { 181 | element.attr('disabled', !result); 182 | }); 183 | 184 | scope.$on("wizard:IndexChanged", function(e, index, count) { 185 | element.toggleClass('ng-hide', index < count - 1); 186 | }); 187 | } 188 | } 189 | }]); 190 | -------------------------------------------------------------------------------- /example-storage/www/js/ion-wizard.min.js: -------------------------------------------------------------------------------- 1 | angular.module("ionic.wizard",[]).directive("ionWizard",["$rootScope","$timeout",function(n,i){return{restrict:"EA",controller:[function(){var n=[];this.addCondition=function(i){n.push(i)},this.getCondition=function(i){return n[i]},this.checkNextCondition=function(i){return i>n.length-1?!1:n[i].next()},this.checkPreviousCondition=function(i){return i>n.length-1?!1:n[i].prev()}}],link:function(t,o,e,r){var d=0;t.swiperOptions=angular.extend(t.swiperOptions||{},{initialSlide:0,autoHeight:!0,onInit:function(n){t.swiper=n}}),t.$on("wizard:Previous",function(){t.swiper.slidePrev(!0)}),t.$on("wizard:Next",function(){t.swiper.slideNext(!0)}),t.$watch("swiper",function(o){o&&(o.on("onTransitionStart",function(t){i(function(){d=t.activeIndex}),n.$broadcast("wizard:IndexChanged",t.activeIndex,o.slides.length)}),t.$watch(function(){return r.checkNextCondition(d)&&r.checkPreviousCondition(d)},function(){if(t.swiper){var i=r.checkNextCondition(d),o=r.checkPreviousCondition(d);i?t.swiper.unlockSwipeToNext():t.swiper.lockSwipeToNext(),o?t.swiper.unlockSwipeToPrev():t.swiper.lockSwipeToPrev(),n.$broadcast("wizard:NextCondition",i),n.$broadcast("wizard:PreviousCondition",o)}}))})}}}]).directive("ionWizardStep",["$q",function(n){return{restrict:"EA",scope:{nextConditionFn:"&nextCondition",prevConditionFn:"&prevCondition"},require:"^^ionWizard",link:function(n,i,t,o){var e=function(){return angular.isUndefined(t.nextCondition)?!0:n.nextConditionFn()},r=function(){return angular.isUndefined(t.prevCondition)?!0:n.prevConditionFn()},d={next:e,prev:r};o.addCondition(d)}}}]).directive("ionWizardPrevious",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.addClass("ng-hide"),t.on("click",function(){n.$broadcast("wizard:Previous")}),i.$on("wizard:IndexChanged",function(n,i){t.toggleClass("ng-hide",0==i)}),i.$on("wizard:PreviousCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardNext",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.on("click",function(){n.$broadcast("wizard:Next")}),i.$on("wizard:IndexChanged",function(n,i,o){t.toggleClass("ng-hide",i==o-1)}),i.$on("wizard:NextCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardStart",[function(){return{restrict:"EA",scope:{startFn:"&ionWizardStart",startCondition:"&condition"},link:function(n,i,t){function o(){return angular.isUndefined(t.condition)?!0:n.startCondition()}i.addClass("ng-hide"),i.on("click",function(){n.startFn()}),n.$watch(function(){return o()},function(n){i.attr("disabled",!n)}),n.$on("wizard:IndexChanged",function(n,t,o){i.toggleClass("ng-hide",o-1>t)})}}}]); -------------------------------------------------------------------------------- /example-storage/www/js/services.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.services', []) 2 | 3 | .factory('Chats', function() { 4 | // Might use a resource here that returns a JSON array 5 | 6 | // Some fake testing data 7 | var chats = [{ 8 | id: 0, 9 | name: 'Ben Sparrow', 10 | lastText: 'You on your way?', 11 | face: 'https://pbs.twimg.com/profile_images/514549811765211136/9SgAuHeY.png' 12 | }, { 13 | id: 1, 14 | name: 'Max Lynx', 15 | lastText: 'Hey, it\'s me', 16 | face: 'https://avatars3.githubusercontent.com/u/11214?v=3&s=460' 17 | }, { 18 | id: 2, 19 | name: 'Andrew Jostlin', 20 | lastText: 'Did you get the ice cream?', 21 | face: 'https://pbs.twimg.com/profile_images/491274378181488640/Tti0fFVJ.jpeg' 22 | }, { 23 | id: 3, 24 | name: 'Adam Bradleyson', 25 | lastText: 'I should buy a boat', 26 | face: 'https://pbs.twimg.com/profile_images/479090794058379264/84TKj_qa.jpeg' 27 | }, { 28 | id: 4, 29 | name: 'Perry Governor', 30 | lastText: 'Look at my mukluks!', 31 | face: 'https://pbs.twimg.com/profile_images/491995398135767040/ie2Z_V6e.jpeg' 32 | }]; 33 | 34 | return { 35 | all: function() { 36 | return chats; 37 | }, 38 | remove: function(chat) { 39 | chats.splice(chats.indexOf(chat), 1); 40 | }, 41 | get: function(chatId) { 42 | for (var i = 0; i < chats.length; i++) { 43 | if (chats[i].id === parseInt(chatId)) { 44 | return chats[i]; 45 | } 46 | } 47 | return null; 48 | } 49 | }; 50 | }); 51 | -------------------------------------------------------------------------------- /example-storage/www/templates/chat-detail.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 |

11 | {{chat.lastText}} 12 |

13 |
14 |
15 | -------------------------------------------------------------------------------- /example-storage/www/templates/intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |

Thanks for trying out this wizard!

27 |
28 |
29 |

30 | Click the buttons above 31 |

32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |

This slide is scrollable

41 |
42 |
43 |

44 | Click the buttons above 45 |

46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |

This slide is scrollable

55 |
56 |
57 |

58 | Click the buttons above 59 |

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |

This slide is scrollable

69 |
70 |
71 |

72 | Click the buttons above 73 |

74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |

This slide is scrollable

83 |
84 |
85 |

86 | Click the buttons above 87 |

88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |

This slide is scrollable

97 |
98 |
99 |

100 | Click the buttons above 101 |

102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |

This slide is scrollable

111 |
112 |
113 |

114 | Click the buttons above 115 |

116 |
117 |
118 |
119 |
120 |
121 |
122 | 123 | 124 |
125 |
126 |
127 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |

The next button has been disabled until the input is filled

139 |
140 |
141 |
142 |
143 |

Now you can move on! Click on the next button. The previous button has now been disabled

144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |

This slide is scrollable

153 |
154 |
155 |

156 | Click the buttons above 157 |

158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |

This slide is scrollable

167 |
168 |
169 |

170 | Click the buttons above 171 |

172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |

This slide is scrollable

181 |
182 |
183 |

184 | Click the buttons above 185 |

186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |

This slide is scrollable

195 |
196 |
197 |

198 | Click the buttons above 199 |

200 |
201 |
202 |
203 |
204 |
205 |
206 | 207 |
208 |
209 |

Thanks for running the wizard!

210 |

As a last step please fill in the field below

211 |
212 | 216 |
217 |

You can now launch the app using the button above!

218 |
219 |
220 |
221 |
222 | 223 |
-------------------------------------------------------------------------------- /example-storage/www/templates/tab-account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Enable Friends 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example-storage/www/templates/tab-chats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

{{chat.name}}

7 |

{{chat.lastText}}

8 | 9 | 10 | 11 | Delete 12 | 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /example-storage/www/templates/tab-dash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
Recent Updates
5 |
6 |
7 | There is a fire in sector 3 8 |
9 |
10 |
11 |
12 |
Health
13 |
14 |
15 | You ate an apple today! 16 |
17 |
18 |
19 |
20 |
Upcoming
21 |
22 |
23 | You have 29 meetings on your calendar tomorrow. 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /example-storage/www/templates/tabs.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | node_modules/ 5 | platforms/ 6 | plugins/ 7 | lib/ -------------------------------------------------------------------------------- /example/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "devDependencies": { 4 | "ionic": "driftyco/ionic-bower#1.3.1" 5 | }, 6 | "resolutions": { 7 | "angular": "1.5.3", 8 | "angular-animate": "1.5.3", 9 | "angular-sanitize": "1.5.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | example 4 | 5 | An Ionic Framework and Cordova project. 6 | 7 | 8 | Ionic Framework Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (!sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /example/hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run 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: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /example/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 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/www/js/app.js: -------------------------------------------------------------------------------- 1 | // Ionic Starter App 2 | 3 | // angular.module is a global place for creating, registering and retrieving Angular modules 4 | // 'starter' is the name of this angular module example (also set in a attribute in index.html) 5 | // the 2nd parameter is an array of 'requires' 6 | // 'starter.services' is found in services.js 7 | // 'starter.controllers' is found in controllers.js 8 | angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ionic.wizard']) 9 | 10 | .run(function($ionicPlatform) { 11 | $ionicPlatform.ready(function() { 12 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 13 | // for form inputs) 14 | if (window.cordova && window.cordova.plugins.Keyboard) { 15 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 16 | } 17 | if (window.StatusBar) { 18 | // org.apache.cordova.statusbar required 19 | StatusBar.styleDefault(); 20 | } 21 | }); 22 | }) 23 | 24 | .config(function($stateProvider, $urlRouterProvider) { 25 | 26 | // Ionic uses AngularUI Router which uses the concept of states 27 | // Learn more here: https://github.com/angular-ui/ui-router 28 | // Set up the various states which the app can be in. 29 | // Each state's controller can be found in controllers.js 30 | $stateProvider 31 | 32 | .state('main', { 33 | url: '/start', 34 | abstract: true, 35 | template: '' 36 | }) 37 | 38 | .state('main.intro', { 39 | url: '/intro', 40 | templateUrl: 'templates/intro.html', 41 | controller: 'IntroCtrl' 42 | }) 43 | 44 | // setup an abstract state for the tabs directive 45 | .state('tab', { 46 | url: "/tab", 47 | abstract: true, 48 | templateUrl: "templates/tabs.html" 49 | }) 50 | 51 | // Each tab has its own nav history stack: 52 | 53 | .state('tab.dash', { 54 | url: '/dash', 55 | views: { 56 | 'tab-dash': { 57 | templateUrl: 'templates/tab-dash.html', 58 | controller: 'DashCtrl' 59 | } 60 | } 61 | }) 62 | 63 | .state('tab.chats', { 64 | url: '/chats', 65 | views: { 66 | 'tab-chats': { 67 | templateUrl: 'templates/tab-chats.html', 68 | controller: 'ChatsCtrl' 69 | } 70 | } 71 | }) 72 | .state('tab.chat-detail', { 73 | url: '/chats/:chatId', 74 | views: { 75 | 'tab-chats': { 76 | templateUrl: 'templates/chat-detail.html', 77 | controller: 'ChatDetailCtrl' 78 | } 79 | } 80 | }) 81 | 82 | .state('tab.account', { 83 | url: '/account', 84 | views: { 85 | 'tab-account': { 86 | templateUrl: 'templates/tab-account.html', 87 | controller: 'AccountCtrl' 88 | } 89 | } 90 | }); 91 | 92 | // if none of the above states are matched, use this as the fallback 93 | $urlRouterProvider.otherwise('/start/intro'); 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /example/www/js/controllers.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.controllers', []) 2 | 3 | .controller('IntroCtrl', ['$scope', '$state', function($scope, $state) { 4 | $scope.step1 = {}; 5 | $scope.step2 = {}; 6 | $scope.step3 = {}; 7 | 8 | $scope.start = function() { 9 | $state.go('tab.dash'); 10 | }; 11 | 12 | $scope.startCondition = function() { 13 | return angular.isDefined($scope.step3.something); 14 | }; 15 | 16 | }]) 17 | 18 | .controller('DashCtrl', function($scope) {}) 19 | 20 | .controller('ChatsCtrl', function($scope, Chats) { 21 | $scope.chats = Chats.all(); 22 | $scope.remove = function(chat) { 23 | Chats.remove(chat); 24 | } 25 | }) 26 | 27 | .controller('ChatDetailCtrl', function($scope, $stateParams, Chats) { 28 | $scope.chat = Chats.get($stateParams.chatId); 29 | }) 30 | 31 | .controller('AccountCtrl', function($scope) { 32 | $scope.settings = { 33 | enableFriends: true 34 | }; 35 | }); 36 | -------------------------------------------------------------------------------- /example/www/js/ion-wizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ionic Wizard v2.0.1 3 | 4 | 2016-02-13 5 | Updated to work with Ionic 1.2 6 | */ 7 | angular.module('ionic.wizard', []) 8 | .directive('ionWizard', ['$rootScope', '$timeout', function($rootScope, $timeout) { 9 | return{ 10 | restrict: 'EA', 11 | controller: [function() { 12 | var conditions = []; 13 | 14 | this.addCondition = function(condition) { 15 | conditions.push(condition); 16 | }; 17 | 18 | this.getCondition = function(index) { 19 | return conditions[index]; 20 | }; 21 | 22 | this.checkNextCondition = function(index) { 23 | return index > (conditions.length - 1) 24 | ? false 25 | : conditions[index].next(); 26 | }; 27 | 28 | this.checkPreviousCondition = function(index) { 29 | return index > (conditions.length - 1) 30 | ? false 31 | : conditions[index].prev(); 32 | }; 33 | 34 | }], 35 | link: function (scope, element, attrs, controller) { 36 | var currentIndex = 0; 37 | 38 | scope.swiperOptions = angular.extend(scope.swiperOptions || {}, { 39 | initialSlide: 0, 40 | autoHeight: true, 41 | onInit: function(swiper){ 42 | scope.swiper = swiper; 43 | } 44 | }); 45 | 46 | scope.$on("wizard:Previous", function() { 47 | scope.swiper.slidePrev(true); 48 | }); 49 | scope.$on("wizard:Next", function() { 50 | scope.swiper.slideNext(true); 51 | }); 52 | 53 | scope.$watch('swiper', function(swiper) { 54 | if (!swiper) return; 55 | 56 | swiper.on('onTransitionStart', function(e){ 57 | $timeout(function() { 58 | currentIndex = e.activeIndex; 59 | }); 60 | $rootScope.$broadcast("wizard:IndexChanged", e.activeIndex, swiper.slides.length); 61 | }); 62 | 63 | // watch the current index's condition for changes and broadcast the new condition state on change 64 | scope.$watch(function() { 65 | return controller.checkNextCondition(currentIndex) && controller.checkPreviousCondition(currentIndex); 66 | }, function() { 67 | if (!scope.swiper) return; 68 | 69 | var allowNext = controller.checkNextCondition(currentIndex), 70 | allowPrev = controller.checkPreviousCondition(currentIndex); 71 | 72 | if (allowNext) 73 | scope.swiper.unlockSwipeToNext() 74 | else 75 | scope.swiper.lockSwipeToNext(); 76 | if (allowPrev) 77 | scope.swiper.unlockSwipeToPrev() 78 | else 79 | scope.swiper.lockSwipeToPrev(); 80 | 81 | $rootScope.$broadcast("wizard:NextCondition", allowNext); 82 | $rootScope.$broadcast("wizard:PreviousCondition", allowPrev); 83 | }); 84 | }); 85 | } 86 | } 87 | 88 | }]) 89 | .directive('ionWizardStep', ['$q', function($q) { 90 | return { 91 | restrict: 'EA', 92 | scope: { 93 | nextConditionFn: '&nextCondition', 94 | prevConditionFn: "&prevCondition" 95 | }, 96 | require: '^^ionWizard', 97 | link: function(scope, element, attrs, controller) { 98 | var nextFn = function() { 99 | // if there's no condition, just set the condition to true, otherwise evaluate 100 | return angular.isUndefined(attrs.nextCondition) 101 | ? true 102 | : scope.nextConditionFn(); 103 | }; 104 | 105 | var prevFn = function() { 106 | return angular.isUndefined(attrs.prevCondition) 107 | ? true 108 | : scope.prevConditionFn(); 109 | }; 110 | 111 | var conditions = { 112 | next: nextFn, 113 | prev: prevFn 114 | }; 115 | 116 | controller.addCondition(conditions); 117 | } 118 | } 119 | }]) 120 | .directive('ionWizardPrevious', ['$rootScope', function($rootScope) { 121 | return{ 122 | restrict: 'EA', 123 | scope: {}, 124 | link: function(scope, element, attrs, controller) { 125 | element.addClass('ng-hide'); 126 | 127 | element.on('click', function() { 128 | $rootScope.$broadcast("wizard:Previous"); 129 | }); 130 | 131 | scope.$on("wizard:IndexChanged", function(e, index) { 132 | element.toggleClass('ng-hide', index == 0); 133 | }); 134 | 135 | scope.$on("wizard:PreviousCondition", function(e, condition) { 136 | element.attr("disabled", !condition); 137 | }); 138 | } 139 | } 140 | }]) 141 | .directive('ionWizardNext', ['$rootScope', function($rootScope) { 142 | return{ 143 | restrict: 'EA', 144 | scope: {}, 145 | link: function(scope, element, attrs, controller) { 146 | element.on('click', function() { 147 | $rootScope.$broadcast("wizard:Next"); 148 | }); 149 | 150 | scope.$on("wizard:IndexChanged", function(e, index, count) { 151 | element.toggleClass('ng-hide', index == count - 1); 152 | }); 153 | 154 | scope.$on("wizard:NextCondition", function(e, condition) { 155 | element.attr("disabled", !condition); 156 | }); 157 | } 158 | } 159 | }]) 160 | .directive('ionWizardStart', [function() { 161 | return{ 162 | restrict: 'EA', 163 | scope: { 164 | startFn: '&ionWizardStart', 165 | startCondition: '&condition' 166 | }, 167 | link: function(scope, element, attrs) { 168 | element.addClass('ng-hide'); 169 | 170 | function checkCondition() { 171 | return (angular.isUndefined(attrs.condition)) ? true : scope.startCondition(); 172 | } 173 | 174 | element.on('click', function() { 175 | scope.startFn(); 176 | }); 177 | 178 | scope.$watch(function() { 179 | return checkCondition() 180 | }, function(result) { 181 | element.attr('disabled', !result); 182 | }); 183 | 184 | scope.$on("wizard:IndexChanged", function(e, index, count) { 185 | element.toggleClass('ng-hide', index < count - 1); 186 | }); 187 | } 188 | } 189 | }]); 190 | -------------------------------------------------------------------------------- /example/www/js/ion-wizard.min.js: -------------------------------------------------------------------------------- 1 | angular.module("ionic.wizard",[]).directive("ionWizard",["$rootScope","$timeout",function(n,i){return{restrict:"EA",controller:[function(){var n=[];this.addCondition=function(i){n.push(i)},this.getCondition=function(i){return n[i]},this.checkNextCondition=function(i){return i>n.length-1?!1:n[i].next()},this.checkPreviousCondition=function(i){return i>n.length-1?!1:n[i].prev()}}],link:function(t,o,e,r){var d=0;t.swiperOptions=angular.extend(t.swiperOptions||{},{initialSlide:0,autoHeight:!0,onInit:function(n){t.swiper=n}}),t.$on("wizard:Previous",function(){t.swiper.slidePrev(!0)}),t.$on("wizard:Next",function(){t.swiper.slideNext(!0)}),t.$watch("swiper",function(o){o&&(o.on("onTransitionStart",function(t){i(function(){d=t.activeIndex}),n.$broadcast("wizard:IndexChanged",t.activeIndex,o.slides.length)}),t.$watch(function(){return r.checkNextCondition(d)&&r.checkPreviousCondition(d)},function(){if(t.swiper){var i=r.checkNextCondition(d),o=r.checkPreviousCondition(d);i?t.swiper.unlockSwipeToNext():t.swiper.lockSwipeToNext(),o?t.swiper.unlockSwipeToPrev():t.swiper.lockSwipeToPrev(),n.$broadcast("wizard:NextCondition",i),n.$broadcast("wizard:PreviousCondition",o)}}))})}}}]).directive("ionWizardStep",["$q",function(n){return{restrict:"EA",scope:{nextConditionFn:"&nextCondition",prevConditionFn:"&prevCondition"},require:"^^ionWizard",link:function(n,i,t,o){var e=function(){return angular.isUndefined(t.nextCondition)?!0:n.nextConditionFn()},r=function(){return angular.isUndefined(t.prevCondition)?!0:n.prevConditionFn()},d={next:e,prev:r};o.addCondition(d)}}}]).directive("ionWizardPrevious",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.addClass("ng-hide"),t.on("click",function(){n.$broadcast("wizard:Previous")}),i.$on("wizard:IndexChanged",function(n,i){t.toggleClass("ng-hide",0==i)}),i.$on("wizard:PreviousCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardNext",["$rootScope",function(n){return{restrict:"EA",scope:{},link:function(i,t,o,e){t.on("click",function(){n.$broadcast("wizard:Next")}),i.$on("wizard:IndexChanged",function(n,i,o){t.toggleClass("ng-hide",i==o-1)}),i.$on("wizard:NextCondition",function(n,i){t.attr("disabled",!i)})}}}]).directive("ionWizardStart",[function(){return{restrict:"EA",scope:{startFn:"&ionWizardStart",startCondition:"&condition"},link:function(n,i,t){function o(){return angular.isUndefined(t.condition)?!0:n.startCondition()}i.addClass("ng-hide"),i.on("click",function(){n.startFn()}),n.$watch(function(){return o()},function(n){i.attr("disabled",!n)}),n.$on("wizard:IndexChanged",function(n,t,o){i.toggleClass("ng-hide",o-1>t)})}}}]); -------------------------------------------------------------------------------- /example/www/js/services.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.services', []) 2 | 3 | .factory('Chats', function() { 4 | // Might use a resource here that returns a JSON array 5 | 6 | // Some fake testing data 7 | var chats = [{ 8 | id: 0, 9 | name: 'Ben Sparrow', 10 | lastText: 'You on your way?', 11 | face: 'https://pbs.twimg.com/profile_images/514549811765211136/9SgAuHeY.png' 12 | }, { 13 | id: 1, 14 | name: 'Max Lynx', 15 | lastText: 'Hey, it\'s me', 16 | face: 'https://avatars3.githubusercontent.com/u/11214?v=3&s=460' 17 | }, { 18 | id: 2, 19 | name: 'Andrew Jostlin', 20 | lastText: 'Did you get the ice cream?', 21 | face: 'https://pbs.twimg.com/profile_images/491274378181488640/Tti0fFVJ.jpeg' 22 | }, { 23 | id: 3, 24 | name: 'Adam Bradleyson', 25 | lastText: 'I should buy a boat', 26 | face: 'https://pbs.twimg.com/profile_images/479090794058379264/84TKj_qa.jpeg' 27 | }, { 28 | id: 4, 29 | name: 'Perry Governor', 30 | lastText: 'Look at my mukluks!', 31 | face: 'https://pbs.twimg.com/profile_images/491995398135767040/ie2Z_V6e.jpeg' 32 | }]; 33 | 34 | return { 35 | all: function() { 36 | return chats; 37 | }, 38 | remove: function(chat) { 39 | chats.splice(chats.indexOf(chat), 1); 40 | }, 41 | get: function(chatId) { 42 | for (var i = 0; i < chats.length; i++) { 43 | if (chats[i].id === parseInt(chatId)) { 44 | return chats[i]; 45 | } 46 | } 47 | return null; 48 | } 49 | }; 50 | }); 51 | -------------------------------------------------------------------------------- /example/www/templates/chat-detail.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 |

11 | {{chat.lastText}} 12 |

13 |
14 |
15 | -------------------------------------------------------------------------------- /example/www/templates/intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |

Thanks for trying out this wizard!

27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |

This slide is scrollable

39 |
40 |
41 |

42 | Click the buttons above 43 |

44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |

This slide is scrollable

53 |
54 |
55 |

56 | Click the buttons above 57 |

58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |

This slide is scrollable

67 |
68 |
69 |

70 | Click the buttons above 71 |

72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |

This slide is scrollable

81 |
82 |
83 |

84 | Click the buttons above 85 |

86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |

This slide is scrollable

95 |
96 |
97 |

98 | Click the buttons above 99 |

100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |

This slide is scrollable

109 |
110 |
111 |

112 | Click the buttons above 113 |

114 |
115 |
116 |
117 |
118 |
119 |
120 | 121 | 122 |
123 |
124 |
125 | 129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |

The next button has been disabled until the input is filled

137 |
138 |
139 |
140 |
141 |

Now you can move on! Click on the next button. The previous button has now been disabled

142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |

This slide is scrollable

151 |
152 |
153 |

154 | Click the buttons above 155 |

156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |

This slide is scrollable

165 |
166 |
167 |

168 | Click the buttons above 169 |

170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |

This slide is scrollable

179 |
180 |
181 |

182 | Click the buttons above 183 |

184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |

This slide is scrollable

193 |
194 |
195 |

196 | Click the buttons above 197 |

198 |
199 |
200 |
201 |
202 |
203 |
204 | 205 |
206 |
207 |

Thanks for running the wizard!

208 |

As a last step please fill in the field below

209 |
210 | 214 |
215 |

You can now launch the app using the button above!

216 |
217 |
218 |
219 |
220 | 221 |
-------------------------------------------------------------------------------- /example/www/templates/tab-account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Enable Friends 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/www/templates/tab-chats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

{{chat.name}}

7 |

{{chat.lastText}}

8 | 9 | 10 | 11 | Delete 12 | 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /example/www/templates/tab-dash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
Recent Updates
5 |
6 |
7 | There is a fire in sector 3 8 |
9 |
10 |
11 |
12 |
Health
13 |
14 |
15 | You ate an apple today! 16 |
17 |
18 |
19 |
20 |
Upcoming
21 |
22 |
23 | You have 29 meetings on your calendar tomorrow. 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /example/www/templates/tabs.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var uglify = require("gulp-uglify"); 3 | var rename = require("gulp-rename"); 4 | var karmaServer = require("karma").server; 5 | 6 | // minify copy to dist/example dirs 7 | gulp.task("build", function() { 8 | gulp.src("src/**/*.js") 9 | .pipe(gulp.dest("dist")) // copy unminified to dist too 10 | .pipe(gulp.dest("example/www/js")) // copy unminified to example folders 11 | .pipe(gulp.dest("example-storage/www/js")) 12 | .pipe(uglify()) 13 | .pipe(rename(function(path) { 14 | path.extname = ".min.js" 15 | })) 16 | .pipe(gulp.dest("dist")) 17 | .pipe(gulp.dest("example/www/js")) 18 | .pipe(gulp.dest("example-storage/www/js")); 19 | }); 20 | 21 | // test single run 22 | gulp.task("test", function() { 23 | new karmaServer.start({ 24 | configFile: __dirname + "/karma.conf.js", 25 | singleRun: true, 26 | autoWatch : false, 27 | }); 28 | }); 29 | 30 | 31 | gulp.task("tdd", function() { 32 | new karmaServer.start({ 33 | configFile: __dirname + "/karma.conf.js" 34 | }) 35 | }); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | 4 | basePath : './', 5 | 6 | files : [ 7 | 'bower_components/ionic/js/ionic.bundle.js', 8 | 'bower_components/angular-mocks/angular-mocks.js', 9 | 'src*/**/*.js', 10 | 'tests*/**/*.js' 11 | ], 12 | 13 | autoWatch : true, 14 | 15 | frameworks: ['jasmine'], 16 | 17 | browsers : ['Chrome'], 18 | 19 | plugins : [ 20 | 'karma-chrome-launcher', 21 | 'karma-firefox-launcher', 22 | 'karma-jasmine', 23 | 'karma-junit-reporter' 24 | ], 25 | 26 | junitReporter : { 27 | outputFile: 'test_out/unit.xml', 28 | suite: 'unit' 29 | } 30 | 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-wizard", 3 | "version": "2.0.1", 4 | "description": "A set of Angular/Ionic directives to create a wizard using Ionic slide box component", 5 | "repository": "https://github.com/arielfaur/ionic-wizard", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "bower": "^1.3.1", 9 | "gulp": "^3.9.0", 10 | "gulp-rename": "^1.2.2", 11 | "gulp-uglify": "^1.2.0", 12 | "http-server": "^0.6.1", 13 | "jasmine-core": "2.3.4", 14 | "jasmine-node": "1.3", 15 | "karma": "~0.12", 16 | "karma-chrome-launcher": "0.2.0", 17 | "karma-firefox-launcher": "^0.1.6", 18 | "karma-jasmine": "0.3.6", 19 | "karma-junit-reporter": "^0.2.2", 20 | "shelljs": "^0.2.6" 21 | }, 22 | "scripts": { 23 | "postinstall": "bower install", 24 | "prestart": "npm install", 25 | "start": "http-server -a localhost -p 8000 -c-1", 26 | "pretest": "npm install", 27 | "test": "node node_modules/karma/bin/karma start karma.conf.js", 28 | "test-single-run": "karma start karma.conf.js --single-run" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ion-wizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ionic Wizard v2.0.1 3 | 4 | 2016-02-13 5 | Updated to work with Ionic 1.2 6 | */ 7 | angular.module('ionic.wizard', []) 8 | .directive('ionWizard', ['$rootScope', '$timeout', function($rootScope, $timeout) { 9 | return{ 10 | restrict: 'EA', 11 | controller: [function() { 12 | var conditions = []; 13 | 14 | this.addCondition = function(condition) { 15 | conditions.push(condition); 16 | }; 17 | 18 | this.getCondition = function(index) { 19 | return conditions[index]; 20 | }; 21 | 22 | this.checkNextCondition = function(index) { 23 | return index > (conditions.length - 1) 24 | ? false 25 | : conditions[index].next(); 26 | }; 27 | 28 | this.checkPreviousCondition = function(index) { 29 | return index > (conditions.length - 1) 30 | ? false 31 | : conditions[index].prev(); 32 | }; 33 | 34 | }], 35 | link: function (scope, element, attrs, controller) { 36 | var currentIndex = 0; 37 | 38 | scope.swiperOptions = angular.extend(scope.swiperOptions || {}, { 39 | initialSlide: 0, 40 | autoHeight: true, 41 | onInit: function(swiper){ 42 | scope.swiper = swiper; 43 | } 44 | }); 45 | 46 | scope.$on("wizard:Previous", function() { 47 | scope.swiper.slidePrev(true); 48 | }); 49 | scope.$on("wizard:Next", function() { 50 | scope.swiper.slideNext(true); 51 | }); 52 | 53 | scope.$watch('swiper', function(swiper) { 54 | if (!swiper) return; 55 | 56 | swiper.on('onTransitionStart', function(e){ 57 | $timeout(function() { 58 | currentIndex = e.activeIndex; 59 | }); 60 | $rootScope.$broadcast("wizard:IndexChanged", e.activeIndex, swiper.slides.length); 61 | }); 62 | 63 | // watch the current index's condition for changes and broadcast the new condition state on change 64 | scope.$watch(function() { 65 | return controller.checkNextCondition(currentIndex) && controller.checkPreviousCondition(currentIndex); 66 | }, function() { 67 | if (!scope.swiper) return; 68 | 69 | var allowNext = controller.checkNextCondition(currentIndex), 70 | allowPrev = controller.checkPreviousCondition(currentIndex); 71 | 72 | if (allowNext) 73 | scope.swiper.unlockSwipeToNext() 74 | else 75 | scope.swiper.lockSwipeToNext(); 76 | if (allowPrev) 77 | scope.swiper.unlockSwipeToPrev() 78 | else 79 | scope.swiper.lockSwipeToPrev(); 80 | 81 | $rootScope.$broadcast("wizard:NextCondition", allowNext); 82 | $rootScope.$broadcast("wizard:PreviousCondition", allowPrev); 83 | }); 84 | }); 85 | } 86 | } 87 | 88 | }]) 89 | .directive('ionWizardStep', ['$q', function($q) { 90 | return { 91 | restrict: 'EA', 92 | scope: { 93 | nextConditionFn: '&nextCondition', 94 | prevConditionFn: "&prevCondition" 95 | }, 96 | require: '^^ionWizard', 97 | link: function(scope, element, attrs, controller) { 98 | var nextFn = function() { 99 | // if there's no condition, just set the condition to true, otherwise evaluate 100 | return angular.isUndefined(attrs.nextCondition) 101 | ? true 102 | : scope.nextConditionFn(); 103 | }; 104 | 105 | var prevFn = function() { 106 | return angular.isUndefined(attrs.prevCondition) 107 | ? true 108 | : scope.prevConditionFn(); 109 | }; 110 | 111 | var conditions = { 112 | next: nextFn, 113 | prev: prevFn 114 | }; 115 | 116 | controller.addCondition(conditions); 117 | } 118 | } 119 | }]) 120 | .directive('ionWizardPrevious', ['$rootScope', function($rootScope) { 121 | return{ 122 | restrict: 'EA', 123 | scope: {}, 124 | link: function(scope, element, attrs, controller) { 125 | element.addClass('ng-hide'); 126 | 127 | element.on('click', function() { 128 | $rootScope.$broadcast("wizard:Previous"); 129 | }); 130 | 131 | scope.$on("wizard:IndexChanged", function(e, index) { 132 | element.toggleClass('ng-hide', index == 0); 133 | }); 134 | 135 | scope.$on("wizard:PreviousCondition", function(e, condition) { 136 | element.attr("disabled", !condition); 137 | }); 138 | } 139 | } 140 | }]) 141 | .directive('ionWizardNext', ['$rootScope', function($rootScope) { 142 | return{ 143 | restrict: 'EA', 144 | scope: {}, 145 | link: function(scope, element, attrs, controller) { 146 | element.on('click', function() { 147 | $rootScope.$broadcast("wizard:Next"); 148 | }); 149 | 150 | scope.$on("wizard:IndexChanged", function(e, index, count) { 151 | element.toggleClass('ng-hide', index == count - 1); 152 | }); 153 | 154 | scope.$on("wizard:NextCondition", function(e, condition) { 155 | element.attr("disabled", !condition); 156 | }); 157 | } 158 | } 159 | }]) 160 | .directive('ionWizardStart', [function() { 161 | return{ 162 | restrict: 'EA', 163 | scope: { 164 | startFn: '&ionWizardStart', 165 | startCondition: '&condition' 166 | }, 167 | link: function(scope, element, attrs) { 168 | element.addClass('ng-hide'); 169 | 170 | function checkCondition() { 171 | return (angular.isUndefined(attrs.condition)) ? true : scope.startCondition(); 172 | } 173 | 174 | element.on('click', function() { 175 | scope.startFn(); 176 | }); 177 | 178 | scope.$watch(function() { 179 | return checkCondition() 180 | }, function(result) { 181 | element.attr('disabled', !result); 182 | }); 183 | 184 | scope.$on("wizard:IndexChanged", function(e, index, count) { 185 | element.toggleClass('ng-hide', index < count - 1); 186 | }); 187 | } 188 | } 189 | }]); 190 | -------------------------------------------------------------------------------- /tests/ion-wizard_test.js: -------------------------------------------------------------------------------- 1 | describe('Unit testing wizard directives', function() { 2 | var $compile, 3 | $rootScope, 4 | $controller, 5 | scope; 6 | 7 | // Load the ionic.wizard module, which contains the directive 8 | beforeEach(module('ionic')); 9 | beforeEach(module('ionic.wizard')); 10 | 11 | // Store references to $rootScope and $compile 12 | // so they are available to all tests in this describe block 13 | beforeEach(inject(function(_$compile_, _$rootScope_, _$controller_){ 14 | // The injector unwraps the underscores (_) from around the parameter names when matching 15 | $compile = _$compile_; 16 | $rootScope = _$rootScope_; 17 | $controller = _$controller_; 18 | 19 | scope = $rootScope.$new(); 20 | 21 | spyOn($rootScope, '$broadcast').and.callThrough(); 22 | })); 23 | 24 | describe('Test wizard next button', function() { 25 | var element, wrappedElement; 26 | 27 | beforeEach(inject(function() { 28 | 29 | element = ""; 30 | wrappedElement = angular.element(element); 31 | })); 32 | 33 | it('Should broadcast next event on click', function() { 34 | // Compile a piece of HTML containing the directive 35 | element = $compile("")(scope); 36 | 37 | element.triggerHandler('click'); 38 | expect($rootScope.$broadcast).toHaveBeenCalledWith("wizard:Next"); 39 | }); 40 | 41 | 42 | it('Should hide next button when reaching the last wizard step', function() { 43 | 44 | // Compile a piece of HTML containing the directive 45 | $compile(wrappedElement)(scope); 46 | 47 | $rootScope.$broadcast('wizard:IndexChanged', 2, 3); 48 | 49 | expect(wrappedElement.hasClass('ng-hide')).toBeTruthy(); 50 | }); 51 | 52 | it('Should display next button if not the end of wizard', function() { 53 | 54 | $compile(wrappedElement)(scope); 55 | 56 | $rootScope.$broadcast('wizard:IndexChanged', 1, 13); 57 | 58 | expect(wrappedElement.hasClass('ng-hide')).toBeFalsy(); 59 | }); 60 | 61 | }); 62 | 63 | describe('Test wizard previous button', function() { 64 | var element, wrappedElement; 65 | 66 | beforeEach(inject(function() { 67 | 68 | element = ""; 69 | wrappedElement = angular.element(element); 70 | })); 71 | 72 | it('Should broadcast next event on click', function() { 73 | // Compile a piece of HTML containing the directive 74 | var el = $compile(element)(scope); 75 | 76 | el.triggerHandler('click'); 77 | expect($rootScope.$broadcast).toHaveBeenCalledWith("wizard:Previous"); 78 | }); 79 | 80 | 81 | it('Should hide previous button on first step', function() { 82 | $compile(wrappedElement)(scope); 83 | 84 | $rootScope.$broadcast('wizard:IndexChanged', 0, 2); 85 | 86 | expect(wrappedElement.hasClass('ng-hide')).toBeTruthy(); 87 | }); 88 | 89 | it('Should display previous button on every other step', function() { 90 | $compile(wrappedElement)(scope); 91 | 92 | $rootScope.$broadcast('wizard:IndexChanged', 5, 7); 93 | 94 | expect(wrappedElement.hasClass('ng-hide')).toBeFalsy(); 95 | }); 96 | 97 | }); 98 | 99 | 100 | describe('Test wizard directive', function() { 101 | var element, controller, nextButtonElement, prevButtonElement; 102 | 103 | var injectDirectives = function injectDirectives(condition) { 104 | inject(function() { 105 | 106 | if (condition === undefined) { 107 | // no conditions 108 | element = angular.element("
Move next
\ 109 |
Move next
\ 110 |
" 111 | ); 112 | 113 | } 114 | else { 115 | scope.nextConditionFn = function() { 116 | return condition; 117 | }; 118 | 119 | scope.prevConditionFn = function() { 120 | return condition; 121 | }; 122 | 123 | element = angular.element("
Move next
\ 124 |
Move next
\ 125 |
" 126 | ); 127 | } 128 | prevButtonElement = angular.element(""); 129 | nextButtonElement = angular.element(""); 130 | 131 | $compile(element)(scope); 132 | $compile(prevButtonElement)(scope); 133 | $compile(nextButtonElement)(scope); 134 | scope.$digest(); 135 | controller = element.controller('ionWizard'); 136 | 137 | }); 138 | } 139 | 140 | describe("next-condition", function() { 141 | describe("for an undefined condition", function() { 142 | beforeEach(function() { 143 | injectDirectives(undefined); 144 | }); 145 | 146 | it("should pass", function() { 147 | var condition = controller.checkNextCondition(0); 148 | expect(condition).toBeTruthy(); 149 | }); 150 | 151 | it("should enable the next button", function() { 152 | var condition = controller.checkNextCondition(0); 153 | $rootScope.$broadcast('wizard:NextCondition', condition); 154 | var buttonDisabled = nextButtonElement.attr("disabled"); 155 | expect(buttonDisabled).toBeFalsy(); 156 | }); 157 | }); 158 | 159 | describe("for a true condition", function() { 160 | beforeEach(function() { 161 | injectDirectives(true); 162 | }); 163 | 164 | it("should pass", function() { 165 | var condition = controller.checkNextCondition(0); 166 | expect(condition).toBeTruthy(); 167 | }); 168 | 169 | it("should enable the next button", function() { 170 | var condition = controller.checkNextCondition(0); 171 | $rootScope.$broadcast('wizard:NextCondition', condition); 172 | var buttonDisabled = nextButtonElement.attr("disabled"); 173 | expect(buttonDisabled).toBeFalsy(); 174 | }); 175 | }); 176 | 177 | describe("for a false condition", function() { 178 | beforeEach(function() { 179 | injectDirectives(false); 180 | }); 181 | 182 | it("should fail", function() { 183 | var condition = controller.checkNextCondition(0); 184 | expect(condition).toBeFalsy(); 185 | }); 186 | 187 | it("should disable the next button", function() { 188 | var condition = controller.checkNextCondition(0); 189 | $rootScope.$broadcast('wizard:NextCondition', condition); 190 | 191 | var buttonDisabled = nextButtonElement.attr("disabled"); 192 | expect(buttonDisabled).toBeTruthy(); 193 | }); 194 | }); 195 | }); 196 | describe("prev-condition", function() { 197 | describe("for an undefined condition", function() { 198 | beforeEach(function() { 199 | injectDirectives(undefined); 200 | }); 201 | 202 | it("should pass", function() { 203 | var condition = controller.checkPreviousCondition(0); 204 | expect(condition).toBeTruthy(); 205 | }); 206 | 207 | it("should enable the previous button", function() { 208 | var condition = controller.checkPreviousCondition(0); 209 | $rootScope.$broadcast('wizard:PreviousCondition', condition); 210 | var buttonDisabled = prevButtonElement.attr("disabled"); 211 | expect(buttonDisabled).toBeFalsy(); 212 | }); 213 | }); 214 | 215 | describe("for a true condition", function() { 216 | beforeEach(function() { 217 | injectDirectives(true); 218 | }); 219 | 220 | it("should pass", function() { 221 | var condition = controller.checkPreviousCondition(0); 222 | expect(condition).toBeTruthy(); 223 | }); 224 | 225 | it("should enable the previous button", function() { 226 | var condition = controller.checkPreviousCondition(0); 227 | $rootScope.$broadcast('wizard:PreviousCondition', condition); 228 | var buttonDisabled = prevButtonElement.attr("disabled"); 229 | expect(buttonDisabled).toBeFalsy(); 230 | }); 231 | }); 232 | 233 | describe("for a false condition", function() { 234 | beforeEach(function() { 235 | injectDirectives(false); 236 | }); 237 | 238 | it("should fail", function() { 239 | var condition = controller.checkPreviousCondition(0); 240 | expect(condition).toBeFalsy(); 241 | }); 242 | 243 | it("should disable the previous button", function() { 244 | var condition = controller.checkPreviousCondition(0); 245 | $rootScope.$broadcast('wizard:PreviousCondition', condition); 246 | var buttonDisabled = prevButtonElement.attr("disabled"); 247 | expect(buttonDisabled).toBeTruthy(); 248 | }); 249 | }); 250 | 251 | }); 252 | }); 253 | 254 | describe('Test wizard start button', function() { 255 | var element, wrappedElement, $ionicSlideBoxDelegate; 256 | 257 | beforeEach(inject(function (_$ionicSlideBoxDelegate_) { 258 | 259 | $ionicSlideBoxDelegate = _$ionicSlideBoxDelegate_; 260 | 261 | element = ""; 262 | wrappedElement = angular.element(element); 263 | })); 264 | 265 | it('Should hide start button on any step but the last one', function () { 266 | 267 | $compile(wrappedElement)(scope); 268 | 269 | $rootScope.$broadcast('wizard:IndexChanged', 5, 13); 270 | 271 | expect(wrappedElement.hasClass('ng-hide')).toBeTruthy(); 272 | }); 273 | 274 | it('Should display start button on the last step', function () { 275 | 276 | $compile(wrappedElement)(scope); 277 | 278 | $rootScope.$broadcast('wizard:IndexChanged', 12, 13); 279 | 280 | expect(wrappedElement.hasClass('ng-hide')).toBeFalsy(); 281 | }); 282 | 283 | describe('Test start condition', function() { 284 | 285 | beforeEach(function() { 286 | scope.onStart_Pass_Condition = function() { 287 | return true; 288 | }; 289 | 290 | scope.onStart_Fail_Condition = function() { 291 | return false; 292 | }; 293 | 294 | scope.startFn = function() { 295 | console.log('Launch app'); 296 | }; 297 | 298 | spyOn(scope, 'startFn'); 299 | spyOn(scope, 'onStart_Fail_Condition').and.callThrough(); 300 | spyOn(scope, 'onStart_Pass_Condition').and.callThrough(); 301 | }); 302 | 303 | 304 | it('Should fail to launch app with falsy condition', function() { 305 | element = ""; 306 | wrappedElement = angular.element(element); 307 | 308 | $compile(wrappedElement)(scope); 309 | scope.$digest(); 310 | expect(wrappedElement.attr('disabled')).toBeTruthy(); 311 | }); 312 | 313 | it('Should launch app with passing condition', function() { 314 | element = ""; 315 | wrappedElement = angular.element(element); 316 | 317 | $compile(wrappedElement)(scope); 318 | scope.$digest(); 319 | expect(wrappedElement.attr('disabled')).toBeFalsy(); 320 | }); 321 | 322 | it('Should launch app if no condition defined', function() { 323 | element = ""; 324 | wrappedElement = angular.element(element); 325 | 326 | $compile(wrappedElement)(scope); 327 | scope.$digest(); 328 | expect(wrappedElement.attr('disabled')).toBeFalsy(); 329 | }); 330 | }); 331 | 332 | 333 | }); 334 | }); --------------------------------------------------------------------------------