jenkins-bot has submitted this change and it was merged.

Change subject: Improve fixed header in overlays on iOS
......................................................................


Improve fixed header in overlays on iOS

Fake fixed header by wrapping overlay content in a div and scrolling
within that div instead (on iOS only). Modify content div's height
when virtual keyboard is open/closed or window resized.

This also improves scrolling in notifications overlay on iOS.

Tested on iOS 7, recent Chrome for Android and Android Browser 2.3.

Bug: 67390

Change-Id: I05f11246ca90a1ce3f741fd93194a827528cd597
---
M includes/Resources.php
M javascripts/common/Overlay.js
M javascripts/common/application.js
M javascripts/modules/editor/VisualEditorOverlay.js
M javascripts/modules/notifications/NotificationsOverlay.js
M javascripts/modules/search/SearchOverlay.js
M less/common/Overlay.less
M less/modules/NotificationsOverlay.less
M less/modules/search/SearchOverlay.less
M templates/Overlay.html
M templates/modules/editor/EditorOverlayBase.html
R templates/modules/notifications/NotificationsOverlayContent.html
A templates/modules/notifications/NotificationsOverlayFooter.html
M templates/modules/search/SearchOverlay.html
14 files changed, 183 insertions(+), 112 deletions(-)

Approvals:
  Kaldari: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/Resources.php b/includes/Resources.php
index c0b463f..f4b4a8f 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -899,7 +899,8 @@
                        'less/modules/NotificationsOverlay.less',
                ),
                'templates' => array(
-                       'modules/notifications/NotificationsOverlay',
+                       'modules/notifications/NotificationsOverlayContent',
+                       'modules/notifications/NotificationsOverlayFooter',
                ),
                'messages' => array(
                        // defined in Echo
diff --git a/javascripts/common/Overlay.js b/javascripts/common/Overlay.js
index 20873d2..052c962 100644
--- a/javascripts/common/Overlay.js
+++ b/javascripts/common/Overlay.js
@@ -3,8 +3,7 @@
 
        var
                View = M.require( 'View' ),
-               // how long it takes for iOS keyboard to open/close
-               IOS_KEYBOARD_DELAY = 300,
+               $window = $( window ),
                Overlay;
 
        /**
@@ -45,7 +44,11 @@
                closeOnContentTap: false,
 
                postRender: function( options ) {
-                       var self = this;
+                       var
+                               self = this,
+                               $overlayContent = this.$overlayContent = 
this.$( '.overlay-content' ),
+                               startY;
+
                        // Truncate any text inside in the overlay header.
                        this.$( '.overlay-header h2 span' ).addClass( 
'truncated-text' );
                        // FIXME change when micro.tap.js in stable
@@ -64,7 +67,31 @@
                                ev.stopPropagation();
                        } );
 
-                       this._fixIosHeader( 'textarea, input' );
+                       if ( M.isIos ) {
+                               $overlayContent
+                               .on( 'touchstart', function( ev ) {
+                                       startY = 
ev.originalEvent.touches[0].pageY;
+                               } )
+                               .on( 'touchmove', function( ev ) {
+                                       var
+                                               y = 
ev.originalEvent.touches[0].pageY,
+                                               contentLenght = 
$overlayContent.prop( 'scrollHeight' ) - $overlayContent.outerHeight();
+
+                                       ev.stopPropagation();
+                                       // prevent scrolling and bouncing 
outside of .overlay-content
+                                       if (
+                                               ( $overlayContent.scrollTop() 
=== 0 && startY < y ) ||
+                                               ( $overlayContent.scrollTop() 
=== contentLenght && startY > y )
+                                       ) {
+                                               ev.preventDefault();
+                                       }
+                               } );
+
+                               // wait for things to render before doing any 
calculations
+                               setTimeout( function() {
+                                       self._fixIosHeader( 'textarea, input' );
+                               }, 0 );
+                       }
                },
 
                // FIXME: remove when OverlayManager used everywhere
@@ -82,6 +109,8 @@
                 * @method
                 */
                show: function() {
+                       var self = this;
+
                        // FIXME: remove when OverlayManager used everywhere
                        if ( this.closeOnBack ) {
                                this._hideOnRoute();
@@ -98,6 +127,17 @@
 
                        if ( this.closeOnContentTap ) {
                                $( '#mw-mf-page-center' ).one( M.tapEvent( 
'click' ), $.proxy( this, 'hide' ) );
+                       }
+
+                       // prevent scrolling and bouncing outside of 
.overlay-content
+                       if ( M.isIos ) {
+                               $window
+                                       .on( 'touchmove.ios', function( ev ) {
+                                               ev.preventDefault();
+                                       } )
+                                       .on( 'resize.ios', function() {
+                                               self._resizeContent( 
$window.height() );
+                                       } );
                        }
 
                        this.$el.addClass( 'visible' );
@@ -129,43 +169,53 @@
                                self.$el.detach();
                        }, 1000 );
 
+                       if ( M.isIos ) {
+                               $window.off( '.ios' );
+                       }
+
                        this.emit( 'hide' );
 
                        return true;
                },
 
