How to make this JavaFX binding smarter? - binding

I have 3 ToggleButtons and a GridPane containing 3 cells.
Case 1:
Only 1 ToggleButton is selected, corresponding GridPane cell is expected to fill whole pane.
Case 2:
2 of the ToggleButtons are selected, corresponding 2 GridPane cells is expected to fill whole pane with equally. (And the same logic when 3 ToggleButtons are selected of course.)
Currently, I am already achieved my goal with the following implementation but what I want to learn is make this smarter. With the word smarter, I mean implementing something shorter that transforms ToggleButton state to a double value (deselected => 0 , selected=> 100). I don't want to implement those by repeating if clauses, but I am trying to implement something that can be attached before .bind() method call.
Current implementation for one ToggleButton is as following:
myToggleButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(newValue){
myGridPane.getColumnConstraints().get(0).setPercentWidth(100);
}else{
myGridPane.getColumnConstraints().get(0).setPercentWidth(0);
}
}
});

Yes you can, try the following for binding PercentWidth
ObservableBooleanValue condition = myToggleButton.selectedProperty();
NumberBinding number = Bindings.when(condition).then(100).otherwise(0);
myGridPane.getColumnConstraints().get(0).percentWidthProperty.bind(number);

Related

Vaadin Dataprovider: how to avoid "auto-fetch"?

Use Case 1 is answered below, Use Case 2 has been moved to a separate question (Vaadin Flow: Returning to a view, the view should not reload data from the backend)
I'd like to use a Vaadin Flow (v14 LTS/v19) grid component backed by a lazy DataProvider which does not automatically fetch data from the backend when the grid is shown.
There are at least two use cases:
showing grid data does not make sense unless the user provided filter parameters
returning to a #PreserveOnRefresh tagged view should not replace the shown data with current data. (further elaborated in update)
Being pretty new to Vaadin 14+, I could not figure out how to achieve this. Every time my GridView is displayed, the count and fetch callbacks of DataProvider are queried. The call originates from the DataCommunicator of the grid.
So for Use Case 1: How to stop the DataProvider from fetching data as long as it does not make sense?
And for Use Case 2: How to prevent overwriting the grid state when adding a grid to the UI for the second time?
Thanks a lot!
StackTrace to my fetch callback (Vaadin Flow 14):
at org.vaadin.example.GridView.fetch(GridView.java:46)
at org.vaadin.example.GridView.lambda$new$c4b2c115$1(GridView.java:23)
at com.vaadin.flow.data.provider.CallbackDataProvider.fetchFromBackEnd(CallbackDataProvider.java:137)
at com.vaadin.flow.data.provider.AbstractBackEndDataProvider.fetch(AbstractBackEndDataProvider.java:61)
at com.vaadin.flow.data.provider.DataCommunicator.fetchFromProvider(DataCommunicator.java:362)
at com.vaadin.flow.data.provider.DataCommunicator.activate(DataCommunicator.java:647)
at com.vaadin.flow.data.provider.DataCommunicator.collectKeysToFlush(DataCommunicator.java:589)
at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:461)
at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425)
at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$2(StateTree.java:390)
at [java.util.stream] omitted
at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:387)
at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411)
at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187)
at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:122)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:91)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1547)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
update 20210430
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
// filter omitted
final Button refresh = new Button("refresh");
refresh.addClickListener(e -> {
System.out.println("refresh clicked");
g.getDataProvider().refreshAll();
});
add(refresh);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
new Exception().printStackTrace(); //figure out who called
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
My MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
MainView is an AppLayout used to switch the contents of the AppLayout from GridView to EmptyView and back.
Use Case 2 is: When returning to GridView, the GridView should be exactly same state as before (which works fine with the TextField).
open GridView -> grid should not be filled with data
enter filter params (not shown in code)
click "refresh" to populate the grid
enter "Spiderman" in TextField "stateCheck"
switch to EmptyView
in the real app: do something in EmptyView and potentially other views
return to GridView -> the grid should not reload the data, it should just stay as it was - just like the TextField still displays "Spiderman", the grid should display the same data as before without reloading it.
For Case 1: In the callback check if you have filter parameters, return an empty set if not. Using the new V17+ API it would look like this:
grid.setItems(query -> {
if(filterParameters.isEmpty()) {
// Return an empty stream
} else {
// Fetch from backend
}
});
You can read more in the docs here: https://vaadin.com/docs/latest/flow/binding-data/data-provider (V19) or https://vaadin.com/docs/v14/flow/binding-data/tutorial-flow-data-provider (V14)
I would need more info on what you're currently doing to help out with Case 2. How are you constructing the view, what does your code look like? A full stack trace with the "Caused by" would also help.
I would recommend only setting the DataProvider to the Grid once the first filter parameter is set. The client-side Grid expects to receive the number of items it requires from the fetch query; it might work in some corner case if you don't provide the requested numbers of items from fetch, but it's not designed to behave like that.
Note that this applies specifically to using DataProviders with filters in Vaadin 14 series - Vaadin 17 introduced a new optional simplified way of fetching items, which changes this equation a bit. It's not backported to Vaadin 14 yet (currently planned for 14.7).

Vaadin grid - change component column in one row only

