I have a Wicket 7 CheckBox and a hidden DateTextField.
When I click on a CheckBox I want the DateTextField to be appeared and vice versa.
For this reason I have added the DateTextField in a WebMarkUpContainer.
If possible I dont want to use Ajax.
The problem is that the WebMarkUpContainer is always hidden.
In general my code is as follows:
class ResultsPanel extends Panel{
private static final class ResultsPage {
final DateTextField startDate = new DateTextField("startDate", new DateTextFieldConfig().withLanguage("el");
final CheckBox checkBox = new CheckBox("checkBox");
final WebMarkupContainer wmc = new WebMarkupContainer("wmc");
// bla bla bla
public Results(String id, CompoundPropertyModel propertyModel) {
super(id, propertyModel);
add(checkBox);
wmc.setOutputMarkupPlaceholderTag(true);
wmc.add(startDate);
add(wmc.setVisible(false));
}
public ResultsPanel(String id){
super(id);
add(new ResultsPage("resultsPage", new CompoundPropertyModel()));
}
}
if you don't need to trigger any server side code when you click on the checkbox, you can consider to use some simple JavaScript code to hide/show the DateTextField. This code can be attached to the checkbox using a JavaScriptHeaderItem. You can find more details in the user guide.
update
In Wicket 7.x You can try
new CheckBox("id", model) {
protected boolean wantOnSelectionChangedNotifications() {
return true;
}
protected void onSelectionChanged(Boolean newSelection) {
// do something, page will be rerendered;
} };
Related
I have a UI bug in a legacy code in our Java project. We display a table, with three columns (HumanReadable, name and value) in a window. In that window, users can click on each cell and update the values. Before that, user clicks the "add" button to add a new row (three new cells). Each cell has a default value, until the user decides to update the value. Now, when the users decides to update the value of the cell, he clicks on the cell and types in the value. The bug is that, once done editing, it keeps the default value in the UI. In the backend, the value has changed (if you click the cell again, it will go into editing mode and show you the value).
I uploaded a short GIF that shows the issue and can be found here.
In that GIF you can see that I updated the default value of the first column to be test. Then I click some other place (to exit the edit mode) and it showed the default value instead of test in the first column.
The method that creates the table:
private void createTable(final Composite parent) {
final Table varTable = new Table(parent, SWT.MULTI);
varTable.setHeaderVisible(true);
varTable.setLinesVisible(true);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(varTable);
varTableViewer = new TableViewer(varTable);
final DataBindingContext bindingContext = new DataBindingContext();
final TableViewerColumn col1 = GuiUtils.createTableColumn(varTableViewer, "Human Readable");
col1.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, dataProperty));
col1.getColumn().setWidth(120);
final TableViewerColumn col2 = GuiUtils.createTableColumn(varTableViewer, "Name");
col2.getColumn().setWidth(120);
col2.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, nameProperty));
final TableViewerColumn col3 = GuiUtils.createTableColumn(varTableViewer, "Value");
col3.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, valueProperty));
KeyBoardNavigationSupport.createSupport(varTableViewer);
input = new WritableList(globalVars, FlowVar.class);
ViewerSupport.bind(varTableViewer, input, BeanProperties.values(new String[] { dataProperty, nameProperty, valueProperty }));
}
The StringEditingSupport class:
public class StringEditingSupport extends ObservableValueEditingSupport {
private class CellEditorPrintValidatorErrors extends TextCellEditor {
public CellEditorPrintValidatorErrors(Composite control) {
super(control);
}
#Override
protected void focusLost(){
if(this.getErrorMessage() != null) {
MessageDialog.openError(this.getControl().getShell(), "Invalid input", this.getErrorMessage());
}
}
}
private final CellEditor cellEditor;
String propertyName;
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName) {
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.propertyName = propertyName;
}
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName, final ICellEditorValidator validator) {
super(viewer, dbc);
cellEditor = new CellEditorPrintValidatorErrors((Composite) viewer.getControl());
cellEditor.setValidator(validator);
this.propertyName = propertyName;
}
#Override
protected IObservableValue doCreateCellEditorObservable(final CellEditor cellEditor) {
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(final Object element, final ViewerCell cell) {
return BeansObservables.observeValue(element, propertyName);
}
#Override
protected CellEditor getCellEditor(final Object element) {
return cellEditor;
}
public String getErrorMessage(){
return cellEditor.getErrorMessage();
}
}
I believe it has something to do with the StringEditingSupport class. This class allows to edit the value in each cell of table. But I couldn't figure out a way to "update" the value shown in the GUI. As I understand input (of type WritableList) contains all the information. Here is the add button listener method:
private class AddButtonSelectionListener extends SelectionAdapter {
#Override
public void widgetSelected(final SelectionEvent e) {
String name = nameProperty;
String meaning = dataProperty;
final List<String> names = new ArrayList<String>();
final List<String> meanings = new ArrayList<String>();
for (final Object var : input) {
names.add(((FlowVar) var).getName());
meanings.add(((FlowVar) var).getData());
}
int index = 0;
while (names.contains(name)) {
name = nameProperty + ++index;
}
index = 0;
while (meanings.contains(meaning)) {
meaning = dataProperty + ++index;
}
input.add(new FlowVar(name, valueProperty, meaning));
}
}
So, as I understand, I need to somehow bind the input to the UI (the content of each cell). I did try many attempts like trying to set a listener to the whole table (varTableViewer.addSelectionChangedListener) but none of them worked. Is it possible to suggest a way to solve this kind of issue?
If anything is missing, please let me know and I'll add it.
I have two ComboBoxes in my editable Grid where the second ComboBox based on the first one. So for example you would have Car Make and then Car Model. When you change the make combobox the model combobox then changes accordingly.
With that in mind I have:
ComboBox<String> makeComboBox = new ComboBox<String>();
ComboBox<String> modelComboBox = new ComboBox<String>();
Specifically:
grid.addColumn(CarRental::getPerson)
.setEditorBinding(binder.forField(personComboxBox).bind(CarRental::getPerson, CarRental::setPerson));
grid.addColumn(CarRental::getMake)
.setEditorBinding(binder.forField(makeComboxBox).bind(CarRental::getMake, CarRental::setMake));
grid.addColumn(CarRental::getModel)
.setEditorBinding(binder.forField(modelComboxBox).bind(CarRental::getModel, CarRental::setModel));
The key here is that I want the modelComboBox to change if the makeComboBox is changed. In other words if you select Honda then I want the model ComboBox to change to Fit, Civic, Accord, and so on. To do this I add a SelectionListener (but it could also be a ValueChangeListener, it doesn't matter, the effect is still the same).
Specifcally I have:
makeComboBox.addSelectionListener(event ->
{
modelComboBox.clear();
modelComboBox.setItems(getModelsBasedOnMake(makeComboBox.getValue()));
// Assuming someone has just edited the make value,
// say changed from Toyota to Honda, then I want the model selected to be empty
});
Because the ComboBox can be different I've added some logic to update the components on theOpenListenerfor the Grid Editor. Specifically I do:
grid.getEditor().addOpenListener(open ->
{
...
CarRental selectedCarRental = (CarRental)event.getBean();
makeComboBox.setItems(makeList);
modelComboBox.setItems(getModelsBasedOnMake(carRental.getMake()));
});
The problem here is that the modelComBoxbox tends to be unselected because if you look at it there's no guarantee which value it will be because there is a conflict.
I looked at temporarily disabling the selectionListener but this all the remove listeners have been deprecated with Vaadin 8. Therefore how can I setup the grid to be able to edit both the car make and model in the grid?
I tried it by a simple example. Looks ok for me. What is the exact problem? (I don't really get your sentence "because if you look at it there's no guarantee which value it will be because there is a conflict.")
#SpringUI
public class VaadinUI extends UI {
#Override
protected void init(VaadinRequest request) {
VerticalLayout layout = new VerticalLayout();
ComboBox<String> cmb1 = new ComboBox<>();
ComboBox<String> cmb2 = new ComboBox<>();
cmb1.setItems("1", "2", "3");
cmb1.addSelectionListener(event -> {
cmb2.clear();
cmb2.setItems(getCmb2Content(event.getValue()));
});
Grid<MyBean> grid = new Grid<>();
grid.setWidth("800px");
grid.setHeightByRows(10);
grid.addColumn(System::identityHashCode).setCaption("ID");
grid.addColumn(MyBean::getProp1).setCaption("Prop 1")
.setEditorBinding(grid.getEditor().getBinder().forField(cmb1).bind(MyBean::getProp1, MyBean::setProp1));
grid.addColumn(MyBean::getProp2).setCaption("Prop 2")
.setEditorBinding(grid.getEditor().getBinder().forField(cmb2).bind(MyBean::getProp2, MyBean::setProp2));
grid.setItems(new MyBean(), new MyBean(), new MyBean());
grid.getEditor().setEnabled(true);
layout.addComponent(grid);
setContent(layout);
}
private List<String> getCmb2Content(String cmb1Content) {
return Arrays.asList(cmb1Content + "1", cmb1Content + "2", cmb1Content + "3");
}
}
public static class MyBean {
private String prop1;
private String prop2;
public String getProp1() {
return prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
public String getProp2() {
return prop2;
}
public void setProp2(String prop2) {
this.prop2 = prop2;
}
}
I have the following problem with Wicket 7.3 and JQuery 2.1.4:
In a dynamic tabbed panel (tabs are added and removed), I want to localize the tab titles and add tooltips. My code
JQueryGenericPanel() {
....
populateItem( ListItem<ITab> item) {
getString();
results in a warning in the log file:
Tried to retrieve a localized string for a component that has not yet been added to the page. This can sometimes lead to an invalid or no localized resource returned. Make sure you are not calling Component#getString() inside your Component's constructor
Using getString() in the panel (which is on the tab) within its method
onInitialize()
does not work, because its too late. The label is already set to "lazy".
Is there any other method similar to "populateItem()" which I can use?
** Addendum **
The code for the tabbed panel is:
public class MyTabbedPanel extends JQueryGenericPanel<List<ITab>> implements ITabsListener {
...
#Override
protected void onInitialize() {
super.onInitialize();
this.add( new ListView<ITab>( "tabs", this.getModel() ) {
...
#Override
protected void populateItem( ListItem<ITab> item ) {
Label link = new Label( "widgetId", new PropertyModel<String>( somePanel, "getTitle()" ) );
The code in the panel is:
private String title = "default";
public String getTitle() { return title; }
#Override
public void onInitialize() {
title = getString( "someKey" );
}
So the PropertyModel fetches the title with 'getTitle()'. Unfortunately this happens before 'onInitialize()' gets called. So the tab title shows "default" instead of the localized text for "someKey".
Instead of
new Label(itemId, getString("key"))
... use:
new Label(itemId, new ResourceModel("key"));
... or if you doing something fancy with the string:
new Label(itemId, new AbstractReadOnlyModel<String>() {
public String getObject() {
return ... + getString("key") + ...;
}
});
When I add components to Vaadin's component (such as TabSheet or Tree) , the added components are cached. When user clicks the tab (or tree nodes) , if it contains db data , it shows stale data , not reflecting the latest db state.
I wonder if there is any way to ensure loading latest data ?
I solve the problem by defining my custom interface :
public interface Reloadable {
void reload();
}
And each component implements this Reloadable interface , such as :
#SpringComponent
public class TeachersView extends VerticalLayout implements Reloadable, Serializable {
#Inject
private TeacherDao teacherDao;
private final static int PAGESIZE = 10;
private MTable<Teacher> mTable = new MTable<>(Teacher.class);
#PostConstruct
public void init() {
// mTable settings skip here
reload();
addComponent(mTable);
}
#Override
public void reload() {
mTable.setBeans(new SortableLazyList<>(
sortablePagingProvider ,
() -> (int) teacherDao.count() ,
PAGESIZE
));
}
private SortableLazyList.SortablePagingProvider<Teacher> sortablePagingProvider =
(firstRow, asc, sortProperty) -> {
return teacherDao.findAll(
new PageRequest(
firstRow / PAGESIZE, PAGESIZE,
asc ? Sort.Direction.ASC : Sort.Direction.DESC,
sortProperty == null ? "id" : sortProperty
)
).getContent();
};
}
And this view is injected to UI class :
#SpringUI(path = "/ui")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
private TeacherDao teacherDao;
#Inject
private TeachersView teachersView;
#Override
protected void init(VaadinRequest vaadinRequest) {
Panel panel = new Panel("Admin Panel");
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
splitPanel.setSplitPosition(15, Unit.PERCENTAGE);
panel.setContent(splitPanel);
Tree tree = new Tree("Menu");
splitPanel.setFirstComponent(tree);
Label home = new Label("Home");
Map<String, Component> map = new HashMap<>();
map.put("Teachers", teachersView);
map.put("Home", home);
map.forEach((k, v) -> tree.addItem(k));
tree.addItemClickListener(event -> {
Component view = map.get(event.getItemId());
if (view instanceof Reloadable) {
((Reloadable) view).reload();
}
splitPanel.setSecondComponent(view);
});
splitPanel.setSecondComponent(home);
setContent(panel);
} // init()
}
Notice the tree.addItemClickListener , I have to check each component if it implements Reloadable , if true , invoke it.
It works . But I don't know if it the standard way achieving this ? I think it should be a common scenario , there should be something like built-in interface for Components to implement , such as onRender like that (but I cannot find one) . Did I miss anything ?
Thanks.
First of all I'm going to suggest this tutorial on Spring & Vaadin that you may have already seen, but I'll be referencing it in a few places and I think it's a good starting point for Vaadin & Spring integration.
Second, out of curiosity, why are you using a tree to build the menu?
In the example provided you seem to be modelling a navigation between some views feature, which is already available in Vaadin, and since you're using Spring, the Vaadin spring & spring-boot extensions makes it really easy to define and navigate between your views. Then you can define some specific behaviour for each view in their own enter() method. I've used the Vaadin dashboard demo as inspiration for the changes below:
#SpringView(name = TeachersView.NAME)
public class TeachersView extends VerticalLayout implements View {
public static final String NAME = "Teachers";
private Label title = new Label("Teachers view");
#PostConstruct
void init() {
addComponent(title);
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// recreate or reload stuff here
title.setValue("Teachers view reloaded # " + new Date());
}
}
#SpringView(name = HomeView.NAME)
public class HomeView extends VerticalLayout implements View {
public static final String NAME = "";
#PostConstruct
void init() {
addComponent(new Label("Home"));
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// meh, nothing special to do here
}
}
public class SpringVaadinUI extends UI {
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest vaadinRequest) {
addStyleName(ValoTheme.UI_WITH_MENU);
Panel panel = new Panel("Admin Panel");
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
splitPanel.setSplitPosition(15, Unit.PERCENTAGE);
panel.setContent(splitPanel);
VerticalLayout navigationBar = new VerticalLayout();
navigationBar.setPrimaryStyleName(ValoTheme.MENU_ROOT);
navigationBar.addComponent(createNavigationButton("Home", FontAwesome.HOME, HomeView.NAME));
navigationBar.addComponent(createNavigationButton("Teachers", FontAwesome.GROUP, TeachersView.NAME));
splitPanel.setFirstComponent(navigationBar);
CssLayout navigationDisplay = new CssLayout();
splitPanel.setSecondComponent(navigationDisplay);
Navigator navigator = new Navigator(this, navigationDisplay);
navigator.addProvider(viewProvider);
setContent(panel);
}
private Button createNavigationButton(String caption, FontAwesome icon, final String viewName) {
Button button = new Button(caption, icon);
button.setPrimaryStyleName(ValoTheme.MENU_ITEM);
button.addStyleName(ValoTheme.BUTTON_SMALL);
button.addStyleName(ValoTheme.BUTTON_BORDERLESS);
button.addClickListener(event -> getUI().getNavigator().navigateTo(viewName));
return button;
}
}
The result is similar to:
If for some reason you can't or don't want to use the navigator, then your solution looks fine. Nonetheless, whichever solution you chose to use, you should know that by default Spring creates singletons. Except a few such as the UI, you should probably change your components to prototypes so you'll get a new instance each time. Otherwise all your users will get the same instances when accessing the application, which I don't think you want to happen.
I am trying to use TextField in javafx.
The scenario: I have list view populated with specific objects and edit button to edit the object associated with list cell of list view.
When I click on edit button it redirects me to a pane with editing feature where I can edit the name of that object and save it using a save button.
So I have to put validation on save button to make it enable and disable.
If I edit the name in text field then it should enable the save button otherwise it should remains disabled.
I have tried using different methods on text fields as below.
textField.textPorperty.addListener(listener -> {
//Logic to enable disable save button
});
As I am using list view, this listener gives me old value as previously edited object which does not satisfy my condition.
I can not use
textField.focusedProperty().addListener((observableValue, oldValue, newValue) -> {});
as It does not give me expected behavior.
Can anyone help me to solve this issue?
You need to implement additional logic that decides whether or not a change to the textProperty should change the enablement state of the button. This requires:
a reference to the initial value (on setting the text to the input, f.i. on changes to selection in the list)
a boolean property that keeps the enablement state (below it's called buffering)
a listener to the textField that updates the enablement state as needed
Below is a very simplified example - just to get you started - that extracts those basics into a dedicated class named BufferedTextInput. Buffering is changed internally on:
set to false if the "subject" value is set or a change is committed/discarded
set to true once on being notified on the first change of the textField
More complex logic (like not buffering on detecting a change back to the original value) can be implemented as needed.
/**
* Bind disable property of commit/cancel button to actual change.
* http://stackoverflow.com/q/29935643/203657
*/
public class ManualBufferingDemo extends Application {
private Parent getContent() {
ObservableList<Person> persons = FXCollections.observableList(Person.persons(),
person -> new Observable[] {person.lastNameProperty()});
ListView<Person> listView = new ListView<>(persons);
TextField lastName = new TextField();
Consumer<String> committer = text -> System.out.println("committing: " + text);
BufferedTextInput buffer = new BufferedTextInput(lastName, committer);
Button save = new Button("Save");
save.setOnAction(e -> {
buffer.commit();
});
save.disableProperty().bind(Bindings.not(buffer.bufferingProperty()));
Button cancel = new Button("Cancel");
cancel.setOnAction(e -> {
buffer.flush();
});
listView.getSelectionModel().selectedItemProperty().addListener((source, old, current) -> {
buffer.setSubject(current.lastNameProperty());
});
cancel.disableProperty().bind(Bindings.not(buffer.bufferingProperty()));
VBox content = new VBox(listView, lastName, save, cancel);
return content;
}
public static class BufferedTextInput {
private ReadOnlyBooleanWrapper buffering;
private StringProperty value;
private TextField input;
private Consumer<String> committer;
public BufferedTextInput(TextField input, Consumer<String> committer) {
buffering = new ReadOnlyBooleanWrapper(this, "buffering", false);
value = new SimpleStringProperty(this, "");
this.input = input;
this.committer = committer;
input.textProperty().addListener((source, old, current) -> {
updateState(old, current);
});
input.setOnAction(e -> commit());
}
private void updateState(String old, String current) {
if (isBuffering()) return;
if (value.get().equals(current)) return;
setBuffering(true);
}
public void setSubject(StringProperty value) {
this.value = value;
input.setText(value.get());
setBuffering(false);
}
public void commit() {
committer.accept(input.getText());
this.value.set(input.getText());
setBuffering(false);
}
public void flush() {
input.setText(value.get());
setBuffering(false);
}
public boolean isBuffering() {
return buffering.get();
}
public ReadOnlyBooleanProperty bufferingProperty() {
return buffering.getReadOnlyProperty();
}
private void setBuffering(boolean buffer) {
buffering.set(buffer);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For production use, such direct coupling between view and model (f.i. when needing the buffering for a complete form) isn't good enough, further separation might be needed. See BufferedObjectProperty and its usage in a FX adaption of the infamous AlbumManager example (very crude)