PopupExtension add-on


PopupExtension
is made to supersede the relatively inflexible PopupView. It’s a popup that allows you to place it, relative to any component, with great flexibility.

Not much more to say - it’s so simple! Feedback is much appreciated.

See it
in action
, or look at it
in Directory
.

Like!

No chance to use it in our project yet, as we’re on 6 for a while, but it looks good to me!

Thanks for this… very useful component!
I get following error though:

UI's content isn't a ComponentContainer. PopupExtension requires one of those to work properly.

I don’t quite understand… my content was a VerticalLayout but changing it to a Panel didn’t seem to help. What else could be the problem?

Would love to see a component like this in the core by the way!

It’s weird that you’d get problems with a VerticalLayout. Make sure that you’re using the latest Vaadin 7 version, and actually call setContent(verticalLayout) in your UI class. You can check out the
demo UI’s source code
to see how I made it.

Panel doesn’t work, since it can contain only one component, and because of an ugly hack around Vaadin, the UI’s container needs to support several components.

You can check our connector hierarchy in the client side by adding the ?debug parameter at the end of your url, first pressing “c” to clear the console, and then “cs” to get the hierarchy. The UI connector should be first, and directly underneath should be a connector for a component that can handle several components.

But, I’m not ruling out any bugs on my side - this was just a quick proof of concept that’s simply a bit more polished than I usually do them :wink: I might work more on this on friday, to pat down some bugs and add maybe some features.

Traditionally, add-ons that i’ve made have slowly been merged into the core (by coincidence), previous ones being SuperImmediateTextField, SessionGuard and maybe in 7.1 Refresher. Maybe this one will be merged too, at some point :wink:

I had the same error message as Mortiz. I traced it down to an initialization order on my side. I’m using the Navigator and Views and my objects were being initialized prior to the Navigator setting a component to display. Moving some of my initializing down to the “enter” method (with a condition to not reinitialize of course) I was able to get around the issue.

Rob

It works when built in the attach() method:

