Gergő Tisza has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/313623

Change subject: [TEST] Make PopupElement extend over containing blocks
......................................................................

[TEST] Make PopupElement extend over containing blocks

Bug: T146531
Change-Id: I204dca07378be25b6977be672263cc767bbf5fe7
---
M resources/lib/oojs-ui/oojs-ui-core.js
M resources/src/mediawiki.special/mediawiki.special.apisandbox.js
2 files changed, 220 insertions(+), 208 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/23/313623/1

diff --git a/resources/lib/oojs-ui/oojs-ui-core.js 
b/resources/lib/oojs-ui/oojs-ui-core.js
index c982010..0df24db 100644
--- a/resources/lib/oojs-ui/oojs-ui-core.js
+++ b/resources/lib/oojs-ui/oojs-ui-core.js
@@ -4600,6 +4600,213 @@
 };
 
 /**
+ * Element that will stick under a specified container, even when it is 
inserted elsewhere in the
+ * document (for example, in a OO.ui.Window's $overlay).
+ *
+ * The elements's position is automatically calculated and maintained when 
window is resized or the
+ * page is scrolled. If you reposition the container manually, you have to 
call #position to make
+ * sure the element is still placed correctly.
+ *
+ * As positioning is only possible when both the element and the container are 
attached to the DOM
+ * and visible, it's only done after you call #togglePositioning. You might 
want to do this inside
+ * the #toggle method to display a floating popup, for example.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit 
to use #$element
+ * @cfg {jQuery} [$floatableContainer] Node to position below
+ */
+OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Properties
+       this.$floatable = null;
+       this.$floatableContainer = null;
+       this.$floatableWindow = null;
+       this.$floatableClosestScrollable = null;
+       this.onFloatableScrollHandler = this.position.bind( this );
+       this.onFloatableWindowResizeHandler = this.position.bind( this );
+
+       // Initialization
+       this.setFloatableContainer( config.$floatableContainer );
+       this.setFloatableElement( config.$floatable || this.$element );
+};
+
+/* Methods */
+
+/**
+ * Set floatable element.
+ *
+ * If an element is already set, it will be cleaned up before setting up the 
new element.
+ *
+ * @param {jQuery} $floatable Element to make floatable
+ */
+OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( 
$floatable ) {
+       if ( this.$floatable ) {
+               this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' 
);
+               this.$floatable.css( { left: '', top: '' } );
+       }
+
+       this.$floatable = $floatable.addClass( 
'oo-ui-floatableElement-floatable' );
+       this.position();
+};
+
+/**
+ * Set floatable container.
+ *
+ * The element will be always positioned under the specified container.
+ *
+ * @param {jQuery|null} $floatableContainer Container to keep visible, or null 
to unset
+ */
+OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( 
$floatableContainer ) {
+       this.$floatableContainer = $floatableContainer;
+       if ( this.$floatable ) {
+               this.position();
+       }
+};
+
+/**
+ * Toggle positioning.
+ *
+ * Do not turn positioning on until after the element is attached to the DOM 
and visible.
+ *
+ * @param {boolean} [positioning] Enable positioning, omit to toggle
+ * @chainable
+ */
+OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( 
positioning ) {
+       var closestScrollableOfContainer, closestScrollableOfFloatable;
+
+       positioning = positioning === undefined ? !this.positioning : 
!!positioning;
+
+       if ( this.positioning !== positioning ) {
+               this.positioning = positioning;
+
+               closestScrollableOfContainer = 
OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 
] );
+               closestScrollableOfFloatable = 
OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] );
+               this.needsCustomPosition = closestScrollableOfContainer !== 
closestScrollableOfFloatable;
+               // If the scrollable is the root, we have to listen to scroll 
events
+               // on the window because of browser inconsistencies.
+               if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) {
+                       closestScrollableOfContainer = 
OO.ui.Element.static.getWindow( closestScrollableOfContainer );
+               }
+
+               if ( positioning ) {
+                       this.$floatableWindow = $( this.getElementWindow() );
+                       this.$floatableWindow.on( 'resize', 
this.onFloatableWindowResizeHandler );
+
+                       this.$floatableClosestScrollable = $( 
closestScrollableOfContainer );
+                       this.$floatableClosestScrollable.on( 'scroll', 
this.onFloatableScrollHandler );
+
+                       // Initial position after visible
+                       this.position();
+               } else {
+                       if ( this.$floatableWindow ) {
+                               this.$floatableWindow.off( 'resize', 
this.onFloatableWindowResizeHandler );
+                               this.$floatableWindow = null;
+                       }
+
+                       if ( this.$floatableClosestScrollable ) {
+                               this.$floatableClosestScrollable.off( 'scroll', 
this.onFloatableScrollHandler );
+                               this.$floatableClosestScrollable = null;
+                       }
+
+                       this.$floatable.css( { left: '', top: '' } );
+               }
+       }
+
+       return this;
+};
+
+/**
+ * Check whether the bottom edge of the given element is within the viewport 
of the given container.
+ *
+ * @private
+ * @param {jQuery} $element
+ * @param {jQuery} $container
+ * @return {boolean}
+ */
+OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( 
$element, $container ) {
+       var elemRect, contRect,
+               leftEdgeInBounds = false,
+               bottomEdgeInBounds = false,
+               rightEdgeInBounds = false;
+
+       elemRect = $element[ 0 ].getBoundingClientRect();
+       if ( $container[ 0 ] === window ) {
+               contRect = {
+                       top: 0,
+                       left: 0,
+                       right: document.documentElement.clientWidth,
+                       bottom: document.documentElement.clientHeight
+               };
+       } else {
+               contRect = $container[ 0 ].getBoundingClientRect();
+       }
+
+       // For completeness, if we still cared about topEdgeInBounds, that'd be:
+       // elemRect.top >= contRect.top && elemRect.top <= contRect.bottom
+       if ( elemRect.left >= contRect.left && elemRect.left <= contRect.right 
) {
+               leftEdgeInBounds = true;
+       }
+       if ( elemRect.bottom >= contRect.top && elemRect.bottom <= 
contRect.bottom ) {
+               bottomEdgeInBounds = true;
+       }
+       if ( elemRect.right >= contRect.left && elemRect.right <= 
contRect.right ) {
+               rightEdgeInBounds = true;
+       }
+
+       // We only care that any part of the bottom edge is visible
+       return bottomEdgeInBounds && ( leftEdgeInBounds || rightEdgeInBounds );
+};
+
+/**
+ * Position the floatable below its container.
+ *
+ * This should only be done when both of them are attached to the DOM and 
visible.
+ *
+ * @chainable
+ */
+OO.ui.mixin.FloatableElement.prototype.position = function () {
+       var pos;
+
+       if ( !this.positioning ) {
+               return this;
+       }
+
+       if ( !this.isElementInViewport( this.$floatableContainer, 
this.$floatableClosestScrollable ) ) {
+               this.$floatable.addClass( 'oo-ui-floatableElement-hidden' );
+               return;
+       } else {
+               this.$floatable.removeClass( 'oo-ui-floatableElement-hidden' );
+       }
+
+       if ( !this.needsCustomPosition ) {
+               return;
+       }
+
+       pos = OO.ui.Element.static.getRelativePosition( 
this.$floatableContainer, this.$floatable.offsetParent() );
+
+       // Position under container
+       pos.top += this.$floatableContainer.height();
+       this.$floatable.css( pos );
+
+       // We updated the position, so re-evaluate the clipping state.
+       // (ClippableElement does not listen to 'scroll' events on 
$floatableContainer's parent, and so
+       // will not notice the need to update itself.)
+       // TODO: This is terrible, we shouldn't need to know about 
ClippableElement at all here. Why does
+       // it not listen to the right events in the right places?
+       if ( this.clip ) {
+               this.clip();
+       }
+
+       return this;
+};
+
+/**
  * PopupElement is mixed into other classes to generate a {@link 
OO.ui.PopupWidget popup widget}.
  * A popup is a container for content. It is overlaid and positioned 
absolutely. By default, each
  * popup has an anchor, which is an arrow-like protrusion that points toward 
the popup’s origin.
@@ -4607,6 +4814,7 @@
  *
  * @abstract
  * @class
+ * @mixins OO.ui.mixin.FloatableElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -4623,7 +4831,16 @@
                config.popup,
                { $autoCloseIgnore: this.$element }
        ) );
+
+       OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, {
+               $floatable: this.popup.$element,
+               $floatableContainer: this.$element
+       } ) );
 };
+
+/* Setup */
+
+OO.mixinClass( OO.ui.mixin.PopupElement, OO.ui.mixin.FloatableElement );
 
 /* Methods */
 
