Vaadin flow select component prevent click event propagation to grid header - vaadin-flow

In Vaadin Flow 23.1.x we have a grid with different filter fields in the header.
In the screen below the "Vendorstatus" is a ComboBox and the ValudateCode is a Select component.
What we now see:
If we click in the Vedonrstatus field, the dropdown opens and nothing else happens, which is ok
If we click in the ValutaCode field, the dropdown opens too, but also the "Sort" of the grid is triggered.
So it looks like the select component does not consume/block the ClickEvent from propagating down to the parent components.
Is there a way to prevent the click to trigger the sort of the column grid?
Workarround would be to re-implement my Select based header filter with ComboBox type ones.
The code to generate the filter header:
headerRow.getCell(col).setComponent(createSelectFilter(...));
And the createSelectFilter method:
private static Component createSelectFilter(...) {
VerticalLayout vl= new VerticalLayout();
Label l= new Label("Headername");
vl.add(l);
Select<MyobjClass> select= new Select<>();
select.setItems(...);
select.getElement().addEventListener("click",
event -> {})
.addEventData("event.stopPropagation()");
select.addValueChangeListener( e --> updateDataFilter());
vl add(select);
return vl;
}
This does not stop propagation of the click to open the dropdown and select a value to also trigger a sort "command" on that column

You must use this method to add the listener them you can
select.getElement().addEventListener("click",
event -> ...)
.addEventData("event.stopPropagation()");

Related

Testing-library unable to find rc-menu

I'm trying to implement integration tests on our React frontend which uses Ant design. Whenever we create a table, we add an action column in which we have a Menu and Menu Items to perform certain actions.
However, I seem unable to find the correct button in the menu item when using react-testing-library. The menu Ant design uses is rc-menu and I believe it renders outside of the rendered component.
Reading the testing-library documentation, I've tried using baseElement and queryByRole to get the correct DOM element, but it doesn't find anything.
Here's an example of what I'm trying to do. It's all async since the table has to wait on certain data before it gets filled in, just an FYI.
Tried it on codesandbox
const row = await screen.findByRole('row', {
name: /test/i
})
const menu = await within(row).findByRole('menu')
fireEvent.mouseDown(menu)
expect(queryByRole('button', { name: /delete/i })).toBeDisabled()
the menu being opened with the delete action as menu item
I had a same issue testing antd + rc-menu component. Looks like the issue is related to delayed popup rendering. Here is an example how I solved it:
jest.useFakeTimers();
const { queryByTestId, getByText } = renderMyComponent();
const nav = await waitFor(() => getByText("My Menu item text"));
act(() => {
fireEvent.mouseEnter(nav);
jest.runAllTimers(); // ! <- this was a trick !
});
jest.useRealTimers();
expect(queryByTestId("submenu-item-test-id")).toBeTruthy();

setColumnOrder() on Grid in Vaadin Flow seems to cause the getSortOrder() list to increment infinitely in the event of the sort listener

