How do I get a GWT menu popup to stay within the browser window?
My GWT app uses a DockLayoutPanel for primary layout and the page itself does not scroll. I have a PopupPanel with a MenuBar and sometimes when a MenuItem is selected the sub menu bar goes off the bottom of the screen abruptly forcing a new scroll bar into the browser and messing up the layout.
How do I get the menu popup to behave nicely and repositi开发者_运维技巧on itself upward when the default positioning would put it out of the browser viewport (the way that PopupPanel.showRelativeTo(uiTarget) positioning works)?
In looking at the MenuBar source, it looks like all the layout is done in private methods, so I can't fix it in subclass, and I don't see any events I can listen to that would allow me to do the repositioning myself.
Take a look at http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/6185225fec64c091/4954d91d1461c71f?lnk=gst&q=context+menu#4954d91d1461c71f.
We've been using this strategy quite successfully for a while now.
Update: There is a bit more to be done. Specifically:
Create a reposition() method, which:
- Determines the max width of all the menu items
- Checks the left edge of the menu + the max width; if greater than the Window's width, use "DOM.setStyleAttribute(elem, "left", left + "px");" to move the menu
- Get the height of the menu; if top of the menu + height of the menu > Window's height, use "DOM.setStyleAttribute(elem, "top", top + "px");" to move it up.
In the onAttach() method, use a deferred command to invoke the reposition() method.
You can intercept the popup just before it is shown, but after its size has been created. This way you have the width of the popup and could move it to another position:
@Override
public void onContextMenu(ContextMenuEvent evt) {
int x = evt.getNativeEvent().getClientX();
int y = evt.getNativeEvent().getClientY();
popupMenu.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
if (x + offsetWidth > Window.getClientWidth()) {
x = Window.getClientWidth() - offsetWidth;
}
//use same technique for height if you want for y, then
setPosition(x, y);
}
});
}
(I know this is an old question, but still comes up if you search for this, so I thought of providing present solution)
Emm...
It is an interesting question...
Looking at the MenuBar source code... especially the method openPopup
private void openPopup(final MenuItem item) {
// Only the last popup to be opened should preview all event
if (parentMenu != null && parentMenu.popup != null) {
parentMenu.popup.setPreviewingAllNativeEvents(false);
}
// Create a new popup for this item, and position it next to
// the item (below if this is a horizontal menu bar, to the
// right if it's a vertical bar).
popup = new DecoratedPopupPanel(true, false, "menuPopup") {
{
setWidget(item.getSubMenu());
setPreviewingAllNativeEvents(true);
item.getSubMenu().onShow();
}
@Override
protected void onPreviewNativeEvent(NativePreviewEvent event) {
// Hook the popup panel's event preview. We use this to keep it from
// auto-hiding when the parent menu is clicked.
if (!event.isCanceled()) {
switch (event.getTypeInt()) {
case Event.ONMOUSEDOWN:
// If the event target is part of the parent menu, suppress the
// event altogether.
EventTarget target = event.getNativeEvent().getEventTarget();
Element parentMenuElement = item.getParentMenu().getElement();
if (parentMenuElement.isOrHasChild(Element.as(target))) {
event.cancel();
return;
}
super.onPreviewNativeEvent(event);
if (event.isCanceled()) {
selectItem(null);
}
return;
}
}
super.onPreviewNativeEvent(event);
}
};
popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
popup.setAnimationEnabled(isAnimationEnabled);
popup.setStyleName(STYLENAME_DEFAULT + "Popup");
String primaryStyleName = getStylePrimaryName();
if (!STYLENAME_DEFAULT.equals(primaryStyleName)) {
popup.addStyleName(primaryStyleName + "Popup");
}
popup.addPopupListener(this);
shownChildMenu = item.getSubMenu();
item.getSubMenu().parentMenu = this;
// Show the popup, ensuring that the menubar's event preview remains on top
// of the popup's.
popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
public void setPosition(int offsetWidth, int offsetHeight) {
// depending on the bidi direction position a menu on the left or right
// of its base item
if (LocaleInfo.getCurrentLocale().isRTL()) {
if (vertical) {
popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() - offsetWidth
+ 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft()
+ item.getOffsetWidth() - offsetWidth,
MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
- 1);
}
} else {
if (vertical) {
popup.setPopupPosition(MenuBar.this.getAbsoluteLeft()
+ MenuBar.this.getOffsetWidth() - 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft(),
MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
- 1);
}
}
}
});
}
It is interesting to point the snippet as
...
popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
public void setPosition(int offsetWidth, int offsetHeight) {
// depending on the bidi direction position a menu on the left or right
// of its base item
if (LocaleInfo.getCurrentLocale().isRTL()) {
if (vertical) {
popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() - offsetWidth
+ 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft()
+ item.getOffsetWidth() - offsetWidth,
MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
- 1);
}
} else {
if (vertical) {
popup.setPopupPosition(MenuBar.this.getAbsoluteLeft()
+ MenuBar.this.getOffsetWidth() - 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft(),
MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
- 1);
}
}
}
});
...
... so I may suppose there is a sense to play around MenuItem object especially its UIObject inherited methods like getAbsoluteLeft() and getAbsoluteTop(), of course ...
I would recommend to extend MenuItem something in this way
//not tested
public class MyMenuItem extends MenuItem
{
private MenuBar aSubMenuBar;//ItemMenu's submenu
//...
@Override
public int getAbsoluteTop() {
// TODO Auto-generated method stub
return super.getAbsoluteTop()+movePopupTo();
}
private int movePopupTo()
{
int moveTo=0;
int bottom=RootPanel.getBodyElement().getAbsoluteBottom();
int rest=bottom -(super.getAbsoluteTop()+this.getaSubMenuBar().getOffsetHeight());
if(rest<0)
{
moveTo=rest;
}
return moveTo;
}
public MenuBar getaSubMenuBar() {
return aSubMenuBar;
}
public void setaSubMenuBar(MenuBar aSubMenuBar) {
this.aSubMenuBar = aSubMenuBar;
}
//...
}
It is not the final solution but a basic conception.
Report if that helped
Good luck
精彩评论