How to force GWT to add unique ID to each DOM element? - gwt

I am having a GWT app,
I would like to add ID to each element automaticlly
if it's impossible, what would be the fastest way to do it manually?

I am not so sure about performance issues. How would adding a dew ids in a form of 10-20 widgets cause performance loss?
To automagically set debug ids on widgets you need to include following in your module.gwt.xml file.
<!-- Enable debug ID. -->
<inherits name="com.google.gwt.user.Debug"/>
<set-property name="gwt.enableDebugId" value="true"/>

You can create your own Widget class that extends GWT Widget and sets an id to each one:
public class myWidget extends Widget {
public myWidget(String id) {
super();
getElement().setId("id");
}
public void setId(String id) {
// Use this method if you use Ui:Binder
getElement().setId("id");
}
}
You can, obviously, extend other classes instead of Widget if you don't need id on all widgets, like FlowPanel, Button, etc.

If you want to have an element to have an Id always, you can go with ensureDebugId of UI Object class. It make sure that, your element have an Id set before attaching it to the dom.

Yes its Possible :
First way:
Button b = new Button();
DOM.setElementAttribute(b.getElement(), "id", "my-button-id")
Second way :
FlowPanel panel = new FlowPanel();
panel.getElement().setId("my-flowpanel-id");
If you want to do assign all the id's attached to DOM you can do
Iterator<Widget> iterator = RootPanel.get().iterator();
while(iterator.hasNext()) {
Widget w = iterator.next();
w.getElement().setId("id");
}

Related

Dynamically add widgets in a cell to represent "tags" in Datagrid