+               _resizeContent: function( windowHeight ) {
+                       this.$overlayContent.height( windowHeight - this.$( 
'.overlay-header-container' ).outerHeight() - this.$( 
'.overlay-footer-container' ).outerHeight() );
+               },
+
+               /**
+                * Resize .overlay-content to occupy 100% of screen space when 
virtual
+                * keyboard is shown/hidden on iOS.
+                *
+                * This function supplements the custom styles for Overlays on 
iOS.
+                * On iOS we scroll the content inside of .overlay-content div 
instead
+                * of scrolling the whole page to achieve a consistent sticky 
header
+                * effect (position: fixed doesn't work on iOS when the virtual 
keyboard
+                * is open).
+                *
+                * @method
+                * @param {string} el CSS selector for elements that may 
trigger virtual
+                * keyboard (usually inputs, textareas, contenteditables).
+                */
                _fixIosHeader: function( el ) {
-                       var $header = this.$( '.overlay-header-container' ), 
$window = $( window );
-                       // This is used to avoid position: fixed weirdness in 
mobile Safari when
-                       // the keyboard is visible
-                       if ( ( /ipad|iphone/i ).test( navigator.userAgent ) ) {
-                               this.$( el ).
-                                       on( 'focus', function() {
-                                               $header.removeClass( 
'position-fixed' );
-                                               // don't show fixed header on 
iPhone, it causes bug 62120
-                                               // (also, there is a Done 
button on the keyboard anyway)
-                                               if ( M.isWideScreen() ) {
-                                                       // wait for the 
keyboard opening animation to finish
-                                                       setTimeout( function() {
-                                                               $header.css( 
'top', $window.scrollTop() );
-                                                       }, IOS_KEYBOARD_DELAY );
-                                                       $window.on( 
'scroll.fixIosHeader', function() {
-                                                               $header.css( 
'top', $window.scrollTop() ).addClass( 'visible' );
-                                                       } );
-                                                       $window.on( 
'touchmove.fixIosHeader', function() {
-                                                               // don't hide 
header if we're at the top
-                                                               if ( 
$window.scrollTop() > 0 ) {
-                                                                       
$header.removeClass( 'visible' );
-                                                               }
-                                                       } );
-                                               }
-                                       } ).
-                                       on( 'blur', function() {
-                                               // wait for the keyboard 
closing animation to finish
+                       var self = this;
+
+                       if ( M.isIos ) {
+                               this._resizeContent( $( window ).height() );
+                               $( el )
+                                       .on( 'focus', function() {
                                                setTimeout( function() {
-                                                       $header.css( 'top', 0 
).addClass( 'position-fixed visible' );
-                                               }, IOS_KEYBOARD_DELAY );
-                                               $window.off( '.fixIosHeader' );
+                                                       var keyboardHeight;
+
+                                                       // detect virtual 
keyboard height
+                                                       $window.scrollTop( 999 
);
+                                                       keyboardHeight = 
$window.scrollTop();
+                                                       $window.scrollTop( 0 );
+
+                                                       self._resizeContent( 
$window.height() - keyboardHeight );
+                                               }, 0 );
+                                       } )
+                                       .on( 'blur', function() {
+                                               self._resizeContent( 
$window.height() );
                                        } );
                        }
                },
diff --git a/javascripts/common/application.js 
b/javascripts/common/application.js
index 353d260..6f023e2 100644
--- a/javascripts/common/application.js
+++ b/javascripts/common/application.js
@@ -16,10 +16,10 @@
                currentPage,
                inWideScreenMode = false,
                ua = window.navigator.userAgent,
-               isAppleDevice = /ipad|iphone/i.test( ua ),
-               isIPhone4 = isAppleDevice && /OS 4_/.test( ua ),
-               isOldIPhone = isAppleDevice && /OS [4]_[0-2]|OS [3]_/.test( ua 
),
-               isIPhone5 = isAppleDevice && /OS 5_/.test( ua ),
+               isIos = /ipad|iphone/i.test( ua ),
+               isIPhone4 = isIos && /OS 4_/.test( ua ),
+               isOldIPhone = isIos && /OS [4]_[0-2]|OS [3]_/.test( ua ),
+               isIPhone5 = isIos && /OS 5_/.test( ua ),
                isAndroid2 = /Android 2/.test( ua );
 
        // See if local storage is supported
