How to start a file download in vaadin without button? - vaadin

I know that it is really easy to create a FileDownloader and call extend with a Button. But how do I start a download without the Button?
In my specific situation right now I have a ComboBox and the file I'd like to send to the user is generated after changing its value, based on the input. The file should be sent immediately without waiting for another click. Is that easily possible?
Thanks
raffael

I found a solution myself. Actually two.
The first one uses the deprecated method Page.open()
public class DownloadComponent extends CustomComponent implements ValueChangeListener {
private ComboBox cb = new ComboBox();
public DownloadComponent() {
cb.addValueChangeListener(this);
cb.setNewItemsAllowed(true);
cb.setImmediate(true);
cb.setNullSelectionAllowed(false);
setCompositionRoot(cb);
}
#Override
public void valueChange(ValueChangeEvent event) {
String val = (String) event.getProperty().getValue();
FileResource res = new FileResource(new File(val));
Page.getCurrent().open(res, null, false);
}
}
The javadoc here mentions some memory and security problems as reason for marking it deprecated
In the second I try to go around this deprecated method by registering the resource in the DownloadComponent. I'd be glad if a vaadin expert comments this solution.
public class DownloadComponent extends CustomComponent implements ValueChangeListener {
private ComboBox cb = new ComboBox();
private static final String MYKEY = "download";
public DownloadComponent() {
cb.addValueChangeListener(this);
cb.setNewItemsAllowed(true);
cb.setImmediate(true);
cb.setNullSelectionAllowed(false);
setCompositionRoot(cb);
}
#Override
public void valueChange(ValueChangeEvent event) {
String val = (String) event.getProperty().getValue();
FileResource res = new FileResource(new File(val));
setResource(MYKEY, res);
ResourceReference rr = ResourceReference.create(res, this, MYKEY);
Page.getCurrent().open(rr.getURL(), null);
}
}
Note: I do not really allow the user to open all my files on the server and you should not do that either. It is just for demonstration.

Here is my work-around. It works like a charm for me. Hope it will help you.
Create a button and hide it by Css (NOT by code: button.setInvisible(false))
final Button downloadInvisibleButton = new Button();
downloadInvisibleButton.setId("DownloadButtonId");
downloadInvisibleButton.addStyleName("InvisibleButton");
In your theme, add this rule to hide the downloadInvisibleButton:
.InvisibleButton {
display: none;
}
When the user clicks on menuItem: extend the fileDownloader to the downloadInvisibleButton, then simulate the click on the downloadInvisibleButton by JavaScript.
menuBar.addItem("Download", new MenuBar.Command() {
#Override
public void menuSelected(MenuBar.MenuItem selectedItem) {
FileDownloader fileDownloader = new FileDownloader(...);
fileDownloader.extend(downloadInvisibleButton);
//Simulate the click on downloadInvisibleButton by JavaScript
Page.getCurrent().getJavaScript()
.execute("document.getElementById('DownloadButtonId').click();");
}
});

Related

Vaadin 11: refreshAll (again)

