I have a linktree for navigation over data which shall replace a component in another div.
For this in the tree I have the treestatelistener implementing
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
Component content = new Label("content");
content.replaceWith(new Label("content", "Hello World"));
}
}
}
All I get is the message:
"This method can only be called on a component that has already been
added to its parent."
The Label with Id "content" is added to the page on init, so it is there before. What do I do wrong?
You are creating a new parent Component every time your treenode listener is called, without adding the 'content' label.
What I would suggest is to try something like this -
public MyPage extends WebPage {
private Component parent = new WebMarkupContainer("parent");
public WebPage() {
parent.add(new Label("content", "hi");
}
...
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
parent.replaceWith(new Label("content", "Hello World"));
}
}
}
I always have issues with just calling Component.replaceWith(). Since I am doing this with AJAX, I always need to call a target.add().
My solution is for now to have a class MyTree extend LinkTree and in that class override the newNodeComponent().
Since in my application everything happens on the IndexPage.class and there I just replace components, I added a method handleSelection() to the IndexPage.Class(extends BasePage extends webpage). There I decide, based on the clicked nodeObject, how to replace a component in the IndexPage
Public Class MyTree extends Linktree{
protected Component newNodeComponent(String id, IModel<Object> model) {
#Override
protected void onNodeLinkClicked(Object node, BaseTree tree, AjaxRequestTarget target) {
super.onNodeLinkClicked(node, tree, target);
((IndexPage)getPage()).handleSelection(target, (TreeNode)node);
}
#Override
protected ResourceReference getResourceFolderOpen(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceFolderClosed(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceItemLeaf(Object node) {
return super.getResourceItemLeaf(node);
}
#Override
protected ResourceReference getImageResourceReference(BaseTree tree, Object node) {
return getResourceForNode(node);
}
}
}
I hope this is explained propperly, else: questions or comments wanted!
Related
I have a widget that inherits from CellTree. If the node not have the child elements, this node can be opened and shows "no data" label.
I'd like to see nodes without child's displayed as empty.
That's how I fill the tree. My DictionaryTreeDataProvider class (relevant part):
public class DictionaryTreeDataProvider extends ListDataProvider<MValue> {
private final DictionariesServiceAsync service = GWT.create(DictionariesService.class);
...
#Override
public void onRangeChanged(HasData<MValue> result) {
service.queryDictionaryValues(range, query, new AsyncCallback<SubsetResult<MValue>>() {
#Override
public void onFailure(Throwable t) {
}
#Override
public void onSuccess(SubsetResult<MValue> result) {
getList().clear();
for (MValue value : result.items) {
getList().add(value);
}
}
});
}
}
On the server side I make EJB call which fills SubsetResult.
I found that this problem fixed in version of GWT-2.5.0-rc2 (see https://groups.google.com/forum/#!topic/google-web-toolkit/d-rFUmyHTT4).
Now everything is OK, thanks to #moutellou.
I did as he suggested:
...
#Override
public void onSuccess(SubsetResult<MValue> result) {
if (result.length == 0) {
updateRowCount(-1, true);
return;
} else {
for (MValue value : result.items) {
// some checks here
getList().add(value);
}
}
}
...
Some alternative solution. Can be defined interface that extends the interface CellTree.Resources.
In this interface must specify the path to the CSS, which override the desired style.
Interface CellTree.Resources:
public class CellTree extends AbstractCellTree implements HasAnimation,
Focusable {
...
/**
* A ClientBundle that provides images for this widget.
*/
public interface Resources extends ClientBundle {
/**
* An image indicating a closed branch.
*/
#ImageOptions(flipRtl = true)
#Source("cellTreeClosedArrow.png")
ImageResource cellTreeClosedItem();
/**
* An image indicating that a node is loading.
*/
#ImageOptions(flipRtl = true)
ImageResource cellTreeLoading();
/**
* An image indicating an open branch.
*/
#ImageOptions(flipRtl = true)
#Source("cellTreeOpenArrow.png")
ImageResource cellTreeOpenItem();
/**
* The background used for selected items.
*/
#ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
ImageResource cellTreeSelectedBackground();
/**
* The styles used in this widget.
*/
#Source(Style.DEFAULT_CSS)
Style cellTreeStyle();
}
...
}
Interface CustomCellTreeResources, based on CellTree.Resources:
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.user.cellview.client.CellTree;
public interface CustomCellTreeResources extends CellTree.Resources {
static final String STYLE_PATH = "components/common/client/static/custom-cell-tree.css";
#Override
#ClientBundle.Source({CellTree.Style.DEFAULT_CSS, STYLE_PATH})
CellTree.Style cellTreeStyle();
}
Overriding rule:
.cellTreeEmptyMessage {
display: none;
}
Create an instance:
private final static CellTree.Resources customCellTreeResources =
GWT.create(CustomCellTreeResources.class);
And next need to explicitly pass customCellTreeResources to the CellTree class constructor.
Message is not displayed more.
Mandatory: before filing the list, ie, before clicking on a node, the list should be cleaned( getList().clear();):
#Override
public void onRangeChanged(HasData<MValue> result) {
service.queryDictionaryValues(range, query,
new AsyncCallback<SubsetResult<MValue>>() {
#Override
public void onFailure(Throwable t) {}
#Override
public void onSuccess(SubsetResult<MValue> result) {
getList().clear();
for (MValue value : result.items) {
getList().add(value);
}
}
});
}
This is how I removed the no data label in my DataProvider
//Fetch children
int size = children.size();
if (size == 0) {
updateRowCount(-1, true); //Method called on AsyncDataProvider
return;
}
In the TreeViewModel, make sure that the isLeaf method returns true if the argument value has no children. Example:
#Override
public boolean isLeaf(Object value) {
if (value instanceof DepartmentDto) {
DepartmentDto department = (DepartmentDto) value;
return department.getEmployees().isEmpty();
} else if (value instanceof EmployeeDto) {
return true;
} else {
return false;
}
}
In this case, a department should declare itself as a leaf only if it has no employees, an employee will declare itself as a leaf, and default to false.
Note that value many also be an internal GWT node. In this example, it might not necessarily be just DepartmentDto and EmployeeDto.
I want to create a Cell factory that returns a TableCell that behaves exactly like TextFieldTableCell, with the following difference: When it loses focus, it commits the changes.
My code is very simple:
public final class TextFieldCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
#Override
public TableCell<S, T> call(TableColumn<S, T> p) {
class EditingCell extends TextFieldTableCell {
public EditingCell() {
super();
setConverter(new DefaultStringConverter());
focusedProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
System.out.println("changed!");
System.out.println("getText() = " + getText());
System.out.println("textProperty() = " + textProperty().get());
System.out.println("getItem = " + getItem());
}
});
}
#Override
public void startEdit() {
super.startEdit();
}
#Override
public void cancelEdit() {
super.cancelEdit();
}
}
return new EditingCell();
}
}
As you see I add a change listener in the focusedProperty. The problem is that the change method is not called (nothing is printed).
How can I get the desired behaviour? Thank you.
Basically, you have to register the listener with the textField's (not the cell's) focusedProperty. As the textfield is a private field of super, it's not directly accessible - you have to look it up once after it was added to the cell. That's when an edit was started for the first time:
private TextField myTextField;
#Override
public void startEdit() {
super.startEdit();
if (isEditing() && myTextField == null) {
// most simple case, assuming that there is no graphic other than the field
// TBD: implement the general case: walk the tree and find the field
myTextField = (TextField) getGraphic();
myTextField.focusedProperty().addListener((e, old, nvalue) -> {
if (!nvalue) {
T edited = getConverter().fromString(myTextField.getText());
commitEdit(edited);
}
});
}
}
Some notes:
this is a workaround around an open issue (vote for it!)
since jdk8, it's not entirely functional: won't commit if you click somewhere else inside the table
a recent answer uses a binding approach which might or not be fully functional (didn't test)
I am using activities and places to develop my application. When I click on the link on the left, my page is loaded, and I put the values in the fields. After I put the values, I send a RPC to the server, and get response back. This is also shown. Now the problem is that even if I click on the link on the left, I am getting the result page again. I am not getting the input page.
Impl code
#UiHandler("entInvoiceCompare")
void onClickSubmit(ClickEvent e) {
GWT.log("Going to enterprise compare place");
//listener.goTo(new EnterpriseInvoiceCompareViewPlace());
NewEBRM.getClientFactory().getPlaceController().goTo(new EnterpriseInvoiceCompareViewPlace());
}
Place
public class EnterpriseInvoiceCompareViewPlace extends Place{
public EnterpriseInvoiceCompareViewPlace() {
GWT.log("EnterpriseInvoiceCompareViewPlace: constructor");
}
public static class Tokenizer implements PlaceTokenizer<EnterpriseInvoiceCompareViewPlace> {
#Override
public String getToken(EnterpriseInvoiceCompareViewPlace place {
GWT.log("EnterpriseInvoiceCompareViewPlace: getToken: call");
return "EntInvoiceCompare";
}
#Override
public EnterpriseInvoiceCompareViewPlace getPlace(String token) {
GWT.log("EnterpriseInvoiceCompareViewPlace: getPlace: call");
return new EnterpriseInvoiceCompareViewPlace();
}
}
}
Activity
#Override
public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
// TODO Auto-generated method stub
GWT.log("EnterpriseInvoiceCompareActivity: start: starting activity");
EnterpriseInvoiceCompareView entInvoiceCompareView = clientFactory.getEnterpriseInvoiceCompareView();
entInvoiceCompareView.setPresenter(this);
containerWidget.setWidget(entInvoiceCompareView.asWidget());
GWT.log("EnterpriseInvoiceCompareActivity: start: ending activity");
}
I have a GWT Cell Tree that I use to display a file structure from a CMS. I am using a AsyncDataProvider that loads data from a custom RPC class I created. I also have a Web Socket system that will broadcast events (File create, renamed, moved, deleted etc) from other clients also working in the system.
What I am trying to wrap my head around is when I recieve one of these events, how I correctly update my Cell Tree?
I suppose this problem would be analogus to having two instances of my Cell Tree on the page, which are presenting the same server-side data and wanting to ensure that when the user updated one, that the other updated as well, via using the EventBus.
I feel this should be pretty simple but I have spent about 6 hours on it now with no headway. My code is included below:
NOTE: I am not using RequestFactory even though it may look like I am it is my custom RPC framework. Also, FileEntity is just a simple representation of a file which has a name accessible by getName().
private void drawTree() {
// fileService is injected earlier on and is my own custom rpc service
TreeViewModel model = new CustomTreeModel(new FileDataProvider(fileService));
CellTree tree = new CellTree(model, "Root");
tree.setAnimationEnabled(true);
getView().getWorkspace().add(tree);
}
private static class CustomTreeModel implements TreeViewModel {
// I am trying to use a single AsyncDataProvider so I have a single point of loading data which I can manipulate (Not sure if this is the correct way to go)
public CustomTreeModel(FileDataProvider dataProvider) {
this.provider = provider;
}
public <T> NodeInfo<?> getNodeInfo(final T value) {
if (!(value instanceof FileEntity)) {
// I already have the root File loaded in my presenter, if we are at the root of the tree, I just add it via a list here
ListDataProvider<FileEntity> dataProvider = new ListDataProvider<FileEntity>();
dataProvider.getList().add(TreeWorkspacePresenter.rootFolder);
return new DefaultNodeInfo<FileEntity>(dataProvider,
new FileCell());
} else {
// Otherwise I know that we are loading some tree child data, and I invoke the AsyncProvider to load it from the server
provider.setFocusFile(value);
return new DefaultNodeInfo<FileEntity>(provider,
new FileCell());
}
}
public boolean isLeaf(Object value) {
if(value == null || value instanceof Folder)
return false;
return true;
}
}
public class FileDataProvider extends AsyncDataProvider<FileEntity> {
private FileEntity focusFile;
private FileService service;
#Inject
public FileDataProvider(FileService service){
this.service = service;
}
public void setFocusFile(FileEntity focusFile){
this.focusFile = focusFile;
}
#Override
protected void onRangeChanged(HasData<FileEntity> display) {
service.getChildren(((Folder) focusFile),
new Reciever<List<FileEntity>>() {
#Override
public void onSuccess(List<FileEntity> files) {
updateRowData(0, files);
}
#Override
public void onFailure(Throwable error) {
Window.alert(error.toString());
}
});
}
}
/**
* The cell used to render Files.
*/
public static class FileCell extends AbstractCell<FileEntity> {
private FileEntity file;
public FileEntity getFile() {
return file;
}
#Override
public void render(Context context, FileEntity file, SafeHtmlBuilder sb) {
if (file != null) {
this.file = file;
sb.appendEscaped(file.getName());
}
}
}
Currently there is no direct support for individual tree item refresh even in the latest gwt version.
But there is a workaround for this. Each tree item is associated with an value. Using this value you can get the corresponding tree item.
In your case, i assume, you know which item to update/refresh ie you know which File Entity has changed. Use this file entity to search for the corresponding tree item. Once you get the tree item you just need to expand and collapse or collapse and expand its parent item. This makes parent item to re-render its children. Your changed file entity is one among the children. So it get refreshed.
public void refreshFileEntity(FileEntity fileEntity)
{
TreeNode fileEntityNode = getFileEntityNode(fileEntity, cellTree.getRootTreeNode()
// For expnad and collapse run this for loop
for ( int i = 0; i < fileEntityNode.getParent().getChildCount(); i++ )
{
if ( !fileEntityNode.getParent().isChildLeaf( i ) )
{
fileEntityNode.getParent().setChildOpen( i, true );
}
}
}
public TreeNode getFileEntityNode(FileEntity fileEntity, TreeNode treeNode)
{
if(treeNode.getChildren == null)
{
return null;
}
for(TreeNode node : treeNode.getChildren())
{
if(fileEntity.getId().equals( node.getValue.getId() ))
{
return node;
}
getEntityNode(fileEntity, node);
}
}
You can use the dataprovider to update the celltree.
You can update the complete cell tree with:
provider.setList(pList);
provider.refresh();
If you want to update only a special cell you can get the listwrapper from the dataprovider and only set one element.
provider.getList().set(12, element);
I have implemented a GEF editor for a graph-like EMF model, with a remove command for a certain type of node in the graph. I think I've done all the necessary steps in order to make this set up work (vainolo's blog has been a great help).
However, when I'm deleting a model element, the view doesn't get refreshed, i.e., the figure for the model element isn't removed from the editor view, and I have no idea why. I'd be extremely grateful if somebody could have a look at my sources and point me to any problems (and possibly solutions :)). Many thanks in advance!
Below are what I think are the important classes for this issue. Please do let me know should I add further code/edit the code, etc. (I've left out code that I thought doesn't help, e.g., getters and setters, class variables). Thanks!
DiagramEditPart
public class DiagramEditPart extends AbstractGraphicalEditPart {
public DiagramEditPart(Diagram model) {
this.setModel(model);
adapter = new DiagramAdapter();
}
#Override protected IFigure createFigure() {
Figure figure = new FreeformLayer();
return figure;
}
#Override protected void createEditPolicies() {
installEditPolicy(EditPolicy.LAYOUT_ROLE, new DiagramXYLayoutPolicy());
}
#Override protected List<EObject> getModelChildren() {
List<EObject> allModelObjects = new ArrayList<EObject>();
if (((Diagram) getModel()).getMyNodes() != null)
allModelObjects.addAll(((Diagram) getModel()).getMyNodes());
return allModelObjects;
}
#Override public void activate() {
if(!isActive()) {
((Diagram) getModel()).eAdapters().add(adapter);
}
super.activate();
}
#Override public void deactivate() {
if(isActive()) {
((Diagram) getModel()).eAdapters().remove(adapter);
}
super.deactivate();
}
public class DiagramAdapter implements Adapter {
#Override public void notifyChanged(Notification notification) {
switch (notification.getEventType()) {
case Notification.REMOVE: refreshChildren();
break;
default:
break;
}
}
#Override public Notifier getTarget() {
return (Diagram) getModel();
}
#Override public void setTarget(Notifier newTarget) {
// Do nothing.
}
#Override public boolean isAdapterForType(Object type) {
return type.equals(Diagram.class);
}
}
}
MyNodeEditPart
public class MyNodeEditPart extends AbstractGraphicalEditPart {
public MyNodeEditPart(MyNode model) {
this.setModel(model);
adapter = new MyNodeAdapter();
}
#Override protected IFigure createFigure() {
return new MyNodeFigure();
}
#Override protected void createEditPolicies() {
installEditPolicy(EditPolicy.COMPONENT_ROLE, new MyNodeComponentEditPolicy());
}
#Override protected void refreshVisuals() {
MyNodeFigure figure = (MyNodeFigure) getFigure();
DiagramEditPart parent = (DiagramEditPart) getParent();
Dimension labelSize = figure.getLabel().getPreferredSize();
Rectangle layout = new Rectangle((getParent().getChildren().indexOf(this) * 50),
(getParent().getChildren().indexOf(this) * 50), (labelSize.width + 20),
(labelSize.height + 20));
parent.setLayoutConstraint(this, figure, layout);
}
public List<Edge> getModelSourceConnections() {
if ((MyNode) getModel() != null && ((MyNode) getModel()).getDiagram() != null) {
ArrayList<Edge> sourceConnections = new ArrayList<Edge>();
for (Edge edge : ((MyNode) getModel()).getDiagram().getOutEdges(((MyNode) getModel()).getId())) {
sourceConnections.add(edge);
}
return sourceConnections;
}
return null;
}
// + the same method for targetconnections
#Override public void activate() {
if (!isActive()) {
((MyNode) getModel()).eAdapters().add(adapter);
}
super.activate();
}
#Override public void deactivate() {
if (isActive()) {
((MyNode) getModel()).eAdapters().remove(adapter);
}
super.deactivate();
}
public class MyNodeAdapter implements Adapter {
#Override
public void notifyChanged(Notification notification) {
refreshVisuals();
}
#Override
public Notifier getTarget() {
return (MyNode) getModel();
}
#Override
public void setTarget(Notifier newTarget) {
// Do nothing
}
#Override
public boolean isAdapterForType(Object type) {
return type.equals(MyNode.class);
}
}
}
MyNodeComponentEditPolicy
public class MyNodeComponentEditPolicy extends ComponentEditPolicy {
#Override
protected Command createDeleteCommand(GroupRequest deleteRequest) {
DeleteMyNodeCommand nodeDeleteCommand = new DeleteMyNodeCommand((MyNode) getHost().getModel());
return nodeDeleteCommand;
}
}
DeleteMyNodeCommand
public class DeleteMyNodeCommand extends Command {
public DeleteMyNodeCommand(MyNode model) {
this.node = model;
this.graph = node.getDiagram();
}
#Override public void execute() {
getMyNode().setDiagram(null);
System.out.println("Is the model still present in the graph? " + getGraph().getMyNodes().contains(getMyNode()));
// Returns false, i.e., graph doesn't contain model object at this point!
}
#Override public void undo() {
getMyNode().setDiagram(getGraph());
}
}
EDIT
Re execc's comment: Yes, refreshChildren() is being called. I've tested this by overriding it and adding a simple System.err line, which is being displayed on the console on deletion of a node:
#Override
public void refreshChildren() {
super.refreshChildren();
System.err.println("refreshChildren() IS being called!");
}
EDIT 2
The funny (well...) thing is, when I close the editor and persist the model, then re-open the same file, the node isn't painted anymore, and is not present in the model. But what does this mean? Am I working on a stale model? Or is refreshing/getting the model children not working properly?
EDIT 3
I've just found a peculiar thing, which might explain the isues I have? In the getModelChildren() method I call allModelObjects.addAll(((Diagram) getModel()).getMyNodes());, and getMyNodes() returns an unmodifiable EList. I found out when I tried to do something along the lines of ((Diagram) getModel()).getMyNodes().remove(getMyNode()) in the delete command, and it threw an UnsupportedOperationException... Hm.
EDIT 4
Er, somebody kill me please?
I've double-checked whether I'm handling the same Diagram object at all times, and while doing this I stumbled across a very embarassing thing:
The getModelChildren() method in DiagramEditPart in the last version read approx. like this:
#Override protected List<EObject> getModelChildren() {
List<EObject> allModelObjects = new ArrayList<EObject>();
EList<MyNode> nodes = ((Diagram) getModel()).getMyNodes();
for (MyNode node : nodes) {
if (node.getDiagram() != null); // ### D'Uh! ###
allModelObjects.add(node);
}
return allModelObjects;
}
I'd like to apologize for stealing everyone's time! Your suggestions were very helpful, and indeed helped my to finally track down the bug!
I've also learned a number of lessons, amongst them: Always paste the original code, over-simplifaction may cloak your bugs! And I've learned a lot about EMF, Adapter, and GEF. Still:
There is one semi-colon too many in line 5 of the following part of the code, namely after the if statement: if (node.getDiagram() != null);:
1 #Override protected List<EObject> getModelChildren() {
2 List<EObject> allModelObjects = new ArrayList<EObject>();
3 EList<MyNode> nodes = ((Diagram) getModel()).getMyNodes();
4 for (MyNode node : nodes) {
5 if (node.getDiagram() != null);
6 allModelObjects.add(node);
7 }
8 return allModelObjects;
9 }