I believe I have found a bug in the Vaadin Grid that can be replicated with the code below.
I would like to save the grid's sorting order whenever it's changed and then restore it when the grid is loaded. The main issue is that the GridSortOrder list just keeps incrementing in size for the event in the sort listener rather than replacing the sortorder list. I believe this is a bug within Vaadin 14-20 because it only happens if I call setColumnOrder() in the reset method below (removing that call it works as expected). In other words calling setColumnOrder() seems to add the list of GridSortOrder to the event in the sort listener when it should be replacing it. This is actually also true if you do grid.getSortOrder() in the listener, meaning it's not just the event but also the grid that is compromised.
This can be replicated consistently with the code below. You will first need to change the sort order of Description column by clicking on the column header. Then just click on Reset button 5-10 times. Once this is done click on the Description column header to change the sort order one more time. The System.out for the listener will show a size of a list of 20 GridSortOrder instead of the expected 2 GridSortOrder. There will be replications of columns over and over.
Grid<Car> grid = new Grid<>();
grid.addColumn(Car::getName)
.setSortable(true)
.setComparator(Car::getName)
.setKey("Name")
.setHeader("Name");
grid.addColumn(Car::getPrice)
.setSortable(true)
.setComparator(Car::getPrice)
.setKey("Price")
.setHeader("Price");
grid.addColumn(Car::getDescription)
.setSortable(true)
.setComparator(Car::getDescription)
.setKey("Description")
.setHeader("Description");
grid.setItems(listOfCars);
grid.setColumnReorderingAllowed(true);
grid.setMultiSort(true);
grid.addSortListener(event -> {
System.out.println("Event: " + event.getSortOrder().size() + " : " + event.isFromClient());
System.out.println("Grid: " + grid.getSortOrder().size() + " : " + event.isFromClient());
});
defaultSort(grid);
Button resetButton = new Button("Reset", click -> {
System.out.println("Reset: " + grid.getSortOrder().size());
defaultSort(grid);
grid.setColumnOrder(
grid.getColumnByKey("Name"),
grid.getColumnByKey("Price"),
grid.getColumnByKey("Description"));
});
add(grid, resetButton);
private void defaultSort(Grid grid) {
grid.sort(List.of(
new GridSortOrder<>(grid.getColumnByKey("Name"), SortDirection.ASCENDING),
new GridSortOrder<>(grid.getColumnByKey("Price"), SortDirection.ASCENDING)
));
}
If I remove grid.setColumnOrder() from the above then the List<GridSortOrder> works as expected and is two in the event listener. What's especially interesting is that event.getSortOrder().size() will increase by 2 every time the resetButton is clicked (but you'll only see it in the sort listener). It doesn't matter if the setColumnOrder() is before or after the call to defaultSort().
With that in mind if you debug grid.setColumnOrder() it seems to call updateClientSideSorterIndicators(sortOrder) which is what I believe is the cause and is where it's being added to the GridSortOrder list. That being said I'm not sure how to proceed from here so that I can reset the grid's setting when clicking on the Reset button.
FYI: This is an issue for me because I'm trying to save the state of the Grid to the database so that when the user reloads the screen (a very complex one) all their grid settings including the sorting order, the column order, and so on are preserved. However with this bug it's impossible to save the sorting order.
PS: This seems to be true in both Vaadin 14 and Vaadin 20.

How can I show a hidden column of a grid using the Vaadin Testbench?

I am doing some integration tests, and I have replaced some tables with a grid. At this moment, I have some visible columns by default and other columns are hidden as follows:
column6.setHidable(true);
column6.setHidden(true);
Now I am trying to do some integration tests. For getting the grid, I can use the method (is the only grid present in this view):
$(GridElement.class).first();
This works fine. But for my test (with Vaadin Testbench), I need to check some values that are inside the hidden columns of the grid. I am talking about this button:
I have tried to use the Vaadin debug console to get the name of the button that allows the user to show/hide columns, but the debug console only can select the entire grid element, not this menu.
Also I have check if inside the GridElement exists any kind of already implemented method that give me access to this menu without any success.
Usually, chrome developer tools (or similar for firefox and ie / edge, etc) is your best friend in such cases. So far I'm not aware of anything dedicated for that particular button. However you can workaround this limitation by selecting the items which compose this feature by their specific classes:
The below test method shows a quick implementation which should give you a starting point:
public class GridManipulationTest extends TestBenchTestCase {
#Before
public void setUp() throws Exception {
System.setProperty("webdriver.chrome.driver", "D:\\Kit\\chromedriver_win32\\chromedriver.exe");
setDriver(new ChromeDriver());
}
#After
public void tearDown() throws Exception {
// TODO uncomment below after checking all works as expected
//getDriver().quit();
}
#Test
public void shouldOpenGridColumnVisibilityPopupAndSelectItems() {
// class for the grid sidebar button
String sideBarButtonClass = "v-grid-sidebar-button";
// class for the sidebar content which gets created when the button is clicked
String sideBarContentClass = "v-grid-sidebar-content";
// xpath to select the item corresponding to the necessary column
// there are perhaps more "elegant" solutions, but this is what I came up with at the time
String columnMenuItemXpath = "//*[contains(#class, 'column-hiding-toggle')]/span/div[text()='Name']";
// open the browser
getDriver().get("http://localhost:8080/");
// get the first available grid
GridElement firstGrid = $(GridElement.class).first();
// look for the grid's sidebar button and click it
firstGrid.findElement((By.className(sideBarButtonClass))).click();
// the sidebar content is created outside the grid structure so don't look for it using the grid search context
WebElement sidebarContent = findElement(By.className(sideBarContentClass));
// look for the expected column name and click it
sidebarContent.findElement(By.xpath(columnMenuItemXpath)).click();
}
}
And of course what it looks like in action