@@ -140,6 +140,10 @@
                        $viewport = $( '#mw-mf-viewport' );
 
                $( '<div id="notifications">' ).appendTo( $viewport );
+
+               if ( isIos ) {
+                       $body.addClass( 'ios' );
+               }
 
                if ( !supportsPositionFixed( navigator.userAgent ) ) {
                        $doc.addClass( 'no-position-fixed' );
@@ -429,6 +433,7 @@
                reloadPage: reloadPage,
                supportsGeoLocation: supportsGeoLocation,
                supportsPositionFixed: supportsPositionFixed,
+               isIos: isIos,
                prettyEncodeTitle: prettyEncodeTitle,
                query: deParam( qs ),
                unlockViewport: unlockViewport,
diff --git a/javascripts/modules/editor/VisualEditorOverlay.js 
b/javascripts/modules/editor/VisualEditorOverlay.js
index cf1e83a..79196cf 100644
--- a/javascripts/modules/editor/VisualEditorOverlay.js
+++ b/javascripts/modules/editor/VisualEditorOverlay.js
@@ -149,17 +149,20 @@
                        this.destroyTarget();
                },
                onSurfaceReady: function () {
+                       var self = this;
                        this.clearSpinner();
                        this.$( '.surface' ).show();
                        this.target.surface.getModel().getDocument().connect( 
this, { 'transact': 'onTransact' } );
                        this.target.surface.$element.addClass( 'content' );
 
-                       // for some reason the first time contenteditables are 
focused, focus
-                       // event doesn't fire if we don't blur them first
-                       this.$( '[contenteditable]' ).blur();
                        // we have to do it here because contenteditable 
elements still do not
                        // exist when postRender is executed
                        this._fixIosHeader( '[contenteditable]' );
+                       // for some reason the first time contenteditables are 
focused, focus
+                       // event doesn't fire if we don't do this (at least on 
iOS Safari 7)
+                       setTimeout( function() {
+                               self.$( '[contenteditable]' ).focus();
+                       }, 0 );
                },
                onTransact: function () {
                        this.hasChanged = true;
diff --git a/javascripts/modules/notifications/NotificationsOverlay.js 
b/javascripts/modules/notifications/NotificationsOverlay.js
index 8d19a98..e643be4 100644
--- a/javascripts/modules/notifications/NotificationsOverlay.js
+++ b/javascripts/modules/notifications/NotificationsOverlay.js
@@ -11,7 +11,8 @@
                        active: false,
                        className: 'overlay notifications-overlay 
navigation-drawer',
                        templatePartials: {
-                               content: M.template.get( 
'modules/notifications/NotificationsOverlay' )
+                               content: M.template.get( 
'modules/notifications/NotificationsOverlayContent' ),
+                               footer: M.template.get( 
'modules/notifications/NotificationsOverlayFooter' )
                        },
                        defaults: {
                                heading: mw.msg( 'notifications' ),
diff --git a/javascripts/modules/search/SearchOverlay.js 
b/javascripts/modules/search/SearchOverlay.js
index b843354..bda3129 100644
--- a/javascripts/modules/search/SearchOverlay.js
+++ b/javascripts/modules/search/SearchOverlay.js
@@ -78,12 +78,13 @@
                                } );
 
                        // tapping on background only should hide the overlay
-                       this.$el.on( M.tapEvent( 'click' ), function() {
-                               window.history.back();
-                       } );
-                       this.$( '> div' ).on( M.tapEvent( 'click' ), function( 
ev ) {
-                               ev.stopPropagation();
-                       } );
+                       this.$overlayContent
+                               .on( M.tapEvent( 'click' ), function() {
+                                       window.history.back();
+                               } )
+                               .find( '> div' ).on( M.tapEvent( 'click' ), 
function( ev ) {
+                                       ev.stopPropagation();
+                               } );
 
                        // hide the keyboard when scrolling starts (avoid weird 
situation when
                        // user taps on an item, the keyboard hides and wrong 
item is clicked)
diff --git a/less/common/Overlay.less b/less/common/Overlay.less
index d911b4a..c907d84 100644
--- a/less/common/Overlay.less
+++ b/less/common/Overlay.less
@@ -242,24 +242,12 @@
        }
 }
 