@@ -4692,6 +4909,7 @@
  */
 OO.ui.PopupButtonWidget.prototype.onAction = function () {
        this.popup.toggle();
+       this.togglePositioning( this.popup.isVisible() );
 };
 
 /**
@@ -7041,213 +7259,6 @@
        if ( $nowClicked.length ) {
                this.$lastClicked = $nowClicked;
        }
-};
-
-/**
- * Element that will stick under a specified container, even when it is 
inserted elsewhere in the
- * document (for example, in a OO.ui.Window's $overlay).
- *
- * The elements's position is automatically calculated and maintained when 
window is resized or the
- * page is scrolled. If you reposition the container manually, you have to 
call #position to make
- * sure the element is still placed correctly.
- *
- * As positioning is only possible when both the element and the container are 
attached to the DOM
- * and visible, it's only done after you call #togglePositioning. You might 
want to do this inside
- * the #toggle method to display a floating popup, for example.
- *
- * @abstract
- * @class
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit 
to use #$element
- * @cfg {jQuery} [$floatableContainer] Node to position below
- */
-OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
-       // Configuration initialization
-       config = config || {};
-
-       // Properties
-       this.$floatable = null;
-       this.$floatableContainer = null;
-       this.$floatableWindow = null;
-       this.$floatableClosestScrollable = null;
-       this.onFloatableScrollHandler = this.position.bind( this );
-       this.onFloatableWindowResizeHandler = this.position.bind( this );
-
-       // Initialization
-       this.setFloatableContainer( config.$floatableContainer );
-       this.setFloatableElement( config.$floatable || this.$element );
-};
-
-/* Methods */
-
-/**
- * Set floatable element.
- *
- * If an element is already set, it will be cleaned up before setting up the 
new element.
- *
- * @param {jQuery} $floatable Element to make floatable
- */
-OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( 
$floatable ) {
-       if ( this.$floatable ) {
-               this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' 
);
-               this.$floatable.css( { left: '', top: '' } );
-       }
-
-       this.$floatable = $floatable.addClass( 
'oo-ui-floatableElement-floatable' );
-       this.position();
-};
-
-/**
- * Set floatable container.
- *
- * The element will be always positioned under the specified container.
- *
- * @param {jQuery|null} $floatableContainer Container to keep visible, or null 
to unset
- */
-OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( 
$floatableContainer ) {
-       this.$floatableContainer = $floatableContainer;
-       if ( this.$floatable ) {
-               this.position();
-       }
-};
-
-/**
- * Toggle positioning.
- *
- * Do not turn positioning on until after the element is attached to the DOM 
and visible.
- *
- * @param {boolean} [positioning] Enable positioning, omit to toggle
- * @chainable
- */
-OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( 
positioning ) {
-       var closestScrollableOfContainer, closestScrollableOfFloatable;
-
-       positioning = positioning === undefined ? !this.positioning : 
!!positioning;
-
-       if ( this.positioning !== positioning ) {
-               this.positioning = positioning;
-
-               closestScrollableOfContainer = 
OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 
] );
-               closestScrollableOfFloatable = 
OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] );
-               this.needsCustomPosition = closestScrollableOfContainer !== 
closestScrollableOfFloatable;
-               // If the scrollable is the root, we have to listen to scroll 
events
-               // on the window because of browser inconsistencies.
-               if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) {
-                       closestScrollableOfContainer = 
OO.ui.Element.static.getWindow( closestScrollableOfContainer );
-               }
-
-               if ( positioning ) {
-                       this.$floatableWindow = $( this.getElementWindow() );
-                       this.$floatableWindow.on( 'resize', 
this.onFloatableWindowResizeHandler );
-
-                       this.$floatableClosestScrollable = $( 
closestScrollableOfContainer );
-                       this.$floatableClosestScrollable.on( 'scroll', 
this.onFloatableScrollHandler );
-
-                       // Initial position after visible
-                       this.position();
-               } else {
-                       if ( this.$floatableWindow ) {
-                               this.$floatableWindow.off( 'resize', 
this.onFloatableWindowResizeHandler );
-                               this.$floatableWindow = null;
-                       }
-
-                       if ( this.$floatableClosestScrollable ) {
-                               this.$floatableClosestScrollable.off( 'scroll', 
this.onFloatableScrollHandler );
-                               this.$floatableClosestScrollable = null;
-                       }
-
-                       this.$floatable.css( { left: '', top: '' } );
-               }
-       }
-
-       return this;
-};
-
-/**
- * Check whether the bottom edge of the given element is within the viewport 
of the given container.
- *
- * @private
- * @param {jQuery} $element
- * @param {jQuery} $container
- * @return {boolean}
- */
-OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( 
$element, $container ) {
-       var elemRect, contRect,
-               leftEdgeInBounds = false,
-               bottomEdgeInBounds = false,
-               rightEdgeInBounds = false;
-
-       elemRect = $element[ 0 ].getBoundingClientRect();
-       if ( $container[ 0 ] === window ) {
-               contRect = {
-                       top: 0,
-                       left: 0,
-                       right: document.documentElement.clientWidth,
-                       bottom: document.documentElement.clientHeight
-               };
-       } else {
-               contRect = $container[ 0 ].getBoundingClientRect();
-       }
-
-       // For completeness, if we still cared about topEdgeInBounds, that'd be:
-       // elemRect.top >= contRect.top && elemRect.top <= contRect.bottom
-       if ( elemRect.left >= contRect.left && elemRect.left <= contRect.right 
) {
-               leftEdgeInBounds = true;
-       }
-       if ( elemRect.bottom >= contRect.top && elemRect.bottom <= 
contRect.bottom ) {
-               bottomEdgeInBounds = true;
-       }
-       if ( elemRect.right >= contRect.left && elemRect.right <= 
contRect.right ) {
-               rightEdgeInBounds = true;
-       }
-
-       // We only care that any part of the bottom edge is visible
-       return bottomEdgeInBounds && ( leftEdgeInBounds || rightEdgeInBounds );
-};
-
-/**
- * Position the floatable below its container.
- *
- * This should only be done when both of them are attached to the DOM and 
visible.
- *
- * @chainable
- */
-OO.ui.mixin.FloatableElement.prototype.position = function () {
-       var pos;
-
-       if ( !this.positioning ) {
-               return this;
-       }
-
-       if ( !this.isElementInViewport( this.$floatableContainer, 
this.$floatableClosestScrollable ) ) {
-               this.$floatable.addClass( 'oo-ui-floatableElement-hidden' );
-               return;
-       } else {
-               this.$floatable.removeClass( 'oo-ui-floatableElement-hidden' );
-       }
-
-       if ( !this.needsCustomPosition ) {
-               return;
-       }
-
-       pos = OO.ui.Element.static.getRelativePosition( 
this.$floatableContainer, this.$floatable.offsetParent() );
-
-       // Position under container
-       pos.top += this.$floatableContainer.height();
-       this.$floatable.css( pos );
-
-       // We updated the position, so re-evaluate the clipping state.
-       // (ClippableElement does not listen to 'scroll' events on 
$floatableContainer's parent, and so
-       // will not notice the need to update itself.)
-       // TODO: This is terrible, we shouldn't need to know about 
ClippableElement at all here. Why does
-       // it not listen to the right events in the right places?
-       if ( this.clip ) {
-               this.clip();
-       }
-
-       return this;
 };
 
 /**
diff --git a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js 
b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js
index 3085426..aeb81a6 100644
--- a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js
+++ b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js
@@ -1000,7 +1000,8 @@
                                                                                
icon: 'info',
                                                                                
popup: {
                                                                                
        $content: $( '<div>' ).append( mw.message( 'apisandbox-continue-help' 
).parse() ),
-                                                                               
        padded: true
+                                                                               
        padded: true,
+                                                                               
        $container: $( 'body' )
                                                                                
}
                                                                        } 
).$element
                                                                )

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I204dca07378be25b6977be672263cc767bbf5fe7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <[email protected]>

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

Reply via email to