FileDownloader and checkbox, download selected items

We've created solution where user has a table with files, each entry has checkbox. He can select as many as he like and then click download button.
We are using such resource, it should allow dynamically download, depending on selected items
private StreamResource createResource(final IndexedContainer container) {
return new StreamResource(new StreamSource() {
#Override
public InputStream getStream() {
for (Object o : container.getItemIds()) {
CheckBox checkbox = (CheckBox) container.getItem(o).getItemProperty(C_CHECK_BOX).getValue();
if (checkbox.getValue()) {
selectedFiles.add(o);
}
}
// do some magic to get stream of selected files
}
}, "download.zip");
}
The problem is that only second and following click on button is giving expected restults.
It's turns out that FileDownoader is getting resource from server and then it is sending current status of component . It is the reason why first click is giving stale result.
Do you have any idea how to overcome this? Is it possible to force: first update component and then download the resource?
Many thanks
Pawel
CheckBox in Vaadin is non-immediate by default, which means that it won't send a request to server when the checkbox is checked (or unchecked) on the browser. Immediate components send queued non-immediate events also to server but it seems that FileDownloader doesn't cause an event that would send non-immediate checkbox values to server.
The only thing you need to do is to set your checkboxes to be immediate when you create those:
checkBox.setImmediate(true);
FileDownloader will not suit your needs. As you can read in the documentation:
Download should be started directly when the user clicks e.g. a Button without going through a server-side click listener to avoid triggering security warnings in some browsers.
That means you cannot dynamically generate download.zip file determined by checkboxes values because that requires a trip to server.
You have at least 2 options. Either create new FileDownloader and generate new Resource download.zip every time user make changes to the checkboxes. Or you can add simple ClickListener to you Button with this line of code:
getUI().getPage().open(resource, "_blank", false);
Related: Vaadin - How to open BrowserWindowOpener from a SINGLE BUTTON
There is also alternative solution to set checkBox.setImmediate(true); . It is possible to send current state of all components, just before click, instead of sending each checkBox change.
This solution is based on this answer: https://stackoverflow.com/a/30643199/1344546
You need to create file downloader button and hide it:
Button hiddenButton = new Button();
hiddenButton.setId(HIDDEN_ID);
hiddenButton.addStyleName("InvisibleButton");
StreamResource zipResource = createResource(container);
FileDownloader fd = new FileDownloader(zipResource);
fd.extend(hiddenButton);
Add css rule to your theme
.InvisibleButton {
display: none;
}
And then create another button, which 1st update state, and then click hidden button.
Button zipDownload = new Button("Download as ZIP file");
zipDownload.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
Page.getCurrent().getJavaScript()
.execute(String.format("document.getElementById('%s').click();", HIDDEN_ID));
}
});

Getting selected ListBox values on button Click | ZK

