I'am populating a ListView with images.
In pseudocode:
populateItem(model){
load base64 from database
image.setDefaultModel(base64)
The image is just a webcomponent and in html it is just <img src="">
How can i show a indicator while the image is loaded?.
I first thought of adding IAjaxIndicatorAware but this triggers the indicator when the image is doing an AjaxRequest.
Since you seem to load and display the image as a Base64 src it will directly get send in the html response and not loaded later (in contrast to images with a src that links to another URI).
You could wrap the image in an AjaxLazyLoadPanel.
This will first display an AjaxIndicator while the content is generated and get later replaced by the actual loaded content once it is done loading/generating:
edit
I got an Exception : Component must be applied to a tag of type [img].
i didn't consider that problem. AjaxLazyLoadPanel allways uses a <div> as a html tag to display the component it loads. To display a base 64 image you would need to wrap it in another Panel:
public class Base64ImagePanel extends Panel {
public Base64ImagePanel(String wicketId, String base64Data, String contentType) {
super(wicketId);
WebMarkupContainer image = new WebMarkupContainer("image") {
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
checkComponentTag(tag, "img");
tag.put("src", "data:" + contentType + ";base64," + base64Data);
}
}
add(image);
}
}
Base64ImagePanel.html:
<wicket:panel>
<img wicket:id="image"></img>
</wicket:panel>
And then use that wrapper Panel in the AjaxLazyLoadPanel:
add(new AjaxLazyLoadPanel("imageLazyLoad") {
#Override
public Component getLazyLoadComponent(String id) {
//load your actual base64 from database, i use some example Strings for demonstration in the following line
Base64ImagePanel imagePanel = new Base64ImagePanel(id, "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", "image/png");
return imagePanel;
}
});
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;
}
}
Since the pages I am going to display contain a lot of text, which I want to pre-format in their own text-files, I do the following:
execute: onModuleLoad()
- make an Async call to the server and load
the text files which are html-formatted
The code looks like this:
homeAsync.load(new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
contentHome.add(new HTML("<h1>FAIL</h1>something went wrong"));
caught.printStackTrace();
}
public void onSuccess(String result) {
contentHome.getElement().setId("inner");
contentHome.add(new HTML(result));
//RootPanel.get("content").add(new HTML(result));
}
});
Now.. inside those text-files are some customized tags:
<!graph>
which I want to replace with some GWT widgets. My problem is, that I just don't know how I could accomplish that easily.
I could do a work around like:
- load text part 1 (async call)
- insert graph for part 1
- load text part 2 (asynca call)
- insert foobar
- and so on ..
I don't even know if this would work but to be honest.. that's simply too ugly ^^
Any suggestions for me?
Knowing the part1 and part2 two makes things much clear.
Yes you can render all the HTML content to the widget (But in a Safe way,Yes Talking about SafeHtml).
And avoid two RPC calls to fetch the strings from DB.If you know part1 and part2 load the string at a time (with an Identifier and split it on client side).
Use HTMLPanel as a template.
An example:
String html = "<div id='one' style='border:3px dotted blue;'></div>"
+ "<div id='two' style='border:3px dotted green;'></div>";
HTMLPanel panel = new HTMLPanel(html);
panel.setSize("200px", "120px");
panel.addStyleName("demo-panel");
panel.add(new Button("Do Nothing"), "one");
panel.add(new TextBox(), "two");
RootPanel.get("demo").add(panel);
You can use following methods also:
Label w = new Label();
w.getElement().setInnerHTML(html);
w.getElement().setInnerSafeHtml(safeHtml);
Currently, I have a TabLayoutPanel with a few tabs, inside of each tab is a set of breadcrumbs. I would like to be able to display my breadcrumbs right next to the tabs (inside the TabBar itself). I haven't yet seen any instance of someone having done this, and I'm beginning to believe I might end up rewriting their TabLayoutPanel class myself and implementing that where needed, but obviously I'd rather not go that route unless there's no alternative.
Anyone have any guidance on this?
Just came across the same problem. Here are most of the relevent code snippets. I added a Unicode arrow when the tab was selected, and removed it when the tab was deselected.
private final String htmlStr ="\u25bb";
private String getTabTitle(String html){
// Designed to work for this example input.
//If you pass a different string you will need to do different matching.
if(html.indexOf("\u25bb") < 0)
return html;
else
return html.substring(html.indexOf("\u25bb")+1);
}
#Override
public void onBeforeSelection(BeforeSelectionEvent<Integer> event) {
if(!getSelectedIndex().equals(event.getItem())) {
notifyCurrentTabOfClosing();
selectedIndex = event.getItem();
String tabtitle = getTabTitle(getTabBar().getTabHTML(selectedIndex));
getTabBar().setTabHTML(selectedIndex, htmlStr+tabtitle);
}
}
}
public void selectTab(Widget widget) {
int widgetIndex = getWidgetIndex(widget);
selectTab(widgetIndex);
String tabtitle = getTabTitle(getTabBar().getTabHTML(widgetIndex));
getTabBar().setTabHTML(widgetIndex, htmlStr+tabtitle);
}
I am trying to create a ListBox using GWT. I am using UiBinder to create the field.
I would like to set a default text on the list box and when a user clicks on the box, it should show me the list items. Once again, if user has not selected any option, it should show me the default text again.
Any way to do this either using Uibinder or some ListBox methods?
If I understand correctly you want a value to show but when the user clicks on the list it disappears and shows you the list items?
As far as I know there is no option to that natively.
What you can do is add the first item to hold your default value.
You can do this grammatically by using addItem in code or using:
<g:Listbox>
<g:item value="-1">Default text</g:item>
</g:Listbox>
works with gwt 2.1+
The value can still be selected.
You can choose to ignore it or add an attribute "disabled" with value "disabled" to the option element:
listbox.getElement().getFirstChildElement().setAttribute("disabled" ,"disabled" )
hope it helps a bit :)
You can also use a renderer to control what is shown if 'Null' is selected.
(Inspired by: How do I add items to GWT ListBox in Uibinder .ui.xml template ?)
private class SimpleRenderer implements Renderer<T>{
private String emptyValue = "Select a value";
#Override
public String render(T val) {
if(val == null) {
return emptyValue;
}
return val.toString();
}
#Override
public void render(T val, Appendable appendable) throws IOException {
appendable.append(render(val));
}
public void setEmptyValue(String emptyValue) {
this.emptyValue = emptyValue;
}
}