I have a page that has a list of Hibernate entities that is loaded dynamically on display of the page. The list is used to create a DataView, which is used to display a paginated list of the entries in the list in a container. Each entry in the list has a delete icon on it. When the delete icon is pressed, I lazy delete the entry, reload the list with entities (which will no longer contain the lazy deleted entry), and reload the container, but the entry is still there in the container until I reload the whole page. Why?
public class LogPage extends ProjectPage{
#SpringBean
private LogDao logDao;
#SpringBean
private LogEntryDao logEntryDao;
private List<LogEntry> logEntryList;
private DataView<LogEntry> dataView;
private WebMarkupContainer logEntryListContainer;
public LogPage(PageParameters pp) {
super(pp);
Project activeProject = SciProSession.get().getActiveProject();
Log log = null;
if (activeProject.getLog()==null){
log = new Log(activeProject);
log = logDao.save(log);
}else{
log = activeProject.getLog();
}
logEntryList = logEntryDao.findAll();
Collections.sort(logEntryList);
logEntryListContainer = new WebMarkupContainer("logEntryListContainer");
logEntryListContainer.setOutputMarkupId(true);
dataView = (DataView<LogEntry>) new DataView<LogEntry>("logEntryDataView", new ListDataProvider(logEntryList)) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(final Item<LogEntry> item) {
final LogEntry logEntry = item.getModelObject();
item.add(new Label("contents", logEntry.getContents()));
item.add(new Label("creator", logEntry.getCreator().toString()));
AjaxActionIcon deleteIcon = new AjaxActionIcon("deleteIcon", ImageIcon.ICON_DELETE){
private static final long serialVersionUID = 1L;
#Override
protected void onClick(AjaxRequestTarget target) {
LogEntry toBeRemoved = logEntryDao.reLoad(logEntry);
toBeRemoved.setDeleted(true);
logEntryDao.save(toBeRemoved);
logEntryList = logEntryDao.findAll();
target.addComponent(logEntryListContainer);
}
};
item.add(deleteIcon);
}
};
dataView.setItemsPerPage(10);
logEntryListContainer.add(dataView);
logEntryListContainer.add(new PagingNavigator("navigator", dataView));
add(logEntryListContainer);
}
}
You are changing what the variable logEntryList points to, but that does not impact what the new ListDataProvider(logEntryList) sees.
Upon reload, what you could do is
logEntryList.clear().addAll(logEntryDao.findAll()) so that the variable to which the data provider points is updated
provide your own DataProvider implementation
You pass in the list of logEntries upon creation of the DataView. Any changes to the list afterwards will not be reflected. Try wrapping the list in a PropertyModel and provide a getter for it.
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.
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 using smartgwt ( not the paid license version ) and I have a listgrid with three entries.
Key, Value, Reset.
The reset-field is a button that should reset any changes to the value and here is where I struggle.
I tried to implement it as simple as
#Override
public void onClick(ClickEvent event)
{
DataSource ds = this.grid.getDataSource();
ds.removeData(record);
ds.fetchData();
this.grid.redraw();
}
grid being my ListGrid and record the row that has been clicked to be reseted.
But this only removes the entry, it is there again if I reload ( even with the right value , because that is what my server does if he gets remove-requests), but I would like that it is there immediately after I click the button and not after clicking around a bit.
I assumed the fetchData and redraw request combined would accomplish this.
edit: Okay some more code, this shows my constructor for the ListGrid and the RevertButton which should remove and add the Record again.
private static final String REVERT_NAME = "revertField";
public MyListGrid(final String name)
{
this.setDataSource(PropertyListDS.getInstance(name);
ListGridField keyField = new ListGridField(ConfigurationDataSourceFields.PROPERTY_NAME, "Property");
ListGridField valueField = new ListGridField(ConfigurationDataSourceFields.PROPERTY_VALUE, "Value");
ListGridField revertField = new ListGridField(REVERT_NAME, "Revert to Default");
valueField.setCanEdit(true);
this.setShowRecordComponents(true);
this.setShowRecordComponentsByCell(true);
this.setAutoFetchData(true);
this.setFields(keyField, valueField, revertField);
}
#Override
protected Canvas createRecordComponent(final ListGridRecord record, Integer colNum)
{
String fieldName = this.getFieldName(colNum);
Canvas canvas = null;
if ( REVERT_NAME.equals(fieldName) )
{
canvas = new RevertButton(this, record);
}
return canvas;
}
private class RevertButton extends IButton implements ClickHandler
{
private final MyListGrid grid;
private final ListGridRecord record;
public RevertButton(final MyListGrid grid, final ListGridRecord record)
{
super();
this.setTitle("Revert to Default");
this.grid = grid;
this.record = record;
this.addClickHandler(this);
}
#Override
public void onClick(ClickEvent event)
{
DataSource ds = this.grid.getDataSource();
ds.removeData(record);
ds.fetchData();
this.grid.redraw();
}
}
Do in this way using DSCallback.
DataSource#removeData() is a async call to the server. Either redraw the grid again or fetch the data again after getting response from server that record has been deleted in DSCallback.
DataSource dataSource = grid.getDataSource();
dataSource.removeData(record,new DSCallback() {
#Override
public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest){
Record[] records=dsResponse.getData();//deleted records
grid.fetchData();//fetch data again
}
});
Please have a look at this thread Removing local record from listGrid without committing
Try with ListGrid#saveAllEdits() before fetching the data again.
You can try with ListGrid#removeSelectedData() to remove the currently selected records from this component. If this is a databound grid, the records will be removed directly from the DataSource.
I follow this example: http://www.wicket-library.com/wicket-examples/repeater/wicket/bookmarkable/org.apache.wicket.examples.repeater.SortingPage?0
and this is my code:
SortablePlayerDataProvider dp = new SortablePlayerDataProvider();
final DataView<Player> dataView = new DataView<Player>("rows", dp, 10) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(final Item<Player> listItem) {
final Player player = listItem.getModelObject();
listItem.setModel(new CompoundPropertyModel<Player>(player));
listItem.add(new Label("name", player.getName()));
listItem.add(new Label("surname", player.getSurname()));
listItem.add(new Label("club", player.getClub()));
}
};
and provider:
public class SortablePlayerDataProvider extends SortableDataProvider<Player> {
private static final long serialVersionUID = 1L;
public SortablePlayerDataProvider() {
setSort("surname", SortOrder.ASCENDING);
}
#Override
public Iterator<Player> iterator(int first, int count) {
return tournamentService.getAllPlayer().subList(first, first + count).iterator();
}
#Override
public int size() {
return tournamentService.getAllPlayer().size();
}
#Override
public IModel<Player> model(final Player object) {
return new LoadableDetachableModel<Player>() {
private static final long serialVersionUID = 1L;
#Override
protected Player load() {
return object;
}
};
}
}
but sorting doesn work and I dont know why. What is wrong ? Is there som optins how to debug it ?
UPDATE:
can someone explain me and show where is this dataProvider sorting items ?
because when I print it:
Iterator<Player> a = dp.iterator(0, dp.size());
for (Iterator<Player> iterator = a; iterator.hasNext();) {
System.out.println(iterator.next());
}
items are still no sorted and when I looking for implementation of this provider I cant find where is this iterator sorted.
ok I found it why is doesnt work. In wicket example items are sorting when you defined iterator:
return getContactsDB().find(first, count, getSort()).iterator();
but I dont understand why I need to sort item. Then I can create just trivial new IDataProvider sort item when I am creating iterator and it is sorted too. This class is really obscure...
I've seethe code of the example above. Maybe you may need to use component OrderByBorder
UPDATE
Data sorting should be performed by your datasource, SortableDataProvider won't do it by its own. If you look at example code you can see that it uses ContactsDatabase as datasource. This class keep two sorted list, one for the 'name' and one for the 'last name' property.
in my form I have input text and then dataView:
private class RegistrationForm extends Form<Table> {
private static final long serialVersionUID = 1L;
public RegistrationForm(final Table table) {
super("registrationForm", new CompoundPropertyModel<Table>(table));
setOutputMarkupId(true);
final TextField<String> text = new TextField<String>("name");
add(text);
DataView<Player> dataView = new DataView<Player>("rows", playerDataProvider) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(final Item<Player> listItem) {
...
on listItem I add ajaxEventBehavior when I double Click on row:
listItem.add(new AjaxEventBehavior("ondblclick") {
private static final long serialVersionUID = 1L;
#Override
protected void onEvent(final AjaxRequestTarget target) {
System.out.println(table.getName());
}
});
Problem is when I double click on table it print null not the value which I have in input TextField. Why ?
I try to update model:
text.updateModel();
or get value from text:
System.out.println(table.getName() + "bbbbbbbbbbbbbbbbb" + text.getInput() + "vv"
+ text.getValue() + "ff");
but with no success.
in form I have also submit button and when I press it every think works. I have problems just with double click
AjaxEventBehavior itself does not submit the form, so you're not getting the text field value. You can use one of the following subclasses instead:
AjaxFormComponentUpdatingBehavior will submit only the one FormComponent it is attached to. It needs to be attached to a FormComponent, so you'll have to use the following if you want to attach it to the entire ListItem:
AjaxFormSubmitBehavior will submit the entire form. This is probably what you need here.
All these behaviors are included in Wicket and their Javadoc is right there with the source code. For the Ajax stuff, check subclasses of AbstractAjaxBehavior, in particular the subclasses of AjaxEventBehavior.