In a GWT web app, I am using a DataGrid to manage elements from a database. I represent a list of elements as rows, the columns being editable fields of their characteristics (id, name, description). I am mostly using the EditTextCell class.
I now want to create a custom cell, for a column that has to represent a list of "tags" that can be attached to every element. From this cell, tags could be added, using a + button (that makes a drop-down menu appear or something), and deleted. Each tag should be a kind of button, or interactive widget (I later want to display pop-up with info, trigger actions, etc).
Actually, it would not be so different from the "tags" bar on the Stack Overflow website...
So I have been looking for a solution:
I thought this would be easy to do. I imagined just putting a FlowPanel in the cell, adding/removing Buttons/Widgets dynamically. But it turns out that in GWT Widgets and Cells and very different objects apparently..
I read making use of the AbstractCell class to create a custom cell allows to do anything, but its working is very low level and obscure to me.
I saw that CompositeCell allows to combine various cell widgets into one cell, but I have not found if it is possible to do it dynamically, or if the widgets are going to be the same for all lines throughout a column. I mostly saw examples about, for instance, how to put two Buttons in every cell of a single column.
What is the easiest way to implement what I need?
EDIT:
So, after some tests, I am going for Andrei's suggestion and going "low-level", creating a custom cell extending AbstractCell<>. I could create an appropriate "render" function, that generates a list of html "button", and also attaches Javascript calls to my Java functions when triggering a Javascript event (onclick, onmouseover, onmouseout...).
It is working pretty well. For instance, by clicking the "+" button at the end a tag list, it calls a MenuBar widget that presents the list of tags that can be added.
But I am struggling to find a way to update the underlying data when adding a tag.
To sum up:
I have a CustomData class that represents the data I want to display in each line of the table. It also contains the list of tags as a Set.
ModelTable (extends DataGrid) is my table.
CustomCell (extends AbstractCell) can renders the list of tags as several buttons on a line.
A click on a "+" button in a cell makes a AddTagMenu popup drop down, from which I can click on the tag to add.
How do I update the content of the cell?
I tried playing around with onBrowserEvent, onEnterKeyDown, bus events... with no success. At best I can indeed add a tag element to the underlying object, but the table is not updated.
It's not possible to meet your requirements without going really "low-level", as you call it.
It's relatively easy to create a cell that would render tags exactly as you want them. Plus icon is also easy, if this is the only action on the cell. However, it is very difficult to make every tag within a cell an interactive widget, because the DataGrid will not let you attach handlers to HTML rendered within a cell. You will need to supply your own IDs to these widgets, and then attach handlers to them in your code. The problem, however, is that when the DataGrid refreshes/re-renders, your handlers will most likely be lost. So you will have to attach them again to every tag in every cell on every change in the DataGrid.
A much simpler approach is to create a composite widget that represents a "row", and then add these "rows" to a FlowPanel. You can easily make it look like a table with CSS, and supply your own widget that looks like a table header. You will need to recreate some of the functionality of the DataGrid, e.g. sorting when clicked on "column" header - if you need this functionality, of course.
As you have already noted, using CompositeCell could be a way to get what you want.
The idea is to create a cell for every tag and then (during rendering) decide which one should be shown (rendered). Finally combine all those cells into one by creating a CompositeCell.
The main disadvantage of this solution is that you need to know all possible tags before you create a DataGrid.
So, if you have a fixed list of possible tags or can get a list of all existing tags and this list is reasonably small, here is a solution.
First, we need to know which tag is represented by a column so I extended a Column class to keep information about a tag. Please, note that TagColumn uses ButtonCell and also handles update when the button is clicked:
public class TagColumn extends Column<DataType, String> {
private TagEnum tag;
public TagColumn(TagEnum tag) {
super(new ButtonCell());
this.tag = tag;
setFieldUpdater(new FieldUpdater<DataType, String>() {
#Override
public void update(int index, DataType object, String value) {
Window.alert("Tag " + getTag().getName() + " clicked");
}
});
}
public TagEnum getTag() {
return tag;
}
#Override
public String getValue(DataType object) {
return tag.getName();
}
}
Then create a cell for each tag (I have hard-coded all tags in a TagEnum):
List<HasCell<DataType, ?>> tagColumns = new ArrayList<HasCell<DataType, ?>>();
for(TagEnum tag : TagEnum.values())
tagColumns.add(new TagColumn(tag));
Now, the most important part: decide either to show the tag or not - overwrite render method of the CompositeCell:
CompositeCell<DataType> tagsCell = new CompositeCell<DataType>(tagColumns) {
#Override
protected <X> void render(Context context, DataType value, SafeHtmlBuilder sb, HasCell<DataType, X> hasCell) {
if(value.getTagList().contains(((TagColumn) hasCell).getTag()))
super.render(context, value, sb, hasCell);
else
sb.appendHtmlConstant("<span></span>");
}
};
This is important to always render any element (for example empty span when the tag should not be shown). Otherwise the CompositeCell's implemantation will get confused when accessing sibling elements.
Finally, full, working example code:
private DataGrid<DataType> getGrid() {
DataGrid<DataType> grid = new DataGrid<DataType>();
List<HasCell<DataType, ?>> tagColumns = new ArrayList<HasCell<DataType, ?>>();
for(TagEnum tag : TagEnum.values())
tagColumns.add(new TagColumn(tag));
CompositeCell<DataType> tagsCell = new CompositeCell<DataType>(tagColumns) {
#Override
protected <X> void render(Context context, DataType value, SafeHtmlBuilder sb, HasCell<DataType, X> hasCell) {
if(value.getTagList().contains(((TagColumn) hasCell).getTag()))
super.render(context, value, sb, hasCell);
else
sb.appendHtmlConstant("<span></span>");
}
};
Column<DataType, DataType> tagsColumn = new Column<DataType, DataType>(tagsCell) {
#Override
public DataType getValue(DataType object) {
return object;
}
};
grid.addColumn(tagsColumn, "Tags");
grid.setRowData(Arrays.asList(
new DataType(Arrays.asList(TagEnum.gwt)),
new DataType(Arrays.asList(TagEnum.table, TagEnum.datagrid)),
new DataType(Arrays.asList(TagEnum.datagrid, TagEnum.widget, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.table, TagEnum.widget, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.table, TagEnum.datagrid, TagEnum.widget, TagEnum.customCell))
)
);
return grid;
}
public class TagColumn extends Column<DataType, String> {
private TagEnum tag;
public TagColumn(TagEnum tag) {
super(new ButtonCell());
this.tag = tag;
setFieldUpdater(new FieldUpdater<DataType, String>() {
#Override
public void update(int index, DataType object, String value) {
Window.alert("Tag " + getTag().getName() + " clicked");
}
});
}
public TagEnum getTag() {
return tag;
}
#Override
public String getValue(DataType object) {
return tag.getName();
}
}
public class DataType {
List<TagEnum> tagList;
public DataType(List<TagEnum> tagList) {
this.tagList = tagList;
}
public List<TagEnum> getTagList() {
return tagList;
}
}
public enum TagEnum {
gwt ("gwt"),
table ("table"),
datagrid ("datagrid"),
widget ("widget"),
customCell ("custom-cell");
private String name;
private TagEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

Using a DataGrid in a HeaderPanel

Edit
Since no one has responded to my original question I think it is worthwhile adding a description of what I am attempting to accomplish, in addition to the existing description of how I have attempted to achieve my goal:
My objective is to create a DataGrid that will resize according to any change in size of its container. This is not difficult to do, but I have an additional requirement, which is to have Panel widgets above and below the DataGrid; these two Panel widgets will contain widgets that are fixed in size (e.g., a row of buttons or text input widgets). My expectation was that a HeaderPanel would be perfect for this, but this doesn't seem to work (as can be seen in my original question, below). So ... an alternative to my original question ("why doesn't this work") is: what is the best way to implement this requirement?
My original question:
I have a DataGrid in the content area of a HeaderPanel, but the detail lines in the DataGrid are not being displayed (the DataGrid column headings are showing, however). Is there an issue with using a DataGrid in the content area of a HeaderPanel? Or is this a simple misuse of the widgets? I'm adding the HeaderPanel to the RootLayoutPanel, which should provide the necessary resize notification (I think). Here is my UiBinder code:
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:c='urn:import:com.google.gwt.user.cellview.client'>
<g:HeaderPanel>
<g:SimplePanel/>
<g:ResizeLayoutPanel>
<c:DataGrid ui:field='dataGrid'/>
</g:ResizeLayoutPanel>
<g:HorizontalPanel>
<g:Button
ui:field='addRecordButton'
text='Add Record'/>
<g:Label ui:field='numberOfRecordsLabel'/>
</g:HorizontalPanel>
</g:HeaderPanel>
</ui:UiBinder>
and here is the Java code:
public class TempGWT implements EntryPoint {
#UiField
Button addRecordButton;
#UiField
DataGrid<Record> dataGrid;
#UiField
Label numberOfRecordsLabel;
private ArrayList<Record> _recordList;
interface TempGWTBinder extends UiBinder<Widget, TempGWT> {
}
private static class Record {
private String _field1;
}
#Override
public void onModuleLoad() {
_recordList = new ArrayList<Record>();
TempGWTBinder binder = GWT.create(TempGWTBinder.class);
Widget widget = binder.createAndBindUi(this);
Column<Record, String> field1Column = new Column<Record, String>(new TextInputCell()) {
#Override
public String getValue(final Record record) {
return record._field1;
}
};
dataGrid.addColumn(field1Column, "Field 1");
RootLayoutPanel.get().add(widget);
}
#UiHandler("addRecordButton")
public void onAddRecordButtonClick(final ClickEvent event) {
Record record = new Record();
record._field1 = "Record " + (_recordList.size() + 1);
_recordList.add(record);
dataGrid.setRowData(_recordList);
numberOfRecordsLabel.setText("Records:" + _recordList.size());
}
}
I've attempted to trace the execution and, although I'm not certain, it looks as though the following happens when I change the size of the browser window and the "resize" request is received by the DataGrid (I've skipped some of the "unimportant" methods):
DataGrid#onResize
HeaderPanel#forceLayout
ScrollPanel#onResize
The DataGrid object contains a HeaderPanel, which contains the headings for the DataGrid and a ScrollPanel. I don't know whether this is the key to the problem, but the ScrollPanel in the DataGrid's HeaderPanel contains a DataGrid$TableWidget object, and TableWidget does not implement RequiresResize; the ScrollPanel#onResize method only sends the resize to its child if the child implements RequiresResize.
The Tables and Frames section of the GWT Developer's Guide makes it clear that I just needed to use a width/height of 100% for the DataGrid! Like so:
<c:DataGrid
ui:field='dataGrid'
width='100%'
height='100%'/>

calling a child page component from parent page component in wicket

I have a problem which I tried to explained in the Image.I hope that will help all to understand what I need.
My Base Page is like this (menuNavPanel is the tree panel):
<div class="colContainer">
<div class="leftColumn" >
<div wicket:id="menuNavPanel"></div>
</div>
<div class="rightColumn">
<wicket:child/>
</div>
</div>
And Ny BIA Page which is a child of Base Page is like this:
<wicket:extend>
<div wicket:id="bodyPanel"></div>
</wicket:extend>
in my Tree Panel, when I click on a node the code is this:
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
// I want to call bodyPanel fo child page passing the unitObject param
}
Now, How can I call bodyPanel fo child page passing the unitObject param from the tree panel of the parent page?
Am I been able to express my problem? Hoping to get some help :)
Instead of doing the override method, upgrade to Wicket 1.5 and utilize the new event bus to communicate between your components. You can create a custom, type-safe, event that is specific to your component's use case: for example "ItemAddedToShoppingCart" or "GlobalThermoNuclearWarStarted".
The linked article in the 1.5 migration guide provides enough information on how to set up things.
I'm not sure I understand que question correctly. Your BasePage defines a left column with the TreePanel and lets subclasses expand themselves inside the right column div. You usually put a BodyPanel inside BasePages's subclasses. And now you want to invoke a BodyPanel's method on some event on the TreePanel.
You could do it with an overridable method on BasePage, which would be called in TreePanel through getPage(). Your child pages would override that method, and its implementation would call the BodyPanel they're holding.
public class BasePage ... {
// Hook
public void treePanelSelected(Object someObject) { }
...
}
public class ChildPage extends BasePage ... {
BodyPanel bodyPanel;
#Override
public void treePanelSelected(Object someObject) {
bodyPanel.selectionChanged/(someObject);
}
...
}
public class TreePanel ... {
...
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
((BasePage)getPage()).treePanelSelected(unitObject);
}
}
From my ignorance on your specific needs and details of implementation, I don't see why is subclassing the BasePage necessary. You could add the BodyPanel right there in the BasePage and control it from the same class.
Thanks all, After reviewing all the nice options I finally opted out for the event bus way defined by martijn. What I did is I have created an event payload and connected the panels for the talking. I also needed to pass the selected Id / entity to the receiving panel.
Is there a way to set a compound property model of the receiving panel according to the model of the tree element so that I don't need to do the model manually ?
I did like this for the time being:
public class TreeNodeClickUpdate {
private final AjaxRequestTarget target;
private final long selectedId;
/**
* Constructor
*
* #param target
* #param selectedId
*/
public TreeNodeClickUpdate(AjaxRequestTarget target, long selectedId)
{
this.target = target;
this.selectedId = selectedId;
}
/** #return ajax request target */
public AjaxRequestTarget getTarget()
{
return target;
}
public long getSelectedId() {
return selectedId;
}
}
On the sender side I've done like this:
send(getPage(), Broadcast.BREADTH,
new TreeNodeClickUpdate(target, unitObject.getId()));
And on the receiving end I got it like this:
#Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof TreeNodeClickUpdate)
{
TreeNodeClickUpdate update = (TreeNodeClickUpdate)event.getPayload();
setSelectedId(update.getSelectedId()); //sets to id field of panel
update.getTarget().add(this);
}
}
and for just as an example in my receiving panel, to get the value I have created a label like this:
label = new Label("label",new PropertyModel<BiaHomePanel>(this,"selectedId"));
Later, in reality I want to get information from the entity and show in form. Is there a nice way to pass models in a better way or I should pass as a parameter in event payload.
There are two ways to do this. One is cleaner, but requires more code. The other is quick and dirty.
Method 1: (Good)
Since your parent page is being extended, you can provide an abstract method in the parent like
protected abstract WebMarkupContainer getBodyPanel();
that is implemented in your child page and returns the appropriate panel. Then, you can call that method from the panel in your parent page. This is similar to the overrideable method suggested by the other user.
Method 2: (Bad)
The Wicket Component Hierarchy is shared between the parent and child pages. So, if you make sure that your bodyPanel has a unique wicketId and is added directly to the root of the page, you can probably just call
get("bodyPanelId");
and it will return the proper panel.
When I was facing the problem, I thought of two ways to solve this (pre 1.5):
a) implement a variation of the observer pattern to notify other component of events like outlined here: Realising complex cross-component ajax actions in wicket - The observer way
b) using wicket visitors to traverse the component tree doing the same.
I decided to go for variant a) but this introduces coupling from your component to your page-implementation which leads to problems when testing panels on their own. So maybe b) might be the better idea but since my application is running quite smoothly with a) implemented and the next big step will be switching over to 1.5 and the event bus, I haven't yet tried b).