here is a good thread about DataProvider.refreshAll() on Vaadin 8.5.1, but it doesn't seem to work this way in Vaadin 11.
I used this starter app to play around. It displays some imaginary product data in a grid.
At first, I added a refresh command to SampleCrudView:
public HorizontalLayout createTopBar() {
...
HorizontalLayout topLayout = new HorizontalLayout();
Button btn = new Button("refresh");
btn.addClickListener(event -> dataProvider.refreshAll());
topLayout.add(btn);
...
return topLayout;
}
The folks from vaadin override getId() in their ProductDataProvider like this to use it as an object identifier:
#Override
public Integer getId(Product product) {
Objects.requireNonNull(product,
"Cannot provide an id for a null product.");
return product.getId();
}
That ProductDataProvider extends ListDataProvider, which is initialized on startup with data from MockDataService, so that we always deal with the same objects. I changed that:
public class MockDataService extends DataService {
...
#Override
public synchronized List<Product> getAllProducts() {
//added ->
MockDataGenerator.resetProductCounter(); //this one sets nextProductId = 1
products = MockDataGenerator.createProducts(categories);
products.stream().forEach(p -> System.out.println(p.getId() + ", " + p.getProductName()));
//<- added
return products;
}
So now you will get new Product instances within the same ID range every time you call getAllProducts():
public class ProductDataProvider extends ListDataProvider<Product> {
...
#Override
public Stream<Product> fetch(Query<Product, SerializablePredicate<Product>> query) {
//added ->
this.getItems().clear();
this.getItems().addAll(DataService.get().getAllProducts());
//<- added
return super.fetch(query);
}
So the point is, this doesn't work - the data in the grid is still the same after "refresh" has been clicked.
Any suggestions?
Regards,
m_OO_m
This is caused by a bug that was fixed a couple a days ago. The fix will be included in the next maintenance release.

Vaadin Grid middle mouse click

I'm trying to emulate normal browser behaviour in my vaadin grid, which includes middle mouse click to open in a new tab:
addItemClickListener(e -> {
boolean newTab = e.getMouseEventDetails().getButton() == MouseEventDetails.MouseButton.MIDDLE || e.getMouseEventDetails().isCtrlKey();
//open in window or new tab
});
However, the middle mouse button is not registered by vaadin. How could I get this to work?
That feature was included in vaadin-grid (which goes into Vaadin 10) and will not work in Vaadin 8.
For Vaadin 8, you can either intercept the event with some client-side extension, or use a ComponentRenderer for adding a Panel to each component (which works, but is not ideal because it degrades performance):
grid.addColumn(item->{
Panel p = new Panel(item.getName());
p.setStyleName(ValoTheme.PANEL_BORDERLESS);
p.addClickListener(ev->{
System.out.println(ev.getButtonName());
});
return p;
}).setRenderer(new ComponentRenderer());
A client-side extension, on the other hand, allows listening to javascript events (such as MouseEvent) and triggering a server event in response. Creating a extension is quite a complex topic (since it uses a part of the API that is normally hidden from the developer) but it allows direct access to rendered DOM, which is not possible otherwise.
The following resources from the documentation may give you a starting point:
Creating a component extension (which describes a simple extension with Java code only) and Integrating JavaScript Components and Extension (which explains how to add native JavaScript code to your extension).
How I solved the problem in my specific case:
Server side:
public class MyGrid<T> extends Grid<T> {
public MyGrid(String caption, DataProvider<T, ?> dataProvider) {
super(caption, dataProvider);
MiddleClickExtension.extend(this);
}
public static class MiddleClickExtension<T> extends AbstractGridExtension<T> {
private MiddleClickExtension(MyGrid<T> grid) {
super.extend(grid);
registerRpc((rowKey, columnInternalId, details) -> grid.fireEvent(
new ItemClick<>(grid, grid.getColumnByInternalId(columnInternalId), grid.getDataCommunicator().getKeyMapper().get(rowKey), details)),
MiddleClickGridExtensionConnector.Rpc.class);
}
public static void extend(MyGrid<?> grid) {
new MiddleClickExtension<>(grid);
}
#Override
public void generateData(Object item, JsonObject jsonObject) {
}
#Override
public void destroyData(Object item) {
}
#Override
public void destroyAllData() {
}
#Override
public void refreshData(Object item) {
}
}
}
Client side:
#Connect(MyGrid.MiddleClickExtension.class)
public class MiddleClickGridExtensionConnector extends AbstractExtensionConnector {
#Override
protected void extend(ServerConnector target) {
getParent().getWidget().addDomHandler(event -> {
if (event.getNativeButton() == NativeEvent.BUTTON_MIDDLE) {
event.preventDefault();
CellReference<JsonObject> cell = getParent().getWidget().getEventCell();
getRpcProxy(Rpc.class).middleClick(cell.getRow().getString(DataCommunicatorConstants.KEY), getParent().getColumnId(cell.getColumn()),
MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
}
}, MouseDownEvent.getType());
}
#Override
public GridConnector getParent() {
return (GridConnector) super.getParent();
}
public interface Rpc extends ServerRpc {
void middleClick(String rowKey, String columnInternalId, MouseEventDetails details);
}
}

Wait panel on smartgwt

Do you know any components in smartgwt in order to do some like this?
I´ve done it using several componets, Labels, Camva, Img, Layouts....
Thanks
Well, I have found this option SC.showPrompt and SC.clearPrompt but I think that it´s not possible add images into of panel.
I did this in the past you can have a try
public class Attente extends Window{
private Label message = new Label();
public String getMessage() {
return message.getTitle();
}
public void setMessage(String message) {
this.message.setContents(message) ;
}
private Img image = new Img("64/Wait-icon.png",64,64);
/**
* Instantie un nouveau attente.
*/
public Attente(){
this.setTitle("Opération en cours, veuillez patienter");
this.setShowHeader(true);
this.centerInPage();
this.setAutoCenter(true);
this.setWidth(300);
this.setHeight(140);
this.setShowCloseButton(false);
this.setShowMinimizeButton(false);
this.setShowMaximizeButton(false);
VLayout layout = new VLayout();
HLayout hLayout = new HLayout();
message.setHeight(15);
message.setAlign(Alignment.CENTER);
message.setStyleName("plVersionCatalogue");
hLayout.addMember(new LayoutSpacer());
hLayout.addMember(image);
hLayout.addMember(new LayoutSpacer());
layout.addMember(new LayoutSpacer());
layout.addMember(hLayout);
layout.addMember(new LayoutSpacer());
layout.addMember(message);
this.addItem(layout);
}
}
And in the code I had a singleton instance of this class which I can use like this when I need:
MyContext.getAttente().show();
or
MyContext.getAttente().hide();

Selenium2 WebDriver (Page factory) error after a postback (Element not found in the cache)

I'm using Selenium 2 tests (written in C#) that choose values from a "select" control.
Selection causes a post-back to the server, which updates the state of the page.
It s really frustrating because every PostBack occurs this exception
Element not found in the cache - perhaps the page has changed since it was looked up
Just to be precise i use Selenium 2 WebDriver (2.32.0.0)
And for my project i Use Pattern Page Factory
The code looks like that
class RegisterPersonelData
{
private IWebDriver driver;
[FindsBy(How = How.Id, Using = "ctl00_ContentMain_register1_txtName")]
private IWebElement txtLastname;
[FindsBy(How = How.Id, Using = "ctl00_ContentMain_register1_lstDrvLic")]
private IWebElement dlDrive;
private SelectElement selectDrive;
[FindsBy(How = How.Id, Using = "ctl00_ContentMain_register1_lstVeh")]
private IWebElement dlVehicule;
private SelectElement selectVehicule;
public RegisterPersonelData(IWebDriver driver)
{
this.driver = driver;
// initialize elements of the LoginPage class
PageFactory.InitElements(driver, this);
// all elements in the 'WebElements' region are now alive!
// FindElement or FindElements no longer required to locate elements
}
public void fillData(string lastname, string drive, string vehicule)
{
txtLastname.SendKeys(lastname);
this.selectDrive = new SelectElement(dlDrive);
selectDrive.SelectByText(drive);
selectVehicule = new SelectElement(dlVehicule);
IWait<IWebDriver> wait = new WebDriverWait(this.driver, TimeSpan.FromSeconds(Convert.ToInt32(ConfigurationManager.AppSettings["ExpliciteWait"])));
wait.Until(x => selectVehicule.Options.Count > 1);
selectVehicule.SelectByText(vehicule);
}
}
And here the code of main
class Program
{
static void Main()
{
IWebDriver driver = MyWebDriver.GetWebDriver(MyWebDriver.BrowserType.FIFREFOX);
driver.Navigate().GoToUrl("http://...");
...
registerPersonelData.fillData("lastname", "Permis B", "Auto");
}
}
This code doesn t work because one postback is triggered ...
I have try to use one explicite wait but it fails too !
Code use to retrieve one element with explicite wait
public static IWait<IWebDriver> GetWaitWebDriver(IWebDriver driver)
{
IWait<IWebDriver> wait = new WebDriverWait(driver, TimeSpan.FromSeconds(Convert.ToInt32(ConfigurationManager.AppSettings["ExpliciteWait"])));
return wait;
}
public static IWebElement GetElementAndWaitForIt(IWebDriver driver, By by)
{
return GetWaitWebDriver(driver).Until(x =>
{
return x.FindElement(by);
});
}
Someone has one idea to fix it ?
You can re-initialize the elements at the end of the fillData method.
PageFactory.InitElements(driver, this);
You could do it in the main as well with:
PageFactory.InitElements(driver, registerPersonelData);
You could also try adding the following to the field you need to reuse.
[CacheLookup]
I have done a lot of tries and finally i have found something usefull
public static void WaitForAnWebElementDisplayed(IWait<IWebDriver> wait, IWebElement webElement)
{
wait.Until(x => webElement.Displayed);
}
public static void WaitForAnWebElementEnabled(IWait<IWebDriver> wait, IWebElement webElement)
{
wait.Until(x => webElement.Enabled);
}
and Consequently i can wait that the load of page triggered after to have choosen one item in select option is completed !

smartGWT TileGrid::onKeyPress -- how to override Enter key, but keep default processing for other keys

I am loading file icons on a tile grid in a smartGWT project. When Enter key is pressed, I want to open the selected file for display.
When I override the onKeyPress handler, it does work, but the tile grid navigational behavior using left/right/up/down arrow keys is lost.
My question is.., how to retain the default processing behavior, while still override the Enter key.
tileGrid.addKeyPressHandler (new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent event) {
if (EventHandler.getKey().equals("Enter")) {
//do something special here
}
else {
**//TODO: do the default processing..**.
}
}
});
EDIT:
#Ras, here is the code that simulates the problem.
package com.rv.gwtsample.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.widgets.events.KeyPressEvent;
import com.smartgwt.client.widgets.events.KeyPressHandler;
import com.smartgwt.client.widgets.tile.TileGrid;
import com.smartgwt.client.widgets.tile.TileRecord;
/**
* #author rvnath
*
*/
public class MyTileGrid implements EntryPoint {
/* (non-Javadoc)
* #see com.google.gwt.core.client.EntryPoint#onModuleLoad()
*/
#Override
public void onModuleLoad() {
// TODO Auto-generated method stub
TileGrid grid = new TileGrid();
grid.setLeft(50);
grid.setTop(50);
grid.setWidth("300");
grid.setHeight("200");
DetailViewerField field = new DetailViewerField("Name");
grid.setFields(field);
grid.addKeyPressHandler(new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent event) {
if (event.getKeyName().equals("Enter"))
GWT.log("Enter pressed");
}
});
Record[] rec = new TileRecord[32];
for (int i=0; i<32; ++i) {
rec[i] = new TileRecord();
}
grid.setData(rec);
grid.draw();
}
}
If I disable the onKeyPress handler, arrow keys can navigate between the elements of the tile grid. If I enable, then the entire tile grid panel scrolls, instead of selection change.
Instead of using KeyPressHandler, try the KeyDownHandler, it works.
tileGrid.addKeyDownHandler(new KeyDownHandler() {
#Override
public void onKeyDown(KeyDownEvent event) {
if (EventHandler.getKey().equalsIgnoreCase("Enter")){
openModal(tileGrid.getSelectedRecord());
}
}
});
Tested with the latest 3.0 smartgwt build.
#Mupparthy, I've also implemented keyPressHandler() for TextAreaItem. I also had the same requirement that only delete & backspace keys were needed to be handled. What I did is, don't handle the else part. It automatically did default processing for other keys including all the arrow keys. So if it's not working for you, provide me a stand alone code so that we can make it work.

Resources