diff --git a/config/build.js b/config/build.js index b95e070772..42b3b811b0 100644 --- a/config/build.js +++ b/config/build.js @@ -6,7 +6,14 @@ module.exports = { 'js/ionic.js', // Utils - 'js/utils/**/*.js', + 'js/utils/animate.js', + 'js/utils/dom.js', + 'js/utils/events.js', + 'js/utils/gestures.js', + 'js/utils/platform.js', + 'js/utils/poly.js', + 'js/utils/utils.js', + 'js/utils/keyboard.js', // Views 'js/views/view.js', diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js index 8a5198de26..60e6200faf 100644 --- a/js/ext/angular/src/controller/ionicScrollController.js +++ b/js/ext/angular/src/controller/ionicScrollController.js @@ -3,8 +3,7 @@ angular.module('ionic.ui.scroll') -.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', '$ionicScrollDelegate', - function($scope, scrollViewOptions, $timeout, $ionicScrollDelegate) { +.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', '$ionicScrollDelegate', '$window', function($scope, scrollViewOptions, $timeout, $ionicScrollDelegate, $window) { scrollViewOptions.bouncing = angular.isDefined(scrollViewOptions.bouncing) ? scrollViewOptions.bouncing : @@ -24,6 +23,14 @@ angular.module('ionic.ui.scroll') //Register delegate for event handling $ionicScrollDelegate.register($scope, $element, scrollView); + $window.addEventListener('resize', resize); + $scope.$on('$destroy', function() { + $window.removeEventListener('resize', resize); + }); + function resize() { + scrollView.resize(); + } + $timeout(function() { scrollView.run(); diff --git a/js/ext/angular/test/controller/ionicScrollController.unit.js b/js/ext/angular/test/controller/ionicScrollController.unit.js index ac11a8b789..eee232bddc 100644 --- a/js/ext/angular/test/controller/ionicScrollController.unit.js +++ b/js/ext/angular/test/controller/ionicScrollController.unit.js @@ -6,7 +6,10 @@ describe('$ionicScroll Controller', function() { function setup(options) { options = options || {}; - options.el = options.el || document.createElement('div'); + options.el = options.el || + //scrollView requires an outer container element and a child + //content element + angular.element('
')[0]; inject(function($controller, $rootScope, $timeout) { scope = $rootScope.$new(); @@ -41,6 +44,25 @@ describe('$ionicScroll Controller', function() { expect(ctrl.scrollView.run).toHaveBeenCalled(); }); + it('should resize the scrollview on window resize', function() { + setup(); + timeout.flush(); + spyOn(ctrl.scrollView, 'resize'); + ionic.trigger('resize', { target: window }); + expect(ctrl.scrollView.resize).toHaveBeenCalled(); + }); + + it('should unbind window event listener on scope destroy', function() { + spyOn(window, 'removeEventListener'); + spyOn(window, 'addEventListener'); + setup(); + expect(window.addEventListener).toHaveBeenCalled(); + expect(window.addEventListener.mostRecentCall.args[0]).toBe('resize'); + scope.$destroy(); + expect(window.removeEventListener).toHaveBeenCalled(); + expect(window.removeEventListener.mostRecentCall.args[0]).toBe('resize'); + }); + it('should register with $ionicScrollDelegate', inject(function($ionicScrollDelegate) { spyOn($ionicScrollDelegate, 'register'); setup(); diff --git a/js/ext/angular/test/list.html b/js/ext/angular/test/list.html index dcb07bb83a..c3320148fc 100644 --- a/js/ext/angular/test/list.html +++ b/js/ext/angular/test/list.html @@ -157,6 +157,7 @@ {{ item.text }} +
diff --git a/js/utils/events.js b/js/utils/events.js index c4014fccf8..d2def899ec 100644 --- a/js/utils/events.js +++ b/js/utils/events.js @@ -3,7 +3,7 @@ * * Author: Max Lynch * - * Framework events handles various mobile browser events, and + * Framework events handles various mobile browser events, and * detects special events like tap/swipe/etc. and emits them * as custom events that can be used in an app. * @@ -48,14 +48,18 @@ VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'], // Trigger a new event - trigger: function(eventType, data) { - var event = new CustomEvent(eventType, { detail: data }); + trigger: function(eventType, data, bubbles, cancelable) { + var event = new CustomEvent(eventType, { + detail: data, + bubbles: !!bubbles, + cancelable: !!cancelable + }); // Make sure to trigger the event on the given target, or dispatch it from // the window if we don't have an event target data && data.target && data.target.dispatchEvent(event) || window.dispatchEvent(event); }, - + // Bind an event on: function(type, callback, element) { var e = element || window; @@ -92,8 +96,8 @@ handlePopState: function(event) { }, }; - - + + // Map some convenient top-level functions for event handling ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); }; ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); }; diff --git a/js/utils/keyboard.js b/js/utils/keyboard.js new file mode 100644 index 0000000000..a1c3e01240 --- /dev/null +++ b/js/utils/keyboard.js @@ -0,0 +1,52 @@ +(function(ionic) { + +ionic.Platform.ready(function() { + if (ionic.Platform.is('android')) { + androidKeyboardFix(); + } +}); + +function androidKeyboardFix() { + var rememberedDeviceWidth = window.innerWidth; + var rememberedDeviceHeight = window.innerHeight; + var keyboardHeight; + + window.addEventListener('resize', resize); + + function resize() { + + //If the width of the window changes, we have an orientation change + if (rememberedDeviceWidth !== window.innerWidth) { + rememberedDeviceWidth = window.innerWidth; + rememberedDeviceHeight = window.innerHeight; + console.info('orientation change. deviceWidth =', rememberedDeviceWidth, + ', deviceHeight =', rememberedDeviceHeight); + + //If the height changes, and it's less than before, we have a keyboard open + } else if (rememberedDeviceHeight !== window.innerHeight && + window.innerHeight < rememberedDeviceHeight) { + document.body.classList.add('hide-footer'); + //Wait for next frame so document.activeElement is set + window.rAF(handleKeyboardChange); + } else { + //Otherwise we have a keyboard close or a *really* weird resize + document.body.classList.remove('hide-footer'); + } + + function handleKeyboardChange() { + //keyboard opens + keyboardHeight = rememberedDeviceHeight - window.innerHeight; + var activeEl = document.activeElement; + if (activeEl) { + //This event is caught by the nearest parent scrollView + //of the activeElement + ionic.trigger('scrollChildIntoView', { + target: activeEl + }, true); + } + + } + } +} + +})(window.ionic); diff --git a/js/views/scrollView.js b/js/views/scrollView.js index 446675ae25..c31e853d02 100644 --- a/js/views/scrollView.js +++ b/js/views/scrollView.js @@ -593,6 +593,28 @@ ionic.views.Scroll = ionic.views.View.inherit({ // Event Handler var container = this.__container; + //Broadcasted when keyboard is shown on some platforms. + //See js/utils/keyboard.js + container.addEventListener('scrollChildIntoView', function(e) { + var deviceHeight = window.innerHeight; + var element = e.target; + var elementHeight = e.target.offsetHeight; + + //getBoundingClientRect() will actually give us position relative to the viewport + var elementDeviceTop = element.getBoundingClientRect().top; + var elementScrollTop = ionic.DomUtil.getPositionInParent(element, container).top; + + //If the element is positioned under the keyboard... + if (elementDeviceTop + elementHeight > deviceHeight) { + //Put element in middle of visible screen + self.scrollTo(0, elementScrollTop + elementHeight - (deviceHeight * 0.5), true); + } + + //Only the first scrollView parent of the element that broadcasted this event + //(the active element that needs to be shown) should receive this event + e.stopPropagation(); + }); + if ('ontouchstart' in window) { container.addEventListener("touchstart", function(e) { diff --git a/scss/_util.scss b/scss/_util.scss index fa84a34f6d..40472910ef 100644 --- a/scss/_util.scss +++ b/scss/_util.scss @@ -4,9 +4,9 @@ * -------------------------------------------------- */ -.hidden, -.hide { - display: none; +.hidden, +.hide { + display: none; } .show { display: block; @@ -15,6 +15,17 @@ visibility: hidden; } +.hide-footer { + .bar-footer, + .tabs { + display: none; + } + .has-footer, + .has-tabs { + bottom: 0; + } +} + .inline { display: inline-block; } @@ -30,10 +41,10 @@ .block { display: block; clear: both; - &:after { - display: block; - visibility: hidden; - clear: both; + &:after { + display: block; + visibility: hidden; + clear: both; height: 0; content: "."; } @@ -101,8 +112,8 @@ /** * Utility Colors * -------------------------------------------------- - * Utility colors are added to help set a naming convention. You'll - * notice we purposely do not use words like "red" or "blue", but + * Utility colors are added to help set a naming convention. You'll + * notice we purposely do not use words like "red" or "blue", but * instead have colors which represent an emotion or generic theme. */ diff --git a/test/inputs.html b/test/inputs.html new file mode 100644 index 0000000000..808fd86498 --- /dev/null +++ b/test/inputs.html @@ -0,0 +1,84 @@ + + + + List + + + + + + + + + + +
+
+ +
+

List Tests

+
+ +
+
+ +
+

Footer time!

+
+ + + +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +

...

+

...

+

...

+

...

+

...

+ +
+ +
+ + +