I'm trying to write a simple application to display chart data. I want to display some data as soon as the user loads the page, so I'm getting data & drawing tables inside of the Runnable as described in the gwt-visualization Getting Started.
Things seem to work alright, except charts tend to get loaded more than once. Below is my onModuleLoad().
private final StatisticsServiceAsync statisticsService = GWT.create(StatisticsService.class);
GWTBeanFactory factory = GWT.create(GWTBeanFactory.class);
DataTable locationData;
AnnotatedTimeLine atl;
GeoMap usMap;
TextBox storeField;
Button log10Button;
DateRange durationChartRange;
String eusrJson = null;
Button b;
HTML last1000Html;
public void onModuleLoad() {
storeField = new TextBox();
storeField.setText("Enter a store");
storeField.addKeyDownHandler(new MyKeyHandler());
b = new Button("Get Stats!");
log10Button = new Button("Show Log10 Scale");
log10Button.addClickHandler(new Log10ClickHandler());
b.addClickHandler(new MyClickHandler());
last1000Html = new HTML();
getLast1000Avg();
Runnable onLoadCallback = new Runnable() {
public void run() {
storeDurationData = DataTable.create();
storeDurationDataLog10 = DataTable.create();
RootPanel.get("storeDurationDiv").add(storeField);
RootPanel.get("storeDurationDiv").add(b);
RootPanel.get("storeDurationDiv").add(log10Button);
RootPanel.get("storeDurationDiv").add(last1000Html);
log10Button.setVisible(false);
// Get initial Data
getAvgByRegion();
getLast1000Avg();
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
#Override
public boolean execute() {
getLast1000Avg();
return true;
}
}, 5000);
}
};
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback, AnnotatedTimeLine.PACKAGE);
VisualizationUtils.loadVisualizationApi(onLoadCallback, GeoMap.PACKAGE);
}
All of the "simple" elements seem to get populated correctly, as the Button, HTML, and TextBox all get placed appropriately (which used to be inside of run, they're where they are now as a result of debugging previous errors). However, the GeoMap gets placed twice, and looking at the logging you can tell that the Runnable's run is being executed at least twice, which seems reasonable, but I don't know how to keep it from adding twice.
I'm probably screwing up something with the Async stuff, but I'm new and confused. Below is my getAvgByRegion() method:
private void getAvgByRegion() {
statisticsService.getEusrForRegion(new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
System.out.println(":(");
}
#Override
public void onSuccess(String result) {
createLocTable();
DataTable dt = parseEusrLocations(result);
usMap = new GeoMap(dt, createGeoMapOptions());
usMap.setSize("800px", "600px");
RootPanel.get("storeDurationDiv").add(usMap);
}
});
}
Any advice on how best to work with GWT is welcome.
So, you call VisualizationUtils.loadVisualizationApi twice, so the onLoadCallback will be run twice (I don't know GWT Google Apis, this is a supposition).
onLoadCallback calls getAvgByRegion, so that one will get called twice too; and it gets data and in the callback creates a new GeoMap and adds it to the RootPanel.get("storeDurationDiv"), so you get two GeoMaps on the screen.
The other widgets(storeField, etc.) are created only once, so adding them repeatedly is not a problem (except performance-wise), as they'll first be removed from their current parent before being added to the new one (which in this case is the same)
Related
I am quite new to Wicket. I am adding a model to a sub-panel(ChartPanel) from a main panel (MainPanel) on a button click.
MainPanel.java
On button click, I am re-adding the chartPanel after I change its model. Following is the code I am using in the buttonClick of the MainPanel. Here the onRenderAnnotations event is generated on some click in the UI.
#OnEvent
public void onRenderAnnotations(RenderAnnotationsEvent aEvent)
{
LOG.trace("clicked on the annotation");
renderChart( aEvent.getRequestHandler());
}
private void renderChart(IPartialPageRequestHandler aRequestHandler)
{
MultiValuedMap<String, Double> recommenderScoreMap = getLatestScores(aRequestHandler);
Map<String,String> curveData = new HashMap<String,String>();
LearningCurve learningCurve = new LearningCurve();
for (String recommenderName : recommenderScoreMap.keySet()) {
String data = recommenderScoreMap.get(recommenderName).stream().map(Object::toString)
.collect(Collectors.joining(", "));
curveData.put(recommenderName,data);
learningCurve.setCurveData(curveData);
learningCurve.setMaximumPointsToPlot(MAX_POINTS_TO_PLOT);
}
chartPanel.setDefaultModel(Model.of(learningCurve));
// to avoid the error, A partial update of the page is being rendered
try {
aRequestHandler.add(chartPanel);
}
catch (IllegalStateException e) {
LOG.warn("Not updating the chart. " + e.toString());
setResponsePage(getPage());
}
}
ChartPanel.java
After this in the chartPanel, I want to use the updated model to add component inside the chartpanel. What would be the best way to do that?
I want to do something like this in the class ChartPanel:
#Override
protected void onRender()
{
super.onModelChanged();
LearningCurve newLearningCurve = getModel().getObject();
requestTarget = ???
String js = createJavascript(newLearningCurve);
requestTarget.prependJavascript(js);
}
My question is, in the above code how to get the request target since it is not an ajax request neither do I get it in the arguments. Should I use some other function where I also get a requestTarget. But I want it to be called every time the model of ChartPanel is updated from anywhere.
Pardon my ignorance. I have been trying for a few days but I am still stuck. I tried to explain it enough but if any information is missing, please comment and I will add it right away.
Thanks.
You should override renderHead() instead:
#Override
public void renderHead(IHeaderResponse response)
{
super.renderHead(response);
response.render(OnLoadHeaderItem.forScript(
createJavascript(newLearningCurve)));
}
This way your chart will be shown correctly regardless whether it was added due to an AjaxRequest or simply when the page is rerendered.
There are a lot of questions and answer around concurrency, and mine could be similar to others, but for me it's not a duplicate as for some reason I must be missing something and hope to get some advice...
My question is more one where I need a second pair of eyes to point out what I'm doing incorrectly to enable my code to run in a background thread, but also updated the GUI, without freezing it.
Initially, a PDF file is uploaded to the application, using a task in a thread.
This works fine.
A progress bar is displayed, which animates without issue:
uploadFile()
public void uploadFile(File fileToProcess) {
fileBeingProcessed = fileToProcess;
Task<Parent> uploadingFileTask = new Task<Parent>() {
#Override
public Parent call() {
try {
progressBarStackPane.setVisible(true);
pdfPath = loadPDF(fileBeingProcessed.getAbsolutePath());
createPDFViewer();
openDocument();
} catch (IOException ex) {
java.util.logging.Logger.getLogger(MainSceneController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
uploadingFileTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
fileHasBeenUploaded = true;
progressBarStackPane.setVisible(false);
uploadFilePane.setVisible(false);
tabPane.setVisible(true);
/* This is where I am getting issue, more so in createThumbnailPanels() */
setupThumbnailFlowPane();
createThumbnailPanels();
/****** ^^^^^^^^^^^^ ******/
}
});
uploadingFileTask.setOnFailed(evt -> {
uploadingFileTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(uploadingFileTask.getException().getSuppressed()));
});
Thread uploadingFileThread = new Thread(uploadingFileTask);
uploadingFileThread.start();
}
Once the document has been uploaded, it is displayed in a tab which allows the user to view the document.
There is a secondary tab, which, after upload, is disabled, until the completion of another task called createThumbnailPanelsTask;
However, before this task is ran, the FlowPane for the Thumbnail Panels is created. This seems to work without issue, and doesn't appear to be the cause of the GUI hanging (this is clearly a loop in createThumbnailPanelsTask, but for clarity I will show setupThumbnailFlowPane()):
setupThumbnailFlowPane()
public void setupThumbnailFlowPane() {
stage = model.getStage();
root = model.getRoot();
secondaryTabScrollPane.setFitToWidth(true);
secondaryTabScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
/**
This will be removed from here when refactored but for now it is here,
I don't think this is anything to do with my issue
**/
Set<Node> nodes = secondaryTabScrollPane.lookupAll(".scroll-bar");
for (final Node node : nodes) {
if (node instanceof ScrollBar) {
ScrollBar sb = (ScrollBar) node;
if (sb.getOrientation() == Orientation.VERTICAL) {
sb.setUnitIncrement(30.0);
}
if (sb.getOrientation() == Orientation.HORIZONTAL) {
sb.setVisible(false);
}
}
}
secondaryTab = new FlowPane();
secondaryTab.setId("secondaryTab");
secondaryTab.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, new CornerRadii(0), new Insets(0))));
secondaryTab.prefWidthProperty().bind(stage.widthProperty());
secondaryTab.prefHeightProperty().bind(stage.heightProperty());
secondaryTab.setPrefWrapLength(stage.widthProperty().intValue() - 150);
secondaryTab.setHgap(5);
secondaryTab.setVgap(30);
secondaryTab.setBorder(new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.NONE, CornerRadii.EMPTY, new BorderWidths(8, 10, 20, 10))));
secondaryTab.setAlignment(Pos.CENTER);
}
Finally, createThumbnailPanels() is called, which is where I believe I am getting the problem.
What is suppose to happen is, after the document has uploaded, the upload file pane is hidden, revealing the Viewer Tab, and also the Secondary Tab.
The secondary tab is disabled at this point, and also has a loading image (a gif) on the left side of it.
The intended behaviour, is that the createThumbnailPanels() task will run in the background, and until it is complete, the tab will remain disabled, however, during this time, the gif image will be rotating, giving the impression there is some loading occurring.
Once the loading has completed, the gif is removed, and the tab is enabled, allowing the user to navigate to it, and see the generated thumbnail panels.
This all works, however, as mentioned, the task is hanging the GUI:
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex = 0; thumbIndex < numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
model.setThumbnailAt(tn, thumbIndex);
eventHandlers.setMouseEventsForThumbnails(tb);
/*
I have added this in as I am under the impression that a task runs in a background thread,
and then to update the GUI, I need to call this:
*/
Platform.runLater(() -> {
secondaryTab.getChildren().add(tb);
});
}
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
/*
Further GUI modification run in setOnSucceeded so it runs on main GUI thread(?)
*/
secondaryTabScrollPane.setContent(secondaryTab);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
Everything, bar the GUI hanging while it creates the panels, works fine.
Once they've been created, the GUI can be controlled again, the loading gif has been removed, the tab is enabled and the user can navigate to it and view the panels.
Clearly, there is something I am missing about concurrency here.
As mentioned, I was under the impression that a Task runs in a background thread, so I'm a little confused by why it doesn't appear to be doing this. Again, clearly something I am missing.
I have read, and read, and read about concurrency, but just can't seem to work out where in my approach I have gone wrong. I am tempted to try using a Service, however, I feel that I am just over complicating things by considering that, and that there is clearly a simply way to do what I want to achieve.
Any help will be greatly appreciated... a push in the right direction, or some clarification on where I have gone wrong in my understanding.
Thanks in advance, no doubt it's something obvious that once sorted will help me avoid this issue in future!
UPDATED CODE
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex= 0; thumbIndex< numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
eventHandlers.setMouseEventsForThumbnails(tb);
model.setThumbnailAt(tn, thumbIndex);
model.setThumbnailPanels(tb);
}
setThumbnailPanelsToScrollPane();
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
// setThumbnailPanelsToScrollPane();
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
setThumbnailPanelsToScrollPane()
public void setThumbnailPanelsToScrollPane() {
Task<Void> setThumbnailPanelsToScrollPaneTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
Platform.runLater(() -> {
secondaryTab.getChildren().addAll(model.getThumbnailPanels());
secondaryTabScrollPane.setContent(main.informationExtractionPanel);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
});
return null;
}
};
setThumbnailPanelsToScrollPaneTask.setOnFailed(evt -> {
setThumbnailPanelsToScrollPaneTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(setThumbnailPanelsToScrollPaneTask.getException().getSuppressed()));
});
Thread setThumbnailPanelsToScrollPaneThread = new Thread(setThumbnailPanelsToScrollPaneTask);
setThumbnailPanelsToScrollPaneThread.start();
}
FYI: If I call setThumbnailPanelsToScrollPane(); in the setOnSucceeded, it doesn't appear to work.
getChildren().add is running on the JavaFX GUI thread(thats what Platform.runLater does), but its only required to run it in a Platform.runLater if the parent that you add children to is connected to the root of the shown gui, that means you should be able to add children to a parent that is not connected to any root, and add the whole parent to the root at the end of the children addition process, if you're doing Platform.runLater in any asynchronous code it will run on the gui thread in your case it is in your asynchronous for loop adding ThumbnailPanels and if the number of them is large the gui will hang.
I have written a piece of code for downloading a file from internet (in background service) and showing the progress of download in a popup stage. The code compiles successfully and there is no runtime error. However no download takes place and progress indicator remains indeterminate.
The code is tailored for illustrating my point. Please have a look at it and let me understand where I have gone wrong.
Thanks!
public class ExampleService extends Application {
URL url;
Stage stage;
public void start(Stage stage)
{
this.stage = stage;
stage.setTitle("Hello World!");
stage.setScene(new Scene(new StackPane(addButton()), 400, 200));
stage.show();
}
private Button addButton()
{
Button downloadButton = new Button("Download");
downloadButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
FileChooser fileSaver = new FileChooser();
fileSaver.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "pdf"));
File file = fileSaver.showSaveDialog(stage);
getDownloadService(file).start();
}
});
return downloadButton;
}
private Service getDownloadService(File file)
{
Service downloadService = new Service()
{
protected Task createTask()
{
return doDownload(file);
}
};
return downloadService;
}
private Task doDownload(File file)
{
Task downloadTask = new Task<Void>()
{
protected Void call() throws Exception
{
url = new URL("http://www.daoudisamir.com/references/vs_ebooks/html5_css3.pdf");
// I have used this url for this context only
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
return null;
}
};
showPopup(downloadTask);
return downloadTask;
}
Popup showPopup(Task downloadTask)
{
ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.progressProperty().bind(downloadTask.progressProperty());
Popup progressPop = new Popup();
progressPop.getContent().add(progressIndicator);
progressPop.show(stage);
return progressPop;
// I have left out function to remove popup for simplicity
}
public static void main(String[] args)
{
launch(args);
}}
The line:
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
...doesn't provide you any information about the progress of your download (there is no callback or any other indication of its progress). It just downloads something without giving you feedback.
You will have to use something else that gives you feedback on the progress.
Take a look at this questions answers for solutions with feedback (it is for Swing, but you should be able to adapt them for JavaFX): Java getting download progress
You bind the ProgressIndicator's progress property to the Task's progress property, so that changes in the latter will be reflected in the former. However you never actually update your Task's progress.
If you want the progress indicator to show something, you're going to have to call updateProgress(workDone, max) within your task's body (or elsewhere). And that might be tricky if the download logic you're using doesn't give you any progress callbacks. (You could, perhaps, spawn a thread to repeatedly check the size of the file on the filesystem and use that as your current workDone; but you'd need to know what the eventual/complete size of the file would be in order to turn this into a percentage, which may or may not be easy.)
So I'm having a heck of a time creating a Datagrid with GWT. I've created my table according the the docs for GWT, and I've added my data, but I can't get it or the datagrid itself to show up at all. What am I missing? I feel I've been tearing my hair out over this. I feel like making an aysnc call might be an issue, but I get no errors. When i compile and execute this portion of my code nothing shows up on the screen and the area where the datagrid is supposed to be on the dock is empty. Am I forgetting something trivial?
static int orderID = 1001;
private static List<OrderLine> orderLineList = new ArrayList<OrderLine>();
final DataGrid<OrderLine> dgOrder = new DataGrid<OrderLine>();
dgOrder.setWidth("100%");
//set columns
TextColumn<OrderLine> orderLineIdColumn = new TextColumn<OrderLine>(){
#Override
public String getValue(OrderLine object) {
return Integer.toString(object.getOrderLineID());
}
};
dgOrder.addColumn(orderLineIdColumn, "OrderLine ID");
TextColumn<OrderLine> productColumn = new TextColumn<OrderLine>(){
#Override
public String getValue(OrderLine object) {
return getProductName(object.getProductNumber());
}
};
dgOrder.addColumn(productColumn, "Product");
TextColumn<OrderLine> quantityColumn = new TextColumn<OrderLine>(){
#Override
public String getValue(OrderLine object) {
return Integer.toString(object.getQuantity());
}
};
dgOrder.addColumn(quantityColumn, "Quantity");
// add data to datagrid
Ioma.dataservice.getOrderLines(orderID, new AsyncCallback<ArrayList<OrderLine>>(){
#Override
public void onFailure(Throwable caught) {// TODO Auto-generated method stub
System.out.println("error in retrieving GP.getOrderLines" + caught.toString());
}
#Override
public void onSuccess(ArrayList<OrderLine> result) {
// TODO Auto-generated method stub
//yes i realize I could also set it to "result" but I use the result in other places as well, I have also tried setting it to result with no success
orderLineList = result;
dgOrder.setRowData(0, orderLineList);
}
});
//add datagrid to the dock
dock.add(dgOrder, DockPanel.EAST);
//add dock to mainPanel
return dock;
When you use the DataGrid you need to give it an explicit size. For CellTable you don't need to so that's why it worked.
When you were using the DataGrid is was there but had both height and width of 0. So you either need to explicitly set the size:
DataGrid<OrderLine> grid = new DataGrid<OrderLine>;
grid.setWidth("500px");
grid.setHeight("600px");
dock.add(dgOrder, DockPanel.EAST);
or you can put the gird in a ProvidesResize widget. So in your case I believe you can make it work with a DockLayoutPanel as opposed to a DockPanel which is what you seem to be using.
I had a smilliar problem . Try to put the datagrid in a Panel that implements the ProvidesResize interface, like SimpleLayoutPanel. Also in my case it helped to set the size of the SimpleLayoutPanel.
Turns out my problem was my declaration.
final DataGrid<OrderLine> dgOrder = new DataGrid<OrderLine>();
should be
final CellTable<OrderLine> dgOrder = new CellTable<OrderLine>();
I can't find out why this works, but it does. I can see my table now. are you not supposed to explicitly call an instance of datagrid I wonder?
Is it possible to nest forms in Wicket that are independent of each other? I want to have a form with a submit button and a cancel button. Both buttons should direct the user to the same page (let's call it Foo). The submit button should send some info to the server first; the cancel button should do nothing.
Here's a really simplified version of my existing code:
Form form = new Form() {
public void onSubmit()
{
PageParameters params = new PageParameters();
params.put("DocumentID", docID);
setResponsePage(Foo.class, params);
}
};
DropDownChoice<String> ddc = new DropDownChoice<String>("name", new PropertyModel<String>(this, "nameSelection"), names);
ddc.setRequired(true);
final Button submitButton = new Button("Submit") {
public void onSubmit() { doSubmitStuff(true); }
};
final Button cancelButton = new Button("Cancel") {
public void onSubmit() { doSubmitStuff(false); }
};
form.add(ddc);
form.add(submitButton);
form.add(cancelButton);
form.add(new FeedbackPanel("validationMessages"));
The problem is, I just added a validator, and it fires even if I press the cancel button, since the cancel button is attached to the same form as everything else. This could be avoided if the cancel button were in a separate form. As far as I know, I can't create a separate form because — due to the structure of the HTML — the separate form would be under the existing form in the component hierarchy.
Can I make the forms separate somehow in spite of the hierarchy? Or is there some other solution I can use?
EDIT:
In response to Don Roby's comment, this is a bit closer to what my code looked like back when I was trying setDefaultFormProcessing():
Form<Object> theForm = new Form<Object>("theForm") {
public void onSubmit()
{
PageParameters params = new PageParameters();
params.put("DocumentID", docID);
setResponsePage(Foo.class, params);
}
};
final CheckBox checkbox = new CheckBox("checkbox", new PropertyModel<Boolean>(this, "something"));
checkbox.add(new PermissionsValidator());
theForm.add(checkbox);
final Button saveButton = new Button("Save") {
public void onSubmit()
{ someMethod(true); }
};
final Button cancelButton = new Button("Cancel") {
public void onSubmit()
{ someMethod(false); }
};
cancelButton.setDefaultFormProcessing(false);
theForm.add(saveButton);
theForm.add(cancelButton);
theForm.add(new FeedbackPanel("validationMessages"));
There is an even simpler solution: call the setDefaultFormProcessing method on the cancel button with false as a parameter:
cancelButton.setDefaultFormProcessing(false);
This way, clicking the cancel button will bypass the form validation (and model updating), directly calling the onSubmit function.
It is possible to "nest" forms in wicket.
See this wiki entry
for some notes on how it works and this wiki entry for how it interacts with validation.
But for what you're after, the answer from Jawher should have worked and is much simpler.
Look at this example code for hints on getting that working.
I'm wondering if you've simplified your code too far in this posting. Can you produce a sample small enough to post that definitely has the problem?