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
I had the same problem and was not satisfied with any answer:
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);