-.overlay-header-container {
+.overlay-header-container,
+.overlay-footer-container {
        width: 100%;
        background: #fff;
-       // needed for _fixIosHeader method to work properly
-       position: absolute;
-       top: 0;
-       // make header reappearing less abrupt when scrolling on iOS with open
-       // keyboard
-       opacity: 0;
-       // prevent .page-list overlaping the header when scrolling
-       // needed for _fixIosHeader method to work properly
+       // prevent .page-list or VE surface overlaping the header when scrolling
        z-index: 5;
-
-       &.visible {
-               opacity: 1;
-               // transform for elements that slide down (VE second toolbar)
-               .transition-transform( .3s, opacity .3s; );
-       }
 
        &.position-fixed {
                // both top and left required for Android 2 for the element to 
be visible
@@ -269,6 +257,14 @@
                // element to be visible, right: 0 doesn't work)
                right: 0;
        }
+}
+
+.overlay-header-container {
+       top: 0;
+}
+
+.overlay-footer-container {
+       bottom: 0;
 }
 
 // Bottom Overlays
@@ -305,6 +301,21 @@
        }
 }
 
+.ios {
+       .overlay-header-container {
+               position: absolute !important;
+       }
+
+       .overlay-footer-container {
+               position: absolute !important;
+       }
+
+       .overlay-content {
+               overflow-y: scroll; // has to be scroll, not auto
+               -webkit-overflow-scrolling: touch;
+       }
+}
+
 
 @media all and (min-width: @wgMFDeviceWidthTablet) {
        .overlay {
diff --git a/less/modules/NotificationsOverlay.less 
b/less/modules/NotificationsOverlay.less
index 8701e32..6ca5e98 100644
--- a/less/modules/NotificationsOverlay.less
+++ b/less/modules/NotificationsOverlay.less
@@ -11,11 +11,15 @@
                position: static !important;
        }
 
-       .mw-mf-notifications {
-               padding-bottom: 0;
-               bottom: @headerHeight;
+       .overlay-content {
+               // this is needed not only on iOS, that's why we repeat it here 
even though
+               // it's in Overlay.less too
+               overflow-y: scroll; // has to be scroll, not auto
+               -webkit-overflow-scrolling: touch;
+               position: absolute;
                top: @headerHeight;
-               overflow-y: auto;
+               bottom: @headerHeight;
+               width: 100%;
                margin-top: 1px;
        }
 
@@ -65,14 +69,6 @@
                border-top: 1px solid @grayLight;
                background-color: #FFF;
                text-align: center;
-               bottom: 0;
-       }
-
-       .mw-mf-notifications,
-       .notifications-archive-link {
-               position: absolute;
-               right: 0;
-               left: 0;
        }
 
        .mw-echo-title {
diff --git a/less/modules/search/SearchOverlay.less 
b/less/modules/search/SearchOverlay.less
index 3b7ce19..6f4ab7c 100644
--- a/less/modules/search/SearchOverlay.less
+++ b/less/modules/search/SearchOverlay.less
@@ -29,6 +29,8 @@
        }
 
        .results {
+               box-shadow: 0 3px 3px 0 rgba(117, 117, 117, .3);
+
                li {
                        padding-left: @headerHeight + @headerTitlePaddingH;
                }
@@ -47,9 +49,5 @@
                h2 {
                        font: inherit;
                }
-       }
-
-       .search-overlay-contents {
-               box-shadow: 0 1px 5px 0 rgba(117, 117, 117, .5);
        }
 }
diff --git a/templates/Overlay.html b/templates/Overlay.html
index 083f62c..9e9ff0e 100644
--- a/templates/Overlay.html
+++ b/templates/Overlay.html
@@ -1,4 +1,4 @@
-<div class="overlay-header-container visible {{#fixedHeader}} 
position-fixed{{/fixedHeader}}">
+<div class="overlay-header-container {{#fixedHeader}} 
position-fixed{{/fixedHeader}}">
        <div class="overlay-header">
                <ul>
                        <li><button class="cancel icon 
icon-cancel">{{closeMsg}}</button></li>
@@ -15,4 +15,9 @@
                {{/headerButtons}}
        </div>
 </div>
-{{>content}}
+<div class="overlay-content">
+       {{>content}}
+</div>
+<div class="overlay-footer-container position-fixed">
+       {{>footer}}
+</div>
diff --git a/templates/modules/editor/EditorOverlayBase.html 
b/templates/modules/editor/EditorOverlayBase.html
index fa87312..23cb19f 100644
--- a/templates/modules/editor/EditorOverlayBase.html
+++ b/templates/modules/editor/EditorOverlayBase.html
@@ -1,4 +1,4 @@
-<div class="overlay-header-container visible position-fixed">
+<div class="overlay-header-container position-fixed">
        {{>header}}
        <div class="overlay-header save-header hideable hidden">
                <ul>
