How to close a Vaadin subwindow on mouseclick outside of the window?
Asked Answered
U

6

5

I'm using vaadin 7 and in my application I use subwindows sometimes. In one case I have a modal window with several components in it. It opens another window when clicked on some of the components inside the modal window. I'd like this window to close automatically when the user clicks outside of it (e.g. on the modal window again). In the Vaadin Sampler this behaviour seems implemented when showing the source (click on the source button in the right upper corner). Also the behaviour should be the same if not opened from a modal window, but from the UI or any other subwindow.

I tried several things:

  • Using Popupview is not possible because I need to open the window from a component (button or image)

  • Adding a BlurListener to the new window doesn't work because if I click inside the window the blurevent is fired (e.g. moving the window)

  • Adding a ClickListener to the UI didn't help because the event was not fired when clicking on the modal window.

What is the right way to achieve that?

Thanks raffael

Undine answered 2/12, 2013 at 10:55 Comment(0)
M
4

I had the same problem and was not satisfied with any answer:

  1. Focus/blur approach proposed by @Steven Spungin does work, but only if there are not other focusable elements inside a window. Otherwise clicking one of those elements will close the window, which is far from desired.
  2. @Steven Spungin proposed a solution for his first answer with the use of "glass element", but it works only for modal windows and is generally not very flexible.
  3. Answer by @dwi wahyu utomo, in which he proposes adding click listener with UI.getCurrent().addClickListener and checking if the click event coordinates are inside a Window, works, but it has a major problem: the click event is "consumed" by Vaadin and is not propagated to the browser. That means that clicking anywhere on the page won't result in normal behaviour, e.g. native context menu will not show up on right click.

I came with an idea to create a simple AbstractExtension based on a client-side connector that extends a specific component and listens to all click events on a page. If the target of the click was not inside of the extended component it notifies server-side connector.

Here is the client-side connector:

@Connect(ClickOutsideComponentExtension.class)
public class ClickOutsideComponentConnector extends AbstractExtensionConnector implements NativePreviewHandler {

    private ComponentConnector extendedConnector;
    private ClickOutsideComponentRpc rpc;
    private HandlerRegistration handlerRegistration;

    @Override
    protected void extend(ServerConnector target) {
        extendedConnector = (ComponentConnector) target;
        rpc = getRpcProxy(ClickOutsideComponentRpc.class);
        handlerRegistration = Event.addNativePreviewHandler(this);
    }

    @Override
    public void onUnregister() {
        super.onUnregister();
        handlerRegistration.removeHandler();
    }

    @Override
    public void onPreviewNativeEvent(NativePreviewEvent event) {
        if (extendedConnector.isEnabled()) {
            Element eventTarget = Element.as(event.getNativeEvent().getEventTarget());
            if (Event.ONCLICK == event.getTypeInt() && !isElementInsideExtendedElement(eventTarget)) {
                rpc.onClickOutside();
            }
        }
    }

    public boolean isElementInsideExtendedElement(Element element) {
        Element outsideElement = extendedConnector.getWidget().getElement();
        Element insideElement = element;

        while (insideElement != null) {
            if (outsideElement.equals(insideElement)) {
                return true;
            }
            insideElement = insideElement.getParentElement();
        }
        return false;
    }

}

RPC for communication between client side and server side:

public interface ClickOutsideComponentRpc extends ServerRpc {
    void onClickOutside();
}

and server-side extension:

public class ClickOutsideComponentExtension extends AbstractExtension {

    private List<ClickOutsideListener> clickOutsideListeners = new ArrayList<>();

    public interface ClickOutsideListener extends Serializable {
        void onClickOutside();
    }

    @Override
    public void extend(AbstractClientConnector target) {
        super.extend(target);
        registerRpc(new ClickOutsideComponentRpc() {


            @Override
            public void onClickOutside() {
                for (ClickOutsideListener listener : clickOutsideListeners) {
                    if (listener != null) {
                        listener.onClickOutside();
                    }
                }
            }
        });
    }

    public void addClickOutsideListener(ClickOutsideListener listener) {
        clickOutsideListeners.add(listener);
    }
}

As stated before, this solution works for clicks outside of any component, so you can do something like this:

Label label = new Label("Try to click outside!");
ClickOutsideComponentExtension ext = new ClickOutsideComponentExtension();
ext.extend(label);
ext.addClickOutsideListener(new ClickOutsideListener() {

    @Override
    public void onClickOutside() {
        Notification.show("Click outside of label");
    }
});
addComponent(label);

or close a window on click outside of it:

Button btn = new Button("Open window");
btn.addClickListener(new ClickListener() {

    @Override
    public void buttonClick(ClickEvent event) {
        Window w = new Window();
        w.setContent(new Button("Focusable button"));
        w.center();
        ClickOutsideComponentExtension ext = new ClickOutsideComponentExtension();
        ext.extend(w);
        ext.addClickOutsideListener(new ClickOutsideListener() {

            @Override
            public void onClickOutside() {
                w.close();
            }
        });
        UI.getCurrent().addWindow(w);
    }
});
addComponent(btn);
Mariken answered 12/5, 2017 at 18:51 Comment(2)
I tried this solution, but it doesnt work in my program using vaadin 7.7.x.Buna
Check if your client-side code (connector and rpc) are placed in client package, otherwise it won't be compiled by GWT as stated in official docs (bottom of the page). Also make sure to recompile widgetset after any client-side code changes.Mariken
R
3

If the window is modal this could help:

