hope you can help me with this because it has been bugging me for a few days now.
I have a wicket DataTable filled with 16 columns. Every row can have some child info (if it has a '+' button is shown in the first cell). Since the child info is just some regular text with a date on when it was entered, I did not want to use a real TreeData object. I have been trying to make the '+' button into an AjaxFallbackButton, which works, but my child data is rendered inside the first cell of the row, which is very impractical.
I am now trying to dynamically create a new row by prepending some JS on the target:
target.prependJavaScript(String.format("var item=document.createElement(\"tr\"); item.id=\"%s\"; $(\"#%s\").after(item);", newChild.getId(), rowId));
This is great because it actually creates a new tag inside my page. But how do I get my new data inside this newly rendered HTML? I guess my problem is that Wicket does not 'know' the new row and if I add the new wicket Item (the container in Wicket with the new row) to the Wicket DataTable and add the body of the DataTable to the target then now rendering of my new row is done.
My markup for the page:
[table]
My custom DataTable extension Java code (extended to make sure all outputMarkUp is added):
#Override
protected void onInitialize() {
super.onInitialize();
this.addTopToolbar(new NavigationToolbar(this));
this.addTopToolbar(new HeadersToolbar<>(this, (ISortableDataProvider)this.getDataProvider()));
this.addBottomToolbar(new NoRecordsToolbar(this));
this.setOutputMarkupId(true);
}
#Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssReferenceHeaderItem.forReference(WarehouseToolbarCssReference.get()));
}
#Override
protected Item<T> newRowItem(String id, int index, IModel<T> model) {
Item<T> item = super.newRowItem(id, index, model);
this.lastId = Integer.valueOf(id);
item.setMarkupId(this.getId() + item.getId());
return item;
}
The first column uses a custom Panel to fill up the cells in the column, but after a lot of tries and tests, I'm actually ending up with the regular Node from Wicket itself:
<wicket:panel xmlns:wicket="http://wicket.apache.org">
<a wicket:id="junction"></a>
<span wicket:id="content" class="tree-content">[content]</span>
cellspacing="0" wicket:id="reasonData">[Reason data]</table>-->
</wicket:panel>
And finally the code that creates the new row and fills up the necessary data (which is itself a new DataTable) and also fills the ajax-target:
public void onClick(AjaxRequestTarget target) {
Integer currentRowIndex = Integer.valueOf(this.findParent(Item.class).findParent(Item.class).getId());
String rowId = this.findParent(Item.class).findParent(Item.class).getMarkupId();
Item<IColumn<OldOrder, String>> newChild = null;
this.dataTable.setVisible(true);
newChild = new Item<>(String.valueOf(this.findParent(DataTable.class).getLastId() + 1), currentRowIndex * -1, null);
newChild.setOutputMarkupId(true);
target.prependJavaScript(String.format("var item=document.createElement(\"tr\"); item.id=\"%s\"; $(\"#%s\").after(item);", newChild.getId(), rowId));
this.populateChildRow(newChild); // This is where the new row gets filled with the new data
Component rows = this.findParent(DataTable.class).getBody().get("rows");
if (rows instanceof WebMarkupContainer) {
((WebMarkupContainer)rows).add(newChild);
}
target.add(this.findParent(DataTable.class).getBody());
}
// This is where the new row gets filled with the new data
private void populateChildRow(Item<IColumn<ParentClass>, String>> item) {
RepeatingView cells = new RepeatingView("cells"){
#Override // Overridden to check if this gets executed... it doesn't
protected void onAfterRender() {
super.onAfterRender();
String stop = "STOP";
}
};
item.add(cells);
Item<?> cellItem = new Item<>("arbitrary", 1, null);
cells.add(cellItem);
cells.setVisible(false);
cells.setOutputMarkupId(true);
cells.add(AttributeAppender.append("colspan", this.findParent(DataTable.class).getColumns().size()));
this.dataTable.setOutputMarkupId(true);
cellItem.add(this.dataTable); // The DataTable with the child data
}
Besides the fact I should clean out some code, the child data is never rendered. Can someone please shed some light on this?
I'd suggest to go with JavaScript all the way, i.e. create a new <tr> and then populate it with JS. Don't use Wicket's Item at all.
When the expand link is clicked do something like target.appendJavaScript("yourOwnFunction('"+link.getMarkupId()+"', "+someDataAsJson+")");. So yourOwnFunction() will use the link id to find its closest parent <tr> and then with JS you can append the new row after it. Once you have the row created you could use the JSON with the data to populate it as you need.
Related
I created a TableTree that contains object of class Component that has a boolean property "selected".
I want to hide the rows from the table where the rows component is not selected.
I tried this:
componentTree.setRowFactory(new Callback<TreeTableView<Component>, TreeTableRow<Component>>() {
#Override
public TreeTableRow<Component> call(TreeTableView<Component> param) {
TreeTableRow<Component> row = new TreeTableRow<Component>() {
#Override
protected void updateItem(Component component, boolean empty) {
if(!empty) {
if (!component.isSelected()) {
setVisible(false);
setManaged(false);
System.out.println("hide");
} else {
setVisible(true);
setManaged(true);
System.out.println("show");
}
}
}
};
return row;
}
});
On system.out I can see a lot of "show" and "hide" messages, but this doesn't affect the table, all rows are shown as before.
Any idea on this topic?
Thanks!
I used eclipse's fx.ui.controls library for the same achieve the same goal before.
<dependency>
<groupId>at.bestsolution.eclipse</groupId>
<artifactId>org.eclipse.fx.ui.controls</artifactId>
<version>2.2.0</version>
</dependency>
The library provides a class: FilterableTreeItem<T> under the tree package. This class was designed to be used in cases like yours. You can provide a Predicate to the root of the tree and the items will get hidden when the value given changes:
// Children
final FilterableTreeItem<Component> childNode1 = new FilterableTreeItem<>(component1);
final FilterableTreeItem<Component> childNode2 = new FilterableTreeItem<>(component2);
final FilterableTreeItem<Component> childNode3 = new FilterableTreeItem<>(component3);
// Root
final FilterableTreeItem<Component> root = new FilterableTreeItem<>(rootComponent);
root.getInternalChildren().setAll(childNode1, childNode2, childNode3);
root.setPredicate((parent, value) -> value.isSelected());
// TreeTableView
final TreeTableView<Component> treeTableView = new TreeTableView<>(root);
Note that you have to use getInternalChildren() to add children and the default getChildren().
FilterableTreeItem<T> also provides a predicateProperty() that you can bind to another property in case you need to update the how items are shown or hidden.
Another advatage of this class is that it shows the whole path up to the root of the items matching that predicate.
I'm building a simple CRUD application with a MySql database as back end. So far I managed to link a grid to the contents of a SQLContainer with the FreeformQuery (so the read is fine).
String query = "select a.id, a.name name, b.name type from asset a join " +
" assettype b on a.assettype_id = b.id";
grid.setContainerDataSource(container);
For the container the SQLContainer. I created a form and I did the binding between its contents and a selected row
grid.addSelectionListener(event -> {
if (event.getSelected().isEmpty()) {
form.setVisible(false);
} else {
Item item = container.getItem(event.getSelected().iterator().next());
form.setAsset(item);
}
});
So, as you see, the form is linked to an Item. setAsset is simply a form method that links the row contents to the text fields of the form.
public void setAsset(Item item) {
this.item = item;
FieldGroup binder = new FieldGroup(this.item);
binder.bind(textField1, "name");
binder.bind(textField2, "type");
setVisible(true);
}
Well, I don't know anymore how to add a row, or how to edit an existing one. For editing a row I tried with a save method for the form (that I call with a button) as follows
private void save() throws UnsupportedOperationException, SQLException {
SQLContainer container = db.getContainer();
container.addItem(item);
container.commit();
}
Well, problem one is that, if I select a grid item, I see the contents of name and type in the text fields but, if I modify their values in the text field before entering the save method, this.item still has the original values (when I thought it would take the new value in the text field because the textfields are bound to the RowItem). Does anybody know what's going on?
In addition, if I want to create a new row, I would like to have something like this
Button createAsset = new Button("Add asset");
createAsset.addClickListener(e ->
{
grid.select(null);
form.setAsset(new Item());
});
and fill the contents of the blank Item() in the form, before pushing it to the table. Of course I can't because Item and RowItem are interfaces. So how can I instantiate an empty row of a container before filling its contents?
Thanks very much in advance.
In the end, I would say that either I din't use the FieldGroup.bind method properly, or that there is a bug in it. This is my original entry
public void setAsset(Item item) {
this.item = item;
FieldGroup binder = new FieldGroup(this.item);
binder.bind(textField1, "name");
binder.bind(textField2, "type");
setVisible(true);
}
which did not work, and this is what works (which to me, is the same)
public void setAsset(Item item) {
this.item = item;
textField1.setPropertyDataSource(item.getItemProperty("name"));
textField2.setPropertyDataSource(item.getItemProperty("type"));
setVisible(true);
}
I'm looking for a way to display a tree in the first column of a table/grid with three other columns, one with a combobox and the others with checkboxes. I've been trying to make this work with a TreeViewer but it doesn't quite fit what I'm looking for. The tree goes together fine. The Combobox column where I used the EditorSupport for the column and return a ComboboxCellEditor in the getCellEditor method but you can only see that there is a combobox in the column when you select a cell in that column. Then when you click out of the cell the selected value goes back to the default blank. The same goes for the checkbox columns where it is is only visible when the cell is selected. I'm looking for something that will display my tree with the combobox column and checkbox columns always visible. I've looked at TableViewer but couldn't find a way to force in a tree in the first column. I've looked at Nebula Grid but that doesn't look like it supports comboboxes. Any tips on how to get one of these to work like what I am looking for or if there is some other tree/table/grid I should be looking at. Thanks.
Edit: Here's the code for the EditingSupport class.
private class ComboBoxEditingSupport extends EditingSupport
{
private ComboBoxCellEditor cellEditor;
public ComboBoxEditingSupport(ColumnViewer viewer)
{
super(viewer);
cellEditor =
new ComboBoxCellEditor(((TreeViewer) viewer).getTree(),
new String[] {
"Some String",
"Some other String" }, SWT.READ_ONLY);
}
#Override
protected CellEditor getCellEditor(Object element)
{
if (element instanceof MyObject
{
return cellEditor;
}
return null;
}
#Override
protected boolean canEdit(Object element)
{
if (element instanceof MyObject
{
return true;
}
return false;
}
#Override
protected Object getValue(Object element)
{
return 0;
}
#Override
protected void setValue(Object element, Object value)
{
TreeItem[] ti = treeViewer.getTree().getSelection();
CCombo combo = ((CCombo) cellEditor.getControl());
String str = combo.getItem(combo.getSelectionIndex());
ti[0].setText(1, str);
}
}
Your setValue method must update the value in your model data (the data returned by your content provider). The element parameter to setValue is the particular model data object (MyObject) that you should update.
After updating the value call:
getViewer().update(element, null);
to get the tree to update the display from the model.
Trying to update the TreeItem directly will not work.
While using EditingSupport for a treeColumn in a TreeViewer, Is there any way i can just reflect the Changes in the View instead of changing the model and then using getViewer().update(element,null);
Detail:
I want to achieve the following functionality:
Show a tree View with |Object| (ComboBox)property|
Upon selection and clicking on the button i want to show user the summary of changes and then upon clicking confirm i want to apply those changes to the model (Object)
I am using a TreeViewer, Within that i have a column with EditingSupport Enabled.
Whenever I select a value from the ComboBox and click somewhere else (lostFocus kind of ) the Value sets to default.
I have figured out that after SetValue() is called the TreeLabelProvider is again called(using debug points)
Is there any way i can just reflect the Changes in the View instead of changing the model and using getViewer().update(element,null);
Some FYIs :
Package Object contains multiple versions
ContentProvider does the job to fetch the object
LabelProvider gets all the Versions from the package(String[]) and shows the first one.
//Code to Create the UI
// blah
TreeViewerColumn column2 = new TreeViewerColumn(treeViewer, SWT.LEFT);
column2.getColumn().setText("Version");
column2.getColumn().setWidth(130);
treeViewer.setLabelProvider(new PackageUpdateTreeLabelProvider());
EditingSupport exampleEditingSupport = new OperationEditingSupport(
column2.getViewer());
column2.setEditingSupport(exampleEditingSupport);
OperationEditingSupport Class
private class OperationEditingSupport extends EditingSupport {
private ComboBoxCellEditor cellEditor = null;
private OperationEditingSupport(ColumnViewer viewer) {
super(viewer);
cellEditor = new ComboBoxCellEditor(
((TreeViewer) viewer).getTree(), new String[] {},
SWT.READ_ONLY);
}
#Override
protected CellEditor getCellEditor(Object element) {
// TODO Auto-generated method stub
if (element instanceof IPackageInfo) {
IPackageInfo pkg = (IPackageInfo) element;
cellEditor.setItems(PackageMgrUtil.getInstance().getVersions(
(IdmPackage) pkg, false, true));
return cellEditor;
}
return null;
}
#Override
protected boolean canEdit(Object element) {
return true;
}
#Override
protected Object getValue(Object element) {
// TODO Auto-generated method stub
return 0;
}
#Override
protected void setValue(Object element, Object value) {
/* only set new value if it differs from old one */
}
}
***************************************************
When i click on the cell of column2 i get the combo box but when i select something and move the focus somewhere else.It again shows the default Value
on debuging i found that :
it agains calls the label Provider which fetches all the Version of the package and then shows the first one hence I can not see any change.
what i want is that it should keep the selection intact without changing the underlying object.
thanks for the help.
Figured it out.
following code added to the SetValue() method solves it.
m_tree = (Tree)getViewer.getControl();
TreeItem[] ti = m_tree.getSelection();
CCombo c = ((CCombo)cellEditor.getControl());
String str = c.getItem(c.getSelectionIndex());
ti[0].setText(1, str );
In my current Apache Wicket project I have a search form for querying the database and displaying the query results in a ListView. The search input box is on the same page as the ListView with the results, and that ListView is filled with query results from a DAO, during invocation of the onSubmit() method of the form.
Everything works fine, but I need to display the number of search results. I tried to create a Label that is filled with the value of the size() method of the list got by the getList() method of the ListView instance, but no luck.
Thank you for any help in advance.
Depending on how you have built this form, you might only need to do label.setModelObject(listResults.size()). It's difficult to tell without seeing how are you doing it.
By what you're telling in your question, probably you're creating your Label like this new Label(labelId, listView.getList().size(). This won't work, you're setting the Label's Model at construction time with a constant value, that's the size of the list at construction time. You need to get that value inside a Model's getObject() to make the value "dynamic". Like, for instance,
AbstractReadOnlyModel sizeModel = new AbstractReadOnlyModel(){
public getObject(){
return listView.getList().getSize();
}
}
new Label(labelId, sizeModel);
With this, every time the page renders, sizeModel().getObejct() will be called to retrieve the value for the Label. In that other way, the Label has got a Model with a constant value.
You could even do label.setModelObject(list.size()) in the onSubmit() method.
From my ignorance on how you have built this form, I'll show you how would I do this. The List of results would be retrieved with a LoadableDetachableModel. That would be the Model of the ListView. Then, the Label can have for instance an AbstractReadOnlyModel that uses the ListViews modelObject to get its size.
public class MyForm extends Form {
private LoadableDetachableModel resultsModel;
private IModel searchModel;
public MyForm(){
searchModel = new Model();
TextField searchTextField = new TextField("search", searchModel);
resultsModel = new LoadableDetachableModel(){
protected Object load(){
return myService.get(searchModel.getModelObject());
}
}
ListView lv = new ListView("list", resultsModel){
// ...
}
Label resultsCount = new Label("count", new AbstractReadOnlyModel(){
public Object getObject(){
return ((List) resultsModel.getObject()).size();
}
})
SubmitButton button = new SubmitButton(){
public void onSubmit(){
//... No actions needed, really
}
}
// add's...
}
}
Using a LoadableDetachableModel for the ListView has the advantage of automatically detaching the Model, and therefore avoiding the whole List of results to get serialized into the Session.