I am very new to ZK framework and trying to customize few things and have struck at one point which I am not sure how to achieve that.
I have a predefined section where I need to show 2 drop down and a button and need to persist those drop down values on button click event.
This is how It has been define in Spring file
<bean id="mybean" parent="parentBean" class="WidgetRenderer">
<property name="detailRenderer">
<bean class="DetailsListRenderer" parent="abstractWidgetDetailRenderer"/>
</property>
</bean>
Here mybean is being used to show main section and I am adding my drop down using this bean while button are being added to detailRenderer.
Save button is bind to onClick event, but I am not sure how I can fetch values from my custom drop down?
I am aware about binding those Dropdown with onClick event but they have to be in same class.
Can any one suggest me how I can fetch values of those drop down.I am creating down down with following code
Listbox listbox = new Listbox();
listbox.appendItem("item1", "item1");
listbox.appendItem("item2", "item2");
This is my button code in another class
protected void createUpdateStatusButton(Widget widget,Div container)
{
Button button = new Button(LabelUtils.getLabel(widget, buttonLabelName, new Object[0]));
button.setParent(container);
button.addEventListener("onClick", new EventListener()
{
public void onEvent(Event event)throws Exception
{
MyClass.this.handleSaveStatusEvent(widget, event);
}
});
}
You may want to listen to the onSelect (I prefer to use Events.ON_SELECT rather than writing the strings) which is more specific to when the Listbox selection changes.
Either way, the key is to get the information you want from the Event passed to your EventListener, rather than going back to your Listbox itself. The basic Event usually carries useful information on getTarget and getData but using more specific events (SelectEvent in this case) will give you access to more relevant info.
button.addEventListener(Events.ON_SELECT, new EventListener<SelectEvent<Listitem, MyDataObject>() {
public void onEvent(SelectEvent<Listitem, MyDataObject> event) {
// Now you can access the details of the selection event..
List<Listitem> selectedItems = event.getSelectedItems();
List<MyDataObject> selectedObjects = event.getSelectedObjects();
}
});
You can find out what events are available for different ZK widgets in their Component Reference documentation.
If I understand the question (I don't think I did in my previous response) you want to gather information from the page (eg: Listbox selection state) when the user clicks a button. Your problem being that you are using disparate classes to compose the page and so don't have access to the various ZK Components when the button is clicked.
(Ignoring the multiple class issue for a minute)
From a high level, there are sort of two camps in the ZK community. The newer MVVM approach suggests the view should push the relevant state to the back end as the user interacts with the front end. This way, the back end never needs to ask for the client state, and when the button is clicked, the values/state are on the server ready to be used.
The other camp binds the client to the server such that the back end always has access to the client Components and when the button is clicked, the values/state can easily be retrieved by interacting with the components.
Another approach is more like what I was talking about in my previous answer, to not bind the back end to the client at all but to rely on event data as much as possible. I favor this approach where it is sufficient.
Now, you're free to choose your favored approach and ZK has lots of documentation on how to work in either of these camps. The question then is where is the client state stored on the server (either pushed there by the client in MVVM or bound there in MVC). I don't think that's a question that can be solved here, that's a software engineering challenge. I personally suggest you take on standard ZK patterns so as not to but heads with the framework. If you really want to go your route, you can grab a reference to the Listbox on the fly like so:
public class Foo {
public static final String LISTBOX_ID = "myListbox";
public void renderListbox(Component parent, MyItem items) {
Listbox listbox = new Listbox();
listbox.setId(LISTBOX_ID);
listbox.setParent(parent);
for (MyItem item : items) {
listbox.appendItem(item.getName(), item);
}
}
}
public class Bar {
#Listen(Events.ON_CLICK + " = #saveButton")
public void saveButtonClicked(Event event) {
Component saveButton = event.getTarget();
Listbox listbox = (Listbox) saveButton.getFellow(Foo.LISTBOX_ID);
Set<Listitem> selection = listbox.getSelectedItems();
// do something
}

Resources