@@ -22,20 +22,20 @@
        </div>
 </div>
 
-<div class="panels">
-       <div class="save-panel panel hideable hidden">
-               <p class="summary-request">{{{summaryRequestMsg}}}</p>
-               <textarea rows="2" class="summary" 
placeholder="{{summaryMsg}}"></textarea>
-               <p class="license">{{{licenseMsg}}}</p>
-       </div>
-       <div class="captcha-panel panel hideable hidden">
-               <div class="inputs-box">
-                       <img src="">
-                       <input class="captcha-word" 
placeholder="{{captchaMsg}}" />
+<div class="overlay-content">
+       <div class="panels">
+               <div class="save-panel panel hideable hidden">
+                       <p class="summary-request">{{{summaryRequestMsg}}}</p>
+                       <textarea rows="2" class="summary" 
placeholder="{{summaryMsg}}"></textarea>
+                       <p class="license">{{{licenseMsg}}}</p>
+               </div>
+               <div class="captcha-panel panel hideable hidden">
+                       <div class="inputs-box">
+                               <img src="">
+                               <input class="captcha-word" 
placeholder="{{captchaMsg}}" />
+                       </div>
                </div>
        </div>
+       <div class="spinner loading"></div>
+       {{>content}}
 </div>
-
-<div class="spinner loading"></div>
-
-{{>content}}
diff --git a/templates/modules/notifications/NotificationsOverlay.html 
b/templates/modules/notifications/NotificationsOverlayContent.html
similarity index 74%
rename from templates/modules/notifications/NotificationsOverlay.html
rename to templates/modules/notifications/NotificationsOverlayContent.html
index 6a5ae7f..6c2fd54 100644
--- a/templates/modules/notifications/NotificationsOverlay.html
+++ b/templates/modules/notifications/NotificationsOverlayContent.html
@@ -7,4 +7,3 @@
                </li>
        {{/notifications}}
 </ul>
-<a href="{{archiveLink}}" 
class="notifications-archive-link">{{archiveLinkMsg}}</a>
diff --git a/templates/modules/notifications/NotificationsOverlayFooter.html 
b/templates/modules/notifications/NotificationsOverlayFooter.html
new file mode 100644
index 0000000..25c6d42
--- /dev/null
+++ b/templates/modules/notifications/NotificationsOverlayFooter.html
@@ -0,0 +1 @@
+<a href="{{archiveLink}}" 
class="notifications-archive-link">{{archiveLinkMsg}}</a>
diff --git a/templates/modules/search/SearchOverlay.html 
b/templates/modules/search/SearchOverlay.html
index 607feb6..cb3710b 100644
--- a/templates/modules/search/SearchOverlay.html
+++ b/templates/modules/search/SearchOverlay.html
@@ -1,19 +1,19 @@
-<div class="search-overlay-contents">
-       <div class="overlay-header-container visible position-fixed">
-               <div class="overlay-header">
-                       <ul>
-                               <li><button class="cancel icon 
icon-cancel">{{closeMsg}}</button></li>
-                       </ul>
-                       <div class="overlay-search">
-                               <form method="get" action="{{action}}">
-                                       <input class="search" type="search" 
name="search" autocomplete="off" placeholder="{{placeholderMsg}}">
-                               </form>
-                       </div>
-                       <ul>
-                               <li><button class="clear icon 
icon-clear">{{clearMsg}}</button></li>
-                       </ul>
+<div class="overlay-header-container position-fixed">
+       <div class="overlay-header">
+               <ul>
+                       <li><button class="cancel icon 
icon-cancel">{{closeMsg}}</button></li>
+               </ul>
+               <div class="overlay-search">
+                       <form method="get" action="{{action}}">
+                               <input class="search" type="search" 
name="search" autocomplete="off" placeholder="{{placeholderMsg}}">
+                       </form>
                </div>
+               <ul>
+                       <li><button class="clear icon 
icon-clear">{{clearMsg}}</button></li>
+               </ul>
        </div>
+</div>
+<div class="overlay-content">
        <div class="search-content overlay-header">
                <ul>
                        <li><button 
class="icon">{{searchContentMsg}}</button></li>

-- 
To view, visit https://gerrit.wikimedia.org/r/145185
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I05f11246ca90a1ce3f741fd93194a827528cd597
Gerrit-PatchSet: 14
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: JGonera <[email protected]>
Gerrit-Reviewer: Awjrichards <[email protected]>
Gerrit-Reviewer: JGonera <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to