public void showWindow() {
    final Window window = new Window();
    Button closeButton = new Button("\u00a0"); // &nbsp;
    closeButton.addClickListener(new ClickListener() {
        private static final long serialVersionUID = 1L;

        @Override
        public void buttonClick(ClickEvent event) {
            window.close();
        }
    });
    closeButton.addStyleName(BaseTheme.BUTTON_LINK);
    closeButton.addStyleName("my-style");
    window.setContent(new VerticalLayout(closeButton));
    window.setModal(true);
    window.setWidth("300px");
    window.setHeight("150px");
    UI.getCurrent().addWindow(window);
}

with this css:

.v-button.v-button-my-style {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -5;
}
Riyadh answered 26/9, 2014 at 20:20 Comment(4)
I don't think this answers the question at all. The solution here is about closing the window by clicking some content inside the window's border however the question at hand is how to close the window when clicking outside the window's borders.Marcy
@rbaleksandar: Have you even tried this solution? Or did you just write your comment without understanding how the css works? Because it does exactly what the question asked for. It works in my application.Buna
@BluE You do realize that 1)my comment is from 2 years ago and 2)Vaadin changes over time as many other frameworks out there...Marcy
@Marcy well, this problem is still present in Vaadin as of now, this solution works perfectly as it did before.Fuller
A
2

If window have fix position and size, then:

public MyWindow extends Window {
 private int x=0,y=100,width=300,height=500; 
 ...

 // constructor
 public MyWindow() {
  ...
  UI.getCurrent().addClickListener(MouseEvents.ClickListener() {
   @Override
   public void click(com.vaadin.event.MouseEvents.ClickEvent event) {
    if(!(event.getRelativeX()>=x && event.getRelativeX()<(x+width) && 
     event.getRelativeY()>=y && event.getRelativeY()<(y+height))) {
     hide();
    }
   }
  };
  ...
 }

 // showing window
 public void show() {
  if(getParent()==null) {
   setPosition(x,y);
   setWidth(width,Unit.PIXELS);
   setHeight(height,Unit.PIXELS);
   UI.getCurrent().addWindow(this);
  }
 }

 // hiding window
 public void hide() {
  if(getParent()!=null) {
   UI.getCurrent.removeWindow(this);
  }
 }

 ...
}

Hopefully solve your problem

Aggrieved answered 15/4, 2016 at 7:46 Comment(1)
Also you can try the same with layout inside UI, it provide clicked component. And you can search for your layout with getParent() method. Then check is it inside the window. LayoutClickNotifier main = (LayoutClickNotifier) UI.getCurrent().getContent(); main.addLayoutClickListener(evt -> { Component comp = evt.getClickedComponent(); .... comp.getParent() .... })Cru
F
2

Try This:

Window window = new Window();
window.setModal(true);
window.addBlurListener(event -> window.close())

Your window will need the focus before firing the blur event after an outside click; You will need to open it first before calling focus(), as Vaadin is inconsiderate in that regard.

window.show();
window.focus();
Forficate answered 3/11, 2016 at 11:52 Comment(0)
T
0

Behaviour from Vaadin Sampler that you mentioned relies on windows that are not modal:

 window.setModal(false); 

In this case you can add click listener to the window (or UI/layout under), wchich closes child window, eg.

 window.addClickListener(new MouseEvents.ClickListener() {

        @Override
        public void click(MouseEvents.ClickEvent event) {
            childWindow.close();         
        }
    });

When window is modal other components are not accessible so there is probably no simple way to define any click event outside of current window that could be fired.

Tuggle answered 30/12, 2013 at 21:39 Comment(0)
F
0

Here's another approach that works better for modal windows with focusable controls.

Once you open a modal window in Vaadin, the UI does not receive any click events, but the Window does. However, the the client does not send glass clicks to the server.

You need to extend the Window to received clicks on the glass. When the glass is clicked, you then close the window.

Beware: We make assumptions on how Vaadin creates the Window element to get to the Glass element. There are most likely better ways. In a nutshell, we wait for the window to attach, then wait 100ms for the glass to attach, then install a click handler on the glass.

The Click Handler

public interface ClickHandler extends ServerRpc {
    void onClick(String elementId);
}

The Extender - server side

public class GlassClickExtender extends AbstractExtension {
    public GlassClickExtender(Window window, ClickHandler clickHandler) {
        extend((AbstractClientConnector) window);
        registerRpc(clickHandler);
    }
}

The Extender - GlassClickExtenderClient

@Connect(GlassClickExtender.class)
public class GlassClickExtenderClient extends AbstractExtensionConnector {

private ClickHandler clickHandler;

@Override
protected void init() {
    clickHandler = getRpcProxy(ClickHandler.class);
    super.init();
}

@Override
protected void extend(ServerConnector serverConnector) {
    try {
        final Widget widget = ((ComponentConnector) serverConnector).getWidget();

        widget.addAttachHandler(new AttachEvent.Handler() {
            @Override
            public void onAttachOrDetach(AttachEvent event) {
                if (event.isAttached()) {
                    new Timer() {
                        @Override
                        public void run() {
                            Element windowElement = widget.getElement();
                            final Element glass = (Element) windowElement.getPreviousSibling();
                            if (glass == null || !glass.getClassName().contains("v-window-modalitycurtain")) {
                                return;
                            }
                            Event.sinkEvents(glass, Event.ONCLICK);
                            Event.setEventListener(glass, new EventListener() {

                                @Override
                                public void onBrowserEvent(Event event) {
                                    if (Event.ONCLICK == event.getTypeInt()) {
                                        clickHandler.onClick(glass.getId());
                                    }
                                }
                            });
                        }
                    }.schedule(100);
                }
                ;
            }

            ;
        });

    } catch (Exception e) {
        System.out.print(e.getMessage());
    }
}

}

Now, extend the window and listen for the click

 new GlassClickExtender(window, elementId -> {
                    close();
                });
Forficate answered 9/3, 2017 at 15:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.