You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14390 lines
449 KiB

  1. /*!
  2. * Copyright 2015 Drifty Co.
  3. * http://drifty.com/
  4. *
  5. * Ionic, v1.3.1
  6. * A powerful HTML5 mobile app framework.
  7. * http://ionicframework.com/
  8. *
  9. * By @maxlynch, @benjsperry, @adamdbradley <3
  10. *
  11. * Licensed under the MIT license. Please see LICENSE for more information.
  12. *
  13. */
  14. (function() {
  15. /* eslint no-unused-vars:0 */
  16. var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']),
  17. extend = angular.extend,
  18. forEach = angular.forEach,
  19. isDefined = angular.isDefined,
  20. isNumber = angular.isNumber,
  21. isString = angular.isString,
  22. jqLite = angular.element,
  23. noop = angular.noop;
  24. /**
  25. * @ngdoc service
  26. * @name $ionicActionSheet
  27. * @module ionic
  28. * @description
  29. * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
  30. * Dangerous options are highlighted in red and made obvious.
  31. *
  32. * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
  33. * hitting escape on the keyboard for desktop testing.
  34. *
  35. * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
  36. *
  37. * @usage
  38. * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
  39. *
  40. * ```js
  41. * angular.module('mySuperApp', ['ionic'])
  42. * .controller(function($scope, $ionicActionSheet, $timeout) {
  43. *
  44. * // Triggered on a button click, or some other target
  45. * $scope.show = function() {
  46. *
  47. * // Show the action sheet
  48. * var hideSheet = $ionicActionSheet.show({
  49. * buttons: [
  50. * { text: '<b>Share</b> This' },
  51. * { text: 'Move' }
  52. * ],
  53. * destructiveText: 'Delete',
  54. * titleText: 'Modify your album',
  55. * cancelText: 'Cancel',
  56. * cancel: function() {
  57. // add cancel code..
  58. },
  59. * buttonClicked: function(index) {
  60. * return true;
  61. * }
  62. * });
  63. *
  64. * // For example's sake, hide the sheet after two seconds
  65. * $timeout(function() {
  66. * hideSheet();
  67. * }, 2000);
  68. *
  69. * };
  70. * });
  71. * ```
  72. *
  73. */
  74. IonicModule
  75. .factory('$ionicActionSheet', [
  76. '$rootScope',
  77. '$compile',
  78. '$animate',
  79. '$timeout',
  80. '$ionicTemplateLoader',
  81. '$ionicPlatform',
  82. '$ionicBody',
  83. 'IONIC_BACK_PRIORITY',
  84. function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) {
  85. return {
  86. show: actionSheet
  87. };
  88. /**
  89. * @ngdoc method
  90. * @name $ionicActionSheet#show
  91. * @description
  92. * Load and return a new action sheet.
  93. *
  94. * A new isolated scope will be created for the
  95. * action sheet and the new element will be appended into the body.
  96. *
  97. * @param {object} options The options for this ActionSheet. Properties:
  98. *
  99. * - `[Object]` `buttons` Which buttons to show. Each button is an object with a `text` field.
  100. * - `{string}` `titleText` The title to show on the action sheet.
  101. * - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.
  102. * - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
  103. * - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or
  104. * the hardware back button is pressed.
  105. * - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
  106. * with the index of the button that was clicked and the button object. Return true to close
  107. * the action sheet, or false to keep it opened.
  108. * - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
  109. * Return true to close the action sheet, or false to keep it opened.
  110. * - `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating
  111. * to a new state. Default true.
  112. * - `{string}` `cssClass` The custom CSS class name.
  113. *
  114. * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.
  115. */
  116. function actionSheet(opts) {
  117. var scope = $rootScope.$new(true);
  118. extend(scope, {
  119. cancel: noop,
  120. destructiveButtonClicked: noop,
  121. buttonClicked: noop,
  122. $deregisterBackButton: noop,
  123. buttons: [],
  124. cancelOnStateChange: true
  125. }, opts || {});
  126. function textForIcon(text) {
  127. if (text && /icon/.test(text)) {
  128. scope.$actionSheetHasIcon = true;
  129. }
  130. }
  131. for (var x = 0; x < scope.buttons.length; x++) {
  132. textForIcon(scope.buttons[x].text);
  133. }
  134. textForIcon(scope.cancelText);
  135. textForIcon(scope.destructiveText);
  136. // Compile the template
  137. var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);
  138. // Grab the sheet element for animation
  139. var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));
  140. var stateChangeListenDone = scope.cancelOnStateChange ?
  141. $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :
  142. noop;
  143. // removes the actionSheet from the screen
  144. scope.removeSheet = function(done) {
  145. if (scope.removed) return;
  146. scope.removed = true;
  147. sheetEl.removeClass('action-sheet-up');
  148. $timeout(function() {
  149. // wait to remove this due to a 300ms delay native
  150. // click which would trigging whatever was underneath this
  151. $ionicBody.removeClass('action-sheet-open');
  152. }, 400);
  153. scope.$deregisterBackButton();
  154. stateChangeListenDone();
  155. $animate.removeClass(element, 'active').then(function() {
  156. scope.$destroy();
  157. element.remove();
  158. // scope.cancel.$scope is defined near the bottom
  159. scope.cancel.$scope = sheetEl = null;
  160. (done || noop)(opts.buttons);
  161. });
  162. };
  163. scope.showSheet = function(done) {
  164. if (scope.removed) return;
  165. $ionicBody.append(element)
  166. .addClass('action-sheet-open');
  167. $animate.addClass(element, 'active').then(function() {
  168. if (scope.removed) return;
  169. (done || noop)();
  170. });
  171. $timeout(function() {
  172. if (scope.removed) return;
  173. sheetEl.addClass('action-sheet-up');
  174. }, 20, false);
  175. };
  176. // registerBackButtonAction returns a callback to deregister the action
  177. scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
  178. function() {
  179. $timeout(scope.cancel);
  180. },
  181. IONIC_BACK_PRIORITY.actionSheet
  182. );
  183. // called when the user presses the cancel button
  184. scope.cancel = function() {
  185. // after the animation is out, call the cancel callback
  186. scope.removeSheet(opts.cancel);
  187. };
  188. scope.buttonClicked = function(index) {
  189. // Check if the button click event returned true, which means
  190. // we can close the action sheet
  191. if (opts.buttonClicked(index, opts.buttons[index]) === true) {
  192. scope.removeSheet();
  193. }
  194. };
  195. scope.destructiveButtonClicked = function() {
  196. // Check if the destructive button click event returned true, which means
  197. // we can close the action sheet
  198. if (opts.destructiveButtonClicked() === true) {
  199. scope.removeSheet();
  200. }
  201. };
  202. scope.showSheet();
  203. // Expose the scope on $ionicActionSheet's return value for the sake
  204. // of testing it.
  205. scope.cancel.$scope = scope;
  206. return scope.cancel;
  207. }
  208. }]);
  209. jqLite.prototype.addClass = function(cssClasses) {
  210. var x, y, cssClass, el, splitClasses, existingClasses;
  211. if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
  212. for (x = 0; x < this.length; x++) {
  213. el = this[x];
  214. if (el.setAttribute) {
  215. if (cssClasses.indexOf(' ') < 0 && el.classList.add) {
  216. el.classList.add(cssClasses);
  217. } else {
  218. existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
  219. .replace(/[\n\t]/g, " ");
  220. splitClasses = cssClasses.split(' ');
  221. for (y = 0; y < splitClasses.length; y++) {
  222. cssClass = splitClasses[y].trim();
  223. if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
  224. existingClasses += cssClass + ' ';
  225. }
  226. }
  227. el.setAttribute('class', existingClasses.trim());
  228. }
  229. }
  230. }
  231. }
  232. return this;
  233. };
  234. jqLite.prototype.removeClass = function(cssClasses) {
  235. var x, y, splitClasses, cssClass, el;
  236. if (cssClasses) {
  237. for (x = 0; x < this.length; x++) {
  238. el = this[x];
  239. if (el.getAttribute) {
  240. if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {
  241. el.classList.remove(cssClasses);
  242. } else {
  243. splitClasses = cssClasses.split(' ');
  244. for (y = 0; y < splitClasses.length; y++) {
  245. cssClass = splitClasses[y];
  246. el.setAttribute('class', (
  247. (" " + (el.getAttribute('class') || '') + " ")
  248. .replace(/[\n\t]/g, " ")
  249. .replace(" " + cssClass.trim() + " ", " ")).trim()
  250. );
  251. }
  252. }
  253. }
  254. }
  255. }
  256. return this;
  257. };
  258. /**
  259. * @ngdoc service
  260. * @name $ionicBackdrop
  261. * @module ionic
  262. * @description
  263. * Shows and hides a backdrop over the UI. Appears behind popups, loading,
  264. * and other overlays.
  265. *
  266. * Often, multiple UI components require a backdrop, but only one backdrop is
  267. * ever needed in the DOM at a time.
  268. *
  269. * Therefore, each component that requires the backdrop to be shown calls
  270. * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()`
  271. * when it is done with the backdrop.
  272. *
  273. * For each time `retain` is called, the backdrop will be shown until `release` is called.
  274. *
  275. * For example, if `retain` is called three times, the backdrop will be shown until `release`
  276. * is called three times.
  277. *
  278. * **Notes:**
  279. * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope,
  280. * this is useful for alerting native components not in html.
  281. *
  282. * @usage
  283. *
  284. * ```js
  285. * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) {
  286. * //Show a backdrop for one second
  287. * $scope.action = function() {
  288. * $ionicBackdrop.retain();
  289. * $timeout(function() {
  290. * $ionicBackdrop.release();
  291. * }, 1000);
  292. * };
  293. *
  294. * // Execute action on backdrop disappearing
  295. * $scope.$on('backdrop.hidden', function() {
  296. * // Execute action
  297. * });
  298. *
  299. * // Execute action on backdrop appearing
  300. * $scope.$on('backdrop.shown', function() {
  301. * // Execute action
  302. * });
  303. *
  304. * }
  305. * ```
  306. */
  307. IonicModule
  308. .factory('$ionicBackdrop', [
  309. '$document', '$timeout', '$$rAF', '$rootScope',
  310. function($document, $timeout, $$rAF, $rootScope) {
  311. var el = jqLite('<div class="backdrop">');
  312. var backdropHolds = 0;
  313. $document[0].body.appendChild(el[0]);
  314. return {
  315. /**
  316. * @ngdoc method
  317. * @name $ionicBackdrop#retain
  318. * @description Retains the backdrop.
  319. */
  320. retain: retain,
  321. /**
  322. * @ngdoc method
  323. * @name $ionicBackdrop#release
  324. * @description
  325. * Releases the backdrop.
  326. */
  327. release: release,
  328. getElement: getElement,
  329. // exposed for testing
  330. _element: el
  331. };
  332. function retain() {
  333. backdropHolds++;
  334. if (backdropHolds === 1) {
  335. el.addClass('visible');
  336. $rootScope.$broadcast('backdrop.shown');
  337. $$rAF(function() {
  338. // If we're still at >0 backdropHolds after async...
  339. if (backdropHolds >= 1) el.addClass('active');
  340. });
  341. }
  342. }
  343. function release() {
  344. if (backdropHolds === 1) {
  345. el.removeClass('active');
  346. $rootScope.$broadcast('backdrop.hidden');
  347. $timeout(function() {
  348. // If we're still at 0 backdropHolds after async...
  349. if (backdropHolds === 0) el.removeClass('visible');
  350. }, 400, false);
  351. }
  352. backdropHolds = Math.max(0, backdropHolds - 1);
  353. }
  354. function getElement() {
  355. return el;
  356. }
  357. }]);
  358. /**
  359. * @private
  360. */
  361. IonicModule
  362. .factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
  363. var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
  364. return function(scope, attrs, bindDefinition) {
  365. forEach(bindDefinition || {}, function(definition, scopeName) {
  366. //Adapted from angular.js $compile
  367. var match = definition.match(LOCAL_REGEXP) || [],
  368. attrName = match[3] || scopeName,
  369. mode = match[1], // @, =, or &
  370. parentGet,
  371. unwatch;
  372. switch (mode) {
  373. case '@':
  374. if (!attrs[attrName]) {
  375. return;
  376. }
  377. attrs.$observe(attrName, function(value) {
  378. scope[scopeName] = value;
  379. });
  380. // we trigger an interpolation to ensure
  381. // the value is there for use immediately
  382. if (attrs[attrName]) {
  383. scope[scopeName] = $interpolate(attrs[attrName])(scope);
  384. }
  385. break;
  386. case '=':
  387. if (!attrs[attrName]) {
  388. return;
  389. }
  390. unwatch = scope.$watch(attrs[attrName], function(value) {
  391. scope[scopeName] = value;
  392. });
  393. //Destroy parent scope watcher when this scope is destroyed
  394. scope.$on('$destroy', unwatch);
  395. break;
  396. case '&':
  397. /* jshint -W044 */
  398. if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
  399. throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
  400. attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
  401. }
  402. parentGet = $parse(attrs[attrName]);
  403. scope[scopeName] = function(locals) {
  404. return parentGet(scope, locals);
  405. };
  406. break;
  407. }
  408. });
  409. };
  410. }]);
  411. /**
  412. * @ngdoc service
  413. * @name $ionicBody
  414. * @module ionic
  415. * @description An angular utility service to easily and efficiently
  416. * add and remove CSS classes from the document's body element.
  417. */
  418. IonicModule
  419. .factory('$ionicBody', ['$document', function($document) {
  420. return {
  421. /**
  422. * @ngdoc method
  423. * @name $ionicBody#addClass
  424. * @description Add a class to the document's body element.
  425. * @param {string} class Each argument will be added to the body element.
  426. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  427. */
  428. addClass: function() {
  429. for (var x = 0; x < arguments.length; x++) {
  430. $document[0].body.classList.add(arguments[x]);
  431. }
  432. return this;
  433. },
  434. /**
  435. * @ngdoc method
  436. * @name $ionicBody#removeClass
  437. * @description Remove a class from the document's body element.
  438. * @param {string} class Each argument will be removed from the body element.
  439. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  440. */
  441. removeClass: function() {
  442. for (var x = 0; x < arguments.length; x++) {
  443. $document[0].body.classList.remove(arguments[x]);
  444. }
  445. return this;
  446. },
  447. /**
  448. * @ngdoc method
  449. * @name $ionicBody#enableClass
  450. * @description Similar to the `add` method, except the first parameter accepts a boolean
  451. * value determining if the class should be added or removed. Rather than writing user code,
  452. * such as "if true then add the class, else then remove the class", this method can be
  453. * given a true or false value which reduces redundant code.
  454. * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
  455. * @param {string} class Each remaining argument would be added or removed depending on
  456. * the first argument.
  457. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  458. */
  459. enableClass: function(shouldEnableClass) {
  460. var args = Array.prototype.slice.call(arguments).slice(1);
  461. if (shouldEnableClass) {
  462. this.addClass.apply(this, args);
  463. } else {
  464. this.removeClass.apply(this, args);
  465. }
  466. return this;
  467. },
  468. /**
  469. * @ngdoc method
  470. * @name $ionicBody#append
  471. * @description Append a child to the document's body.
  472. * @param {element} element The element to be appended to the body. The passed in element
  473. * can be either a jqLite element, or a DOM element.
  474. * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  475. */
  476. append: function(ele) {
  477. $document[0].body.appendChild(ele.length ? ele[0] : ele);
  478. return this;
  479. },
  480. /**
  481. * @ngdoc method
  482. * @name $ionicBody#get
  483. * @description Get the document's body element.
  484. * @returns {element} Returns the document's body element.
  485. */
  486. get: function() {
  487. return $document[0].body;
  488. }
  489. };
  490. }]);
  491. IonicModule
  492. .factory('$ionicClickBlock', [
  493. '$document',
  494. '$ionicBody',
  495. '$timeout',
  496. function($document, $ionicBody, $timeout) {
  497. var CSS_HIDE = 'click-block-hide';
  498. var cbEle, fallbackTimer, pendingShow;
  499. function preventClick(ev) {
  500. ev.preventDefault();
  501. ev.stopPropagation();
  502. }
  503. function addClickBlock() {
  504. if (pendingShow) {
  505. if (cbEle) {
  506. cbEle.classList.remove(CSS_HIDE);
  507. } else {
  508. cbEle = $document[0].createElement('div');
  509. cbEle.className = 'click-block';
  510. $ionicBody.append(cbEle);
  511. cbEle.addEventListener('touchstart', preventClick);
  512. cbEle.addEventListener('mousedown', preventClick);
  513. }
  514. pendingShow = false;
  515. }
  516. }
  517. function removeClickBlock() {
  518. cbEle && cbEle.classList.add(CSS_HIDE);
  519. }
  520. return {
  521. show: function(autoExpire) {
  522. pendingShow = true;
  523. $timeout.cancel(fallbackTimer);
  524. fallbackTimer = $timeout(this.hide, autoExpire || 310, false);
  525. addClickBlock();
  526. },
  527. hide: function() {
  528. pendingShow = false;
  529. $timeout.cancel(fallbackTimer);
  530. removeClickBlock();
  531. }
  532. };
  533. }]);
  534. /**
  535. * @ngdoc service
  536. * @name $ionicGesture
  537. * @module ionic
  538. * @description An angular service exposing ionic
  539. * {@link ionic.utility:ionic.EventController}'s gestures.
  540. */
  541. IonicModule
  542. .factory('$ionicGesture', [function() {
  543. return {
  544. /**
  545. * @ngdoc method
  546. * @name $ionicGesture#on
  547. * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
  548. * @param {string} eventType The gesture event to listen for.
  549. * @param {function(e)} callback The function to call when the gesture
  550. * happens.
  551. * @param {element} $element The angular element to listen for the event on.
  552. * @param {object} options object.
  553. * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  554. */
  555. on: function(eventType, cb, $element, options) {
  556. return window.ionic.onGesture(eventType, cb, $element[0], options);
  557. },
  558. /**
  559. * @ngdoc method
  560. * @name $ionicGesture#off
  561. * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
  562. * @param {ionic.Gesture} gesture The gesture that should be removed.
  563. * @param {string} eventType The gesture event to remove the listener for.
  564. * @param {function(e)} callback The listener to remove.
  565. */
  566. off: function(gesture, eventType, cb) {
  567. return window.ionic.offGesture(gesture, eventType, cb);
  568. }
  569. };
  570. }]);
  571. /**
  572. * @ngdoc service
  573. * @name $ionicHistory
  574. * @module ionic
  575. * @description
  576. * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
  577. * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
  578. * the forward view (if there is one). However, a typical web browser only keeps track of one
  579. * history stack in a linear fashion.
  580. *
  581. * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
  582. * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
  583. * tab and back, the back button relates not to the previous tab, but to the previous pages
  584. * visited within _that_ tab.
  585. *
  586. * `$ionicHistory` facilitates this parallel history architecture.
  587. */
  588. IonicModule
  589. .factory('$ionicHistory', [
  590. '$rootScope',
  591. '$state',
  592. '$location',
  593. '$window',
  594. '$timeout',
  595. '$ionicViewSwitcher',
  596. '$ionicNavViewDelegate',
  597. function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
  598. // history actions while navigating views
  599. var ACTION_INITIAL_VIEW = 'initialView';
  600. var ACTION_NEW_VIEW = 'newView';
  601. var ACTION_MOVE_BACK = 'moveBack';
  602. var ACTION_MOVE_FORWARD = 'moveForward';
  603. // direction of navigation
  604. var DIRECTION_BACK = 'back';
  605. var DIRECTION_FORWARD = 'forward';
  606. var DIRECTION_ENTER = 'enter';
  607. var DIRECTION_EXIT = 'exit';
  608. var DIRECTION_SWAP = 'swap';
  609. var DIRECTION_NONE = 'none';
  610. var stateChangeCounter = 0;
  611. var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav;
  612. var viewHistory = {
  613. histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
  614. views: {},
  615. backView: null,
  616. forwardView: null,
  617. currentView: null
  618. };
  619. var View = function() {};
  620. View.prototype.initialize = function(data) {
  621. if (data) {
  622. for (var name in data) this[name] = data[name];
  623. return this;
  624. }
  625. return null;
  626. };
  627. View.prototype.go = function() {
  628. if (this.stateName) {
  629. return $state.go(this.stateName, this.stateParams);
  630. }
  631. if (this.url && this.url !== $location.url()) {
  632. if (viewHistory.backView === this) {
  633. return $window.history.go(-1);
  634. } else if (viewHistory.forwardView === this) {
  635. return $window.history.go(1);
  636. }
  637. $location.url(this.url);
  638. }
  639. return null;
  640. };
  641. View.prototype.destroy = function() {
  642. if (this.scope) {
  643. this.scope.$destroy && this.scope.$destroy();
  644. this.scope = null;
  645. }
  646. };
  647. function getViewById(viewId) {
  648. return (viewId ? viewHistory.views[ viewId ] : null);
  649. }
  650. function getBackView(view) {
  651. return (view ? getViewById(view.backViewId) : null);
  652. }
  653. function getForwardView(view) {
  654. return (view ? getViewById(view.forwardViewId) : null);
  655. }
  656. function getHistoryById(historyId) {
  657. return (historyId ? viewHistory.histories[ historyId ] : null);
  658. }
  659. function getHistory(scope) {
  660. var histObj = getParentHistoryObj(scope);
  661. if (!viewHistory.histories[ histObj.historyId ]) {
  662. // this history object exists in parent scope, but doesn't
  663. // exist in the history data yet
  664. viewHistory.histories[ histObj.historyId ] = {
  665. historyId: histObj.historyId,
  666. parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
  667. stack: [],
  668. cursor: -1
  669. };
  670. }
  671. return getHistoryById(histObj.historyId);
  672. }
  673. function getParentHistoryObj(scope) {
  674. var parentScope = scope;
  675. while (parentScope) {
  676. if (parentScope.hasOwnProperty('$historyId')) {
  677. // this parent scope has a historyId
  678. return { historyId: parentScope.$historyId, scope: parentScope };
  679. }
  680. // nothing found keep climbing up
  681. parentScope = parentScope.$parent;
  682. }
  683. // no history for the parent, use the root
  684. return { historyId: 'root', scope: $rootScope };
  685. }
  686. function setNavViews(viewId) {
  687. viewHistory.currentView = getViewById(viewId);
  688. viewHistory.backView = getBackView(viewHistory.currentView);
  689. viewHistory.forwardView = getForwardView(viewHistory.currentView);
  690. }
  691. function getCurrentStateId() {
  692. var id;
  693. if ($state && $state.current && $state.current.name) {
  694. id = $state.current.name;
  695. if ($state.params) {
  696. for (var key in $state.params) {
  697. if ($state.params.hasOwnProperty(key) && $state.params[key]) {
  698. id += "_" + key + "=" + $state.params[key];
  699. }
  700. }
  701. }
  702. return id;
  703. }
  704. // if something goes wrong make sure its got a unique stateId
  705. return ionic.Utils.nextUid();
  706. }
  707. function getCurrentStateParams() {
  708. var rtn;
  709. if ($state && $state.params) {
  710. for (var key in $state.params) {
  711. if ($state.params.hasOwnProperty(key)) {
  712. rtn = rtn || {};
  713. rtn[key] = $state.params[key];
  714. }
  715. }
  716. }
  717. return rtn;
  718. }
  719. return {
  720. register: function(parentScope, viewLocals) {
  721. var currentStateId = getCurrentStateId(),
  722. hist = getHistory(parentScope),
  723. currentView = viewHistory.currentView,
  724. backView = viewHistory.backView,
  725. forwardView = viewHistory.forwardView,
  726. viewId = null,
  727. action = null,
  728. direction = DIRECTION_NONE,
  729. historyId = hist.historyId,
  730. url = $location.url(),
  731. tmp, x, ele;
  732. if (lastStateId !== currentStateId) {
  733. lastStateId = currentStateId;
  734. stateChangeCounter++;
  735. }
  736. if (forcedNav) {
  737. // we've previously set exactly what to do
  738. viewId = forcedNav.viewId;
  739. action = forcedNav.action;
  740. direction = forcedNav.direction;
  741. forcedNav = null;
  742. } else if (backView && backView.stateId === currentStateId) {
  743. // they went back one, set the old current view as a forward view
  744. viewId = backView.viewId;
  745. historyId = backView.historyId;
  746. action = ACTION_MOVE_BACK;
  747. if (backView.historyId === currentView.historyId) {
  748. // went back in the same history
  749. direction = DIRECTION_BACK;
  750. } else if (currentView) {
  751. direction = DIRECTION_EXIT;
  752. tmp = getHistoryById(backView.historyId);
  753. if (tmp && tmp.parentHistoryId === currentView.historyId) {
  754. direction = DIRECTION_ENTER;
  755. } else {
  756. tmp = getHistoryById(currentView.historyId);
  757. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  758. direction = DIRECTION_SWAP;
  759. }
  760. }
  761. }
  762. } else if (forwardView && forwardView.stateId === currentStateId) {
  763. // they went to the forward one, set the forward view to no longer a forward view
  764. viewId = forwardView.viewId;
  765. historyId = forwardView.historyId;
  766. action = ACTION_MOVE_FORWARD;
  767. if (forwardView.historyId === currentView.historyId) {
  768. direction = DIRECTION_FORWARD;
  769. } else if (currentView) {
  770. direction = DIRECTION_EXIT;
  771. if (currentView.historyId === hist.parentHistoryId) {
  772. direction = DIRECTION_ENTER;
  773. } else {
  774. tmp = getHistoryById(currentView.historyId);
  775. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  776. direction = DIRECTION_SWAP;
  777. }
  778. }
  779. }
  780. tmp = getParentHistoryObj(parentScope);
  781. if (forwardView.historyId && tmp.scope) {
  782. // if a history has already been created by the forward view then make sure it stays the same
  783. tmp.scope.$historyId = forwardView.historyId;
  784. historyId = forwardView.historyId;
  785. }
  786. } else if (currentView && currentView.historyId !== historyId &&
  787. hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
  788. hist.stack[hist.cursor].stateId === currentStateId) {
  789. // they just changed to a different history and the history already has views in it
  790. var switchToView = hist.stack[hist.cursor];
  791. viewId = switchToView.viewId;
  792. historyId = switchToView.historyId;
  793. action = ACTION_MOVE_BACK;
  794. direction = DIRECTION_SWAP;
  795. tmp = getHistoryById(currentView.historyId);
  796. if (tmp && tmp.parentHistoryId === historyId) {
  797. direction = DIRECTION_EXIT;
  798. } else {
  799. tmp = getHistoryById(historyId);
  800. if (tmp && tmp.parentHistoryId === currentView.historyId) {
  801. direction = DIRECTION_ENTER;
  802. }
  803. }
  804. // if switching to a different history, and the history of the view we're switching
  805. // to has an existing back view from a different history than itself, then
  806. // it's back view would be better represented using the current view as its back view
  807. tmp = getViewById(switchToView.backViewId);
  808. if (tmp && switchToView.historyId !== tmp.historyId) {
  809. // the new view is being removed from it's old position in the history and being placed at the top,
  810. // so we need to update any views that reference it as a backview, otherwise there will be infinitely loops
  811. var viewIds = Object.keys(viewHistory.views);
  812. viewIds.forEach(function(viewId) {
  813. var view = viewHistory.views[viewId];
  814. if ( view.backViewId === switchToView.viewId ) {
  815. view.backViewId = null;
  816. }
  817. });
  818. hist.stack[hist.cursor].backViewId = currentView.viewId;
  819. }
  820. } else {
  821. // create an element from the viewLocals template
  822. ele = $ionicViewSwitcher.createViewEle(viewLocals);
  823. if (this.isAbstractEle(ele, viewLocals)) {
  824. return {
  825. action: 'abstractView',
  826. direction: DIRECTION_NONE,
  827. ele: ele
  828. };
  829. }
  830. // set a new unique viewId
  831. viewId = ionic.Utils.nextUid();
  832. if (currentView) {
  833. // set the forward view if there is a current view (ie: if its not the first view)
  834. currentView.forwardViewId = viewId;
  835. action = ACTION_NEW_VIEW;
  836. // check if there is a new forward view within the same history
  837. if (forwardView && currentView.stateId !== forwardView.stateId &&
  838. currentView.historyId === forwardView.historyId) {
  839. // they navigated to a new view but the stack already has a forward view
  840. // since its a new view remove any forwards that existed
  841. tmp = getHistoryById(forwardView.historyId);
  842. if (tmp) {
  843. // the forward has a history
  844. for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
  845. // starting from the end destroy all forwards in this history from this point
  846. var stackItem = tmp.stack[x];
  847. stackItem && stackItem.destroy && stackItem.destroy();
  848. tmp.stack.splice(x);
  849. }
  850. historyId = forwardView.historyId;
  851. }
  852. }
  853. // its only moving forward if its in the same history
  854. if (hist.historyId === currentView.historyId) {
  855. direction = DIRECTION_FORWARD;
  856. } else if (currentView.historyId !== hist.historyId) {
  857. // DB: this is a new view in a different tab
  858. direction = DIRECTION_ENTER;
  859. tmp = getHistoryById(currentView.historyId);
  860. if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  861. direction = DIRECTION_SWAP;
  862. } else {
  863. tmp = getHistoryById(tmp.parentHistoryId);
  864. if (tmp && tmp.historyId === hist.historyId) {
  865. direction = DIRECTION_EXIT;
  866. }
  867. }
  868. }
  869. } else {
  870. // there's no current view, so this must be the initial view
  871. action = ACTION_INITIAL_VIEW;
  872. }
  873. if (stateChangeCounter < 2) {
  874. // views that were spun up on the first load should not animate
  875. direction = DIRECTION_NONE;
  876. }
  877. // add the new view
  878. viewHistory.views[viewId] = this.createView({
  879. viewId: viewId,
  880. index: hist.stack.length,
  881. historyId: hist.historyId,
  882. backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
  883. forwardViewId: null,
  884. stateId: currentStateId,
  885. stateName: this.currentStateName(),
  886. stateParams: getCurrentStateParams(),
  887. url: url,
  888. canSwipeBack: canSwipeBack(ele, viewLocals)
  889. });
  890. // add the new view to this history's stack
  891. hist.stack.push(viewHistory.views[viewId]);
  892. }
  893. deregisterStateChangeListener && deregisterStateChangeListener();
  894. $timeout.cancel(nextViewExpireTimer);
  895. if (nextViewOptions) {
  896. if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
  897. if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
  898. if (nextViewOptions.historyRoot) {
  899. for (x = 0; x < hist.stack.length; x++) {
  900. if (hist.stack[x].viewId === viewId) {
  901. hist.stack[x].index = 0;
  902. hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
  903. } else {
  904. delete viewHistory.views[hist.stack[x].viewId];
  905. }
  906. }
  907. hist.stack = [viewHistory.views[viewId]];
  908. }
  909. nextViewOptions = null;
  910. }
  911. setNavViews(viewId);
  912. if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
  913. for (x = 0; x < hist.stack.length; x++) {
  914. if (hist.stack[x].viewId == viewId) {
  915. action = 'dupNav';
  916. direction = DIRECTION_NONE;
  917. if (x > 0) {
  918. hist.stack[x - 1].forwardViewId = null;
  919. }
  920. viewHistory.forwardView = null;
  921. viewHistory.currentView.index = viewHistory.backView.index;
  922. viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
  923. viewHistory.backView = getBackView(viewHistory.backView);
  924. hist.stack.splice(x, 1);
  925. break;
  926. }
  927. }
  928. }
  929. hist.cursor = viewHistory.currentView.index;
  930. return {
  931. viewId: viewId,
  932. action: action,
  933. direction: direction,
  934. historyId: historyId,
  935. enableBack: this.enabledBack(viewHistory.currentView),
  936. isHistoryRoot: (viewHistory.currentView.index === 0),
  937. ele: ele
  938. };
  939. },
  940. registerHistory: function(scope) {
  941. scope.$historyId = ionic.Utils.nextUid();
  942. },
  943. createView: function(data) {
  944. var newView = new View();
  945. return newView.initialize(data);
  946. },
  947. getViewById: getViewById,
  948. /**
  949. * @ngdoc method
  950. * @name $ionicHistory#viewHistory
  951. * @description The app's view history data, such as all the views and histories, along
  952. * with how they are ordered and linked together within the navigation stack.
  953. * @returns {object} Returns an object containing the apps view history data.
  954. */
  955. viewHistory: function() {
  956. return viewHistory;
  957. },
  958. /**
  959. * @ngdoc method
  960. * @name $ionicHistory#currentView
  961. * @description The app's current view.
  962. * @returns {object} Returns the current view.
  963. */
  964. currentView: function(view) {
  965. if (arguments.length) {
  966. viewHistory.currentView = view;
  967. }
  968. return viewHistory.currentView;
  969. },
  970. /**
  971. * @ngdoc method
  972. * @name $ionicHistory#currentHistoryId
  973. * @description The ID of the history stack which is the parent container of the current view.
  974. * @returns {string} Returns the current history ID.
  975. */
  976. currentHistoryId: function() {
  977. return viewHistory.currentView ? viewHistory.currentView.historyId : null;
  978. },
  979. /**
  980. * @ngdoc method
  981. * @name $ionicHistory#currentTitle
  982. * @description Gets and sets the current view's title.
  983. * @param {string=} val The title to update the current view with.
  984. * @returns {string} Returns the current view's title.
  985. */
  986. currentTitle: function(val) {
  987. if (viewHistory.currentView) {
  988. if (arguments.length) {
  989. viewHistory.currentView.title = val;
  990. }
  991. return viewHistory.currentView.title;
  992. }
  993. },
  994. /**
  995. * @ngdoc method
  996. * @name $ionicHistory#backView
  997. * @description Returns the view that was before the current view in the history stack.
  998. * If the user navigated from View A to View B, then View A would be the back view, and
  999. * View B would be the current view.
  1000. * @returns {object} Returns the back view.
  1001. */
  1002. backView: function(view) {
  1003. if (arguments.length) {
  1004. viewHistory.backView = view;
  1005. }
  1006. return viewHistory.backView;
  1007. },
  1008. /**
  1009. * @ngdoc method
  1010. * @name $ionicHistory#backTitle
  1011. * @description Gets the back view's title.
  1012. * @returns {string} Returns the back view's title.
  1013. */
  1014. backTitle: function(view) {
  1015. var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;
  1016. return backView && backView.title;
  1017. },
  1018. /**
  1019. * @ngdoc method
  1020. * @name $ionicHistory#forwardView
  1021. * @description Returns the view that was in front of the current view in the history stack.
  1022. * A forward view would exist if the user navigated from View A to View B, then
  1023. * navigated back to View A. At this point then View B would be the forward view, and View
  1024. * A would be the current view.
  1025. * @returns {object} Returns the forward view.
  1026. */
  1027. forwardView: function(view) {
  1028. if (arguments.length) {
  1029. viewHistory.forwardView = view;
  1030. }
  1031. return viewHistory.forwardView;
  1032. },
  1033. /**
  1034. * @ngdoc method
  1035. * @name $ionicHistory#currentStateName
  1036. * @description Returns the current state name.
  1037. * @returns {string}
  1038. */
  1039. currentStateName: function() {
  1040. return ($state && $state.current ? $state.current.name : null);
  1041. },
  1042. isCurrentStateNavView: function(navView) {
  1043. return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
  1044. },
  1045. goToHistoryRoot: function(historyId) {
  1046. if (historyId) {
  1047. var hist = getHistoryById(historyId);
  1048. if (hist && hist.stack.length) {
  1049. if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
  1050. return;
  1051. }
  1052. forcedNav = {
  1053. viewId: hist.stack[0].viewId,
  1054. action: ACTION_MOVE_BACK,
  1055. direction: DIRECTION_BACK
  1056. };
  1057. hist.stack[0].go();
  1058. }
  1059. }
  1060. },
  1061. /**
  1062. * @ngdoc method
  1063. * @name $ionicHistory#goBack
  1064. * @param {number=} backCount Optional negative integer setting how many views to go
  1065. * back. By default it'll go back one view by using the value `-1`. To go back two
  1066. * views you would use `-2`. If the number goes farther back than the number of views
  1067. * in the current history's stack then it'll go to the first view in the current history's
  1068. * stack. If the number is zero or greater then it'll do nothing. It also does not
  1069. * cross history stacks, meaning it can only go as far back as the current history.
  1070. * @description Navigates the app to the back view, if a back view exists.
  1071. */
  1072. goBack: function(backCount) {
  1073. if (isDefined(backCount) && backCount !== -1) {
  1074. if (backCount > -1) return;
  1075. var currentHistory = viewHistory.histories[this.currentHistoryId()];
  1076. var newCursor = currentHistory.cursor + backCount + 1;
  1077. if (newCursor < 1) {
  1078. newCursor = 1;
  1079. }
  1080. currentHistory.cursor = newCursor;
  1081. setNavViews(currentHistory.stack[newCursor].viewId);
  1082. var cursor = newCursor - 1;
  1083. var clearStateIds = [];
  1084. var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
  1085. while (fwdView) {
  1086. clearStateIds.push(fwdView.stateId || fwdView.viewId);
  1087. cursor++;
  1088. if (cursor >= currentHistory.stack.length) break;
  1089. fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
  1090. }
  1091. var self = this;
  1092. if (clearStateIds.length) {
  1093. $timeout(function() {
  1094. self.clearCache(clearStateIds);
  1095. }, 300);
  1096. }
  1097. }
  1098. viewHistory.backView && viewHistory.backView.go();
  1099. },
  1100. /**
  1101. * @ngdoc method
  1102. * @name $ionicHistory#removeBackView
  1103. * @description Remove the previous view from the history completely, including the
  1104. * cached element and scope (if they exist).
  1105. */
  1106. removeBackView: function() {
  1107. var self = this;
  1108. var currentHistory = viewHistory.histories[this.currentHistoryId()];
  1109. var currentCursor = currentHistory.cursor;
  1110. var currentView = currentHistory.stack[currentCursor];
  1111. var backView = currentHistory.stack[currentCursor - 1];
  1112. var replacementView = currentHistory.stack[currentCursor - 2];
  1113. // fail if we dont have enough views in the history
  1114. if (!backView || !replacementView) {
  1115. return;
  1116. }
  1117. // remove the old backView and the cached element/scope
  1118. currentHistory.stack.splice(currentCursor - 1, 1);
  1119. self.clearCache([backView.viewId]);
  1120. // make the replacementView and currentView point to each other (bypass the old backView)
  1121. currentView.backViewId = replacementView.viewId;
  1122. currentView.index = currentView.index - 1;
  1123. replacementView.forwardViewId = currentView.viewId;
  1124. // update the cursor and set new backView
  1125. viewHistory.backView = replacementView;
  1126. currentHistory.currentCursor += -1;
  1127. },
  1128. enabledBack: function(view) {
  1129. var backView = getBackView(view);
  1130. return !!(backView && backView.historyId === view.historyId);
  1131. },
  1132. /**
  1133. * @ngdoc method
  1134. * @name $ionicHistory#clearHistory
  1135. * @description Clears out the app's entire history, except for the current view.
  1136. */
  1137. clearHistory: function() {
  1138. var
  1139. histories = viewHistory.histories,
  1140. currentView = viewHistory.currentView;
  1141. if (histories) {
  1142. for (var historyId in histories) {
  1143. if (histories[historyId].stack) {
  1144. histories[historyId].stack = [];
  1145. histories[historyId].cursor = -1;
  1146. }
  1147. if (currentView && currentView.historyId === historyId) {
  1148. currentView.backViewId = currentView.forwardViewId = null;
  1149. histories[historyId].stack.push(currentView);
  1150. } else if (histories[historyId].destroy) {
  1151. histories[historyId].destroy();
  1152. }
  1153. }
  1154. }
  1155. for (var viewId in viewHistory.views) {
  1156. if (viewId !== currentView.viewId) {
  1157. delete viewHistory.views[viewId];
  1158. }
  1159. }
  1160. if (currentView) {
  1161. setNavViews(currentView.viewId);
  1162. }
  1163. },
  1164. /**
  1165. * @ngdoc method
  1166. * @name $ionicHistory#clearCache
  1167. * @return promise
  1168. * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
  1169. * This both removes the view element from the DOM, and destroy it's scope.
  1170. */
  1171. clearCache: function(stateIds) {
  1172. return $timeout(function() {
  1173. $ionicNavViewDelegate._instances.forEach(function(instance) {
  1174. instance.clearCache(stateIds);
  1175. });
  1176. });
  1177. },
  1178. /**
  1179. * @ngdoc method
  1180. * @name $ionicHistory#nextViewOptions
  1181. * @description Sets options for the next view. This method can be useful to override
  1182. * certain view/transition defaults right before a view transition happens. For example,
  1183. * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
  1184. * an animated view transition does not happen when a side menu is open, and also sets
  1185. * the next view as the root of its history stack. After the transition these options
  1186. * are set back to null.
  1187. *
  1188. * Available options:
  1189. *
  1190. * * `disableAnimate`: Do not animate the next transition.
  1191. * * `disableBack`: The next view should forget its back view, and set it to null.
  1192. * * `historyRoot`: The next view should become the root view in its history stack.
  1193. *
  1194. * ```js
  1195. * $ionicHistory.nextViewOptions({
  1196. * disableAnimate: true,
  1197. * disableBack: true
  1198. * });
  1199. * ```
  1200. */
  1201. nextViewOptions: function(opts) {
  1202. deregisterStateChangeListener && deregisterStateChangeListener();
  1203. if (arguments.length) {
  1204. $timeout.cancel(nextViewExpireTimer);
  1205. if (opts === null) {
  1206. nextViewOptions = opts;
  1207. } else {
  1208. nextViewOptions = nextViewOptions || {};
  1209. extend(nextViewOptions, opts);
  1210. if (nextViewOptions.expire) {
  1211. deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() {
  1212. nextViewExpireTimer = $timeout(function() {
  1213. nextViewOptions = null;
  1214. }, nextViewOptions.expire);
  1215. });
  1216. }
  1217. }
  1218. }
  1219. return nextViewOptions;
  1220. },
  1221. isAbstractEle: function(ele, viewLocals) {
  1222. if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {
  1223. return true;
  1224. }
  1225. return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
  1226. },
  1227. isActiveScope: function(scope) {
  1228. if (!scope) return false;
  1229. var climbScope = scope;
  1230. var currentHistoryId = this.currentHistoryId();
  1231. var foundHistoryId;
  1232. while (climbScope) {
  1233. if (climbScope.$$disconnected) {
  1234. return false;
  1235. }
  1236. if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
  1237. foundHistoryId = true;
  1238. }
  1239. if (currentHistoryId) {
  1240. if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
  1241. return true;
  1242. }
  1243. if (climbScope.hasOwnProperty('$activeHistoryId')) {
  1244. if (currentHistoryId == climbScope.$activeHistoryId) {
  1245. if (climbScope.hasOwnProperty('$historyId')) {
  1246. return true;
  1247. }
  1248. if (!foundHistoryId) {
  1249. return true;
  1250. }
  1251. }
  1252. }
  1253. }
  1254. if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
  1255. foundHistoryId = false;
  1256. }
  1257. climbScope = climbScope.$parent;
  1258. }
  1259. return currentHistoryId ? currentHistoryId == 'root' : true;
  1260. }
  1261. };
  1262. function isAbstractTag(ele) {
  1263. return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
  1264. }
  1265. function canSwipeBack(ele, viewLocals) {
  1266. if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {
  1267. return false;
  1268. }
  1269. if (ele && ele.attr('can-swipe-back') === 'false') {
  1270. return false;
  1271. }
  1272. var eleChild = ele.find('ion-view');
  1273. if (eleChild && eleChild.attr('can-swipe-back') === 'false') {
  1274. return false;
  1275. }
  1276. return true;
  1277. }
  1278. }])
  1279. .run([
  1280. '$rootScope',
  1281. '$state',
  1282. '$location',
  1283. '$document',
  1284. '$ionicPlatform',
  1285. '$ionicHistory',
  1286. 'IONIC_BACK_PRIORITY',
  1287. function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {
  1288. // always reset the keyboard state when change stage
  1289. $rootScope.$on('$ionicView.beforeEnter', function() {
  1290. ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
  1291. });
  1292. $rootScope.$on('$ionicHistory.change', function(e, data) {
  1293. if (!data) return null;
  1294. var viewHistory = $ionicHistory.viewHistory();
  1295. var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
  1296. if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
  1297. // the history they're going to already exists
  1298. // go to it's last view in its stack
  1299. var view = hist.stack[ hist.cursor ];
  1300. return view.go(data);
  1301. }
  1302. // this history does not have a URL, but it does have a uiSref
  1303. // figure out its URL from the uiSref
  1304. if (!data.url && data.uiSref) {
  1305. data.url = $state.href(data.uiSref);
  1306. }
  1307. if (data.url) {
  1308. // don't let it start with a #, messes with $location.url()
  1309. if (data.url.indexOf('#') === 0) {
  1310. data.url = data.url.replace('#', '');
  1311. }
  1312. if (data.url !== $location.url()) {
  1313. // we've got a good URL, ready GO!
  1314. $location.url(data.url);
  1315. }
  1316. }
  1317. });
  1318. $rootScope.$ionicGoBack = function(backCount) {
  1319. $ionicHistory.goBack(backCount);
  1320. };
  1321. // Set the document title when a new view is shown
  1322. $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
  1323. if (data && data.title) {
  1324. $document[0].title = data.title;
  1325. }
  1326. });
  1327. // Triggered when devices with a hardware back button (Android) is clicked by the user
  1328. // This is a Cordova/Phonegap platform specifc method
  1329. function onHardwareBackButton(e) {
  1330. var backView = $ionicHistory.backView();
  1331. if (backView) {
  1332. // there is a back view, go to it
  1333. backView.go();
  1334. } else {
  1335. // there is no back view, so close the app instead
  1336. ionic.Platform.exitApp();
  1337. }
  1338. e.preventDefault();
  1339. return false;
  1340. }
  1341. $ionicPlatform.registerBackButtonAction(
  1342. onHardwareBackButton,
  1343. IONIC_BACK_PRIORITY.view
  1344. );
  1345. }]);
  1346. /**
  1347. * @ngdoc provider
  1348. * @name $ionicConfigProvider
  1349. * @module ionic
  1350. * @description
  1351. * Ionic automatically takes platform configurations into account to adjust things like what
  1352. * transition style to use and whether tab icons should show on the top or bottom. For example,
  1353. * iOS will move forward by transitioning the entering view from right to center and the leaving
  1354. * view from center to left. However, Android will transition with the entering view going from
  1355. * bottom to center, covering the previous view, which remains stationary. It should be noted
  1356. * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
  1357. * developing on a desktop browser, it's going to take on iOS default configs.
  1358. *
  1359. * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
  1360. * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
  1361. * phase and within the app itself.
  1362. *
  1363. * By default, all base config variables are set to `'platform'`, which means it'll take on the
  1364. * default config of the platform on which it's running. Config variables can be set at this
  1365. * level so all platforms follow the same setting, rather than its platform config.
  1366. * The following code would set the same config variable for all platforms:
  1367. *
  1368. * ```js
  1369. * $ionicConfigProvider.views.maxCache(10);
  1370. * ```
  1371. *
  1372. * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
  1373. * property. The config below would only apply to Android devices.
  1374. *
  1375. * ```js
  1376. * $ionicConfigProvider.platform.android.views.maxCache(5);
  1377. * ```
  1378. *
  1379. * @usage
  1380. * ```js
  1381. * var myApp = angular.module('reallyCoolApp', ['ionic']);
  1382. *
  1383. * myApp.config(function($ionicConfigProvider) {
  1384. * $ionicConfigProvider.views.maxCache(5);
  1385. *
  1386. * // note that you can also chain configs
  1387. * $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
  1388. * });
  1389. * ```
  1390. */
  1391. /**
  1392. * @ngdoc method
  1393. * @name $ionicConfigProvider#views.transition
  1394. * @description Animation style when transitioning between views. Default `platform`.
  1395. *
  1396. * @param {string} transition Which style of view transitioning to use.
  1397. *
  1398. * * `platform`: Dynamically choose the correct transition style depending on the platform
  1399. * the app is running from. If the platform is not `ios` or `android` then it will default
  1400. * to `ios`.
  1401. * * `ios`: iOS style transition.
  1402. * * `android`: Android style transition.
  1403. * * `none`: Do not perform animated transitions.
  1404. *
  1405. * @returns {string} value
  1406. */
  1407. /**
  1408. * @ngdoc method
  1409. * @name $ionicConfigProvider#views.maxCache
  1410. * @description Maximum number of view elements to cache in the DOM. When the max number is
  1411. * exceeded, the view with the longest time period since it was accessed is removed. Views that
  1412. * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
  1413. * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
  1414. * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
  1415. * each view transition, and the next time the same view is shown, it will have to re-compile,
  1416. * attach to the DOM, and link the element again. This disables caching, in effect.
  1417. * @param {number} maxNumber Maximum number of views to retain. Default `10`.
  1418. * @returns {number} How many views Ionic will hold onto until the a view is removed.
  1419. */
  1420. /**
  1421. * @ngdoc method
  1422. * @name $ionicConfigProvider#views.forwardCache
  1423. * @description By default, when navigating, views that were recently visited are cached, and
  1424. * the same instance data and DOM elements are referenced when navigating back. However, when
  1425. * navigating back in the history, the "forward" views are removed from the cache. If you
  1426. * navigate forward to the same view again, it'll create a new DOM element and controller
  1427. * instance. Basically, any forward views are reset each time. Set this config to `true` to have
  1428. * forward views cached and not reset on each load.
  1429. * @param {boolean} value
  1430. * @returns {boolean}
  1431. */
  1432. /**
  1433. * @ngdoc method
  1434. * @name $ionicConfigProvider#views.swipeBackEnabled
  1435. * @description By default on iOS devices, swipe to go back functionality is enabled by default.
  1436. * This method can be used to disable it globally, or on a per-view basis.
  1437. * Note: This functionality is only supported on iOS.
  1438. * @param {boolean} value
  1439. * @returns {boolean}
  1440. */
  1441. /**
  1442. * @ngdoc method
  1443. * @name $ionicConfigProvider#scrolling.jsScrolling
  1444. * @description Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to
  1445. * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`.
  1446. * @param {boolean} value Defaults to `false` as of Ionic 1.2
  1447. * @returns {boolean}
  1448. */
  1449. /**
  1450. * @ngdoc method
  1451. * @name $ionicConfigProvider#backButton.icon
  1452. * @description Back button icon.
  1453. * @param {string} value
  1454. * @returns {string}
  1455. */
  1456. /**
  1457. * @ngdoc method
  1458. * @name $ionicConfigProvider#backButton.text
  1459. * @description Back button text.
  1460. * @param {string} value Defaults to `Back`.
  1461. * @returns {string}
  1462. */
  1463. /**
  1464. * @ngdoc method
  1465. * @name $ionicConfigProvider#backButton.previousTitleText
  1466. * @description If the previous title text should become the back button text. This
  1467. * is the default for iOS.
  1468. * @param {boolean} value
  1469. * @returns {boolean}
  1470. */
  1471. /**
  1472. * @ngdoc method
  1473. * @name $ionicConfigProvider#form.checkbox
  1474. * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
  1475. * @param {string} value
  1476. * @returns {string}
  1477. */
  1478. /**
  1479. * @ngdoc method
  1480. * @name $ionicConfigProvider#form.toggle
  1481. * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
  1482. * @param {string} value
  1483. * @returns {string}
  1484. */
  1485. /**
  1486. * @ngdoc method
  1487. * @name $ionicConfigProvider#spinner.icon
  1488. * @description Default spinner icon to use.
  1489. * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`,
  1490. * `dots`, `lines`, `ripple`, or `spiral`.
  1491. * @returns {string}
  1492. */
  1493. /**
  1494. * @ngdoc method
  1495. * @name $ionicConfigProvider#tabs.style
  1496. * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
  1497. * @param {string} value Available values include `striped` and `standard`.
  1498. * @returns {string}
  1499. */
  1500. /**
  1501. * @ngdoc method
  1502. * @name $ionicConfigProvider#tabs.position
  1503. * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
  1504. * @param {string} value Available values include `top` and `bottom`.
  1505. * @returns {string}
  1506. */
  1507. /**
  1508. * @ngdoc method
  1509. * @name $ionicConfigProvider#templates.maxPrefetch
  1510. * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
  1511. * $stateProvider.state. If set to `0`, the user will have to wait
  1512. * for a template to be fetched the first time when navigating to a new page. Default `30`.
  1513. * @param {integer} value Max number of template to prefetch from the templateUrls defined in
  1514. * `$stateProvider.state()`.
  1515. * @returns {integer}
  1516. */
  1517. /**
  1518. * @ngdoc method
  1519. * @name $ionicConfigProvider#navBar.alignTitle
  1520. * @description Which side of the navBar to align the title. Default `center`.
  1521. *
  1522. * @param {string} value side of the navBar to align the title.
  1523. *
  1524. * * `platform`: Dynamically choose the correct title style depending on the platform
  1525. * the app is running from. If the platform is `ios`, it will default to `center`.
  1526. * If the platform is `android`, it will default to `left`. If the platform is not
  1527. * `ios` or `android`, it will default to `center`.
  1528. *
  1529. * * `left`: Left align the title in the navBar
  1530. * * `center`: Center align the title in the navBar
  1531. * * `right`: Right align the title in the navBar.
  1532. *
  1533. * @returns {string} value
  1534. */
  1535. /**
  1536. * @ngdoc method
  1537. * @name $ionicConfigProvider#navBar.positionPrimaryButtons
  1538. * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
  1539. *
  1540. * @param {string} value side of the navBar to align the primary navBar buttons.
  1541. *
  1542. * * `platform`: Dynamically choose the correct title style depending on the platform
  1543. * the app is running from. If the platform is `ios`, it will default to `left`.
  1544. * If the platform is `android`, it will default to `right`. If the platform is not
  1545. * `ios` or `android`, it will default to `left`.
  1546. *
  1547. * * `left`: Left align the primary navBar buttons in the navBar
  1548. * * `right`: Right align the primary navBar buttons in the navBar.
  1549. *
  1550. * @returns {string} value
  1551. */
  1552. /**
  1553. * @ngdoc method
  1554. * @name $ionicConfigProvider#navBar.positionSecondaryButtons
  1555. * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
  1556. *
  1557. * @param {string} value side of the navBar to align the secondary navBar buttons.
  1558. *
  1559. * * `platform`: Dynamically choose the correct title style depending on the platform
  1560. * the app is running from. If the platform is `ios`, it will default to `right`.
  1561. * If the platform is `android`, it will default to `right`. If the platform is not
  1562. * `ios` or `android`, it will default to `right`.
  1563. *
  1564. * * `left`: Left align the secondary navBar buttons in the navBar
  1565. * * `right`: Right align the secondary navBar buttons in the navBar.
  1566. *
  1567. * @returns {string} value
  1568. */
  1569. IonicModule
  1570. .provider('$ionicConfig', function() {
  1571. var provider = this;
  1572. provider.platform = {};
  1573. var PLATFORM = 'platform';
  1574. var configProperties = {
  1575. views: {
  1576. maxCache: PLATFORM,
  1577. forwardCache: PLATFORM,
  1578. transition: PLATFORM,
  1579. swipeBackEnabled: PLATFORM,
  1580. swipeBackHitWidth: PLATFORM
  1581. },
  1582. navBar: {
  1583. alignTitle: PLATFORM,
  1584. positionPrimaryButtons: PLATFORM,
  1585. positionSecondaryButtons: PLATFORM,
  1586. transition: PLATFORM
  1587. },
  1588. backButton: {
  1589. icon: PLATFORM,
  1590. text: PLATFORM,
  1591. previousTitleText: PLATFORM
  1592. },
  1593. form: {
  1594. checkbox: PLATFORM,
  1595. toggle: PLATFORM
  1596. },
  1597. scrolling: {
  1598. jsScrolling: PLATFORM
  1599. },
  1600. spinner: {
  1601. icon: PLATFORM
  1602. },
  1603. tabs: {
  1604. style: PLATFORM,
  1605. position: PLATFORM
  1606. },
  1607. templates: {
  1608. maxPrefetch: PLATFORM
  1609. },
  1610. platform: {}
  1611. };
  1612. createConfig(configProperties, provider, '');
  1613. // Default
  1614. // -------------------------
  1615. setPlatformConfig('default', {
  1616. views: {
  1617. maxCache: 10,
  1618. forwardCache: false,
  1619. transition: 'ios',
  1620. swipeBackEnabled: true,
  1621. swipeBackHitWidth: 45
  1622. },
  1623. navBar: {
  1624. alignTitle: 'center',
  1625. positionPrimaryButtons: 'left',
  1626. positionSecondaryButtons: 'right',
  1627. transition: 'view'
  1628. },
  1629. backButton: {
  1630. icon: 'ion-ios-arrow-back',
  1631. text: 'Back',
  1632. previousTitleText: true
  1633. },
  1634. form: {
  1635. checkbox: 'circle',
  1636. toggle: 'large'
  1637. },
  1638. scrolling: {
  1639. jsScrolling: true
  1640. },
  1641. spinner: {
  1642. icon: 'ios'
  1643. },
  1644. tabs: {
  1645. style: 'standard',
  1646. position: 'bottom'
  1647. },
  1648. templates: {
  1649. maxPrefetch: 30
  1650. }
  1651. });
  1652. // iOS (it is the default already)
  1653. // -------------------------
  1654. setPlatformConfig('ios', {});
  1655. // Android
  1656. // -------------------------
  1657. setPlatformConfig('android', {
  1658. views: {
  1659. transition: 'android',
  1660. swipeBackEnabled: false
  1661. },
  1662. navBar: {
  1663. alignTitle: 'left',
  1664. positionPrimaryButtons: 'right',
  1665. positionSecondaryButtons: 'right'
  1666. },
  1667. backButton: {
  1668. icon: 'ion-android-arrow-back',
  1669. text: false,
  1670. previousTitleText: false
  1671. },
  1672. form: {
  1673. checkbox: 'square',
  1674. toggle: 'small'
  1675. },
  1676. spinner: {
  1677. icon: 'android'
  1678. },
  1679. tabs: {
  1680. style: 'striped',
  1681. position: 'top'
  1682. },
  1683. scrolling: {
  1684. jsScrolling: false
  1685. }
  1686. });
  1687. // Windows Phone
  1688. // -------------------------
  1689. setPlatformConfig('windowsphone', {
  1690. //scrolling: {
  1691. // jsScrolling: false
  1692. //}
  1693. spinner: {
  1694. icon: 'android'
  1695. }
  1696. });
  1697. provider.transitions = {
  1698. views: {},
  1699. navBar: {}
  1700. };
  1701. // iOS Transitions
  1702. // -----------------------
  1703. provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
  1704. function setStyles(ele, opacity, x, boxShadowOpacity) {
  1705. var css = {};
  1706. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
  1707. css.opacity = opacity;
  1708. if (boxShadowOpacity > -1) {
  1709. css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
  1710. }
  1711. css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
  1712. ionic.DomUtil.cachedStyles(ele, css);
  1713. }
  1714. var d = {
  1715. run: function(step) {
  1716. if (direction == 'forward') {
  1717. setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
  1718. setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
  1719. } else if (direction == 'back') {
  1720. setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
  1721. setStyles(leavingEle, 1, step * 100, 1 - step);
  1722. } else {
  1723. // swap, enter, exit
  1724. setStyles(enteringEle, 1, 0, -1);
  1725. setStyles(leavingEle, 0, 0, -1);
  1726. }
  1727. },
  1728. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1729. };
  1730. return d;
  1731. };
  1732. provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  1733. function setStyles(ctrl, opacity, titleX, backTextX) {
  1734. var css = {};
  1735. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
  1736. css.opacity = opacity === 1 ? '' : opacity;
  1737. ctrl.setCss('buttons-left', css);
  1738. ctrl.setCss('buttons-right', css);
  1739. ctrl.setCss('back-button', css);
  1740. css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
  1741. ctrl.setCss('back-text', css);
  1742. css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
  1743. ctrl.setCss('title', css);
  1744. }
  1745. function enter(ctrlA, ctrlB, step) {
  1746. if (!ctrlA || !ctrlB) return;
  1747. var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
  1748. var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
  1749. setStyles(ctrlA, step, titleX, backTextX);
  1750. }
  1751. function leave(ctrlA, ctrlB, step) {
  1752. if (!ctrlA || !ctrlB) return;
  1753. var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
  1754. setStyles(ctrlA, 1 - step, titleX, 0);
  1755. }
  1756. var d = {
  1757. run: function(step) {
  1758. var enteringHeaderCtrl = enteringHeaderBar.controller();
  1759. var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
  1760. if (d.direction == 'back') {
  1761. leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
  1762. enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
  1763. } else {
  1764. enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
  1765. leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
  1766. }
  1767. },
  1768. direction: direction,
  1769. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1770. };
  1771. return d;
  1772. };
  1773. // Android Transitions
  1774. // -----------------------
  1775. provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
  1776. shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
  1777. function setStyles(ele, x, opacity) {
  1778. var css = {};
  1779. css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
  1780. css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
  1781. css.opacity = opacity;
  1782. ionic.DomUtil.cachedStyles(ele, css);
  1783. }
  1784. var d = {
  1785. run: function(step) {
  1786. if (direction == 'forward') {
  1787. setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker
  1788. setStyles(leavingEle, step * -100, 1);
  1789. } else if (direction == 'back') {
  1790. setStyles(enteringEle, (1 - step) * -100, 1);
  1791. setStyles(leavingEle, step * 100, 1);
  1792. } else {
  1793. // swap, enter, exit
  1794. setStyles(enteringEle, 0, 1);
  1795. setStyles(leavingEle, 0, 0);
  1796. }
  1797. },
  1798. shouldAnimate: shouldAnimate
  1799. };
  1800. return d;
  1801. };
  1802. provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  1803. function setStyles(ctrl, opacity) {
  1804. if (!ctrl) return;
  1805. var css = {};
  1806. css.opacity = opacity === 1 ? '' : opacity;
  1807. ctrl.setCss('buttons-left', css);
  1808. ctrl.setCss('buttons-right', css);
  1809. ctrl.setCss('back-button', css);
  1810. ctrl.setCss('back-text', css);
  1811. ctrl.setCss('title', css);
  1812. }
  1813. return {
  1814. run: function(step) {
  1815. setStyles(enteringHeaderBar.controller(), step);
  1816. setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
  1817. },
  1818. shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
  1819. };
  1820. };
  1821. // No Transition
  1822. // -----------------------
  1823. provider.transitions.views.none = function(enteringEle, leavingEle) {
  1824. return {
  1825. run: function(step) {
  1826. provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
  1827. },
  1828. shouldAnimate: false
  1829. };
  1830. };
  1831. provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
  1832. return {
  1833. run: function(step) {
  1834. provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
  1835. provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
  1836. },
  1837. shouldAnimate: false
  1838. };
  1839. };
  1840. // private: used to set platform configs
  1841. function setPlatformConfig(platformName, platformConfigs) {
  1842. configProperties.platform[platformName] = platformConfigs;
  1843. provider.platform[platformName] = {};
  1844. addConfig(configProperties, configProperties.platform[platformName]);
  1845. createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
  1846. }
  1847. // private: used to recursively add new platform configs
  1848. function addConfig(configObj, platformObj) {
  1849. for (var n in configObj) {
  1850. if (n != PLATFORM && configObj.hasOwnProperty(n)) {
  1851. if (angular.isObject(configObj[n])) {
  1852. if (!isDefined(platformObj[n])) {
  1853. platformObj[n] = {};
  1854. }
  1855. addConfig(configObj[n], platformObj[n]);
  1856. } else if (!isDefined(platformObj[n])) {
  1857. platformObj[n] = null;
  1858. }
  1859. }
  1860. }
  1861. }
  1862. // private: create methods for each config to get/set
  1863. function createConfig(configObj, providerObj, platformPath) {
  1864. forEach(configObj, function(value, namespace) {
  1865. if (angular.isObject(configObj[namespace])) {
  1866. // recursively drill down the config object so we can create a method for each one
  1867. providerObj[namespace] = {};
  1868. createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
  1869. } else {
  1870. // create a method for the provider/config methods that will be exposed
  1871. providerObj[namespace] = function(newValue) {
  1872. if (arguments.length) {
  1873. configObj[namespace] = newValue;
  1874. return providerObj;
  1875. }
  1876. if (configObj[namespace] == PLATFORM) {
  1877. // if the config is set to 'platform', then get this config's platform value
  1878. var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
  1879. if (platformConfig || platformConfig === false) {
  1880. return platformConfig;
  1881. }
  1882. // didnt find a specific platform config, now try the default
  1883. return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
  1884. }
  1885. return configObj[namespace];
  1886. };
  1887. }
  1888. });
  1889. }
  1890. function stringObj(obj, str) {
  1891. str = str.split(".");
  1892. for (var i = 0; i < str.length; i++) {
  1893. if (obj && isDefined(obj[str[i]])) {
  1894. obj = obj[str[i]];
  1895. } else {
  1896. return null;
  1897. }
  1898. }
  1899. return obj;
  1900. }
  1901. provider.setPlatformConfig = setPlatformConfig;
  1902. // private: Service definition for internal Ionic use
  1903. /**
  1904. * @ngdoc service
  1905. * @name $ionicConfig
  1906. * @module ionic
  1907. * @private
  1908. */
  1909. provider.$get = function() {
  1910. return provider;
  1911. };
  1912. })
  1913. // Fix for URLs in Cordova apps on Windows Phone
  1914. // http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
  1915. // running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
  1916. .config(['$compileProvider', function($compileProvider) {
  1917. $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);
  1918. $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);
  1919. }]);
  1920. var LOADING_TPL =
  1921. '<div class="loading-container">' +
  1922. '<div class="loading">' +
  1923. '</div>' +
  1924. '</div>';
  1925. /**
  1926. * @ngdoc service
  1927. * @name $ionicLoading
  1928. * @module ionic
  1929. * @description
  1930. * An overlay that can be used to indicate activity while blocking user
  1931. * interaction.
  1932. *
  1933. * @usage
  1934. * ```js
  1935. * angular.module('LoadingApp', ['ionic'])
  1936. * .controller('LoadingCtrl', function($scope, $ionicLoading) {
  1937. * $scope.show = function() {
  1938. * $ionicLoading.show({
  1939. * template: 'Loading...'
  1940. * }).then(function(){
  1941. * console.log("The loading indicator is now displayed");
  1942. * });
  1943. * };
  1944. * $scope.hide = function(){
  1945. * $ionicLoading.hide().then(function(){
  1946. * console.log("The loading indicator is now hidden");
  1947. * });
  1948. * };
  1949. * });
  1950. * ```
  1951. */
  1952. /**
  1953. * @ngdoc object
  1954. * @name $ionicLoadingConfig
  1955. * @module ionic
  1956. * @description
  1957. * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
  1958. *
  1959. * @usage
  1960. * ```js
  1961. * var app = angular.module('myApp', ['ionic'])
  1962. * app.constant('$ionicLoadingConfig', {
  1963. * template: 'Default Loading Template...'
  1964. * });
  1965. * app.controller('AppCtrl', function($scope, $ionicLoading) {
  1966. * $scope.showLoading = function() {
  1967. * //options default to values in $ionicLoadingConfig
  1968. * $ionicLoading.show().then(function(){
  1969. * console.log("The loading indicator is now displayed");
  1970. * });
  1971. * };
  1972. * });
  1973. * ```
  1974. */
  1975. IonicModule
  1976. .constant('$ionicLoadingConfig', {
  1977. template: '<ion-spinner></ion-spinner>'
  1978. })
  1979. .factory('$ionicLoading', [
  1980. '$ionicLoadingConfig',
  1981. '$ionicBody',
  1982. '$ionicTemplateLoader',
  1983. '$ionicBackdrop',
  1984. '$timeout',
  1985. '$q',
  1986. '$log',
  1987. '$compile',
  1988. '$ionicPlatform',
  1989. '$rootScope',
  1990. 'IONIC_BACK_PRIORITY',
  1991. function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {
  1992. var loaderInstance;
  1993. //default values
  1994. var deregisterBackAction = noop;
  1995. var deregisterStateListener1 = noop;
  1996. var deregisterStateListener2 = noop;
  1997. var loadingShowDelay = $q.when();
  1998. return {
  1999. /**
  2000. * @ngdoc method
  2001. * @name $ionicLoading#show
  2002. * @description Shows a loading indicator. If the indicator is already shown,
  2003. * it will set the options given and keep the indicator shown.
  2004. * @returns {promise} A promise which is resolved when the loading indicator is presented.
  2005. * @param {object} opts The options for the loading indicator. Available properties:
  2006. * - `{string=}` `template` The html content of the indicator.
  2007. * - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
  2008. * - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
  2009. * - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
  2010. * - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
  2011. * to a new state. Default false.
  2012. * - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
  2013. * - `{number=}` `duration` How many milliseconds to wait until automatically
  2014. * hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
  2015. */
  2016. show: showLoader,
  2017. /**
  2018. * @ngdoc method
  2019. * @name $ionicLoading#hide
  2020. * @description Hides the loading indicator, if shown.
  2021. * @returns {promise} A promise which is resolved when the loading indicator is hidden.
  2022. */
  2023. hide: hideLoader,
  2024. /**
  2025. * @private for testing
  2026. */
  2027. _getLoader: getLoader
  2028. };
  2029. function getLoader() {
  2030. if (!loaderInstance) {
  2031. loaderInstance = $ionicTemplateLoader.compile({
  2032. template: LOADING_TPL,
  2033. appendTo: $ionicBody.get()
  2034. })
  2035. .then(function(self) {
  2036. self.show = function(options) {
  2037. var templatePromise = options.templateUrl ?
  2038. $ionicTemplateLoader.load(options.templateUrl) :
  2039. //options.content: deprecated
  2040. $q.when(options.template || options.content || '');
  2041. self.scope = options.scope || self.scope;
  2042. if (!self.isShown) {
  2043. //options.showBackdrop: deprecated
  2044. self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
  2045. if (self.hasBackdrop) {
  2046. $ionicBackdrop.retain();
  2047. $ionicBackdrop.getElement().addClass('backdrop-loading');
  2048. }
  2049. }
  2050. if (options.duration) {
  2051. $timeout.cancel(self.durationTimeout);
  2052. self.durationTimeout = $timeout(
  2053. angular.bind(self, self.hide),
  2054. +options.duration
  2055. );
  2056. }
  2057. deregisterBackAction();
  2058. //Disable hardware back button while loading
  2059. deregisterBackAction = $ionicPlatform.registerBackButtonAction(
  2060. noop,
  2061. IONIC_BACK_PRIORITY.loading
  2062. );
  2063. templatePromise.then(function(html) {
  2064. if (html) {
  2065. var loading = self.element.children();
  2066. loading.html(html);
  2067. $compile(loading.contents())(self.scope);
  2068. }
  2069. //Don't show until template changes
  2070. if (self.isShown) {
  2071. self.element.addClass('visible');
  2072. ionic.requestAnimationFrame(function() {
  2073. if (self.isShown) {
  2074. self.element.addClass('active');
  2075. $ionicBody.addClass('loading-active');
  2076. }
  2077. });
  2078. }
  2079. });
  2080. self.isShown = true;
  2081. };
  2082. self.hide = function() {
  2083. deregisterBackAction();
  2084. if (self.isShown) {
  2085. if (self.hasBackdrop) {
  2086. $ionicBackdrop.release();
  2087. $ionicBackdrop.getElement().removeClass('backdrop-loading');
  2088. }
  2089. self.element.removeClass('active');
  2090. $ionicBody.removeClass('loading-active');
  2091. self.element.removeClass('visible');
  2092. ionic.requestAnimationFrame(function() {
  2093. !self.isShown && self.element.removeClass('visible');
  2094. });
  2095. }
  2096. $timeout.cancel(self.durationTimeout);
  2097. self.isShown = false;
  2098. var loading = self.element.children();
  2099. loading.html("");
  2100. };
  2101. return self;
  2102. });
  2103. }
  2104. return loaderInstance;
  2105. }
  2106. function showLoader(options) {
  2107. options = extend({}, $ionicLoadingConfig || {}, options || {});
  2108. // use a default delay of 100 to avoid some issues reported on github
  2109. // https://github.com/driftyco/ionic/issues/3717
  2110. var delay = options.delay || options.showDelay || 0;
  2111. deregisterStateListener1();
  2112. deregisterStateListener2();
  2113. if (options.hideOnStateChange) {
  2114. deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);
  2115. deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);
  2116. }
  2117. //If loading.show() was called previously, cancel it and show with our new options
  2118. $timeout.cancel(loadingShowDelay);
  2119. loadingShowDelay = $timeout(noop, delay);
  2120. return loadingShowDelay.then(getLoader).then(function(loader) {
  2121. return loader.show(options);
  2122. });
  2123. }
  2124. function hideLoader() {
  2125. deregisterStateListener1();
  2126. deregisterStateListener2();
  2127. $timeout.cancel(loadingShowDelay);
  2128. return getLoader().then(function(loader) {
  2129. return loader.hide();
  2130. });
  2131. }
  2132. }]);
  2133. /**
  2134. * @ngdoc service
  2135. * @name $ionicModal
  2136. * @module ionic
  2137. * @codepen gblny
  2138. * @description
  2139. *
  2140. * Related: {@link ionic.controller:ionicModal ionicModal controller}.
  2141. *
  2142. * The Modal is a content pane that can go over the user's main view
  2143. * temporarily. Usually used for making a choice or editing an item.
  2144. *
  2145. * Put the content of the modal inside of an `<ion-modal-view>` element.
  2146. *
  2147. * **Notes:**
  2148. * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
  2149. * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
  2150. * called when the modal is removed.
  2151. *
  2152. * - This example assumes your modal is in your main index file or another template file. If it is in its own
  2153. * template file, remove the script tags and call it by file name.
  2154. *
  2155. * @usage
  2156. * ```html
  2157. * <script id="my-modal.html" type="text/ng-template">
  2158. * <ion-modal-view>
  2159. * <ion-header-bar>
  2160. * <h1 class="title">My Modal title</h1>
  2161. * </ion-header-bar>
  2162. * <ion-content>
  2163. * Hello!
  2164. * </ion-content>
  2165. * </ion-modal-view>
  2166. * </script>
  2167. * ```
  2168. * ```js
  2169. * angular.module('testApp', ['ionic'])
  2170. * .controller('MyController', function($scope, $ionicModal) {
  2171. * $ionicModal.fromTemplateUrl('my-modal.html', {
  2172. * scope: $scope,
  2173. * animation: 'slide-in-up'
  2174. * }).then(function(modal) {
  2175. * $scope.modal = modal;
  2176. * });
  2177. * $scope.openModal = function() {
  2178. * $scope.modal.show();
  2179. * };
  2180. * $scope.closeModal = function() {
  2181. * $scope.modal.hide();
  2182. * };
  2183. * // Cleanup the modal when we're done with it!
  2184. * $scope.$on('$destroy', function() {
  2185. * $scope.modal.remove();
  2186. * });
  2187. * // Execute action on hide modal
  2188. * $scope.$on('modal.hidden', function() {
  2189. * // Execute action
  2190. * });
  2191. * // Execute action on remove modal
  2192. * $scope.$on('modal.removed', function() {
  2193. * // Execute action
  2194. * });
  2195. * });
  2196. * ```
  2197. */
  2198. IonicModule
  2199. .factory('$ionicModal', [
  2200. '$rootScope',
  2201. '$ionicBody',
  2202. '$compile',
  2203. '$timeout',
  2204. '$ionicPlatform',
  2205. '$ionicTemplateLoader',
  2206. '$$q',
  2207. '$log',
  2208. '$ionicClickBlock',
  2209. '$window',
  2210. 'IONIC_BACK_PRIORITY',
  2211. function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {
  2212. /**
  2213. * @ngdoc controller
  2214. * @name ionicModal
  2215. * @module ionic
  2216. * @description
  2217. * Instantiated by the {@link ionic.service:$ionicModal} service.
  2218. *
  2219. * Be sure to call [remove()](#remove) when you are done with each modal
  2220. * to clean it up and avoid memory leaks.
  2221. *
  2222. * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
  2223. * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
  2224. * called when the modal is removed.
  2225. */
  2226. var ModalView = ionic.views.Modal.inherit({
  2227. /**
  2228. * @ngdoc method
  2229. * @name ionicModal#initialize
  2230. * @description Creates a new modal controller instance.
  2231. * @param {object} options An options object with the following properties:
  2232. * - `{object=}` `scope` The scope to be a child of.
  2233. * Default: creates a child of $rootScope.
  2234. * - `{string=}` `animation` The animation to show & hide with.
  2235. * Default: 'slide-in-up'
  2236. * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
  2237. * the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show
  2238. * on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).
  2239. * Default: false.
  2240. * - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
  2241. * Default: true.
  2242. * - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
  2243. * back button on Android and similar devices. Default: true.
  2244. */
  2245. initialize: function(opts) {
  2246. ionic.views.Modal.prototype.initialize.call(this, opts);
  2247. this.animation = opts.animation || 'slide-in-up';
  2248. },
  2249. /**
  2250. * @ngdoc method
  2251. * @name ionicModal#show
  2252. * @description Show this modal instance.
  2253. * @returns {promise} A promise which is resolved when the modal is finished animating in.
  2254. */
  2255. show: function(target) {
  2256. var self = this;
  2257. if (self.scope.$$destroyed) {
  2258. $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
  2259. return $$q.when();
  2260. }
  2261. // on iOS, clicks will sometimes bleed through/ghost click on underlying
  2262. // elements
  2263. $ionicClickBlock.show(600);
  2264. stack.add(self);
  2265. var modalEl = jqLite(self.modalEl);
  2266. self.el.classList.remove('hide');
  2267. $timeout(function() {
  2268. if (!self._isShown) return;
  2269. $ionicBody.addClass(self.viewType + '-open');
  2270. }, 400, false);
  2271. if (!self.el.parentElement) {
  2272. modalEl.addClass(self.animation);
  2273. $ionicBody.append(self.el);
  2274. }
  2275. // if modal was closed while the keyboard was up, reset scroll view on
  2276. // next show since we can only resize it once it's visible
  2277. var scrollCtrl = modalEl.data('$$ionicScrollController');
  2278. scrollCtrl && scrollCtrl.resize();
  2279. if (target && self.positionView) {
  2280. self.positionView(target, modalEl);
  2281. // set up a listener for in case the window size changes
  2282. self._onWindowResize = function() {
  2283. if (self._isShown) self.positionView(target, modalEl);
  2284. };
  2285. ionic.on('resize', self._onWindowResize, window);
  2286. }
  2287. modalEl.addClass('ng-enter active')
  2288. .removeClass('ng-leave ng-leave-active');
  2289. self._isShown = true;
  2290. self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
  2291. self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,
  2292. IONIC_BACK_PRIORITY.modal
  2293. );
  2294. ionic.views.Modal.prototype.show.call(self);
  2295. $timeout(function() {
  2296. if (!self._isShown) return;
  2297. modalEl.addClass('ng-enter-active');
  2298. ionic.trigger('resize');
  2299. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
  2300. self.el.classList.add('active');
  2301. self.scope.$broadcast('$ionicHeader.align');
  2302. self.scope.$broadcast('$ionicFooter.align');
  2303. self.scope.$broadcast('$ionic.modalPresented');
  2304. }, 20);
  2305. return $timeout(function() {
  2306. if (!self._isShown) return;
  2307. self.$el.on('touchmove', function(e) {
  2308. //Don't allow scrolling while open by dragging on backdrop
  2309. var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll');
  2310. if (!isInScroll) {
  2311. e.preventDefault();
  2312. }
  2313. });
  2314. //After animating in, allow hide on backdrop click
  2315. self.$el.on('click', function(e) {
  2316. if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) {
  2317. self.hide();
  2318. }
  2319. });
  2320. }, 400);
  2321. },
  2322. /**
  2323. * @ngdoc method
  2324. * @name ionicModal#hide
  2325. * @description Hide this modal instance.
  2326. * @returns {promise} A promise which is resolved when the modal is finished animating out.
  2327. */
  2328. hide: function() {
  2329. var self = this;
  2330. var modalEl = jqLite(self.modalEl);
  2331. // on iOS, clicks will sometimes bleed through/ghost click on underlying
  2332. // elements
  2333. $ionicClickBlock.show(600);
  2334. stack.remove(self);
  2335. self.el.classList.remove('active');
  2336. modalEl.addClass('ng-leave');
  2337. $timeout(function() {
  2338. if (self._isShown) return;
  2339. modalEl.addClass('ng-leave-active')
  2340. .removeClass('ng-enter ng-enter-active active');
  2341. self.scope.$broadcast('$ionic.modalRemoved');
  2342. }, 20, false);
  2343. self.$el.off('click');
  2344. self._isShown = false;
  2345. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
  2346. self._deregisterBackButton && self._deregisterBackButton();
  2347. ionic.views.Modal.prototype.hide.call(self);
  2348. // clean up event listeners
  2349. if (self.positionView) {
  2350. ionic.off('resize', self._onWindowResize, window);
  2351. }
  2352. return $timeout(function() {
  2353. $ionicBody.removeClass(self.viewType + '-open');
  2354. self.el.classList.add('hide');
  2355. }, self.hideDelay || 320);
  2356. },
  2357. /**
  2358. * @ngdoc method
  2359. * @name ionicModal#remove
  2360. * @description Remove this modal instance from the DOM and clean up.
  2361. * @returns {promise} A promise which is resolved when the modal is finished animating out.
  2362. */
  2363. remove: function() {
  2364. var self = this,
  2365. deferred, promise;
  2366. self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
  2367. // Only hide modal, when it is actually shown!
  2368. // The hide function shows a click-block-div for a split second, because on iOS,
  2369. // clicks will sometimes bleed through/ghost click on underlying elements.
  2370. // However, this will make the app unresponsive for short amount of time.
  2371. // We don't want that, if the modal window is already hidden.
  2372. if (self._isShown) {
  2373. promise = self.hide();
  2374. } else {
  2375. deferred = $$q.defer();
  2376. deferred.resolve();
  2377. promise = deferred.promise;
  2378. }
  2379. return promise.then(function() {
  2380. self.scope.$destroy();
  2381. self.$el.remove();
  2382. });
  2383. },
  2384. /**
  2385. * @ngdoc method
  2386. * @name ionicModal#isShown
  2387. * @returns boolean Whether this modal is currently shown.
  2388. */
  2389. isShown: function() {
  2390. return !!this._isShown;
  2391. }
  2392. });
  2393. var createModal = function(templateString, options) {
  2394. // Create a new scope for the modal
  2395. var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
  2396. options.viewType = options.viewType || 'modal';
  2397. extend(scope, {
  2398. $hasHeader: false,
  2399. $hasSubheader: false,
  2400. $hasFooter: false,
  2401. $hasSubfooter: false,
  2402. $hasTabs: false,
  2403. $hasTabsTop: false
  2404. });
  2405. // Compile the template
  2406. var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
  2407. options.$el = element;
  2408. options.el = element[0];
  2409. options.modalEl = options.el.querySelector('.' + options.viewType);
  2410. var modal = new ModalView(options);
  2411. modal.scope = scope;
  2412. // If this wasn't a defined scope, we can assign the viewType to the isolated scope
  2413. // we created
  2414. if (!options.scope) {
  2415. scope[ options.viewType ] = modal;
  2416. }
  2417. return modal;
  2418. };
  2419. var modalStack = [];
  2420. var stack = {
  2421. add: function(modal) {
  2422. modalStack.push(modal);
  2423. },
  2424. remove: function(modal) {
  2425. var index = modalStack.indexOf(modal);
  2426. if (index > -1 && index < modalStack.length) {
  2427. modalStack.splice(index, 1);
  2428. }
  2429. },
  2430. isHighest: function(modal) {
  2431. var index = modalStack.indexOf(modal);
  2432. return (index > -1 && index === modalStack.length - 1);
  2433. }
  2434. };
  2435. return {
  2436. /**
  2437. * @ngdoc method
  2438. * @name $ionicModal#fromTemplate
  2439. * @param {string} templateString The template string to use as the modal's
  2440. * content.
  2441. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
  2442. * @returns {object} An instance of an {@link ionic.controller:ionicModal}
  2443. * controller.
  2444. */
  2445. fromTemplate: function(templateString, options) {
  2446. var modal = createModal(templateString, options || {});
  2447. return modal;
  2448. },
  2449. /**
  2450. * @ngdoc method
  2451. * @name $ionicModal#fromTemplateUrl
  2452. * @param {string} templateUrl The url to load the template from.
  2453. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
  2454. * options object.
  2455. * @returns {promise} A promise that will be resolved with an instance of
  2456. * an {@link ionic.controller:ionicModal} controller.
  2457. */
  2458. fromTemplateUrl: function(url, options, _) {
  2459. var cb;
  2460. //Deprecated: allow a callback as second parameter. Now we return a promise.
  2461. if (angular.isFunction(options)) {
  2462. cb = options;
  2463. options = _;
  2464. }
  2465. return $ionicTemplateLoader.load(url).then(function(templateString) {
  2466. var modal = createModal(templateString, options || {});
  2467. cb && cb(modal);
  2468. return modal;
  2469. });
  2470. },
  2471. stack: stack
  2472. };
  2473. }]);
  2474. /**
  2475. * @ngdoc service
  2476. * @name $ionicNavBarDelegate
  2477. * @module ionic
  2478. * @description
  2479. * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
  2480. *
  2481. * @usage
  2482. *
  2483. * ```html
  2484. * <body ng-controller="MyCtrl">
  2485. * <ion-nav-bar>
  2486. * <button ng-click="setNavTitle('banana')">
  2487. * Set title to banana!
  2488. * </button>
  2489. * </ion-nav-bar>
  2490. * </body>
  2491. * ```
  2492. * ```js
  2493. * function MyCtrl($scope, $ionicNavBarDelegate) {
  2494. * $scope.setNavTitle = function(title) {
  2495. * $ionicNavBarDelegate.title(title);
  2496. * }
  2497. * }
  2498. * ```
  2499. */
  2500. IonicModule
  2501. .service('$ionicNavBarDelegate', ionic.DelegateService([
  2502. /**
  2503. * @ngdoc method
  2504. * @name $ionicNavBarDelegate#align
  2505. * @description Aligns the title with the buttons in a given direction.
  2506. * @param {string=} direction The direction to the align the title text towards.
  2507. * Available: 'left', 'right', 'center'. Default: 'center'.
  2508. */
  2509. 'align',
  2510. /**
  2511. * @ngdoc method
  2512. * @name $ionicNavBarDelegate#showBackButton
  2513. * @description
  2514. * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
  2515. * (if it exists and there is a previous view that can be navigated to).
  2516. * @param {boolean=} show Whether to show the back button.
  2517. * @returns {boolean} Whether the back button is shown.
  2518. */
  2519. 'showBackButton',
  2520. /**
  2521. * @ngdoc method
  2522. * @name $ionicNavBarDelegate#showBar
  2523. * @description
  2524. * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
  2525. * @param {boolean} show Whether to show the bar.
  2526. * @returns {boolean} Whether the bar is shown.
  2527. */
  2528. 'showBar',
  2529. /**
  2530. * @ngdoc method
  2531. * @name $ionicNavBarDelegate#title
  2532. * @description
  2533. * Set the title for the {@link ionic.directive:ionNavBar}.
  2534. * @param {string} title The new title to show.
  2535. */
  2536. 'title',
  2537. // DEPRECATED, as of v1.0.0-beta14 -------
  2538. 'changeTitle',
  2539. 'setTitle',
  2540. 'getTitle',
  2541. 'back',
  2542. 'getPreviousTitle'
  2543. // END DEPRECATED -------
  2544. ]));
  2545. IonicModule
  2546. .service('$ionicNavViewDelegate', ionic.DelegateService([
  2547. 'clearCache'
  2548. ]));
  2549. /**
  2550. * @ngdoc service
  2551. * @name $ionicPlatform
  2552. * @module ionic
  2553. * @description
  2554. * An angular abstraction of {@link ionic.utility:ionic.Platform}.
  2555. *
  2556. * Used to detect the current platform, as well as do things like override the
  2557. * Android back button in PhoneGap/Cordova.
  2558. */
  2559. IonicModule
  2560. .constant('IONIC_BACK_PRIORITY', {
  2561. view: 100,
  2562. sideMenu: 150,
  2563. modal: 200,
  2564. actionSheet: 300,
  2565. popup: 400,
  2566. loading: 500
  2567. })
  2568. .provider('$ionicPlatform', function() {
  2569. return {
  2570. $get: ['$q', '$ionicScrollDelegate', function($q, $ionicScrollDelegate) {
  2571. var self = {
  2572. /**
  2573. * @ngdoc method
  2574. * @name $ionicPlatform#onHardwareBackButton
  2575. * @description
  2576. * Some platforms have a hardware back button, so this is one way to
  2577. * bind to it.
  2578. * @param {function} callback the callback to trigger when this event occurs
  2579. */
  2580. onHardwareBackButton: function(cb) {
  2581. ionic.Platform.ready(function() {
  2582. document.addEventListener('backbutton', cb, false);
  2583. });
  2584. },
  2585. /**
  2586. * @ngdoc method
  2587. * @name $ionicPlatform#offHardwareBackButton
  2588. * @description
  2589. * Remove an event listener for the backbutton.
  2590. * @param {function} callback The listener function that was
  2591. * originally bound.
  2592. */
  2593. offHardwareBackButton: function(fn) {
  2594. ionic.Platform.ready(function() {
  2595. document.removeEventListener('backbutton', fn);
  2596. });
  2597. },
  2598. /**
  2599. * @ngdoc method
  2600. * @name $ionicPlatform#registerBackButtonAction
  2601. * @description
  2602. * Register a hardware back button action. Only one action will execute
  2603. * when the back button is clicked, so this method decides which of
  2604. * the registered back button actions has the highest priority.
  2605. *
  2606. * For example, if an actionsheet is showing, the back button should
  2607. * close the actionsheet, but it should not also go back a page view
  2608. * or close a modal which may be open.
  2609. *
  2610. * The priorities for the existing back button hooks are as follows:
  2611. * Return to previous view = 100
  2612. * Close side menu = 150
  2613. * Dismiss modal = 200
  2614. * Close action sheet = 300
  2615. * Dismiss popup = 400
  2616. * Dismiss loading overlay = 500
  2617. *
  2618. * Your back button action will override each of the above actions
  2619. * whose priority is less than the priority you provide. For example,
  2620. * an action assigned a priority of 101 will override the 'return to
  2621. * previous view' action, but not any of the other actions.
  2622. *
  2623. * @param {function} callback Called when the back button is pressed,
  2624. * if this listener is the highest priority.
  2625. * @param {number} priority Only the highest priority will execute.
  2626. * @param {*=} actionId The id to assign this action. Default: a
  2627. * random unique id.
  2628. * @returns {function} A function that, when called, will deregister
  2629. * this backButtonAction.
  2630. */
  2631. $backButtonActions: {},
  2632. registerBackButtonAction: function(fn, priority, actionId) {
  2633. if (!self._hasBackButtonHandler) {
  2634. // add a back button listener if one hasn't been setup yet
  2635. self.$backButtonActions = {};
  2636. self.onHardwareBackButton(self.hardwareBackButtonClick);
  2637. self._hasBackButtonHandler = true;
  2638. }
  2639. var action = {
  2640. id: (actionId ? actionId : ionic.Utils.nextUid()),
  2641. priority: (priority ? priority : 0),
  2642. fn: fn
  2643. };
  2644. self.$backButtonActions[action.id] = action;
  2645. // return a function to de-register this back button action
  2646. return function() {
  2647. delete self.$backButtonActions[action.id];
  2648. };
  2649. },
  2650. /**
  2651. * @private
  2652. */
  2653. hardwareBackButtonClick: function(e) {
  2654. // loop through all the registered back button actions
  2655. // and only run the last one of the highest priority
  2656. var priorityAction, actionId;
  2657. for (actionId in self.$backButtonActions) {
  2658. if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
  2659. priorityAction = self.$backButtonActions[actionId];
  2660. }
  2661. }
  2662. if (priorityAction) {
  2663. priorityAction.fn(e);
  2664. return priorityAction;
  2665. }
  2666. },
  2667. is: function(type) {
  2668. return ionic.Platform.is(type);
  2669. },
  2670. /**
  2671. * @ngdoc method
  2672. * @name $ionicPlatform#on
  2673. * @description
  2674. * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
  2675. * `offline`, etc. More information about available event types can be found in
  2676. * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
  2677. * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
  2678. * @param {function} callback Called when the Cordova event is fired.
  2679. * @returns {function} Returns a deregistration function to remove the event listener.
  2680. */
  2681. on: function(type, cb) {
  2682. ionic.Platform.ready(function() {
  2683. document.addEventListener(type, cb, false);
  2684. });
  2685. return function() {
  2686. ionic.Platform.ready(function() {
  2687. document.removeEventListener(type, cb);
  2688. });
  2689. };
  2690. },
  2691. /**
  2692. * @ngdoc method
  2693. * @name $ionicPlatform#ready
  2694. * @description
  2695. * Trigger a callback once the device is ready,
  2696. * or immediately if the device is already ready.
  2697. * @param {function=} callback The function to call.
  2698. * @returns {promise} A promise which is resolved when the device is ready.
  2699. */
  2700. ready: function(cb) {
  2701. var q = $q.defer();
  2702. ionic.Platform.ready(function() {
  2703. window.addEventListener('statusTap', function() {
  2704. $ionicScrollDelegate.scrollTop(true);
  2705. });
  2706. q.resolve();
  2707. cb && cb();
  2708. });
  2709. return q.promise;
  2710. }
  2711. };
  2712. return self;
  2713. }]
  2714. };
  2715. });
  2716. /**
  2717. * @ngdoc service
  2718. * @name $ionicPopover
  2719. * @module ionic
  2720. * @description
  2721. *
  2722. * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
  2723. *
  2724. * The Popover is a view that floats above an apps content. Popovers provide an
  2725. * easy way to present or gather information from the user and are
  2726. * commonly used in the following situations:
  2727. *
  2728. * - Show more info about the current view
  2729. * - Select a commonly used tool or configuration
  2730. * - Present a list of actions to perform inside one of your views
  2731. *
  2732. * Put the content of the popover inside of an `<ion-popover-view>` element.
  2733. *
  2734. * @usage
  2735. * ```html
  2736. * <p>
  2737. * <button ng-click="openPopover($event)">Open Popover</button>
  2738. * </p>
  2739. *
  2740. * <script id="my-popover.html" type="text/ng-template">
  2741. * <ion-popover-view>
  2742. * <ion-header-bar>
  2743. * <h1 class="title">My Popover Title</h1>
  2744. * </ion-header-bar>
  2745. * <ion-content>
  2746. * Hello!
  2747. * </ion-content>
  2748. * </ion-popover-view>
  2749. * </script>
  2750. * ```
  2751. * ```js
  2752. * angular.module('testApp', ['ionic'])
  2753. * .controller('MyController', function($scope, $ionicPopover) {
  2754. *
  2755. * // .fromTemplate() method
  2756. * var template = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> Hello! </ion-content></ion-popover-view>';
  2757. *
  2758. * $scope.popover = $ionicPopover.fromTemplate(template, {
  2759. * scope: $scope
  2760. * });
  2761. *
  2762. * // .fromTemplateUrl() method
  2763. * $ionicPopover.fromTemplateUrl('my-popover.html', {
  2764. * scope: $scope
  2765. * }).then(function(popover) {
  2766. * $scope.popover = popover;
  2767. * });
  2768. *
  2769. *
  2770. * $scope.openPopover = function($event) {
  2771. * $scope.popover.show($event);
  2772. * };
  2773. * $scope.closePopover = function() {
  2774. * $scope.popover.hide();
  2775. * };
  2776. * //Cleanup the popover when we're done with it!
  2777. * $scope.$on('$destroy', function() {
  2778. * $scope.popover.remove();
  2779. * });
  2780. * // Execute action on hide popover
  2781. * $scope.$on('popover.hidden', function() {
  2782. * // Execute action
  2783. * });
  2784. * // Execute action on remove popover
  2785. * $scope.$on('popover.removed', function() {
  2786. * // Execute action
  2787. * });
  2788. * });
  2789. * ```
  2790. */
  2791. IonicModule
  2792. .factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
  2793. function($ionicModal, $ionicPosition, $document, $window) {
  2794. var POPOVER_BODY_PADDING = 6;
  2795. var POPOVER_OPTIONS = {
  2796. viewType: 'popover',
  2797. hideDelay: 1,
  2798. animation: 'none',
  2799. positionView: positionView
  2800. };
  2801. function positionView(target, popoverEle) {
  2802. var targetEle = jqLite(target.target || target);
  2803. var buttonOffset = $ionicPosition.offset(targetEle);
  2804. var popoverWidth = popoverEle.prop('offsetWidth');
  2805. var popoverHeight = popoverEle.prop('offsetHeight');
  2806. // Use innerWidth and innerHeight, because clientWidth and clientHeight
  2807. // doesn't work consistently for body on all platforms
  2808. var bodyWidth = $window.innerWidth;
  2809. var bodyHeight = $window.innerHeight;
  2810. var popoverCSS = {
  2811. left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
  2812. };
  2813. var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
  2814. if (popoverCSS.left < POPOVER_BODY_PADDING) {
  2815. popoverCSS.left = POPOVER_BODY_PADDING;
  2816. } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
  2817. popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
  2818. }
  2819. // If the popover when popped down stretches past bottom of screen,
  2820. // make it pop up if there's room above
  2821. if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&
  2822. buttonOffset.top - popoverHeight > 0) {
  2823. popoverCSS.top = buttonOffset.top - popoverHeight;
  2824. popoverEle.addClass('popover-bottom');
  2825. } else {
  2826. popoverCSS.top = buttonOffset.top + buttonOffset.height;
  2827. popoverEle.removeClass('popover-bottom');
  2828. }
  2829. arrowEle.css({
  2830. left: buttonOffset.left + buttonOffset.width / 2 -
  2831. arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
  2832. });
  2833. popoverEle.css({
  2834. top: popoverCSS.top + 'px',
  2835. left: popoverCSS.left + 'px',
  2836. marginLeft: '0',
  2837. opacity: '1'
  2838. });
  2839. }
  2840. /**
  2841. * @ngdoc controller
  2842. * @name ionicPopover
  2843. * @module ionic
  2844. * @description
  2845. * Instantiated by the {@link ionic.service:$ionicPopover} service.
  2846. *
  2847. * Be sure to call [remove()](#remove) when you are done with each popover
  2848. * to clean it up and avoid memory leaks.
  2849. *
  2850. * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
  2851. * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
  2852. * called when the popover is removed.
  2853. */
  2854. /**
  2855. * @ngdoc method
  2856. * @name ionicPopover#initialize
  2857. * @description Creates a new popover controller instance.
  2858. * @param {object} options An options object with the following properties:
  2859. * - `{object=}` `scope` The scope to be a child of.
  2860. * Default: creates a child of $rootScope.
  2861. * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
  2862. * the popover when shown. Default: false.
  2863. * - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
  2864. * Default: true.
  2865. * - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
  2866. * back button on Android and similar devices. Default: true.
  2867. */
  2868. /**
  2869. * @ngdoc method
  2870. * @name ionicPopover#show
  2871. * @description Show this popover instance.
  2872. * @param {$event} $event The $event or target element which the popover should align
  2873. * itself next to.
  2874. * @returns {promise} A promise which is resolved when the popover is finished animating in.
  2875. */
  2876. /**
  2877. * @ngdoc method
  2878. * @name ionicPopover#hide
  2879. * @description Hide this popover instance.
  2880. * @returns {promise} A promise which is resolved when the popover is finished animating out.
  2881. */
  2882. /**
  2883. * @ngdoc method
  2884. * @name ionicPopover#remove
  2885. * @description Remove this popover instance from the DOM and clean up.
  2886. * @returns {promise} A promise which is resolved when the popover is finished animating out.
  2887. */
  2888. /**
  2889. * @ngdoc method
  2890. * @name ionicPopover#isShown
  2891. * @returns boolean Whether this popover is currently shown.
  2892. */
  2893. return {
  2894. /**
  2895. * @ngdoc method
  2896. * @name $ionicPopover#fromTemplate
  2897. * @param {string} templateString The template string to use as the popovers's
  2898. * content.
  2899. * @param {object} options Options to be passed to the initialize method.
  2900. * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
  2901. * controller (ionicPopover is built on top of $ionicPopover).
  2902. */
  2903. fromTemplate: function(templateString, options) {
  2904. return $ionicModal.fromTemplate(templateString, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
  2905. },
  2906. /**
  2907. * @ngdoc method
  2908. * @name $ionicPopover#fromTemplateUrl
  2909. * @param {string} templateUrl The url to load the template from.
  2910. * @param {object} options Options to be passed to the initialize method.
  2911. * @returns {promise} A promise that will be resolved with an instance of
  2912. * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
  2913. */
  2914. fromTemplateUrl: function(url, options) {
  2915. return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
  2916. }
  2917. };
  2918. }]);
  2919. var POPUP_TPL =
  2920. '<div class="popup-container" ng-class="cssClass">' +
  2921. '<div class="popup">' +
  2922. '<div class="popup-head">' +
  2923. '<h3 class="popup-title" ng-bind-html="title"></h3>' +
  2924. '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
  2925. '</div>' +
  2926. '<div class="popup-body">' +
  2927. '</div>' +
  2928. '<div class="popup-buttons" ng-show="buttons.length">' +
  2929. '<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +
  2930. '</div>' +
  2931. '</div>' +
  2932. '</div>';
  2933. /**
  2934. * @ngdoc service
  2935. * @name $ionicPopup
  2936. * @module ionic
  2937. * @restrict E
  2938. * @codepen zkmhJ
  2939. * @description
  2940. *
  2941. * The Ionic Popup service allows programmatically creating and showing popup
  2942. * windows that require the user to respond in order to continue.
  2943. *
  2944. * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
  2945. * and `confirm()` functions that users are used to, in addition to allowing popups with completely
  2946. * custom content and look.
  2947. *
  2948. * An input can be given an `autofocus` attribute so it automatically receives focus when
  2949. * the popup first shows. However, depending on certain use-cases this can cause issues with
  2950. * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
  2951. * an opt-in feature and not the default.
  2952. *
  2953. * @usage
  2954. * A few basic examples, see below for details about all of the options available.
  2955. *
  2956. * ```js
  2957. *angular.module('mySuperApp', ['ionic'])
  2958. *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
  2959. *
  2960. * // Triggered on a button click, or some other target
  2961. * $scope.showPopup = function() {
  2962. * $scope.data = {};
  2963. *
  2964. * // An elaborate, custom popup
  2965. * var myPopup = $ionicPopup.show({
  2966. * template: '<input type="password" ng-model="data.wifi">',
  2967. * title: 'Enter Wi-Fi Password',
  2968. * subTitle: 'Please use normal things',
  2969. * scope: $scope,
  2970. * buttons: [
  2971. * { text: 'Cancel' },
  2972. * {
  2973. * text: '<b>Save</b>',
  2974. * type: 'button-positive',
  2975. * onTap: function(e) {
  2976. * if (!$scope.data.wifi) {
  2977. * //don't allow the user to close unless he enters wifi password
  2978. * e.preventDefault();
  2979. * } else {
  2980. * return $scope.data.wifi;
  2981. * }
  2982. * }
  2983. * }
  2984. * ]
  2985. * });
  2986. *
  2987. * myPopup.then(function(res) {
  2988. * console.log('Tapped!', res);
  2989. * });
  2990. *
  2991. * $timeout(function() {
  2992. * myPopup.close(); //close the popup after 3 seconds for some reason
  2993. * }, 3000);
  2994. * };
  2995. *
  2996. * // A confirm dialog
  2997. * $scope.showConfirm = function() {
  2998. * var confirmPopup = $ionicPopup.confirm({
  2999. * title: 'Consume Ice Cream',
  3000. * template: 'Are you sure you want to eat this ice cream?'
  3001. * });
  3002. *
  3003. * confirmPopup.then(function(res) {
  3004. * if(res) {
  3005. * console.log('You are sure');
  3006. * } else {
  3007. * console.log('You are not sure');
  3008. * }
  3009. * });
  3010. * };
  3011. *
  3012. * // An alert dialog
  3013. * $scope.showAlert = function() {
  3014. * var alertPopup = $ionicPopup.alert({
  3015. * title: 'Don\'t eat that!',
  3016. * template: 'It might taste good'
  3017. * });
  3018. *
  3019. * alertPopup.then(function(res) {
  3020. * console.log('Thank you for not eating my delicious ice cream cone');
  3021. * });
  3022. * };
  3023. *});
  3024. *```
  3025. */
  3026. IonicModule
  3027. .factory('$ionicPopup', [
  3028. '$ionicTemplateLoader',
  3029. '$ionicBackdrop',
  3030. '$q',
  3031. '$timeout',
  3032. '$rootScope',
  3033. '$ionicBody',
  3034. '$compile',
  3035. '$ionicPlatform',
  3036. '$ionicModal',
  3037. 'IONIC_BACK_PRIORITY',
  3038. function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, $ionicModal, IONIC_BACK_PRIORITY) {
  3039. //TODO allow this to be configured
  3040. var config = {
  3041. stackPushDelay: 75
  3042. };
  3043. var popupStack = [];
  3044. var $ionicPopup = {
  3045. /**
  3046. * @ngdoc method
  3047. * @description
  3048. * Show a complex popup. This is the master show function for all popups.
  3049. *
  3050. * A complex popup has a `buttons` array, with each button having a `text` and `type`
  3051. * field, in addition to an `onTap` function. The `onTap` function, called when
  3052. * the corresponding button on the popup is tapped, will by default close the popup
  3053. * and resolve the popup promise with its return value. If you wish to prevent the
  3054. * default and keep the popup open on button tap, call `event.preventDefault()` on the
  3055. * passed in tap event. Details below.
  3056. *
  3057. * @name $ionicPopup#show
  3058. * @param {object} options The options for the new popup, of the form:
  3059. *
  3060. * ```
  3061. * {
  3062. * title: '', // String. The title of the popup.
  3063. * cssClass: '', // String, The custom CSS class name
  3064. * subTitle: '', // String (optional). The sub-title of the popup.
  3065. * template: '', // String (optional). The html template to place in the popup body.
  3066. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3067. * scope: null, // Scope (optional). A scope to link to the popup content.
  3068. * buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
  3069. * text: 'Cancel',
  3070. * type: 'button-default',
  3071. * onTap: function(e) {
  3072. * // e.preventDefault() will stop the popup from closing when tapped.
  3073. * e.preventDefault();
  3074. * }
  3075. * }, {
  3076. * text: 'OK',
  3077. * type: 'button-positive',
  3078. * onTap: function(e) {
  3079. * // Returning a value will cause the promise to resolve with the given value.
  3080. * return scope.data.response;
  3081. * }
  3082. * }]
  3083. * }
  3084. * ```
  3085. *
  3086. * @returns {object} A promise which is resolved when the popup is closed. Has an additional
  3087. * `close` function, which can be used to programmatically close the popup.
  3088. */
  3089. show: showPopup,
  3090. /**
  3091. * @ngdoc method
  3092. * @name $ionicPopup#alert
  3093. * @description Show a simple alert popup with a message and one button that the user can
  3094. * tap to close the popup.
  3095. *
  3096. * @param {object} options The options for showing the alert, of the form:
  3097. *
  3098. * ```
  3099. * {
  3100. * title: '', // String. The title of the popup.
  3101. * cssClass: '', // String, The custom CSS class name
  3102. * subTitle: '', // String (optional). The sub-title of the popup.
  3103. * template: '', // String (optional). The html template to place in the popup body.
  3104. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3105. * okText: '', // String (default: 'OK'). The text of the OK button.
  3106. * okType: '', // String (default: 'button-positive'). The type of the OK button.
  3107. * }
  3108. * ```
  3109. *
  3110. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3111. * function `close`, which can be called with any value to programmatically close the popup
  3112. * with the given value.
  3113. */
  3114. alert: showAlert,
  3115. /**
  3116. * @ngdoc method
  3117. * @name $ionicPopup#confirm
  3118. * @description
  3119. * Show a simple confirm popup with a Cancel and OK button.
  3120. *
  3121. * Resolves the promise with true if the user presses the OK button, and false if the
  3122. * user presses the Cancel button.
  3123. *
  3124. * @param {object} options The options for showing the confirm popup, of the form:
  3125. *
  3126. * ```
  3127. * {
  3128. * title: '', // String. The title of the popup.
  3129. * cssClass: '', // String, The custom CSS class name
  3130. * subTitle: '', // String (optional). The sub-title of the popup.
  3131. * template: '', // String (optional). The html template to place in the popup body.
  3132. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3133. * cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
  3134. * cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
  3135. * okText: '', // String (default: 'OK'). The text of the OK button.
  3136. * okType: '', // String (default: 'button-positive'). The type of the OK button.
  3137. * }
  3138. * ```
  3139. *
  3140. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3141. * function `close`, which can be called with any value to programmatically close the popup
  3142. * with the given value.
  3143. */
  3144. confirm: showConfirm,
  3145. /**
  3146. * @ngdoc method
  3147. * @name $ionicPopup#prompt
  3148. * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
  3149. * Resolves the promise with the value of the input if the user presses OK, and with undefined
  3150. * if the user presses Cancel.
  3151. *
  3152. * ```javascript
  3153. * $ionicPopup.prompt({
  3154. * title: 'Password Check',
  3155. * template: 'Enter your secret password',
  3156. * inputType: 'password',
  3157. * inputPlaceholder: 'Your password'
  3158. * }).then(function(res) {
  3159. * console.log('Your password is', res);
  3160. * });
  3161. * ```
  3162. * @param {object} options The options for showing the prompt popup, of the form:
  3163. *
  3164. * ```
  3165. * {
  3166. * title: '', // String. The title of the popup.
  3167. * cssClass: '', // String, The custom CSS class name
  3168. * subTitle: '', // String (optional). The sub-title of the popup.
  3169. * template: '', // String (optional). The html template to place in the popup body.
  3170. * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
  3171. * inputType: // String (default: 'text'). The type of input to use
  3172. * defaultText: // String (default: ''). The initial value placed into the input.
  3173. * maxLength: // Integer (default: null). Specify a maxlength attribute for the input.
  3174. * inputPlaceholder: // String (default: ''). A placeholder to use for the input.
  3175. * cancelText: // String (default: 'Cancel'. The text of the Cancel button.
  3176. * cancelType: // String (default: 'button-default'). The type of the Cancel button.
  3177. * okText: // String (default: 'OK'). The text of the OK button.
  3178. * okType: // String (default: 'button-positive'). The type of the OK button.
  3179. * }
  3180. * ```
  3181. *
  3182. * @returns {object} A promise which is resolved when the popup is closed. Has one additional
  3183. * function `close`, which can be called with any value to programmatically close the popup
  3184. * with the given value.
  3185. */
  3186. prompt: showPrompt,
  3187. /**
  3188. * @private for testing
  3189. */
  3190. _createPopup: createPopup,
  3191. _popupStack: popupStack
  3192. };
  3193. return $ionicPopup;
  3194. function createPopup(options) {
  3195. options = extend({
  3196. scope: null,
  3197. title: '',
  3198. buttons: []
  3199. }, options || {});
  3200. var self = {};
  3201. self.scope = (options.scope || $rootScope).$new();
  3202. self.element = jqLite(POPUP_TPL);
  3203. self.responseDeferred = $q.defer();
  3204. $ionicBody.get().appendChild(self.element[0]);
  3205. $compile(self.element)(self.scope);
  3206. extend(self.scope, {
  3207. title: options.title,
  3208. buttons: options.buttons,
  3209. subTitle: options.subTitle,
  3210. cssClass: options.cssClass,
  3211. $buttonTapped: function(button, event) {
  3212. var result = (button.onTap || noop).apply(self, [event]);
  3213. event = event.originalEvent || event; //jquery events
  3214. if (!event.defaultPrevented) {
  3215. self.responseDeferred.resolve(result);
  3216. }
  3217. }
  3218. });
  3219. $q.when(
  3220. options.templateUrl ?
  3221. $ionicTemplateLoader.load(options.templateUrl) :
  3222. (options.template || options.content || '')
  3223. ).then(function(template) {
  3224. var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
  3225. if (template) {
  3226. popupBody.html(template);
  3227. $compile(popupBody.contents())(self.scope);
  3228. } else {
  3229. popupBody.remove();
  3230. }
  3231. });
  3232. self.show = function() {
  3233. if (self.isShown || self.removed) return;
  3234. $ionicModal.stack.add(self);
  3235. self.isShown = true;
  3236. ionic.requestAnimationFrame(function() {
  3237. //if hidden while waiting for raf, don't show
  3238. if (!self.isShown) return;
  3239. self.element.removeClass('popup-hidden');
  3240. self.element.addClass('popup-showing active');
  3241. focusInput(self.element);
  3242. });
  3243. };
  3244. self.hide = function(callback) {
  3245. callback = callback || noop;
  3246. if (!self.isShown) return callback();
  3247. $ionicModal.stack.remove(self);
  3248. self.isShown = false;
  3249. self.element.removeClass('active');
  3250. self.element.addClass('popup-hidden');
  3251. $timeout(callback, 250, false);
  3252. };
  3253. self.remove = function() {
  3254. if (self.removed) return;
  3255. self.hide(function() {
  3256. self.element.remove();
  3257. self.scope.$destroy();
  3258. });
  3259. self.removed = true;
  3260. };
  3261. return self;
  3262. }
  3263. function onHardwareBackButton() {
  3264. var last = popupStack[popupStack.length - 1];
  3265. last && last.responseDeferred.resolve();
  3266. }
  3267. function showPopup(options) {
  3268. var popup = $ionicPopup._createPopup(options);
  3269. var showDelay = 0;
  3270. if (popupStack.length > 0) {
  3271. showDelay = config.stackPushDelay;
  3272. $timeout(popupStack[popupStack.length - 1].hide, showDelay, false);
  3273. } else {
  3274. //Add popup-open & backdrop if this is first popup
  3275. $ionicBody.addClass('popup-open');
  3276. $ionicBackdrop.retain();
  3277. //only show the backdrop on the first popup
  3278. $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
  3279. onHardwareBackButton,
  3280. IONIC_BACK_PRIORITY.popup
  3281. );
  3282. }
  3283. // Expose a 'close' method on the returned promise
  3284. popup.responseDeferred.promise.close = function popupClose(result) {
  3285. if (!popup.removed) popup.responseDeferred.resolve(result);
  3286. };
  3287. //DEPRECATED: notify the promise with an object with a close method
  3288. popup.responseDeferred.notify({ close: popup.responseDeferred.close });
  3289. doShow();
  3290. return popup.responseDeferred.promise;
  3291. function doShow() {
  3292. popupStack.push(popup);
  3293. $timeout(popup.show, showDelay, false);
  3294. popup.responseDeferred.promise.then(function(result) {
  3295. var index = popupStack.indexOf(popup);
  3296. if (index !== -1) {
  3297. popupStack.splice(index, 1);
  3298. }
  3299. popup.remove();
  3300. if (popupStack.length > 0) {
  3301. popupStack[popupStack.length - 1].show();
  3302. } else {
  3303. $ionicBackdrop.release();
  3304. //Remove popup-open & backdrop if this is last popup
  3305. $timeout(function() {
  3306. // wait to remove this due to a 300ms delay native
  3307. // click which would trigging whatever was underneath this
  3308. if (!popupStack.length) {
  3309. $ionicBody.removeClass('popup-open');
  3310. }
  3311. }, 400, false);
  3312. ($ionicPopup._backButtonActionDone || noop)();
  3313. }
  3314. return result;
  3315. });
  3316. }
  3317. }
  3318. function focusInput(element) {
  3319. var focusOn = element[0].querySelector('[autofocus]');
  3320. if (focusOn) {
  3321. focusOn.focus();
  3322. }
  3323. }
  3324. function showAlert(opts) {
  3325. return showPopup(extend({
  3326. buttons: [{
  3327. text: opts.okText || 'OK',
  3328. type: opts.okType || 'button-positive',
  3329. onTap: function() {
  3330. return true;
  3331. }
  3332. }]
  3333. }, opts || {}));
  3334. }
  3335. function showConfirm(opts) {
  3336. return showPopup(extend({
  3337. buttons: [{
  3338. text: opts.cancelText || 'Cancel',
  3339. type: opts.cancelType || 'button-default',
  3340. onTap: function() { return false; }
  3341. }, {
  3342. text: opts.okText || 'OK',
  3343. type: opts.okType || 'button-positive',
  3344. onTap: function() { return true; }
  3345. }]
  3346. }, opts || {}));
  3347. }
  3348. function showPrompt(opts) {
  3349. var scope = $rootScope.$new(true);
  3350. scope.data = {};
  3351. scope.data.fieldtype = opts.inputType ? opts.inputType : 'text';
  3352. scope.data.response = opts.defaultText ? opts.defaultText : '';
  3353. scope.data.placeholder = opts.inputPlaceholder ? opts.inputPlaceholder : '';
  3354. scope.data.maxlength = opts.maxLength ? parseInt(opts.maxLength) : '';
  3355. var text = '';
  3356. if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
  3357. text = '<span>' + opts.template + '</span>';
  3358. delete opts.template;
  3359. }
  3360. return showPopup(extend({
  3361. template: text + '<input ng-model="data.response" '
  3362. + 'type="{{ data.fieldtype }}"'
  3363. + 'maxlength="{{ data.maxlength }}"'
  3364. + 'placeholder="{{ data.placeholder }}"'
  3365. + '>',
  3366. scope: scope,
  3367. buttons: [{
  3368. text: opts.cancelText || 'Cancel',
  3369. type: opts.cancelType || 'button-default',
  3370. onTap: function() {}
  3371. }, {
  3372. text: opts.okText || 'OK',
  3373. type: opts.okType || 'button-positive',
  3374. onTap: function() {
  3375. return scope.data.response || '';
  3376. }
  3377. }]
  3378. }, opts || {}));
  3379. }
  3380. }]);
  3381. /**
  3382. * @ngdoc service
  3383. * @name $ionicPosition
  3384. * @module ionic
  3385. * @description
  3386. * A set of utility methods that can be use to retrieve position of DOM elements.
  3387. * It is meant to be used where we need to absolute-position DOM elements in
  3388. * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
  3389. *
  3390. * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
  3391. * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
  3392. */
  3393. IonicModule
  3394. .factory('$ionicPosition', ['$document', '$window', function($document, $window) {
  3395. function getStyle(el, cssprop) {
  3396. if (el.currentStyle) { //IE
  3397. return el.currentStyle[cssprop];
  3398. } else if ($window.getComputedStyle) {
  3399. return $window.getComputedStyle(el)[cssprop];
  3400. }
  3401. // finally try and get inline style
  3402. return el.style[cssprop];
  3403. }
  3404. /**
  3405. * Checks if a given element is statically positioned
  3406. * @param element - raw DOM element
  3407. */
  3408. function isStaticPositioned(element) {
  3409. return (getStyle(element, 'position') || 'static') === 'static';
  3410. }
  3411. /**
  3412. * returns the closest, non-statically positioned parentOffset of a given element
  3413. * @param element
  3414. */
  3415. var parentOffsetEl = function(element) {
  3416. var docDomEl = $document[0];
  3417. var offsetParent = element.offsetParent || docDomEl;
  3418. while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
  3419. offsetParent = offsetParent.offsetParent;
  3420. }
  3421. return offsetParent || docDomEl;
  3422. };
  3423. return {
  3424. /**
  3425. * @ngdoc method
  3426. * @name $ionicPosition#position
  3427. * @description Get the current coordinates of the element, relative to the offset parent.
  3428. * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
  3429. * @param {element} element The element to get the position of.
  3430. * @returns {object} Returns an object containing the properties top, left, width and height.
  3431. */
  3432. position: function(element) {
  3433. var elBCR = this.offset(element);
  3434. var offsetParentBCR = { top: 0, left: 0 };
  3435. var offsetParentEl = parentOffsetEl(element[0]);
  3436. if (offsetParentEl != $document[0]) {
  3437. offsetParentBCR = this.offset(jqLite(offsetParentEl));
  3438. offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
  3439. offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
  3440. }
  3441. var boundingClientRect = element[0].getBoundingClientRect();
  3442. return {
  3443. width: boundingClientRect.width || element.prop('offsetWidth'),
  3444. height: boundingClientRect.height || element.prop('offsetHeight'),
  3445. top: elBCR.top - offsetParentBCR.top,
  3446. left: elBCR.left - offsetParentBCR.left
  3447. };
  3448. },
  3449. /**
  3450. * @ngdoc method
  3451. * @name $ionicPosition#offset
  3452. * @description Get the current coordinates of the element, relative to the document.
  3453. * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
  3454. * @param {element} element The element to get the offset of.
  3455. * @returns {object} Returns an object containing the properties top, left, width and height.
  3456. */
  3457. offset: function(element) {
  3458. var boundingClientRect = element[0].getBoundingClientRect();
  3459. return {
  3460. width: boundingClientRect.width || element.prop('offsetWidth'),
  3461. height: boundingClientRect.height || element.prop('offsetHeight'),
  3462. top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
  3463. left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
  3464. };
  3465. }
  3466. };
  3467. }]);
  3468. /**
  3469. * @ngdoc service
  3470. * @name $ionicScrollDelegate
  3471. * @module ionic
  3472. * @description
  3473. * Delegate for controlling scrollViews (created by
  3474. * {@link ionic.directive:ionContent} and
  3475. * {@link ionic.directive:ionScroll} directives).
  3476. *
  3477. * Methods called directly on the $ionicScrollDelegate service will control all scroll
  3478. * views. Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
  3479. * method to control specific scrollViews.
  3480. *
  3481. * @usage
  3482. *
  3483. * ```html
  3484. * <body ng-controller="MainCtrl">
  3485. * <ion-content>
  3486. * <button ng-click="scrollTop()">Scroll to Top!</button>
  3487. * </ion-content>
  3488. * </body>
  3489. * ```
  3490. * ```js
  3491. * function MainCtrl($scope, $ionicScrollDelegate) {
  3492. * $scope.scrollTop = function() {
  3493. * $ionicScrollDelegate.scrollTop();
  3494. * };
  3495. * }
  3496. * ```
  3497. *
  3498. * Example of advanced usage, with two scroll areas using `delegate-handle`
  3499. * for fine control.
  3500. *
  3501. * ```html
  3502. * <body ng-controller="MainCtrl">
  3503. * <ion-content delegate-handle="mainScroll">
  3504. * <button ng-click="scrollMainToTop()">
  3505. * Scroll content to top!
  3506. * </button>
  3507. * <ion-scroll delegate-handle="small" style="height: 100px;">
  3508. * <button ng-click="scrollSmallToTop()">
  3509. * Scroll small area to top!
  3510. * </button>
  3511. * </ion-scroll>
  3512. * </ion-content>
  3513. * </body>
  3514. * ```
  3515. * ```js
  3516. * function MainCtrl($scope, $ionicScrollDelegate) {
  3517. * $scope.scrollMainToTop = function() {
  3518. * $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
  3519. * };
  3520. * $scope.scrollSmallToTop = function() {
  3521. * $ionicScrollDelegate.$getByHandle('small').scrollTop();
  3522. * };
  3523. * }
  3524. * ```
  3525. */
  3526. IonicModule
  3527. .service('$ionicScrollDelegate', ionic.DelegateService([
  3528. /**
  3529. * @ngdoc method
  3530. * @name $ionicScrollDelegate#resize
  3531. * @description Tell the scrollView to recalculate the size of its container.
  3532. */
  3533. 'resize',
  3534. /**
  3535. * @ngdoc method
  3536. * @name $ionicScrollDelegate#scrollTop
  3537. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3538. */
  3539. 'scrollTop',
  3540. /**
  3541. * @ngdoc method
  3542. * @name $ionicScrollDelegate#scrollBottom
  3543. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3544. */
  3545. 'scrollBottom',
  3546. /**
  3547. * @ngdoc method
  3548. * @name $ionicScrollDelegate#scrollTo
  3549. * @param {number} left The x-value to scroll to.
  3550. * @param {number} top The y-value to scroll to.
  3551. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3552. */
  3553. 'scrollTo',
  3554. /**
  3555. * @ngdoc method
  3556. * @name $ionicScrollDelegate#scrollBy
  3557. * @param {number} left The x-offset to scroll by.
  3558. * @param {number} top The y-offset to scroll by.
  3559. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3560. */
  3561. 'scrollBy',
  3562. /**
  3563. * @ngdoc method
  3564. * @name $ionicScrollDelegate#zoomTo
  3565. * @param {number} level Level to zoom to.
  3566. * @param {boolean=} animate Whether to animate the zoom.
  3567. * @param {number=} originLeft Zoom in at given left coordinate.
  3568. * @param {number=} originTop Zoom in at given top coordinate.
  3569. */
  3570. 'zoomTo',
  3571. /**
  3572. * @ngdoc method
  3573. * @name $ionicScrollDelegate#zoomBy
  3574. * @param {number} factor The factor to zoom by.
  3575. * @param {boolean=} animate Whether to animate the zoom.
  3576. * @param {number=} originLeft Zoom in at given left coordinate.
  3577. * @param {number=} originTop Zoom in at given top coordinate.
  3578. */
  3579. 'zoomBy',
  3580. /**
  3581. * @ngdoc method
  3582. * @name $ionicScrollDelegate#getScrollPosition
  3583. * @returns {object} The scroll position of this view, with the following properties:
  3584. * - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
  3585. * - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
  3586. * - `{number}` `zoom` The current zoom level.
  3587. */
  3588. 'getScrollPosition',
  3589. /**
  3590. * @ngdoc method
  3591. * @name $ionicScrollDelegate#anchorScroll
  3592. * @description Tell the scrollView to scroll to the element with an id
  3593. * matching window.location.hash.
  3594. *
  3595. * If no matching element is found, it will scroll to top.
  3596. *
  3597. * @param {boolean=} shouldAnimate Whether the scroll should animate.
  3598. */
  3599. 'anchorScroll',
  3600. /**
  3601. * @ngdoc method
  3602. * @name $ionicScrollDelegate#freezeScroll
  3603. * @description Does not allow this scroll view to scroll either x or y.
  3604. * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.
  3605. * @returns {boolean} If the scroll view is being prevented from scrolling or not.
  3606. */
  3607. 'freezeScroll',
  3608. /**
  3609. * @ngdoc method
  3610. * @name $ionicScrollDelegate#freezeAllScrolls
  3611. * @description Does not allow any of the app's scroll views to scroll either x or y.
  3612. * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.
  3613. */
  3614. 'freezeAllScrolls',
  3615. /**
  3616. * @ngdoc method
  3617. * @name $ionicScrollDelegate#getScrollView
  3618. * @returns {object} The scrollView associated with this delegate.
  3619. */
  3620. 'getScrollView'
  3621. /**
  3622. * @ngdoc method
  3623. * @name $ionicScrollDelegate#$getByHandle
  3624. * @param {string} handle
  3625. * @returns `delegateInstance` A delegate instance that controls only the
  3626. * scrollViews with `delegate-handle` matching the given handle.
  3627. *
  3628. * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
  3629. */
  3630. ]));
  3631. /**
  3632. * @ngdoc service
  3633. * @name $ionicSideMenuDelegate
  3634. * @module ionic
  3635. *
  3636. * @description
  3637. * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
  3638. *
  3639. * Methods called directly on the $ionicSideMenuDelegate service will control all side
  3640. * menus. Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
  3641. * method to control specific ionSideMenus instances.
  3642. *
  3643. * @usage
  3644. *
  3645. * ```html
  3646. * <body ng-controller="MainCtrl">
  3647. * <ion-side-menus>
  3648. * <ion-side-menu-content>
  3649. * Content!
  3650. * <button ng-click="toggleLeftSideMenu()">
  3651. * Toggle Left Side Menu
  3652. * </button>
  3653. * </ion-side-menu-content>
  3654. * <ion-side-menu side="left">
  3655. * Left Menu!
  3656. * <ion-side-menu>
  3657. * </ion-side-menus>
  3658. * </body>
  3659. * ```
  3660. * ```js
  3661. * function MainCtrl($scope, $ionicSideMenuDelegate) {
  3662. * $scope.toggleLeftSideMenu = function() {
  3663. * $ionicSideMenuDelegate.toggleLeft();
  3664. * };
  3665. * }
  3666. * ```
  3667. */
  3668. IonicModule
  3669. .service('$ionicSideMenuDelegate', ionic.DelegateService([
  3670. /**
  3671. * @ngdoc method
  3672. * @name $ionicSideMenuDelegate#toggleLeft
  3673. * @description Toggle the left side menu (if it exists).
  3674. * @param {boolean=} isOpen Whether to open or close the menu.
  3675. * Default: Toggles the menu.
  3676. */
  3677. 'toggleLeft',
  3678. /**
  3679. * @ngdoc method
  3680. * @name $ionicSideMenuDelegate#toggleRight
  3681. * @description Toggle the right side menu (if it exists).
  3682. * @param {boolean=} isOpen Whether to open or close the menu.
  3683. * Default: Toggles the menu.
  3684. */
  3685. 'toggleRight',
  3686. /**
  3687. * @ngdoc method
  3688. * @name $ionicSideMenuDelegate#getOpenRatio
  3689. * @description Gets the ratio of open amount over menu width. For example, a
  3690. * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
  3691. * a ratio of 0.5.
  3692. *
  3693. * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
  3694. * opened/opening, and between 0 and -1 if right menu is opened/opening.
  3695. */
  3696. 'getOpenRatio',
  3697. /**
  3698. * @ngdoc method
  3699. * @name $ionicSideMenuDelegate#isOpen
  3700. * @returns {boolean} Whether either the left or right menu is currently opened.
  3701. */
  3702. 'isOpen',
  3703. /**
  3704. * @ngdoc method
  3705. * @name $ionicSideMenuDelegate#isOpenLeft
  3706. * @returns {boolean} Whether the left menu is currently opened.
  3707. */
  3708. 'isOpenLeft',
  3709. /**
  3710. * @ngdoc method
  3711. * @name $ionicSideMenuDelegate#isOpenRight
  3712. * @returns {boolean} Whether the right menu is currently opened.
  3713. */
  3714. 'isOpenRight',
  3715. /**
  3716. * @ngdoc method
  3717. * @name $ionicSideMenuDelegate#canDragContent
  3718. * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
  3719. * side menus.
  3720. * @returns {boolean} Whether the content can be dragged to open side menus.
  3721. */
  3722. 'canDragContent',
  3723. /**
  3724. * @ngdoc method
  3725. * @name $ionicSideMenuDelegate#edgeDragThreshold
  3726. * @param {boolean|number=} value Set whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Accepts three different values:
  3727. * - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
  3728. * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
  3729. * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
  3730. * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
  3731. */
  3732. 'edgeDragThreshold'
  3733. /**
  3734. * @ngdoc method
  3735. * @name $ionicSideMenuDelegate#$getByHandle
  3736. * @param {string} handle
  3737. * @returns `delegateInstance` A delegate instance that controls only the
  3738. * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
  3739. * the given handle.
  3740. *
  3741. * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
  3742. */
  3743. ]));
  3744. /**
  3745. * @ngdoc service
  3746. * @name $ionicSlideBoxDelegate
  3747. * @module ionic
  3748. * @description
  3749. * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
  3750. *
  3751. * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes. Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
  3752. * method to control specific slide box instances.
  3753. *
  3754. * @usage
  3755. *
  3756. * ```html
  3757. * <ion-view>
  3758. * <ion-slide-box>
  3759. * <ion-slide>
  3760. * <div class="box blue">
  3761. * <button ng-click="nextSlide()">Next slide!</button>
  3762. * </div>
  3763. * </ion-slide>
  3764. * <ion-slide>
  3765. * <div class="box red">
  3766. * Slide 2!
  3767. * </div>
  3768. * </ion-slide>
  3769. * </ion-slide-box>
  3770. * </ion-view>
  3771. * ```
  3772. * ```js
  3773. * function MyCtrl($scope, $ionicSlideBoxDelegate) {
  3774. * $scope.nextSlide = function() {
  3775. * $ionicSlideBoxDelegate.next();
  3776. * }
  3777. * }
  3778. * ```
  3779. */
  3780. IonicModule
  3781. .service('$ionicSlideBoxDelegate', ionic.DelegateService([
  3782. /**
  3783. * @ngdoc method
  3784. * @name $ionicSlideBoxDelegate#update
  3785. * @description
  3786. * Update the slidebox (for example if using Angular with ng-repeat,
  3787. * resize it for the elements inside).
  3788. */
  3789. 'update',
  3790. /**
  3791. * @ngdoc method
  3792. * @name $ionicSlideBoxDelegate#slide
  3793. * @param {number} to The index to slide to.
  3794. * @param {number=} speed The number of milliseconds the change should take.
  3795. */
  3796. 'slide',
  3797. 'select',
  3798. /**
  3799. * @ngdoc method
  3800. * @name $ionicSlideBoxDelegate#enableSlide
  3801. * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
  3802. * @returns {boolean} Whether sliding is enabled.
  3803. */
  3804. 'enableSlide',
  3805. /**
  3806. * @ngdoc method
  3807. * @name $ionicSlideBoxDelegate#previous
  3808. * @param {number=} speed The number of milliseconds the change should take.
  3809. * @description Go to the previous slide. Wraps around if at the beginning.
  3810. */
  3811. 'previous',
  3812. /**
  3813. * @ngdoc method
  3814. * @name $ionicSlideBoxDelegate#next
  3815. * @param {number=} speed The number of milliseconds the change should take.
  3816. * @description Go to the next slide. Wraps around if at the end.
  3817. */
  3818. 'next',
  3819. /**
  3820. * @ngdoc method
  3821. * @name $ionicSlideBoxDelegate#stop
  3822. * @description Stop sliding. The slideBox will not move again until
  3823. * explicitly told to do so.
  3824. */
  3825. 'stop',
  3826. 'autoPlay',
  3827. /**
  3828. * @ngdoc method
  3829. * @name $ionicSlideBoxDelegate#start
  3830. * @description Start sliding again if the slideBox was stopped.
  3831. */
  3832. 'start',
  3833. /**
  3834. * @ngdoc method
  3835. * @name $ionicSlideBoxDelegate#currentIndex
  3836. * @returns number The index of the current slide.
  3837. */
  3838. 'currentIndex',
  3839. 'selected',
  3840. /**
  3841. * @ngdoc method
  3842. * @name $ionicSlideBoxDelegate#slidesCount
  3843. * @returns number The number of slides there are currently.
  3844. */
  3845. 'slidesCount',
  3846. 'count',
  3847. 'loop'
  3848. /**
  3849. * @ngdoc method
  3850. * @name $ionicSlideBoxDelegate#$getByHandle
  3851. * @param {string} handle
  3852. * @returns `delegateInstance` A delegate instance that controls only the
  3853. * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
  3854. * the given handle.
  3855. *
  3856. * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
  3857. */
  3858. ]));
  3859. /**
  3860. * @ngdoc service
  3861. * @name $ionicTabsDelegate
  3862. * @module ionic
  3863. *
  3864. * @description
  3865. * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
  3866. *
  3867. * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
  3868. * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
  3869. * method to control specific ionTabs instances.
  3870. *
  3871. * @usage
  3872. *
  3873. * ```html
  3874. * <body ng-controller="MyCtrl">
  3875. * <ion-tabs>
  3876. *
  3877. * <ion-tab title="Tab 1">
  3878. * Hello tab 1!
  3879. * <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
  3880. * </ion-tab>
  3881. * <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
  3882. *
  3883. * </ion-tabs>
  3884. * </body>
  3885. * ```
  3886. * ```js
  3887. * function MyCtrl($scope, $ionicTabsDelegate) {
  3888. * $scope.selectTabWithIndex = function(index) {
  3889. * $ionicTabsDelegate.select(index);
  3890. * }
  3891. * }
  3892. * ```
  3893. */
  3894. IonicModule
  3895. .service('$ionicTabsDelegate', ionic.DelegateService([
  3896. /**
  3897. * @ngdoc method
  3898. * @name $ionicTabsDelegate#select
  3899. * @description Select the tab matching the given index.
  3900. *
  3901. * @param {number} index Index of the tab to select.
  3902. */
  3903. 'select',
  3904. /**
  3905. * @ngdoc method
  3906. * @name $ionicTabsDelegate#selectedIndex
  3907. * @returns `number` The index of the selected tab, or -1.
  3908. */
  3909. 'selectedIndex',
  3910. /**
  3911. * @ngdoc method
  3912. * @name $ionicTabsDelegate#showBar
  3913. * @description
  3914. * Set/get whether the {@link ionic.directive:ionTabs} is shown
  3915. * @param {boolean} show Whether to show the bar.
  3916. * @returns {boolean} Whether the bar is shown.
  3917. */
  3918. 'showBar'
  3919. /**
  3920. * @ngdoc method
  3921. * @name $ionicTabsDelegate#$getByHandle
  3922. * @param {string} handle
  3923. * @returns `delegateInstance` A delegate instance that controls only the
  3924. * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
  3925. * the given handle.
  3926. *
  3927. * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
  3928. */
  3929. ]));
  3930. // closure to keep things neat
  3931. (function() {
  3932. var templatesToCache = [];
  3933. /**
  3934. * @ngdoc service
  3935. * @name $ionicTemplateCache
  3936. * @module ionic
  3937. * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
  3938. * @usage
  3939. * State templates are cached automatically, but you can optionally cache other templates.
  3940. *
  3941. * ```js
  3942. * $ionicTemplateCache('myNgIncludeTemplate.html');
  3943. * ```
  3944. *
  3945. * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
  3946. * in the `$state` definition
  3947. *
  3948. * ```js
  3949. * angular.module('myApp', ['ionic'])
  3950. * .config(function($stateProvider, $ionicConfigProvider) {
  3951. *
  3952. * // disable preemptive template caching globally
  3953. * $ionicConfigProvider.templates.prefetch(false);
  3954. *
  3955. * // disable individual states
  3956. * $stateProvider
  3957. * .state('tabs', {
  3958. * url: "/tab",
  3959. * abstract: true,
  3960. * prefetchTemplate: false,
  3961. * templateUrl: "tabs-templates/tabs.html"
  3962. * })
  3963. * .state('tabs.home', {
  3964. * url: "/home",
  3965. * views: {
  3966. * 'home-tab': {
  3967. * prefetchTemplate: false,
  3968. * templateUrl: "tabs-templates/home.html",
  3969. * controller: 'HomeTabCtrl'
  3970. * }
  3971. * }
  3972. * });
  3973. * });
  3974. * ```
  3975. */
  3976. IonicModule
  3977. .factory('$ionicTemplateCache', [
  3978. '$http',
  3979. '$templateCache',
  3980. '$timeout',
  3981. function($http, $templateCache, $timeout) {
  3982. var toCache = templatesToCache,
  3983. hasRun;
  3984. function $ionicTemplateCache(templates) {
  3985. if (typeof templates === 'undefined') {
  3986. return run();
  3987. }
  3988. if (isString(templates)) {
  3989. templates = [templates];
  3990. }
  3991. forEach(templates, function(template) {
  3992. toCache.push(template);
  3993. });
  3994. if (hasRun) {
  3995. run();
  3996. }
  3997. }
  3998. // run through methods - internal method
  3999. function run() {
  4000. var template;
  4001. $ionicTemplateCache._runCount++;
  4002. hasRun = true;
  4003. // ignore if race condition already zeroed out array
  4004. if (toCache.length === 0) return;
  4005. var i = 0;
  4006. while (i < 4 && (template = toCache.pop())) {
  4007. // note that inline templates are ignored by this request
  4008. if (isString(template)) $http.get(template, { cache: $templateCache });
  4009. i++;
  4010. }
  4011. // only preload 3 templates a second
  4012. if (toCache.length) {
  4013. $timeout(run, 1000);
  4014. }
  4015. }
  4016. // exposing for testing
  4017. $ionicTemplateCache._runCount = 0;
  4018. // default method
  4019. return $ionicTemplateCache;
  4020. }])
  4021. // Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
  4022. .config([
  4023. '$stateProvider',
  4024. '$ionicConfigProvider',
  4025. function($stateProvider, $ionicConfigProvider) {
  4026. var stateProviderState = $stateProvider.state;
  4027. $stateProvider.state = function(stateName, definition) {
  4028. // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
  4029. if (typeof definition === 'object') {
  4030. var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
  4031. if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
  4032. if (angular.isObject(definition.views)) {
  4033. for (var key in definition.views) {
  4034. enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
  4035. if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
  4036. }
  4037. }
  4038. }
  4039. return stateProviderState.call($stateProvider, stateName, definition);
  4040. };
  4041. }])
  4042. // process the templateUrls collected by the $stateProvider, adding them to the cache
  4043. .run(['$ionicTemplateCache', function($ionicTemplateCache) {
  4044. $ionicTemplateCache();
  4045. }]);
  4046. })();
  4047. IonicModule
  4048. .factory('$ionicTemplateLoader', [
  4049. '$compile',
  4050. '$controller',
  4051. '$http',
  4052. '$q',
  4053. '$rootScope',
  4054. '$templateCache',
  4055. function($compile, $controller, $http, $q, $rootScope, $templateCache) {
  4056. return {
  4057. load: fetchTemplate,
  4058. compile: loadAndCompile
  4059. };
  4060. function fetchTemplate(url) {
  4061. return $http.get(url, {cache: $templateCache})
  4062. .then(function(response) {
  4063. return response.data && response.data.trim();
  4064. });
  4065. }
  4066. function loadAndCompile(options) {
  4067. options = extend({
  4068. template: '',
  4069. templateUrl: '',
  4070. scope: null,
  4071. controller: null,
  4072. locals: {},
  4073. appendTo: null
  4074. }, options || {});
  4075. var templatePromise = options.templateUrl ?
  4076. this.load(options.templateUrl) :
  4077. $q.when(options.template);
  4078. return templatePromise.then(function(template) {
  4079. var controller;
  4080. var scope = options.scope || $rootScope.$new();
  4081. //Incase template doesn't have just one root element, do this
  4082. var element = jqLite('<div>').html(template).contents();
  4083. if (options.controller) {
  4084. controller = $controller(
  4085. options.controller,
  4086. extend(options.locals, {
  4087. $scope: scope
  4088. })
  4089. );
  4090. element.children().data('$ngControllerController', controller);
  4091. }
  4092. if (options.appendTo) {
  4093. jqLite(options.appendTo).append(element);
  4094. }
  4095. $compile(element)(scope);
  4096. return {
  4097. element: element,
  4098. scope: scope
  4099. };
  4100. });
  4101. }
  4102. }]);
  4103. /**
  4104. * @private
  4105. * DEPRECATED, as of v1.0.0-beta14 -------
  4106. */
  4107. IonicModule
  4108. .factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
  4109. function warn(oldMethod, newMethod) {
  4110. $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
  4111. }
  4112. warn('', '');
  4113. var methodsMap = {
  4114. getCurrentView: 'currentView',
  4115. getBackView: 'backView',
  4116. getForwardView: 'forwardView',
  4117. getCurrentStateName: 'currentStateName',
  4118. nextViewOptions: 'nextViewOptions',
  4119. clearHistory: 'clearHistory'
  4120. };
  4121. forEach(methodsMap, function(newMethod, oldMethod) {
  4122. methodsMap[oldMethod] = function() {
  4123. warn('.' + oldMethod, '.' + newMethod);
  4124. return $ionicHistory[newMethod].apply(this, arguments);
  4125. };
  4126. });
  4127. return methodsMap;
  4128. }]);
  4129. /**
  4130. * @private
  4131. * TODO document
  4132. */
  4133. IonicModule.factory('$ionicViewSwitcher', [
  4134. '$timeout',
  4135. '$document',
  4136. '$q',
  4137. '$ionicClickBlock',
  4138. '$ionicConfig',
  4139. '$ionicNavBarDelegate',
  4140. function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
  4141. var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
  4142. var DATA_NO_CACHE = '$noCache';
  4143. var DATA_DESTROY_ELE = '$destroyEle';
  4144. var DATA_ELE_IDENTIFIER = '$eleId';
  4145. var DATA_VIEW_ACCESSED = '$accessed';
  4146. var DATA_FALLBACK_TIMER = '$fallbackTimer';
  4147. var DATA_VIEW = '$viewData';
  4148. var NAV_VIEW_ATTR = 'nav-view';
  4149. var VIEW_STATUS_ACTIVE = 'active';
  4150. var VIEW_STATUS_CACHED = 'cached';
  4151. var VIEW_STATUS_STAGED = 'stage';
  4152. var transitionCounter = 0;
  4153. var nextTransition, nextDirection;
  4154. ionic.transition = ionic.transition || {};
  4155. ionic.transition.isActive = false;
  4156. var isActiveTimer;
  4157. var cachedAttr = ionic.DomUtil.cachedAttr;
  4158. var transitionPromises = [];
  4159. var defaultTimeout = 1100;
  4160. var ionicViewSwitcher = {
  4161. create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
  4162. // get a reference to an entering/leaving element if they exist
  4163. // loop through to see if the view is already in the navViewElement
  4164. var enteringEle, leavingEle;
  4165. var transitionId = ++transitionCounter;
  4166. var alreadyInDom;
  4167. var switcher = {
  4168. init: function(registerData, callback) {
  4169. ionicViewSwitcher.isTransitioning(true);
  4170. switcher.loadViewElements(registerData);
  4171. switcher.render(registerData, function() {
  4172. callback && callback();
  4173. });
  4174. },
  4175. loadViewElements: function(registerData) {
  4176. var x, l, viewEle;
  4177. var viewElements = navViewCtrl.getViewElements();
  4178. var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
  4179. var navViewActiveEleId = navViewCtrl.activeEleId();
  4180. for (x = 0, l = viewElements.length; x < l; x++) {
  4181. viewEle = viewElements.eq(x);
  4182. if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
  4183. // we found an existing element in the DOM that should be entering the view
  4184. if (viewEle.data(DATA_NO_CACHE)) {
  4185. // the existing element should not be cached, don't use it
  4186. viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
  4187. viewEle.data(DATA_DESTROY_ELE, true);
  4188. } else {
  4189. enteringEle = viewEle;
  4190. }
  4191. } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
  4192. leavingEle = viewEle;
  4193. }
  4194. if (enteringEle && leavingEle) break;
  4195. }
  4196. alreadyInDom = !!enteringEle;
  4197. if (!alreadyInDom) {
  4198. // still no existing element to use
  4199. // create it using existing template/scope/locals
  4200. enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
  4201. // existing elements in the DOM are looked up by their state name and state id
  4202. enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
  4203. }
  4204. if (renderEnd) {
  4205. navViewCtrl.activeEleId(enteringEleIdentifier);
  4206. }
  4207. registerData.ele = null;
  4208. },
  4209. render: function(registerData, callback) {
  4210. if (alreadyInDom) {
  4211. // it was already found in the DOM, just reconnect the scope
  4212. ionic.Utils.reconnectScope(enteringEle.scope());
  4213. } else {
  4214. // the entering element is not already in the DOM
  4215. // set that the entering element should be "staged" and its
  4216. // styles of where this element will go before it hits the DOM
  4217. navViewAttr(enteringEle, VIEW_STATUS_STAGED);
  4218. var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
  4219. var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
  4220. transitionFn(enteringEle, null, enteringData.direction, true).run(0);
  4221. enteringEle.data(DATA_VIEW, {
  4222. viewId: enteringData.viewId,
  4223. historyId: enteringData.historyId,
  4224. stateName: enteringData.stateName,
  4225. stateParams: enteringData.stateParams
  4226. });
  4227. // if the current state has cache:false
  4228. // or the element has cache-view="false" attribute
  4229. if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
  4230. enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
  4231. enteringEle.data(DATA_NO_CACHE, true);
  4232. }
  4233. // append the entering element to the DOM, create a new scope and run link
  4234. var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
  4235. delete enteringData.direction;
  4236. delete enteringData.transition;
  4237. viewScope.$emit('$ionicView.loaded', enteringData);
  4238. }
  4239. // update that this view was just accessed
  4240. enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
  4241. callback && callback();
  4242. },
  4243. transition: function(direction, enableBack, allowAnimate) {
  4244. var deferred;
  4245. var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
  4246. var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
  4247. enteringData.transitionId = leavingData.transitionId = transitionId;
  4248. enteringData.fromCache = !!alreadyInDom;
  4249. enteringData.enableBack = !!enableBack;
  4250. enteringData.renderStart = renderStart;
  4251. enteringData.renderEnd = renderEnd;
  4252. cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
  4253. cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
  4254. // cancel any previous transition complete fallbacks
  4255. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4256. // get the transition ready and see if it'll animate
  4257. var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
  4258. var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,
  4259. enteringData.shouldAnimate && allowAnimate && renderEnd);
  4260. if (viewTransition.shouldAnimate) {
  4261. // attach transitionend events (and fallback timer)
  4262. enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);
  4263. enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));
  4264. $ionicClickBlock.show(defaultTimeout);
  4265. }
  4266. if (renderStart) {
  4267. // notify the views "before" the transition starts
  4268. switcher.emit('before', enteringData, leavingData);
  4269. // stage entering element, opacity 0, no transition duration
  4270. navViewAttr(enteringEle, VIEW_STATUS_STAGED);
  4271. // render the elements in the correct location for their starting point
  4272. viewTransition.run(0);
  4273. }
  4274. if (renderEnd) {
  4275. // create a promise so we can keep track of when all transitions finish
  4276. // only required if this transition should complete
  4277. deferred = $q.defer();
  4278. transitionPromises.push(deferred.promise);
  4279. }
  4280. if (renderStart && renderEnd) {
  4281. // CSS "auto" transitioned, not manually transitioned
  4282. // wait a frame so the styles apply before auto transitioning
  4283. $timeout(function() {
  4284. ionic.requestAnimationFrame(onReflow);
  4285. });
  4286. } else if (!renderEnd) {
  4287. // just the start of a manual transition
  4288. // but it will not render the end of the transition
  4289. navViewAttr(enteringEle, 'entering');
  4290. navViewAttr(leavingEle, 'leaving');
  4291. // return the transition run method so each step can be ran manually
  4292. return {
  4293. run: viewTransition.run,
  4294. cancel: function(shouldAnimate) {
  4295. if (shouldAnimate) {
  4296. enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
  4297. enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));
  4298. $ionicClickBlock.show(defaultTimeout);
  4299. } else {
  4300. cancelTransition();
  4301. }
  4302. viewTransition.shouldAnimate = shouldAnimate;
  4303. viewTransition.run(0);
  4304. viewTransition = null;
  4305. }
  4306. };
  4307. } else if (renderEnd) {
  4308. // just the end of a manual transition
  4309. // happens after the manual transition has completed
  4310. // and a full history change has happened
  4311. onReflow();
  4312. }
  4313. function onReflow() {
  4314. // remove that we're staging the entering element so it can auto transition
  4315. navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
  4316. navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
  4317. // start the auto transition and let the CSS take over
  4318. viewTransition.run(1);
  4319. // trigger auto transitions on the associated nav bars
  4320. $ionicNavBarDelegate._instances.forEach(function(instance) {
  4321. instance.triggerTransitionStart(transitionId);
  4322. });
  4323. if (!viewTransition.shouldAnimate) {
  4324. // no animated auto transition
  4325. transitionComplete();
  4326. }
  4327. }
  4328. // Make sure that transitionend events bubbling up from children won't fire
  4329. // transitionComplete. Will only go forward if ev.target == the element listening.
  4330. function completeOnTransitionEnd(ev) {
  4331. if (ev.target !== this) return;
  4332. transitionComplete();
  4333. }
  4334. function transitionComplete() {
  4335. if (transitionComplete.x) return;
  4336. transitionComplete.x = true;
  4337. enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);
  4338. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4339. leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
  4340. // resolve that this one transition (there could be many w/ nested views)
  4341. deferred && deferred.resolve(navViewCtrl);
  4342. // the most recent transition added has completed and all the active
  4343. // transition promises should be added to the services array of promises
  4344. if (transitionId === transitionCounter) {
  4345. $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
  4346. // emit that the views have finished transitioning
  4347. // each parent nav-view will update which views are active and cached
  4348. switcher.emit('after', enteringData, leavingData);
  4349. switcher.cleanup(enteringData);
  4350. }
  4351. // tell the nav bars that the transition has ended
  4352. $ionicNavBarDelegate._instances.forEach(function(instance) {
  4353. instance.triggerTransitionEnd();
  4354. });
  4355. // remove any references that could cause memory issues
  4356. nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
  4357. }
  4358. // Make sure that transitionend events bubbling up from children won't fire
  4359. // transitionComplete. Will only go forward if ev.target == the element listening.
  4360. function cancelOnTransitionEnd(ev) {
  4361. if (ev.target !== this) return;
  4362. cancelTransition();
  4363. }
  4364. function cancelTransition() {
  4365. navViewAttr(enteringEle, VIEW_STATUS_CACHED);
  4366. navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);
  4367. enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
  4368. $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
  4369. ionicViewSwitcher.transitionEnd([navViewCtrl]);
  4370. }
  4371. },
  4372. emit: function(step, enteringData, leavingData) {
  4373. var enteringScope = getScopeForElement(enteringEle, enteringData);
  4374. var leavingScope = getScopeForElement(leavingEle, leavingData);
  4375. var prefixesAreEqual;
  4376. if ( !enteringData.viewId || enteringData.abstractView ) {
  4377. // it's an abstract view, so treat it accordingly
  4378. // we only get access to the leaving scope once in the transition,
  4379. // so dispatch all events right away if it exists
  4380. if ( leavingScope ) {
  4381. leavingScope.$emit('$ionicView.beforeLeave', leavingData);
  4382. leavingScope.$emit('$ionicView.leave', leavingData);
  4383. leavingScope.$emit('$ionicView.afterLeave', leavingData);
  4384. leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);
  4385. leavingScope.$broadcast('$ionicParentView.leave', leavingData);
  4386. leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);
  4387. }
  4388. }
  4389. else {
  4390. // it's a regular view, so do the normal process
  4391. if (step == 'after') {
  4392. if (enteringScope) {
  4393. enteringScope.$emit('$ionicView.enter', enteringData);
  4394. enteringScope.$broadcast('$ionicParentView.enter', enteringData);
  4395. }
  4396. if (leavingScope) {
  4397. leavingScope.$emit('$ionicView.leave', leavingData);
  4398. leavingScope.$broadcast('$ionicParentView.leave', leavingData);
  4399. }
  4400. else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
  4401. // we only want to dispatch this when we are doing a single-tier
  4402. // state change such as changing a tab, so compare the state
  4403. // for the same state-prefix but different suffix
  4404. prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
  4405. if ( prefixesAreEqual ) {
  4406. enteringScope.$emit('$ionicNavView.leave', leavingData);
  4407. }
  4408. }
  4409. }
  4410. if (enteringScope) {
  4411. enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
  4412. enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);
  4413. }
  4414. if (leavingScope) {
  4415. leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
  4416. leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);
  4417. } else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
  4418. // we only want to dispatch this when we are doing a single-tier
  4419. // state change such as changing a tab, so compare the state
  4420. // for the same state-prefix but different suffix
  4421. prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
  4422. if ( prefixesAreEqual ) {
  4423. enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
  4424. }
  4425. }
  4426. }
  4427. },
  4428. cleanup: function(transData) {
  4429. // check if any views should be removed
  4430. if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
  4431. // if they just navigated back we can destroy the forward view
  4432. // do not remove forward views if cacheForwardViews config is true
  4433. destroyViewEle(leavingEle);
  4434. }
  4435. var viewElements = navViewCtrl.getViewElements();
  4436. var viewElementsLength = viewElements.length;
  4437. var x, viewElement;
  4438. var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
  4439. var removableEle;
  4440. var oldestAccess = Date.now();
  4441. for (x = 0; x < viewElementsLength; x++) {
  4442. viewElement = viewElements.eq(x);
  4443. if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
  4444. // remember what was the oldest element to be accessed so it can be destroyed
  4445. oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
  4446. removableEle = viewElements.eq(x);
  4447. } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
  4448. destroyViewEle(viewElement);
  4449. }
  4450. }
  4451. destroyViewEle(removableEle);
  4452. if (enteringEle.data(DATA_NO_CACHE)) {
  4453. enteringEle.data(DATA_DESTROY_ELE, true);
  4454. }
  4455. },
  4456. enteringEle: function() { return enteringEle; },
  4457. leavingEle: function() { return leavingEle; }
  4458. };
  4459. return switcher;
  4460. },
  4461. transitionEnd: function(navViewCtrls) {
  4462. forEach(navViewCtrls, function(navViewCtrl) {
  4463. navViewCtrl.transitionEnd();
  4464. });
  4465. ionicViewSwitcher.isTransitioning(false);
  4466. $ionicClickBlock.hide();
  4467. transitionPromises = [];
  4468. },
  4469. nextTransition: function(val) {
  4470. nextTransition = val;
  4471. },
  4472. nextDirection: function(val) {
  4473. nextDirection = val;
  4474. },
  4475. isTransitioning: function(val) {
  4476. if (arguments.length) {
  4477. ionic.transition.isActive = !!val;
  4478. $timeout.cancel(isActiveTimer);
  4479. if (val) {
  4480. isActiveTimer = $timeout(function() {
  4481. ionicViewSwitcher.isTransitioning(false);
  4482. }, 999);
  4483. }
  4484. }
  4485. return ionic.transition.isActive;
  4486. },
  4487. createViewEle: function(viewLocals) {
  4488. var containerEle = $document[0].createElement('div');
  4489. if (viewLocals && viewLocals.$template) {
  4490. containerEle.innerHTML = viewLocals.$template;
  4491. if (containerEle.children.length === 1) {
  4492. containerEle.children[0].classList.add('pane');
  4493. if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {
  4494. angular.element(containerEle.children[0]).attr("abstract", "true");
  4495. }
  4496. else {
  4497. if ( viewLocals.$$state && viewLocals.$$state.self ) {
  4498. angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);
  4499. }
  4500. }
  4501. return jqLite(containerEle.children[0]);
  4502. }
  4503. }
  4504. containerEle.className = "pane";
  4505. return jqLite(containerEle);
  4506. },
  4507. viewEleIsActive: function(viewEle, isActiveAttr) {
  4508. navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
  4509. },
  4510. getTransitionData: getTransitionData,
  4511. navViewAttr: navViewAttr,
  4512. destroyViewEle: destroyViewEle
  4513. };
  4514. return ionicViewSwitcher;
  4515. function getViewElementIdentifier(locals, view) {
  4516. if (viewState(locals)['abstract']) return viewState(locals).name;
  4517. if (view) return view.stateId || view.viewId;
  4518. return ionic.Utils.nextUid();
  4519. }
  4520. function viewState(locals) {
  4521. return locals && locals.$$state && locals.$$state.self || {};
  4522. }
  4523. function getTransitionData(viewLocals, enteringEle, direction, view) {
  4524. // Priority
  4525. // 1) attribute directive on the button/link to this view
  4526. // 2) entering element's attribute
  4527. // 3) entering view's $state config property
  4528. // 4) view registration data
  4529. // 5) global config
  4530. // 6) fallback value
  4531. var state = viewState(viewLocals);
  4532. var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
  4533. var navBarTransition = $ionicConfig.navBar.transition();
  4534. direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
  4535. return extend(getViewData(view), {
  4536. transition: viewTransition,
  4537. navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
  4538. direction: direction,
  4539. shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
  4540. });
  4541. }
  4542. function getViewData(view) {
  4543. view = view || {};
  4544. return {
  4545. viewId: view.viewId,
  4546. historyId: view.historyId,
  4547. stateId: view.stateId,
  4548. stateName: view.stateName,
  4549. stateParams: view.stateParams
  4550. };
  4551. }
  4552. function navViewAttr(ele, value) {
  4553. if (arguments.length > 1) {
  4554. cachedAttr(ele, NAV_VIEW_ATTR, value);
  4555. } else {
  4556. return cachedAttr(ele, NAV_VIEW_ATTR);
  4557. }
  4558. }
  4559. function destroyViewEle(ele) {
  4560. // we found an element that should be removed
  4561. // destroy its scope, then remove the element
  4562. if (ele && ele.length) {
  4563. var viewScope = ele.scope();
  4564. if (viewScope) {
  4565. viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
  4566. viewScope.$destroy();
  4567. }
  4568. ele.remove();
  4569. }
  4570. }
  4571. function compareStatePrefixes(enteringStateName, exitingStateName) {
  4572. var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');
  4573. var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');
  4574. // if either of the prefixes are empty, just return false
  4575. if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {
  4576. return false;
  4577. }
  4578. var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);
  4579. var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);
  4580. return enteringPrefix === exitingPrefix;
  4581. }
  4582. function getScopeForElement(element, stateData) {
  4583. if ( !element ) {
  4584. return null;
  4585. }
  4586. // check if it's abstract
  4587. var attributeValue = angular.element(element).attr("abstract");
  4588. var stateValue = angular.element(element).attr("state");
  4589. if ( attributeValue !== "true" ) {
  4590. // it's not an abstract view, so make sure the element
  4591. // matches the state. Due to abstract view weirdness,
  4592. // sometimes it doesn't. If it doesn't, don't dispatch events
  4593. // so leave the scope undefined
  4594. if ( stateValue === stateData.stateName ) {
  4595. return angular.element(element).scope();
  4596. }
  4597. return null;
  4598. }
  4599. else {
  4600. // it is an abstract element, so look for element with the "state" attributeValue
  4601. // set to the name of the stateData state
  4602. var elements = aggregateNavViewChildren(element);
  4603. for ( var i = 0; i < elements.length; i++ ) {
  4604. var state = angular.element(elements[i]).attr("state");
  4605. if ( state === stateData.stateName ) {
  4606. stateData.abstractView = true;
  4607. return angular.element(elements[i]).scope();
  4608. }
  4609. }
  4610. // we didn't find a match, so return null
  4611. return null;
  4612. }
  4613. }
  4614. function aggregateNavViewChildren(element) {
  4615. var aggregate = [];
  4616. var navViews = angular.element(element).find("ion-nav-view");
  4617. for ( var i = 0; i < navViews.length; i++ ) {
  4618. var children = angular.element(navViews[i]).children();
  4619. var childrenAggregated = [];
  4620. for ( var j = 0; j < children.length; j++ ) {
  4621. childrenAggregated = childrenAggregated.concat(children[j]);
  4622. }
  4623. aggregate = aggregate.concat(childrenAggregated);
  4624. }
  4625. return aggregate;
  4626. }
  4627. }]);
  4628. /**
  4629. * ================== angular-ios9-uiwebview.patch.js v1.1.1 ==================
  4630. *
  4631. * This patch works around iOS9 UIWebView regression that causes infinite digest
  4632. * errors in Angular.
  4633. *
  4634. * The patch can be applied to Angular 1.2.0 1.4.5. Newer versions of Angular
  4635. * have the workaround baked in.
  4636. *
  4637. * To apply this patch load/bundle this file with your application and add a
  4638. * dependency on the "ngIOS9UIWebViewPatch" module to your main app module.
  4639. *
  4640. * For example:
  4641. *
  4642. * ```
  4643. * angular.module('myApp', ['ngRoute'])`
  4644. * ```
  4645. *
  4646. * becomes
  4647. *
  4648. * ```
  4649. * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch'])
  4650. * ```
  4651. *
  4652. *
  4653. * More info:
  4654. * - https://openradar.appspot.com/22186109
  4655. * - https://github.com/angular/angular.js/issues/12241
  4656. * - https://github.com/driftyco/ionic/issues/4082
  4657. *
  4658. *
  4659. * @license AngularJS
  4660. * (c) 2010-2015 Google, Inc. http://angularjs.org
  4661. * License: MIT
  4662. */
  4663. angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {
  4664. 'use strict';
  4665. $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {
  4666. if (isIOS9UIWebView($window.navigator.userAgent)) {
  4667. return applyIOS9Shim($delegate);
  4668. }
  4669. return $delegate;
  4670. function isIOS9UIWebView(userAgent) {
  4671. return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
  4672. }
  4673. function applyIOS9Shim(browser) {
  4674. var pendingLocationUrl = null;
  4675. var originalUrlFn = browser.url;
  4676. browser.url = function() {
  4677. if (arguments.length) {
  4678. pendingLocationUrl = arguments[0];
  4679. return originalUrlFn.apply(browser, arguments);
  4680. }
  4681. return pendingLocationUrl || originalUrlFn.apply(browser, arguments);
  4682. };
  4683. window.addEventListener('popstate', clearPendingLocationUrl, false);
  4684. window.addEventListener('hashchange', clearPendingLocationUrl, false);
  4685. function clearPendingLocationUrl() {
  4686. pendingLocationUrl = null;
  4687. }
  4688. return browser;
  4689. }
  4690. }]);
  4691. }]);
  4692. /**
  4693. * @private
  4694. * Parts of Ionic requires that $scope data is attached to the element.
  4695. * We do not want to disable adding $scope data to the $element when
  4696. * $compileProvider.debugInfoEnabled(false) is used.
  4697. */
  4698. IonicModule.config(['$provide', function($provide) {
  4699. $provide.decorator('$compile', ['$delegate', function($compile) {
  4700. $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
  4701. var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
  4702. $element.data(dataName, scope);
  4703. };
  4704. return $compile;
  4705. }]);
  4706. }]);
  4707. /**
  4708. * @private
  4709. */
  4710. IonicModule.config([
  4711. '$provide',
  4712. function($provide) {
  4713. function $LocationDecorator($location, $timeout) {
  4714. $location.__hash = $location.hash;
  4715. //Fix: when window.location.hash is set, the scrollable area
  4716. //found nearest to body's scrollTop is set to scroll to an element
  4717. //with that ID.
  4718. $location.hash = function(value) {
  4719. if (isDefined(value) && value.length > 0) {
  4720. $timeout(function() {
  4721. var scroll = document.querySelector('.scroll-content');
  4722. if (scroll) {
  4723. scroll.scrollTop = 0;
  4724. }
  4725. }, 0, false);
  4726. }
  4727. return $location.__hash(value);
  4728. };
  4729. return $location;
  4730. }
  4731. $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
  4732. }]);
  4733. IonicModule
  4734. .controller('$ionicHeaderBar', [
  4735. '$scope',
  4736. '$element',
  4737. '$attrs',
  4738. '$q',
  4739. '$ionicConfig',
  4740. '$ionicHistory',
  4741. function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
  4742. var TITLE = 'title';
  4743. var BACK_TEXT = 'back-text';
  4744. var BACK_BUTTON = 'back-button';
  4745. var DEFAULT_TITLE = 'default-title';
  4746. var PREVIOUS_TITLE = 'previous-title';
  4747. var HIDE = 'hide';
  4748. var self = this;
  4749. var titleText = '';
  4750. var previousTitleText = '';
  4751. var titleLeft = 0;
  4752. var titleRight = 0;
  4753. var titleCss = '';
  4754. var isBackEnabled = false;
  4755. var isBackShown = true;
  4756. var isNavBackShown = true;
  4757. var isBackElementShown = false;
  4758. var titleTextWidth = 0;
  4759. self.beforeEnter = function(viewData) {
  4760. $scope.$broadcast('$ionicView.beforeEnter', viewData);
  4761. };
  4762. self.title = function(newTitleText) {
  4763. if (arguments.length && newTitleText !== titleText) {
  4764. getEle(TITLE).innerHTML = newTitleText;
  4765. titleText = newTitleText;
  4766. titleTextWidth = 0;
  4767. }
  4768. return titleText;
  4769. };
  4770. self.enableBack = function(shouldEnable, disableReset) {
  4771. // whether or not the back button show be visible, according
  4772. // to the navigation and history
  4773. if (arguments.length) {
  4774. isBackEnabled = shouldEnable;
  4775. if (!disableReset) self.updateBackButton();
  4776. }
  4777. return isBackEnabled;
  4778. };
  4779. self.showBack = function(shouldShow, disableReset) {
  4780. // different from enableBack() because this will always have the back
  4781. // visually hidden if false, even if the history says it should show
  4782. if (arguments.length) {
  4783. isBackShown = shouldShow;
  4784. if (!disableReset) self.updateBackButton();
  4785. }
  4786. return isBackShown;
  4787. };
  4788. self.showNavBack = function(shouldShow) {
  4789. // different from showBack() because this is for the entire nav bar's
  4790. // setting for all of it's child headers. For internal use.
  4791. isNavBackShown = shouldShow;
  4792. self.updateBackButton();
  4793. };
  4794. self.updateBackButton = function() {
  4795. var ele;
  4796. if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
  4797. isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
  4798. ele = getEle(BACK_BUTTON);
  4799. ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
  4800. }
  4801. if (isBackEnabled) {
  4802. ele = ele || getEle(BACK_BUTTON);
  4803. if (ele) {
  4804. if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
  4805. ele = getEle(BACK_BUTTON + ' .icon');
  4806. if (ele) {
  4807. self.backButtonIcon = $ionicConfig.backButton.icon();
  4808. ele.className = 'icon ' + self.backButtonIcon;
  4809. }
  4810. }
  4811. if (self.backButtonText !== $ionicConfig.backButton.text()) {
  4812. ele = getEle(BACK_BUTTON + ' .back-text');
  4813. if (ele) {
  4814. ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
  4815. }
  4816. }
  4817. }
  4818. }
  4819. };
  4820. self.titleTextWidth = function() {
  4821. var element = getEle(TITLE);
  4822. if ( element ) {
  4823. // If the element has a nav-bar-title, use that instead
  4824. // to calculate the width of the title
  4825. var children = angular.element(element).children();
  4826. for ( var i = 0; i < children.length; i++ ) {
  4827. if ( angular.element(children[i]).hasClass('nav-bar-title') ) {
  4828. element = children[i];
  4829. break;
  4830. }
  4831. }
  4832. }
  4833. var bounds = ionic.DomUtil.getTextBounds(element);
  4834. titleTextWidth = Math.min(bounds && bounds.width || 30);
  4835. return titleTextWidth;
  4836. };
  4837. self.titleWidth = function() {
  4838. var titleWidth = self.titleTextWidth();
  4839. var offsetWidth = getEle(TITLE).offsetWidth;
  4840. if (offsetWidth < titleWidth) {
  4841. titleWidth = offsetWidth + (titleLeft - titleRight - 5);
  4842. }
  4843. return titleWidth;
  4844. };
  4845. self.titleTextX = function() {
  4846. return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
  4847. };
  4848. self.titleLeftRight = function() {
  4849. return titleLeft - titleRight;
  4850. };
  4851. self.backButtonTextLeft = function() {
  4852. var offsetLeft = 0;
  4853. var ele = getEle(BACK_TEXT);
  4854. while (ele) {
  4855. offsetLeft += ele.offsetLeft;
  4856. ele = ele.parentElement;
  4857. }
  4858. return offsetLeft;
  4859. };
  4860. self.resetBackButton = function(viewData) {
  4861. if ($ionicConfig.backButton.previousTitleText()) {
  4862. var previousTitleEle = getEle(PREVIOUS_TITLE);
  4863. if (previousTitleEle) {
  4864. previousTitleEle.classList.remove(HIDE);
  4865. var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
  4866. var newPreviousTitleText = $ionicHistory.backTitle(view);
  4867. if (newPreviousTitleText !== previousTitleText) {
  4868. previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
  4869. }
  4870. }
  4871. var defaultTitleEle = getEle(DEFAULT_TITLE);
  4872. if (defaultTitleEle) {
  4873. defaultTitleEle.classList.remove(HIDE);
  4874. }
  4875. }
  4876. };
  4877. self.align = function(textAlign) {
  4878. var titleEle = getEle(TITLE);
  4879. textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
  4880. var widths = self.calcWidths(textAlign, false);
  4881. if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
  4882. var previousTitleWidths = self.calcWidths(textAlign, true);
  4883. var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
  4884. if (self.titleTextWidth() <= availableTitleWidth) {
  4885. widths = previousTitleWidths;
  4886. }
  4887. }
  4888. return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
  4889. };
  4890. self.calcWidths = function(textAlign, isPreviousTitle) {
  4891. var titleEle = getEle(TITLE);
  4892. var backBtnEle = getEle(BACK_BUTTON);
  4893. var x, y, z, b, c, d, childSize, bounds;
  4894. var childNodes = $element[0].childNodes;
  4895. var buttonsLeft = 0;
  4896. var buttonsRight = 0;
  4897. var isCountRightOfTitle;
  4898. var updateTitleLeft = 0;
  4899. var updateTitleRight = 0;
  4900. var updateCss = '';
  4901. var backButtonWidth = 0;
  4902. // Compute how wide the left children are
  4903. // Skip all titles (there may still be two titles, one leaving the dom)
  4904. // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
  4905. for (x = 0; x < childNodes.length; x++) {
  4906. c = childNodes[x];
  4907. childSize = 0;
  4908. if (c.nodeType == 1) {
  4909. // element node
  4910. if (c === titleEle) {
  4911. isCountRightOfTitle = true;
  4912. continue;
  4913. }
  4914. if (c.classList.contains(HIDE)) {
  4915. continue;
  4916. }
  4917. if (isBackShown && c === backBtnEle) {
  4918. for (y = 0; y < c.childNodes.length; y++) {
  4919. b = c.childNodes[y];
  4920. if (b.nodeType == 1) {
  4921. if (b.classList.contains(BACK_TEXT)) {
  4922. for (z = 0; z < b.children.length; z++) {
  4923. d = b.children[z];
  4924. if (isPreviousTitle) {
  4925. if (d.classList.contains(DEFAULT_TITLE)) continue;
  4926. backButtonWidth += d.offsetWidth;
  4927. } else {
  4928. if (d.classList.contains(PREVIOUS_TITLE)) continue;
  4929. backButtonWidth += d.offsetWidth;
  4930. }
  4931. }
  4932. } else {
  4933. backButtonWidth += b.offsetWidth;
  4934. }
  4935. } else if (b.nodeType == 3 && b.nodeValue.trim()) {
  4936. bounds = ionic.DomUtil.getTextBounds(b);
  4937. backButtonWidth += bounds && bounds.width || 0;
  4938. }
  4939. }
  4940. childSize = backButtonWidth || c.offsetWidth;
  4941. } else {
  4942. // not the title, not the back button, not a hidden element
  4943. childSize = c.offsetWidth;
  4944. }
  4945. } else if (c.nodeType == 3 && c.nodeValue.trim()) {
  4946. // text node
  4947. bounds = ionic.DomUtil.getTextBounds(c);
  4948. childSize = bounds && bounds.width || 0;
  4949. }
  4950. if (isCountRightOfTitle) {
  4951. buttonsRight += childSize;
  4952. } else {
  4953. buttonsLeft += childSize;
  4954. }
  4955. }
  4956. // Size and align the header titleEle based on the sizes of the left and
  4957. // right children, and the desired alignment mode
  4958. if (textAlign == 'left') {
  4959. updateCss = 'title-left';
  4960. if (buttonsLeft) {
  4961. updateTitleLeft = buttonsLeft + 15;
  4962. }
  4963. if (buttonsRight) {
  4964. updateTitleRight = buttonsRight + 15;
  4965. }
  4966. } else if (textAlign == 'right') {
  4967. updateCss = 'title-right';
  4968. if (buttonsLeft) {
  4969. updateTitleLeft = buttonsLeft + 15;
  4970. }
  4971. if (buttonsRight) {
  4972. updateTitleRight = buttonsRight + 15;
  4973. }
  4974. } else {
  4975. // center the default
  4976. var margin = Math.max(buttonsLeft, buttonsRight) + 10;
  4977. if (margin > 10) {
  4978. updateTitleLeft = updateTitleRight = margin;
  4979. }
  4980. }
  4981. return {
  4982. backButtonWidth: backButtonWidth,
  4983. buttonsLeft: buttonsLeft,
  4984. buttonsRight: buttonsRight,
  4985. titleLeft: updateTitleLeft,
  4986. titleRight: updateTitleRight,
  4987. showPrevTitle: isPreviousTitle,
  4988. css: updateCss
  4989. };
  4990. };
  4991. self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
  4992. var deferred = $q.defer();
  4993. // only make DOM updates when there are actual changes
  4994. if (titleEle) {
  4995. if (updateTitleLeft !== titleLeft) {
  4996. titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
  4997. titleLeft = updateTitleLeft;
  4998. }
  4999. if (updateTitleRight !== titleRight) {
  5000. titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
  5001. titleRight = updateTitleRight;
  5002. }
  5003. if (updateCss !== titleCss) {
  5004. updateCss && titleEle.classList.add(updateCss);
  5005. titleCss && titleEle.classList.remove(titleCss);
  5006. titleCss = updateCss;
  5007. }
  5008. }
  5009. if ($ionicConfig.backButton.previousTitleText()) {
  5010. var prevTitle = getEle(PREVIOUS_TITLE);
  5011. var defaultTitle = getEle(DEFAULT_TITLE);
  5012. prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
  5013. defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
  5014. }
  5015. ionic.requestAnimationFrame(function() {
  5016. if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
  5017. var minRight = buttonsRight + 5;
  5018. var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
  5019. updateTitleRight = testRight < minRight ? minRight : testRight;
  5020. if (updateTitleRight !== titleRight) {
  5021. titleEle.style.right = updateTitleRight + 'px';
  5022. titleRight = updateTitleRight;
  5023. }
  5024. }
  5025. deferred.resolve();
  5026. });
  5027. return deferred.promise;
  5028. };
  5029. self.setCss = function(elementClassname, css) {
  5030. ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
  5031. };
  5032. var eleCache = {};
  5033. function getEle(className) {
  5034. if (!eleCache[className]) {
  5035. eleCache[className] = $element[0].querySelector('.' + className);
  5036. }
  5037. return eleCache[className];
  5038. }
  5039. $scope.$on('$destroy', function() {
  5040. for (var n in eleCache) eleCache[n] = null;
  5041. });
  5042. }]);
  5043. IonicModule
  5044. .controller('$ionInfiniteScroll', [
  5045. '$scope',
  5046. '$attrs',
  5047. '$element',
  5048. '$timeout',
  5049. function($scope, $attrs, $element, $timeout) {
  5050. var self = this;
  5051. self.isLoading = false;
  5052. $scope.icon = function() {
  5053. return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';
  5054. };
  5055. $scope.spinner = function() {
  5056. return isDefined($attrs.spinner) ? $attrs.spinner : '';
  5057. };
  5058. $scope.$on('scroll.infiniteScrollComplete', function() {
  5059. finishInfiniteScroll();
  5060. });
  5061. $scope.$on('$destroy', function() {
  5062. if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);
  5063. if (self.scrollEl && self.scrollEl.removeEventListener) {
  5064. self.scrollEl.removeEventListener('scroll', self.checkBounds);
  5065. }
  5066. });
  5067. // debounce checking infinite scroll events
  5068. self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);
  5069. function onInfinite() {
  5070. ionic.requestAnimationFrame(function() {
  5071. $element[0].classList.add('active');
  5072. });
  5073. self.isLoading = true;
  5074. $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
  5075. }
  5076. function finishInfiniteScroll() {
  5077. ionic.requestAnimationFrame(function() {
  5078. $element[0].classList.remove('active');
  5079. });
  5080. $timeout(function() {
  5081. if (self.jsScrolling) self.scrollView.resize();
  5082. // only check bounds again immediately if the page isn't cached (scroll el has height)
  5083. if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||
  5084. !self.jsScrolling) {
  5085. self.checkBounds();
  5086. }
  5087. }, 30, false);
  5088. self.isLoading = false;
  5089. }
  5090. // check if we've scrolled far enough to trigger an infinite scroll
  5091. function checkInfiniteBounds() {
  5092. if (self.isLoading) return;
  5093. var maxScroll = {};
  5094. if (self.jsScrolling) {
  5095. maxScroll = self.getJSMaxScroll();
  5096. var scrollValues = self.scrollView.getValues();
  5097. if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
  5098. (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
  5099. onInfinite();
  5100. }
  5101. } else {
  5102. maxScroll = self.getNativeMaxScroll();
  5103. if ((
  5104. maxScroll.left !== -1 &&
  5105. self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth
  5106. ) || (
  5107. maxScroll.top !== -1 &&
  5108. self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight
  5109. )) {
  5110. onInfinite();
  5111. }
  5112. }
  5113. }
  5114. // determine the threshold at which we should fire an infinite scroll
  5115. // note: this gets processed every scroll event, can it be cached?
  5116. self.getJSMaxScroll = function() {
  5117. var maxValues = self.scrollView.getScrollMax();
  5118. return {
  5119. left: self.scrollView.options.scrollingX ?
  5120. calculateMaxValue(maxValues.left) :
  5121. -1,
  5122. top: self.scrollView.options.scrollingY ?
  5123. calculateMaxValue(maxValues.top) :
  5124. -1
  5125. };
  5126. };
  5127. self.getNativeMaxScroll = function() {
  5128. var maxValues = {
  5129. left: self.scrollEl.scrollWidth,
  5130. top: self.scrollEl.scrollHeight
  5131. };
  5132. var computedStyle = window.getComputedStyle(self.scrollEl) || {};
  5133. return {
  5134. left: maxValues.left &&
  5135. (computedStyle.overflowX === 'scroll' ||
  5136. computedStyle.overflowX === 'auto' ||
  5137. self.scrollEl.style['overflow-x'] === 'scroll') ?
  5138. calculateMaxValue(maxValues.left) : -1,
  5139. top: maxValues.top &&
  5140. (computedStyle.overflowY === 'scroll' ||
  5141. computedStyle.overflowY === 'auto' ||
  5142. self.scrollEl.style['overflow-y'] === 'scroll' ) ?
  5143. calculateMaxValue(maxValues.top) : -1
  5144. };
  5145. };
  5146. // determine pixel refresh distance based on % or value
  5147. function calculateMaxValue(maximum) {
  5148. var distance = ($attrs.distance || '2.5%').trim();
  5149. var isPercent = distance.indexOf('%') !== -1;
  5150. return isPercent ?
  5151. maximum * (1 - parseFloat(distance) / 100) :
  5152. maximum - parseFloat(distance);
  5153. }
  5154. //for testing
  5155. self.__finishInfiniteScroll = finishInfiniteScroll;
  5156. }]);
  5157. /**
  5158. * @ngdoc service
  5159. * @name $ionicListDelegate
  5160. * @module ionic
  5161. *
  5162. * @description
  5163. * Delegate for controlling the {@link ionic.directive:ionList} directive.
  5164. *
  5165. * Methods called directly on the $ionicListDelegate service will control all lists.
  5166. * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
  5167. * method to control specific ionList instances.
  5168. *
  5169. * @usage
  5170. * ```html
  5171. * {% raw %}
  5172. * <ion-content ng-controller="MyCtrl">
  5173. * <button class="button" ng-click="showDeleteButtons()"></button>
  5174. * <ion-list>
  5175. * <ion-item ng-repeat="i in items">
  5176. * Hello, {{i}}!
  5177. * <ion-delete-button class="ion-minus-circled"></ion-delete-button>
  5178. * </ion-item>
  5179. * </ion-list>
  5180. * </ion-content>
  5181. * {% endraw %}
  5182. * ```
  5183. * ```js
  5184. * function MyCtrl($scope, $ionicListDelegate) {
  5185. * $scope.showDeleteButtons = function() {
  5186. * $ionicListDelegate.showDelete(true);
  5187. * };
  5188. * }
  5189. * ```
  5190. */
  5191. IonicModule.service('$ionicListDelegate', ionic.DelegateService([
  5192. /**
  5193. * @ngdoc method
  5194. * @name $ionicListDelegate#showReorder
  5195. * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
  5196. * @returns {boolean} Whether the reorder buttons are shown.
  5197. */
  5198. 'showReorder',
  5199. /**
  5200. * @ngdoc method
  5201. * @name $ionicListDelegate#showDelete
  5202. * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
  5203. * @returns {boolean} Whether the delete buttons are shown.
  5204. */
  5205. 'showDelete',
  5206. /**
  5207. * @ngdoc method
  5208. * @name $ionicListDelegate#canSwipeItems
  5209. * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
  5210. * option buttons.
  5211. * @returns {boolean} Whether the list is able to swipe to show option buttons.
  5212. */
  5213. 'canSwipeItems',
  5214. /**
  5215. * @ngdoc method
  5216. * @name $ionicListDelegate#closeOptionButtons
  5217. * @description Closes any option buttons on the list that are swiped open.
  5218. */
  5219. 'closeOptionButtons'
  5220. /**
  5221. * @ngdoc method
  5222. * @name $ionicListDelegate#$getByHandle
  5223. * @param {string} handle
  5224. * @returns `delegateInstance` A delegate instance that controls only the
  5225. * {@link ionic.directive:ionList} directives with `delegate-handle` matching
  5226. * the given handle.
  5227. *
  5228. * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
  5229. */
  5230. ]))
  5231. .controller('$ionicList', [
  5232. '$scope',
  5233. '$attrs',
  5234. '$ionicListDelegate',
  5235. '$ionicHistory',
  5236. function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
  5237. var self = this;
  5238. var isSwipeable = true;
  5239. var isReorderShown = false;
  5240. var isDeleteShown = false;
  5241. var deregisterInstance = $ionicListDelegate._registerInstance(
  5242. self, $attrs.delegateHandle, function() {
  5243. return $ionicHistory.isActiveScope($scope);
  5244. }
  5245. );
  5246. $scope.$on('$destroy', deregisterInstance);
  5247. self.showReorder = function(show) {
  5248. if (arguments.length) {
  5249. isReorderShown = !!show;
  5250. }
  5251. return isReorderShown;
  5252. };
  5253. self.showDelete = function(show) {
  5254. if (arguments.length) {
  5255. isDeleteShown = !!show;
  5256. }
  5257. return isDeleteShown;
  5258. };
  5259. self.canSwipeItems = function(can) {
  5260. if (arguments.length) {
  5261. isSwipeable = !!can;
  5262. }
  5263. return isSwipeable;
  5264. };
  5265. self.closeOptionButtons = function() {
  5266. self.listView && self.listView.clearDragEffects();
  5267. };
  5268. }]);
  5269. IonicModule
  5270. .controller('$ionicNavBar', [
  5271. '$scope',
  5272. '$element',
  5273. '$attrs',
  5274. '$compile',
  5275. '$timeout',
  5276. '$ionicNavBarDelegate',
  5277. '$ionicConfig',
  5278. '$ionicHistory',
  5279. function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
  5280. var CSS_HIDE = 'hide';
  5281. var DATA_NAV_BAR_CTRL = '$ionNavBarController';
  5282. var PRIMARY_BUTTONS = 'primaryButtons';
  5283. var SECONDARY_BUTTONS = 'secondaryButtons';
  5284. var BACK_BUTTON = 'backButton';
  5285. var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
  5286. var self = this;
  5287. var headerBars = [];
  5288. var navElementHtml = {};
  5289. var isVisible = true;
  5290. var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
  5291. $element.parent().data(DATA_NAV_BAR_CTRL, self);
  5292. var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
  5293. var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
  5294. self.init = function() {
  5295. $element.addClass('nav-bar-container');
  5296. ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
  5297. // create two nav bar blocks which will trade out which one is shown
  5298. self.createHeaderBar(false);
  5299. self.createHeaderBar(true);
  5300. $scope.$emit('ionNavBar.init', delegateHandle);
  5301. };
  5302. self.createHeaderBar = function(isActive) {
  5303. var containerEle = jqLite('<div class="nav-bar-block">');
  5304. ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
  5305. var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
  5306. var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);
  5307. if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
  5308. var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
  5309. var navEle = {};
  5310. var lastViewItemEle = {};
  5311. var leftButtonsEle, rightButtonsEle;
  5312. navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
  5313. navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
  5314. // append title in the header, this is the rock to where buttons append
  5315. headerBarEle.append(titleEle);
  5316. forEach(ITEM_TYPES, function(itemType) {
  5317. // create default button elements
  5318. navEle[itemType] = createNavElement(itemType);
  5319. // append and position buttons
  5320. positionItem(navEle[itemType], itemType);
  5321. });
  5322. // add header-item to the root children
  5323. for (var x = 0; x < headerBarEle[0].children.length; x++) {
  5324. headerBarEle[0].children[x].classList.add('header-item');
  5325. }
  5326. // compile header and append to the DOM
  5327. containerEle.append(headerBarEle);
  5328. $element.append($compile(containerEle)($scope.$new()));
  5329. var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
  5330. headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
  5331. headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
  5332. var headerBarInstance = {
  5333. isActive: isActive,
  5334. title: function(newTitleText) {
  5335. headerBarCtrl.title(newTitleText);
  5336. },
  5337. setItem: function(navBarItemEle, itemType) {
  5338. // first make sure any exiting nav bar item has been removed
  5339. headerBarInstance.removeItem(itemType);
  5340. if (navBarItemEle) {
  5341. if (itemType === 'title') {
  5342. // clear out the text based title
  5343. headerBarInstance.title("");
  5344. }
  5345. // there's a custom nav bar item
  5346. positionItem(navBarItemEle, itemType);
  5347. if (navEle[itemType]) {
  5348. // make sure the default on this itemType is hidden
  5349. navEle[itemType].addClass(CSS_HIDE);
  5350. }
  5351. lastViewItemEle[itemType] = navBarItemEle;
  5352. } else if (navEle[itemType]) {
  5353. // there's a default button for this side and no view button
  5354. navEle[itemType].removeClass(CSS_HIDE);
  5355. }
  5356. },
  5357. removeItem: function(itemType) {
  5358. if (lastViewItemEle[itemType]) {
  5359. lastViewItemEle[itemType].scope().$destroy();
  5360. lastViewItemEle[itemType].remove();
  5361. lastViewItemEle[itemType] = null;
  5362. }
  5363. },
  5364. containerEle: function() {
  5365. return containerEle;
  5366. },
  5367. headerBarEle: function() {
  5368. return headerBarEle;
  5369. },
  5370. afterLeave: function() {
  5371. forEach(ITEM_TYPES, function(itemType) {
  5372. headerBarInstance.removeItem(itemType);
  5373. });
  5374. headerBarCtrl.resetBackButton();
  5375. },
  5376. controller: function() {
  5377. return headerBarCtrl;
  5378. },
  5379. destroy: function() {
  5380. forEach(ITEM_TYPES, function(itemType) {
  5381. headerBarInstance.removeItem(itemType);
  5382. });
  5383. containerEle.scope().$destroy();
  5384. for (var n in navEle) {
  5385. if (navEle[n]) {
  5386. navEle[n].removeData();
  5387. navEle[n] = null;
  5388. }
  5389. }
  5390. leftButtonsEle && leftButtonsEle.removeData();
  5391. rightButtonsEle && rightButtonsEle.removeData();
  5392. titleEle.removeData();
  5393. headerBarEle.removeData();
  5394. containerEle.remove();
  5395. containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
  5396. }
  5397. };
  5398. function positionItem(ele, itemType) {
  5399. if (!ele) return;
  5400. if (itemType === 'title') {
  5401. // title element
  5402. titleEle.append(ele);
  5403. } else if (itemType == 'rightButtons' ||
  5404. (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
  5405. (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
  5406. // right side
  5407. if (!rightButtonsEle) {
  5408. rightButtonsEle = jqLite('<div class="buttons buttons-right">');
  5409. headerBarEle.append(rightButtonsEle);
  5410. }
  5411. if (itemType == SECONDARY_BUTTONS) {
  5412. rightButtonsEle.append(ele);
  5413. } else {
  5414. rightButtonsEle.prepend(ele);
  5415. }
  5416. } else {
  5417. // left side
  5418. if (!leftButtonsEle) {
  5419. leftButtonsEle = jqLite('<div class="buttons buttons-left">');
  5420. if (navEle[BACK_BUTTON]) {
  5421. navEle[BACK_BUTTON].after(leftButtonsEle);
  5422. } else {
  5423. headerBarEle.prepend(leftButtonsEle);
  5424. }
  5425. }
  5426. if (itemType == SECONDARY_BUTTONS) {
  5427. leftButtonsEle.append(ele);
  5428. } else {
  5429. leftButtonsEle.prepend(ele);
  5430. }
  5431. }
  5432. }
  5433. headerBars.push(headerBarInstance);
  5434. return headerBarInstance;
  5435. };
  5436. self.navElement = function(type, html) {
  5437. if (isDefined(html)) {
  5438. navElementHtml[type] = html;
  5439. }
  5440. return navElementHtml[type];
  5441. };
  5442. self.update = function(viewData) {
  5443. var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
  5444. viewData.transition = $ionicConfig.views.transition();
  5445. if (!showNavBar) {
  5446. viewData.direction = 'none';
  5447. }
  5448. self.enable(showNavBar);
  5449. var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
  5450. var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
  5451. var enteringHeaderCtrl = enteringHeaderBar.controller();
  5452. // update if the entering header should show the back button or not
  5453. enteringHeaderCtrl.enableBack(viewData.enableBack, true);
  5454. enteringHeaderCtrl.showBack(viewData.showBack, true);
  5455. enteringHeaderCtrl.updateBackButton();
  5456. // update the entering header bar's title
  5457. self.title(viewData.title, enteringHeaderBar);
  5458. self.showBar(showNavBar);
  5459. // update the nav bar items, depending if the view has their own or not
  5460. if (viewData.navBarItems) {
  5461. forEach(ITEM_TYPES, function(itemType) {
  5462. enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
  5463. });
  5464. }
  5465. // begin transition of entering and leaving header bars
  5466. self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
  5467. self.isInitialized = true;
  5468. navSwipeAttr('');
  5469. };
  5470. self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
  5471. var enteringHeaderBarCtrl = enteringHeaderBar.controller();
  5472. var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
  5473. var transitionId = viewData.transitionId;
  5474. enteringHeaderBarCtrl.beforeEnter(viewData);
  5475. var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
  5476. ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
  5477. ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
  5478. if (navBarTransition.shouldAnimate && viewData.renderEnd) {
  5479. navBarAttr(enteringHeaderBar, 'stage');
  5480. } else {
  5481. navBarAttr(enteringHeaderBar, 'entering');
  5482. navBarAttr(leavingHeaderBar, 'leaving');
  5483. }
  5484. enteringHeaderBarCtrl.resetBackButton(viewData);
  5485. navBarTransition.run(0);
  5486. self.activeTransition = {
  5487. run: function(step) {
  5488. navBarTransition.shouldAnimate = false;
  5489. navBarTransition.direction = 'back';
  5490. navBarTransition.run(step);
  5491. },
  5492. cancel: function(shouldAnimate, speed, cancelData) {
  5493. navSwipeAttr(speed);
  5494. navBarAttr(leavingHeaderBar, 'active');
  5495. navBarAttr(enteringHeaderBar, 'cached');
  5496. navBarTransition.shouldAnimate = shouldAnimate;
  5497. navBarTransition.run(0);
  5498. self.activeTransition = navBarTransition = null;
  5499. var runApply;
  5500. if (cancelData.showBar !== self.showBar()) {
  5501. self.showBar(cancelData.showBar);
  5502. }
  5503. if (cancelData.showBackButton !== self.showBackButton()) {
  5504. self.showBackButton(cancelData.showBackButton);
  5505. }
  5506. if (runApply) {
  5507. $scope.$apply();
  5508. }
  5509. },
  5510. complete: function(shouldAnimate, speed) {
  5511. navSwipeAttr(speed);
  5512. navBarTransition.shouldAnimate = shouldAnimate;
  5513. navBarTransition.run(1);
  5514. queuedTransitionEnd = transitionEnd;
  5515. }
  5516. };
  5517. $timeout(enteringHeaderBarCtrl.align, 16);
  5518. queuedTransitionStart = function() {
  5519. if (latestTransitionId !== transitionId) return;
  5520. navBarAttr(enteringHeaderBar, 'entering');
  5521. navBarAttr(leavingHeaderBar, 'leaving');
  5522. navBarTransition.run(1);
  5523. queuedTransitionEnd = function() {
  5524. if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
  5525. transitionEnd();
  5526. }
  5527. };
  5528. queuedTransitionStart = null;
  5529. };
  5530. function transitionEnd() {
  5531. for (var x = 0; x < headerBars.length; x++) {
  5532. headerBars[x].isActive = false;
  5533. }
  5534. enteringHeaderBar.isActive = true;
  5535. navBarAttr(enteringHeaderBar, 'active');
  5536. navBarAttr(leavingHeaderBar, 'cached');
  5537. self.activeTransition = navBarTransition = queuedTransitionEnd = null;
  5538. }
  5539. queuedTransitionStart();
  5540. };
  5541. self.triggerTransitionStart = function(triggerTransitionId) {
  5542. latestTransitionId = triggerTransitionId;
  5543. queuedTransitionStart && queuedTransitionStart();
  5544. };
  5545. self.triggerTransitionEnd = function() {
  5546. queuedTransitionEnd && queuedTransitionEnd();
  5547. };
  5548. self.showBar = function(shouldShow) {
  5549. if (arguments.length) {
  5550. self.visibleBar(shouldShow);
  5551. $scope.$parent.$hasHeader = !!shouldShow;
  5552. }
  5553. return !!$scope.$parent.$hasHeader;
  5554. };
  5555. self.visibleBar = function(shouldShow) {
  5556. if (shouldShow && !isVisible) {
  5557. $element.removeClass(CSS_HIDE);
  5558. self.align();
  5559. } else if (!shouldShow && isVisible) {
  5560. $element.addClass(CSS_HIDE);
  5561. }
  5562. isVisible = shouldShow;
  5563. };
  5564. self.enable = function(val) {
  5565. // set primary to show first
  5566. self.visibleBar(val);
  5567. // set non primary to hide second
  5568. for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
  5569. if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
  5570. }
  5571. };
  5572. /**
  5573. * @ngdoc method
  5574. * @name $ionicNavBar#showBackButton
  5575. * @description Show/hide the nav bar back button when there is a
  5576. * back view. If the back button is not possible, for example, the
  5577. * first view in the stack, then this will not force the back button
  5578. * to show.
  5579. */
  5580. self.showBackButton = function(shouldShow) {
  5581. if (arguments.length) {
  5582. for (var x = 0; x < headerBars.length; x++) {
  5583. headerBars[x].controller().showNavBack(!!shouldShow);
  5584. }
  5585. $scope.$isBackButtonShown = !!shouldShow;
  5586. }
  5587. return $scope.$isBackButtonShown;
  5588. };
  5589. /**
  5590. * @ngdoc method
  5591. * @name $ionicNavBar#showActiveBackButton
  5592. * @description Show/hide only the active header bar's back button.
  5593. */
  5594. self.showActiveBackButton = function(shouldShow) {
  5595. var headerBar = getOnScreenHeaderBar();
  5596. if (headerBar) {
  5597. if (arguments.length) {
  5598. return headerBar.controller().showBack(shouldShow);
  5599. }
  5600. return headerBar.controller().showBack();
  5601. }
  5602. };
  5603. self.title = function(newTitleText, headerBar) {
  5604. if (isDefined(newTitleText)) {
  5605. newTitleText = newTitleText || '';
  5606. headerBar = headerBar || getOnScreenHeaderBar();
  5607. headerBar && headerBar.title(newTitleText);
  5608. $scope.$title = newTitleText;
  5609. $ionicHistory.currentTitle(newTitleText);
  5610. }
  5611. return $scope.$title;
  5612. };
  5613. self.align = function(val, headerBar) {
  5614. headerBar = headerBar || getOnScreenHeaderBar();
  5615. headerBar && headerBar.controller().align(val);
  5616. };
  5617. self.hasTabsTop = function(isTabsTop) {
  5618. $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');
  5619. };
  5620. self.hasBarSubheader = function(isBarSubheader) {
  5621. $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');
  5622. };
  5623. // DEPRECATED, as of v1.0.0-beta14 -------
  5624. self.changeTitle = function(val) {
  5625. deprecatedWarning('changeTitle(val)', 'title(val)');
  5626. self.title(val);
  5627. };
  5628. self.setTitle = function(val) {
  5629. deprecatedWarning('setTitle(val)', 'title(val)');
  5630. self.title(val);
  5631. };
  5632. self.getTitle = function() {
  5633. deprecatedWarning('getTitle()', 'title()');
  5634. return self.title();
  5635. };
  5636. self.back = function() {
  5637. deprecatedWarning('back()', '$ionicHistory.goBack()');
  5638. $ionicHistory.goBack();
  5639. };
  5640. self.getPreviousTitle = function() {
  5641. deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
  5642. $ionicHistory.goBack();
  5643. };
  5644. function deprecatedWarning(oldMethod, newMethod) {
  5645. var warn = console.warn || console.log;
  5646. warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
  5647. }
  5648. // END DEPRECATED -------
  5649. function createNavElement(type) {
  5650. if (navElementHtml[type]) {
  5651. return jqLite(navElementHtml[type]);
  5652. }
  5653. }
  5654. function getOnScreenHeaderBar() {
  5655. for (var x = 0; x < headerBars.length; x++) {
  5656. if (headerBars[x].isActive) return headerBars[x];
  5657. }
  5658. }
  5659. function getOffScreenHeaderBar() {
  5660. for (var x = 0; x < headerBars.length; x++) {
  5661. if (!headerBars[x].isActive) return headerBars[x];
  5662. }
  5663. }
  5664. function navBarAttr(ctrl, val) {
  5665. ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
  5666. }
  5667. function navSwipeAttr(val) {
  5668. ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  5669. }
  5670. $scope.$on('$destroy', function() {
  5671. $scope.$parent.$hasHeader = false;
  5672. $element.parent().removeData(DATA_NAV_BAR_CTRL);
  5673. for (var x = 0; x < headerBars.length; x++) {
  5674. headerBars[x].destroy();
  5675. }
  5676. $element.remove();
  5677. $element = headerBars = null;
  5678. deregisterInstance();
  5679. });
  5680. }]);
  5681. IonicModule
  5682. .controller('$ionicNavView', [
  5683. '$scope',
  5684. '$element',
  5685. '$attrs',
  5686. '$compile',
  5687. '$controller',
  5688. '$ionicNavBarDelegate',
  5689. '$ionicNavViewDelegate',
  5690. '$ionicHistory',
  5691. '$ionicViewSwitcher',
  5692. '$ionicConfig',
  5693. '$ionicScrollDelegate',
  5694. '$ionicSideMenuDelegate',
  5695. function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) {
  5696. var DATA_ELE_IDENTIFIER = '$eleId';
  5697. var DATA_DESTROY_ELE = '$destroyEle';
  5698. var DATA_NO_CACHE = '$noCache';
  5699. var VIEW_STATUS_ACTIVE = 'active';
  5700. var VIEW_STATUS_CACHED = 'cached';
  5701. var self = this;
  5702. var direction;
  5703. var isPrimary = false;
  5704. var navBarDelegate;
  5705. var activeEleId;
  5706. var navViewAttr = $ionicViewSwitcher.navViewAttr;
  5707. var disableRenderStartViewId, disableAnimation;
  5708. self.scope = $scope;
  5709. self.element = $element;
  5710. self.init = function() {
  5711. var navViewName = $attrs.name || '';
  5712. // Find the details of the parent view directive (if any) and use it
  5713. // to derive our own qualified view name, then hang our own details
  5714. // off the DOM so child directives can find it.
  5715. var parent = $element.parent().inheritedData('$uiView');
  5716. var parentViewName = ((parent && parent.state) ? parent.state.name : '');
  5717. if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;
  5718. var viewData = { name: navViewName, state: null };
  5719. $element.data('$uiView', viewData);
  5720. var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
  5721. $scope.$on('$destroy', function() {
  5722. deregisterInstance();
  5723. // ensure no scrolls have been left frozen
  5724. if (self.isSwipeFreeze) {
  5725. $ionicScrollDelegate.freezeAllScrolls(false);
  5726. }
  5727. });
  5728. $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
  5729. $scope.$on('$ionicTabs.top', onTabsTop);
  5730. $scope.$on('$ionicSubheader', onBarSubheader);
  5731. $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
  5732. $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
  5733. $scope.$on('$ionicTabs.leave', onTabsLeave);
  5734. ionic.Platform.ready(function() {
  5735. if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) {
  5736. self.initSwipeBack();
  5737. }
  5738. });
  5739. return viewData;
  5740. };
  5741. self.register = function(viewLocals) {
  5742. var leavingView = extend({}, $ionicHistory.currentView());
  5743. // register that a view is coming in and get info on how it should transition
  5744. var registerData = $ionicHistory.register($scope, viewLocals);
  5745. // update which direction
  5746. self.update(registerData);
  5747. // begin rendering and transitioning
  5748. var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
  5749. var renderStart = (disableRenderStartViewId !== registerData.viewId);
  5750. self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
  5751. };
  5752. self.update = function(registerData) {
  5753. // always reset that this is the primary navView
  5754. isPrimary = true;
  5755. // remember what direction this navView should use
  5756. // this may get updated later by a child navView
  5757. direction = registerData.direction;
  5758. var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
  5759. if (parentNavViewCtrl) {
  5760. // this navView is nested inside another one
  5761. // update the parent to use this direction and not
  5762. // the other it originally was set to
  5763. // inform the parent navView that it is not the primary navView
  5764. parentNavViewCtrl.isPrimary(false);
  5765. if (direction === 'enter' || direction === 'exit') {
  5766. // they're entering/exiting a history
  5767. // find parent navViewController
  5768. parentNavViewCtrl.direction(direction);
  5769. if (direction === 'enter') {
  5770. // reset the direction so this navView doesn't animate
  5771. // because it's parent will
  5772. direction = 'none';
  5773. }
  5774. }
  5775. }
  5776. };
  5777. self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
  5778. // register the view and figure out where it lives in the various
  5779. // histories and nav stacks, along with how views should enter/leave
  5780. var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);
  5781. // init the rendering of views for this navView directive
  5782. switcher.init(registerData, function() {
  5783. // the view is now compiled, in the dom and linked, now lets transition the views.
  5784. // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
  5785. // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
  5786. // kick off the transition of views
  5787. switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);
  5788. // reset private vars for next time
  5789. disableRenderStartViewId = disableAnimation = null;
  5790. });
  5791. };
  5792. self.beforeEnter = function(transitionData) {
  5793. if (isPrimary) {
  5794. // only update this nav-view's nav-bar if this is the primary nav-view
  5795. navBarDelegate = transitionData.navBarDelegate;
  5796. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5797. associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
  5798. navSwipeAttr('');
  5799. }
  5800. };
  5801. self.activeEleId = function(eleId) {
  5802. if (arguments.length) {
  5803. activeEleId = eleId;
  5804. }
  5805. return activeEleId;
  5806. };
  5807. self.transitionEnd = function() {
  5808. var viewElements = $element.children();
  5809. var x, l, viewElement;
  5810. for (x = 0, l = viewElements.length; x < l; x++) {
  5811. viewElement = viewElements.eq(x);
  5812. if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
  5813. // this is the active element
  5814. navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
  5815. } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
  5816. // this is a leaving element or was the former active element, or is an cached element
  5817. if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
  5818. // this element shouldn't stay cached
  5819. $ionicViewSwitcher.destroyViewEle(viewElement);
  5820. } else {
  5821. // keep in the DOM, mark as cached
  5822. navViewAttr(viewElement, VIEW_STATUS_CACHED);
  5823. // disconnect the leaving scope
  5824. ionic.Utils.disconnectScope(viewElement.scope());
  5825. }
  5826. }
  5827. }
  5828. navSwipeAttr('');
  5829. // ensure no scrolls have been left frozen
  5830. if (self.isSwipeFreeze) {
  5831. $ionicScrollDelegate.freezeAllScrolls(false);
  5832. }
  5833. };
  5834. function onTabsLeave(ev, data) {
  5835. var viewElements = $element.children();
  5836. var viewElement, viewScope;
  5837. for (var x = 0, l = viewElements.length; x < l; x++) {
  5838. viewElement = viewElements.eq(x);
  5839. if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
  5840. viewScope = viewElement.scope();
  5841. viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
  5842. viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);
  5843. break;
  5844. }
  5845. }
  5846. }
  5847. self.cacheCleanup = function() {
  5848. var viewElements = $element.children();
  5849. for (var x = 0, l = viewElements.length; x < l; x++) {
  5850. if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
  5851. $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
  5852. }
  5853. }
  5854. };
  5855. self.clearCache = function(stateIds) {
  5856. var viewElements = $element.children();
  5857. var viewElement, viewScope, x, l, y, eleIdentifier;
  5858. for (x = 0, l = viewElements.length; x < l; x++) {
  5859. viewElement = viewElements.eq(x);
  5860. if (stateIds) {
  5861. eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
  5862. for (y = 0; y < stateIds.length; y++) {
  5863. if (eleIdentifier === stateIds[y]) {
  5864. $ionicViewSwitcher.destroyViewEle(viewElement);
  5865. }
  5866. }
  5867. continue;
  5868. }
  5869. if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
  5870. $ionicViewSwitcher.destroyViewEle(viewElement);
  5871. } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
  5872. viewScope = viewElement.scope();
  5873. viewScope && viewScope.$broadcast('$ionicView.clearCache');
  5874. }
  5875. }
  5876. };
  5877. self.getViewElements = function() {
  5878. return $element.children();
  5879. };
  5880. self.appendViewElement = function(viewEle, viewLocals) {
  5881. // compile the entering element and get the link function
  5882. var linkFn = $compile(viewEle);
  5883. $element.append(viewEle);
  5884. var viewScope = $scope.$new();
  5885. if (viewLocals && viewLocals.$$controller) {
  5886. viewLocals.$scope = viewScope;
  5887. var controller = $controller(viewLocals.$$controller, viewLocals);
  5888. if (viewLocals.$$controllerAs) {
  5889. viewScope[viewLocals.$$controllerAs] = controller;
  5890. }
  5891. $element.children().data('$ngControllerController', controller);
  5892. }
  5893. linkFn(viewScope);
  5894. return viewScope;
  5895. };
  5896. self.title = function(val) {
  5897. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5898. associatedNavBarCtrl && associatedNavBarCtrl.title(val);
  5899. };
  5900. /**
  5901. * @ngdoc method
  5902. * @name $ionicNavView#enableBackButton
  5903. * @description Enable/disable if the back button can be shown or not. For
  5904. * example, the very first view in the navigation stack would not have a
  5905. * back view, so the back button would be disabled.
  5906. */
  5907. self.enableBackButton = function(shouldEnable) {
  5908. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5909. associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
  5910. };
  5911. /**
  5912. * @ngdoc method
  5913. * @name $ionicNavView#showBackButton
  5914. * @description Show/hide the nav bar active back button. If the back button
  5915. * is not possible this will not force the back button to show. The
  5916. * `enableBackButton()` method handles if a back button is even possible or not.
  5917. */
  5918. self.showBackButton = function(shouldShow) {
  5919. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5920. if (associatedNavBarCtrl) {
  5921. if (arguments.length) {
  5922. return associatedNavBarCtrl.showActiveBackButton(shouldShow);
  5923. }
  5924. return associatedNavBarCtrl.showActiveBackButton();
  5925. }
  5926. return true;
  5927. };
  5928. self.showBar = function(val) {
  5929. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5930. if (associatedNavBarCtrl) {
  5931. if (arguments.length) {
  5932. return associatedNavBarCtrl.showBar(val);
  5933. }
  5934. return associatedNavBarCtrl.showBar();
  5935. }
  5936. return true;
  5937. };
  5938. self.isPrimary = function(val) {
  5939. if (arguments.length) {
  5940. isPrimary = val;
  5941. }
  5942. return isPrimary;
  5943. };
  5944. self.direction = function(val) {
  5945. if (arguments.length) {
  5946. direction = val;
  5947. }
  5948. return direction;
  5949. };
  5950. self.initSwipeBack = function() {
  5951. var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
  5952. var viewTransition, associatedNavBarCtrl, backView;
  5953. var deregDragStart, deregDrag, deregRelease;
  5954. var windowWidth, startDragX, dragPoints;
  5955. var cancelData = {};
  5956. function onDragStart(ev) {
  5957. if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return;
  5958. startDragX = getDragX(ev);
  5959. if (startDragX > swipeBackHitWidth) return;
  5960. backView = $ionicHistory.backView();
  5961. var currentView = $ionicHistory.currentView();
  5962. if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;
  5963. if (!windowWidth) windowWidth = window.innerWidth;
  5964. self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);
  5965. var registerData = {
  5966. direction: 'back'
  5967. };
  5968. dragPoints = [];
  5969. cancelData = {
  5970. showBar: self.showBar(),
  5971. showBackButton: self.showBackButton()
  5972. };
  5973. var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
  5974. switcher.loadViewElements(registerData);
  5975. switcher.render(registerData);
  5976. viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);
  5977. associatedNavBarCtrl = getAssociatedNavBarCtrl();
  5978. deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
  5979. deregRelease = ionic.onGesture('release', onRelease, $element[0]);
  5980. }
  5981. function onDrag(ev) {
  5982. if (isPrimary && viewTransition) {
  5983. var dragX = getDragX(ev);
  5984. dragPoints.push({
  5985. t: Date.now(),
  5986. x: dragX
  5987. });
  5988. if (dragX >= windowWidth - 15) {
  5989. onRelease(ev);
  5990. } else {
  5991. var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
  5992. viewTransition.run(step);
  5993. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
  5994. }
  5995. }
  5996. }
  5997. function onRelease(ev) {
  5998. if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {
  5999. var now = Date.now();
  6000. var releaseX = getDragX(ev);
  6001. var startDrag = dragPoints[dragPoints.length - 1];
  6002. for (var x = dragPoints.length - 2; x >= 0; x--) {
  6003. if (now - startDrag.t > 200) {
  6004. break;
  6005. }
  6006. startDrag = dragPoints[x];
  6007. }
  6008. var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
  6009. var releaseSwipeCompletion = getSwipeCompletion(releaseX);
  6010. var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);
  6011. // private variables because ui-router has no way to pass custom data using $state.go
  6012. disableRenderStartViewId = backView.viewId;
  6013. disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
  6014. if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
  6015. // complete view transition on release
  6016. var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
  6017. navSwipeAttr(disableAnimation ? '' : speed);
  6018. backView.go();
  6019. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
  6020. } else {
  6021. // cancel view transition on release
  6022. navSwipeAttr(disableAnimation ? '' : 'fast');
  6023. disableRenderStartViewId = null;
  6024. viewTransition.cancel(!disableAnimation);
  6025. associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
  6026. disableAnimation = null;
  6027. }
  6028. }
  6029. ionic.offGesture(deregDrag, 'drag', onDrag);
  6030. ionic.offGesture(deregRelease, 'release', onRelease);
  6031. windowWidth = viewTransition = dragPoints = null;
  6032. self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
  6033. }
  6034. function getDragX(ev) {
  6035. return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
  6036. }
  6037. function getSwipeCompletion(dragX) {
  6038. return (dragX - startDragX) / windowWidth;
  6039. }
  6040. deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);
  6041. $scope.$on('$destroy', function() {
  6042. ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
  6043. ionic.offGesture(deregDrag, 'drag', onDrag);
  6044. ionic.offGesture(deregRelease, 'release', onRelease);
  6045. self.element = viewTransition = associatedNavBarCtrl = null;
  6046. });
  6047. };
  6048. function navSwipeAttr(val) {
  6049. ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  6050. }
  6051. function onTabsTop(ev, isTabsTop) {
  6052. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  6053. associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
  6054. }
  6055. function onBarSubheader(ev, isBarSubheader) {
  6056. var associatedNavBarCtrl = getAssociatedNavBarCtrl();
  6057. associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
  6058. }
  6059. function getAssociatedNavBarCtrl() {
  6060. if (navBarDelegate) {
  6061. for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
  6062. if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
  6063. return $ionicNavBarDelegate._instances[x];
  6064. }
  6065. }
  6066. }
  6067. return $element.inheritedData('$ionNavBarController');
  6068. }
  6069. }]);
  6070. IonicModule
  6071. .controller('$ionicRefresher', [
  6072. '$scope',
  6073. '$attrs',
  6074. '$element',
  6075. '$ionicBind',
  6076. '$timeout',
  6077. function($scope, $attrs, $element, $ionicBind, $timeout) {
  6078. var self = this,
  6079. isDragging = false,
  6080. isOverscrolling = false,
  6081. dragOffset = 0,
  6082. lastOverscroll = 0,
  6083. ptrThreshold = 60,
  6084. activated = false,
  6085. scrollTime = 500,
  6086. startY = null,
  6087. deltaY = null,
  6088. canOverscroll = true,
  6089. scrollParent,
  6090. scrollChild;
  6091. if (!isDefined($attrs.pullingIcon)) {
  6092. $attrs.$set('pullingIcon', 'ion-android-arrow-down');
  6093. }
  6094. $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
  6095. $scope.showIcon = isDefined($attrs.refreshingIcon);
  6096. $ionicBind($scope, $attrs, {
  6097. pullingIcon: '@',
  6098. pullingText: '@',
  6099. refreshingIcon: '@',
  6100. refreshingText: '@',
  6101. spinner: '@',
  6102. disablePullingRotation: '@',
  6103. $onRefresh: '&onRefresh',
  6104. $onPulling: '&onPulling'
  6105. });
  6106. function handleMousedown(e) {
  6107. e.touches = e.touches || [{
  6108. screenX: e.screenX,
  6109. screenY: e.screenY
  6110. }];
  6111. // Mouse needs this
  6112. startY = Math.floor(e.touches[0].screenY);
  6113. }
  6114. function handleTouchstart(e) {
  6115. e.touches = e.touches || [{
  6116. screenX: e.screenX,
  6117. screenY: e.screenY
  6118. }];
  6119. startY = e.touches[0].screenY;
  6120. }
  6121. function handleTouchend() {
  6122. // reset Y
  6123. startY = null;
  6124. // if this wasn't an overscroll, get out immediately
  6125. if (!canOverscroll && !isDragging) {
  6126. return;
  6127. }
  6128. // the user has overscrolled but went back to native scrolling
  6129. if (!isDragging) {
  6130. dragOffset = 0;
  6131. isOverscrolling = false;
  6132. setScrollLock(false);
  6133. } else {
  6134. isDragging = false;
  6135. dragOffset = 0;
  6136. // the user has scroll far enough to trigger a refresh
  6137. if (lastOverscroll > ptrThreshold) {
  6138. start();
  6139. scrollTo(ptrThreshold, scrollTime);
  6140. // the user has overscrolled but not far enough to trigger a refresh
  6141. } else {
  6142. scrollTo(0, scrollTime, deactivate);
  6143. isOverscrolling = false;
  6144. }
  6145. }
  6146. }
  6147. function handleTouchmove(e) {
  6148. e.touches = e.touches || [{
  6149. screenX: e.screenX,
  6150. screenY: e.screenY
  6151. }];
  6152. // Force mouse events to have had a down event first
  6153. if (!startY && e.type == 'mousemove') {
  6154. return;
  6155. }
  6156. // if multitouch or regular scroll event, get out immediately
  6157. if (!canOverscroll || e.touches.length > 1) {
  6158. return;
  6159. }
  6160. //if this is a new drag, keep track of where we start
  6161. if (startY === null) {
  6162. startY = e.touches[0].screenY;
  6163. }
  6164. deltaY = e.touches[0].screenY - startY;
  6165. // how far have we dragged so far?
  6166. // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
  6167. // Only do this if we're not on crosswalk
  6168. if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && !ionic.Platform.isCrosswalk() && scrollParent.scrollTop === 0 && deltaY > 0) {
  6169. isDragging = true;
  6170. e.preventDefault();
  6171. }
  6172. // if we've dragged up and back down in to native scroll territory
  6173. if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
  6174. if (isOverscrolling) {
  6175. isOverscrolling = false;
  6176. setScrollLock(false);
  6177. }
  6178. if (isDragging) {
  6179. nativescroll(scrollParent, deltaY - dragOffset * -1);
  6180. }
  6181. // if we're not at overscroll 0 yet, 0 out
  6182. if (lastOverscroll !== 0) {
  6183. overscroll(0);
  6184. }
  6185. return;
  6186. } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
  6187. // starting overscroll, but drag started below scrollTop 0, so we need to offset the position
  6188. dragOffset = deltaY;
  6189. }
  6190. // prevent native scroll events while overscrolling
  6191. e.preventDefault();
  6192. // if not overscrolling yet, initiate overscrolling
  6193. if (!isOverscrolling) {
  6194. isOverscrolling = true;
  6195. setScrollLock(true);
  6196. }
  6197. isDragging = true;
  6198. // overscroll according to the user's drag so far
  6199. overscroll((deltaY - dragOffset) / 3);
  6200. // update the icon accordingly
  6201. if (!activated && lastOverscroll > ptrThreshold) {
  6202. activated = true;
  6203. ionic.requestAnimationFrame(activate);
  6204. } else if (activated && lastOverscroll < ptrThreshold) {
  6205. activated = false;
  6206. ionic.requestAnimationFrame(deactivate);
  6207. }
  6208. }
  6209. function handleScroll(e) {
  6210. // canOverscrol is used to greatly simplify the drag handler during normal scrolling
  6211. canOverscroll = (e.target.scrollTop === 0) || isDragging;
  6212. }
  6213. function overscroll(val) {
  6214. scrollChild.style[ionic.CSS.TRANSFORM] = 'translate3d(0px, ' + val + 'px, 0px)';
  6215. lastOverscroll = val;
  6216. }
  6217. function nativescroll(target, newScrollTop) {
  6218. // creates a scroll event that bubbles, can be cancelled, and with its view
  6219. // and detail property initialized to window and 1, respectively
  6220. target.scrollTop = newScrollTop;
  6221. var e = document.createEvent("UIEvents");
  6222. e.initUIEvent("scroll", true, true, window, 1);
  6223. target.dispatchEvent(e);
  6224. }
  6225. function setScrollLock(enabled) {
  6226. // set the scrollbar to be position:fixed in preparation to overscroll
  6227. // or remove it so the app can be natively scrolled
  6228. if (enabled) {
  6229. ionic.requestAnimationFrame(function() {
  6230. scrollChild.classList.add('overscroll');
  6231. show();
  6232. });
  6233. } else {
  6234. ionic.requestAnimationFrame(function() {
  6235. scrollChild.classList.remove('overscroll');
  6236. hide();
  6237. deactivate();
  6238. });
  6239. }
  6240. }
  6241. $scope.$on('scroll.refreshComplete', function() {
  6242. // prevent the complete from firing before the scroll has started
  6243. $timeout(function() {
  6244. ionic.requestAnimationFrame(tail);
  6245. // scroll back to home during tail animation
  6246. scrollTo(0, scrollTime, deactivate);
  6247. // return to native scrolling after tail animation has time to finish
  6248. $timeout(function() {
  6249. if (isOverscrolling) {
  6250. isOverscrolling = false;
  6251. setScrollLock(false);
  6252. }
  6253. }, scrollTime);
  6254. }, scrollTime);
  6255. });
  6256. function scrollTo(Y, duration, callback) {
  6257. // scroll animation loop w/ easing
  6258. // credit https://gist.github.com/dezinezync/5487119
  6259. var start = Date.now(),
  6260. from = lastOverscroll;
  6261. if (from === Y) {
  6262. callback();
  6263. return; /* Prevent scrolling to the Y point if already there */
  6264. }
  6265. // decelerating to zero velocity
  6266. function easeOutCubic(t) {
  6267. return (--t) * t * t + 1;
  6268. }
  6269. // scroll loop
  6270. function scroll() {
  6271. var currentTime = Date.now(),
  6272. time = Math.min(1, ((currentTime - start) / duration)),
  6273. // where .5 would be 50% of time on a linear scale easedT gives a
  6274. // fraction based on the easing method
  6275. easedT = easeOutCubic(time);
  6276. overscroll(Math.floor((easedT * (Y - from)) + from));
  6277. if (time < 1) {
  6278. ionic.requestAnimationFrame(scroll);
  6279. } else {
  6280. if (Y < 5 && Y > -5) {
  6281. isOverscrolling = false;
  6282. setScrollLock(false);
  6283. }
  6284. callback && callback();
  6285. }
  6286. }
  6287. // start scroll loop
  6288. ionic.requestAnimationFrame(scroll);
  6289. }
  6290. var touchStartEvent, touchMoveEvent, touchEndEvent;
  6291. if (window.navigator.pointerEnabled) {
  6292. touchStartEvent = 'pointerdown';
  6293. touchMoveEvent = 'pointermove';
  6294. touchEndEvent = 'pointerup';
  6295. } else if (window.navigator.msPointerEnabled) {
  6296. touchStartEvent = 'MSPointerDown';
  6297. touchMoveEvent = 'MSPointerMove';
  6298. touchEndEvent = 'MSPointerUp';
  6299. } else {
  6300. touchStartEvent = 'touchstart';
  6301. touchMoveEvent = 'touchmove';
  6302. touchEndEvent = 'touchend';
  6303. }
  6304. self.init = function() {
  6305. scrollParent = $element.parent().parent()[0];
  6306. scrollChild = $element.parent()[0];
  6307. if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||
  6308. !scrollChild || !scrollChild.classList.contains('scroll')) {
  6309. throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
  6310. }
  6311. ionic.on(touchStartEvent, handleTouchstart, scrollChild);
  6312. ionic.on(touchMoveEvent, handleTouchmove, scrollChild);
  6313. ionic.on(touchEndEvent, handleTouchend, scrollChild);
  6314. ionic.on('mousedown', handleMousedown, scrollChild);
  6315. ionic.on('mousemove', handleTouchmove, scrollChild);
  6316. ionic.on('mouseup', handleTouchend, scrollChild);
  6317. ionic.on('scroll', handleScroll, scrollParent);
  6318. // cleanup when done
  6319. $scope.$on('$destroy', destroy);
  6320. };
  6321. function destroy() {
  6322. if ( scrollChild ) {
  6323. ionic.off(touchStartEvent, handleTouchstart, scrollChild);
  6324. ionic.off(touchMoveEvent, handleTouchmove, scrollChild);
  6325. ionic.off(touchEndEvent, handleTouchend, scrollChild);
  6326. ionic.off('mousedown', handleMousedown, scrollChild);
  6327. ionic.off('mousemove', handleTouchmove, scrollChild);
  6328. ionic.off('mouseup', handleTouchend, scrollChild);
  6329. }
  6330. if ( scrollParent ) {
  6331. ionic.off('scroll', handleScroll, scrollParent);
  6332. }
  6333. scrollParent = null;
  6334. scrollChild = null;
  6335. }
  6336. // DOM manipulation and broadcast methods shared by JS and Native Scrolling
  6337. // getter used by JS Scrolling
  6338. self.getRefresherDomMethods = function() {
  6339. return {
  6340. activate: activate,
  6341. deactivate: deactivate,
  6342. start: start,
  6343. show: show,
  6344. hide: hide,
  6345. tail: tail
  6346. };
  6347. };
  6348. function activate() {
  6349. $element[0].classList.add('active');
  6350. $scope.$onPulling();
  6351. }
  6352. function deactivate() {
  6353. // give tail 150ms to finish
  6354. $timeout(function() {
  6355. // deactivateCallback
  6356. $element.removeClass('active refreshing refreshing-tail');
  6357. if (activated) activated = false;
  6358. }, 150);
  6359. }
  6360. function start() {
  6361. // startCallback
  6362. $element[0].classList.add('refreshing');
  6363. var q = $scope.$onRefresh();
  6364. if (q && q.then) {
  6365. q['finally'](function() {
  6366. $scope.$broadcast('scroll.refreshComplete');
  6367. });
  6368. }
  6369. }
  6370. function show() {
  6371. // showCallback
  6372. $element[0].classList.remove('invisible');
  6373. }
  6374. function hide() {
  6375. // showCallback
  6376. $element[0].classList.add('invisible');
  6377. }
  6378. function tail() {
  6379. // tailCallback
  6380. $element[0].classList.add('refreshing-tail');
  6381. }
  6382. // for testing
  6383. self.__handleTouchmove = handleTouchmove;
  6384. self.__getScrollChild = function() { return scrollChild; };
  6385. self.__getScrollParent = function() { return scrollParent; };
  6386. }
  6387. ]);
  6388. /**
  6389. * @private
  6390. */
  6391. IonicModule
  6392. .controller('$ionicScroll', [
  6393. '$scope',
  6394. 'scrollViewOptions',
  6395. '$timeout',
  6396. '$window',
  6397. '$location',
  6398. '$document',
  6399. '$ionicScrollDelegate',
  6400. '$ionicHistory',
  6401. function($scope,
  6402. scrollViewOptions,
  6403. $timeout,
  6404. $window,
  6405. $location,
  6406. $document,
  6407. $ionicScrollDelegate,
  6408. $ionicHistory) {
  6409. var self = this;
  6410. // for testing
  6411. self.__timeout = $timeout;
  6412. self._scrollViewOptions = scrollViewOptions; //for testing
  6413. self.isNative = function() {
  6414. return !!scrollViewOptions.nativeScrolling;
  6415. };
  6416. var element = self.element = scrollViewOptions.el;
  6417. var $element = self.$element = jqLite(element);
  6418. var scrollView;
  6419. if (self.isNative()) {
  6420. scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);
  6421. } else {
  6422. scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
  6423. }
  6424. //Attach self to element as a controller so other directives can require this controller
  6425. //through `require: '$ionicScroll'
  6426. //Also attach to parent so that sibling elements can require this
  6427. ($element.parent().length ? $element.parent() : $element)
  6428. .data('$$ionicScrollController', self);
  6429. var deregisterInstance = $ionicScrollDelegate._registerInstance(
  6430. self, scrollViewOptions.delegateHandle, function() {
  6431. return $ionicHistory.isActiveScope($scope);
  6432. }
  6433. );
  6434. if (!isDefined(scrollViewOptions.bouncing)) {
  6435. ionic.Platform.ready(function() {
  6436. if (scrollView && scrollView.options) {
  6437. scrollView.options.bouncing = true;
  6438. if (ionic.Platform.isAndroid()) {
  6439. // No bouncing by default on Android
  6440. scrollView.options.bouncing = false;
  6441. // Faster scroll decel
  6442. scrollView.options.deceleration = 0.95;
  6443. }
  6444. }
  6445. });
  6446. }
  6447. var resize = angular.bind(scrollView, scrollView.resize);
  6448. angular.element($window).on('resize', resize);
  6449. var scrollFunc = function(e) {
  6450. var detail = (e.originalEvent || e).detail || {};
  6451. $scope.$onScroll && $scope.$onScroll({
  6452. event: e,
  6453. scrollTop: detail.scrollTop || 0,
  6454. scrollLeft: detail.scrollLeft || 0
  6455. });
  6456. };
  6457. $element.on('scroll', scrollFunc);
  6458. $scope.$on('$destroy', function() {
  6459. deregisterInstance();
  6460. scrollView && scrollView.__cleanup && scrollView.__cleanup();
  6461. angular.element($window).off('resize', resize);
  6462. if ( $element ) {
  6463. $element.off('scroll', scrollFunc);
  6464. }
  6465. if ( self._scrollViewOptions ) {
  6466. self._scrollViewOptions.el = null;
  6467. }
  6468. if ( scrollViewOptions ) {
  6469. scrollViewOptions.el = null;
  6470. }
  6471. scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = element = self.$element = $element = null;
  6472. });
  6473. $timeout(function() {
  6474. scrollView && scrollView.run && scrollView.run();
  6475. });
  6476. self.getScrollView = function() {
  6477. return scrollView;
  6478. };
  6479. self.getScrollPosition = function() {
  6480. return scrollView.getValues();
  6481. };
  6482. self.resize = function() {
  6483. return $timeout(resize, 0, false).then(function() {
  6484. $element && $element.triggerHandler('scroll-resize');
  6485. });
  6486. };
  6487. self.scrollTop = function(shouldAnimate) {
  6488. self.resize().then(function() {
  6489. if (!scrollView) {
  6490. return;
  6491. }
  6492. scrollView.scrollTo(0, 0, !!shouldAnimate);
  6493. });
  6494. };
  6495. self.scrollBottom = function(shouldAnimate) {
  6496. self.resize().then(function() {
  6497. if (!scrollView) {
  6498. return;
  6499. }
  6500. var max = scrollView.getScrollMax();
  6501. scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
  6502. });
  6503. };
  6504. self.scrollTo = function(left, top, shouldAnimate) {
  6505. self.resize().then(function() {
  6506. if (!scrollView) {
  6507. return;
  6508. }
  6509. scrollView.scrollTo(left, top, !!shouldAnimate);
  6510. });
  6511. };
  6512. self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
  6513. self.resize().then(function() {
  6514. if (!scrollView) {
  6515. return;
  6516. }
  6517. scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
  6518. });
  6519. };
  6520. self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
  6521. self.resize().then(function() {
  6522. if (!scrollView) {
  6523. return;
  6524. }
  6525. scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
  6526. });
  6527. };
  6528. self.scrollBy = function(left, top, shouldAnimate) {
  6529. self.resize().then(function() {
  6530. if (!scrollView) {
  6531. return;
  6532. }
  6533. scrollView.scrollBy(left, top, !!shouldAnimate);
  6534. });
  6535. };
  6536. self.anchorScroll = function(shouldAnimate) {
  6537. self.resize().then(function() {
  6538. if (!scrollView) {
  6539. return;
  6540. }
  6541. var hash = $location.hash();
  6542. var elm = hash && $document[0].getElementById(hash);
  6543. if (!(hash && elm)) {
  6544. scrollView.scrollTo(0, 0, !!shouldAnimate);
  6545. return;
  6546. }
  6547. var curElm = elm;
  6548. var scrollLeft = 0, scrollTop = 0;
  6549. do {
  6550. if (curElm !== null) scrollLeft += curElm.offsetLeft;
  6551. if (curElm !== null) scrollTop += curElm.offsetTop;
  6552. curElm = curElm.offsetParent;
  6553. } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
  6554. scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
  6555. });
  6556. };
  6557. self.freezeScroll = scrollView.freeze;
  6558. self.freezeScrollShut = scrollView.freezeShut;
  6559. self.freezeAllScrolls = function(shouldFreeze) {
  6560. for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {
  6561. $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);
  6562. }
  6563. };
  6564. /**
  6565. * @private
  6566. */
  6567. self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {
  6568. self.refresher = refresherElement;
  6569. var refresherHeight = self.refresher.clientHeight || 60;
  6570. scrollView.activatePullToRefresh(
  6571. refresherHeight,
  6572. refresherMethods
  6573. );
  6574. };
  6575. }]);
  6576. IonicModule
  6577. .controller('$ionicSideMenus', [
  6578. '$scope',
  6579. '$attrs',
  6580. '$ionicSideMenuDelegate',
  6581. '$ionicPlatform',
  6582. '$ionicBody',
  6583. '$ionicHistory',
  6584. '$ionicScrollDelegate',
  6585. 'IONIC_BACK_PRIORITY',
  6586. '$rootScope',
  6587. function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY, $rootScope) {
  6588. var self = this;
  6589. var rightShowing, leftShowing, isDragging;
  6590. var startX, lastX, offsetX, isAsideExposed;
  6591. var enableMenuWithBackViews = true;
  6592. self.$scope = $scope;
  6593. self.initialize = function(options) {
  6594. self.left = options.left;
  6595. self.right = options.right;
  6596. self.setContent(options.content);
  6597. self.dragThresholdX = options.dragThresholdX || 10;
  6598. $ionicHistory.registerHistory(self.$scope);
  6599. };
  6600. /**
  6601. * Set the content view controller if not passed in the constructor options.
  6602. *
  6603. * @param {object} content
  6604. */
  6605. self.setContent = function(content) {
  6606. if (content) {
  6607. self.content = content;
  6608. self.content.onDrag = function(e) {
  6609. self._handleDrag(e);
  6610. };
  6611. self.content.endDrag = function(e) {
  6612. self._endDrag(e);
  6613. };
  6614. }
  6615. };
  6616. self.isOpenLeft = function() {
  6617. return self.getOpenAmount() > 0;
  6618. };
  6619. self.isOpenRight = function() {
  6620. return self.getOpenAmount() < 0;
  6621. };
  6622. /**
  6623. * Toggle the left menu to open 100%
  6624. */
  6625. self.toggleLeft = function(shouldOpen) {
  6626. if (isAsideExposed || !self.left.isEnabled) return;
  6627. var openAmount = self.getOpenAmount();
  6628. if (arguments.length === 0) {
  6629. shouldOpen = openAmount <= 0;
  6630. }
  6631. self.content.enableAnimation();
  6632. if (!shouldOpen) {
  6633. self.openPercentage(0);
  6634. $rootScope.$emit('$ionicSideMenuClose', 'left');
  6635. } else {
  6636. self.openPercentage(100);
  6637. $rootScope.$emit('$ionicSideMenuOpen', 'left');
  6638. }
  6639. };
  6640. /**
  6641. * Toggle the right menu to open 100%
  6642. */
  6643. self.toggleRight = function(shouldOpen) {
  6644. if (isAsideExposed || !self.right.isEnabled) return;
  6645. var openAmount = self.getOpenAmount();
  6646. if (arguments.length === 0) {
  6647. shouldOpen = openAmount >= 0;
  6648. }
  6649. self.content.enableAnimation();
  6650. if (!shouldOpen) {
  6651. self.openPercentage(0);
  6652. $rootScope.$emit('$ionicSideMenuClose', 'right');
  6653. } else {
  6654. self.openPercentage(-100);
  6655. $rootScope.$emit('$ionicSideMenuOpen', 'right');
  6656. }
  6657. };
  6658. self.toggle = function(side) {
  6659. if (side == 'right') {
  6660. self.toggleRight();
  6661. } else {
  6662. self.toggleLeft();
  6663. }
  6664. };
  6665. /**
  6666. * Close all menus.
  6667. */
  6668. self.close = function() {
  6669. self.openPercentage(0);
  6670. $rootScope.$emit('$ionicSideMenuClose', 'left');
  6671. $rootScope.$emit('$ionicSideMenuClose', 'right');
  6672. };
  6673. /**
  6674. * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
  6675. */
  6676. self.getOpenAmount = function() {
  6677. return self.content && self.content.getTranslateX() || 0;
  6678. };
  6679. /**
  6680. * @return {float} The ratio of open amount over menu width. For example, a
  6681. * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
  6682. * for right menu.
  6683. */
  6684. self.getOpenRatio = function() {
  6685. var amount = self.getOpenAmount();
  6686. if (amount >= 0) {
  6687. return amount / self.left.width;
  6688. }
  6689. return amount / self.right.width;
  6690. };
  6691. self.isOpen = function() {
  6692. return self.getOpenAmount() !== 0;
  6693. };
  6694. /**
  6695. * @return {float} The percentage of open amount over menu width. For example, a
  6696. * menu of width 100 open 50 pixels would be open 50%. Value is negative
  6697. * for right menu.
  6698. */
  6699. self.getOpenPercentage = function() {
  6700. return self.getOpenRatio() * 100;
  6701. };
  6702. /**
  6703. * Open the menu with a given percentage amount.
  6704. * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
  6705. */
  6706. self.openPercentage = function(percentage) {
  6707. var p = percentage / 100;
  6708. if (self.left && percentage >= 0) {
  6709. self.openAmount(self.left.width * p);
  6710. } else if (self.right && percentage < 0) {
  6711. self.openAmount(self.right.width * p);
  6712. }
  6713. // add the CSS class "menu-open" if the percentage does not
  6714. // equal 0, otherwise remove the class from the body element
  6715. $ionicBody.enableClass((percentage !== 0), 'menu-open');
  6716. self.content.setCanScroll(percentage == 0);
  6717. };
  6718. /*
  6719. function freezeAllScrolls(shouldFreeze) {
  6720. if (shouldFreeze && !self.isScrollFreeze) {
  6721. $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
  6722. } else if (!shouldFreeze && self.isScrollFreeze) {
  6723. $ionicScrollDelegate.freezeAllScrolls(false);
  6724. }
  6725. self.isScrollFreeze = shouldFreeze;
  6726. }
  6727. */
  6728. /**
  6729. * Open the menu the given pixel amount.
  6730. * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
  6731. * negative value for right menu (only one menu will be visible at a time).
  6732. */
  6733. self.openAmount = function(amount) {
  6734. var maxLeft = self.left && self.left.width || 0;
  6735. var maxRight = self.right && self.right.width || 0;
  6736. // Check if we can move to that side, depending if the left/right panel is enabled
  6737. if (!(self.left && self.left.isEnabled) && amount > 0) {
  6738. self.content.setTranslateX(0);
  6739. return;
  6740. }
  6741. if (!(self.right && self.right.isEnabled) && amount < 0) {
  6742. self.content.setTranslateX(0);
  6743. return;
  6744. }
  6745. if (leftShowing && amount > maxLeft) {
  6746. self.content.setTranslateX(maxLeft);
  6747. return;
  6748. }
  6749. if (rightShowing && amount < -maxRight) {
  6750. self.content.setTranslateX(-maxRight);
  6751. return;
  6752. }
  6753. self.content.setTranslateX(amount);
  6754. if (amount >= 0) {
  6755. leftShowing = true;
  6756. rightShowing = false;
  6757. if (amount > 0) {
  6758. // Push the z-index of the right menu down
  6759. self.right && self.right.pushDown && self.right.pushDown();
  6760. // Bring the z-index of the left menu up
  6761. self.left && self.left.bringUp && self.left.bringUp();
  6762. }
  6763. } else {
  6764. rightShowing = true;
  6765. leftShowing = false;
  6766. // Bring the z-index of the right menu up
  6767. self.right && self.right.bringUp && self.right.bringUp();
  6768. // Push the z-index of the left menu down
  6769. self.left && self.left.pushDown && self.left.pushDown();
  6770. }
  6771. };
  6772. /**
  6773. * Given an event object, find the final resting position of this side
  6774. * menu. For example, if the user "throws" the content to the right and
  6775. * releases the touch, the left menu should snap open (animated, of course).
  6776. *
  6777. * @param {Event} e the gesture event to use for snapping
  6778. */
  6779. self.snapToRest = function(e) {
  6780. // We want to animate at the end of this
  6781. self.content.enableAnimation();
  6782. isDragging = false;
  6783. // Check how much the panel is open after the drag, and
  6784. // what the drag velocity is
  6785. var ratio = self.getOpenRatio();
  6786. if (ratio === 0) {
  6787. // Just to be safe
  6788. self.openPercentage(0);
  6789. return;
  6790. }
  6791. var velocityThreshold = 0.3;
  6792. var velocityX = e.gesture.velocityX;
  6793. var direction = e.gesture.direction;
  6794. // Going right, less than half, too slow (snap back)
  6795. if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
  6796. self.openPercentage(0);
  6797. }
  6798. // Going left, more than half, too slow (snap back)
  6799. else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
  6800. self.openPercentage(100);
  6801. }
  6802. // Going left, less than half, too slow (snap back)
  6803. else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
  6804. self.openPercentage(0);
  6805. }
  6806. // Going right, more than half, too slow (snap back)
  6807. else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
  6808. self.openPercentage(-100);
  6809. }
  6810. // Going right, more than half, or quickly (snap open)
  6811. else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
  6812. self.openPercentage(100);
  6813. }
  6814. // Going left, more than half, or quickly (span open)
  6815. else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
  6816. self.openPercentage(-100);
  6817. }
  6818. // Snap back for safety
  6819. else {
  6820. self.openPercentage(0);
  6821. }
  6822. };
  6823. self.enableMenuWithBackViews = function(val) {
  6824. if (arguments.length) {
  6825. enableMenuWithBackViews = !!val;
  6826. }
  6827. return enableMenuWithBackViews;
  6828. };
  6829. self.isAsideExposed = function() {
  6830. return !!isAsideExposed;
  6831. };
  6832. self.exposeAside = function(shouldExposeAside) {
  6833. if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
  6834. self.close();
  6835. isAsideExposed = shouldExposeAside;
  6836. if ((self.left && self.left.isEnabled) && (self.right && self.right.isEnabled)) {
  6837. self.content.setMarginLeftAndRight(isAsideExposed ? self.left.width : 0, isAsideExposed ? self.right.width : 0);
  6838. } else if (self.left && self.left.isEnabled) {
  6839. // set the left marget width if it should be exposed
  6840. // otherwise set false so there's no left margin
  6841. self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
  6842. } else if (self.right && self.right.isEnabled) {
  6843. self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
  6844. }
  6845. self.$scope.$emit('$ionicExposeAside', isAsideExposed);
  6846. };
  6847. self.activeAsideResizing = function(isResizing) {
  6848. $ionicBody.enableClass(isResizing, 'aside-resizing');
  6849. };
  6850. // End a drag with the given event
  6851. self._endDrag = function(e) {
  6852. if (isAsideExposed) return;
  6853. if (isDragging) {
  6854. self.snapToRest(e);
  6855. }
  6856. startX = null;
  6857. lastX = null;
  6858. offsetX = null;
  6859. };
  6860. // Handle a drag event
  6861. self._handleDrag = function(e) {
  6862. if (isAsideExposed || !$scope.dragContent) return;
  6863. // If we don't have start coords, grab and store them
  6864. if (!startX) {
  6865. startX = e.gesture.touches[0].pageX;
  6866. lastX = startX;
  6867. } else {
  6868. // Grab the current tap coords
  6869. lastX = e.gesture.touches[0].pageX;
  6870. }
  6871. // Calculate difference from the tap points
  6872. if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
  6873. // if the difference is greater than threshold, start dragging using the current
  6874. // point as the starting point
  6875. startX = lastX;
  6876. isDragging = true;
  6877. // Initialize dragging
  6878. self.content.disableAnimation();
  6879. offsetX = self.getOpenAmount();
  6880. }
  6881. if (isDragging) {
  6882. self.openAmount(offsetX + (lastX - startX));
  6883. //self.content.setCanScroll(false);
  6884. }
  6885. };
  6886. self.canDragContent = function(canDrag) {
  6887. if (arguments.length) {
  6888. $scope.dragContent = !!canDrag;
  6889. }
  6890. return $scope.dragContent;
  6891. };
  6892. self.edgeThreshold = 25;
  6893. self.edgeThresholdEnabled = false;
  6894. self.edgeDragThreshold = function(value) {
  6895. if (arguments.length) {
  6896. if (isNumber(value) && value > 0) {
  6897. self.edgeThreshold = value;
  6898. self.edgeThresholdEnabled = true;
  6899. } else {
  6900. self.edgeThresholdEnabled = !!value;
  6901. }
  6902. }
  6903. return self.edgeThresholdEnabled;
  6904. };
  6905. self.isDraggableTarget = function(e) {
  6906. //Only restrict edge when sidemenu is closed and restriction is enabled
  6907. var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
  6908. var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
  6909. e.gesture.startEvent.center.pageX;
  6910. var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
  6911. startX <= self.edgeThreshold ||
  6912. startX >= self.content.element.offsetWidth - self.edgeThreshold;
  6913. var backView = $ionicHistory.backView();
  6914. var menuEnabled = enableMenuWithBackViews ? true : !backView;
  6915. if (!menuEnabled) {
  6916. var currentView = $ionicHistory.currentView() || {};
  6917. return (dragIsWithinBounds && (backView.historyId !== currentView.historyId));
  6918. }
  6919. return ($scope.dragContent || self.isOpen()) &&
  6920. dragIsWithinBounds &&
  6921. !e.gesture.srcEvent.defaultPrevented &&
  6922. menuEnabled &&
  6923. !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
  6924. !e.target.isContentEditable &&
  6925. !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
  6926. };
  6927. $scope.sideMenuContentTranslateX = 0;
  6928. var deregisterBackButtonAction = noop;
  6929. var closeSideMenu = angular.bind(self, self.close);
  6930. $scope.$watch(function() {
  6931. return self.getOpenAmount() !== 0;
  6932. }, function(isOpen) {
  6933. deregisterBackButtonAction();
  6934. if (isOpen) {
  6935. deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
  6936. closeSideMenu,
  6937. IONIC_BACK_PRIORITY.sideMenu
  6938. );
  6939. }
  6940. });
  6941. var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
  6942. self, $attrs.delegateHandle, function() {
  6943. return $ionicHistory.isActiveScope($scope);
  6944. }
  6945. );
  6946. $scope.$on('$destroy', function() {
  6947. deregisterInstance();
  6948. deregisterBackButtonAction();
  6949. self.$scope = null;
  6950. if (self.content) {
  6951. self.content.setCanScroll(true);
  6952. self.content.element = null;
  6953. self.content = null;
  6954. }
  6955. });
  6956. self.initialize({
  6957. left: {
  6958. width: 275
  6959. },
  6960. right: {
  6961. width: 275
  6962. }
  6963. });
  6964. }]);
  6965. (function(ionic) {
  6966. var TRANSLATE32 = 'translate(32,32)';
  6967. var STROKE_OPACITY = 'stroke-opacity';
  6968. var ROUND = 'round';
  6969. var INDEFINITE = 'indefinite';
  6970. var DURATION = '750ms';
  6971. var NONE = 'none';
  6972. var SHORTCUTS = {
  6973. a: 'animate',
  6974. an: 'attributeName',
  6975. at: 'animateTransform',
  6976. c: 'circle',
  6977. da: 'stroke-dasharray',
  6978. os: 'stroke-dashoffset',
  6979. f: 'fill',
  6980. lc: 'stroke-linecap',
  6981. rc: 'repeatCount',
  6982. sw: 'stroke-width',
  6983. t: 'transform',
  6984. v: 'values'
  6985. };
  6986. var SPIN_ANIMATION = {
  6987. v: '0,32,32;360,32,32',
  6988. an: 'transform',
  6989. type: 'rotate',
  6990. rc: INDEFINITE,
  6991. dur: DURATION
  6992. };
  6993. function createSvgElement(tagName, data, parent, spinnerName) {
  6994. var ele = document.createElement(SHORTCUTS[tagName] || tagName);
  6995. var k, x, y;
  6996. for (k in data) {
  6997. if (angular.isArray(data[k])) {
  6998. for (x = 0; x < data[k].length; x++) {
  6999. if (data[k][x].fn) {
  7000. for (y = 0; y < data[k][x].t; y++) {
  7001. createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);
  7002. }
  7003. } else {
  7004. createSvgElement(k, data[k][x], ele, spinnerName);
  7005. }
  7006. }
  7007. } else {
  7008. setSvgAttribute(ele, k, data[k]);
  7009. }
  7010. }
  7011. parent.appendChild(ele);
  7012. }
  7013. function setSvgAttribute(ele, k, v) {
  7014. ele.setAttribute(SHORTCUTS[k] || k, v);
  7015. }
  7016. function animationValues(strValues, i) {
  7017. var values = strValues.split(';');
  7018. var back = values.slice(i);
  7019. var front = values.slice(0, values.length - back.length);
  7020. values = back.concat(front).reverse();
  7021. return values.join(';') + ';' + values[0];
  7022. }
  7023. var IOS_SPINNER = {
  7024. sw: 4,
  7025. lc: ROUND,
  7026. line: [{
  7027. fn: function(i, spinnerName) {
  7028. return {
  7029. y1: spinnerName == 'ios' ? 17 : 12,
  7030. y2: spinnerName == 'ios' ? 29 : 20,
  7031. t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
  7032. a: [{
  7033. fn: function() {
  7034. return {
  7035. an: STROKE_OPACITY,
  7036. dur: DURATION,
  7037. v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
  7038. rc: INDEFINITE
  7039. };
  7040. },
  7041. t: 1
  7042. }]
  7043. };
  7044. },
  7045. t: 12
  7046. }]
  7047. };
  7048. var spinners = {
  7049. android: {
  7050. c: [{
  7051. sw: 6,
  7052. da: 128,
  7053. os: 82,
  7054. r: 26,
  7055. cx: 32,
  7056. cy: 32,
  7057. f: NONE
  7058. }]
  7059. },
  7060. ios: IOS_SPINNER,
  7061. 'ios-small': IOS_SPINNER,
  7062. bubbles: {
  7063. sw: 0,
  7064. c: [{
  7065. fn: function(i) {
  7066. return {
  7067. cx: 24 * Math.cos(2 * Math.PI * i / 8),
  7068. cy: 24 * Math.sin(2 * Math.PI * i / 8),
  7069. t: TRANSLATE32,
  7070. a: [{
  7071. fn: function() {
  7072. return {
  7073. an: 'r',
  7074. dur: DURATION,
  7075. v: animationValues('1;2;3;4;5;6;7;8', i),
  7076. rc: INDEFINITE
  7077. };
  7078. },
  7079. t: 1
  7080. }]
  7081. };
  7082. },
  7083. t: 8
  7084. }]
  7085. },
  7086. circles: {
  7087. c: [{
  7088. fn: function(i) {
  7089. return {
  7090. r: 5,
  7091. cx: 24 * Math.cos(2 * Math.PI * i / 8),
  7092. cy: 24 * Math.sin(2 * Math.PI * i / 8),
  7093. t: TRANSLATE32,
  7094. sw: 0,
  7095. a: [{
  7096. fn: function() {
  7097. return {
  7098. an: 'fill-opacity',
  7099. dur: DURATION,
  7100. v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
  7101. rc: INDEFINITE
  7102. };
  7103. },
  7104. t: 1
  7105. }]
  7106. };
  7107. },
  7108. t: 8
  7109. }]
  7110. },
  7111. crescent: {
  7112. c: [{
  7113. sw: 4,
  7114. da: 128,
  7115. os: 82,
  7116. r: 26,
  7117. cx: 32,
  7118. cy: 32,
  7119. f: NONE,
  7120. at: [SPIN_ANIMATION]
  7121. }]
  7122. },
  7123. dots: {
  7124. c: [{
  7125. fn: function(i) {
  7126. return {
  7127. cx: 16 + (16 * i),
  7128. cy: 32,
  7129. sw: 0,
  7130. a: [{
  7131. fn: function() {
  7132. return {
  7133. an: 'fill-opacity',
  7134. dur: DURATION,
  7135. v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
  7136. rc: INDEFINITE
  7137. };
  7138. },
  7139. t: 1
  7140. }, {
  7141. fn: function() {
  7142. return {
  7143. an: 'r',
  7144. dur: DURATION,
  7145. v: animationValues('4;5;6;5;4;3;3', i),
  7146. rc: INDEFINITE
  7147. };
  7148. },
  7149. t: 1
  7150. }]
  7151. };
  7152. },
  7153. t: 3
  7154. }]
  7155. },
  7156. lines: {
  7157. sw: 7,
  7158. lc: ROUND,
  7159. line: [{
  7160. fn: function(i) {
  7161. return {
  7162. x1: 10 + (i * 14),
  7163. x2: 10 + (i * 14),
  7164. a: [{
  7165. fn: function() {
  7166. return {
  7167. an: 'y1',
  7168. dur: DURATION,
  7169. v: animationValues('16;18;28;18;16', i),
  7170. rc: INDEFINITE
  7171. };
  7172. },
  7173. t: 1
  7174. }, {
  7175. fn: function() {
  7176. return {
  7177. an: 'y2',
  7178. dur: DURATION,
  7179. v: animationValues('48;44;36;46;48', i),
  7180. rc: INDEFINITE
  7181. };
  7182. },
  7183. t: 1
  7184. }, {
  7185. fn: function() {
  7186. return {
  7187. an: STROKE_OPACITY,
  7188. dur: DURATION,
  7189. v: animationValues('1;.8;.5;.4;1', i),
  7190. rc: INDEFINITE
  7191. };
  7192. },
  7193. t: 1
  7194. }]
  7195. };
  7196. },
  7197. t: 4
  7198. }]
  7199. },
  7200. ripple: {
  7201. f: NONE,
  7202. 'fill-rule': 'evenodd',
  7203. sw: 3,
  7204. circle: [{
  7205. fn: function(i) {
  7206. return {
  7207. cx: 32,
  7208. cy: 32,
  7209. a: [{
  7210. fn: function() {
  7211. return {
  7212. an: 'r',
  7213. begin: (i * -1) + 's',
  7214. dur: '2s',
  7215. v: '0;24',
  7216. keyTimes: '0;1',
  7217. keySplines: '0.1,0.2,0.3,1',
  7218. calcMode: 'spline',
  7219. rc: INDEFINITE
  7220. };
  7221. },
  7222. t: 1
  7223. }, {
  7224. fn: function() {
  7225. return {
  7226. an: STROKE_OPACITY,
  7227. begin: (i * -1) + 's',
  7228. dur: '2s',
  7229. v: '.2;1;.2;0',
  7230. rc: INDEFINITE
  7231. };
  7232. },
  7233. t: 1
  7234. }]
  7235. };
  7236. },
  7237. t: 2
  7238. }]
  7239. },
  7240. spiral: {
  7241. defs: [{
  7242. linearGradient: [{
  7243. id: 'sGD',
  7244. gradientUnits: 'userSpaceOnUse',
  7245. x1: 55, y1: 46, x2: 2, y2: 46,
  7246. stop: [{
  7247. offset: 0.1,
  7248. class: 'stop1'
  7249. }, {
  7250. offset: 1,
  7251. class: 'stop2'
  7252. }]
  7253. }]
  7254. }],
  7255. g: [{
  7256. sw: 4,
  7257. lc: ROUND,
  7258. f: NONE,
  7259. path: [{
  7260. stroke: 'url(#sGD)',
  7261. d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
  7262. }, {
  7263. d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
  7264. }],
  7265. at: [SPIN_ANIMATION]
  7266. }]
  7267. }
  7268. };
  7269. var animations = {
  7270. android: function(ele) {
  7271. // Note that this is called as a function, not a constructor.
  7272. var self = {};
  7273. this.stop = false;
  7274. var rIndex = 0;
  7275. var rotateCircle = 0;
  7276. var startTime;
  7277. var svgEle = ele.querySelector('g');
  7278. var circleEle = ele.querySelector('circle');
  7279. function run() {
  7280. if (self.stop) return;
  7281. var v = easeInOutCubic(Date.now() - startTime, 650);
  7282. var scaleX = 1;
  7283. var translateX = 0;
  7284. var dasharray = (188 - (58 * v));
  7285. var dashoffset = (182 - (182 * v));
  7286. if (rIndex % 2) {
  7287. scaleX = -1;
  7288. translateX = -64;
  7289. dasharray = (128 - (-58 * v));
  7290. dashoffset = (182 * v);
  7291. }
  7292. var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];
  7293. setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));
  7294. setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));
  7295. setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');
  7296. rotateCircle += 4.1;
  7297. if (rotateCircle > 359) rotateCircle = 0;
  7298. setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');
  7299. if (v >= 1) {
  7300. rIndex++;
  7301. if (rIndex > 7) rIndex = 0;
  7302. startTime = Date.now();
  7303. }
  7304. ionic.requestAnimationFrame(run);
  7305. }
  7306. return function() {
  7307. startTime = Date.now();
  7308. run();
  7309. return self;
  7310. };
  7311. }
  7312. };
  7313. function easeInOutCubic(t, c) {
  7314. t /= c / 2;
  7315. if (t < 1) return 1 / 2 * t * t * t;
  7316. t -= 2;
  7317. return 1 / 2 * (t * t * t + 2);
  7318. }
  7319. IonicModule
  7320. .controller('$ionicSpinner', [
  7321. '$element',
  7322. '$attrs',
  7323. '$ionicConfig',
  7324. function($element, $attrs, $ionicConfig) {
  7325. var spinnerName, anim;
  7326. this.init = function() {
  7327. spinnerName = $attrs.icon || $ionicConfig.spinner.icon();
  7328. var container = document.createElement('div');
  7329. createSvgElement('svg', {
  7330. viewBox: '0 0 64 64',
  7331. g: [spinners[spinnerName]]
  7332. }, container, spinnerName);
  7333. // Specifically for animations to work,
  7334. // Android 4.3 and below requires the element to be
  7335. // added as an html string, rather than dynmically
  7336. // building up the svg element and appending it.
  7337. $element.html(container.innerHTML);
  7338. this.start();
  7339. return spinnerName;
  7340. };
  7341. this.start = function() {
  7342. animations[spinnerName] && (anim = animations[spinnerName]($element[0])());
  7343. };
  7344. this.stop = function() {
  7345. animations[spinnerName] && (anim.stop = true);
  7346. };
  7347. }]);
  7348. })(ionic);
  7349. IonicModule
  7350. .controller('$ionicTab', [
  7351. '$scope',
  7352. '$ionicHistory',
  7353. '$attrs',
  7354. '$location',
  7355. '$state',
  7356. function($scope, $ionicHistory, $attrs, $location, $state) {
  7357. this.$scope = $scope;
  7358. //All of these exposed for testing
  7359. this.hrefMatchesState = function() {
  7360. return $attrs.href && $location.path().indexOf(
  7361. $attrs.href.replace(/^#/, '').replace(/\/$/, '')
  7362. ) === 0;
  7363. };
  7364. this.srefMatchesState = function() {
  7365. return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
  7366. };
  7367. this.navNameMatchesState = function() {
  7368. return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
  7369. };
  7370. this.tabMatchesState = function() {
  7371. return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
  7372. };
  7373. }]);
  7374. IonicModule
  7375. .controller('$ionicTabs', [
  7376. '$scope',
  7377. '$element',
  7378. '$ionicHistory',
  7379. function($scope, $element, $ionicHistory) {
  7380. var self = this;
  7381. var selectedTab = null;
  7382. var previousSelectedTab = null;
  7383. var selectedTabIndex;
  7384. var isVisible = true;
  7385. self.tabs = [];
  7386. self.selectedIndex = function() {
  7387. return self.tabs.indexOf(selectedTab);
  7388. };
  7389. self.selectedTab = function() {
  7390. return selectedTab;
  7391. };
  7392. self.previousSelectedTab = function() {
  7393. return previousSelectedTab;
  7394. };
  7395. self.add = function(tab) {
  7396. $ionicHistory.registerHistory(tab);
  7397. self.tabs.push(tab);
  7398. };
  7399. self.remove = function(tab) {
  7400. var tabIndex = self.tabs.indexOf(tab);
  7401. if (tabIndex === -1) {
  7402. return;
  7403. }
  7404. //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
  7405. if (tab.$tabSelected) {
  7406. self.deselect(tab);
  7407. //Try to select a new tab if we're removing a tab
  7408. if (self.tabs.length === 1) {
  7409. //Do nothing if there are no other tabs to select
  7410. } else {
  7411. //Select previous tab if it's the last tab, else select next tab
  7412. var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
  7413. self.select(self.tabs[newTabIndex]);
  7414. }
  7415. }
  7416. self.tabs.splice(tabIndex, 1);
  7417. };
  7418. self.deselect = function(tab) {
  7419. if (tab.$tabSelected) {
  7420. previousSelectedTab = selectedTab;
  7421. selectedTab = selectedTabIndex = null;
  7422. tab.$tabSelected = false;
  7423. (tab.onDeselect || noop)();
  7424. tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
  7425. }
  7426. };
  7427. self.select = function(tab, shouldEmitEvent) {
  7428. var tabIndex;
  7429. if (isNumber(tab)) {
  7430. tabIndex = tab;
  7431. if (tabIndex >= self.tabs.length) return;
  7432. tab = self.tabs[tabIndex];
  7433. } else {
  7434. tabIndex = self.tabs.indexOf(tab);
  7435. }
  7436. if (arguments.length === 1) {
  7437. shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
  7438. }
  7439. if (selectedTab && selectedTab.$historyId == tab.$historyId) {
  7440. if (shouldEmitEvent) {
  7441. $ionicHistory.goToHistoryRoot(tab.$historyId);
  7442. }
  7443. } else if (selectedTabIndex !== tabIndex) {
  7444. forEach(self.tabs, function(tab) {
  7445. self.deselect(tab);
  7446. });
  7447. selectedTab = tab;
  7448. selectedTabIndex = tabIndex;
  7449. if (self.$scope && self.$scope.$parent) {
  7450. self.$scope.$parent.$activeHistoryId = tab.$historyId;
  7451. }
  7452. //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
  7453. tab.$tabSelected = true;
  7454. (tab.onSelect || noop)();
  7455. if (shouldEmitEvent) {
  7456. $scope.$emit('$ionicHistory.change', {
  7457. type: 'tab',
  7458. tabIndex: tabIndex,
  7459. historyId: tab.$historyId,
  7460. navViewName: tab.navViewName,
  7461. hasNavView: !!tab.navViewName,
  7462. title: tab.title,
  7463. url: tab.href,
  7464. uiSref: tab.uiSref
  7465. });
  7466. }
  7467. $scope.$broadcast("tabSelected", { selectedTab: tab, selectedTabIndex: tabIndex});
  7468. }
  7469. };
  7470. self.hasActiveScope = function() {
  7471. for (var x = 0; x < self.tabs.length; x++) {
  7472. if ($ionicHistory.isActiveScope(self.tabs[x])) {
  7473. return true;
  7474. }
  7475. }
  7476. return false;
  7477. };
  7478. self.showBar = function(show) {
  7479. if (arguments.length) {
  7480. if (show) {
  7481. $element.removeClass('tabs-item-hide');
  7482. } else {
  7483. $element.addClass('tabs-item-hide');
  7484. }
  7485. isVisible = !!show;
  7486. }
  7487. return isVisible;
  7488. };
  7489. }]);
  7490. IonicModule
  7491. .controller('$ionicView', [
  7492. '$scope',
  7493. '$element',
  7494. '$attrs',
  7495. '$compile',
  7496. '$rootScope',
  7497. function($scope, $element, $attrs, $compile, $rootScope) {
  7498. var self = this;
  7499. var navElementHtml = {};
  7500. var navViewCtrl;
  7501. var navBarDelegateHandle;
  7502. var hasViewHeaderBar;
  7503. var deregisters = [];
  7504. var viewTitle;
  7505. var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
  7506. // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
  7507. ev.stopPropagation();
  7508. navBarDelegateHandle = delegateHandle;
  7509. });
  7510. self.init = function() {
  7511. deregIonNavBarInit();
  7512. var modalCtrl = $element.inheritedData('$ionModalController');
  7513. navViewCtrl = $element.inheritedData('$ionNavViewController');
  7514. // don't bother if inside a modal or there's no parent navView
  7515. if (!navViewCtrl || modalCtrl) return;
  7516. // add listeners for when this view changes
  7517. $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
  7518. $scope.$on('$ionicView.afterEnter', afterEnter);
  7519. $scope.$on('$ionicView.beforeLeave', deregisterFns);
  7520. };
  7521. self.beforeEnter = function(ev, transData) {
  7522. // this event was emitted, starting at intial ion-view, then bubbles up
  7523. // only the first ion-view should do something with it, parent ion-views should ignore
  7524. if (transData && !transData.viewNotified) {
  7525. transData.viewNotified = true;
  7526. if (!$rootScope.$$phase) $scope.$digest();
  7527. viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
  7528. var navBarItems = {};
  7529. for (var n in navElementHtml) {
  7530. navBarItems[n] = generateNavBarItem(navElementHtml[n]);
  7531. }
  7532. navViewCtrl.beforeEnter(extend(transData, {
  7533. title: viewTitle,
  7534. showBack: !attrTrue('hideBackButton'),
  7535. navBarItems: navBarItems,
  7536. navBarDelegate: navBarDelegateHandle || null,
  7537. showNavBar: !attrTrue('hideNavBar'),
  7538. hasHeaderBar: !!hasViewHeaderBar
  7539. }));
  7540. // make sure any existing observers are cleaned up
  7541. deregisterFns();
  7542. }
  7543. };
  7544. function afterEnter() {
  7545. // only listen for title updates after it has entered
  7546. // but also deregister the observe before it leaves
  7547. var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
  7548. if (viewTitleAttr) {
  7549. titleUpdate($attrs[viewTitleAttr]);
  7550. deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
  7551. }
  7552. if (isDefined($attrs.hideBackButton)) {
  7553. deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
  7554. navViewCtrl.showBackButton(!val);
  7555. }));
  7556. }
  7557. if (isDefined($attrs.hideNavBar)) {
  7558. deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
  7559. navViewCtrl.showBar(!val);
  7560. }));
  7561. }
  7562. }
  7563. function titleUpdate(newTitle) {
  7564. if (isDefined(newTitle) && newTitle !== viewTitle) {
  7565. viewTitle = newTitle;
  7566. navViewCtrl.title(viewTitle);
  7567. }
  7568. }
  7569. function deregisterFns() {
  7570. // remove all existing $attrs.$observe's
  7571. for (var x = 0; x < deregisters.length; x++) {
  7572. deregisters[x]();
  7573. }
  7574. deregisters = [];
  7575. }
  7576. function generateNavBarItem(html) {
  7577. if (html) {
  7578. // every time a view enters we need to recreate its view buttons if they exist
  7579. return $compile(html)($scope.$new());
  7580. }
  7581. }
  7582. function attrTrue(key) {
  7583. return !!$scope.$eval($attrs[key]);
  7584. }
  7585. self.navElement = function(type, html) {
  7586. navElementHtml[type] = html;
  7587. };
  7588. }]);
  7589. /*
  7590. * We don't document the ionActionSheet directive, we instead document
  7591. * the $ionicActionSheet service
  7592. */
  7593. IonicModule
  7594. .directive('ionActionSheet', ['$document', function($document) {
  7595. return {
  7596. restrict: 'E',
  7597. scope: true,
  7598. replace: true,
  7599. link: function($scope, $element) {
  7600. var keyUp = function(e) {
  7601. if (e.which == 27) {
  7602. $scope.cancel();
  7603. $scope.$apply();
  7604. }
  7605. };
  7606. var backdropClick = function(e) {
  7607. if (e.target == $element[0]) {
  7608. $scope.cancel();
  7609. $scope.$apply();
  7610. }
  7611. };
  7612. $scope.$on('$destroy', function() {
  7613. $element.remove();
  7614. $document.unbind('keyup', keyUp);
  7615. });
  7616. $document.bind('keyup', keyUp);
  7617. $element.bind('click', backdropClick);
  7618. },
  7619. template: '<div class="action-sheet-backdrop">' +
  7620. '<div class="action-sheet-wrapper">' +
  7621. '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +
  7622. '<div class="action-sheet-group action-sheet-options">' +
  7623. '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
  7624. '<button class="button action-sheet-option" ng-click="buttonClicked($index)" ng-class="b.className" ng-repeat="b in buttons" ng-bind-html="b.text"></button>' +
  7625. '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
  7626. '</div>' +
  7627. '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +
  7628. '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
  7629. '</div>' +
  7630. '</div>' +
  7631. '</div>' +
  7632. '</div>'
  7633. };
  7634. }]);
  7635. /**
  7636. * @ngdoc directive
  7637. * @name ionCheckbox
  7638. * @module ionic
  7639. * @restrict E
  7640. * @codepen hqcju
  7641. * @description
  7642. * The checkbox is no different than the HTML checkbox input, except it's styled differently.
  7643. *
  7644. * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
  7645. *
  7646. * @usage
  7647. * ```html
  7648. * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
  7649. * ```
  7650. */
  7651. IonicModule
  7652. .directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
  7653. return {
  7654. restrict: 'E',
  7655. replace: true,
  7656. require: '?ngModel',
  7657. transclude: true,
  7658. template:
  7659. '<label class="item item-checkbox">' +
  7660. '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
  7661. '<input type="checkbox">' +
  7662. '<i class="checkbox-icon"></i>' +
  7663. '</div>' +
  7664. '<div class="item-content disable-pointer-events" ng-transclude></div>' +
  7665. '</label>',
  7666. compile: function(element, attr) {
  7667. var input = element.find('input');
  7668. forEach({
  7669. 'name': attr.name,
  7670. 'ng-value': attr.ngValue,
  7671. 'ng-model': attr.ngModel,
  7672. 'ng-checked': attr.ngChecked,
  7673. 'ng-disabled': attr.ngDisabled,
  7674. 'ng-true-value': attr.ngTrueValue,
  7675. 'ng-false-value': attr.ngFalseValue,
  7676. 'ng-change': attr.ngChange,
  7677. 'ng-required': attr.ngRequired,
  7678. 'required': attr.required
  7679. }, function(value, name) {
  7680. if (isDefined(value)) {
  7681. input.attr(name, value);
  7682. }
  7683. });
  7684. var checkboxWrapper = element[0].querySelector('.checkbox');
  7685. checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
  7686. }
  7687. };
  7688. }]);
  7689. /**
  7690. * @ngdoc directive
  7691. * @restrict A
  7692. * @name collectionRepeat
  7693. * @module ionic
  7694. * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15
  7695. * @description
  7696. * `collection-repeat` allows an app to show huge lists of items much more performantly than
  7697. * `ng-repeat`.
  7698. *
  7699. * It renders into the DOM only as many items as are currently visible.
  7700. *
  7701. * This means that on a phone screen that can fit eight items, only the eight items matching
  7702. * the current scroll position will be rendered.
  7703. *
  7704. * **The Basics**:
  7705. *
  7706. * - The data given to collection-repeat must be an array.
  7707. * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that
  7708. * every item in the list has the same dimensions as the first item.
  7709. * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is
  7710. * assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings
  7711. * won't.
  7712. *
  7713. * **Performance Tips**:
  7714. *
  7715. * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes.
  7716. * To increase performance of images on iOS, cache your images in advance and,
  7717. * if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194).
  7718. *
  7719. * @usage
  7720. * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693))
  7721. * ```html
  7722. * <ion-content>
  7723. * <ion-item collection-repeat="item in items">
  7724. * {% raw %}{{item}}{% endraw %}
  7725. * </ion-item>
  7726. * </ion-content>
  7727. * ```
  7728. *
  7729. * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664))
  7730. * ```html
  7731. * <ion-content>
  7732. * <img collection-repeat="photo in photos"
  7733. * item-width="33%"
  7734. * item-height="200px"
  7735. * ng-src="{% raw %}{{photo.url}}{% endraw %}">
  7736. * </ion-content>
  7737. * ```
  7738. *
  7739. * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e))
  7740. * ```html
  7741. * <ion-content>
  7742. * <h2>Available Kittens:</h2>
  7743. * <ion-scroll direction="x" class="available-scroller">
  7744. * <div class="photo" collection-repeat="photo in main.photos"
  7745. * item-height="250" item-width="photo.width + 30">
  7746. * <img ng-src="{% raw %}{{photo.src}}{% endraw %}">
  7747. * </div>
  7748. * </ion-scroll>
  7749. * </ion-content>
  7750. * ```
  7751. *
  7752. * @param {expression} collection-repeat The expression indicating how to enumerate a collection,
  7753. * of the format `variable in expression` where variable is the user defined loop variable
  7754. * and `expression` is a scope expression giving the collection to enumerate.
  7755. * For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`.
  7756. * @param {expression=} item-width The width of the repeated element. The expression must return
  7757. * a number (pixels) or a percentage. Defaults to the width of the first item in the list.
  7758. * (previously named collection-item-width)
  7759. * @param {expression=} item-height The height of the repeated element. The expression must return
  7760. * a number (pixels) or a percentage. Defaults to the height of the first item in the list.
  7761. * (previously named collection-item-height)
  7762. * @param {number=} item-render-buffer The number of items to load before and after the visible
  7763. * items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but
  7764. * don't set it too high or you'll see performance loss.
  7765. * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem
  7766. * where, when an element is interchanged as scrolling, its image will still have the old src
  7767. * while the new src loads. Setting this to true comes with a small performance loss.
  7768. */
  7769. IonicModule
  7770. .directive('collectionRepeat', CollectionRepeatDirective)
  7771. .factory('$ionicCollectionManager', RepeatManagerFactory);
  7772. var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
  7773. var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;
  7774. var DEFAULT_RENDER_BUFFER = 3;
  7775. CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];
  7776. function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {
  7777. return {
  7778. restrict: 'A',
  7779. priority: 1000,
  7780. transclude: 'element',
  7781. $$tlb: true,
  7782. require: '^^$ionicScroll',
  7783. link: postLink
  7784. };
  7785. function postLink(scope, element, attr, scrollCtrl, transclude) {
  7786. var scrollView = scrollCtrl.scrollView;
  7787. var node = element[0];
  7788. var containerNode = angular.element('<div class="collection-repeat-container">')[0];
  7789. node.parentNode.replaceChild(containerNode, node);
  7790. if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
  7791. throw new Error("collection-repeat expected a parent x or y scrollView, not " +
  7792. "an xy scrollView.");
  7793. }
  7794. var repeatExpr = attr.collectionRepeat;
  7795. var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
  7796. if (!match) {
  7797. throw new Error("collection-repeat expected expression in form of '_item_ in " +
  7798. "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");
  7799. }
  7800. var keyExpr = match[1];
  7801. var listExpr = match[2];
  7802. var listGetter = $parse(listExpr);
  7803. var heightData = {};
  7804. var widthData = {};
  7805. var computedStyleDimensions = {};
  7806. var data = [];
  7807. var repeatManager;
  7808. // attr.collectionBufferSize is deprecated
  7809. var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;
  7810. var renderBuffer = angular.isDefined(renderBufferExpr) ?
  7811. parseInt(renderBufferExpr) :
  7812. DEFAULT_RENDER_BUFFER;
  7813. // attr.collectionItemHeight is deprecated
  7814. var heightExpr = attr.itemHeight || attr.collectionItemHeight;
  7815. // attr.collectionItemWidth is deprecated
  7816. var widthExpr = attr.itemWidth || attr.collectionItemWidth;
  7817. var afterItemsContainer = initAfterItemsContainer();
  7818. var changeValidator = makeChangeValidator();
  7819. initDimensions();
  7820. // Dimensions are refreshed on resize or data change.
  7821. scrollCtrl.$element.on('scroll-resize', refreshDimensions);
  7822. angular.element($window).on('resize', onResize);
  7823. var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
  7824. scrollCtrl.scrollView.resize();
  7825. onResize();
  7826. }));
  7827. $timeout(refreshDimensions, 0, false);
  7828. function onResize() {
  7829. if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {
  7830. refreshDimensions();
  7831. }
  7832. }
  7833. scope.$watchCollection(listGetter, function(newValue) {
  7834. data = newValue || (newValue = []);
  7835. if (!angular.isArray(newValue)) {
  7836. throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
  7837. "but got a " + typeof value);
  7838. }
  7839. // Wait for this digest to end before refreshing everything.
  7840. scope.$$postDigest(function() {
  7841. getRepeatManager().setData(data);
  7842. if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();
  7843. });
  7844. });
  7845. scope.$on('$destroy', function() {
  7846. angular.element($window).off('resize', onResize);
  7847. unlistenToExposeAside();
  7848. scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);
  7849. computedStyleNode && computedStyleNode.parentNode &&
  7850. computedStyleNode.parentNode.removeChild(computedStyleNode);
  7851. computedStyleScope && computedStyleScope.$destroy();
  7852. computedStyleScope = computedStyleNode = null;
  7853. repeatManager && repeatManager.destroy();
  7854. repeatManager = null;
  7855. });
  7856. function makeChangeValidator() {
  7857. var self;
  7858. return (self = {
  7859. dataLength: 0,
  7860. width: 0,
  7861. height: 0,
  7862. // A resize triggers a refresh only if we have data, the scrollView has size,
  7863. // and the size has changed.
  7864. resizeRequiresRefresh: function(newWidth, newHeight) {
  7865. var requiresRefresh = self.dataLength && newWidth && newHeight &&
  7866. (newWidth !== self.width || newHeight !== self.height);
  7867. self.width = newWidth;
  7868. self.height = newHeight;
  7869. return !!requiresRefresh;
  7870. },
  7871. // A change in data only triggers a refresh if the data has length, or if the data's
  7872. // length is less than before.
  7873. dataChangeRequiresRefresh: function(newData) {
  7874. var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;
  7875. self.dataLength = newData.length;
  7876. return !!requiresRefresh;
  7877. }
  7878. });
  7879. }
  7880. function getRepeatManager() {
  7881. return repeatManager || (repeatManager = new $ionicCollectionManager({
  7882. afterItemsNode: afterItemsContainer[0],
  7883. containerNode: containerNode,
  7884. heightData: heightData,
  7885. widthData: widthData,
  7886. forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),
  7887. keyExpression: keyExpr,
  7888. renderBuffer: renderBuffer,
  7889. scope: scope,
  7890. scrollView: scrollCtrl.scrollView,
  7891. transclude: transclude
  7892. }));
  7893. }
  7894. function initAfterItemsContainer() {
  7895. var container = angular.element(
  7896. scrollView.__content.querySelector('.collection-repeat-after-container')
  7897. );
  7898. // Put everything in the view after the repeater into a container.
  7899. if (!container.length) {
  7900. var elementIsAfterRepeater = false;
  7901. var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
  7902. if (ionic.DomUtil.contains(node, containerNode)) {
  7903. elementIsAfterRepeater = true;
  7904. return false;
  7905. }
  7906. return elementIsAfterRepeater;
  7907. });
  7908. container = angular.element('<span class="collection-repeat-after-container">');
  7909. if (scrollView.options.scrollingX) {
  7910. container.addClass('horizontal');
  7911. }
  7912. container.append(afterNodes);
  7913. scrollView.__content.appendChild(container[0]);
  7914. }
  7915. return container;
  7916. }
  7917. function initDimensions() {
  7918. //Height and width have four 'modes':
  7919. //1) Computed Mode
  7920. // - Nothing is supplied, so we getComputedStyle() on one element in the list and use
  7921. // that width and height value for the width and height of every item. This is re-computed
  7922. // every resize.
  7923. //2) Constant Mode, Static Integer
  7924. // - The user provides a constant number for width or height, in pixels. We parse it,
  7925. // store it on the `value` field, and it never changes
  7926. //3) Constant Mode, Percent
  7927. // - The user provides a percent string for width or height. The getter for percent is
  7928. // stored on the `getValue()` field, and is re-evaluated once every resize. The result
  7929. // is stored on the `value` field.
  7930. //4) Dynamic Mode
  7931. // - The user provides a dynamic expression for the width or height. This is re-evaluated
  7932. // for every item, stored on the `.getValue()` field.
  7933. if (heightExpr) {
  7934. parseDimensionAttr(heightExpr, heightData);
  7935. } else {
  7936. heightData.computed = true;
  7937. }
  7938. if (widthExpr) {
  7939. parseDimensionAttr(widthExpr, widthData);
  7940. } else {
  7941. widthData.computed = true;
  7942. }
  7943. }
  7944. function refreshDimensions() {
  7945. var hasData = data.length > 0;
  7946. if (hasData && (heightData.computed || widthData.computed)) {
  7947. computeStyleDimensions();
  7948. }
  7949. if (hasData && heightData.computed) {
  7950. heightData.value = computedStyleDimensions.height;
  7951. if (!heightData.value) {
  7952. throw new Error('collection-repeat tried to compute the height of repeated elements "' +
  7953. repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +
  7954. 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
  7955. }
  7956. } else if (!heightData.dynamic && heightData.getValue) {
  7957. // If it's a constant with a getter (eg percent), we just refresh .value after resize
  7958. heightData.value = heightData.getValue();
  7959. }
  7960. if (hasData && widthData.computed) {
  7961. widthData.value = computedStyleDimensions.width;
  7962. if (!widthData.value) {
  7963. throw new Error('collection-repeat tried to compute the width of repeated elements "' +
  7964. repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +
  7965. 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
  7966. }
  7967. } else if (!widthData.dynamic && widthData.getValue) {
  7968. // If it's a constant with a getter (eg percent), we just refresh .value after resize
  7969. widthData.value = widthData.getValue();
  7970. }
  7971. // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,
  7972. // .getValue() will be used.
  7973. getRepeatManager().refreshLayout();
  7974. }
  7975. function parseDimensionAttr(attrValue, dimensionData) {
  7976. if (!attrValue) return;
  7977. var parsedValue;
  7978. // Try to just parse the plain attr value
  7979. try {
  7980. parsedValue = $parse(attrValue);
  7981. } catch (e) {
  7982. // If the parse fails and the value has `px` or `%` in it, surround the attr in
  7983. // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`
  7984. if (attrValue.trim().match(/\d+(px|%)$/)) {
  7985. attrValue = '"' + attrValue + '"';
  7986. }
  7987. parsedValue = $parse(attrValue);
  7988. }
  7989. var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();
  7990. var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);
  7991. dimensionData.attrValue = attrValue;
  7992. // If it's a constant, it's either a percent or just a constant pixel number.
  7993. if (isConstant) {
  7994. // For percents, store the percent getter on .getValue()
  7995. if (attrValue.indexOf('%') > -1) {
  7996. var decimalValue = parseFloat(parsedValue()) / 100;
  7997. dimensionData.getValue = dimensionData === heightData ?
  7998. function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :
  7999. function() { return Math.floor(decimalValue * scrollView.__clientWidth); };
  8000. } else {
  8001. // For static constants, just store the static constant.
  8002. dimensionData.value = parseInt(parsedValue());
  8003. }
  8004. } else {
  8005. dimensionData.dynamic = true;
  8006. dimensionData.getValue = dimensionData === heightData ?
  8007. function heightGetter(scope, locals) {
  8008. var result = parsedValue(scope, locals);
  8009. if (result.charAt && result.charAt(result.length - 1) === '%') {
  8010. return Math.floor(parseFloat(result) / 100 * scrollView.__clientHeight);
  8011. }
  8012. return parseInt(result);
  8013. } :
  8014. function widthGetter(scope, locals) {
  8015. var result = parsedValue(scope, locals);
  8016. if (result.charAt && result.charAt(result.length - 1) === '%') {
  8017. return Math.floor(parseFloat(result) / 100 * scrollView.__clientWidth);
  8018. }
  8019. return parseInt(result);
  8020. };
  8021. }
  8022. }
  8023. var computedStyleNode;
  8024. var computedStyleScope;
  8025. function computeStyleDimensions() {
  8026. if (!computedStyleNode) {
  8027. transclude(computedStyleScope = scope.$new(), function(clone) {
  8028. clone[0].removeAttribute('collection-repeat'); // remove absolute position styling
  8029. computedStyleNode = clone[0];
  8030. });
  8031. }
  8032. computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];
  8033. if (!$rootScope.$$phase) computedStyleScope.$digest();
  8034. containerNode.appendChild(computedStyleNode);
  8035. var style = $window.getComputedStyle(computedStyleNode);
  8036. computedStyleDimensions.width = parseInt(style.width);
  8037. computedStyleDimensions.height = parseInt(style.height);
  8038. containerNode.removeChild(computedStyleNode);
  8039. }
  8040. }
  8041. }
  8042. RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
  8043. function RepeatManagerFactory($rootScope, $window, $$rAF) {
  8044. var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
  8045. return function RepeatController(options) {
  8046. var afterItemsNode = options.afterItemsNode;
  8047. var containerNode = options.containerNode;
  8048. var forceRefreshImages = options.forceRefreshImages;
  8049. var heightData = options.heightData;
  8050. var widthData = options.widthData;
  8051. var keyExpression = options.keyExpression;
  8052. var renderBuffer = options.renderBuffer;
  8053. var scope = options.scope;
  8054. var scrollView = options.scrollView;
  8055. var transclude = options.transclude;
  8056. var data = [];
  8057. var getterLocals = {};
  8058. var heightFn = heightData.getValue || function() { return heightData.value; };
  8059. var heightGetter = function(index, value) {
  8060. getterLocals[keyExpression] = value;
  8061. getterLocals.$index = index;
  8062. return heightFn(scope, getterLocals);
  8063. };
  8064. var widthFn = widthData.getValue || function() { return widthData.value; };
  8065. var widthGetter = function(index, value) {
  8066. getterLocals[keyExpression] = value;
  8067. getterLocals.$index = index;
  8068. return widthFn(scope, getterLocals);
  8069. };
  8070. var isVertical = !!scrollView.options.scrollingY;
  8071. // We say it's a grid view if we're either dynamic or not 100% width
  8072. var isGridView = isVertical ?
  8073. (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :
  8074. (heightData.dynamic || heightData.value !== scrollView.__clientHeight);
  8075. var isStaticView = !heightData.dynamic && !widthData.dynamic;
  8076. var PRIMARY = 'PRIMARY';
  8077. var SECONDARY = 'SECONDARY';
  8078. var TRANSLATE_TEMPLATE_STR = isVertical ?
  8079. 'translate3d(SECONDARYpx,PRIMARYpx,0)' :
  8080. 'translate3d(PRIMARYpx,SECONDARYpx,0)';
  8081. var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?
  8082. 'height: PRIMARYpx; width: SECONDARYpx;' :
  8083. 'height: SECONDARYpx; width: PRIMARYpx;';
  8084. var estimatedHeight;
  8085. var estimatedWidth;
  8086. var repeaterBeforeSize = 0;
  8087. var repeaterAfterSize = 0;
  8088. var renderStartIndex = -1;
  8089. var renderEndIndex = -1;
  8090. var renderAfterBoundary = -1;
  8091. var renderBeforeBoundary = -1;
  8092. var itemsPool = [];
  8093. var itemsLeaving = [];
  8094. var itemsEntering = [];
  8095. var itemsShownMap = {};
  8096. var nextItemId = 0;
  8097. var scrollViewSetDimensions = isVertical ?
  8098. function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
  8099. function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
  8100. // view is a mix of list/grid methods + static/dynamic methods.
  8101. // See bottom for implementations. Available methods:
  8102. //
  8103. // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),
  8104. // calculateDimensions(toIndex), getDimensions(index),
  8105. // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()
  8106. var view = isVertical ? new VerticalViewType() : new HorizontalViewType();
  8107. (isGridView ? GridViewType : ListViewType).call(view);
  8108. (isStaticView ? StaticViewType : DynamicViewType).call(view);
  8109. var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';
  8110. var originalGetContentSize = scrollView.options[contentSizeStr];
  8111. scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);
  8112. scrollView.__$callback = scrollView.__callback;
  8113. scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
  8114. var scrollValue = view.getScrollValue();
  8115. if (renderStartIndex === -1 ||
  8116. scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
  8117. scrollValue < renderBeforeBoundary) {
  8118. render();
  8119. }
  8120. scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
  8121. };
  8122. var isLayoutReady = false;
  8123. var isDataReady = false;
  8124. this.refreshLayout = function() {
  8125. if (data.length) {
  8126. estimatedHeight = heightGetter(0, data[0]);
  8127. estimatedWidth = widthGetter(0, data[0]);
  8128. } else {
  8129. // If we don't have any data in our array, just guess.
  8130. estimatedHeight = 100;
  8131. estimatedWidth = 100;
  8132. }
  8133. // Get the size of every element AFTER the repeater. We have to get the margin before and
  8134. // after the first/last element to fix a browser bug with getComputedStyle() not counting
  8135. // the first/last child's margins into height.
  8136. var style = getComputedStyle(afterItemsNode) || {};
  8137. var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};
  8138. var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};
  8139. repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +
  8140. (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +
  8141. (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);
  8142. // Get the offsetTop of the repeater.
  8143. repeaterBeforeSize = 0;
  8144. var current = containerNode;
  8145. do {
  8146. repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
  8147. } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
  8148. var containerPrevNode = containerNode.previousElementSibling;
  8149. var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};
  8150. var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);
  8151. // Because we position the collection container with position: relative, it doesn't take
  8152. // into account where to position itself relative to the previous element's marginBottom.
  8153. // To compensate, we translate the container up by the previous element's margin.
  8154. containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8155. .replace(PRIMARY, -beforeMargin)
  8156. .replace(SECONDARY, 0);
  8157. repeaterBeforeSize -= beforeMargin;
  8158. if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
  8159. scrollView.__clientWidth = scrollView.__container.clientWidth;
  8160. scrollView.__clientHeight = scrollView.__container.clientHeight;
  8161. }
  8162. (view.onRefreshLayout || angular.noop)();
  8163. view.refreshDirection();
  8164. scrollViewSetDimensions();
  8165. // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
  8166. // plus the size of the renderBuffer.
  8167. if (!isLayoutReady) {
  8168. var poolSize = Math.max(20, renderBuffer * 3);
  8169. for (var i = 0; i < poolSize; i++) {
  8170. itemsPool.push(new RepeatItem());
  8171. }
  8172. }
  8173. isLayoutReady = true;
  8174. if (isLayoutReady && isDataReady) {
  8175. // If the resize or latest data change caused the scrollValue to
  8176. // now be out of bounds, resize the scrollView.
  8177. if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
  8178. scrollView.__scrollTop > scrollView.__maxScrollTop) {
  8179. scrollView.resize();
  8180. }
  8181. forceRerender(true);
  8182. }
  8183. };
  8184. this.setData = function(newData) {
  8185. data = newData;
  8186. (view.onRefreshData || angular.noop)();
  8187. isDataReady = true;
  8188. };
  8189. this.destroy = function() {
  8190. render.destroyed = true;
  8191. itemsPool.forEach(function(item) {
  8192. item.scope.$destroy();
  8193. item.scope = item.element = item.node = item.images = null;
  8194. });
  8195. itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;
  8196. itemsShownMap = {};
  8197. //Restore the scrollView's normal behavior and resize it to normal size.
  8198. scrollView.options[contentSizeStr] = originalGetContentSize;
  8199. scrollView.__callback = scrollView.__$callback;
  8200. scrollView.resize();
  8201. (view.onDestroy || angular.noop)();
  8202. };
  8203. function forceRerender() {
  8204. return render(true);
  8205. }
  8206. function render(forceRerender) {
  8207. if (render.destroyed) return;
  8208. var i;
  8209. var ii;
  8210. var item;
  8211. var dim;
  8212. var scope;
  8213. var scrollValue = view.getScrollValue();
  8214. var scrollValueEnd = scrollValue + view.scrollPrimarySize;
  8215. view.updateRenderRange(scrollValue, scrollValueEnd);
  8216. renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);
  8217. renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);
  8218. for (i in itemsShownMap) {
  8219. if (i < renderStartIndex || i > renderEndIndex) {
  8220. item = itemsShownMap[i];
  8221. delete itemsShownMap[i];
  8222. itemsLeaving.push(item);
  8223. item.isShown = false;
  8224. }
  8225. }
  8226. // Render indicies that aren't shown yet
  8227. //
  8228. // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render
  8229. // loop will often push the render time over the edge from less than one frame to over
  8230. // one frame, causing visible jank.
  8231. // DON'T call any other functions inside this loop unless it's vital.
  8232. for (i = renderStartIndex; i <= renderEndIndex; i++) {
  8233. // We only go forward with render if the index is in data, the item isn't already shown,
  8234. // or forceRerender is on.
  8235. if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;
  8236. item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :
  8237. itemsPool.length ? itemsPool.shift() :
  8238. new RepeatItem());
  8239. itemsEntering.push(item);
  8240. item.isShown = true;
  8241. scope = item.scope;
  8242. scope.$index = i;
  8243. scope[keyExpression] = data[i];
  8244. scope.$first = (i === 0);
  8245. scope.$last = (i === (data.length - 1));
  8246. scope.$middle = !(scope.$first || scope.$last);
  8247. scope.$odd = !(scope.$even = (i & 1) === 0);
  8248. if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);
  8249. dim = view.getDimensions(i);
  8250. if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {
  8251. item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8252. .replace(PRIMARY, (item.primaryPos = dim.primaryPos))
  8253. .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));
  8254. }
  8255. if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
  8256. item.node.style.cssText = item.node.style.cssText
  8257. .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
  8258. //TODO fix item.primarySize + 1 hack
  8259. .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
  8260. .replace(SECONDARY, (item.secondarySize = dim.secondarySize))
  8261. );
  8262. }
  8263. }
  8264. // If we reach the end of the list, render the afterItemsNode - this contains all the
  8265. // elements the developer placed after the collection-repeat
  8266. if (renderEndIndex === data.length - 1) {
  8267. dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;
  8268. afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
  8269. .replace(PRIMARY, dim.primaryPos + dim.primarySize)
  8270. .replace(SECONDARY, 0);
  8271. }
  8272. while (itemsLeaving.length) {
  8273. item = itemsLeaving.pop();
  8274. item.scope.$broadcast('$collectionRepeatLeave');
  8275. ionic.Utils.disconnectScope(item.scope);
  8276. itemsPool.push(item);
  8277. item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
  8278. item.primaryPos = item.secondaryPos = null;
  8279. }
  8280. if (forceRefreshImages) {
  8281. for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {
  8282. if (!item.images) continue;
  8283. for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {
  8284. var src = img.src;
  8285. img.src = ONE_PX_TRANSPARENT_IMG_SRC;
  8286. img.src = src;
  8287. }
  8288. }
  8289. }
  8290. if (forceRerender) {
  8291. var rootScopePhase = $rootScope.$$phase;
  8292. while (itemsEntering.length) {
  8293. item = itemsEntering.pop();
  8294. if (!rootScopePhase) item.scope.$digest();
  8295. }
  8296. } else {
  8297. digestEnteringItems();
  8298. }
  8299. }
  8300. function digestEnteringItems() {
  8301. var item;
  8302. if (digestEnteringItems.running) return;
  8303. digestEnteringItems.running = true;
  8304. $$rAF(function process() {
  8305. var rootScopePhase = $rootScope.$$phase;
  8306. while (itemsEntering.length) {
  8307. item = itemsEntering.pop();
  8308. if (item.isShown) {
  8309. if (!rootScopePhase) item.scope.$digest();
  8310. }
  8311. }
  8312. digestEnteringItems.running = false;
  8313. });
  8314. }
  8315. function RepeatItem() {
  8316. var self = this;
  8317. this.scope = scope.$new();
  8318. this.id = 'item' + (nextItemId++);
  8319. transclude(this.scope, function(clone) {
  8320. self.element = clone;
  8321. self.element.data('$$collectionRepeatItem', self);
  8322. // TODO destroy
  8323. self.node = clone[0];
  8324. // Batch style setting to lower repaints
  8325. self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
  8326. self.node.style.cssText += ' height: 0px; width: 0px;';
  8327. ionic.Utils.disconnectScope(self.scope);
  8328. containerNode.appendChild(self.node);
  8329. self.images = clone[0].getElementsByTagName('img');
  8330. });
  8331. }
  8332. function VerticalViewType() {
  8333. this.getItemPrimarySize = heightGetter;
  8334. this.getItemSecondarySize = widthGetter;
  8335. this.getScrollValue = function() {
  8336. return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,
  8337. scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));
  8338. };
  8339. this.refreshDirection = function() {
  8340. this.scrollPrimarySize = scrollView.__clientHeight;
  8341. this.scrollSecondarySize = scrollView.__clientWidth;
  8342. this.estimatedPrimarySize = estimatedHeight;
  8343. this.estimatedSecondarySize = estimatedWidth;
  8344. this.estimatedItemsAcross = isGridView &&
  8345. Math.floor(scrollView.__clientWidth / estimatedWidth) ||
  8346. 1;
  8347. };
  8348. }
  8349. function HorizontalViewType() {
  8350. this.getItemPrimarySize = widthGetter;
  8351. this.getItemSecondarySize = heightGetter;
  8352. this.getScrollValue = function() {
  8353. return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,
  8354. scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));
  8355. };
  8356. this.refreshDirection = function() {
  8357. this.scrollPrimarySize = scrollView.__clientWidth;
  8358. this.scrollSecondarySize = scrollView.__clientHeight;
  8359. this.estimatedPrimarySize = estimatedWidth;
  8360. this.estimatedSecondarySize = estimatedHeight;
  8361. this.estimatedItemsAcross = isGridView &&
  8362. Math.floor(scrollView.__clientHeight / estimatedHeight) ||
  8363. 1;
  8364. };
  8365. }
  8366. function GridViewType() {
  8367. this.getEstimatedSecondaryPos = function(index) {
  8368. return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;
  8369. };
  8370. this.getEstimatedPrimaryPos = function(index) {
  8371. return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;
  8372. };
  8373. this.getEstimatedIndex = function(scrollValue) {
  8374. return Math.floor(scrollValue / this.estimatedPrimarySize) *
  8375. this.estimatedItemsAcross;
  8376. };
  8377. }
  8378. function ListViewType() {
  8379. this.getEstimatedSecondaryPos = function() {
  8380. return 0;
  8381. };
  8382. this.getEstimatedPrimaryPos = function(index) {
  8383. return index * this.estimatedPrimarySize;
  8384. };
  8385. this.getEstimatedIndex = function(scrollValue) {
  8386. return Math.floor((scrollValue) / this.estimatedPrimarySize);
  8387. };
  8388. }
  8389. function StaticViewType() {
  8390. this.getContentSize = function() {
  8391. return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +
  8392. repeaterBeforeSize + repeaterAfterSize;
  8393. };
  8394. // static view always returns the same object for getDimensions, to avoid memory allocation
  8395. // while scrolling. This could be dangerous if this was a public function, but it's not.
  8396. // Only we use it.
  8397. var dim = {};
  8398. this.getDimensions = function(index) {
  8399. dim.primaryPos = this.getEstimatedPrimaryPos(index);
  8400. dim.secondaryPos = this.getEstimatedSecondaryPos(index);
  8401. dim.primarySize = this.estimatedPrimarySize;
  8402. dim.secondarySize = this.estimatedSecondarySize;
  8403. return dim;
  8404. };
  8405. this.updateRenderRange = function(scrollValue, scrollValueEnd) {
  8406. renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));
  8407. // Make sure the renderEndIndex takes into account all the items on the row
  8408. renderEndIndex = Math.min(data.length - 1,
  8409. this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);
  8410. renderBeforeBoundary = Math.max(0,
  8411. this.getEstimatedPrimaryPos(renderStartIndex));
  8412. renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +
  8413. this.estimatedPrimarySize;
  8414. };
  8415. }
  8416. function DynamicViewType() {
  8417. var self = this;
  8418. var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
  8419. var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
  8420. var dimensionsIndex;
  8421. var dimensions = [];
  8422. // Get the dimensions at index. {width, height, left, top}.
  8423. // We start with no dimensions calculated, then any time dimensions are asked for at an
  8424. // index we calculate dimensions up to there.
  8425. function calculateDimensionsList(toIndex) {
  8426. var i, prevDimension, dim;
  8427. for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {
  8428. prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
  8429. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8430. dim.secondarySize = self.scrollSecondarySize;
  8431. dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
  8432. dim.secondaryPos = 0;
  8433. }
  8434. }
  8435. function calculateDimensionsGrid(toIndex) {
  8436. var i, prevDimension, dim;
  8437. for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {
  8438. prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
  8439. dim.secondarySize = Math.min(
  8440. self.getItemSecondarySize(i, data[i]),
  8441. self.scrollSecondarySize
  8442. );
  8443. dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
  8444. if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
  8445. dim.secondaryPos = 0;
  8446. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8447. dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
  8448. dim.rowStartIndex = i;
  8449. dim.rowPrimarySize = dim.primarySize;
  8450. } else {
  8451. dim.primarySize = self.getItemPrimarySize(i, data[i]);
  8452. dim.primaryPos = prevDimension.primaryPos;
  8453. dim.rowStartIndex = prevDimension.rowStartIndex;
  8454. dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
  8455. dimensions[dim.rowStartIndex].rowPrimarySize,
  8456. dim.primarySize
  8457. );
  8458. dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
  8459. }
  8460. }
  8461. }
  8462. this.getContentSize = function() {
  8463. var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;
  8464. return ((dim.primaryPos + dim.primarySize) || 0) +
  8465. this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +
  8466. repeaterBeforeSize + repeaterAfterSize;
  8467. };
  8468. this.onDestroy = function() {
  8469. dimensions.length = 0;
  8470. };
  8471. this.onRefreshData = function() {
  8472. var i;
  8473. var ii;
  8474. // Make sure dimensions has as many items as data.length.
  8475. // This is to be sure we don't have to allocate objects while scrolling.
  8476. for (i = dimensions.length, ii = data.length; i < ii; i++) {
  8477. dimensions.push({});
  8478. }
  8479. dimensionsIndex = -1;
  8480. };
  8481. this.onRefreshLayout = function() {
  8482. dimensionsIndex = -1;
  8483. };
  8484. this.getDimensions = function(index) {
  8485. index = Math.min(index, data.length - 1);
  8486. if (dimensionsIndex < index) {
  8487. // Once we start asking for dimensions near the end of the list, go ahead and calculate
  8488. // everything. This is to make sure when the user gets to the end of the list, the
  8489. // scroll height of the list is 100% accurate (not estimated anymore).
  8490. if (index > data.length * 0.9) {
  8491. calculateDimensions(data.length - 1);
  8492. dimensionsIndex = data.length - 1;
  8493. scrollViewSetDimensions();
  8494. } else {
  8495. calculateDimensions(index);
  8496. dimensionsIndex = index;
  8497. debouncedScrollViewSetDimensions();
  8498. }
  8499. }
  8500. return dimensions[index];
  8501. };
  8502. var oldRenderStartIndex = -1;
  8503. var oldScrollValue = -1;
  8504. this.updateRenderRange = function(scrollValue, scrollValueEnd) {
  8505. var i;
  8506. var len;
  8507. var dim;
  8508. // Calculate more dimensions than we estimate we'll need, to be sure.
  8509. this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );
  8510. // -- Calculate renderStartIndex
  8511. // base case: start at 0
  8512. if (oldRenderStartIndex === -1 || scrollValue === 0) {
  8513. i = 0;
  8514. // scrolling down
  8515. } else if (scrollValue >= oldScrollValue) {
  8516. for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
  8517. if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
  8518. break;
  8519. }
  8520. }
  8521. // scrolling up
  8522. } else {
  8523. for (i = oldRenderStartIndex; i >= 0; i--) {
  8524. if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {
  8525. // when grid view, make sure the render starts at the beginning of a row.
  8526. i = isGridView ? dim.rowStartIndex : i;
  8527. break;
  8528. }
  8529. }
  8530. }
  8531. renderStartIndex = Math.min(Math.max(0, i), data.length - 1);
  8532. renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;
  8533. // -- Calculate renderEndIndex
  8534. var lastRowDim;
  8535. for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
  8536. if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
  8537. // Go all the way to the end of the row if we're in a grid
  8538. if (isGridView) {
  8539. lastRowDim = dim;
  8540. while (i < len - 1 &&
  8541. (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {
  8542. i++;
  8543. }
  8544. }
  8545. break;
  8546. }
  8547. }
  8548. renderEndIndex = Math.min(i, data.length - 1);
  8549. renderAfterBoundary = renderEndIndex !== -1 ?
  8550. ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
  8551. -1;
  8552. oldScrollValue = scrollValue;
  8553. oldRenderStartIndex = renderStartIndex;
  8554. };
  8555. }
  8556. };
  8557. }
  8558. /**
  8559. * @ngdoc directive
  8560. * @name ionContent
  8561. * @module ionic
  8562. * @delegate ionic.service:$ionicScrollDelegate
  8563. * @restrict E
  8564. *
  8565. * @description
  8566. * The ionContent directive provides an easy to use content area that can be configured
  8567. * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
  8568. *
  8569. * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
  8570. * (for performance reasons) only the browser's native overflow scrolling will suffice,
  8571. * and so we've made it easy to toggle between the Ionic scroll implementation and
  8572. * overflow scrolling.
  8573. *
  8574. * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
  8575. * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
  8576. * directive.
  8577. *
  8578. * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate}
  8579. * after the content has been added.
  8580. *
  8581. * Be aware that this directive gets its own child scope. If you do not understand why this
  8582. * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
  8583. *
  8584. * @param {string=} delegate-handle The handle used to identify this scrollView
  8585. * with {@link ionic.service:$ionicScrollDelegate}.
  8586. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
  8587. * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
  8588. * @param {boolean=} padding Whether to add padding to the content.
  8589. * Defaults to true on iOS, false on Android.
  8590. * @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
  8591. * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
  8592. * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default.
  8593. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
  8594. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
  8595. * @param {string=} start-x Initial horizontal scroll position. Default 0.
  8596. * @param {string=} start-y Initial vertical scroll position. Default 0.
  8597. * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
  8598. * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals.
  8599. * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
  8600. * of the content. Defaults to true on iOS, false on Android.
  8601. * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
  8602. */
  8603. IonicModule
  8604. .directive('ionContent', [
  8605. '$timeout',
  8606. '$controller',
  8607. '$ionicBind',
  8608. '$ionicConfig',
  8609. function($timeout, $controller, $ionicBind, $ionicConfig) {
  8610. return {
  8611. restrict: 'E',
  8612. require: '^?ionNavView',
  8613. scope: true,
  8614. priority: 800,
  8615. compile: function(element, attr) {
  8616. var innerElement;
  8617. var scrollCtrl;
  8618. element.addClass('scroll-content ionic-scroll');
  8619. if (attr.scroll != 'false') {
  8620. //We cannot use normal transclude here because it breaks element.data()
  8621. //inheritance on compile
  8622. innerElement = jqLite('<div class="scroll"></div>');
  8623. innerElement.append(element.contents());
  8624. element.append(innerElement);
  8625. } else {
  8626. element.addClass('scroll-content-false');
  8627. }
  8628. var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
  8629. // collection-repeat requires JS scrolling
  8630. if (nativeScrolling) {
  8631. nativeScrolling = !element[0].querySelector('[collection-repeat]');
  8632. }
  8633. return { pre: prelink };
  8634. function prelink($scope, $element, $attr) {
  8635. var parentScope = $scope.$parent;
  8636. $scope.$watch(function() {
  8637. return (parentScope.$hasHeader ? ' has-header' : '') +
  8638. (parentScope.$hasSubheader ? ' has-subheader' : '') +
  8639. (parentScope.$hasFooter ? ' has-footer' : '') +
  8640. (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
  8641. (parentScope.$hasTabs ? ' has-tabs' : '') +
  8642. (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
  8643. }, function(className, oldClassName) {
  8644. $element.removeClass(oldClassName);
  8645. $element.addClass(className);
  8646. });
  8647. //Only this ionContent should use these variables from parent scopes
  8648. $scope.$hasHeader = $scope.$hasSubheader =
  8649. $scope.$hasFooter = $scope.$hasSubfooter =
  8650. $scope.$hasTabs = $scope.$hasTabsTop =
  8651. false;
  8652. $ionicBind($scope, $attr, {
  8653. $onScroll: '&onScroll',
  8654. $onScrollComplete: '&onScrollComplete',
  8655. hasBouncing: '@',
  8656. padding: '@',
  8657. direction: '@',
  8658. scrollbarX: '@',
  8659. scrollbarY: '@',
  8660. startX: '@',
  8661. startY: '@',
  8662. scrollEventInterval: '@'
  8663. });
  8664. $scope.direction = $scope.direction || 'y';
  8665. if (isDefined($attr.padding)) {
  8666. $scope.$watch($attr.padding, function(newVal) {
  8667. (innerElement || $element).toggleClass('padding', !!newVal);
  8668. });
  8669. }
  8670. if ($attr.scroll === "false") {
  8671. //do nothing
  8672. } else {
  8673. var scrollViewOptions = {};
  8674. // determined in compile phase above
  8675. if (nativeScrolling) {
  8676. // use native scrolling
  8677. $element.addClass('overflow-scroll');
  8678. scrollViewOptions = {
  8679. el: $element[0],
  8680. delegateHandle: attr.delegateHandle,
  8681. startX: $scope.$eval($scope.startX) || 0,
  8682. startY: $scope.$eval($scope.startY) || 0,
  8683. nativeScrolling: true
  8684. };
  8685. } else {
  8686. // Use JS scrolling
  8687. scrollViewOptions = {
  8688. el: $element[0],
  8689. delegateHandle: attr.delegateHandle,
  8690. locking: (attr.locking || 'true') === 'true',
  8691. bouncing: $scope.$eval($scope.hasBouncing),
  8692. startX: $scope.$eval($scope.startX) || 0,
  8693. startY: $scope.$eval($scope.startY) || 0,
  8694. scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
  8695. scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
  8696. scrollingX: $scope.direction.indexOf('x') >= 0,
  8697. scrollingY: $scope.direction.indexOf('y') >= 0,
  8698. scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
  8699. scrollingComplete: onScrollComplete
  8700. };
  8701. }
  8702. // init scroll controller with appropriate options
  8703. scrollCtrl = $controller('$ionicScroll', {
  8704. $scope: $scope,
  8705. scrollViewOptions: scrollViewOptions
  8706. });
  8707. $scope.scrollCtrl = scrollCtrl;
  8708. $scope.$on('$destroy', function() {
  8709. if (scrollViewOptions) {
  8710. scrollViewOptions.scrollingComplete = noop;
  8711. delete scrollViewOptions.el;
  8712. }
  8713. innerElement = null;
  8714. $element = null;
  8715. attr.$$element = null;
  8716. });
  8717. }
  8718. function onScrollComplete() {
  8719. $scope.$onScrollComplete({
  8720. scrollTop: scrollCtrl.scrollView.__scrollTop,
  8721. scrollLeft: scrollCtrl.scrollView.__scrollLeft
  8722. });
  8723. }
  8724. }
  8725. }
  8726. };
  8727. }]);
  8728. /**
  8729. * @ngdoc directive
  8730. * @name exposeAsideWhen
  8731. * @module ionic
  8732. * @restrict A
  8733. * @parent ionic.directive:ionSideMenus
  8734. *
  8735. * @description
  8736. * It is common for a tablet application to hide a menu when in portrait mode, but to show the
  8737. * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
  8738. * directive can be used to accomplish a similar interface.
  8739. *
  8740. * By default, side menus are hidden underneath its side menu content, and can be opened by either
  8741. * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
  8742. * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
  8743. * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
  8744. * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
  8745. * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
  8746. * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
  8747. * viewports.
  8748. *
  8749. * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
  8750. * the most common use-case. However, for added flexibility, any valid media query could be added
  8751. * as the value, such as `(min-width:600px)` or even multiple queries such as
  8752. * `(min-width:750px) and (max-width:1200px)`.
  8753. * @usage
  8754. * ```html
  8755. * <ion-side-menus>
  8756. * <!-- Center content -->
  8757. * <ion-side-menu-content>
  8758. * </ion-side-menu-content>
  8759. *
  8760. * <!-- Left menu -->
  8761. * <ion-side-menu expose-aside-when="large">
  8762. * </ion-side-menu>
  8763. * </ion-side-menus>
  8764. * ```
  8765. * For a complete side menu example, see the
  8766. * {@link ionic.directive:ionSideMenus} documentation.
  8767. */
  8768. IonicModule.directive('exposeAsideWhen', ['$window', function($window) {
  8769. return {
  8770. restrict: 'A',
  8771. require: '^ionSideMenus',
  8772. link: function($scope, $element, $attr, sideMenuCtrl) {
  8773. var prevInnerWidth = $window.innerWidth;
  8774. var prevInnerHeight = $window.innerHeight;
  8775. ionic.on('resize', function() {
  8776. if (prevInnerWidth === $window.innerWidth && prevInnerHeight === $window.innerHeight) {
  8777. return;
  8778. }
  8779. prevInnerWidth = $window.innerWidth;
  8780. prevInnerHeight = $window.innerHeight;
  8781. onResize();
  8782. }, $window);
  8783. function checkAsideExpose() {
  8784. var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
  8785. sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);
  8786. sideMenuCtrl.activeAsideResizing(false);
  8787. }
  8788. function onResize() {
  8789. sideMenuCtrl.activeAsideResizing(true);
  8790. debouncedCheck();
  8791. }
  8792. var debouncedCheck = ionic.debounce(function() {
  8793. $scope.$apply(checkAsideExpose);
  8794. }, 300, false);
  8795. $scope.$evalAsync(checkAsideExpose);
  8796. }
  8797. };
  8798. }]);
  8799. var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDragStart onDrag onDragEnd onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
  8800. GESTURE_DIRECTIVES.forEach(function(name) {
  8801. IonicModule.directive(name, gestureDirective(name));
  8802. });
  8803. /**
  8804. * @ngdoc directive
  8805. * @name onHold
  8806. * @module ionic
  8807. * @restrict A
  8808. *
  8809. * @description
  8810. * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
  8811. *
  8812. * @usage
  8813. * ```html
  8814. * <button on-hold="onHold()" class="button">Test</button>
  8815. * ```
  8816. */
  8817. /**
  8818. * @ngdoc directive
  8819. * @name onTap
  8820. * @module ionic
  8821. * @restrict A
  8822. *
  8823. * @description
  8824. * Quick touch at a location. If the duration of the touch goes
  8825. * longer than 250ms it is no longer a tap gesture.
  8826. *
  8827. * @usage
  8828. * ```html
  8829. * <button on-tap="onTap()" class="button">Test</button>
  8830. * ```
  8831. */
  8832. /**
  8833. * @ngdoc directive
  8834. * @name onDoubleTap
  8835. * @module ionic
  8836. * @restrict A
  8837. *
  8838. * @description
  8839. * Double tap touch at a location.
  8840. *
  8841. * @usage
  8842. * ```html
  8843. * <button on-double-tap="onDoubleTap()" class="button">Test</button>
  8844. * ```
  8845. */
  8846. /**
  8847. * @ngdoc directive
  8848. * @name onTouch
  8849. * @module ionic
  8850. * @restrict A
  8851. *
  8852. * @description
  8853. * Called immediately when the user first begins a touch. This
  8854. * gesture does not wait for a touchend/mouseup.
  8855. *
  8856. * @usage
  8857. * ```html
  8858. * <button on-touch="onTouch()" class="button">Test</button>
  8859. * ```
  8860. */
  8861. /**
  8862. * @ngdoc directive
  8863. * @name onRelease
  8864. * @module ionic
  8865. * @restrict A
  8866. *
  8867. * @description
  8868. * Called when the user ends a touch.
  8869. *
  8870. * @usage
  8871. * ```html
  8872. * <button on-release="onRelease()" class="button">Test</button>
  8873. * ```
  8874. */
  8875. /**
  8876. * @ngdoc directive
  8877. * @name onDragStart
  8878. * @module ionic
  8879. * @restrict A
  8880. *
  8881. * @description
  8882. * Called when a drag gesture has started.
  8883. *
  8884. * @usage
  8885. * ```html
  8886. * <button on-drag-start="onDragStart()" class="button">Test</button>
  8887. * ```
  8888. */
  8889. /**
  8890. * @ngdoc directive
  8891. * @name onDrag
  8892. * @module ionic
  8893. * @restrict A
  8894. *
  8895. * @description
  8896. * Move with one touch around on the page. Blocking the scrolling when
  8897. * moving left and right is a good practice. When all the drag events are
  8898. * blocking you disable scrolling on that area.
  8899. *
  8900. * @usage
  8901. * ```html
  8902. * <button on-drag="onDrag()" class="button">Test</button>
  8903. * ```
  8904. */
  8905. /**
  8906. * @ngdoc directive
  8907. * @name onDragEnd
  8908. * @module ionic
  8909. * @restrict A
  8910. *
  8911. * @description
  8912. * Called when a drag gesture has ended.
  8913. *
  8914. * @usage
  8915. * ```html
  8916. * <button on-drag-end="onDragEnd()" class="button">Test</button>
  8917. * ```
  8918. */
  8919. /**
  8920. * @ngdoc directive
  8921. * @name onDragUp
  8922. * @module ionic
  8923. * @restrict A
  8924. *
  8925. * @description
  8926. * Called when the element is dragged up.
  8927. *
  8928. * @usage
  8929. * ```html
  8930. * <button on-drag-up="onDragUp()" class="button">Test</button>
  8931. * ```
  8932. */
  8933. /**
  8934. * @ngdoc directive
  8935. * @name onDragRight
  8936. * @module ionic
  8937. * @restrict A
  8938. *
  8939. * @description
  8940. * Called when the element is dragged to the right.
  8941. *
  8942. * @usage
  8943. * ```html
  8944. * <button on-drag-right="onDragRight()" class="button">Test</button>
  8945. * ```
  8946. */
  8947. /**
  8948. * @ngdoc directive
  8949. * @name onDragDown
  8950. * @module ionic
  8951. * @restrict A
  8952. *
  8953. * @description
  8954. * Called when the element is dragged down.
  8955. *
  8956. * @usage
  8957. * ```html
  8958. * <button on-drag-down="onDragDown()" class="button">Test</button>
  8959. * ```
  8960. */
  8961. /**
  8962. * @ngdoc directive
  8963. * @name onDragLeft
  8964. * @module ionic
  8965. * @restrict A
  8966. *
  8967. * @description
  8968. * Called when the element is dragged to the left.
  8969. *
  8970. * @usage
  8971. * ```html
  8972. * <button on-drag-left="onDragLeft()" class="button">Test</button>
  8973. * ```
  8974. */
  8975. /**
  8976. * @ngdoc directive
  8977. * @name onSwipe
  8978. * @module ionic
  8979. * @restrict A
  8980. *
  8981. * @description
  8982. * Called when a moving touch has a high velocity in any direction.
  8983. *
  8984. * @usage
  8985. * ```html
  8986. * <button on-swipe="onSwipe()" class="button">Test</button>
  8987. * ```
  8988. */
  8989. /**
  8990. * @ngdoc directive
  8991. * @name onSwipeUp
  8992. * @module ionic
  8993. * @restrict A
  8994. *
  8995. * @description
  8996. * Called when a moving touch has a high velocity moving up.
  8997. *
  8998. * @usage
  8999. * ```html
  9000. * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
  9001. * ```
  9002. */
  9003. /**
  9004. * @ngdoc directive
  9005. * @name onSwipeRight
  9006. * @module ionic
  9007. * @restrict A
  9008. *
  9009. * @description
  9010. * Called when a moving touch has a high velocity moving to the right.
  9011. *
  9012. * @usage
  9013. * ```html
  9014. * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
  9015. * ```
  9016. */
  9017. /**
  9018. * @ngdoc directive
  9019. * @name onSwipeDown
  9020. * @module ionic
  9021. * @restrict A
  9022. *
  9023. * @description
  9024. * Called when a moving touch has a high velocity moving down.
  9025. *
  9026. * @usage
  9027. * ```html
  9028. * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
  9029. * ```
  9030. */
  9031. /**
  9032. * @ngdoc directive
  9033. * @name onSwipeLeft
  9034. * @module ionic
  9035. * @restrict A
  9036. *
  9037. * @description
  9038. * Called when a moving touch has a high velocity moving to the left.
  9039. *
  9040. * @usage
  9041. * ```html
  9042. * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
  9043. * ```
  9044. */
  9045. function gestureDirective(directiveName) {
  9046. return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
  9047. var eventType = directiveName.substr(2).toLowerCase();
  9048. return function(scope, element, attr) {
  9049. var fn = $parse( attr[directiveName] );
  9050. var listener = function(ev) {
  9051. scope.$apply(function() {
  9052. fn(scope, {
  9053. $event: ev
  9054. });
  9055. });
  9056. };
  9057. var gesture = $ionicGesture.on(eventType, listener, element);
  9058. scope.$on('$destroy', function() {
  9059. $ionicGesture.off(gesture, eventType, listener);
  9060. });
  9061. };
  9062. }];
  9063. }
  9064. IonicModule
  9065. //.directive('ionHeaderBar', tapScrollToTopDirective())
  9066. /**
  9067. * @ngdoc directive
  9068. * @name ionHeaderBar
  9069. * @module ionic
  9070. * @restrict E
  9071. *
  9072. * @description
  9073. * Adds a fixed header bar above some content.
  9074. *
  9075. * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
  9076. * See [the header CSS docs](/docs/components/#subheader).
  9077. *
  9078. * @param {string=} align-title How to align the title. By default the title
  9079. * will be aligned the same as how the platform aligns its titles (iOS centers
  9080. * titles, Android aligns them left).
  9081. * Available: 'left', 'right', or 'center'. Defaults to the same as the platform.
  9082. * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
  9083. * content to the top when tapped. Set no-tap-scroll to true to disable this
  9084. * behavior.
  9085. * Available: true or false. Defaults to false.
  9086. *
  9087. * @usage
  9088. * ```html
  9089. * <ion-header-bar align-title="left" class="bar-positive">
  9090. * <div class="buttons">
  9091. * <button class="button" ng-click="doSomething()">Left Button</button>
  9092. * </div>
  9093. * <h1 class="title">Title!</h1>
  9094. * <div class="buttons">
  9095. * <button class="button">Right Button</button>
  9096. * </div>
  9097. * </ion-header-bar>
  9098. * <ion-content class="has-header">
  9099. * Some content!
  9100. * </ion-content>
  9101. * ```
  9102. */
  9103. .directive('ionHeaderBar', headerFooterBarDirective(true))
  9104. /**
  9105. * @ngdoc directive
  9106. * @name ionFooterBar
  9107. * @module ionic
  9108. * @restrict E
  9109. *
  9110. * @description
  9111. * Adds a fixed footer bar below some content.
  9112. *
  9113. * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
  9114. * See [the footer CSS docs](/docs/components/#footer).
  9115. *
  9116. * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
  9117. * will not align correctly. This will be fixed soon.
  9118. *
  9119. * @param {string=} align-title Where to align the title.
  9120. * Available: 'left', 'right', or 'center'. Defaults to 'center'.
  9121. *
  9122. * @usage
  9123. * ```html
  9124. * <ion-content class="has-footer">
  9125. * Some content!
  9126. * </ion-content>
  9127. * <ion-footer-bar align-title="left" class="bar-assertive">
  9128. * <div class="buttons">
  9129. * <button class="button">Left Button</button>
  9130. * </div>
  9131. * <h1 class="title">Title!</h1>
  9132. * <div class="buttons" ng-click="doSomething()">
  9133. * <button class="button">Right Button</button>
  9134. * </div>
  9135. * </ion-footer-bar>
  9136. * ```
  9137. */
  9138. .directive('ionFooterBar', headerFooterBarDirective(false));
  9139. function tapScrollToTopDirective() { //eslint-disable-line no-unused-vars
  9140. return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
  9141. return {
  9142. restrict: 'E',
  9143. link: function($scope, $element, $attr) {
  9144. if ($attr.noTapScroll == 'true') {
  9145. return;
  9146. }
  9147. ionic.on('tap', onTap, $element[0]);
  9148. $scope.$on('$destroy', function() {
  9149. ionic.off('tap', onTap, $element[0]);
  9150. });
  9151. function onTap(e) {
  9152. var depth = 3;
  9153. var current = e.target;
  9154. //Don't scroll to top in certain cases
  9155. while (depth-- && current) {
  9156. if (current.classList.contains('button') ||
  9157. current.tagName.match(/input|textarea|select/i) ||
  9158. current.isContentEditable) {
  9159. return;
  9160. }
  9161. current = current.parentNode;
  9162. }
  9163. var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
  9164. var bounds = $element[0].getBoundingClientRect();
  9165. if (ionic.DomUtil.rectContains(
  9166. touch.pageX, touch.pageY,
  9167. bounds.left, bounds.top - 20,
  9168. bounds.left + bounds.width, bounds.top + bounds.height
  9169. )) {
  9170. $ionicScrollDelegate.scrollTop(true);
  9171. }
  9172. }
  9173. }
  9174. };
  9175. }];
  9176. }
  9177. function headerFooterBarDirective(isHeader) {
  9178. return ['$document', '$timeout', function($document, $timeout) {
  9179. return {
  9180. restrict: 'E',
  9181. controller: '$ionicHeaderBar',
  9182. compile: function(tElement) {
  9183. tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
  9184. // top style tabs? if so, remove bottom border for seamless display
  9185. $timeout(function() {
  9186. if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
  9187. });
  9188. return { pre: prelink };
  9189. function prelink($scope, $element, $attr, ctrl) {
  9190. if (isHeader) {
  9191. $scope.$watch(function() { return $element[0].className; }, function(value) {
  9192. var isShown = value.indexOf('ng-hide') === -1;
  9193. var isSubheader = value.indexOf('bar-subheader') !== -1;
  9194. $scope.$hasHeader = isShown && !isSubheader;
  9195. $scope.$hasSubheader = isShown && isSubheader;
  9196. $scope.$emit('$ionicSubheader', $scope.$hasSubheader);
  9197. });
  9198. $scope.$on('$destroy', function() {
  9199. delete $scope.$hasHeader;
  9200. delete $scope.$hasSubheader;
  9201. });
  9202. ctrl.align();
  9203. $scope.$on('$ionicHeader.align', function() {
  9204. ionic.requestAnimationFrame(function() {
  9205. ctrl.align();
  9206. });
  9207. });
  9208. } else {
  9209. $scope.$watch(function() { return $element[0].className; }, function(value) {
  9210. var isShown = value.indexOf('ng-hide') === -1;
  9211. var isSubfooter = value.indexOf('bar-subfooter') !== -1;
  9212. $scope.$hasFooter = isShown && !isSubfooter;
  9213. $scope.$hasSubfooter = isShown && isSubfooter;
  9214. });
  9215. $scope.$on('$destroy', function() {
  9216. delete $scope.$hasFooter;
  9217. delete $scope.$hasSubfooter;
  9218. });
  9219. $scope.$watch('$hasTabs', function(val) {
  9220. $element.toggleClass('has-tabs', !!val);
  9221. });
  9222. ctrl.align();
  9223. $scope.$on('$ionicFooter.align', function() {
  9224. ionic.requestAnimationFrame(function() {
  9225. ctrl.align();
  9226. });
  9227. });
  9228. }
  9229. }
  9230. }
  9231. };
  9232. }];
  9233. }
  9234. /**
  9235. * @ngdoc directive
  9236. * @name ionInfiniteScroll
  9237. * @module ionic
  9238. * @parent ionic.directive:ionContent, ionic.directive:ionScroll
  9239. * @restrict E
  9240. *
  9241. * @description
  9242. * The ionInfiniteScroll directive allows you to call a function whenever
  9243. * the user gets to the bottom of the page or near the bottom of the page.
  9244. *
  9245. * The expression you pass in for `on-infinite` is called when the user scrolls
  9246. * greater than `distance` away from the bottom of the content. Once `on-infinite`
  9247. * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
  9248. * event from your controller (see below example).
  9249. *
  9250. * @param {expression} on-infinite What to call when the scroller reaches the
  9251. * bottom.
  9252. * @param {string=} distance The distance from the bottom that the scroll must
  9253. * reach to trigger the on-infinite expression. Default: 1%.
  9254. * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG
  9255. * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons.
  9256. * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'. This is depreicated
  9257. * in favor of the SVG {@link ionic.directive:ionSpinner}.
  9258. * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load.
  9259. *
  9260. * @usage
  9261. * ```html
  9262. * <ion-content ng-controller="MyController">
  9263. * <ion-list>
  9264. * ....
  9265. * ....
  9266. * </ion-list>
  9267. *
  9268. * <ion-infinite-scroll
  9269. * on-infinite="loadMore()"
  9270. * distance="1%">
  9271. * </ion-infinite-scroll>
  9272. * </ion-content>
  9273. * ```
  9274. * ```js
  9275. * function MyController($scope, $http) {
  9276. * $scope.items = [];
  9277. * $scope.loadMore = function() {
  9278. * $http.get('/more-items').success(function(items) {
  9279. * useItems(items);
  9280. * $scope.$broadcast('scroll.infiniteScrollComplete');
  9281. * });
  9282. * };
  9283. *
  9284. * $scope.$on('$stateChangeSuccess', function() {
  9285. * $scope.loadMore();
  9286. * });
  9287. * }
  9288. * ```
  9289. *
  9290. * An easy to way to stop infinite scroll once there is no more data to load
  9291. * is to use angular's `ng-if` directive:
  9292. *
  9293. * ```html
  9294. * <ion-infinite-scroll
  9295. * ng-if="moreDataCanBeLoaded()"
  9296. * icon="ion-loading-c"
  9297. * on-infinite="loadMoreData()">
  9298. * </ion-infinite-scroll>
  9299. * ```
  9300. */
  9301. IonicModule
  9302. .directive('ionInfiniteScroll', ['$timeout', function($timeout) {
  9303. return {
  9304. restrict: 'E',
  9305. require: ['?^$ionicScroll', 'ionInfiniteScroll'],
  9306. template: function($element, $attrs) {
  9307. if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';
  9308. return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';
  9309. },
  9310. scope: true,
  9311. controller: '$ionInfiniteScroll',
  9312. link: function($scope, $element, $attrs, ctrls) {
  9313. var infiniteScrollCtrl = ctrls[1];
  9314. var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];
  9315. var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();
  9316. // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling
  9317. if (jsScrolling) {
  9318. infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
  9319. $scope.scrollingType = 'js-scrolling';
  9320. //bind to JS scroll events
  9321. scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);
  9322. } else {
  9323. // grabbing the scrollable element, to determine dimensions, and current scroll pos
  9324. var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');
  9325. infiniteScrollCtrl.scrollEl = scrollEl;
  9326. // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work
  9327. if (!scrollEl) {
  9328. throw 'Infinite scroll must be used inside a scrollable div';
  9329. }
  9330. //bind to native scroll events
  9331. infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);
  9332. }
  9333. // Optionally check bounds on start after scrollView is fully rendered
  9334. var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;
  9335. if (doImmediateCheck) {
  9336. $timeout(function() { infiniteScrollCtrl.checkBounds(); });
  9337. }
  9338. }
  9339. };
  9340. }]);
  9341. /**
  9342. * @ngdoc directive
  9343. * @name ionInput
  9344. * @parent ionic.directive:ionList
  9345. * @module ionic
  9346. * @restrict E
  9347. * Creates a text input group that can easily be focused
  9348. *
  9349. * @usage
  9350. *
  9351. * ```html
  9352. * <ion-list>
  9353. * <ion-input>
  9354. * <input type="text" placeholder="First Name">
  9355. * </ion-input>
  9356. *
  9357. * <ion-input>
  9358. * <ion-label>Username</ion-label>
  9359. * <input type="text">
  9360. * </ion-input>
  9361. * </ion-list>
  9362. * ```
  9363. */
  9364. var labelIds = -1;
  9365. IonicModule
  9366. .directive('ionInput', [function() {
  9367. return {
  9368. restrict: 'E',
  9369. controller: ['$scope', '$element', function($scope, $element) {
  9370. this.$scope = $scope;
  9371. this.$element = $element;
  9372. this.setInputAriaLabeledBy = function(id) {
  9373. var inputs = $element[0].querySelectorAll('input,textarea');
  9374. inputs.length && inputs[0].setAttribute('aria-labelledby', id);
  9375. };
  9376. this.focus = function() {
  9377. var inputs = $element[0].querySelectorAll('input,textarea');
  9378. inputs.length && inputs[0].focus();
  9379. };
  9380. }]
  9381. };
  9382. }]);
  9383. /**
  9384. * @ngdoc directive
  9385. * @name ionLabel
  9386. * @parent ionic.directive:ionList
  9387. * @module ionic
  9388. * @restrict E
  9389. *
  9390. * New in Ionic 1.2. It is strongly recommended that you use `<ion-label>` in place
  9391. * of any `<label>` elements for maximum cross-browser support and performance.
  9392. *
  9393. * Creates a label for a form input.
  9394. *
  9395. * @usage
  9396. *
  9397. * ```html
  9398. * <ion-list>
  9399. * <ion-input>
  9400. * <ion-label>Username</ion-label>
  9401. * <input type="text">
  9402. * </ion-input>
  9403. * </ion-list>
  9404. * ```
  9405. */
  9406. IonicModule
  9407. .directive('ionLabel', [function() {
  9408. return {
  9409. restrict: 'E',
  9410. require: '?^ionInput',
  9411. compile: function() {
  9412. return function link($scope, $element, $attrs, ionInputCtrl) {
  9413. var element = $element[0];
  9414. $element.addClass('input-label');
  9415. $element.attr('aria-label', $element.text());
  9416. var id = element.id || '_label-' + ++labelIds;
  9417. if (!element.id) {
  9418. $element.attr('id', id);
  9419. }
  9420. if (ionInputCtrl) {
  9421. ionInputCtrl.setInputAriaLabeledBy(id);
  9422. $element.on('click', function() {
  9423. ionInputCtrl.focus();
  9424. });
  9425. }
  9426. };
  9427. }
  9428. };
  9429. }]);
  9430. /**
  9431. * Input label adds accessibility to <span class="input-label">.
  9432. */
  9433. IonicModule
  9434. .directive('inputLabel', [function() {
  9435. return {
  9436. restrict: 'C',
  9437. require: '?^ionInput',
  9438. compile: function() {
  9439. return function link($scope, $element, $attrs, ionInputCtrl) {
  9440. var element = $element[0];
  9441. $element.attr('aria-label', $element.text());
  9442. var id = element.id || '_label-' + ++labelIds;
  9443. if (!element.id) {
  9444. $element.attr('id', id);
  9445. }
  9446. if (ionInputCtrl) {
  9447. ionInputCtrl.setInputAriaLabeledBy(id);
  9448. }
  9449. };
  9450. }
  9451. };
  9452. }]);
  9453. /**
  9454. * @ngdoc directive
  9455. * @name ionItem
  9456. * @parent ionic.directive:ionList
  9457. * @module ionic
  9458. * @restrict E
  9459. * Creates a list-item that can easily be swiped,
  9460. * deleted, reordered, edited, and more.
  9461. *
  9462. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9463. *
  9464. * Can be assigned any item class name. See the
  9465. * [list CSS documentation](/docs/components/#list).
  9466. *
  9467. * @usage
  9468. *
  9469. * ```html
  9470. * <ion-list>
  9471. * <ion-item>Hello!</ion-item>
  9472. * <ion-item href="#/detail">
  9473. * Link to detail page
  9474. * </ion-item>
  9475. * </ion-list>
  9476. * ```
  9477. */
  9478. IonicModule
  9479. .directive('ionItem', ['$$rAF', function($$rAF) {
  9480. return {
  9481. restrict: 'E',
  9482. controller: ['$scope', '$element', function($scope, $element) {
  9483. this.$scope = $scope;
  9484. this.$element = $element;
  9485. }],
  9486. scope: true,
  9487. compile: function($element, $attrs) {
  9488. var isAnchor = isDefined($attrs.href) ||
  9489. isDefined($attrs.ngHref) ||
  9490. isDefined($attrs.uiSref);
  9491. var isComplexItem = isAnchor ||
  9492. //Lame way of testing, but we have to know at compile what to do with the element
  9493. /ion-(delete|option|reorder)-button/i.test($element.html());
  9494. if (isComplexItem) {
  9495. var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');
  9496. innerElement.addClass('item-content');
  9497. if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {
  9498. innerElement.attr('ng-href', '{{$href()}}');
  9499. if (isDefined($attrs.target)) {
  9500. innerElement.attr('target', '{{$target()}}');
  9501. }
  9502. }
  9503. innerElement.append($element.contents());
  9504. $element.addClass('item item-complex')
  9505. .append(innerElement);
  9506. } else {
  9507. $element.addClass('item');
  9508. }
  9509. return function link($scope, $element, $attrs) {
  9510. $scope.$href = function() {
  9511. return $attrs.href || $attrs.ngHref;
  9512. };
  9513. $scope.$target = function() {
  9514. return $attrs.target;
  9515. };
  9516. var content = $element[0].querySelector('.item-content');
  9517. if (content) {
  9518. $scope.$on('$collectionRepeatLeave', function() {
  9519. if (content && content.$$ionicOptionsOpen) {
  9520. content.style[ionic.CSS.TRANSFORM] = '';
  9521. content.style[ionic.CSS.TRANSITION] = 'none';
  9522. $$rAF(function() {
  9523. content.style[ionic.CSS.TRANSITION] = '';
  9524. });
  9525. content.$$ionicOptionsOpen = false;
  9526. }
  9527. });
  9528. }
  9529. };
  9530. }
  9531. };
  9532. }]);
  9533. var ITEM_TPL_DELETE_BUTTON =
  9534. '<div class="item-left-edit item-delete enable-pointer-events">' +
  9535. '</div>';
  9536. /**
  9537. * @ngdoc directive
  9538. * @name ionDeleteButton
  9539. * @parent ionic.directive:ionItem
  9540. * @module ionic
  9541. * @restrict E
  9542. * Creates a delete button inside a list item, that is visible when the
  9543. * {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
  9544. * `$ionicListDelegate.showDelete(true)` is called.
  9545. *
  9546. * Takes any ionicon as a class.
  9547. *
  9548. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9549. *
  9550. * @usage
  9551. *
  9552. * ```html
  9553. * <ion-list show-delete="shouldShowDelete">
  9554. * <ion-item>
  9555. * <ion-delete-button class="ion-minus-circled"></ion-delete-button>
  9556. * Hello, list item!
  9557. * </ion-item>
  9558. * </ion-list>
  9559. * <ion-toggle ng-model="shouldShowDelete">
  9560. * Show Delete?
  9561. * </ion-toggle>
  9562. * ```
  9563. */
  9564. IonicModule
  9565. .directive('ionDeleteButton', function() {
  9566. function stopPropagation(ev) {
  9567. ev.stopPropagation();
  9568. }
  9569. return {
  9570. restrict: 'E',
  9571. require: ['^^ionItem', '^?ionList'],
  9572. //Run before anything else, so we can move it before other directives process
  9573. //its location (eg ngIf relies on the location of the directive in the dom)
  9574. priority: Number.MAX_VALUE,
  9575. compile: function($element, $attr) {
  9576. //Add the classes we need during the compile phase, so that they stay
  9577. //even if something else like ngIf removes the element and re-addss it
  9578. $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
  9579. return function($scope, $element, $attr, ctrls) {
  9580. var itemCtrl = ctrls[0];
  9581. var listCtrl = ctrls[1];
  9582. var container = jqLite(ITEM_TPL_DELETE_BUTTON);
  9583. container.append($element);
  9584. itemCtrl.$element.append(container).addClass('item-left-editable');
  9585. //Don't bubble click up to main .item
  9586. $element.on('click', stopPropagation);
  9587. init();
  9588. $scope.$on('$ionic.reconnectScope', init);
  9589. function init() {
  9590. listCtrl = listCtrl || $element.controller('ionList');
  9591. if (listCtrl && listCtrl.showDelete()) {
  9592. container.addClass('visible active');
  9593. }
  9594. }
  9595. };
  9596. }
  9597. };
  9598. });
  9599. IonicModule
  9600. .directive('itemFloatingLabel', function() {
  9601. return {
  9602. restrict: 'C',
  9603. link: function(scope, element) {
  9604. var el = element[0];
  9605. var input = el.querySelector('input, textarea');
  9606. var inputLabel = el.querySelector('.input-label');
  9607. if (!input || !inputLabel) return;
  9608. var onInput = function() {
  9609. if (input.value) {
  9610. inputLabel.classList.add('has-input');
  9611. } else {
  9612. inputLabel.classList.remove('has-input');
  9613. }
  9614. };
  9615. input.addEventListener('input', onInput);
  9616. var ngModelCtrl = jqLite(input).controller('ngModel');
  9617. if (ngModelCtrl) {
  9618. ngModelCtrl.$render = function() {
  9619. input.value = ngModelCtrl.$viewValue || '';
  9620. onInput();
  9621. };
  9622. }
  9623. scope.$on('$destroy', function() {
  9624. input.removeEventListener('input', onInput);
  9625. });
  9626. }
  9627. };
  9628. });
  9629. var ITEM_TPL_OPTION_BUTTONS =
  9630. '<div class="item-options invisible">' +
  9631. '</div>';
  9632. /**
  9633. * @ngdoc directive
  9634. * @name ionOptionButton
  9635. * @parent ionic.directive:ionItem
  9636. * @module ionic
  9637. * @restrict E
  9638. * @description
  9639. * Creates an option button inside a list item, that is visible when the item is swiped
  9640. * to the left by the user. Swiped open option buttons can be hidden with
  9641. * {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate.closeOptionButtons}.
  9642. *
  9643. * Can be assigned any button class.
  9644. *
  9645. * See {@link ionic.directive:ionList} for a complete example & explanation.
  9646. *
  9647. * @usage
  9648. *
  9649. * ```html
  9650. * <ion-list>
  9651. * <ion-item>
  9652. * I love kittens!
  9653. * <ion-option-button class="button-positive">Share</ion-option-button>
  9654. * <ion-option-button class="button-assertive">Edit</ion-option-button>
  9655. * </ion-item>
  9656. * </ion-list>
  9657. * ```
  9658. */
  9659. IonicModule.directive('ionOptionButton', [function() {
  9660. function stopPropagation(e) {
  9661. e.stopPropagation();
  9662. }
  9663. return {
  9664. restrict: 'E',
  9665. require: '^ionItem',
  9666. priority: Number.MAX_VALUE,
  9667. compile: function($element, $attr) {
  9668. $attr.$set('class', ($attr['class'] || '') + ' button', true);
  9669. return function($scope, $element, $attr, itemCtrl) {
  9670. if (!itemCtrl.optionsContainer) {
  9671. itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
  9672. itemCtrl.$element.append(itemCtrl.optionsContainer);
  9673. }
  9674. itemCtrl.optionsContainer.append($element);
  9675. itemCtrl.$element.addClass('item-right-editable');
  9676. //Don't bubble click up to main .item
  9677. $element.on('click', stopPropagation);
  9678. };
  9679. }
  9680. };
  9681. }]);
  9682. var ITEM_TPL_REORDER_BUTTON =
  9683. '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
  9684. '</div>';
  9685. /**
  9686. * @ngdoc directive
  9687. * @name ionReorderButton
  9688. * @parent ionic.directive:ionItem
  9689. * @module ionic
  9690. * @restrict E
  9691. * Creates a reorder button inside a list item, that is visible when the
  9692. * {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
  9693. * `$ionicListDelegate.showReorder(true)` is called.
  9694. *
  9695. * Can be dragged to reorder items in the list. Takes any ionicon class.
  9696. *
  9697. * Note: Reordering works best when used with `ng-repeat`. Be sure that all `ion-item` children of an `ion-list` are part of the same `ng-repeat` expression.
  9698. *
  9699. * When an item reorder is complete, the expression given in the `on-reorder` attribute is called. The `on-reorder` expression is given two locals that can be used: `$fromIndex` and `$toIndex`. See below for an example.
  9700. *
  9701. * Look at {@link ionic.directive:ionList} for more examples.
  9702. *
  9703. * @usage
  9704. *
  9705. * ```html
  9706. * <ion-list ng-controller="MyCtrl" show-reorder="true">
  9707. * <ion-item ng-repeat="item in items">
  9708. * Item {{item}}
  9709. * <ion-reorder-button class="ion-navicon"
  9710. * on-reorder="moveItem(item, $fromIndex, $toIndex)">
  9711. * </ion-reorder-button>
  9712. * </ion-item>
  9713. * </ion-list>
  9714. * ```
  9715. * ```js
  9716. * function MyCtrl($scope) {
  9717. * $scope.items = [1, 2, 3, 4];
  9718. * $scope.moveItem = function(item, fromIndex, toIndex) {
  9719. * //Move the item in the array
  9720. * $scope.items.splice(fromIndex, 1);
  9721. * $scope.items.splice(toIndex, 0, item);
  9722. * };
  9723. * }
  9724. * ```
  9725. *
  9726. * @param {expression=} on-reorder Expression to call when an item is reordered.
  9727. * Parameters given: $fromIndex, $toIndex.
  9728. */
  9729. IonicModule
  9730. .directive('ionReorderButton', ['$parse', function($parse) {
  9731. return {
  9732. restrict: 'E',
  9733. require: ['^ionItem', '^?ionList'],
  9734. priority: Number.MAX_VALUE,
  9735. compile: function($element, $attr) {
  9736. $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
  9737. $element[0].setAttribute('data-prevent-scroll', true);
  9738. return function($scope, $element, $attr, ctrls) {
  9739. var itemCtrl = ctrls[0];
  9740. var listCtrl = ctrls[1];
  9741. var onReorderFn = $parse($attr.onReorder);
  9742. $scope.$onReorder = function(oldIndex, newIndex) {
  9743. onReorderFn($scope, {
  9744. $fromIndex: oldIndex,
  9745. $toIndex: newIndex
  9746. });
  9747. };
  9748. // prevent clicks from bubbling up to the item
  9749. if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {
  9750. $element[0].onclick = function(e) {
  9751. e.stopPropagation();
  9752. return false;
  9753. };
  9754. }
  9755. var container = jqLite(ITEM_TPL_REORDER_BUTTON);
  9756. container.append($element);
  9757. itemCtrl.$element.append(container).addClass('item-right-editable');
  9758. if (listCtrl && listCtrl.showReorder()) {
  9759. container.addClass('visible active');
  9760. }
  9761. };
  9762. }
  9763. };
  9764. }]);
  9765. /**
  9766. * @ngdoc directive
  9767. * @name keyboardAttach
  9768. * @module ionic
  9769. * @restrict A
  9770. *
  9771. * @description
  9772. * keyboard-attach is an attribute directive which will cause an element to float above
  9773. * the keyboard when the keyboard shows. Currently only supports the
  9774. * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
  9775. *
  9776. * ### Notes
  9777. * - This directive requires the
  9778. * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
  9779. * - On Android not in fullscreen mode, i.e. you have
  9780. * `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
  9781. * this directive is unnecessary since it is the default behavior.
  9782. * - On iOS, if there is an input in your footer, you will need to set
  9783. * `cordova.plugins.Keyboard.disableScroll(true)`.
  9784. *
  9785. * @usage
  9786. *
  9787. * ```html
  9788. * <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
  9789. * <h1 class="title">Title!</h1>
  9790. * </ion-footer-bar>
  9791. * ```
  9792. */
  9793. IonicModule
  9794. .directive('keyboardAttach', function() {
  9795. return function(scope, element) {
  9796. ionic.on('native.keyboardshow', onShow, window);
  9797. ionic.on('native.keyboardhide', onHide, window);
  9798. //deprecated
  9799. ionic.on('native.showkeyboard', onShow, window);
  9800. ionic.on('native.hidekeyboard', onHide, window);
  9801. var scrollCtrl;
  9802. function onShow(e) {
  9803. if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
  9804. return;
  9805. }
  9806. //for testing
  9807. var keyboardHeight = e.keyboardHeight || (e.detail && e.detail.keyboardHeight);
  9808. element.css('bottom', keyboardHeight + "px");
  9809. scrollCtrl = element.controller('$ionicScroll');
  9810. if (scrollCtrl) {
  9811. scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
  9812. }
  9813. }
  9814. function onHide() {
  9815. if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
  9816. return;
  9817. }
  9818. element.css('bottom', '');
  9819. if (scrollCtrl) {
  9820. scrollCtrl.scrollView.__container.style.bottom = '';
  9821. }
  9822. }
  9823. scope.$on('$destroy', function() {
  9824. ionic.off('native.keyboardshow', onShow, window);
  9825. ionic.off('native.keyboardhide', onHide, window);
  9826. //deprecated
  9827. ionic.off('native.showkeyboard', onShow, window);
  9828. ionic.off('native.hidekeyboard', onHide, window);
  9829. });
  9830. };
  9831. });
  9832. function keyboardAttachGetClientHeight(element) {
  9833. return element.clientHeight;
  9834. }
  9835. /**
  9836. * @ngdoc directive
  9837. * @name ionList
  9838. * @module ionic
  9839. * @delegate ionic.service:$ionicListDelegate
  9840. * @codepen JsHjf
  9841. * @restrict E
  9842. * @description
  9843. * The List is a widely used interface element in almost any mobile app, and can include
  9844. * content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
  9845. *
  9846. * Both the list, which contains items, and the list items themselves can be any HTML
  9847. * element. The containing element requires the `list` class and each list item requires
  9848. * the `item` class.
  9849. *
  9850. * However, using the ionList and ionItem directives make it easy to support various
  9851. * interaction modes such as swipe to edit, drag to reorder, and removing items.
  9852. *
  9853. * Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
  9854. * {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
  9855. *
  9856. * @usage
  9857. *
  9858. * Basic Usage:
  9859. *
  9860. * ```html
  9861. * <ion-list>
  9862. * <ion-item ng-repeat="item in items">
  9863. * {% raw %}Hello, {{item}}!{% endraw %}
  9864. * </ion-item>
  9865. * </ion-list>
  9866. * ```
  9867. *
  9868. * Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
  9869. *
  9870. * ```html
  9871. * <ion-list ng-controller="MyCtrl"
  9872. * show-delete="shouldShowDelete"
  9873. * show-reorder="shouldShowReorder"
  9874. * can-swipe="listCanSwipe">
  9875. * <ion-item ng-repeat="item in items"
  9876. * class="item-thumbnail-left">
  9877. *
  9878. * {% raw %}<img ng-src="{{item.img}}">
  9879. * <h2>{{item.title}}</h2>
  9880. * <p>{{item.description}}</p>{% endraw %}
  9881. * <ion-option-button class="button-positive"
  9882. * ng-click="share(item)">
  9883. * Share
  9884. * </ion-option-button>
  9885. * <ion-option-button class="button-info"
  9886. * ng-click="edit(item)">
  9887. * Edit
  9888. * </ion-option-button>
  9889. * <ion-delete-button class="ion-minus-circled"
  9890. * ng-click="items.splice($index, 1)">
  9891. * </ion-delete-button>
  9892. * <ion-reorder-button class="ion-navicon"
  9893. * on-reorder="reorderItem(item, $fromIndex, $toIndex)">
  9894. * </ion-reorder-button>
  9895. *
  9896. * </ion-item>
  9897. * </ion-list>
  9898. * ```
  9899. *
  9900. *```javascript
  9901. * app.controller('MyCtrl', function($scope) {
  9902. * $scope.shouldShowDelete = false;
  9903. * $scope.shouldShowReorder = false;
  9904. * $scope.listCanSwipe = true
  9905. * });
  9906. *```
  9907. *
  9908. * @param {string=} delegate-handle The handle used to identify this list with
  9909. * {@link ionic.service:$ionicListDelegate}.
  9910. * @param type {string=} The type of list to use (list-inset or card)
  9911. * @param show-delete {boolean=} Whether the delete buttons for the items in the list are
  9912. * currently shown or hidden.
  9913. * @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
  9914. * currently shown or hidden.
  9915. * @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
  9916. * option buttons. Default: true.
  9917. */
  9918. IonicModule
  9919. .directive('ionList', [
  9920. '$timeout',
  9921. function($timeout) {
  9922. return {
  9923. restrict: 'E',
  9924. require: ['ionList', '^?$ionicScroll'],
  9925. controller: '$ionicList',
  9926. compile: function($element, $attr) {
  9927. var listEl = jqLite('<div class="list">')
  9928. .append($element.contents())
  9929. .addClass($attr.type);
  9930. $element.append(listEl);
  9931. return function($scope, $element, $attrs, ctrls) {
  9932. var listCtrl = ctrls[0];
  9933. var scrollCtrl = ctrls[1];
  9934. // Wait for child elements to render...
  9935. $timeout(init);
  9936. function init() {
  9937. var listView = listCtrl.listView = new ionic.views.ListView({
  9938. el: $element[0],
  9939. listEl: $element.children()[0],
  9940. scrollEl: scrollCtrl && scrollCtrl.element,
  9941. scrollView: scrollCtrl && scrollCtrl.scrollView,
  9942. onReorder: function(el, oldIndex, newIndex) {
  9943. var itemScope = jqLite(el).scope();
  9944. if (itemScope && itemScope.$onReorder) {
  9945. // Make sure onReorder is called in apply cycle,
  9946. // but also make sure it has no conflicts by doing
  9947. // $evalAsync
  9948. $timeout(function() {
  9949. itemScope.$onReorder(oldIndex, newIndex);
  9950. });
  9951. }
  9952. },
  9953. canSwipe: function() {
  9954. return listCtrl.canSwipeItems();
  9955. }
  9956. });
  9957. $scope.$on('$destroy', function() {
  9958. if (listView) {
  9959. listView.deregister && listView.deregister();
  9960. listView = null;
  9961. }
  9962. });
  9963. if (isDefined($attr.canSwipe)) {
  9964. $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
  9965. listCtrl.canSwipeItems(value);
  9966. });
  9967. }
  9968. if (isDefined($attr.showDelete)) {
  9969. $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
  9970. listCtrl.showDelete(value);
  9971. });
  9972. }
  9973. if (isDefined($attr.showReorder)) {
  9974. $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
  9975. listCtrl.showReorder(value);
  9976. });
  9977. }
  9978. $scope.$watch(function() {
  9979. return listCtrl.showDelete();
  9980. }, function(isShown, wasShown) {
  9981. //Only use isShown=false if it was already shown
  9982. if (!isShown && !wasShown) { return; }
  9983. if (isShown) listCtrl.closeOptionButtons();
  9984. listCtrl.canSwipeItems(!isShown);
  9985. $element.children().toggleClass('list-left-editing', isShown);
  9986. $element.toggleClass('disable-pointer-events', isShown);
  9987. var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
  9988. setButtonShown(deleteButton, listCtrl.showDelete);
  9989. });
  9990. $scope.$watch(function() {
  9991. return listCtrl.showReorder();
  9992. }, function(isShown, wasShown) {
  9993. //Only use isShown=false if it was already shown
  9994. if (!isShown && !wasShown) { return; }
  9995. if (isShown) listCtrl.closeOptionButtons();
  9996. listCtrl.canSwipeItems(!isShown);
  9997. $element.children().toggleClass('list-right-editing', isShown);
  9998. $element.toggleClass('disable-pointer-events', isShown);
  9999. var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
  10000. setButtonShown(reorderButton, listCtrl.showReorder);
  10001. });
  10002. function setButtonShown(el, shown) {
  10003. shown() && el.addClass('visible') || el.removeClass('active');
  10004. ionic.requestAnimationFrame(function() {
  10005. shown() && el.addClass('active') || el.removeClass('visible');
  10006. });
  10007. }
  10008. }
  10009. };
  10010. }
  10011. };
  10012. }]);
  10013. /**
  10014. * @ngdoc directive
  10015. * @name menuClose
  10016. * @module ionic
  10017. * @restrict AC
  10018. *
  10019. * @description
  10020. * `menu-close` is an attribute directive that closes a currently opened side menu.
  10021. * Note that by default, navigation transitions will not animate between views when
  10022. * the menu is open. Additionally, this directive will reset the entering view's
  10023. * history stack, making the new page the root of the history stack. This is done
  10024. * to replicate the user experience seen in most side menu implementations, which is
  10025. * to not show the back button at the root of the stack and show only the
  10026. * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
  10027. * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
  10028. *
  10029. * @usage
  10030. * Below is an example of a link within a side menu. Tapping this link would
  10031. * automatically close the currently opened menu.
  10032. *
  10033. * ```html
  10034. * <a menu-close href="#/home" class="item">Home</a>
  10035. * ```
  10036. *
  10037. * Note that if your destination state uses a resolve and that resolve asynchronously
  10038. * takes longer than a standard transition (300ms), you'll need to set the
  10039. * `nextViewOptions` manually as your resolve completes.
  10040. *
  10041. * ```js
  10042. * $ionicHistory.nextViewOptions({
  10043. * historyRoot: true,
  10044. * disableAnimate: true,
  10045. * expire: 300
  10046. * });
  10047. * ```
  10048. */
  10049. IonicModule
  10050. .directive('menuClose', ['$ionicHistory', '$timeout', function($ionicHistory, $timeout) {
  10051. return {
  10052. restrict: 'AC',
  10053. link: function($scope, $element) {
  10054. $element.bind('click', function() {
  10055. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10056. if (sideMenuCtrl) {
  10057. $ionicHistory.nextViewOptions({
  10058. historyRoot: true,
  10059. disableAnimate: true,
  10060. expire: 300
  10061. });
  10062. // if no transition in 300ms, reset nextViewOptions
  10063. // the expire should take care of it, but will be cancelled in some
  10064. // cases. This directive is an exception to the rules of history.js
  10065. $timeout( function() {
  10066. $ionicHistory.nextViewOptions({
  10067. historyRoot: false,
  10068. disableAnimate: false
  10069. });
  10070. }, 300);
  10071. sideMenuCtrl.close();
  10072. }
  10073. });
  10074. }
  10075. };
  10076. }]);
  10077. /**
  10078. * @ngdoc directive
  10079. * @name menuToggle
  10080. * @module ionic
  10081. * @restrict AC
  10082. *
  10083. * @description
  10084. * Toggle a side menu on the given side.
  10085. *
  10086. * @usage
  10087. * Below is an example of a link within a nav bar. Tapping this button
  10088. * would open the given side menu, and tapping it again would close it.
  10089. *
  10090. * ```html
  10091. * <ion-nav-bar>
  10092. * <ion-nav-buttons side="left">
  10093. * <!-- Toggle left side menu -->
  10094. * <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
  10095. * </ion-nav-buttons>
  10096. * <ion-nav-buttons side="right">
  10097. * <!-- Toggle right side menu -->
  10098. * <button menu-toggle="right" class="button button-icon icon ion-navicon"></button>
  10099. * </ion-nav-buttons>
  10100. * </ion-nav-bar>
  10101. * ```
  10102. *
  10103. * ### Button Hidden On Child Views
  10104. * By default, the menu toggle button will only appear on a root
  10105. * level side-menu page. Navigating in to child views will hide the menu-
  10106. * toggle button. They can be made visible on child pages by setting the
  10107. * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus}
  10108. * directive to true.
  10109. *
  10110. * ```html
  10111. * <ion-side-menus enable-menu-with-back-views="true">
  10112. * ```
  10113. */
  10114. IonicModule
  10115. .directive('menuToggle', function() {
  10116. return {
  10117. restrict: 'AC',
  10118. link: function($scope, $element, $attr) {
  10119. $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
  10120. if (viewData.enableBack) {
  10121. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10122. if (!sideMenuCtrl.enableMenuWithBackViews()) {
  10123. $element.addClass('hide');
  10124. }
  10125. } else {
  10126. $element.removeClass('hide');
  10127. }
  10128. });
  10129. $element.bind('click', function() {
  10130. var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
  10131. sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
  10132. });
  10133. }
  10134. };
  10135. });
  10136. /*
  10137. * We don't document the ionModal directive, we instead document
  10138. * the $ionicModal service
  10139. */
  10140. IonicModule
  10141. .directive('ionModal', [function() {
  10142. return {
  10143. restrict: 'E',
  10144. transclude: true,
  10145. replace: true,
  10146. controller: [function() {}],
  10147. template: '<div class="modal-backdrop">' +
  10148. '<div class="modal-backdrop-bg"></div>' +
  10149. '<div class="modal-wrapper" ng-transclude></div>' +
  10150. '</div>'
  10151. };
  10152. }]);
  10153. IonicModule
  10154. .directive('ionModalView', function() {
  10155. return {
  10156. restrict: 'E',
  10157. compile: function(element) {
  10158. element.addClass('modal');
  10159. }
  10160. };
  10161. });
  10162. /**
  10163. * @ngdoc directive
  10164. * @name ionNavBackButton
  10165. * @module ionic
  10166. * @restrict E
  10167. * @parent ionNavBar
  10168. * @description
  10169. * Creates a back button inside an {@link ionic.directive:ionNavBar}.
  10170. *
  10171. * The back button will appear when the user is able to go back in the current navigation stack. By
  10172. * default, the markup of the back button is automatically built using platform-appropriate defaults
  10173. * (iOS back button icon on iOS and Android icon on Android).
  10174. *
  10175. * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
  10176. * app will navigate back one view when the back button is clicked. More advanced behavior is also
  10177. * possible, as outlined below.
  10178. *
  10179. * @usage
  10180. *
  10181. * Recommended markup for default settings:
  10182. *
  10183. * ```html
  10184. * <ion-nav-bar>
  10185. * <ion-nav-back-button>
  10186. * </ion-nav-back-button>
  10187. * </ion-nav-bar>
  10188. * ```
  10189. *
  10190. * With custom inner markup, and automatically adds a default click action:
  10191. *
  10192. * ```html
  10193. * <ion-nav-bar>
  10194. * <ion-nav-back-button class="button-clear">
  10195. * <i class="ion-arrow-left-c"></i> Back
  10196. * </ion-nav-back-button>
  10197. * </ion-nav-bar>
  10198. * ```
  10199. *
  10200. * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}:
  10201. *
  10202. * ```html
  10203. * <ion-nav-bar ng-controller="MyCtrl">
  10204. * <ion-nav-back-button class="button-clear"
  10205. * ng-click="myGoBack()">
  10206. * <i class="ion-arrow-left-c"></i> Back
  10207. * </ion-nav-back-button>
  10208. * </ion-nav-bar>
  10209. * ```
  10210. * ```js
  10211. * function MyCtrl($scope, $ionicHistory) {
  10212. * $scope.myGoBack = function() {
  10213. * $ionicHistory.goBack();
  10214. * };
  10215. * }
  10216. * ```
  10217. */
  10218. IonicModule
  10219. .directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
  10220. return {
  10221. restrict: 'E',
  10222. require: '^ionNavBar',
  10223. compile: function(tElement, tAttrs) {
  10224. // clone the back button, but as a <div>
  10225. var buttonEle = $document[0].createElement('button');
  10226. for (var n in tAttrs.$attr) {
  10227. buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
  10228. }
  10229. if (!tAttrs.ngClick) {
  10230. buttonEle.setAttribute('ng-click', '$ionicGoBack()');
  10231. }
  10232. buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
  10233. buttonEle.innerHTML = tElement.html() || '';
  10234. var childNode;
  10235. var hasIcon = hasIconClass(tElement[0]);
  10236. var hasInnerText;
  10237. var hasButtonText;
  10238. var hasPreviousTitle;
  10239. for (var x = 0; x < tElement[0].childNodes.length; x++) {
  10240. childNode = tElement[0].childNodes[x];
  10241. if (childNode.nodeType === 1) {
  10242. if (hasIconClass(childNode)) {
  10243. hasIcon = true;
  10244. } else if (childNode.classList.contains('default-title')) {
  10245. hasButtonText = true;
  10246. } else if (childNode.classList.contains('previous-title')) {
  10247. hasPreviousTitle = true;
  10248. }
  10249. } else if (!hasInnerText && childNode.nodeType === 3) {
  10250. hasInnerText = !!childNode.nodeValue.trim();
  10251. }
  10252. }
  10253. function hasIconClass(ele) {
  10254. return /ion-|icon/.test(ele.className);
  10255. }
  10256. var defaultIcon = $ionicConfig.backButton.icon();
  10257. if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
  10258. buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
  10259. buttonEle.className += ' button-clear';
  10260. }
  10261. if (!hasInnerText) {
  10262. var buttonTextEle = $document[0].createElement('span');
  10263. buttonTextEle.className = 'back-text';
  10264. if (!hasButtonText && $ionicConfig.backButton.text()) {
  10265. buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
  10266. }
  10267. if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
  10268. buttonTextEle.innerHTML += '<span class="previous-title"></span>';
  10269. }
  10270. buttonEle.appendChild(buttonTextEle);
  10271. }
  10272. tElement.attr('class', 'hide');
  10273. tElement.empty();
  10274. return {
  10275. pre: function($scope, $element, $attr, navBarCtrl) {
  10276. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10277. navBarCtrl.navElement('backButton', buttonEle.outerHTML);
  10278. buttonEle = null;
  10279. }
  10280. };
  10281. }
  10282. };
  10283. }]);
  10284. /**
  10285. * @ngdoc directive
  10286. * @name ionNavBar
  10287. * @module ionic
  10288. * @delegate ionic.service:$ionicNavBarDelegate
  10289. * @restrict E
  10290. *
  10291. * @description
  10292. * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
  10293. * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
  10294. *
  10295. * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
  10296. *
  10297. * We can add buttons depending on the currently visible view using
  10298. * {@link ionic.directive:ionNavButtons}.
  10299. *
  10300. * Note that the ion-nav-bar element will only work correctly if your content has an
  10301. * ionView around it.
  10302. *
  10303. * @usage
  10304. *
  10305. * ```html
  10306. * <body ng-app="starter">
  10307. * <!-- The nav bar that will be updated as we navigate -->
  10308. * <ion-nav-bar class="bar-positive">
  10309. * </ion-nav-bar>
  10310. *
  10311. * <!-- where the initial view template will be rendered -->
  10312. * <ion-nav-view>
  10313. * <ion-view>
  10314. * <ion-content>Hello!</ion-content>
  10315. * </ion-view>
  10316. * </ion-nav-view>
  10317. * </body>
  10318. * ```
  10319. *
  10320. * @param {string=} delegate-handle The handle used to identify this navBar
  10321. * with {@link ionic.service:$ionicNavBarDelegate}.
  10322. * @param align-title {string=} Where to align the title of the navbar.
  10323. * Available: 'left', 'right', 'center'. Defaults to 'center'.
  10324. * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
  10325. * to the top when tapped. Set no-tap-scroll to true to disable this behavior.
  10326. *
  10327. * </table><br/>
  10328. */
  10329. IonicModule
  10330. .directive('ionNavBar', function() {
  10331. return {
  10332. restrict: 'E',
  10333. controller: '$ionicNavBar',
  10334. scope: true,
  10335. link: function($scope, $element, $attr, ctrl) {
  10336. ctrl.init();
  10337. }
  10338. };
  10339. });
  10340. /**
  10341. * @ngdoc directive
  10342. * @name ionNavButtons
  10343. * @module ionic
  10344. * @restrict E
  10345. * @parent ionNavView
  10346. *
  10347. * @description
  10348. * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
  10349. * from within an {@link ionic.directive:ionView}. This gives each
  10350. * view template the ability to specify which buttons should show in the nav bar,
  10351. * overriding any default buttons already placed in the nav bar.
  10352. *
  10353. * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
  10354. * buttons generally map to the left side of the header, and secondary buttons are
  10355. * generally on the right side. However, their exact locations are platform-specific.
  10356. * For example, in iOS, the primary buttons are on the far left of the header, and
  10357. * secondary buttons are on the far right, with the header title centered between them.
  10358. * For Android, however, both groups of buttons are on the far right of the header,
  10359. * with the header title aligned left.
  10360. *
  10361. * We recommend always using `primary` and `secondary`, so the buttons correctly map
  10362. * to the side familiar to users of each platform. However, in cases where buttons should
  10363. * always be on an exact side, both `left` and `right` sides are still available. For
  10364. * example, a toggle button for a left side menu should be on the left side; in this case,
  10365. * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
  10366. *
  10367. * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
  10368. * `ion-nav-bar` element (basically, don't wrap it in another div).
  10369. *
  10370. * @usage
  10371. * ```html
  10372. * <ion-nav-bar>
  10373. * </ion-nav-bar>
  10374. * <ion-nav-view>
  10375. * <ion-view>
  10376. * <ion-nav-buttons side="primary">
  10377. * <button class="button" ng-click="doSomething()">
  10378. * I'm a button on the primary of the navbar!
  10379. * </button>
  10380. * </ion-nav-buttons>
  10381. * <ion-content>
  10382. * Some super content here!
  10383. * </ion-content>
  10384. * </ion-view>
  10385. * </ion-nav-view>
  10386. * ```
  10387. *
  10388. * @param {string} side The side to place the buttons in the
  10389. * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
  10390. */
  10391. IonicModule
  10392. .directive('ionNavButtons', ['$document', function($document) {
  10393. return {
  10394. require: '^ionNavBar',
  10395. restrict: 'E',
  10396. compile: function(tElement, tAttrs) {
  10397. var side = 'left';
  10398. if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
  10399. side = tAttrs.side.toLowerCase();
  10400. }
  10401. var spanEle = $document[0].createElement('span');
  10402. spanEle.className = side + '-buttons';
  10403. spanEle.innerHTML = tElement.html();
  10404. var navElementType = side + 'Buttons';
  10405. tElement.attr('class', 'hide');
  10406. tElement.empty();
  10407. return {
  10408. pre: function($scope, $element, $attrs, navBarCtrl) {
  10409. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10410. var parentViewCtrl = $element.parent().data('$ionViewController');
  10411. if (parentViewCtrl) {
  10412. // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
  10413. parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
  10414. } else {
  10415. // these are buttons for all views that do not have their own ion-nav-buttons
  10416. navBarCtrl.navElement(navElementType, spanEle.outerHTML);
  10417. }
  10418. spanEle = null;
  10419. }
  10420. };
  10421. }
  10422. };
  10423. }]);
  10424. /**
  10425. * @ngdoc directive
  10426. * @name navDirection
  10427. * @module ionic
  10428. * @restrict A
  10429. *
  10430. * @description
  10431. * The direction which the nav view transition should animate. Available options
  10432. * are: `forward`, `back`, `enter`, `exit`, `swap`.
  10433. *
  10434. * @usage
  10435. *
  10436. * ```html
  10437. * <a nav-direction="forward" href="#/home">Home</a>
  10438. * ```
  10439. */
  10440. IonicModule
  10441. .directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
  10442. return {
  10443. restrict: 'A',
  10444. priority: 1000,
  10445. link: function($scope, $element, $attr) {
  10446. $element.bind('click', function() {
  10447. $ionicViewSwitcher.nextDirection($attr.navDirection);
  10448. });
  10449. }
  10450. };
  10451. }]);
  10452. /**
  10453. * @ngdoc directive
  10454. * @name ionNavTitle
  10455. * @module ionic
  10456. * @restrict E
  10457. * @parent ionNavView
  10458. *
  10459. * @description
  10460. *
  10461. * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
  10462. * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
  10463. * view the ability to specify its own custom title element, such as an image or any HTML,
  10464. * rather than being text-only. Alternatively, text-only titles can be updated using the
  10465. * `view-title` {@link ionic.directive:ionView} attribute.
  10466. *
  10467. * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
  10468. * `ion-nav-bar` element (basically don't wrap it in another div).
  10469. *
  10470. * @usage
  10471. * ```html
  10472. * <ion-nav-bar>
  10473. * </ion-nav-bar>
  10474. * <ion-nav-view>
  10475. * <ion-view>
  10476. * <ion-nav-title>
  10477. * <img src="logo.svg">
  10478. * </ion-nav-title>
  10479. * <ion-content>
  10480. * Some super content here!
  10481. * </ion-content>
  10482. * </ion-view>
  10483. * </ion-nav-view>
  10484. * ```
  10485. *
  10486. */
  10487. IonicModule
  10488. .directive('ionNavTitle', ['$document', function($document) {
  10489. return {
  10490. require: '^ionNavBar',
  10491. restrict: 'E',
  10492. compile: function(tElement, tAttrs) {
  10493. var navElementType = 'title';
  10494. var spanEle = $document[0].createElement('span');
  10495. for (var n in tAttrs.$attr) {
  10496. spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
  10497. }
  10498. spanEle.classList.add('nav-bar-title');
  10499. spanEle.innerHTML = tElement.html();
  10500. tElement.attr('class', 'hide');
  10501. tElement.empty();
  10502. return {
  10503. pre: function($scope, $element, $attrs, navBarCtrl) {
  10504. // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
  10505. var parentViewCtrl = $element.parent().data('$ionViewController');
  10506. if (parentViewCtrl) {
  10507. // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
  10508. parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
  10509. } else {
  10510. // these are buttons for all views that do not have their own ion-nav-buttons
  10511. navBarCtrl.navElement(navElementType, spanEle.outerHTML);
  10512. }
  10513. spanEle = null;
  10514. }
  10515. };
  10516. }
  10517. };
  10518. }]);
  10519. /**
  10520. * @ngdoc directive
  10521. * @name navTransition
  10522. * @module ionic
  10523. * @restrict A
  10524. *
  10525. * @description
  10526. * The transition type which the nav view transition should use when it animates.
  10527. * Current, options are `ios`, `android`, and `none`. More options coming soon.
  10528. *
  10529. * @usage
  10530. *
  10531. * ```html
  10532. * <a nav-transition="none" href="#/home">Home</a>
  10533. * ```
  10534. */
  10535. IonicModule
  10536. .directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
  10537. return {
  10538. restrict: 'A',
  10539. priority: 1000,
  10540. link: function($scope, $element, $attr) {
  10541. $element.bind('click', function() {
  10542. $ionicViewSwitcher.nextTransition($attr.navTransition);
  10543. });
  10544. }
  10545. };
  10546. }]);
  10547. /**
  10548. * @ngdoc directive
  10549. * @name ionNavView
  10550. * @module ionic
  10551. * @restrict E
  10552. * @codepen odqCz
  10553. *
  10554. * @description
  10555. * As a user navigates throughout your app, Ionic is able to keep track of their
  10556. * navigation history. By knowing their history, transitions between views
  10557. * correctly enter and exit using the platform's transition style. An additional
  10558. * benefit to Ionic's navigation system is its ability to manage multiple
  10559. * histories. For example, each tab can have it's own navigation history stack.
  10560. *
  10561. * Ionic uses the AngularUI Router module so app interfaces can be organized
  10562. * into various "states". Like Angular's core $route service, URLs can be used
  10563. * to control the views. However, the AngularUI Router provides a more powerful
  10564. * state manager in that states are bound to named, nested, and parallel views,
  10565. * allowing more than one template to be rendered on the same page.
  10566. * Additionally, each state is not required to be bound to a URL, and data can
  10567. * be pushed to each state which allows much flexibility.
  10568. *
  10569. * The ionNavView directive is used to render templates in your application. Each template
  10570. * is part of a state. States are usually mapped to a url, and are defined programatically
  10571. * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
  10572. * and remember to replace ui-view with ion-nav-view in examples).
  10573. *
  10574. * @usage
  10575. * In this example, we will create a navigation view that contains our different states for the app.
  10576. *
  10577. * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
  10578. * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
  10579. * navigation stack.
  10580. *
  10581. * Next, we need to setup our states that will be rendered.
  10582. *
  10583. * ```js
  10584. * var app = angular.module('myApp', ['ionic']);
  10585. * app.config(function($stateProvider) {
  10586. * $stateProvider
  10587. * .state('index', {
  10588. * url: '/',
  10589. * templateUrl: 'home.html'
  10590. * })
  10591. * .state('music', {
  10592. * url: '/music',
  10593. * templateUrl: 'music.html'
  10594. * });
  10595. * });
  10596. * ```
  10597. * Then on app start, $stateProvider will look at the url, see if it matches the index state,
  10598. * and then try to load home.html into the `<ion-nav-view>`.
  10599. *
  10600. * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
  10601. * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
  10602. * So here is one way to put home.html into our app:
  10603. *
  10604. * ```html
  10605. * <script id="home" type="text/ng-template">
  10606. * <!-- The title of the ion-view will be shown on the navbar -->
  10607. * <ion-view view-title="Home">
  10608. * <ion-content ng-controller="HomeCtrl">
  10609. * <!-- The content of the page -->
  10610. * <a href="#/music">Go to music page!</a>
  10611. * </ion-content>
  10612. * </ion-view>
  10613. * </script>
  10614. * ```
  10615. *
  10616. * This is good to do because the template will be cached for very fast loading, instead of
  10617. * having to fetch them from the network.
  10618. *
  10619. * ## Caching
  10620. *
  10621. * By default, views are cached to improve performance. When a view is navigated away from, its
  10622. * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
  10623. * navigating to a view that is already cached, its scope is then reconnected, and the existing
  10624. * element that was left in the DOM becomes the active view. This also allows for the scroll
  10625. * position of previous views to be maintained.
  10626. *
  10627. * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
  10628. * 10 views, and not only can this be configured, but apps can also explicitly state which views
  10629. * should and should not be cached.
  10630. *
  10631. * Note that because we are caching these views, *we arent destroying scopes*. Instead, scopes
  10632. * are being disconnected from the watch cycle. Because scopes are not being destroyed and
  10633. * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
  10634. * needs to know when a view has entered or has left, then view events emitted from the
  10635. * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful.
  10636. *
  10637. * By default, when navigating back in the history, the "forward" views are removed from the cache.
  10638. * If you navigate forward to the same view again, it'll create a new DOM element and controller
  10639. * instance. Basically, any forward views are reset each time. This can be configured using the
  10640. * {@link ionic.provider:$ionicConfigProvider}:
  10641. *
  10642. * ```js
  10643. * $ionicConfigProvider.views.forwardCache(true);
  10644. * ```
  10645. *
  10646. * #### Disable cache globally
  10647. *
  10648. * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
  10649. * which can be cached, but this can also be use to disable all caching by setting it to 0.
  10650. *
  10651. * ```js
  10652. * $ionicConfigProvider.views.maxCache(0);
  10653. * ```
  10654. *
  10655. * #### Disable cache within state provider
  10656. *
  10657. * ```js
  10658. * $stateProvider.state('myState', {
  10659. * cache: false,
  10660. * url : '/myUrl',
  10661. * templateUrl : 'my-template.html'
  10662. * })
  10663. * ```
  10664. *
  10665. * #### Disable cache with an attribute
  10666. *
  10667. * ```html
  10668. * <ion-view cache-view="false" view-title="My Title!">
  10669. * ...
  10670. * </ion-view>
  10671. * ```
  10672. *
  10673. *
  10674. * ## AngularUI Router
  10675. *
  10676. * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
  10677. * more info. Below is a great video by the AngularUI Router team that may help to explain
  10678. * how it all works:
  10679. *
  10680. * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
  10681. * frameborder="0" allowfullscreen></iframe>
  10682. *
  10683. * Note: We do not recommend using [resolve](https://github.com/angular-ui/ui-router/wiki#resolve)
  10684. * of AngularUI Router. The recommended approach is to execute any logic needed before beginning the state transition.
  10685. *
  10686. * @param {string=} name A view name. The name should be unique amongst the other views in the
  10687. * same state. You can have views of the same name that live in different states. For more
  10688. * information, see ui-router's
  10689. * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
  10690. */
  10691. IonicModule
  10692. .directive('ionNavView', [
  10693. '$state',
  10694. '$ionicConfig',
  10695. function($state, $ionicConfig) {
  10696. // IONIC's fork of Angular UI Router, v0.2.10
  10697. // the navView handles registering views in the history and how to transition between them
  10698. return {
  10699. restrict: 'E',
  10700. terminal: true,
  10701. priority: 2000,
  10702. transclude: true,
  10703. controller: '$ionicNavView',
  10704. compile: function(tElement, tAttrs, transclude) {
  10705. // a nav view element is a container for numerous views
  10706. tElement.addClass('view-container');
  10707. ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
  10708. return function($scope, $element, $attr, navViewCtrl) {
  10709. var latestLocals;
  10710. // Put in the compiled initial view
  10711. transclude($scope, function(clone) {
  10712. $element.append(clone);
  10713. });
  10714. var viewData = navViewCtrl.init();
  10715. // listen for $stateChangeSuccess
  10716. $scope.$on('$stateChangeSuccess', function() {
  10717. updateView(false);
  10718. });
  10719. $scope.$on('$viewContentLoading', function() {
  10720. updateView(false);
  10721. });
  10722. // initial load, ready go
  10723. updateView(true);
  10724. function updateView(firstTime) {
  10725. // get the current local according to the $state
  10726. var viewLocals = $state.$current && $state.$current.locals[viewData.name];
  10727. // do not update THIS nav-view if its is not the container for the given state
  10728. // if the viewLocals are the same as THIS latestLocals, then nothing to do
  10729. if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
  10730. // update the latestLocals
  10731. latestLocals = viewLocals;
  10732. viewData.state = viewLocals.$$state;
  10733. // register, update and transition to the new view
  10734. navViewCtrl.register(viewLocals);
  10735. }
  10736. };
  10737. }
  10738. };
  10739. }]);
  10740. IonicModule
  10741. .config(['$provide', function($provide) {
  10742. $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
  10743. // drop the default ngClick directive
  10744. $delegate.shift();
  10745. return $delegate;
  10746. }]);
  10747. }])
  10748. /**
  10749. * @private
  10750. */
  10751. .factory('$ionicNgClick', ['$parse', function($parse) {
  10752. return function(scope, element, clickExpr) {
  10753. var clickHandler = angular.isFunction(clickExpr) ?
  10754. clickExpr :
  10755. $parse(clickExpr);
  10756. element.on('click', function(event) {
  10757. scope.$apply(function() {
  10758. clickHandler(scope, {$event: (event)});
  10759. });
  10760. });
  10761. // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
  10762. // something else nearby.
  10763. element.onclick = noop;
  10764. };
  10765. }])
  10766. .directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
  10767. return function(scope, element, attr) {
  10768. $ionicNgClick(scope, element, attr.ngClick);
  10769. };
  10770. }])
  10771. .directive('ionStopEvent', function() {
  10772. return {
  10773. restrict: 'A',
  10774. link: function(scope, element, attr) {
  10775. element.bind(attr.ionStopEvent, eventStopPropagation);
  10776. }
  10777. };
  10778. });
  10779. function eventStopPropagation(e) {
  10780. e.stopPropagation();
  10781. }
  10782. /**
  10783. * @ngdoc directive
  10784. * @name ionPane
  10785. * @module ionic
  10786. * @restrict E
  10787. *
  10788. * @description A simple container that fits content, with no side effects. Adds the 'pane' class to the element.
  10789. */
  10790. IonicModule
  10791. .directive('ionPane', function() {
  10792. return {
  10793. restrict: 'E',
  10794. link: function(scope, element) {
  10795. element.addClass('pane');
  10796. }
  10797. };
  10798. });
  10799. /*
  10800. * We don't document the ionPopover directive, we instead document
  10801. * the $ionicPopover service
  10802. */
  10803. IonicModule
  10804. .directive('ionPopover', [function() {
  10805. return {
  10806. restrict: 'E',
  10807. transclude: true,
  10808. replace: true,
  10809. controller: [function() {}],
  10810. template: '<div class="popover-backdrop">' +
  10811. '<div class="popover-wrapper" ng-transclude></div>' +
  10812. '</div>'
  10813. };
  10814. }]);
  10815. IonicModule
  10816. .directive('ionPopoverView', function() {
  10817. return {
  10818. restrict: 'E',
  10819. compile: function(element) {
  10820. element.append(jqLite('<div class="popover-arrow">'));
  10821. element.addClass('popover');
  10822. }
  10823. };
  10824. });
  10825. /**
  10826. * @ngdoc directive
  10827. * @name ionRadio
  10828. * @module ionic
  10829. * @restrict E
  10830. * @codepen saoBG
  10831. * @description
  10832. * The radio directive is no different than the HTML radio input, except it's styled differently.
  10833. *
  10834. * Radio behaves like [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
  10835. *
  10836. * @usage
  10837. * ```html
  10838. * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
  10839. * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
  10840. * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
  10841. * ```
  10842. *
  10843. * @param {string=} name The name of the radio input.
  10844. * @param {expression=} value The value of the radio input.
  10845. * @param {boolean=} disabled The state of the radio input.
  10846. * @param {string=} icon The icon to use when the radio input is selected.
  10847. * @param {expression=} ng-value Angular equivalent of the value attribute.
  10848. * @param {expression=} ng-model The angular model for the radio input.
  10849. * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
  10850. * @param {expression=} ng-change Triggers given expression when radio input's model changes
  10851. */
  10852. IonicModule
  10853. .directive('ionRadio', function() {
  10854. return {
  10855. restrict: 'E',
  10856. replace: true,
  10857. require: '?ngModel',
  10858. transclude: true,
  10859. template:
  10860. '<label class="item item-radio">' +
  10861. '<input type="radio" name="radio-group">' +
  10862. '<div class="radio-content">' +
  10863. '<div class="item-content disable-pointer-events" ng-transclude></div>' +
  10864. '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
  10865. '</div>' +
  10866. '</label>',
  10867. compile: function(element, attr) {
  10868. if (attr.icon) {
  10869. var iconElm = element.find('i');
  10870. iconElm.removeClass('ion-checkmark').addClass(attr.icon);
  10871. }
  10872. var input = element.find('input');
  10873. forEach({
  10874. 'name': attr.name,
  10875. 'value': attr.value,
  10876. 'disabled': attr.disabled,
  10877. 'ng-value': attr.ngValue,
  10878. 'ng-model': attr.ngModel,
  10879. 'ng-disabled': attr.ngDisabled,
  10880. 'ng-change': attr.ngChange,
  10881. 'ng-required': attr.ngRequired,
  10882. 'required': attr.required
  10883. }, function(value, name) {
  10884. if (isDefined(value)) {
  10885. input.attr(name, value);
  10886. }
  10887. });
  10888. return function(scope, element, attr) {
  10889. scope.getValue = function() {
  10890. return scope.ngValue || attr.value;
  10891. };
  10892. };
  10893. }
  10894. };
  10895. });
  10896. /**
  10897. * @ngdoc directive
  10898. * @name ionRefresher
  10899. * @module ionic
  10900. * @restrict E
  10901. * @parent ionic.directive:ionContent, ionic.directive:ionScroll
  10902. * @description
  10903. * Allows you to add pull-to-refresh to a scrollView.
  10904. *
  10905. * Place it as the first child of your {@link ionic.directive:ionContent} or
  10906. * {@link ionic.directive:ionScroll} element.
  10907. *
  10908. * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
  10909. * from your controller.
  10910. *
  10911. * @usage
  10912. *
  10913. * ```html
  10914. * <ion-content ng-controller="MyController">
  10915. * <ion-refresher
  10916. * pulling-text="Pull to refresh..."
  10917. * on-refresh="doRefresh()">
  10918. * </ion-refresher>
  10919. * <ion-list>
  10920. * <ion-item ng-repeat="item in items"></ion-item>
  10921. * </ion-list>
  10922. * </ion-content>
  10923. * ```
  10924. * ```js
  10925. * angular.module('testApp', ['ionic'])
  10926. * .controller('MyController', function($scope, $http) {
  10927. * $scope.items = [1,2,3];
  10928. * $scope.doRefresh = function() {
  10929. * $http.get('/new-items')
  10930. * .success(function(newItems) {
  10931. * $scope.items = newItems;
  10932. * })
  10933. * .finally(function() {
  10934. * // Stop the ion-refresher from spinning
  10935. * $scope.$broadcast('scroll.refreshComplete');
  10936. * });
  10937. * };
  10938. * });
  10939. * ```
  10940. *
  10941. * @param {expression=} on-refresh Called when the user pulls down enough and lets go
  10942. * of the refresher.
  10943. * @param {expression=} on-pulling Called when the user starts to pull down
  10944. * on the refresher.
  10945. * @param {string=} pulling-text The text to display while the user is pulling down.
  10946. * @param {string=} pulling-icon The icon to display while the user is pulling down.
  10947. * Default: 'ion-android-arrow-down'.
  10948. * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
  10949. * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner}
  10950. * is now the default, replacing rotating font icons. Set to `none` to disable both the
  10951. * spinner and the icon.
  10952. * @param {string=} refreshing-icon The font icon to display after user lets go of the
  10953. * refresher. This is deprecated in favor of the SVG {@link ionic.directive:ionSpinner}.
  10954. * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
  10955. * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
  10956. *
  10957. */
  10958. IonicModule
  10959. .directive('ionRefresher', [function() {
  10960. return {
  10961. restrict: 'E',
  10962. replace: true,
  10963. require: ['?^$ionicScroll', 'ionRefresher'],
  10964. controller: '$ionicRefresher',
  10965. template:
  10966. '<div class="scroll-refresher invisible" collection-repeat-ignore>' +
  10967. '<div class="ionic-refresher-content" ' +
  10968. 'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
  10969. '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
  10970. '<i class="icon {{pullingIcon}}"></i>' +
  10971. '</div>' +
  10972. '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
  10973. '<div class="icon-refreshing">' +
  10974. '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +
  10975. '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +
  10976. '</div>' +
  10977. '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
  10978. '</div>' +
  10979. '</div>',
  10980. link: function($scope, $element, $attrs, ctrls) {
  10981. // JS Scrolling uses the scroll controller
  10982. var scrollCtrl = ctrls[0],
  10983. refresherCtrl = ctrls[1];
  10984. if (!scrollCtrl || scrollCtrl.isNative()) {
  10985. // Kick off native scrolling
  10986. refresherCtrl.init();
  10987. } else {
  10988. $element[0].classList.add('js-scrolling');
  10989. scrollCtrl._setRefresher(
  10990. $scope,
  10991. $element[0],
  10992. refresherCtrl.getRefresherDomMethods()
  10993. );
  10994. $scope.$on('scroll.refreshComplete', function() {
  10995. $scope.$evalAsync(function() {
  10996. scrollCtrl.scrollView.finishPullToRefresh();
  10997. });
  10998. });
  10999. }
  11000. }
  11001. };
  11002. }]);
  11003. /**
  11004. * @ngdoc directive
  11005. * @name ionScroll
  11006. * @module ionic
  11007. * @delegate ionic.service:$ionicScrollDelegate
  11008. * @codepen mwFuh
  11009. * @restrict E
  11010. *
  11011. * @description
  11012. * Creates a scrollable container for all content inside.
  11013. *
  11014. * @usage
  11015. *
  11016. * Basic usage:
  11017. *
  11018. * ```html
  11019. * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
  11020. * <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
  11021. * </ion-scroll>
  11022. * ```
  11023. *
  11024. * Note that it's important to set the height of the scroll box as well as the height of the inner
  11025. * content to enable scrolling. This makes it possible to have full control over scrollable areas.
  11026. *
  11027. * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
  11028. *
  11029. * @param {string=} delegate-handle The handle used to identify this scrollView
  11030. * with {@link ionic.service:$ionicScrollDelegate}.
  11031. * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
  11032. * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
  11033. * @param {boolean=} paging Whether to scroll with paging.
  11034. * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
  11035. * @param {expression=} on-scroll Called whenever the user scrolls.
  11036. * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
  11037. * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
  11038. * @param {boolean=} zooming Whether to support pinch-to-zoom
  11039. * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
  11040. * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
  11041. * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
  11042. * of the content. Defaults to true on iOS, false on Android.
  11043. */
  11044. IonicModule
  11045. .directive('ionScroll', [
  11046. '$timeout',
  11047. '$controller',
  11048. '$ionicBind',
  11049. '$ionicConfig',
  11050. function($timeout, $controller, $ionicBind, $ionicConfig) {
  11051. return {
  11052. restrict: 'E',
  11053. scope: true,
  11054. controller: function() {},
  11055. compile: function(element, attr) {
  11056. element.addClass('scroll-view ionic-scroll');
  11057. //We cannot transclude here because it breaks element.data() inheritance on compile
  11058. var innerElement = jqLite('<div class="scroll"></div>');
  11059. innerElement.append(element.contents());
  11060. element.append(innerElement);
  11061. var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
  11062. return { pre: prelink };
  11063. function prelink($scope, $element, $attr) {
  11064. $ionicBind($scope, $attr, {
  11065. direction: '@',
  11066. paging: '@',
  11067. $onScroll: '&onScroll',
  11068. scroll: '@',
  11069. scrollbarX: '@',
  11070. scrollbarY: '@',
  11071. zooming: '@',
  11072. minZoom: '@',
  11073. maxZoom: '@'
  11074. });
  11075. $scope.direction = $scope.direction || 'y';
  11076. if (isDefined($attr.padding)) {
  11077. $scope.$watch($attr.padding, function(newVal) {
  11078. innerElement.toggleClass('padding', !!newVal);
  11079. });
  11080. }
  11081. if ($scope.$eval($scope.paging) === true) {
  11082. innerElement.addClass('scroll-paging');
  11083. }
  11084. if (!$scope.direction) { $scope.direction = 'y'; }
  11085. var isPaging = $scope.$eval($scope.paging) === true;
  11086. if (nativeScrolling) {
  11087. $element.addClass('overflow-scroll');
  11088. }
  11089. $element.addClass('scroll-' + $scope.direction);
  11090. var scrollViewOptions = {
  11091. el: $element[0],
  11092. delegateHandle: $attr.delegateHandle,
  11093. locking: ($attr.locking || 'true') === 'true',
  11094. bouncing: $scope.$eval($attr.hasBouncing),
  11095. paging: isPaging,
  11096. scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
  11097. scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
  11098. scrollingX: $scope.direction.indexOf('x') >= 0,
  11099. scrollingY: $scope.direction.indexOf('y') >= 0,
  11100. zooming: $scope.$eval($scope.zooming) === true,
  11101. maxZoom: $scope.$eval($scope.maxZoom) || 3,
  11102. minZoom: $scope.$eval($scope.minZoom) || 0.5,
  11103. preventDefault: true,
  11104. nativeScrolling: nativeScrolling
  11105. };
  11106. if (isPaging) {
  11107. scrollViewOptions.speedMultiplier = 0.8;
  11108. scrollViewOptions.bouncing = false;
  11109. }
  11110. $controller('$ionicScroll', {
  11111. $scope: $scope,
  11112. scrollViewOptions: scrollViewOptions
  11113. });
  11114. }
  11115. }
  11116. };
  11117. }]);
  11118. /**
  11119. * @ngdoc directive
  11120. * @name ionSideMenu
  11121. * @module ionic
  11122. * @restrict E
  11123. * @parent ionic.directive:ionSideMenus
  11124. *
  11125. * @description
  11126. * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
  11127. *
  11128. * @usage
  11129. * ```html
  11130. * <ion-side-menu
  11131. * side="left"
  11132. * width="myWidthValue + 20"
  11133. * is-enabled="shouldLeftSideMenuBeEnabled()">
  11134. * </ion-side-menu>
  11135. * ```
  11136. * For a complete side menu example, see the
  11137. * {@link ionic.directive:ionSideMenus} documentation.
  11138. *
  11139. * @param {string} side Which side the side menu is currently on. Allowed values: 'left' or 'right'.
  11140. * @param {boolean=} is-enabled Whether this side menu is enabled.
  11141. * @param {number=} width How many pixels wide the side menu should be. Defaults to 275.
  11142. */
  11143. IonicModule
  11144. .directive('ionSideMenu', function() {
  11145. return {
  11146. restrict: 'E',
  11147. require: '^ionSideMenus',
  11148. scope: true,
  11149. compile: function(element, attr) {
  11150. angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
  11151. angular.isUndefined(attr.width) && attr.$set('width', '275');
  11152. element.addClass('menu menu-' + attr.side);
  11153. return function($scope, $element, $attr, sideMenuCtrl) {
  11154. $scope.side = $attr.side || 'left';
  11155. var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
  11156. width: attr.width,
  11157. el: $element[0],
  11158. isEnabled: true
  11159. });
  11160. $scope.$watch($attr.width, function(val) {
  11161. var numberVal = +val;
  11162. if (numberVal && numberVal == val) {
  11163. sideMenu.setWidth(+val);
  11164. }
  11165. });
  11166. $scope.$watch($attr.isEnabled, function(val) {
  11167. sideMenu.setIsEnabled(!!val);
  11168. });
  11169. };
  11170. }
  11171. };
  11172. });
  11173. /**
  11174. * @ngdoc directive
  11175. * @name ionSideMenuContent
  11176. * @module ionic
  11177. * @restrict E
  11178. * @parent ionic.directive:ionSideMenus
  11179. *
  11180. * @description
  11181. * A container for the main visible content, sibling to one or more
  11182. * {@link ionic.directive:ionSideMenu} directives.
  11183. *
  11184. * @usage
  11185. * ```html
  11186. * <ion-side-menu-content
  11187. * edge-drag-threshold="true"
  11188. * drag-content="true">
  11189. * </ion-side-menu-content>
  11190. * ```
  11191. * For a complete side menu example, see the
  11192. * {@link ionic.directive:ionSideMenus} documentation.
  11193. *
  11194. * @param {boolean=} drag-content Whether the content can be dragged. Default true.
  11195. * @param {boolean|number=} edge-drag-threshold Whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Default false. Accepts three types of values:
  11196. * - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
  11197. * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
  11198. * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
  11199. *
  11200. */
  11201. IonicModule
  11202. .directive('ionSideMenuContent', [
  11203. '$timeout',
  11204. '$ionicGesture',
  11205. '$window',
  11206. function($timeout, $ionicGesture, $window) {
  11207. return {
  11208. restrict: 'EA', //DEPRECATED 'A'
  11209. require: '^ionSideMenus',
  11210. scope: true,
  11211. compile: function(element, attr) {
  11212. element.addClass('menu-content pane');
  11213. return { pre: prelink };
  11214. function prelink($scope, $element, $attr, sideMenuCtrl) {
  11215. var startCoord = null;
  11216. var primaryScrollAxis = null;
  11217. if (isDefined(attr.dragContent)) {
  11218. $scope.$watch(attr.dragContent, function(value) {
  11219. sideMenuCtrl.canDragContent(value);
  11220. });
  11221. } else {
  11222. sideMenuCtrl.canDragContent(true);
  11223. }
  11224. if (isDefined(attr.edgeDragThreshold)) {
  11225. $scope.$watch(attr.edgeDragThreshold, function(value) {
  11226. sideMenuCtrl.edgeDragThreshold(value);
  11227. });
  11228. }
  11229. // Listen for taps on the content to close the menu
  11230. function onContentTap(gestureEvt) {
  11231. if (sideMenuCtrl.getOpenAmount() !== 0) {
  11232. sideMenuCtrl.close();
  11233. gestureEvt.gesture.srcEvent.preventDefault();
  11234. startCoord = null;
  11235. primaryScrollAxis = null;
  11236. } else if (!startCoord) {
  11237. startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11238. }
  11239. }
  11240. function onDragX(e) {
  11241. if (!sideMenuCtrl.isDraggableTarget(e)) return;
  11242. if (getPrimaryScrollAxis(e) == 'x') {
  11243. sideMenuCtrl._handleDrag(e);
  11244. e.gesture.srcEvent.preventDefault();
  11245. }
  11246. }
  11247. function onDragY(e) {
  11248. if (getPrimaryScrollAxis(e) == 'x') {
  11249. e.gesture.srcEvent.preventDefault();
  11250. }
  11251. }
  11252. function onDragRelease(e) {
  11253. sideMenuCtrl._endDrag(e);
  11254. startCoord = null;
  11255. primaryScrollAxis = null;
  11256. }
  11257. function getPrimaryScrollAxis(gestureEvt) {
  11258. // gets whether the user is primarily scrolling on the X or Y
  11259. // If a majority of the drag has been on the Y since the start of
  11260. // the drag, but the X has moved a little bit, it's still a Y drag
  11261. if (primaryScrollAxis) {
  11262. // we already figured out which way they're scrolling
  11263. return primaryScrollAxis;
  11264. }
  11265. if (gestureEvt && gestureEvt.gesture) {
  11266. if (!startCoord) {
  11267. // get the starting point
  11268. startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11269. } else {
  11270. // we already have a starting point, figure out which direction they're going
  11271. var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
  11272. var xDistance = Math.abs(endCoord.x - startCoord.x);
  11273. var yDistance = Math.abs(endCoord.y - startCoord.y);
  11274. var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
  11275. if (Math.max(xDistance, yDistance) > 30) {
  11276. // ok, we pretty much know which way they're going
  11277. // let's lock it in
  11278. primaryScrollAxis = scrollAxis;
  11279. }
  11280. return scrollAxis;
  11281. }
  11282. }
  11283. return 'y';
  11284. }
  11285. var content = {
  11286. element: element[0],
  11287. onDrag: function() {},
  11288. endDrag: function() {},
  11289. setCanScroll: function(canScroll) {
  11290. var c = $element[0].querySelector('.scroll');
  11291. if (!c) {
  11292. return;
  11293. }
  11294. var content = angular.element(c.parentElement);
  11295. if (!content) {
  11296. return;
  11297. }
  11298. // freeze our scroll container if we have one
  11299. var scrollScope = content.scope();
  11300. scrollScope.scrollCtrl && scrollScope.scrollCtrl.freezeScrollShut(!canScroll);
  11301. },
  11302. getTranslateX: function() {
  11303. return $scope.sideMenuContentTranslateX || 0;
  11304. },
  11305. setTranslateX: ionic.animationFrameThrottle(function(amount) {
  11306. var xTransform = content.offsetX + amount;
  11307. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
  11308. $timeout(function() {
  11309. $scope.sideMenuContentTranslateX = amount;
  11310. });
  11311. }),
  11312. setMarginLeft: ionic.animationFrameThrottle(function(amount) {
  11313. if (amount) {
  11314. amount = parseInt(amount, 10);
  11315. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
  11316. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11317. content.offsetX = amount;
  11318. } else {
  11319. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11320. $element[0].style.width = '';
  11321. content.offsetX = 0;
  11322. }
  11323. }),
  11324. setMarginRight: ionic.animationFrameThrottle(function(amount) {
  11325. if (amount) {
  11326. amount = parseInt(amount, 10);
  11327. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11328. content.offsetX = amount;
  11329. } else {
  11330. $element[0].style.width = '';
  11331. content.offsetX = 0;
  11332. }
  11333. // reset incase left gets grabby
  11334. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11335. }),
  11336. setMarginLeftAndRight: ionic.animationFrameThrottle(function(amountLeft, amountRight) {
  11337. amountLeft = amountLeft && parseInt(amountLeft, 10) || 0;
  11338. amountRight = amountRight && parseInt(amountRight, 10) || 0;
  11339. var amount = amountLeft + amountRight;
  11340. if (amount > 0) {
  11341. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amountLeft + 'px,0,0)';
  11342. $element[0].style.width = ($window.innerWidth - amount) + 'px';
  11343. content.offsetX = amountLeft;
  11344. } else {
  11345. $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11346. $element[0].style.width = '';
  11347. content.offsetX = 0;
  11348. }
  11349. // reset incase left gets grabby
  11350. //$element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
  11351. }),
  11352. enableAnimation: function() {
  11353. $scope.animationEnabled = true;
  11354. $element[0].classList.add('menu-animated');
  11355. },
  11356. disableAnimation: function() {
  11357. $scope.animationEnabled = false;
  11358. $element[0].classList.remove('menu-animated');
  11359. },
  11360. offsetX: 0
  11361. };
  11362. sideMenuCtrl.setContent(content);
  11363. // add gesture handlers
  11364. var gestureOpts = { stop_browser_behavior: false };
  11365. gestureOpts.prevent_default_directions = ['left', 'right'];
  11366. var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
  11367. var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
  11368. var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
  11369. var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
  11370. var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
  11371. var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
  11372. // Cleanup
  11373. $scope.$on('$destroy', function() {
  11374. if (content) {
  11375. content.element = null;
  11376. content = null;
  11377. }
  11378. $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
  11379. $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
  11380. $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
  11381. $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
  11382. $ionicGesture.off(releaseGesture, 'release', onDragRelease);
  11383. $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
  11384. });
  11385. }
  11386. }
  11387. };
  11388. }]);
  11389. IonicModule
  11390. /**
  11391. * @ngdoc directive
  11392. * @name ionSideMenus
  11393. * @module ionic
  11394. * @delegate ionic.service:$ionicSideMenuDelegate
  11395. * @restrict E
  11396. *
  11397. * @description
  11398. * A container element for side menu(s) and the main content. Allows the left and/or right side menu
  11399. * to be toggled by dragging the main content area side to side.
  11400. *
  11401. * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
  11402. * directive. The `menu-close` attribute is usually added to links and buttons within
  11403. * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
  11404. * automatically close.
  11405. *
  11406. * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle}
  11407. * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close`
  11408. * directive. The side menu will automatically hide on child pages, but can be overridden with the
  11409. * enable-menu-with-back-views attribute mentioned below.
  11410. *
  11411. * By default, side menus are hidden underneath their side menu content and can be opened by swiping
  11412. * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
  11413. * {@link ionic.directive:exposeAsideWhen} attribute directive to an
  11414. * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
  11415. * "when" the menu should be exposed (always viewable).
  11416. *
  11417. * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif)
  11418. *
  11419. * For more information on side menus, check out:
  11420. *
  11421. * - {@link ionic.directive:ionSideMenuContent}
  11422. * - {@link ionic.directive:ionSideMenu}
  11423. * - {@link ionic.directive:menuToggle}
  11424. * - {@link ionic.directive:menuClose}
  11425. * - {@link ionic.directive:exposeAsideWhen}
  11426. *
  11427. * @usage
  11428. * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a
  11429. * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content,
  11430. * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place.
  11431. *
  11432. * ```html
  11433. * <ion-side-menus>
  11434. * <!-- Left menu -->
  11435. * <ion-side-menu side="left">
  11436. * </ion-side-menu>
  11437. *
  11438. * <ion-side-menu-content>
  11439. * <!-- Main content, usually <ion-nav-view> -->
  11440. * </ion-side-menu-content>
  11441. *
  11442. * <!-- Right menu -->
  11443. * <ion-side-menu side="right">
  11444. * </ion-side-menu>
  11445. *
  11446. * </ion-side-menus>
  11447. * ```
  11448. * ```js
  11449. * function ContentController($scope, $ionicSideMenuDelegate) {
  11450. * $scope.toggleLeft = function() {
  11451. * $ionicSideMenuDelegate.toggleLeft();
  11452. * };
  11453. * }
  11454. * ```
  11455. *
  11456. * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
  11457. * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
  11458. * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
  11459. * page without a back button visible), then any menuToggle buttons will show again, and menus will be
  11460. * enabled again.
  11461. * @param {string=} delegate-handle The handle used to identify this side menu
  11462. * with {@link ionic.service:$ionicSideMenuDelegate}.
  11463. *
  11464. */
  11465. .directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
  11466. return {
  11467. restrict: 'ECA',
  11468. controller: '$ionicSideMenus',
  11469. compile: function(element, attr) {
  11470. attr.$set('class', (attr['class'] || '') + ' view');
  11471. return { pre: prelink };
  11472. function prelink($scope, $element, $attrs, ctrl) {
  11473. ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
  11474. $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
  11475. if (!$scope.$exposeAside) $scope.$exposeAside = {};
  11476. $scope.$exposeAside.active = isAsideExposed;
  11477. $ionicBody.enableClass(isAsideExposed, 'aside-open');
  11478. });
  11479. $scope.$on('$ionicView.beforeEnter', function(ev, d) {
  11480. if (d.historyId) {
  11481. $scope.$activeHistoryId = d.historyId;
  11482. }
  11483. });
  11484. $scope.$on('$destroy', function() {
  11485. $ionicBody.removeClass('menu-open', 'aside-open');
  11486. });
  11487. }
  11488. }
  11489. };
  11490. }]);
  11491. /**
  11492. * @ngdoc directive
  11493. * @name ionSlideBox
  11494. * @module ionic
  11495. * @codepen AjgEB
  11496. * @deprecated will be removed in the next Ionic release in favor of the new ion-slides component.
  11497. * Don't depend on the internal behavior of this widget.
  11498. * @delegate ionic.service:$ionicSlideBoxDelegate
  11499. * @restrict E
  11500. * @description
  11501. * The Slide Box is a multi-page container where each page can be swiped or dragged between:
  11502. *
  11503. *
  11504. * @usage
  11505. * ```html
  11506. * <ion-slide-box on-slide-changed="slideHasChanged($index)">
  11507. * <ion-slide>
  11508. * <div class="box blue"><h1>BLUE</h1></div>
  11509. * </ion-slide>
  11510. * <ion-slide>
  11511. * <div class="box yellow"><h1>YELLOW</h1></div>
  11512. * </ion-slide>
  11513. * <ion-slide>
  11514. * <div class="box pink"><h1>PINK</h1></div>
  11515. * </ion-slide>
  11516. * </ion-slide-box>
  11517. * ```
  11518. *
  11519. * @param {string=} delegate-handle The handle used to identify this slideBox
  11520. * with {@link ionic.service:$ionicSlideBoxDelegate}.
  11521. * @param {boolean=} does-continue Whether the slide box should loop.
  11522. * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
  11523. * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
  11524. * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
  11525. * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
  11526. * @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable.
  11527. * @param {expression=} active-slide Model to bind the current slide index to.
  11528. */
  11529. IonicModule
  11530. .directive('ionSlideBox', [
  11531. '$animate',
  11532. '$timeout',
  11533. '$compile',
  11534. '$ionicSlideBoxDelegate',
  11535. '$ionicHistory',
  11536. '$ionicScrollDelegate',
  11537. function($animate, $timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
  11538. return {
  11539. restrict: 'E',
  11540. replace: true,
  11541. transclude: true,
  11542. scope: {
  11543. autoPlay: '=',
  11544. doesContinue: '@',
  11545. slideInterval: '@',
  11546. showPager: '@',
  11547. pagerClick: '&',
  11548. disableScroll: '@',
  11549. onSlideChanged: '&',
  11550. activeSlide: '=?',
  11551. bounce: '@'
  11552. },
  11553. controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
  11554. var _this = this;
  11555. var continuous = $scope.$eval($scope.doesContinue) === true;
  11556. var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true
  11557. var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
  11558. var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
  11559. var slider = new ionic.views.Slider({
  11560. el: $element[0],
  11561. auto: slideInterval,
  11562. continuous: continuous,
  11563. startSlide: $scope.activeSlide,
  11564. bouncing: bouncing,
  11565. slidesChanged: function() {
  11566. $scope.currentSlide = slider.currentIndex();
  11567. // Try to trigger a digest
  11568. $timeout(function() {});
  11569. },
  11570. callback: function(slideIndex) {
  11571. $scope.currentSlide = slideIndex;
  11572. $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
  11573. $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
  11574. $scope.activeSlide = slideIndex;
  11575. // Try to trigger a digest
  11576. $timeout(function() {});
  11577. },
  11578. onDrag: function() {
  11579. freezeAllScrolls(true);
  11580. },
  11581. onDragEnd: function() {
  11582. freezeAllScrolls(false);
  11583. }
  11584. });
  11585. function freezeAllScrolls(shouldFreeze) {
  11586. if (shouldFreeze && !_this.isScrollFreeze) {
  11587. $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
  11588. } else if (!shouldFreeze && _this.isScrollFreeze) {
  11589. $ionicScrollDelegate.freezeAllScrolls(false);
  11590. }
  11591. _this.isScrollFreeze = shouldFreeze;
  11592. }
  11593. slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
  11594. $scope.$watch('activeSlide', function(nv) {
  11595. if (isDefined(nv)) {
  11596. slider.slide(nv);
  11597. }
  11598. });
  11599. $scope.$on('slideBox.nextSlide', function() {
  11600. slider.next();
  11601. });
  11602. $scope.$on('slideBox.prevSlide', function() {
  11603. slider.prev();
  11604. });
  11605. $scope.$on('slideBox.setSlide', function(e, index) {
  11606. slider.slide(index);
  11607. });
  11608. //Exposed for testing
  11609. this.__slider = slider;
  11610. var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
  11611. slider, $attrs.delegateHandle, function() {
  11612. return $ionicHistory.isActiveScope($scope);
  11613. }
  11614. );
  11615. $scope.$on('$destroy', function() {
  11616. deregisterInstance();
  11617. slider.kill();
  11618. });
  11619. this.slidesCount = function() {
  11620. return slider.slidesCount();
  11621. };
  11622. this.onPagerClick = function(index) {
  11623. $scope.pagerClick({index: index});
  11624. };
  11625. $timeout(function() {
  11626. slider.load();
  11627. });
  11628. }],
  11629. template: '<div class="slider">' +
  11630. '<div class="slider-slides" ng-transclude>' +
  11631. '</div>' +
  11632. '</div>',
  11633. link: function($scope, $element, $attr) {
  11634. // Disable ngAnimate for slidebox and its children
  11635. $animate.enabled($element, false);
  11636. // if showPager is undefined, show the pager
  11637. if (!isDefined($attr.showPager)) {
  11638. $scope.showPager = true;
  11639. getPager().toggleClass('hide', !true);
  11640. }
  11641. $attr.$observe('showPager', function(show) {
  11642. if (show === undefined) return;
  11643. show = $scope.$eval(show);
  11644. getPager().toggleClass('hide', !show);
  11645. });
  11646. var pager;
  11647. function getPager() {
  11648. if (!pager) {
  11649. var childScope = $scope.$new();
  11650. pager = jqLite('<ion-pager></ion-pager>');
  11651. $element.append(pager);
  11652. pager = $compile(pager)(childScope);
  11653. }
  11654. return pager;
  11655. }
  11656. }
  11657. };
  11658. }])
  11659. .directive('ionSlide', function() {
  11660. return {
  11661. restrict: 'E',
  11662. require: '?^ionSlideBox',
  11663. compile: function(element) {
  11664. element.addClass('slider-slide');
  11665. }
  11666. };
  11667. })
  11668. .directive('ionPager', function() {
  11669. return {
  11670. restrict: 'E',
  11671. replace: true,
  11672. require: '^ionSlideBox',
  11673. template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)"><i class="icon ion-record"></i></span></div>',
  11674. link: function($scope, $element, $attr, slideBox) {
  11675. var selectPage = function(index) {
  11676. var children = $element[0].children;
  11677. var length = children.length;
  11678. for (var i = 0; i < length; i++) {
  11679. if (i == index) {
  11680. children[i].classList.add('active');
  11681. } else {
  11682. children[i].classList.remove('active');
  11683. }
  11684. }
  11685. };
  11686. $scope.pagerClick = function(index) {
  11687. slideBox.onPagerClick(index);
  11688. };
  11689. $scope.numSlides = function() {
  11690. return new Array(slideBox.slidesCount());
  11691. };
  11692. $scope.$watch('currentSlide', function(v) {
  11693. selectPage(v);
  11694. });
  11695. }
  11696. };
  11697. });
  11698. /**
  11699. * @ngdoc directive
  11700. * @name ionSlides
  11701. * @module ionic
  11702. * @delegate ionic.service:$ionicSlideBoxDelegate
  11703. * @restrict E
  11704. * @description
  11705. * The Slides component is a powerful multi-page container where each page can be swiped or dragged between.
  11706. *
  11707. * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from
  11708. * [idangerous](http://www.idangero.us/).
  11709. *
  11710. * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
  11711. *
  11712. * @usage
  11713. * ```html
  11714. * <ion-content scroll="false">
  11715. * <ion-slides options="options" slider="data.slider">
  11716. * <ion-slide-page>
  11717. * <div class="box blue"><h1>BLUE</h1></div>
  11718. * </ion-slide-page>
  11719. * <ion-slide-page>
  11720. * <div class="box yellow"><h1>YELLOW</h1></div>
  11721. * </ion-slide-page>
  11722. * <ion-slide-page>
  11723. * <div class="box pink"><h1>PINK</h1></div>
  11724. * </ion-slide-page>
  11725. * </ion-slides>
  11726. * </ion-content>
  11727. * ```
  11728. *
  11729. * ```js
  11730. * $scope.options = {
  11731. * loop: false,
  11732. * effect: 'fade',
  11733. * speed: 500,
  11734. * }
  11735. *
  11736. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11737. * // data.slider is the instance of Swiper
  11738. * $scope.slider = data.slider;
  11739. * });
  11740. *
  11741. * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){
  11742. * console.log('Slide change is beginning');
  11743. * });
  11744. *
  11745. * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){
  11746. * // note: the indexes are 0-based
  11747. * $scope.activeIndex = data.activeIndex;
  11748. * $scope.previousIndex = data.previousIndex;
  11749. * });
  11750. *
  11751. * ```
  11752. *
  11753. * ## Slide Events
  11754. *
  11755. * The slides component dispatches events when the active slide changes
  11756. *
  11757. * <table class="table">
  11758. * <tr>
  11759. * <td><code>$ionicSlides.slideChangeStart</code></td>
  11760. * <td>This event is emitted when a slide change begins</td>
  11761. * </tr>
  11762. * <tr>
  11763. * <td><code>$ionicSlides.slideChangeEnd</code></td>
  11764. * <td>This event is emitted when a slide change completes</td>
  11765. * </tr>
  11766. * <tr>
  11767. * <td><code>$ionicSlides.sliderInitialized</code></td>
  11768. * <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td>
  11769. * </tr>
  11770. * </table>
  11771. *
  11772. *
  11773. * ## Updating Slides Dynamically
  11774. * When applying data to the slider at runtime, typically everything will work as expected.
  11775. *
  11776. * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly.
  11777. *
  11778. * ```
  11779. * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
  11780. * // grab an instance of the slider
  11781. * $scope.slider = data.slider;
  11782. * });
  11783. *
  11784. * function dataChangeHandler(){
  11785. * // call this function when data changes, such as an HTTP request, etc
  11786. * if ( $scope.slider ){
  11787. * $scope.slider.updateLoop();
  11788. * }
  11789. * }
  11790. * ```
  11791. *
  11792. */
  11793. IonicModule
  11794. .directive('ionSlides', [
  11795. '$animate',
  11796. '$timeout',
  11797. '$compile',
  11798. function($animate, $timeout, $compile) {
  11799. return {
  11800. restrict: 'E',
  11801. transclude: true,
  11802. scope: {
  11803. options: '=',
  11804. slider: '='
  11805. },
  11806. template: '<div class="swiper-container">' +
  11807. '<div class="swiper-wrapper" ng-transclude>' +
  11808. '</div>' +
  11809. '<div ng-hide="!showPager" class="swiper-pagination"></div>' +
  11810. '</div>',
  11811. controller: ['$scope', '$element', function($scope, $element) {
  11812. var _this = this;
  11813. this.update = function() {
  11814. $timeout(function() {
  11815. if (!_this.__slider) {
  11816. return;
  11817. }
  11818. _this.__slider.update();
  11819. if (_this._options.loop) {
  11820. _this.__slider.createLoop();
  11821. }
  11822. var slidesLength = _this.__slider.slides.length;
  11823. // Don't allow pager to show with > 10 slides
  11824. if (slidesLength > 10) {
  11825. $scope.showPager = false;
  11826. }
  11827. // When slide index is greater than total then slide to last index
  11828. if (_this.__slider.activeIndex > slidesLength - 1) {
  11829. _this.__slider.slideTo(slidesLength - 1);
  11830. }
  11831. });
  11832. };
  11833. this.rapidUpdate = ionic.debounce(function() {
  11834. _this.update();
  11835. }, 50);
  11836. this.getSlider = function() {
  11837. return _this.__slider;
  11838. };
  11839. var options = $scope.options || {};
  11840. var newOptions = angular.extend({
  11841. pagination: $element.children().children()[1],
  11842. paginationClickable: true,
  11843. lazyLoading: true,
  11844. preloadImages: false
  11845. }, options);
  11846. this._options = newOptions;
  11847. $timeout(function() {
  11848. var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);
  11849. $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });
  11850. _this.__slider = slider;
  11851. $scope.slider = _this.__slider;
  11852. $scope.$on('$destroy', function() {
  11853. slider.destroy();
  11854. _this.__slider = null;
  11855. });
  11856. });
  11857. $timeout(function() {
  11858. // if it's a loop, render the slides again just incase
  11859. _this.rapidUpdate();
  11860. }, 200);
  11861. }],
  11862. link: function($scope) {
  11863. $scope.showPager = true;
  11864. // Disable ngAnimate for slidebox and its children
  11865. //$animate.enabled(false, $element);
  11866. }
  11867. };
  11868. }])
  11869. .directive('ionSlidePage', [function() {
  11870. return {
  11871. restrict: 'E',
  11872. require: '?^ionSlides',
  11873. transclude: true,
  11874. replace: true,
  11875. template: '<div class="swiper-slide" ng-transclude></div>',
  11876. link: function($scope, $element, $attr, ionSlidesCtrl) {
  11877. ionSlidesCtrl.rapidUpdate();
  11878. $scope.$on('$destroy', function() {
  11879. ionSlidesCtrl.rapidUpdate();
  11880. });
  11881. }
  11882. };
  11883. }]);
  11884. /**
  11885. * @ngdoc directive
  11886. * @name ionSpinner
  11887. * @module ionic
  11888. * @restrict E
  11889. *
  11890. * @description
  11891. * The `ionSpinner` directive provides a variety of animated spinners.
  11892. * Spinners enables you to give your users feedback that the app is
  11893. * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
  11894. * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
  11895. * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
  11896. * While font icons are great for simple or stationary graphics, they're not suited to
  11897. * provide great animations, which is why Ionic uses SVG instead.
  11898. *
  11899. * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
  11900. * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
  11901. * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
  11902. * within 3KB.
  11903. *
  11904. * <style>
  11905. * .spinner-table {
  11906. * max-width: 280px;
  11907. * }
  11908. * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
  11909. * vertical-align: middle;
  11910. * width: 42px;
  11911. * height: 42px;
  11912. * }
  11913. * .spinner {
  11914. * stroke: #444;
  11915. * fill: #444; }
  11916. * .spinner svg {
  11917. * width: 28px;
  11918. * height: 28px; }
  11919. * .spinner.spinner-inverse {
  11920. * stroke: #fff;
  11921. * fill: #fff; }
  11922. *
  11923. * .spinner-android {
  11924. * stroke: #4b8bf4; }
  11925. *
  11926. * .spinner-ios, .spinner-ios-small {
  11927. * stroke: #69717d; }
  11928. *
  11929. * .spinner-spiral .stop1 {
  11930. * stop-color: #fff;
  11931. * stop-opacity: 0; }
  11932. * .spinner-spiral.spinner-inverse .stop1 {
  11933. * stop-color: #000; }
  11934. * .spinner-spiral.spinner-inverse .stop2 {
  11935. * stop-color: #fff; }
  11936. * </style>
  11937. *
  11938. * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
  11939. * <table class="table spinner-table" ng-app="ionic">
  11940. * <tr>
  11941. * <th>
  11942. * <code>android</code>
  11943. * </th>
  11944. * <td>
  11945. * <ion-spinner icon="android"></ion-spinner>
  11946. * </td>
  11947. * </tr>
  11948. * <tr>
  11949. * <th>
  11950. * <code>ios</code>
  11951. * </th>
  11952. * <td>
  11953. * <ion-spinner icon="ios"></ion-spinner>
  11954. * </td>
  11955. * </tr>
  11956. * <tr>
  11957. * <th>
  11958. * <code>ios-small</code>
  11959. * </th>
  11960. * <td>
  11961. * <ion-spinner icon="ios-small"></ion-spinner>
  11962. * </td>
  11963. * </tr>
  11964. * <tr>
  11965. * <th>
  11966. * <code>bubbles</code>
  11967. * </th>
  11968. * <td>
  11969. * <ion-spinner icon="bubbles"></ion-spinner>
  11970. * </td>
  11971. * </tr>
  11972. * <tr>
  11973. * <th>
  11974. * <code>circles</code>
  11975. * </th>
  11976. * <td>
  11977. * <ion-spinner icon="circles"></ion-spinner>
  11978. * </td>
  11979. * </tr>
  11980. * <tr>
  11981. * <th>
  11982. * <code>crescent</code>
  11983. * </th>
  11984. * <td>
  11985. * <ion-spinner icon="crescent"></ion-spinner>
  11986. * </td>
  11987. * </tr>
  11988. * <tr>
  11989. * <th>
  11990. * <code>dots</code>
  11991. * </th>
  11992. * <td>
  11993. * <ion-spinner icon="dots"></ion-spinner>
  11994. * </td>
  11995. * </tr>
  11996. * <tr>
  11997. * <th>
  11998. * <code>lines</code>
  11999. * </th>
  12000. * <td>
  12001. * <ion-spinner icon="lines"></ion-spinner>
  12002. * </td>
  12003. * </tr>
  12004. * <tr>
  12005. * <th>
  12006. * <code>ripple</code>
  12007. * </th>
  12008. * <td>
  12009. * <ion-spinner icon="ripple"></ion-spinner>
  12010. * </td>
  12011. * </tr>
  12012. * <tr>
  12013. * <th>
  12014. * <code>spiral</code>
  12015. * </th>
  12016. * <td>
  12017. * <ion-spinner icon="spiral"></ion-spinner>
  12018. * </td>
  12019. * </tr>
  12020. * </table>
  12021. *
  12022. * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
  12023. * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
  12024. * and scaled to any size.
  12025. *
  12026. *
  12027. * @usage
  12028. * The following code would use the default spinner for the platform it's running from. If it's neither
  12029. * iOS or Android, it'll default to use `ios`.
  12030. *
  12031. * ```html
  12032. * <ion-spinner></ion-spinner>
  12033. * ```
  12034. *
  12035. * By setting the `icon` attribute, you can specify which spinner to use, no matter what
  12036. * the platform is.
  12037. *
  12038. * ```html
  12039. * <ion-spinner icon="spiral"></ion-spinner>
  12040. * ```
  12041. *
  12042. * ## Spinner Colors
  12043. * Like with most of Ionic's other components, spinners can also be styled using
  12044. * Ionic's standard color naming convention. For example:
  12045. *
  12046. * ```html
  12047. * <ion-spinner class="spinner-energized"></ion-spinner>
  12048. * ```
  12049. *
  12050. *
  12051. * ## Styling SVG with CSS
  12052. * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
  12053. * have different names, for example, SVG uses the term `stroke` instead of `border`, and
  12054. * `fill` instead of `background-color`.
  12055. *
  12056. * ```css
  12057. * .spinner svg {
  12058. * width: 28px;
  12059. * height: 28px;
  12060. * stroke: #444;
  12061. * fill: #444;
  12062. * }
  12063. * ```
  12064. *
  12065. */
  12066. IonicModule
  12067. .directive('ionSpinner', function() {
  12068. return {
  12069. restrict: 'E',
  12070. controller: '$ionicSpinner',
  12071. link: function($scope, $element, $attrs, ctrl) {
  12072. var spinnerName = ctrl.init();
  12073. $element.addClass('spinner spinner-' + spinnerName);
  12074. $element.on('$destroy', function onDestroy() {
  12075. ctrl.stop();
  12076. });
  12077. }
  12078. };
  12079. });
  12080. /**
  12081. * @ngdoc directive
  12082. * @name ionTab
  12083. * @module ionic
  12084. * @restrict E
  12085. * @parent ionic.directive:ionTabs
  12086. *
  12087. * @description
  12088. * Contains a tab's content. The content only exists while the given tab is selected.
  12089. *
  12090. * Each ionTab has its own view history.
  12091. *
  12092. * @usage
  12093. * ```html
  12094. * <ion-tab
  12095. * title="Tab!"
  12096. * icon="my-icon"
  12097. * href="#/tab/tab-link"
  12098. * on-select="onTabSelected()"
  12099. * on-deselect="onTabDeselected()">
  12100. * </ion-tab>
  12101. * ```
  12102. * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
  12103. *
  12104. * @param {string} title The title of the tab.
  12105. * @param {string=} href The link that this tab will navigate to when tapped.
  12106. * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
  12107. * @param {string=} icon-on The icon of the tab while it is selected.
  12108. * @param {string=} icon-off The icon of the tab while it is not selected.
  12109. * @param {expression=} badge The badge to put on this tab (usually a number).
  12110. * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
  12111. * @param {expression=} on-select Called when this tab is selected.
  12112. * @param {expression=} on-deselect Called when this tab is deselected.
  12113. * @param {expression=} ng-click By default, the tab will be selected on click. If ngClick is set, it will not. You can explicitly switch tabs using {@link ionic.service:$ionicTabsDelegate#select $ionicTabsDelegate.select()}.
  12114. * @param {expression=} hidden Whether the tab is to be hidden or not.
  12115. * @param {expression=} disabled Whether the tab is to be disabled or not.
  12116. */
  12117. IonicModule
  12118. .directive('ionTab', [
  12119. '$compile',
  12120. '$ionicConfig',
  12121. '$ionicBind',
  12122. '$ionicViewSwitcher',
  12123. function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
  12124. //Returns ' key="value"' if value exists
  12125. function attrStr(k, v) {
  12126. return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
  12127. }
  12128. return {
  12129. restrict: 'E',
  12130. require: ['^ionTabs', 'ionTab'],
  12131. controller: '$ionicTab',
  12132. scope: true,
  12133. compile: function(element, attr) {
  12134. //We create the tabNavTemplate in the compile phase so that the
  12135. //attributes we pass down won't be interpolated yet - we want
  12136. //to pass down the 'raw' versions of the attributes
  12137. var tabNavTemplate = '<ion-tab-nav' +
  12138. attrStr('ng-click', attr.ngClick) +
  12139. attrStr('title', attr.title) +
  12140. attrStr('icon', attr.icon) +
  12141. attrStr('icon-on', attr.iconOn) +
  12142. attrStr('icon-off', attr.iconOff) +
  12143. attrStr('badge', attr.badge) +
  12144. attrStr('badge-style', attr.badgeStyle) +
  12145. attrStr('hidden', attr.hidden) +
  12146. attrStr('disabled', attr.disabled) +
  12147. attrStr('class', attr['class']) +
  12148. '></ion-tab-nav>';
  12149. //Remove the contents of the element so we can compile them later, if tab is selected
  12150. var tabContentEle = document.createElement('div');
  12151. for (var x = 0; x < element[0].children.length; x++) {
  12152. tabContentEle.appendChild(element[0].children[x].cloneNode(true));
  12153. }
  12154. var childElementCount = tabContentEle.childElementCount;
  12155. element.empty();
  12156. var navViewName, isNavView;
  12157. if (childElementCount) {
  12158. if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
  12159. // get the name if it's a nav-view
  12160. navViewName = tabContentEle.children[0].getAttribute('name');
  12161. tabContentEle.children[0].classList.add('view-container');
  12162. isNavView = true;
  12163. }
  12164. if (childElementCount === 1) {
  12165. // make the 1 child element the primary tab content container
  12166. tabContentEle = tabContentEle.children[0];
  12167. }
  12168. if (!isNavView) tabContentEle.classList.add('pane');
  12169. tabContentEle.classList.add('tab-content');
  12170. }
  12171. return function link($scope, $element, $attr, ctrls) {
  12172. var childScope;
  12173. var childElement;
  12174. var tabsCtrl = ctrls[0];
  12175. var tabCtrl = ctrls[1];
  12176. var isTabContentAttached = false;
  12177. $scope.$tabSelected = false;
  12178. $ionicBind($scope, $attr, {
  12179. onSelect: '&',
  12180. onDeselect: '&',
  12181. title: '@',
  12182. uiSref: '@',
  12183. href: '@'
  12184. });
  12185. tabsCtrl.add($scope);
  12186. $scope.$on('$destroy', function() {
  12187. if (!$scope.$tabsDestroy) {
  12188. // if the containing ionTabs directive is being destroyed
  12189. // then don't bother going through the controllers remove
  12190. // method, since remove will reset the active tab as each tab
  12191. // is being destroyed, causing unnecessary view loads and transitions
  12192. tabsCtrl.remove($scope);
  12193. }
  12194. tabNavElement.isolateScope().$destroy();
  12195. tabNavElement.remove();
  12196. tabNavElement = tabContentEle = childElement = null;
  12197. });
  12198. //Remove title attribute so browser-tooltip does not apear
  12199. $element[0].removeAttribute('title');
  12200. if (navViewName) {
  12201. tabCtrl.navViewName = $scope.navViewName = navViewName;
  12202. }
  12203. $scope.$on('$stateChangeSuccess', selectIfMatchesState);
  12204. selectIfMatchesState();
  12205. function selectIfMatchesState() {
  12206. if (tabCtrl.tabMatchesState()) {
  12207. tabsCtrl.select($scope, false);
  12208. }
  12209. }
  12210. var tabNavElement = jqLite(tabNavTemplate);
  12211. tabNavElement.data('$ionTabsController', tabsCtrl);
  12212. tabNavElement.data('$ionTabController', tabCtrl);
  12213. tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
  12214. function tabSelected(isSelected) {
  12215. if (isSelected && childElementCount) {
  12216. // this tab is being selected
  12217. // check if the tab is already in the DOM
  12218. // only do this if the tab has child elements
  12219. if (!isTabContentAttached) {
  12220. // tab should be selected and is NOT in the DOM
  12221. // create a new scope and append it
  12222. childScope = $scope.$new();
  12223. childElement = jqLite(tabContentEle);
  12224. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12225. tabsCtrl.$element.append(childElement);
  12226. $compile(childElement)(childScope);
  12227. isTabContentAttached = true;
  12228. }
  12229. // remove the hide class so the tabs content shows up
  12230. $ionicViewSwitcher.viewEleIsActive(childElement, true);
  12231. } else if (isTabContentAttached && childElement) {
  12232. // this tab should NOT be selected, and it is already in the DOM
  12233. if ($ionicConfig.views.maxCache() > 0) {
  12234. // keep the tabs in the DOM, only css hide it
  12235. $ionicViewSwitcher.viewEleIsActive(childElement, false);
  12236. } else {
  12237. // do not keep tabs in the DOM
  12238. destroyTab();
  12239. }
  12240. }
  12241. }
  12242. function destroyTab() {
  12243. childScope && childScope.$destroy();
  12244. isTabContentAttached && childElement && childElement.remove();
  12245. tabContentEle.innerHTML = '';
  12246. isTabContentAttached = childScope = childElement = null;
  12247. }
  12248. $scope.$watch('$tabSelected', tabSelected);
  12249. $scope.$on('$ionicView.afterEnter', function() {
  12250. $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
  12251. });
  12252. $scope.$on('$ionicView.clearCache', function() {
  12253. if (!$scope.$tabSelected) {
  12254. destroyTab();
  12255. }
  12256. });
  12257. };
  12258. }
  12259. };
  12260. }]);
  12261. IonicModule
  12262. .directive('ionTabNav', [function() {
  12263. return {
  12264. restrict: 'E',
  12265. replace: true,
  12266. require: ['^ionTabs', '^ionTab'],
  12267. template:
  12268. '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +
  12269. ' ng-disabled="disabled()" class="tab-item">' +
  12270. '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
  12271. '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +
  12272. '<span class="tab-title" ng-bind-html="title"></span>' +
  12273. '</a>',
  12274. scope: {
  12275. title: '@',
  12276. icon: '@',
  12277. iconOn: '@',
  12278. iconOff: '@',
  12279. badge: '=',
  12280. hidden: '@',
  12281. disabled: '&',
  12282. badgeStyle: '@',
  12283. 'class': '@'
  12284. },
  12285. link: function($scope, $element, $attrs, ctrls) {
  12286. var tabsCtrl = ctrls[0],
  12287. tabCtrl = ctrls[1];
  12288. //Remove title attribute so browser-tooltip does not apear
  12289. $element[0].removeAttribute('title');
  12290. $scope.selectTab = function(e) {
  12291. e.preventDefault();
  12292. tabsCtrl.select(tabCtrl.$scope, true);
  12293. };
  12294. if (!$attrs.ngClick) {
  12295. $element.on('click', function(event) {
  12296. $scope.$apply(function() {
  12297. $scope.selectTab(event);
  12298. });
  12299. });
  12300. }
  12301. $scope.isHidden = function() {
  12302. if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
  12303. return false;
  12304. };
  12305. $scope.getIconOn = function() {
  12306. return $scope.iconOn || $scope.icon;
  12307. };
  12308. $scope.getIconOff = function() {
  12309. return $scope.iconOff || $scope.icon;
  12310. };
  12311. $scope.isTabActive = function() {
  12312. return tabsCtrl.selectedTab() === tabCtrl.$scope;
  12313. };
  12314. $scope.getIcon = function() {
  12315. if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {
  12316. // active
  12317. return $scope.iconOn || $scope.icon;
  12318. }
  12319. else {
  12320. // inactive
  12321. return $scope.iconOff || $scope.icon;
  12322. }
  12323. };
  12324. }
  12325. };
  12326. }]);
  12327. /**
  12328. * @ngdoc directive
  12329. * @name ionTabs
  12330. * @module ionic
  12331. * @delegate ionic.service:$ionicTabsDelegate
  12332. * @restrict E
  12333. * @codepen odqCz
  12334. *
  12335. * @description
  12336. * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
  12337. * through.
  12338. *
  12339. * Assign any [tabs class](/docs/components#tabs) to the element to define
  12340. * its look and feel.
  12341. *
  12342. * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
  12343. * of the screen, below the nav-bar. This follows each OS's design specification, but can be
  12344. * configured with the {@link ionic.provider:$ionicConfigProvider}.
  12345. *
  12346. * See the {@link ionic.directive:ionTab} directive's documentation for more details on
  12347. * individual tabs.
  12348. *
  12349. * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
  12350. * certain CSS bug.
  12351. *
  12352. * @usage
  12353. * ```html
  12354. * <ion-tabs class="tabs-positive tabs-icon-top">
  12355. *
  12356. * <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
  12357. * <!-- Tab 1 content -->
  12358. * </ion-tab>
  12359. *
  12360. * <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
  12361. * <!-- Tab 2 content -->
  12362. * </ion-tab>
  12363. *
  12364. * <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
  12365. * <!-- Tab 3 content -->
  12366. * </ion-tab>
  12367. *
  12368. * </ion-tabs>
  12369. * ```
  12370. *
  12371. * @param {string=} delegate-handle The handle used to identify these tabs
  12372. * with {@link ionic.service:$ionicTabsDelegate}.
  12373. */
  12374. IonicModule
  12375. .directive('ionTabs', [
  12376. '$ionicTabsDelegate',
  12377. '$ionicConfig',
  12378. function($ionicTabsDelegate, $ionicConfig) {
  12379. return {
  12380. restrict: 'E',
  12381. scope: true,
  12382. controller: '$ionicTabs',
  12383. compile: function(tElement) {
  12384. //We cannot use regular transclude here because it breaks element.data()
  12385. //inheritance on compile
  12386. var innerElement = jqLite('<div class="tab-nav tabs">');
  12387. innerElement.append(tElement.contents());
  12388. tElement.append(innerElement)
  12389. .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
  12390. return { pre: prelink, post: postLink };
  12391. function prelink($scope, $element, $attr, tabsCtrl) {
  12392. var deregisterInstance = $ionicTabsDelegate._registerInstance(
  12393. tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
  12394. );
  12395. tabsCtrl.$scope = $scope;
  12396. tabsCtrl.$element = $element;
  12397. tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
  12398. $scope.$watch(function() { return $element[0].className; }, function(value) {
  12399. var isTabsTop = value.indexOf('tabs-top') !== -1;
  12400. var isHidden = value.indexOf('tabs-item-hide') !== -1;
  12401. $scope.$hasTabs = !isTabsTop && !isHidden;
  12402. $scope.$hasTabsTop = isTabsTop && !isHidden;
  12403. $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
  12404. });
  12405. function emitLifecycleEvent(ev, data) {
  12406. ev.stopPropagation();
  12407. var previousSelectedTab = tabsCtrl.previousSelectedTab();
  12408. if (previousSelectedTab) {
  12409. previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
  12410. }
  12411. }
  12412. $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
  12413. $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
  12414. $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
  12415. $scope.$on('$destroy', function() {
  12416. // variable to inform child tabs that they're all being blown away
  12417. // used so that while destorying an individual tab, each one
  12418. // doesn't select the next tab as the active one, which causes unnecessary
  12419. // loading of tab views when each will eventually all go away anyway
  12420. $scope.$tabsDestroy = true;
  12421. deregisterInstance();
  12422. tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
  12423. delete $scope.$hasTabs;
  12424. delete $scope.$hasTabsTop;
  12425. });
  12426. }
  12427. function postLink($scope, $element, $attr, tabsCtrl) {
  12428. if (!tabsCtrl.selectedTab()) {
  12429. // all the tabs have been added
  12430. // but one hasn't been selected yet
  12431. tabsCtrl.select(0);
  12432. }
  12433. }
  12434. }
  12435. };
  12436. }]);
  12437. /**
  12438. * @ngdoc directive
  12439. * @name ionTitle
  12440. * @module ionic
  12441. * @restrict E
  12442. *
  12443. * Used for titles in header and nav bars. New in 1.2
  12444. *
  12445. * Identical to <div class="title"> but with future compatibility for Ionic 2
  12446. *
  12447. * @usage
  12448. *
  12449. * ```html
  12450. * <ion-nav-bar>
  12451. * <ion-title>Hello</ion-title>
  12452. * <ion-nav-bar>
  12453. * ```
  12454. */
  12455. IonicModule
  12456. .directive('ionTitle', [function() {
  12457. return {
  12458. restrict: 'E',
  12459. compile: function(element) {
  12460. element.addClass('title');
  12461. }
  12462. };
  12463. }]);
  12464. /**
  12465. * @ngdoc directive
  12466. * @name ionToggle
  12467. * @module ionic
  12468. * @codepen tfAzj
  12469. * @restrict E
  12470. *
  12471. * @description
  12472. * A toggle is an animated switch which binds a given model to a boolean.
  12473. *
  12474. * Allows dragging of the switch's nub.
  12475. *
  12476. * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
  12477. *
  12478. * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
  12479. *
  12480. * @usage
  12481. * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
  12482. * and has the `toggle-calm` CSS class assigned to the inner element.
  12483. *
  12484. * ```html
  12485. * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
  12486. * ```
  12487. */
  12488. IonicModule
  12489. .directive('ionToggle', [
  12490. '$timeout',
  12491. '$ionicConfig',
  12492. function($timeout, $ionicConfig) {
  12493. return {
  12494. restrict: 'E',
  12495. replace: true,
  12496. require: '?ngModel',
  12497. transclude: true,
  12498. template:
  12499. '<div class="item item-toggle">' +
  12500. '<div ng-transclude></div>' +
  12501. '<label class="toggle">' +
  12502. '<input type="checkbox">' +
  12503. '<div class="track">' +
  12504. '<div class="handle"></div>' +
  12505. '</div>' +
  12506. '</label>' +
  12507. '</div>',
  12508. compile: function(element, attr) {
  12509. var input = element.find('input');
  12510. forEach({
  12511. 'name': attr.name,
  12512. 'ng-value': attr.ngValue,
  12513. 'ng-model': attr.ngModel,
  12514. 'ng-checked': attr.ngChecked,
  12515. 'ng-disabled': attr.ngDisabled,
  12516. 'ng-true-value': attr.ngTrueValue,
  12517. 'ng-false-value': attr.ngFalseValue,
  12518. 'ng-change': attr.ngChange,
  12519. 'ng-required': attr.ngRequired,
  12520. 'required': attr.required
  12521. }, function(value, name) {
  12522. if (isDefined(value)) {
  12523. input.attr(name, value);
  12524. }
  12525. });
  12526. if (attr.toggleClass) {
  12527. element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
  12528. }
  12529. element.addClass('toggle-' + $ionicConfig.form.toggle());
  12530. return function($scope, $element) {
  12531. var el = $element[0].getElementsByTagName('label')[0];
  12532. var checkbox = el.children[0];
  12533. var track = el.children[1];
  12534. var handle = track.children[0];
  12535. var ngModelController = jqLite(checkbox).controller('ngModel');
  12536. $scope.toggle = new ionic.views.Toggle({
  12537. el: el,
  12538. track: track,
  12539. checkbox: checkbox,
  12540. handle: handle,
  12541. onChange: function() {
  12542. if (ngModelController) {
  12543. ngModelController.$setViewValue(checkbox.checked);
  12544. $scope.$apply();
  12545. }
  12546. }
  12547. });
  12548. $scope.$on('$destroy', function() {
  12549. $scope.toggle.destroy();
  12550. });
  12551. };
  12552. }
  12553. };
  12554. }]);
  12555. /**
  12556. * @ngdoc directive
  12557. * @name ionView
  12558. * @module ionic
  12559. * @restrict E
  12560. * @parent ionNavView
  12561. *
  12562. * @description
  12563. * A container for view content and any navigational and header bar information. When a view
  12564. * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
  12565. * information, such as its title, whether the back button should be displayed or not, whether the
  12566. * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
  12567. * should use to animate, and which direction to animate.
  12568. *
  12569. * *Views are cached to improve performance.* When a view is navigated away from, its element is
  12570. * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
  12571. * view that is already cached, its scope is reconnected, and the existing element, which was
  12572. * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
  12573. * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
  12574. * as an attribute on the view itself (see below).
  12575. *
  12576. * @usage
  12577. * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
  12578. * "My Page" as the title.
  12579. *
  12580. * ```html
  12581. * <ion-nav-bar></ion-nav-bar>
  12582. * <ion-nav-view>
  12583. * <ion-view view-title="My Page">
  12584. * <ion-content>
  12585. * Hello!
  12586. * </ion-content>
  12587. * </ion-view>
  12588. * </ion-nav-view>
  12589. * ```
  12590. *
  12591. * ## View LifeCycle and Events
  12592. *
  12593. * Views can be cached, which means ***controllers normally only load once***, which may
  12594. * affect your controller logic. To know when a view has entered or left, events
  12595. * have been added that are emitted from the view's scope. These events also
  12596. * contain data about the view, such as the title and whether the back button should
  12597. * show. Also contained is transition data, such as the transition type and
  12598. * direction that will be or was used.
  12599. *
  12600. * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
  12601. * desirable for a child/nested view to be notified of the event.
  12602. * For this use case, `$ionicParentView` life cycle events are broadcast downwards.
  12603. *
  12604. * <table class="table">
  12605. * <tr>
  12606. * <td><code>$ionicView.loaded</code></td>
  12607. * <td>The view has loaded. This event only happens once per
  12608. * view being created and added to the DOM. If a view leaves but is cached,
  12609. * then this event will not fire again on a subsequent viewing. The loaded event
  12610. * is good place to put your setup code for the view; however, it is not the
  12611. * recommended event to listen to when a view becomes active.</td>
  12612. * </tr>
  12613. * <tr>
  12614. * <td><code>$ionicView.enter</code></td>
  12615. * <td>The view has fully entered and is now the active view.
  12616. * This event will fire, whether it was the first load or a cached view.</td>
  12617. * </tr>
  12618. * <tr>
  12619. * <td><code>$ionicView.leave</code></td>
  12620. * <td>The view has finished leaving and is no longer the
  12621. * active view. This event will fire, whether it is cached or destroyed.</td>
  12622. * </tr>
  12623. * <tr>
  12624. * <td><code>$ionicView.beforeEnter</code></td>
  12625. * <td>The view is about to enter and become the active view.</td>
  12626. * </tr>
  12627. * <tr>
  12628. * <td><code>$ionicView.beforeLeave</code></td>
  12629. * <td>The view is about to leave and no longer be the active view.</td>
  12630. * </tr>
  12631. * <tr>
  12632. * <td><code>$ionicView.afterEnter</code></td>
  12633. * <td>The view has fully entered and is now the active view.</td>
  12634. * </tr>
  12635. * <tr>
  12636. * <td><code>$ionicView.afterLeave</code></td>
  12637. * <td>The view has finished leaving and is no longer the active view.</td>
  12638. * </tr>
  12639. * <tr>
  12640. * <td><code>$ionicView.unloaded</code></td>
  12641. * <td>The view's controller has been destroyed and its element has been
  12642. * removed from the DOM.</td>
  12643. * </tr>
  12644. * <tr>
  12645. * <td><code>$ionicParentView.enter</code></td>
  12646. * <td>The parent view has fully entered and is now the active view.
  12647. * This event will fire, whether it was the first load or a cached view.</td>
  12648. * </tr>
  12649. * <tr>
  12650. * <td><code>$ionicParentView.leave</code></td>
  12651. * <td>The parent view has finished leaving and is no longer the
  12652. * active view. This event will fire, whether it is cached or destroyed.</td>
  12653. * </tr>
  12654. * <tr>
  12655. * <td><code>$ionicParentView.beforeEnter</code></td>
  12656. * <td>The parent view is about to enter and become the active view.</td>
  12657. * </tr>
  12658. * <tr>
  12659. * <td><code>$ionicParentView.beforeLeave</code></td>
  12660. * <td>The parent view is about to leave and no longer be the active view.</td>
  12661. * </tr>
  12662. * <tr>
  12663. * <td><code>$ionicParentView.afterEnter</code></td>
  12664. * <td>The parent view has fully entered and is now the active view.</td>
  12665. * </tr>
  12666. * <tr>
  12667. * <td><code>$ionicParentView.afterLeave</code></td>
  12668. * <td>The parent view has finished leaving and is no longer the active view.</td>
  12669. * </tr>
  12670. * </table>
  12671. *
  12672. * ## LifeCycle Event Usage
  12673. *
  12674. * Below is an example of how to listen to life cycle events and
  12675. * access state parameter data
  12676. *
  12677. * ```js
  12678. * $scope.$on("$ionicView.beforeEnter", function(event, data){
  12679. * // handle event
  12680. * console.log("State Params: ", data.stateParams);
  12681. * });
  12682. *
  12683. * $scope.$on("$ionicView.enter", function(event, data){
  12684. * // handle event
  12685. * console.log("State Params: ", data.stateParams);
  12686. * });
  12687. *
  12688. * $scope.$on("$ionicView.afterEnter", function(event, data){
  12689. * // handle event
  12690. * console.log("State Params: ", data.stateParams);
  12691. * });
  12692. * ```
  12693. *
  12694. * ## Caching
  12695. *
  12696. * Caching can be disabled and enabled in multiple ways. By default, Ionic will
  12697. * cache a maximum of 10 views. You can optionally choose to disable caching at
  12698. * either an individual view basis, or by global configuration. Please see the
  12699. * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
  12700. *
  12701. * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
  12702. * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
  12703. * @param {boolean=} cache-view If this view should be allowed to be cached or not.
  12704. * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
  12705. * more info. Default `true`
  12706. * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
  12707. * This does not enable the swipe to go back feature if it is not available for the platform it's running
  12708. * from, or there isn't a previous view. Default `true`
  12709. * @param {boolean=} hide-back-button Whether to hide the back button on the parent
  12710. * {@link ionic.directive:ionNavBar} by default.
  12711. * @param {boolean=} hide-nav-bar Whether to hide the parent
  12712. * {@link ionic.directive:ionNavBar} by default.
  12713. */
  12714. IonicModule
  12715. .directive('ionView', function() {
  12716. return {
  12717. restrict: 'EA',
  12718. priority: 1000,
  12719. controller: '$ionicView',
  12720. compile: function(tElement) {
  12721. tElement.addClass('pane');
  12722. tElement[0].removeAttribute('title');
  12723. return function link($scope, $element, $attrs, viewCtrl) {
  12724. viewCtrl.init();
  12725. };
  12726. }
  12727. };
  12728. });
  12729. })();