I have a grid with several columns. For three columns i used the column renderer. Each of the columns contains one button.
If i click one of those buttons, i want to replace the three buttons in that specific row with two other buttons. All the other rows should not be affected. Is this possible in a Vaadin grid?
Components in different columns don't know each other, as they are all defined in a separate scope (in the componentRenderer of their own column. You cannot define the Button outside of the componentRenderer as you found out in another question today). So the "obvious" solution won't work, where you add a clickListener on the Button to directly change the other buttons.
If you had one column with 3 Buttons inside then this would be much easier.
There is a way, but I see this more as a hack than as a solution. Because you need some extra implementation in the item class for this to work.
In the ComponentRenderer, you can add an if-statement where you look at some value of the item. In one case, you'll render button 1, in the other case you'll render the other button. And in the click listeners of the button you change that value in the item and refresh the dataprovider, so the componentRenderer is invoked again. It will now see the value on the item has changed, therefore displaying some other Button.
Here is some code to show what I mean:
// grid item class
public class Foo {
private boolean buttonPressed = false;
public Foo(){
}
public isButtonPressed(){
return buttonPressed;
}
public setButtonPressed(boolean buttonPressed){
this.buttonPressed = buttonPressed;
}
}
// adding of button columns
// do this 3 times for a test of your scenario.
grid.addComponentColumn(item -> {
if(!item.isButtonPressed()){
return new Button("Before Button was Pressed", click -> {
item.setButtonPressed(true);
grid.getDataProvider().refresh(item);
});
} else {
return new Button("Button was Pressed", click -> {
item.setButtonPressed(false);
grid.getDataProvider().refresh(item);
})
}
})

Swiping to a element not present in dom in appium ios

I have an app where a particular parent that has n elements lets say 10 elements (tiles) and only 5 are present in the dom and visible in the current screen. The next 5 would appear in dom/screen on swiping up. How do I swipe to a particular element say 7th element?
I'm not able to get find the element by id/name as the element is not in dom so I'm not able to use swipe until visibility of an element. also, the number of such elements varies based on how the backend system is configured so I cannot make a fixed number of swipes. Also since I do not know the last element in the parent I'm at risk of running into an infinite loop if the particular element is not present in the app (due to a bug).
Please help on how to solve this.
You can use following methods to scroll down until expected element. To avoid the infinite loop you can keep count of while loop.
public void scrollDown()
{
TouchAction action = new TouchAction((AppiumDriver)driver);
action.press(new PointOption().point(300, 701)).waitAction(newWaitOptions().waitOptions(Duration.ofSeconds(2))).moveTo(new PointOption().point(300, 441)).release();
action.perform();
}
public void scrollTillElement(By element)
{
int countofLoop = 0;
List<WebElement> elements = driver.findElements(element);
while (elements.size()==0)
{
countofLoop++;
scrollDown();
elements = driver.findElements(element);
if(countofLoop==20)
{
break;
}
}
}

Customize SmartGwt ListGrid dynamically for passwords

I have a com.smartgwt.client.widgets.grid.ListGrid for my configurations screen.
I have 3 ListGridFields name, value, isHidden.
I want to use PasswordItem if isHidden is true, and TextItem if isidden is false.
How can I customize the grid?
I tried with setEditorCustomizer, but it only works when I am editing a cell. In view mode I am able to see the text.
I don't think there's a way to do what you want (show the PasswordItem editor when visualizing the ListGrid's fields). As you already found out, setEditorCustomizer works only when in editing mode.
But you can mask the field values. Here is how to do it:
// very important for not having to set all fields all over again
// when the target field is customized
listGrid.setUseAllDataSourceFields(true);
// customize the isHidden field to make it respond to changes and
// hide/show the password field accordingly
ListGridField isHidden = new ListGridField("isHiddenFieldName");
isHidden.addChangedHandler(new ChangedHandler() {
#Override
public void onChanged(ChangedEvent event) {
// the name of this field has to match the name of the field you
// want to hide (as defined in your data source descriptor,
// ListGridField definition, etc).
ListGridField passwordField = new ListGridField("passwordFieldName");
if ((Boolean) event.getValue() == true) {
passwordField.setCellFormatter(new CellFormatter() {
#Override
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
return ((String) value).replaceAll(".", "*");
}
});
}
// you need to re-add here the isHidden field for the ChangeHandler to
// be present when recreating the ListGrid
listGrid.setFields(isHidden, passwordField);
listGrid.markForRedraw();
}
});
// add the customized field to the listGrid, so that we can have the
// desired ChangeHandler for the isHidden field
listGrid.setFields(isHidden);
Bear in mind that if you hide the value (or use a PassowrdItem), an 'expert' user could see the value, simply because the server is sending the value to the client.
If you actually have a security constraint, you may use DataSourceField.viewRequires, which accepts velocity expressions.

Vaadin addStyleName problem

I created a TextField with TextChangeListener. When user types in certain values (in this case 'admin') then addStyleName is invoked on that field and font color becomes red. But afterwards, the value is blank and each entered character is being cleared.
Here is the code of the application. Why after adding new style to TextField its value changes?
public class VaadintestApplication extends Application {
#Override
public void init() {
Window mainWindow = new Window("Vaadintest Application");
setTheme("test");
TextField textField = new TextField("username");
textField.setEnabled(true);
textField.setTextChangeEventMode(TextChangeEventMode.EAGER);
textField.addListener(new TextChangeListener() {
public void textChange(TextChangeEvent event) {
if ("admin".equals(event.getText())) {
((TextField) event.getComponent()).addStyleName("text-error");
} else {
((TextField) event.getComponent()).removeStyleName("text-error");
}
}
});
mainWindow.addComponent(textField);
setMainWindow(mainWindow);
}
}
I would guess that the following happens:
The style name change triggers a repaint on the server, causing the TextField component to be serialized again to the client
The client receives the serialization (the whole bloody thing, not just the changed parts, because that's how things work with Vaadin), and hence it changes the contents of the textfield, while ignoring any changes that are pending from the text change listener
Solutions:
Update the value of the TextField at the same time you add/remove the style name: ((TextField) event.getComponent()).setValue(event.getText())
Create a custom client side widget which extends VTextField and add the functionality there

Resources