├── .gitignore ├── .jshintrc ├── LICENSE ├── Makefile ├── README.md ├── bower.json ├── demo ├── content │ ├── app.js │ ├── az_subtle.png │ ├── backwards.html │ ├── home.html │ ├── iui.css │ ├── iui │ │ ├── back-img.png │ │ ├── backButton.png │ │ ├── backButtonBack.png │ │ ├── backButtonBrdr.png │ │ ├── backButtonSel.png │ │ ├── blueButton.png │ │ ├── cancel.png │ │ ├── default-theme.css │ │ ├── grayButton.png │ │ ├── listArrow.png │ │ ├── listArrowSel.png │ │ ├── listGroup.png │ │ ├── loading.gif │ │ ├── pinstripes.png │ │ ├── redButton.png │ │ ├── selection.png │ │ ├── thumb.png │ │ ├── title-img.png │ │ ├── toggle.png │ │ ├── toggleOn.png │ │ ├── toolButton.png │ │ ├── toolbar.png │ │ └── whiteButton.png │ ├── monkey.html │ ├── monkey.jpg │ ├── page1.html │ ├── page2.html │ └── popup.html └── index.html ├── mobile-nav.css ├── mobile-nav.js ├── mobile-nav.min.js └── src ├── _common.js ├── change.js ├── navigate.js ├── transition.css └── view.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | npm_debug.log 4 | *.rdb 5 | dist 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Andy Joslin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIST=dist 2 | VERSION= 3 | FILENAME=${DIST}/mobile-nav${VERSION} 4 | 5 | default: lint build demo 6 | 7 | lint: 8 | jshint src/*.js 9 | 10 | build: concat min 11 | rm -f ${FILENAME}.zip 12 | zip ${FILENAME}.zip ${DIST}/*.js ${DIST}/*.css 13 | 14 | concat: 15 | mkdir -p ${DIST} 16 | cat src/*.js > ${FILENAME}.js 17 | cat src/*.css > ${FILENAME}.css 18 | 19 | min: 20 | uglifyjs < ${FILENAME}.js > ${FILENAME}.min.js 21 | 22 | demo: 23 | cp -R demo/* ${DIST} 24 | 25 | release: build 26 | cp ${DIST}/*.js ${DIST}/*.css . 27 | 28 | .PHONY: lint build concat min demo 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-mobile-nav 2 | ================== 3 | 4 | [Demo](http://ajoslin.github.com/angular-mobile-nav) (Only will work in webkit browsers) 5 | 6 | [Download](http://ajoslin.github.com/angular-mobile-nav/mobile-nav.zip) (.zip) 7 | 8 | A simple navigation service and directive which will transition between partials. Intended for mobile applications on Android/iOS. 9 | 10 | Licensed with MIT License. 11 | 12 | State of this Project (as of July 2013) 13 | ------------------------------- 14 | 15 | * I will add no new features to this - only continue to maintain it. 16 | * I recommend a solution like [Ionic Framework](http://ionicframework.com) for a full-scale mobile application solution. 17 | * angular-mobile-nav is a good solution for a minimal mobile angularjs navigation library, with no frills or addons. 18 | * However, it could be done *much* simpler post angular-1.2.x, using ng-view, ng-animate, and some locationChangeSuccess listening to detect back. 19 | * If you want to fix bugs with this, please email me and ask and I may make you a collaborator. 20 | 21 | Usage 22 | ----- 23 | 24 | * **Requires AngularJS 1.1.4+** 25 | 26 | * Include `mobile-nav.js` and `mobile-nav.css` into your page 27 | * Declare `'mobile-navigate'` as a dependency for your angular app: `angular.module('myApp', ['ajoslin.mobile-navigate']);` 28 | * Setup your routes as normal with `$routeProvider`. 29 | * Use the `$navigate` service to do your transitions, instead of `` links. Use `$navigate.go('/path')`, and `$navigate.back()`. 30 | * You can erase history (eg when switching tabs) with `$navigate.eraseHistory()` 31 | * You can add transition classes of your own (check out the css file for how the current ones are done). There are three presets available: `slide`, `modal`, and `none`. Use them in the `go` function, eg `$navigate.go('/path', 'modal')`. 32 | * Use the `` element instead of the normal ``. 33 | 34 | Development 35 | ----------- 36 | 37 | * To use the Makefile, install jshint and uglifyjs with `npm install -g jshint uglify-js`. 38 | * If you are on windows and can't use a Makefile, there's nothing else at the moment. 39 | * To get the demo to work, you first have to run `make`, then open the demo at `dist/index.html`. 40 | * When pushing a new build, go to the gh-pages branch and move the contents dist folder up one level (`mv dist/* .`) 41 | 42 | Improving Phonegap Performance on Android 43 | ----------------------------------------- 44 | 45 | See [this wiki article](https://github.com/ajoslin/angular-mobile-nav/wiki/PhoneGap,-improving-performance) by **@ArtworkAD**. 46 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mobile-nav", 3 | "version": "1.0.0", 4 | "main": [ 5 | "./mobile-nav.js", 6 | "./mobile-nav.css" 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "components", 13 | "LICENSE", 14 | "Makefile", 15 | "README.md", 16 | "demo", 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /demo/content/app.js: -------------------------------------------------------------------------------- 1 | angular.module('myApp', ['ajoslin.mobile-navigate']) 2 | .config(function($routeProvider) { 3 | $routeProvider.when("/one", { 4 | templateUrl: "content/page1.html" 5 | }).when("/two", { 6 | templateUrl: "content/page2.html", 7 | transition: "modal" //this is overwritten by the go() in home.html 8 | }).when("/popup", { 9 | templateUrl: "content/popup.html", 10 | transition: "modal" 11 | }).when("/monkey", { 12 | templateUrl: "content/monkey.html" 13 | }).when("/backwards", { 14 | templateUrl: "content/backwards.html", 15 | reverse: true 16 | }).when("/", { 17 | templateUrl: "content/home.html" 18 | }).otherwise({ 19 | redirectTo: "/" 20 | }); 21 | }) 22 | .run(function($route, $http, $templateCache) { 23 | angular.forEach($route.routes, function(r) { 24 | if (r.templateUrl) { 25 | $http.get(r.templateUrl, {cache: $templateCache}); 26 | } 27 | }); 28 | }) 29 | .controller('MainCtrl', function($scope, $navigate) { 30 | $scope.$navigate = $navigate; 31 | }) 32 | .directive('ngTap', function() { 33 | var isTouchDevice = !!("ontouchstart" in window); 34 | return function(scope, elm, attrs) { 35 | if (isTouchDevice) { 36 | var tapping = false; 37 | elm.bind('touchstart', function() { tapping = true; }); 38 | elm.bind('touchmove', function() { tapping = false; }); 39 | elm.bind('touchend', function() { 40 | tapping && scope.$apply(attrs.ngTap); 41 | }); 42 | } else { 43 | elm.bind('click', function() { 44 | scope.$apply(attrs.ngTap); 45 | }); 46 | } 47 | }; 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /demo/content/az_subtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/az_subtle.png -------------------------------------------------------------------------------- /demo/content/backwards.html: -------------------------------------------------------------------------------- 1 |
2 |

Backwards

3 |
4 |
5 |

Woah! We just navigated to this page with a backwards transition, without actually doing a back in history.

6 |

That's pretty cool.

7 |
8 |
9 | -------------------------------------------------------------------------------- /demo/content/home.html: -------------------------------------------------------------------------------- 1 |
2 |

Home

3 |
4 |
5 | 6 |

{{angular}} lets you do transitions between partials for easy angular mobile app creation.

7 | 12 | 16 |
17 | -------------------------------------------------------------------------------- /demo/content/iui.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2007-12, iUI Project Members 3 | See LICENSE.txt for licensing terms 4 | Version @VERSION@ 5 | */ 6 | body { 7 | margin: 0; 8 | font-family: Helvetica; 9 | background: #FFFFFF; 10 | color: #000000; 11 | -webkit-user-select: none; 12 | -webkit-text-size-adjust: none; 13 | overflow: hidden; 14 | width: 100%; 15 | height: 100%; 16 | } 17 | 18 | .content { 19 | margin: 0; 20 | padding: 0; 21 | width: 100%; 22 | position: absolute; 23 | left: 0; 24 | top: 45px; 25 | bottom: 0; 26 | right: 0; 27 | overflow-y: scroll; 28 | -webkit-overflow-scrolling: touch; 29 | } 30 | 31 | li > a[selected], li > a:active { 32 | background-color: #194fdb !important; 33 | background-repeat: no-repeat, repeat-x !important; /* TG: !important */ 34 | background-position: right center, left top !important; /* TG: !important */ 35 | color: #FFFFFF !important; 36 | } 37 | 38 | li > a[selected="progress"] { 39 | } 40 | 41 | /************************************************************************************************/ 42 | 43 | .toolbar { 44 | box-sizing: border-box; 45 | -webkit-box-sizing: border-box; 46 | -moz-box-sizing: border-box; 47 | border-bottom: 1px solid #2d3642; 48 | border-top: 1px solid #6d84a2; 49 | padding: 0 10px 10px 10px; 50 | height: 45px; 51 | } 52 | 53 | .toolbar > h1 { 54 | position: absolute; 55 | overflow: hidden; 56 | left: 50%; 57 | margin: 1px 0 0 -75px; 58 | padding-top: 10px; 59 | height: 45px; 60 | font-size: 20px; 61 | width: 150px; 62 | font-weight: bold; 63 | text-shadow: rgba(0, 0, 0, 0.4) 0px -1px 0; 64 | text-align: center; 65 | text-overflow: ellipsis; 66 | white-space: nowrap; 67 | color: #FFFFFF; 68 | } 69 | 70 | body.landscape > .toolbar > h1 { 71 | margin-left: -125px; 72 | width: 250px; 73 | } 74 | 75 | .button { 76 | position: absolute; 77 | overflow: hidden; 78 | left: 6px; 79 | top: 8px; 80 | margin: 0; 81 | border-width: 0 5px; 82 | padding: 0 3px; 83 | height: 30px; 84 | line-height: 30px; 85 | font-family: inherit; 86 | font-size: 12px; 87 | font-weight: bold; 88 | color: #FFFFFF; 89 | text-shadow: rgba(0, 0, 0, 0.6) 0px -1px 0; 90 | text-overflow: ellipsis; 91 | text-decoration: none; 92 | white-space: nowrap; 93 | background: none; 94 | } 95 | 96 | .blueButton { 97 | border-width: 0 5px; 98 | } 99 | 100 | .leftButton { 101 | left: 6px; 102 | } 103 | 104 | #backButton { 105 | left: 6px; 106 | padding: 0; 107 | max-width: 55px; 108 | border-width: 0 8px 0 14px; 109 | margin: auto; 110 | } 111 | 112 | .whiteButton, 113 | .redButton, 114 | .grayButton { 115 | display: block; 116 | border-width: 0 12px; 117 | padding: 10px; 118 | text-align: center; 119 | font-size: 20px; 120 | font-weight: bold; 121 | text-decoration: inherit; 122 | color: inherit; 123 | } 124 | 125 | /* TG */ .grayButton[selected], .grayButton:active, 126 | .whiteButton { 127 | text-shadow: rgba(255, 255, 255, 0.7) 0 1px 0; 128 | /* TG */ background:transparent !important; 129 | } 130 | 131 | .redButton { 132 | /* TG */ background:transparent !important; 133 | color:#fff; 134 | text-shadow: #7a0001 0 -1px 0; 135 | } 136 | 137 | /* TG */ .whiteButton[selected], .whiteButton:active, 138 | .grayButton { 139 | color: #FFFFFF; 140 | /* TG */ background:transparent !important; 141 | } 142 | 143 | /************************************************************************************************/ 144 | 145 | ul > li { 146 | position: relative; 147 | margin: 0; 148 | border-bottom: 1px solid #E0E0E0; 149 | padding: 8px 0 8px 10px; 150 | font-size: 20px; 151 | font-weight: bold; 152 | list-style: none; 153 | } 154 | 155 | ul > li.group { 156 | position: relative; 157 | top: -1px; 158 | margin-bottom: -2px; 159 | border-top: 1px solid #7d7d7d; 160 | border-bottom: 1px solid #999999; 161 | padding: 1px 10px; 162 | font-size: 17px; 163 | font-weight: bold; 164 | text-shadow: rgba(0, 0, 0, 0.4) 0 1px 0; 165 | color: #FFFFFF; 166 | } 167 | 168 | ul > li.group:first-child { 169 | top: 0; 170 | border-top: none; 171 | } 172 | 173 | ul > li > a { 174 | display: block; 175 | margin: -8px 0 -8px -10px; 176 | padding: 8px 32px 8px 10px; 177 | text-decoration: none; 178 | color: inherit; 179 | } 180 | 181 | a[target="_replace"] { 182 | box-sizing: border-box; 183 | -webkit-box-sizing: border-box; 184 | -moz-box-sizing: border-box; 185 | padding-top: 25px; 186 | padding-bottom: 25px; 187 | font-size: 18px; 188 | color: cornflowerblue; 189 | background-color: #FFFFFF; 190 | background-image: none; 191 | } 192 | 193 | /************************************************************************************************/ 194 | 195 | .dialog { 196 | top: 0; 197 | width: 100%; 198 | min-height: 417px; 199 | z-index: 2; 200 | background: rgba(0, 0, 0, 0.8); 201 | padding: 0; 202 | text-align: right; 203 | } 204 | 205 | .dialog > fieldset { 206 | box-sizing: border-box; 207 | -webkit-box-sizing: border-box; 208 | -moz-box-sizing: border-box; 209 | width: 100%; 210 | margin: 0; 211 | border: none; 212 | border-top: 1px solid #6d84a2; 213 | padding: 10px 6px; 214 | } 215 | 216 | .dialog > fieldset > h1 { 217 | margin: 0 10px 0 10px; 218 | padding: 0; 219 | font-size: 20px; 220 | font-weight: bold; 221 | color: #FFFFFF; 222 | text-shadow: rgba(0, 0, 0, 0.4) 0px -1px 0; 223 | text-align: center; 224 | } 225 | 226 | .dialog > fieldset > label { 227 | position: absolute; 228 | margin: 16px 0 0 6px; 229 | font-size: 14px; 230 | color: #999999; 231 | width: 5em; 232 | text-align: left; 233 | } 234 | 235 | input:not([type|=submit]):not([type|=radio]):not([type|=checkbox]) { 236 | box-sizing: border-box; 237 | -webkit-box-sizing: border-box; 238 | -moz-box-sizing: border-box; 239 | width: 100%; 240 | margin: 8px 0 0 0; 241 | padding: 6px 6px 6px 5em; 242 | font-size: 14px; 243 | font-weight: normal; 244 | } 245 | 246 | /************************************************************************************************/ 247 | 248 | .panel { 249 | box-sizing: border-box; 250 | -webkit-box-sizing: border-box; 251 | -moz-box-sizing: border-box; 252 | padding: 10px; 253 | } 254 | 255 | .panel > ul, 256 | .panel > fieldset { 257 | position: relative; 258 | margin: 0 0 20px 0; 259 | padding: 0; 260 | background: #FFFFFF; 261 | -webkit-border-radius: 10px; 262 | -moz-border-radius: 10px; 263 | border: 1px solid #999999; 264 | font-size: 16px; 265 | } 266 | 267 | .panel > ul li, 268 | .row { 269 | position: relative; 270 | border-bottom: 1px solid #999999; 271 | -webkit-border-radius: 0; 272 | -moz-border-radius: 0; 273 | } 274 | 275 | .panel > ul li:last-child, 276 | fieldset > .row:last-child { 277 | border-bottom: none !important; 278 | } 279 | 280 | .row > input:not([type|=radio]):not([type|=checkbox]) { 281 | box-sizing: border-box; 282 | -webkit-box-sizing: border-box; 283 | -moz-box-sizing: border-box; 284 | margin: 0; 285 | border: none; 286 | padding: 12px 10px 0 110px; 287 | height: 42px; 288 | background: none; 289 | } 290 | 291 | .row > input[type|=radio], .row > input[type|=checkbox], 292 | .row > select { 293 | float: right; 294 | margin: 7px 7px 0 0; 295 | height: 25px; 296 | width: 25px; 297 | } 298 | 299 | .panel input[type|=submit] { 300 | width: 100% 301 | } 302 | 303 | .row > select { 304 | width: auto; 305 | margin: 9px 7px 0 0; 306 | } 307 | 308 | .row > label { 309 | position: absolute; 310 | top: 0; 311 | left: 0; 312 | margin: 0 0 0 14px; 313 | line-height: 42px; 314 | font-weight: bold; 315 | } 316 | 317 | .row > span { 318 | position: absolute; 319 | padding: 12px 10px 0 110px; 320 | margin: 0; 321 | } 322 | 323 | .row > .toggle { 324 | position: absolute; 325 | top: 6px; 326 | right: 6px; 327 | width: 100px; 328 | height: 28px; 329 | } 330 | 331 | .toggle { 332 | border: 1px solid #888888; 333 | -webkit-border-radius: 6px; 334 | -moz-border-radius: 6px; 335 | font-size: 19px; 336 | font-weight: bold; 337 | line-height: 30px; 338 | } 339 | 340 | .toggle[toggled="true"] { 341 | border: 1px solid #143fae; 342 | } 343 | 344 | .toggleOn { 345 | display: none; 346 | position: absolute; 347 | width: 60px; 348 | text-align: center; 349 | left: 0; 350 | top: 0; 351 | color: #FFFFFF; 352 | text-shadow: rgba(0, 0, 0, 0.4) 0px -1px 0; 353 | } 354 | 355 | .toggleOff { 356 | position: absolute; 357 | width: 60px; 358 | text-align: center; 359 | right: 0; 360 | top: 0; 361 | color: #666666; 362 | } 363 | 364 | .toggle[toggled="true"] > .toggleOn { 365 | display: block; 366 | } 367 | 368 | .toggle[toggled="true"] > .toggleOff { 369 | display: none; 370 | } 371 | 372 | .thumb { 373 | position: absolute; 374 | top: -1px; 375 | left: -1px; 376 | width: 40px; 377 | height: 28px; 378 | border: 1px solid #888888; 379 | -webkit-border-radius: 6px; 380 | -moz-border-radius: 6px; 381 | } 382 | 383 | .toggle[toggled="true"] > .thumb { 384 | left: auto; 385 | right: -1px; 386 | } 387 | 388 | .panel > h2 { 389 | margin: 0 0 8px 14px; 390 | font-size: inherit; 391 | font-weight: bold; 392 | color: #4d4d70; 393 | text-shadow: rgba(255, 255, 255, 0.75) 2px 2px 0; 394 | } 395 | 396 | /************************************************************************************************/ 397 | 398 | #preloader { 399 | display: none; 400 | } 401 | -------------------------------------------------------------------------------- /demo/content/iui/back-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/back-img.png -------------------------------------------------------------------------------- /demo/content/iui/backButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/backButton.png -------------------------------------------------------------------------------- /demo/content/iui/backButtonBack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/backButtonBack.png -------------------------------------------------------------------------------- /demo/content/iui/backButtonBrdr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/backButtonBrdr.png -------------------------------------------------------------------------------- /demo/content/iui/backButtonSel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/backButtonSel.png -------------------------------------------------------------------------------- /demo/content/iui/blueButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/blueButton.png -------------------------------------------------------------------------------- /demo/content/iui/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/cancel.png -------------------------------------------------------------------------------- /demo/content/iui/default-theme.css: -------------------------------------------------------------------------------- 1 | /* default-theme.css (c) 2007-2012 by iUI Project Members, see LICENSE.txt for license */ 2 | body { 3 | color: #000000; 4 | } 5 | 6 | li > a[selected], li > a:active { 7 | background-image: url(listArrowSel.png), url(selection.png) !important; 8 | } 9 | 10 | li > a[selected="progress"] { 11 | background-image: url(loading.gif), url(selection.png) !important; 12 | } 13 | 14 | /************************************************************************************************/ 15 | 16 | .toolbar { 17 | background: url(toolbar.png) #6d84a2 repeat-x; 18 | } 19 | 20 | .button { 21 | -webkit-border-image: url(toolButton.png) 0 5 0 5; 22 | -moz-border-image: url(toolButton.png) 0 5 0 5; 23 | } 24 | 25 | .blueButton { 26 | -webkit-border-image: url(blueButton.png) 0 5 0 5; 27 | -moz-border-image: url(blueButton.png) 0 5 0 5; 28 | } 29 | 30 | #backButton { 31 | -webkit-border-image: url(backButtonBrdr.png) 0 8 0 14; 32 | -moz-border-image: url(backButtonBrdr.png) 0 8 0 14; 33 | background: url(backButtonBack.png) repeat-x; 34 | } 35 | 36 | #backButton:active { 37 | -webkit-border-image: url(backButtonSel.png) 0 8 0 14; 38 | -moz-border-image: url(backButtonSel.png) 0 8 0 14; 39 | background: url(backButtonSel.png) repeat-x; 40 | } 41 | 42 | .whiteButton { 43 | -webkit-border-image: url(whiteButton.png) 0 12 0 12; 44 | -moz-border-image: url(whiteButton.png) 0 12 0 12; 45 | text-shadow: rgba(255, 255, 255, 0.7) 0 1px 0; 46 | } 47 | 48 | .redButton { 49 | -webkit-border-image: url(redButton.png) 0 12 0 12; 50 | -moz-border-image: url(redButton.png) 0 12 0 12; 51 | } 52 | 53 | .grayButton { 54 | -webkit-border-image: url(grayButton.png) 0 12 0 12; 55 | -moz-border-image: url(grayButton.png) 0 12 0 12; 56 | color: #FFFFFF; 57 | } 58 | 59 | /************************************************************************************************/ 60 | 61 | ul > li.group { 62 | opacity:0.7; 63 | background: url(listGroup.png) repeat-x; 64 | } 65 | 66 | ul > li > a { 67 | background: url(listArrow.png) no-repeat right center; 68 | } 69 | 70 | /************************************************************************************************/ 71 | 72 | .dialog > fieldset { 73 | background: url(toolbar.png) #7388a5 repeat-x; 74 | } 75 | 76 | /************************************************************************************************/ 77 | 78 | .panel { 79 | background: #c5ccd4 url(../az_subtle.png); 80 | } 81 | 82 | .toggle { 83 | border: 1px solid #888888; 84 | background: #FFFFFF url(toggle.png) repeat-x; 85 | } 86 | 87 | .toggle[toggled="true"] { 88 | background: #194fdb url(toggleOn.png) repeat-x; 89 | } 90 | 91 | .thumb { 92 | background: #ffffff url(thumb.png) repeat-x; 93 | } 94 | 95 | /************************************************************************************************/ 96 | #preloader { 97 | display: none; 98 | background-image: url(loading.gif), url(backButtonSel.png), url(selection.png), 99 | url(blueButton.png), url(listArrowSel.png), url(listArrowSel.png), url(listGroup.png); 100 | } 101 | 102 | .toolbar > h1.titleImg { 103 | background: url(title-img.png) no-repeat top center; 104 | color: rgba(0,0,0,0); 105 | } 106 | 107 | .backButtonImg { 108 | width: 50px; 109 | background: url(back-img.png) no-repeat center left, 110 | url(backButtonBack.png) repeat-x top left !important; 111 | color: rgba(0,0,0,0); 112 | } 113 | 114 | -------------------------------------------------------------------------------- /demo/content/iui/grayButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/grayButton.png -------------------------------------------------------------------------------- /demo/content/iui/listArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/listArrow.png -------------------------------------------------------------------------------- /demo/content/iui/listArrowSel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/listArrowSel.png -------------------------------------------------------------------------------- /demo/content/iui/listGroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/listGroup.png -------------------------------------------------------------------------------- /demo/content/iui/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/loading.gif -------------------------------------------------------------------------------- /demo/content/iui/pinstripes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/pinstripes.png -------------------------------------------------------------------------------- /demo/content/iui/redButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/redButton.png -------------------------------------------------------------------------------- /demo/content/iui/selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/selection.png -------------------------------------------------------------------------------- /demo/content/iui/thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/thumb.png -------------------------------------------------------------------------------- /demo/content/iui/title-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/title-img.png -------------------------------------------------------------------------------- /demo/content/iui/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/toggle.png -------------------------------------------------------------------------------- /demo/content/iui/toggleOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/toggleOn.png -------------------------------------------------------------------------------- /demo/content/iui/toolButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/toolButton.png -------------------------------------------------------------------------------- /demo/content/iui/toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/toolbar.png -------------------------------------------------------------------------------- /demo/content/iui/whiteButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/iui/whiteButton.png -------------------------------------------------------------------------------- /demo/content/monkey.html: -------------------------------------------------------------------------------- 1 |
2 |

Monkey

3 | Back 4 |
5 |
6 |
7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /demo/content/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajoslin/angular-mobile-nav/2c6242336ffbcafee5b013fbda1243e11a480b63/demo/content/monkey.jpg -------------------------------------------------------------------------------- /demo/content/page1.html: -------------------------------------------------------------------------------- 1 |
2 |

First Page

3 | Back 4 |
5 |
6 | Here's some content! 7 |
8 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /demo/content/page2.html: -------------------------------------------------------------------------------- 1 |
2 |

Page two

3 | Back 4 | See the Monkey! 5 |
6 |
7 |

8 | There's a monkey on the next page. Just a warning. 9 |

10 |

Here's some text for you. Try scrolling down, change the page, and go back. You'll see mobile-nav remembers your scroll position. It's a nice thing called a scrollable directive - put it on a scrollable element and that guy will persist his scroll even when he's destroyed.

11 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec venenatis leo sed libero adipiscing egestas. Mauris elit quam, dignissim non accumsan et, cursus ac nunc. In mattis nulla eu quam sollicitudin eu rutrum mi mollis. Pellentesque non ullamcorper lorem. Suspendisse euismod laoreet metus, in tempor erat blandit a. Proin vel eros porta leo bibendum consectetur. Aliquam erat volutpat. Aliquam vitae tincidunt odio. Duis condimentum pharetra hendrerit. Proin sit amet feugiat odio. Nulla diam ligula, semper vitae luctus non, dapibus eget augue. Nulla vitae eros leo.

12 | 13 |

Nam id tempor orci. Suspendisse potenti. Pellentesque facilisis justo non enim interdum tempus. Nullam a nulla id orci feugiat laoreet non nec magna. In ut placerat odio. Sed convallis aliquet bibendum. Integer luctus mollis metus, vel aliquet libero molestie quis.

14 | 15 |

Etiam nec urna mi. Integer hendrerit, ante eu semper congue, mauris felis molestie sem, nec tempor augue dui eget tortor. Etiam metus erat, tincidunt non faucibus at, elementum et massa. Ut fermentum condimentum vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Cras nisi lorem, elementum at iaculis sed, faucibus ut magna. Sed et nulla in nisi egestas iaculis commodo cursus odio. Sed aliquam quam non ipsum pulvinar et auctor turpis mattis. In sed lobortis massa. Fusce a ligula metus. Quisque vel nisl magna, sit amet placerat risus. Vestibulum lectus sem, molestie sit amet rutrum vel, eleifend et lacus. Integer blandit convallis ante, vitae bibendum massa porta adipiscing.

16 | 17 |

Vivamus ante sapien, tempus quis dignissim quis, sollicitudin nec ante. Integer congue lobortis nulla, sed congue tellus ultricies vitae. Etiam aliquam adipiscing cursus. Suspendisse ultricies vulputate sem. Quisque porta nunc quis libero dictum aliquet. Vivamus tristique ultrices euismod. Fusce sed sapien sit amet est luctus varius eget at massa. Ut pharetra nibh eget dui varius auctor porta lectus pretium. Integer semper nunc a neque blandit nec porta nisi fringilla. Suspendisse nibh urna, dignissim non fermentum nec, mattis vitae sem. Donec luctus, ligula sit amet venenatis lobortis, arcu arcu dapibus erat, ac eleifend felis libero vel odio. Vivamus facilisis purus vel eros vulputate auctor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

18 | 19 |

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur quis elit a orci vestibulum porta. Donec varius interdum tortor, molestie gravida massa egestas ac. Donec enim quam, vehicula vel vestibulum quis, bibendum quis lectus. Vestibulum blandit, elit a semper consectetur, dolor felis interdum urna, vel scelerisque diam magna at leo. Nulla malesuada ullamcorper sem, id placerat velit adipiscing eget. Vestibulum volutpat mollis tortor, et dapibus felis gravida nec. Proin bibendum mi quis tellus lacinia ultrices. Cras placerat purus ut metus laoreet nec molestie nisi ullamcorper. Morbi id urna et elit condimentum congue. Nulla lobortis velit eu ipsum mattis sit amet mattis odio malesuada. Praesent sem metus, porttitor et sodales quis, tempor at metus. Suspendisse potenti.

20 | 21 |

Phasellus euismod ornare viverra. Donec volutpat nunc nibh. Nulla suscipit egestas mi aliquet vehicula. Ut interdum mauris non elit accumsan congue. Suspendisse convallis quam sit amet augue sollicitudin iaculis. Donec venenatis vehicula sem, id fermentum odio mollis eu. Nullam at metus ante. Fusce fringilla vehicula aliquet. Integer non nunc a odio feugiat volutpat sed sit amet leo. Quisque ultrices cursus dui quis rutrum. Mauris sem neque, euismod quis aliquet sed, tincidunt in mi.

22 | 23 |

Morbi vel arcu sem, vel faucibus tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam a lacus eu nibh ultrices tincidunt nec eu augue. Sed erat urna, dictum laoreet convallis at, elementum id felis. Mauris id risus sapien. Pellentesque id sapien quam, eu egestas odio. Suspendisse potenti.

24 | 25 |

Aliquam vel tortor eu nulla malesuada condimentum. Donec ultricies nulla ut erat fringilla at mattis orci molestie. Morbi eget lacus velit. Fusce vel sem quis lectus commodo congue. Praesent lorem nisi, vestibulum eu dictum eget, commodo eget urna. Ut id urna ipsum. Donec adipiscing augue ac velit placerat dictum. Quisque mattis gravida dui. Morbi id eros quam, ut adipiscing diam. Sed aliquet nunc non diam elementum posuere. Suspendisse auctor eleifend neque, nec porta nulla dapibus iaculis. Vivamus dapibus blandit magna et vulputate. Aliquam erat volutpat. Mauris mauris nisl, condimentum sed eleifend sed, ornare quis justo.

26 | 27 |

Donec orci est, tincidunt ac tincidunt non, ultrices malesuada nibh. In sodales porttitor erat sed ornare. Curabitur ac placerat massa. Phasellus faucibus porta felis quis pulvinar. Ut adipiscing iaculis tempor. Proin justo felis, pharetra nec bibendum eget, vehicula in enim. Donec tincidunt rutrum tellus, a faucibus metus mollis semper. Suspendisse pellentesque augue non libero posuere vitae mattis dui rhoncus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc nec nisi neque. Nullam feugiat tristique tincidunt. Proin metus lacus, varius a laoreet quis, congue sollicitudin urna. Nam lobortis, elit quis hendrerit ultrices, lorem libero luctus risus, eu adipiscing nulla dolor quis elit. Vestibulum augue augue, auctor non varius vitae, feugiat non nibh.

28 | 29 |

Donec rutrum aliquet massa nec facilisis. Phasellus accumsan tellus cursus justo blandit semper. Nam imperdiet nisi id turpis sagittis fringilla. Etiam quis eros ligula, vel pulvinar mauris. Donec pharetra risus vitae felis aliquam sagittis vitae vitae odio. Cras blandit nulla id ipsum feugiat sed ultricies lectus fringilla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse ut tempor magna. Maecenas ornare mauris et neque porta vel commodo massa porta. Sed tempor bibendum quam id pulvinar. Nam tempor, mauris eu luctus pretium, magna massa pharetra libero, vel ultrices quam nibh in ipsum. Nulla nec nisi quis lacus iaculis dignissim.

30 |
31 | -------------------------------------------------------------------------------- /demo/content/popup.html: -------------------------------------------------------------------------------- 1 |
2 |

Popup

3 | Done 4 |
5 |
6 | Here's a popup. You'd put some modally content in here. Probably. 7 |
8 | And of course, everything is still angular-y 9 |
10 |
11 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Angular Mobile Nav 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mobile-nav.css: -------------------------------------------------------------------------------- 1 | 2 | /* Prefix all pages with mb- for no collisions */ 3 | 4 | /* Page class for all mobile pages */ 5 | .mb-page { 6 | position: absolute; 7 | width: 100%; 8 | height: 100%; 9 | -webkit-backface-visibility: none; 10 | } 11 | 12 | /* 13 | * Slide 14 | * slide pages horizontally 15 | */ 16 | @-webkit-keyframes slideInFromRight { 17 | 0% { -webkit-transform: translate3d(100%, 0, 0); } 18 | 100% { -webkit-transform: translate3d(0, 0, 0); } 19 | } 20 | @-webkit-keyframes slideOutToRight { 21 | 0% { -webkit-transform: translate3d(0, 0, 0); } 22 | 100% { -webkit-transform: translate3d(100%, 0, 0); } 23 | } 24 | @-webkit-keyframes slideInFromLeft { 25 | 0% { -webkit-transform: translate3d(-100%,0,0); } 26 | 100% { -webkit-transform: translate3d(0, 0, 0); } 27 | } 28 | @-webkit-keyframes slideOutToLeft { 29 | 0% { -webkit-transform: translate3d(0,0,0); } 30 | 100% { -webkit-transform: translate3d(-100%, 0, 0); } 31 | } 32 | @keyframes slideInFromRight { 33 | 0% { transform: translate3d(100%, 0, 0); } 34 | 100% { transform: translate3d(0, 0, 0); } 35 | } 36 | @keyframes slideOutToRight { 37 | 0% { transform: translate3d(0, 0, 0); } 38 | 100% { transform: translate3d(100%, 0, 0); } 39 | } 40 | @keyframes slideInFromLeft { 41 | 0% { transform: translate3d(-100%,0,0); } 42 | 100% { transform: translate3d(0, 0, 0); } 43 | } 44 | @keyframes slideOutToLeft { 45 | 0% { transform: translate3d(0,0,0); } 46 | 100% { transform: translate3d(-100%, 0, 0); } 47 | } 48 | 49 | .mb-slide { 50 | -webkit-animation-duration: 0.3s; 51 | -webkit-animation-timing-function: ease; 52 | animation-duration: 0.3s; 53 | animation-timing-function: ease; 54 | } 55 | .mb-slide.mb-in { 56 | -webkit-animation-name: slideInFromRight; 57 | animation-name: slideInFromRight; 58 | } 59 | .mb-slide.mb-in.mb-reverse { 60 | -webkit-animation-name: slideOutToRight; 61 | -webkit-transform: translate3d(100%, 0, 0); 62 | animation-name: slideOutToRight; 63 | transform: translate3d(100%, 0, 0); 64 | } 65 | .mb-slide.mb-out { 66 | -webkit-animation-name: slideOutToLeft; 67 | -webkit-transform: translate3d(-100%, 0, 0); 68 | animation-name: slideOutToLeft; 69 | transform: translate3d(-100%, 0, 0); 70 | } 71 | .mb-slide.mb-out.mb-reverse { 72 | -webkit-animation-name: slideInFromLeft; 73 | animation-name: slideInFromLeft; 74 | } 75 | 76 | /* 77 | * Slide up and down - like modal, but exiting page is animated too. 78 | */ 79 | 80 | @-webkit-keyframes slideInFromTop { 81 | 0% { -webkit-transform: translate3d(0, 100%, 0); } 82 | 100% { -webkit-transform: translate3d(0, 0, 0); } 83 | } 84 | @-webkit-keyframes slideOutToTop { 85 | 0% { -webkit-transform: translate3d(0, 0, 0); } 86 | 100% { -webkit-transform: translate3d(0, 100%, 0); } 87 | } 88 | @-webkit-keyframes slideInFromBottom { 89 | 0% { -webkit-transform: translate3d(0,-100%,0); } 90 | 100% { -webkit-transform: translate3d(0, 0, 0); } 91 | } 92 | @-webkit-keyframes slideOutToBottom { 93 | 0% { -webkit-transform: translate3d(0,0,0); } 94 | 100% { -webkit-transform: translate3d(0, -100%, 0); } 95 | } 96 | @keyframes slideInFromTop { 97 | 0% { transform: translate3d(0, 100%, 0); } 98 | 100% { transform: translate3d(0, 0, 0); } 99 | } 100 | @keyframes slideOutToTop { 101 | 0% { transform: translate3d(0, 0, 0); } 102 | 100% { transform: translate3d(0, 100%, 0); } 103 | } 104 | @keyframes slideInFromBottom { 105 | 0% { transform: translate3d(0,-100%,0); } 106 | 100% { transform: translate3d(0, 0, 0); } 107 | } 108 | @keyframes slideOutToBottom { 109 | 0% { transform: translate3d(0,0,0); } 110 | 100% { transform: translate3d(0, -100%, 0); } 111 | } 112 | 113 | .mb-slide-up { 114 | -webkit-animation-duration: 0.3s; 115 | -webkit-animation-timing-function: ease; 116 | animation-duration: 0.3s; 117 | animation-timing-function: ease; 118 | } 119 | .mb-slide-up.mb-in { 120 | -webkit-animation-name: slideInFromTop; 121 | animation-name: slideInFromTop; 122 | } 123 | .mb-slide-up.mb-in.reverse { 124 | -webkit-animation-name: slideOutToTop; 125 | -webkit-transform: translate3d(0,-100%,0); 126 | animation-name: slideOutToTop; 127 | transform: translate3d(0,-100%,0); 128 | } 129 | .mb-slide-up.mb-out { 130 | -webkit-animation-name: slideOutToBottom; 131 | -webkit-transform: translate3d(0,100%,0); 132 | animation-name: slideOutToBottom; 133 | transform: translate3d(0,100%,0); 134 | } 135 | .mb-slide-up.mb-out.reverse { 136 | -webkit-animation-name: slideInFromBottom; 137 | animation-name: slideInFromBottom; 138 | } 139 | 140 | 141 | /* 142 | * Modal! 143 | * slide a page in from the bottom onto a page 144 | */ 145 | @-webkit-keyframes modalUp { 146 | 0% { -webkit-transform: translate3d(0, 100%, 0); } 147 | 100% { -webkit-transform: translate3d(0, 0, 0); } 148 | } 149 | @-webkit-keyframes modalDown { 150 | 0% { -webkit-transform: translate3d(0, 0, 0); } 151 | 100% { -webkit-transform: translate3d(0, 100%, 0); } 152 | } 153 | @keyframes modalUp { 154 | 0% { transform: translate3d(0, 100%, 0); } 155 | 100% { transform: translate3d(0, 0, 0); } 156 | } 157 | @keyframes modalDown { 158 | 0% { transform: translate3d(0, 0, 0); } 159 | 100% { transform: translate3d(0, 100%, 0); } 160 | } 161 | .mb-modal { 162 | z-index: 10; 163 | -webkit-animation-duration: 0.4s; 164 | animation-duration: 0.4s; 165 | } 166 | .mb-modal.mb-in, 167 | .mb-modal.mb-out.mb-reverse { 168 | -webkit-animation-name: modalUp; 169 | animation-name: modalUp; 170 | } 171 | .mb-modal.mb-in.mb-reverse, 172 | .mb-modal.mb-out { 173 | z-index: 9; /* Lower than modal-in */ 174 | -webkit-animation-name: modalDown; 175 | -webkit-transform: translate3d(0, 100%, 0); 176 | animation-name: modalDown; 177 | transform: translate3d(0, 100%, 0); 178 | } 179 | 180 | -------------------------------------------------------------------------------- /mobile-nav.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-mobile-nav by Andy Joslin 3 | * http://github.com/ajoslin/angular-mobile-nav 4 | * @license MIT License http://goo.gl/Z8Nlo 5 | */ 6 | 7 | angular.module('ajoslin.mobile-navigate', []) 8 | .run(['$navigate', '$rootScope', function($navigate, $rootScope) { 9 | //Android back button functionality for phonegap 10 | document.addEventListener("deviceready", function() { 11 | document.addEventListener("backbutton", function() { 12 | $rootScope.$apply(function() { 13 | var backSuccess = $navigate.back(); 14 | if (!backSuccess) { 15 | navigator.app.exitApp(); 16 | } 17 | }); 18 | }); 19 | }); 20 | }]); 21 | /* 22 | * $change 23 | * Service to transition between two elements 24 | */ 25 | angular.module('ajoslin.mobile-navigate') 26 | 27 | .provider('$change', function() { 28 | var transitionPresets = { //[nextClass, prevClass] 29 | //Modal: new page pops up, old page sits there until new page is over it 30 | 'modal': ['modal', ''], 31 | 'none': ['', ''] 32 | }; 33 | var defaultOptions = { 34 | 'prefix': 'mb-' 35 | }; 36 | var IN_CLASS = "in"; 37 | var OUT_CLASS = "out"; 38 | var REVERSE_CLASS = "reverse"; 39 | var DONE_CLASS = "done"; 40 | var ANIMATION_END = "animationName" in document.documentElement.style ? "animationend" : "webkitAnimationEnd"; 41 | 42 | this.setTransitionPreset = function(transitionName, inClass, outClass) { 43 | inClass = inClass || ''; 44 | outClass = outClass || inClass; //Default to outClass same as inClass 45 | transitionPresets[transitionName] = [inClass, outClass]; 46 | }; 47 | this.options = function(opts) { 48 | defaultOptions = angular.extend(defaultOptions, opts || {}); 49 | }; 50 | 51 | this.$get = ['$q', '$rootScope', function($q, $rootScope) { 52 | 53 | return function change(next, prev, transType, reverse, options) { 54 | options = angular.extend(options || {}, defaultOptions); 55 | var deferred = $q.defer(), 56 | nextTransClass, prevTransClass; 57 | 58 | //buildClassString 59 | //Transforms array of classes into prefixed class string 60 | //(better for performance than multiple .addClass() 61 | //@param classes: Array{string} 62 | //@return string classNames 63 | function buildClassString(classes) { 64 | return classes.reduce(function(accumulator, cls) { 65 | return accumulator + (cls ? (' ' + options.prefix + cls) : ''); 66 | }, ''); 67 | } 68 | 69 | //Convert a preset (eg 'modal') to its array of preset classes if it exists 70 | //else, just convert eg 'slide' to ['slide', 'slide'], so both elements get it 71 | //The array layout is [nextinationClass, prevClass] 72 | var transition = transitionPresets[transType] ? 73 | transitionPresets[transType] : 74 | [transType, transType]; 75 | 76 | //Hack for white flash: z-index stops flash, offsetWidth thing forces z-index to apply 77 | next.css('z-index','-100'); 78 | next[0].offsetWidth += 0; 79 | 80 | var nextClasses = buildClassString([ 81 | reverse ? OUT_CLASS : IN_CLASS, 82 | (nextTransClass = transition[reverse ? 1 : 0]), 83 | reverse && REVERSE_CLASS || '' 84 | ]); 85 | next.addClass(nextClasses); 86 | 87 | var prevClasses; 88 | if (prev) { 89 | prevClasses = buildClassString([ 90 | reverse ? IN_CLASS : OUT_CLASS, 91 | (prevTransClass = transition[reverse ? 0 : 1]), 92 | reverse && REVERSE_CLASS || '' 93 | ]); 94 | prev.addClass(prevClasses); 95 | } 96 | 97 | next.css('z-index', ''); 98 | next[0].offsetWidth += 0; 99 | 100 | function done() { 101 | $rootScope.$apply(function() { 102 | deferred.resolve(); 103 | }); 104 | } 105 | 106 | //Find which element (sometimes none) to bind for ending 107 | var boundElement; 108 | if (nextTransClass && nextTransClass.length) { 109 | (boundElement = next).bind(ANIMATION_END, done); 110 | } else if (prev && prevTransClass && prevTransClass.length) { 111 | (boundElement = prev).bind(ANIMATION_END, done); 112 | } else { 113 | deferred.resolve(); 114 | } 115 | 116 | deferred.promise.then(function() { 117 | boundElement && boundElement.unbind(ANIMATION_END, done); 118 | next.removeClass(nextClasses); 119 | prev && prev.removeClass(prevClasses); 120 | }); 121 | 122 | //Let the user of change 'cancel' to finish transition early if they wish 123 | deferred.promise.cancel = function() { 124 | deferred.resolve(); 125 | }; 126 | return deferred.promise; 127 | }; 128 | }]; 129 | }); 130 | angular.module('ajoslin.mobile-navigate') 131 | 132 | .provider('$navigate', function() { 133 | this.$get = ['$rootScope', '$location', '$route', function($rootScope, $location, $route) { 134 | var nav = {}, 135 | navHistory = []; //we keep our own version of history and ignore window.history 136 | 137 | function Page(path, transition, isReverse) { 138 | var _path = path, 139 | _transition = transition || 'slide', 140 | _isReverse = isReverse, 141 | _onceTransition; 142 | 143 | this.transition = function() { 144 | var trans; 145 | if (_onceTransition) { 146 | trans = _onceTransition; 147 | _onceTransition = null; 148 | } else { 149 | trans = _transition; 150 | } 151 | return trans; 152 | }; 153 | this.path = function() { return _path; }; 154 | this.reverse = function() { return _isReverse; }; 155 | 156 | //For setting a transition on a page - but only one time 157 | //Eg say on startup, we want to transition in with 'none', 158 | //but want to be 'slide' after that 159 | this.transitionOnce = function(trans) { 160 | _onceTransition = trans; 161 | }; 162 | } 163 | 164 | function navigate(destination, source, isBack) { 165 | $rootScope.$broadcast('$pageTransitionStart', destination, source, isBack); 166 | nav.current = nav.next; 167 | } 168 | 169 | /* 170 | * Will listen for a route change success and call the selected callback 171 | * Only one listen is ever active, so if you press for example 172 | * /link1 then press back before /link1 is done, it will go listen for the back 173 | */ 174 | nav.onRouteSuccess = null; 175 | //Add a default onroutesuccess for the very first page 176 | function defaultRouteSuccess($event, next, last) { 177 | nav.current && navHistory.push(nav.current); 178 | nav.next = new Page($location.path()); 179 | nav.next.transitionOnce('none'); 180 | navigate(nav.next); 181 | nav.onRouteSuccess = null; 182 | } 183 | $rootScope.$on('$routeChangeSuccess', function($event, next, last) { 184 | // Only navigate if it's a valid route and it's not gonna just redirect immediately 185 | if (!next.$$route || !next.$$route.redirectTo) { 186 | (nav.onRouteSuccess || defaultRouteSuccess)($event, next, last); 187 | } 188 | }); 189 | 190 | /* 191 | * go -transitions to new page 192 | * @param path - new path 193 | * @param {optional} String transition 194 | * @param {optional} boolean isReverse, default false 195 | */ 196 | nav.go = function go(path, transition, isReverse) { 197 | if (typeof transition == 'boolean') { 198 | isReverse = transition; 199 | transition = null; 200 | } 201 | $location.path(path); 202 | //Wait for successful route change before actually doing stuff 203 | nav.onRouteSuccess = function($event, next, last) { 204 | nav.current && navHistory.push(nav.current); 205 | nav.next = new Page(path, transition || (next.$$route && next.$$route.transition), isReverse); 206 | navigate(nav.next, nav.current, false); 207 | }; 208 | }; 209 | //Sometimes you want to erase history 210 | nav.eraseHistory = function() { 211 | navHistory.length = 0; 212 | }; 213 | nav.back = function() { 214 | if (navHistory.length > 0) { 215 | var previous = navHistory[navHistory.length-1]; 216 | $location.path(previous.path()); 217 | nav.onRouteSuccess = function() { 218 | navHistory.pop(); 219 | nav.next = previous; 220 | navigate(nav.next, nav.current, true); 221 | }; 222 | return true; 223 | } 224 | return false; 225 | }; 226 | 227 | return nav; 228 | }]; 229 | }); 230 | angular.module('ajoslin.mobile-navigate') 231 | .directive('mobileView', ['$rootScope', '$compile', '$controller', '$route', '$change', '$q', 232 | function($rootScope, $compile, $controller, $route, $change, $q) { 233 | 234 | function link(scope, viewElement, attrs) { 235 | //Insert page into dom 236 | function insertPage(page) { 237 | var current = $route.current, 238 | locals = current && current.locals; 239 | 240 | page.element = angular.element(document.createElement("div")); 241 | page.element.html(locals.$template); 242 | page.element.addClass('mb-page'); //always has to have page class 243 | page.scope = scope.$new(); 244 | if (current.controller) { 245 | locals.$scope = page.scope; 246 | page.controller = $controller(current.controller, locals); 247 | page.element.contents().data('$ngControllerController', page.controller); 248 | } 249 | $compile(page.element.contents())(page.scope); 250 | if (locals && locals.$template) { 251 | // only append page element if a template exists 252 | viewElement.append(page.element); 253 | } 254 | page.scope.$emit('$viewContentLoaded'); 255 | page.scope.$eval(attrs.onLoad); 256 | return page; 257 | } 258 | 259 | 260 | var currentTrans; 261 | scope.$on('$pageTransitionStart', function ($event, dest, source, reverse) { 262 | function changePage() { 263 | var current = $route.current && $route.current.$$route || {}; 264 | var transition = reverse ? source.transition() : dest.transition(); 265 | 266 | insertPage(dest); 267 | 268 | //If the page is marked as reverse, reverse the direction 269 | if (dest.reverse() || current.reverse) { 270 | reverse = !reverse; 271 | } 272 | 273 | function doTransition() { 274 | 275 | var promise = $change(dest.element, (source ? source.element : null), 276 | transition, reverse); 277 | 278 | promise.then(function() { 279 | if (source) { 280 | $rootScope.$broadcast('$pageTransitionSuccess', dest, source); 281 | source.scope.$destroy(); 282 | source.element.remove(); 283 | source = undefined; 284 | } 285 | }); 286 | 287 | return promise; 288 | } 289 | 290 | //Set next element to display: none, then wait until transition is 291 | //ready, then show it again. 292 | dest.element.css('display', 'none'); 293 | 294 | //Allow a deferTransition expression, which is allowed to return a promise. 295 | //The next page will be inserted, but not transitioned in until the promise 296 | //is fulfilled. 297 | var deferTransitionPromise = scope.$eval(attrs.deferTransition) || $q.when(); 298 | deferTransitionPromise.cancel = function() { 299 | cancelled = true; 300 | //Undo display none from waiting for transition 301 | dest.element.css('display', ''); 302 | }; 303 | 304 | var cancelled = false; 305 | deferTransitionPromise.then(function() { 306 | if (!cancelled) { 307 | //Undo display none from waiting for transition 308 | dest.element.css('display', ''); 309 | return doTransition(); 310 | } 311 | }); 312 | 313 | return deferTransitionPromise; 314 | } 315 | currentTrans && currentTrans.cancel(); 316 | currentTrans = changePage(dest, source, reverse); 317 | }); 318 | } 319 | return { 320 | restrict: 'EA', 321 | link: link 322 | }; 323 | }]) 324 | 325 | .directive('scrollable', ['$route', function($route) { 326 | var scrollCache = {}; 327 | return { 328 | restrict: 'EA', 329 | link: function(scope, elm, attrs) { 330 | var route = $route.current ? $route.current.$$route : {}; 331 | var template = route.templateUrl || route.template; 332 | var rawElm = elm[0]; 333 | 334 | //On scope creation, see if we remembered any scroll for this templateUrl 335 | //If we did, set it 336 | if (template) { 337 | //Set oldScroll after a timeout so the page has time to fully load 338 | setTimeout(function() { 339 | var oldScroll = scrollCache[template]; 340 | if (oldScroll) { 341 | rawElm.scrollTop = oldScroll; 342 | } 343 | }); 344 | 345 | scope.$on('$destroy', function() { 346 | scrollCache[template] = rawElm.scrollTop; 347 | }); 348 | } 349 | } 350 | }; 351 | }]); 352 | -------------------------------------------------------------------------------- /mobile-nav.min.js: -------------------------------------------------------------------------------- 1 | angular.module("ajoslin.mobile-navigate",[]).run(["$navigate","$rootScope",function($navigate,$rootScope){document.addEventListener("deviceready",function(){document.addEventListener("backbutton",function(){$rootScope.$apply(function(){var backSuccess=$navigate.back();if(!backSuccess){navigator.app.exitApp()}})})})}]);angular.module("ajoslin.mobile-navigate").provider("$change",function(){var transitionPresets={modal:["modal",""],none:["",""]};var defaultOptions={prefix:"mb-"};var IN_CLASS="in";var OUT_CLASS="out";var REVERSE_CLASS="reverse";var DONE_CLASS="done";var ANIMATION_END="animationName"in document.documentElement.style?"animationend":"webkitAnimationEnd";this.setTransitionPreset=function(transitionName,inClass,outClass){inClass=inClass||"";outClass=outClass||inClass;transitionPresets[transitionName]=[inClass,outClass]};this.options=function(opts){defaultOptions=angular.extend(defaultOptions,opts||{})};this.$get=["$q","$rootScope",function($q,$rootScope){return function change(next,prev,transType,reverse,options){options=angular.extend(options||{},defaultOptions);var deferred=$q.defer(),nextTransClass,prevTransClass;function buildClassString(classes){return classes.reduce(function(accumulator,cls){return accumulator+(cls?" "+options.prefix+cls:"")},"")}var transition=transitionPresets[transType]?transitionPresets[transType]:[transType,transType];next.css("z-index","-100");next[0].offsetWidth+=0;var nextClasses=buildClassString([reverse?OUT_CLASS:IN_CLASS,nextTransClass=transition[reverse?1:0],reverse&&REVERSE_CLASS||""]);next.addClass(nextClasses);var prevClasses;if(prev){prevClasses=buildClassString([reverse?IN_CLASS:OUT_CLASS,prevTransClass=transition[reverse?0:1],reverse&&REVERSE_CLASS||""]);prev.addClass(prevClasses)}next.css("z-index","");next[0].offsetWidth+=0;function done(){$rootScope.$apply(function(){deferred.resolve()})}var boundElement;if(nextTransClass&&nextTransClass.length){(boundElement=next).bind(ANIMATION_END,done)}else if(prev&&prevTransClass&&prevTransClass.length){(boundElement=prev).bind(ANIMATION_END,done)}else{deferred.resolve()}deferred.promise.then(function(){boundElement&&boundElement.unbind(ANIMATION_END,done);next.removeClass(nextClasses);prev&&prev.removeClass(prevClasses)});deferred.promise.cancel=function(){deferred.resolve()};return deferred.promise}}]});angular.module("ajoslin.mobile-navigate").provider("$navigate",function(){this.$get=["$rootScope","$location","$route",function($rootScope,$location,$route){var nav={},navHistory=[];function Page(path,transition,isReverse){var _path=path,_transition=transition||"slide",_isReverse=isReverse,_onceTransition;this.transition=function(){var trans;if(_onceTransition){trans=_onceTransition;_onceTransition=null}else{trans=_transition}return trans};this.path=function(){return _path};this.reverse=function(){return _isReverse};this.transitionOnce=function(trans){_onceTransition=trans}}function navigate(destination,source,isBack){$rootScope.$broadcast("$pageTransitionStart",destination,source,isBack);nav.current=nav.next}nav.onRouteSuccess=null;function defaultRouteSuccess($event,next,last){nav.current&&navHistory.push(nav.current);nav.next=new Page($location.path());nav.next.transitionOnce("none");navigate(nav.next);nav.onRouteSuccess=null}$rootScope.$on("$routeChangeSuccess",function($event,next,last){if(!next.$$route||!next.$$route.redirectTo){(nav.onRouteSuccess||defaultRouteSuccess)($event,next,last)}});nav.go=function go(path,transition,isReverse){if(typeof transition=="boolean"){isReverse=transition;transition=null}$location.path(path);nav.onRouteSuccess=function($event,next,last){nav.current&&navHistory.push(nav.current);nav.next=new Page(path,transition||next.$$route&&next.$$route.transition,isReverse);navigate(nav.next,nav.current,false)}};nav.eraseHistory=function(){navHistory.length=0};nav.back=function(){if(navHistory.length>0){var previous=navHistory[navHistory.length-1];$location.path(previous.path());nav.onRouteSuccess=function(){navHistory.pop();nav.next=previous;navigate(nav.next,nav.current,true)};return true}return false};return nav}]});angular.module("ajoslin.mobile-navigate").directive("mobileView",["$rootScope","$compile","$controller","$route","$change","$q",function($rootScope,$compile,$controller,$route,$change,$q){function link(scope,viewElement,attrs){function insertPage(page){var current=$route.current,locals=current&¤t.locals;page.element=angular.element(document.createElement("div"));page.element.html(locals.$template);page.element.addClass("mb-page");page.scope=scope.$new();if(current.controller){locals.$scope=page.scope;page.controller=$controller(current.controller,locals);page.element.contents().data("$ngControllerController",page.controller)}$compile(page.element.contents())(page.scope);if(locals&&locals.$template){viewElement.append(page.element)}page.scope.$emit("$viewContentLoaded");page.scope.$eval(attrs.onLoad);return page}var currentTrans;scope.$on("$pageTransitionStart",function($event,dest,source,reverse){function changePage(){var current=$route.current&&$route.current.$$route||{};var transition=reverse?source.transition():dest.transition();insertPage(dest);if(dest.reverse()||current.reverse){reverse=!reverse}function doTransition(){var promise=$change(dest.element,source?source.element:null,transition,reverse);promise.then(function(){if(source){$rootScope.$broadcast("$pageTransitionSuccess",dest,source);source.scope.$destroy();source.element.remove();source=undefined}});return promise}dest.element.css("display","none");var deferTransitionPromise=scope.$eval(attrs.deferTransition)||$q.when();deferTransitionPromise.cancel=function(){cancelled=true;dest.element.css("display","")};var cancelled=false;deferTransitionPromise.then(function(){if(!cancelled){dest.element.css("display","");return doTransition()}});return deferTransitionPromise}currentTrans&¤tTrans.cancel();currentTrans=changePage(dest,source,reverse)})}return{restrict:"EA",link:link}}]).directive("scrollable",["$route",function($route){var scrollCache={};return{restrict:"EA",link:function(scope,elm,attrs){var route=$route.current?$route.current.$$route:{};var template=route.templateUrl||route.template;var rawElm=elm[0];if(template){setTimeout(function(){var oldScroll=scrollCache[template];if(oldScroll){rawElm.scrollTop=oldScroll}});scope.$on("$destroy",function(){scrollCache[template]=rawElm.scrollTop})}}}}]); -------------------------------------------------------------------------------- /src/_common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-mobile-nav by Andy Joslin 3 | * http://github.com/ajoslin/angular-mobile-nav 4 | * @license MIT License http://goo.gl/Z8Nlo 5 | */ 6 | 7 | angular.module('ajoslin.mobile-navigate', []) 8 | .run(['$navigate', '$rootScope', function($navigate, $rootScope) { 9 | //Android back button functionality for phonegap 10 | document.addEventListener("deviceready", function() { 11 | document.addEventListener("backbutton", function() { 12 | $rootScope.$apply(function() { 13 | var backSuccess = $navigate.back(); 14 | if (!backSuccess) { 15 | navigator.app.exitApp(); 16 | } 17 | }); 18 | }); 19 | }); 20 | }]); 21 | -------------------------------------------------------------------------------- /src/change.js: -------------------------------------------------------------------------------- 1 | /* 2 | * $change 3 | * Service to transition between two elements 4 | */ 5 | angular.module('ajoslin.mobile-navigate') 6 | 7 | .provider('$change', function() { 8 | var transitionPresets = { //[nextClass, prevClass] 9 | //Modal: new page pops up, old page sits there until new page is over it 10 | 'modal': ['modal', ''], 11 | 'none': ['', ''] 12 | }; 13 | var defaultOptions = { 14 | 'prefix': 'mb-' 15 | }; 16 | var IN_CLASS = "in"; 17 | var OUT_CLASS = "out"; 18 | var REVERSE_CLASS = "reverse"; 19 | var DONE_CLASS = "done"; 20 | 21 | this.setTransitionPreset = function(transitionName, inClass, outClass) { 22 | inClass = inClass || ''; 23 | outClass = outClass || inClass; //Default to outClass same as inClass 24 | transitionPresets[transitionName] = [inClass, outClass]; 25 | }; 26 | this.options = function(opts) { 27 | defaultOptions = angular.extend(defaultOptions, opts || {}); 28 | }; 29 | 30 | this.$get = ['$q', '$rootScope', '$sniffer', function($q, $rootScope, $sniffer) { 31 | //TODO remove this fix when angular-1.2 comes out 32 | //This fixes a known bug with android $sniffer in angular-1.1.x not finding prefix properly 33 | if (!$sniffer.vendorPrefix) { 34 | if (angular.isString( $document[0].body.style.webkitTransition )) { 35 | $sniffer.vendorPrefix = 'webkit'; 36 | } 37 | } 38 | var ANIMATION_END = $sniffer.vendorPrefix ? 39 | $sniffer.vendorPrefix.toLowerCase() + 'AnimationEnd' : 40 | 'animationend'; 41 | 42 | return function change(next, prev, transType, reverse, options) { 43 | options = angular.extend(options || {}, defaultOptions); 44 | var deferred = $q.defer(), 45 | nextTransClass, prevTransClass; 46 | 47 | //buildClassString 48 | //Transforms array of classes into prefixed class string 49 | //(better for performance than multiple .addClass() 50 | //@param classes: Array{string} 51 | //@return string classNames 52 | function buildClassString(classes) { 53 | return classes.reduce(function(accumulator, cls) { 54 | return accumulator + (cls ? (' ' + options.prefix + cls) : ''); 55 | }, ''); 56 | } 57 | 58 | //Convert a preset (eg 'modal') to its array of preset classes if it exists 59 | //else, just convert eg 'slide' to ['slide', 'slide'], so both elements get it 60 | //The array layout is [nextinationClass, prevClass] 61 | var transition = transitionPresets[transType] ? 62 | transitionPresets[transType] : 63 | [transType, transType]; 64 | 65 | //Hack for white flash: z-index stops flash, offsetWidth thing forces z-index to apply 66 | next.css('z-index','-100'); 67 | next[0].offsetWidth += 0; 68 | 69 | var nextClasses = buildClassString([ 70 | reverse ? OUT_CLASS : IN_CLASS, 71 | (nextTransClass = transition[reverse ? 1 : 0]), 72 | reverse && REVERSE_CLASS || '' 73 | ]); 74 | next.addClass(nextClasses); 75 | 76 | var prevClasses; 77 | if (prev) { 78 | prevClasses = buildClassString([ 79 | reverse ? IN_CLASS : OUT_CLASS, 80 | (prevTransClass = transition[reverse ? 0 : 1]), 81 | reverse && REVERSE_CLASS || '' 82 | ]); 83 | prev.addClass(prevClasses); 84 | } 85 | 86 | next.css('z-index', ''); 87 | next[0].offsetWidth += 0; 88 | 89 | function done() { 90 | $rootScope.$apply(function() { 91 | deferred.resolve(); 92 | }); 93 | } 94 | 95 | //Find which element (sometimes none) to bind for ending 96 | var boundElement; 97 | if (nextTransClass && nextTransClass.length) { 98 | (boundElement = next).bind(ANIMATION_END, done); 99 | } else if (prev && prevTransClass && prevTransClass.length) { 100 | (boundElement = prev).bind(ANIMATION_END, done); 101 | } else { 102 | deferred.resolve(); 103 | } 104 | 105 | deferred.promise.then(function() { 106 | boundElement && boundElement.unbind(ANIMATION_END, done); 107 | next.removeClass(nextClasses); 108 | prev && prev.removeClass(prevClasses); 109 | }); 110 | 111 | //Let the user of change 'cancel' to finish transition early if they wish 112 | deferred.promise.cancel = function() { 113 | deferred.resolve(); 114 | }; 115 | return deferred.promise; 116 | }; 117 | }]; 118 | }); 119 | -------------------------------------------------------------------------------- /src/navigate.js: -------------------------------------------------------------------------------- 1 | angular.module('ajoslin.mobile-navigate') 2 | 3 | .provider('$navigate', function() { 4 | this.$get = ['$rootScope', '$location', '$route', function($rootScope, $location, $route) { 5 | var nav = {}, 6 | navHistory = []; //we keep our own version of history and ignore window.history 7 | 8 | function Page(path, transition, isReverse) { 9 | var _path = path, 10 | _transition = transition || 'slide', 11 | _isReverse = isReverse, 12 | _onceTransition; 13 | 14 | this.transition = function() { 15 | var trans; 16 | if (_onceTransition) { 17 | trans = _onceTransition; 18 | _onceTransition = null; 19 | } else { 20 | trans = _transition; 21 | } 22 | return trans; 23 | }; 24 | this.path = function() { return _path; }; 25 | this.reverse = function() { return _isReverse; }; 26 | 27 | //For setting a transition on a page - but only one time 28 | //Eg say on startup, we want to transition in with 'none', 29 | //but want to be 'slide' after that 30 | this.transitionOnce = function(trans) { 31 | _onceTransition = trans; 32 | }; 33 | } 34 | 35 | function navigate(destination, source, isBack) { 36 | $rootScope.$broadcast('$pageTransitionStart', destination, source, isBack); 37 | nav.current = nav.next; 38 | } 39 | 40 | /* 41 | * Will listen for a route change success and call the selected callback 42 | * Only one listen is ever active, so if you press for example 43 | * /link1 then press back before /link1 is done, it will go listen for the back 44 | */ 45 | nav.onRouteSuccess = null; 46 | //Add a default onroutesuccess for the very first page 47 | function defaultRouteSuccess($event, next, last) { 48 | nav.current && navHistory.push(nav.current); 49 | nav.next = new Page($location.path()); 50 | nav.next.transitionOnce('none'); 51 | navigate(nav.next); 52 | nav.onRouteSuccess = null; 53 | } 54 | $rootScope.$on('$routeChangeSuccess', function($event, next, last) { 55 | // Only navigate if it's a valid route and it's not gonna just redirect immediately 56 | if (!next.$$route || !next.$$route.redirectTo) { 57 | (nav.onRouteSuccess || defaultRouteSuccess)($event, next, last); 58 | } 59 | }); 60 | 61 | /* 62 | * go -transitions to new page 63 | * @param path - new path 64 | * @param {optional} String transition 65 | * @param {optional} boolean isReverse, default false 66 | */ 67 | nav.go = function go(path, transition, isReverse) { 68 | if (typeof transition == 'boolean') { 69 | isReverse = transition; 70 | transition = null; 71 | } 72 | $location.path(path); 73 | //Wait for successful route change before actually doing stuff 74 | nav.onRouteSuccess = function($event, next, last) { 75 | nav.current && navHistory.push(nav.current); 76 | nav.next = new Page(path, transition || (next.$$route && next.$$route.transition), isReverse); 77 | navigate(nav.next, nav.current, false); 78 | }; 79 | }; 80 | //Sometimes you want to erase history 81 | nav.eraseHistory = function() { 82 | navHistory.length = 0; 83 | }; 84 | nav.back = function() { 85 | if (navHistory.length > 0) { 86 | var previous = navHistory[navHistory.length-1]; 87 | $location.path(previous.path()); 88 | nav.onRouteSuccess = function() { 89 | navHistory.pop(); 90 | nav.next = previous; 91 | navigate(nav.next, nav.current, true); 92 | }; 93 | return true; 94 | } 95 | return false; 96 | }; 97 | 98 | return nav; 99 | }]; 100 | }); 101 | -------------------------------------------------------------------------------- /src/transition.css: -------------------------------------------------------------------------------- 1 | 2 | /* Prefix all pages with mb- for no collisions */ 3 | 4 | /* Page class for all mobile pages */ 5 | .mb-page { 6 | position: absolute; 7 | width: 100%; 8 | height: 100%; 9 | -webkit-backface-visibility: none; 10 | } 11 | 12 | /* 13 | * Slide 14 | * slide pages horizontally 15 | */ 16 | @-webkit-keyframes slideInFromRight { 17 | 0% { -webkit-transform: translate3d(100%, 0, 0); } 18 | 100% { -webkit-transform: translate3d(0, 0, 0); } 19 | } 20 | @-webkit-keyframes slideOutToRight { 21 | 0% { -webkit-transform: translate3d(0, 0, 0); } 22 | 100% { -webkit-transform: translate3d(100%, 0, 0); } 23 | } 24 | @-webkit-keyframes slideInFromLeft { 25 | 0% { -webkit-transform: translate3d(-100%,0,0); } 26 | 100% { -webkit-transform: translate3d(0, 0, 0); } 27 | } 28 | @-webkit-keyframes slideOutToLeft { 29 | 0% { -webkit-transform: translate3d(0,0,0); } 30 | 100% { -webkit-transform: translate3d(-100%, 0, 0); } 31 | } 32 | @keyframes slideInFromRight { 33 | 0% { transform: translate3d(100%, 0, 0); } 34 | 100% { transform: translate3d(0, 0, 0); } 35 | } 36 | @keyframes slideOutToRight { 37 | 0% { transform: translate3d(0, 0, 0); } 38 | 100% { transform: translate3d(100%, 0, 0); } 39 | } 40 | @keyframes slideInFromLeft { 41 | 0% { transform: translate3d(-100%,0,0); } 42 | 100% { transform: translate3d(0, 0, 0); } 43 | } 44 | @keyframes slideOutToLeft { 45 | 0% { transform: translate3d(0,0,0); } 46 | 100% { transform: translate3d(-100%, 0, 0); } 47 | } 48 | 49 | .mb-slide { 50 | -webkit-animation-duration: 0.3s; 51 | -webkit-animation-timing-function: ease; 52 | animation-duration: 0.3s; 53 | animation-timing-function: ease; 54 | } 55 | .mb-slide.mb-in { 56 | -webkit-animation-name: slideInFromRight; 57 | animation-name: slideInFromRight; 58 | } 59 | .mb-slide.mb-in.mb-reverse { 60 | -webkit-animation-name: slideOutToRight; 61 | -webkit-transform: translate3d(100%, 0, 0); 62 | animation-name: slideOutToRight; 63 | transform: translate3d(100%, 0, 0); 64 | } 65 | .mb-slide.mb-out { 66 | -webkit-animation-name: slideOutToLeft; 67 | -webkit-transform: translate3d(-100%, 0, 0); 68 | animation-name: slideOutToLeft; 69 | transform: translate3d(-100%, 0, 0); 70 | } 71 | .mb-slide.mb-out.mb-reverse { 72 | -webkit-animation-name: slideInFromLeft; 73 | animation-name: slideInFromLeft; 74 | } 75 | 76 | /* 77 | * Slide up and down - like modal, but exiting page is animated too. 78 | */ 79 | 80 | @-webkit-keyframes slideInFromTop { 81 | 0% { -webkit-transform: translate3d(0, 100%, 0); } 82 | 100% { -webkit-transform: translate3d(0, 0, 0); } 83 | } 84 | @-webkit-keyframes slideOutToTop { 85 | 0% { -webkit-transform: translate3d(0, 0, 0); } 86 | 100% { -webkit-transform: translate3d(0, 100%, 0); } 87 | } 88 | @-webkit-keyframes slideInFromBottom { 89 | 0% { -webkit-transform: translate3d(0,-100%,0); } 90 | 100% { -webkit-transform: translate3d(0, 0, 0); } 91 | } 92 | @-webkit-keyframes slideOutToBottom { 93 | 0% { -webkit-transform: translate3d(0,0,0); } 94 | 100% { -webkit-transform: translate3d(0, -100%, 0); } 95 | } 96 | @keyframes slideInFromTop { 97 | 0% { transform: translate3d(0, 100%, 0); } 98 | 100% { transform: translate3d(0, 0, 0); } 99 | } 100 | @keyframes slideOutToTop { 101 | 0% { transform: translate3d(0, 0, 0); } 102 | 100% { transform: translate3d(0, 100%, 0); } 103 | } 104 | @keyframes slideInFromBottom { 105 | 0% { transform: translate3d(0,-100%,0); } 106 | 100% { transform: translate3d(0, 0, 0); } 107 | } 108 | @keyframes slideOutToBottom { 109 | 0% { transform: translate3d(0,0,0); } 110 | 100% { transform: translate3d(0, -100%, 0); } 111 | } 112 | 113 | .mb-slide-up { 114 | -webkit-animation-duration: 0.3s; 115 | -webkit-animation-timing-function: ease; 116 | animation-duration: 0.3s; 117 | animation-timing-function: ease; 118 | } 119 | .mb-slide-up.mb-in { 120 | -webkit-animation-name: slideInFromTop; 121 | animation-name: slideInFromTop; 122 | } 123 | .mb-slide-up.mb-in.reverse { 124 | -webkit-animation-name: slideOutToTop; 125 | -webkit-transform: translate3d(0,-100%,0); 126 | animation-name: slideOutToTop; 127 | transform: translate3d(0,-100%,0); 128 | } 129 | .mb-slide-up.mb-out { 130 | -webkit-animation-name: slideOutToBottom; 131 | -webkit-transform: translate3d(0,100%,0); 132 | animation-name: slideOutToBottom; 133 | transform: translate3d(0,100%,0); 134 | } 135 | .mb-slide-up.mb-out.reverse { 136 | -webkit-animation-name: slideInFromBottom; 137 | animation-name: slideInFromBottom; 138 | } 139 | 140 | 141 | /* 142 | * Modal! 143 | * slide a page in from the bottom onto a page 144 | */ 145 | @-webkit-keyframes modalUp { 146 | 0% { -webkit-transform: translate3d(0, 100%, 0); } 147 | 100% { -webkit-transform: translate3d(0, 0, 0); } 148 | } 149 | @-webkit-keyframes modalDown { 150 | 0% { -webkit-transform: translate3d(0, 0, 0); } 151 | 100% { -webkit-transform: translate3d(0, 100%, 0); } 152 | } 153 | @keyframes modalUp { 154 | 0% { transform: translate3d(0, 100%, 0); } 155 | 100% { transform: translate3d(0, 0, 0); } 156 | } 157 | @keyframes modalDown { 158 | 0% { transform: translate3d(0, 0, 0); } 159 | 100% { transform: translate3d(0, 100%, 0); } 160 | } 161 | .mb-modal { 162 | z-index: 10; 163 | -webkit-animation-duration: 0.4s; 164 | animation-duration: 0.4s; 165 | } 166 | .mb-modal.mb-in, 167 | .mb-modal.mb-out.mb-reverse { 168 | -webkit-animation-name: modalUp; 169 | animation-name: modalUp; 170 | } 171 | .mb-modal.mb-in.mb-reverse, 172 | .mb-modal.mb-out { 173 | z-index: 9; /* Lower than modal-in */ 174 | -webkit-animation-name: modalDown; 175 | -webkit-transform: translate3d(0, 100%, 0); 176 | animation-name: modalDown; 177 | transform: translate3d(0, 100%, 0); 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/view.js: -------------------------------------------------------------------------------- 1 | angular.module('ajoslin.mobile-navigate') 2 | .directive('mobileView', ['$rootScope', '$compile', '$controller', '$route', '$change', '$q', 3 | function($rootScope, $compile, $controller, $route, $change, $q) { 4 | 5 | function link(scope, viewElement, attrs) { 6 | //Insert page into dom 7 | function insertPage(page) { 8 | var current = $route.current, 9 | locals = current && current.locals; 10 | 11 | page.element = angular.element(document.createElement("div")); 12 | page.element.html(locals.$template); 13 | page.element.addClass('mb-page'); //always has to have page class 14 | page.scope = scope.$new(); 15 | if (current.controller) { 16 | locals.$scope = page.scope; 17 | page.controller = $controller(current.controller, locals); 18 | page.element.contents().data('$ngControllerController', page.controller); 19 | } 20 | $compile(page.element.contents())(page.scope); 21 | if (locals && locals.$template) { 22 | // only append page element if a template exists 23 | viewElement.append(page.element); 24 | } 25 | page.scope.$emit('$viewContentLoaded'); 26 | page.scope.$eval(attrs.onLoad); 27 | return page; 28 | } 29 | 30 | 31 | var currentTrans; 32 | scope.$on('$pageTransitionStart', function ($event, dest, source, reverse) { 33 | function changePage() { 34 | var current = $route.current && $route.current.$$route || {}; 35 | var transition = reverse ? source.transition() : dest.transition(); 36 | 37 | insertPage(dest); 38 | 39 | //If the page is marked as reverse, reverse the direction 40 | if (dest.reverse() || current.reverse) { 41 | reverse = !reverse; 42 | } 43 | 44 | function doTransition() { 45 | 46 | var promise = $change(dest.element, (source ? source.element : null), 47 | transition, reverse); 48 | 49 | promise.then(function() { 50 | if (source) { 51 | $rootScope.$broadcast('$pageTransitionSuccess', dest, source); 52 | source.scope.$destroy(); 53 | source.element.remove(); 54 | source = undefined; 55 | } 56 | }); 57 | 58 | return promise; 59 | } 60 | 61 | //Set next element to display: none, then wait until transition is 62 | //ready, then show it again. 63 | dest.element.css('display', 'none'); 64 | 65 | //Allow a deferTransition expression, which is allowed to return a promise. 66 | //The next page will be inserted, but not transitioned in until the promise 67 | //is fulfilled. 68 | var deferTransitionPromise = scope.$eval(attrs.deferTransition) || $q.when(); 69 | deferTransitionPromise.cancel = function() { 70 | cancelled = true; 71 | //Undo display none from waiting for transition 72 | dest.element.css('display', ''); 73 | }; 74 | 75 | var cancelled = false; 76 | deferTransitionPromise.then(function() { 77 | if (!cancelled) { 78 | //Undo display none from waiting for transition 79 | dest.element.css('display', ''); 80 | return doTransition(); 81 | } 82 | }); 83 | 84 | return deferTransitionPromise; 85 | } 86 | currentTrans && currentTrans.cancel(); 87 | currentTrans = changePage(dest, source, reverse); 88 | }); 89 | } 90 | return { 91 | restrict: 'EA', 92 | link: link 93 | }; 94 | }]) 95 | 96 | .directive('scrollable', ['$route', function($route) { 97 | var scrollCache = {}; 98 | return { 99 | restrict: 'EA', 100 | link: function(scope, elm, attrs) { 101 | var route = $route.current ? $route.current.$$route : {}; 102 | var template = route.templateUrl || route.template; 103 | var rawElm = elm[0]; 104 | 105 | //On scope creation, see if we remembered any scroll for this templateUrl 106 | //If we did, set it 107 | if (template) { 108 | //Set oldScroll after a timeout so the page has time to fully load 109 | setTimeout(function() { 110 | var oldScroll = scrollCache[template]; 111 | if (oldScroll) { 112 | rawElm.scrollTop = oldScroll; 113 | } 114 | }); 115 | 116 | scope.$on('$destroy', function() { 117 | scrollCache[template] = rawElm.scrollTop; 118 | }); 119 | } 120 | } 121 | }; 122 | }]); 123 | --------------------------------------------------------------------------------