GWT adding a ClickHandler to a DOM element

lets say i have a custom widget which has a ClickHandler. Here's the example:
public class TestWidget extends Composite {
private static TestWidgetUiBinder uiBinder = GWT
.create(TestWidgetUiBinder.class);
interface TestWidgetUiBinder extends UiBinder<Widget, TestWidget> {
}
#UiField
Button button;
public TestWidget(String firstName) {
initWidget(uiBinder.createAndBindUi(this));
button.setText(firstName);
}
#UiHandler("button")
void onClick(ClickEvent e) {
Window.alert("Hello!");
}
}
When i try to add this Widget like this:
TestWidget testWidget = new TestWidget("myTestWidget");
RootPanel.get().add(testWidget);
everything is fine. If i click on my button i get the message i expect.
However if i add it like this:
TestWidget testWidget = new TestWidget("myTestWidget");
RootPanel.getBodyElement().appendChild(testWidget.getElement());
my click event is not being fired. I'm struggeling to understand why.
It would be nice if someone could explain this to me or link me to an resource where i can read this up. Finally i would like to know if it is possible to add the clickhandler afterwards i appended the child event and if that way is recommended. Thanks it advance for help.
kuku
When you call add(), Widget.onAttach() is called on the widget that is being added to the panel. onAttach does some work to register the widget to receive events. appendChild() simply attaches one DOM element to another and does nothing else. You should be able to get events working in the second case by doing this:
Element element = testWidget.getElement();
RootPanel.getBodyElement().appendChild(element);
DOM.sinkEvents(element,
Event.getTypeInt(ClickEvent.getType().getName())
| DOM.getEventsSunk(element);
However, I haven't tested this and I wouldn't recommend that you use it in a real application. Using add() is definitely preferred, using appendChild() in this way has no advantages and may lead to unexpected behaviour.

Wrapping pre-made elements using GWT?

I've been working with GWT for awhile, I can't find a way to integrate it with a preexisting website which is a real downer. My page content is already generated for me using jsp, like:
<div id='A'></div>
<div id='B'></div>
etc.
there is no way for me to do something like this though:
public void onModuleLoad() {
SimplePanel spA = new SimplePanel(
Document.getElementById("A"));
spA.add(new Label("hello"));
SimplePanel spB = new SimplePanel(
Document.getElementById("B"));
spB.setWidth("200px");
etc ..
}
seems like there's no way to just wrap a pre-existing element. Is this true, or am I missing how to do this? I need be able to wrap a bunch of elements like this, to manipulate them later on. I see TextBox, Button, a few other classes have wrap() methods, however nothing like that exists for elements,
Thanks
There is a way to wrap existing DOM elements, like Label's wrap() method. For example:
Label label = Label.wrap(DOM.getElementById("A"));
label.setText("Foo!");
Other GWT classes can wrap DOM elements too, like Button, and CheckBox using its constructor.
Use HTMLPanel:
class MyPanel extends HTMLPanel {
private SimplePanel a = new SimplePanel();
private SimplePanel b = new SimplePanel();
public MyPanel() {
super("<div id="a"></div><div id="b"></div>);
addAndReplaceElement(a, "a");
addAndReplaceElement(b, "b");
}
}