public class PopupExtensionExample extends VerticalLayout
{
    @Override
    public void attach() {
      final Button button = new Button("Click Me");
      final PopupExtension popupExtension = PopupExtension.extend(button);
      // this wouldn't work when done in constructor!
      ......

Hm, the problems you’re seeing probably relate to the fact that “UI.getCurrent().getContent()” doesn’t return what it should return, since it is called within PopupExtension.extend(). Maybe it’s NULL in some cases? I’m honesty not too familiar on the initialization of that value, so it just might be a false assumption I’m making.

It’s nice to know that the .attach() method is a safe place to put this into, but usually needing to resort to .attach() is a Bad Thing, and my add-on should work around that. I’ll try, at some point, to see if there’s a way to fix this and make it more reliable.

Overriding the attach() method did work for me. There is a little problem with “closeOnOutsideMouseClick”… if I for example want to select something from a ComboBox in the popup, the click is also triggered as an outside-click, so the popup will close.
Is there a way to add a CloseListener?

Oh, crap. I need to take a look at that. Sounds like it’ll be an ugly hack, but, hey, the whole extension is an ugly hack :wink:

Currently no, but it’s trivial for me to add one in. I’ll try to remember to add both open and close listeners there. It’s also worth noting that the component in the pop-up is (currently) sent through the wire alongside the PopupExtension, so it’s not lazy loaded or anything. I probably will change that at some point.

sounds good, thanks Henrik! :slight_smile:

The error

still occurs when the PopupExtension is installed within a
Panel
!

public class PopupExtensionExample extends Panel
{
	@Override
	public void attach() {
		final TextField textField = new TextField();
		final Button button = new Button("V");
		final AbstractOrderedLayout ground = new HorizontalLayout();
		ground.addComponent(textField);
		ground.addComponent(button);
		setContent(ground);

		final PopupExtension popupExtension = PopupExtension.extend(textField);	
		// TODO: throws UnsupportedOperationException

The weird thing with this is that you
need
a Panel when using
ShortcutListener
, else the keypresses might not arrive at the TextField the listener was installed on (maybe this will be fixed sometimes by Vaadin).

What I’m trying to do is to open the popup when user hits Cursor-Down in the TextField:

		panel.addShortcutListener(new ShortcutListener(null, KeyCode.ARROW_DOWN, null) {
			@Override
			public void handleAction(Object sender, Object target) {
				if (popupExtension.isOpen())
					popupExtension.close();
				else;
					popupExtension.open();
			}
		});

Alas, PopupExtension requires the UI of the application to be a ComponentContainer, not the parent of PopupExtension itself. If your main layout still is a ComponentContainer, you might be composing your components in such a way that your UI.getCurrent() still returns null at that point.

Unfortunately, I didn’t remember to fix the things I mention above during easter :/. I’ll try to do better and fix them at some point this week (maybe).

The relationship between shortcut listeners, event bubbling and textfields doesn’t seem to be related to PopupExtension

I uploaded a new version to Directory with PopupVisibilityListener support.

I tried to break my code by initializing a PopupExtension within the constructor of a CustomComponent, but apparently UI.getCurrent().getContent() still returned the correct ComponentContainer I had. So I’m struggling to find the problem some of you have. The only explanations I’ve come to are that the UI content is either a Panel/CustomComponent or other non-ComponentContainer object, or that the PopupExtension is being used in an external thread that isn’t aware of the contents of UI. Nevertheless, I added a more explicit error message when this happens, so it’s easier to debug.

Having said that, since this seems to be a real issue, I’m considering the option to let the developer place a hacked component in a safe place, and leave it to that developer’s responsibilities to keep its state valid, instead of trying to figure out this myself in a generic sense.

The close-on-click issue is still left untouched, but maybe I get to that soon enough.

Actually, i disabled the download of that version, since I embarrassingly didn’t code everything right and there’s a huge bug in there. I’ll release a new version of this later today, and it will include a workaround for the “ui not componentcontainer” thing.

0.3.0 is now available, fixing both visibility listeners and allowing you to manually manage the PopupExtensionDataTransferComponent for when your UI’s content isn’t a ComponentContainer.

Hi Henrik, this works!
Here is a sample, for clarity how to do with a Panel parent:


public class PopupExtensionExample extends Panel
{
	public PopupExtensionExample() {
		setSizeUndefined();

		final TextField textField = new TextField();
		
		final PopupExtension.PopupExtensionManualBundle popupExtensionBundle = PopupExtension.extendWithManualBundle(textField);
		final PopupExtension popupExtension = popupExtensionBundle.getPopupExtension();
		popupExtension.closeOnOutsideMouseClick(true);
		popupExtension.setAnchor(Alignment.BOTTOM_LEFT);
		popupExtension.setDirection(Alignment.BOTTOM_RIGHT);
		popupExtension.setContent(buildPopupContent());
		
		final Button button = new Button("V");
		button.addClickListener(new Button.ClickListener() {
			@Override
			public void buttonClick(final ClickEvent event) {
				System.err.println("popup open: "+popupExtension.isOpen());
				if (popupExtension.isOpen())
					popupExtension.close();
				else
					popupExtension.open();
			}
		});

		layout(textField, button, popupExtensionBundle.getDataTransferComponent());
	}
	
	private void layout(TextField textField, Button button, Component popupExtensionDataTransferComponent) {
		final AbstractOrderedLayout layout = new HorizontalLayout();
		setContent(layout);
		layout.addComponent(textField);
		layout.addComponent(button);
		layout.addComponent(popupExtensionDataTransferComponent);
	}

	private Component buildPopupContent() {
		return new Label("Hello PopupExtension");
	}
}

There is only one small issue left:
When you test this application, it turns out that
popupExtension.isOpen()
, together with
closeOnOutsideMouseClick(true)
, does not always work as expected.

The occurrence is as follows:

[list]

[]
Click on the “V” Button
[
]
Popup opens as expected
[]
Click again on the “V” Button
[
]
Popup closes as expected
[*]
Click again on the “V” Button

[/list]
Expected:
Popup opens

Result:
Popup does not open, another click is necessary to open it again. This does not happen when you click anywhere outside the Popup and the Button to close the Popup.

This might be because the click on the button is interpreted as “outside click”, and is processed before the button listener. So the button listener performs another popup.open(), but the popup does not open.

Is there a way to work around this?

Hi Henrik!

I’ve just started trying out this extension to use in our V7 project, planning to use it to replace the CustomOverlays Add On for v6. One of our main uses would be for an incremental search field - it’s a text field; user starts entering a string, and we display a pop-up showing an table containing search results, updating the results as the user enters.

With the extension, it appears that “something weird” happens with columns if the table hasn’t been added to the normal component hierarchy.

I’ve attached an example, and screen shots showing the odd behaviour. The button on the left simply creates a new table, and shows it via the popupextension. The column widths are incorrect, and the column headers aren’t shown (columns-1.png). On the right, I’ve created the table and initially added it to the main layout attached to the hierarchy. The column widths are correct. On pressing the button (on the right) the table is removed and displayed via the popup extension. The column widths remain correct. (columns-2.png)

I am guessing that something is happening when the table is added/rendered in the main layout that is not happening when it is displayed as a popup. What that something is, I’ve no idea!

Any advice?

Oh - and is there a way to add a stylename to the popup-'s div? If not, could there be?

Cheers,

Charles.
12906.java (3.37 KB)
12907.png
12908.png


Fred:
that sounds like a bug with just bad logic and a race condition on my part, and your assumptions are the same as mine. I thought I disabled event bubbling when an outside click is noticed, but maybe I forgot to do that, or maybe the button’s client side event is caught before it’s sent to the “is it outside” logic. Anyways, I’ll take a look at that once/if I find the time.


Charles:
Sorry, no advice. It might be a thing with Table, but it can also be a thing with the PopupExtension (either way wouldn’t surprise me, honestly). Normally, the PopupExtension sends the component(s) it contains to the client immediately (this is something I’ve planned to change), so there’s probably some size calculation that goes awry when the popup suddenly appears and gets dimensions, as it initially doesn’t have those. Sounds like a bug, and as previously, as soon as I find the time and inspiration, I’ll tackle this one.

Regarding your second question: there isn’t currently, but i don’t see why there couldn’t be a way to set the popup stylename. This should be pretty easy to do.

I added issues
#4
and
#5
to the Github project. Thanks guys!

Sorry, didn’t have time this week. I’ll try to find the time and inspiration as soon as possible.