Dynamic image tooltip - wicket

I try to figure out how can I display dynamically a tooltip containing image from byte array.
I don't want to put image href or base64 content on start. It should work dynamically: if user hover something the tooltip will display and obtain image I have in byte array.

You need DynamicImageResource. Map it to an URL and override byte[] getImageData(IResource.Attributes attributes) abstract method that generates image data.
In your WebApplication class:
mount(new MountMapper("/images/my-tooltip", new DynamicImageResource("png") {
#Override
protected byte[] getImageData(IResource.Attributes attributes) {
// TODO your array with data
return new byte[];
}
}));

Related

Show loading Screen while Image is loading in wicket

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;
}
});

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;
}
}

Lazily Initialize/Load a GXT Widget When Its Parent Becomes Visible

I am very new to GXT (and GWT/GWTP for that matter). I want to know if I can lazily load a GXT (4.0) [custom] widget when a modal Dialog that contains its panel is displayed (dialog initially not shown and only appears when a button is clicked).
The reason I want this behavior is that this widget needs to load an applet HTML code which is not available when the page initially instantiates all the other field widgets/panels/dialogs. Therefore, I need to delay obtaining the applet code from another application until the dialog is explicitly appears.
Is this possible?
Lazy loading of images and urls loaded into a GWT Frame (IFrameElement). You can catch the event once say the image/url is loaded using the LoadHandler event.
private void loadLoginPage() {
final Frame frame = new Frame(url_base + url_login); // set the frames url
frame.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
// Get the document from the loaded frame (similar to RootPanel on main page)
Document doc = IFrameElement.as(frame.getElement()).getContentDocument();
// From here you can wrap widgets in the frame like this
final TextBox username = TextBox.wrap(doc.getElementById("username")); // from the html doc using id="username"
// grab a div element
DivElement div = DivElement.as(doc.getElementById("mydiv"));
// Create content to be added to the doc
ButtonElement button_elem = doc.createPushButtonElement();
Button button = Button.wrap(button_elem);
// attach to the document
div.appendChild(button.getElement());
}
});
// Attach to DOM
RootPanel.get("mything").add(frame);
}
And similar for image loading.
Image image = new Image(image_url);
image.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
// image is ready to be used.
}
});
// attach to DOM to initiate loading of the image
Hope this helps...

how to load a svg in gwt widget from an url

I have a servlet that delivers png and svg images. With png i have no problem:
Image image = new Image(GWT.getHostPageBaseURL() + token);
But how to get svg to work? I already added "lib-gwt-svg" to my dependencies. There is a SVGImage class:
SVGImage svg = new SVGImage(OMSVGParser.parse(???));
The parse takes a string. Is there a way to load a raw String from an URL?
Or how to get it to work (with or without "lib-gwt-svg")?
Update:
thx to Andrei Volgin: he pointed out that it should work with "Image" and it does (i just had to correct the mime type to "image/svg+xml"). But the scripts within svg-image don't work this way (it looks like the images is rendered as a normal bitmap image).
I need the image rendered as svg (with scripts).
If you use a URL to load an image, you don't need any libraries at all. And you don't need a servlet to deliver them. Just add images to your /war/images folder. Then, in your GWT code:
Image image = new Image();
image.setUrl("images/myImage.svg");
myPanel.add(image);
You may want to add some logic for browsers that do not support svg files.
I found a solution that works without any external library (like Andrei's Solution) but keeps also the embedded scripts working. I used the info from here - i used a HTMLPanel and loaded the image via "RequestBuilder":
String url = GWT.getHostPageBaseURL() + link.getToken();
RequestBuilder rB = new RequestBuilder(RequestBuilder.GET, url);
rB.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
//create Widget
chartImage = new HTMLPanel(response.getText());
//add to layout
layout.add(chartImage);
}
#Override
public void onError(Request request, Throwable exception) {
// TODO Auto-generated method stub
}
});

Overlay images in gxt Tree Panel

I'm developing a gxt application that provides a view for svn-like versioning: A TreePanel that displays a file system structure with files and folders having different states (new, modified, deleted, etc.).
I want to display a small overlay icon on each of the items to reflect its state. What I could do, is to create an icon for each state, but I don't want to do that, because I would end up in creating a large bundle of redundant images.
What would be the best way to implement overlay icon capabilities in trees or grids?
After checking the dom of the tree structure, I figured out that a tree icon is displayed using the background-url css attribute. To ensure the image is being displayed with correct size, the src attribute of the element is filled with a placeholder image url.
The trick is, to replace the placeholder image with the overlay icon, since it is displayed above the actual image.
To accomplish this, I extended ClippedImagePrototype to inject the url of the overlay image:
public class OverlayImagePrototype extends ClippedImagePrototype {
protected String overlayImageUrl = null;
public static AbstractImagePrototype create(ImageResource resource, String overlayUrl) {
return new OverlayImagePrototype(resource.getSafeUri(),
resource.getLeft(), resource.getTop(), resource.getWidth(),
resource.getHeight(), overlayUrl);
}
private OverlayImagePrototype(SafeUri url, int left, int top, int width,
int height, String overlayUrl) {
super(url, left, top, width, height);
this.overlayImageUrl = overlayUrl;
}
public ImagePrototypeElement createElement() {
ImagePrototypeElement imgEl = super.createElement();
if (overlayImageUrl != null) {
imgEl.setAttribute("src", overlayImageUrl);
}
return imgEl;
}
}
This is how I use OverlayImagePrototype in the IconProvider implementation:
tree.setIconProvider(new ModelIconProvider<ModelData>() {
public AbstractImagePrototype getIcon(ModelData model) {
return OverlayImagePrototype.create(model.get("icon"), model.get("overlayIconUrl"));
}
});