Printing gwt widget - gwt

Can anyone tell me how to print gwt widget?
I gont through the folowing thread and tried .
http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/eea48bafbe8eed63
Still I cant do that.I can able to print the widget without any css style.I want to include the css styles(css files) also.Thanks for your valuable help.

I have faced similar situation using the same link you have used.
It prints String, Widgets,Or Elements without any problem.
Solution I found is that you need some delay before you call Print.
So, There are few alternate solutions.
1. Write down a loop which causes delay of 2-3 seconds.
2. Use Time to get delay of 2-3 seconds
3. Use Window.Confirm to ask some question like 'Do you want to print ?'
Hope this will help to others

You can create your own class and on click of Print button in your application, by using native print method you can call Window.Print().
GWT UIObject can be used for this purpose and the method and it will be converted into string.
public class NTPrint {
/**
* If true, use a Timer instead to print the internal frame
*/
public static boolean USE_TIMER = true;
/**
* Time in seconds to wait before printing the internal frame when using Timer
*/
public static int TIMER_DELAY = 1;
public static native void it() /*-{
$wnd.print();
}-*/;
public static void it(String html) {
try{
buildFrame(html);
if (USE_TIMER) {
Timer timer = new Timer() {
public void run() {
printFrame();
}
};
timer.schedule(TIMER_DELAY * 1000);
} else {
printFrame();
}
}
catch (Throwable exc) {
CommonUtil.printStackTrace(exc);
Window.alert(exc.getMessage());
}
}
/**
* This method will be called when you pass Widget without style.
* #param uiObjects
*/
public static void it( UIObject...uiObjects) {
StringBuffer objString= new StringBuffer();
for(UIObject obj: uiObjects)
objString.append(obj.toString());
it("", objString.toString());
}
}
OnClick of Print button,
/**
* prints all forms
* #param documentInfo
*/
public static void printForms(List<FormTransaction> formTransactions, String documentInfo, List<CellTransaction> cellTransactionList, boolean isComment, Document document) {
String style = "<link rel='styleSheet' type='text/css' href='v4workflow/css/style.css'>"
+ "<link rel='styleSheet' type='text/css' href='v4workflow/css/gwtcontrols.css'>"
+ "<style type='text/css' media='print'>"
+ "#media print {"
+ ".footerText{font-size:13px; font-weight:normal;margin-top:0px;}"
+ ".break {page-break-after:always}"
+ "}" + "</style>";
List<UIObject> uiObjects = new ArrayList<UIObject>();
NTPrintLayout printLayout = new NTPrintLayout();
printLayout.setSelectedDocument(null);
uiObjects.add(createHeader());
NTPrint.it(style,uiObjects);
}
public static FlexTable createHeader(){
FlexTable flexTable = new FlexTable();
flexTable.setWidth("100%");
return flexTable;
}

Related

GWT get CellTable contents for printing or export

I have a GWT CellTable that gets populated using somewhat of a complicated and tedious process. I want the user to be able to print or export the data from that table.
I would rather not re-render the table contents for export since it is a tedious process.
How can I get the contents of all the rows from all the pages of my CellTable so I can put together a document for printing or export?
I'd be fine with a method of grabbing the actual HTML of the table, or an algorithm for iterating through and grabbing the rendered contents from cells. If someone has a better suggestion, that'd be appreciated as well.
It seems there is no viable way to get the CellTable to give me the data for export without re-rendering contents. Since that would cost the same execution time as doing it myself, I resorted to rendering it myself. I used the following code to render HTML and display it in a new popup for printing. The print() method gets called from my Print button.
/**
* Print in a new popup.
* http://www.coderanch.com/t/564198/GWT/GWT-injecting-HTML-text-browser
*/
public static native void printHTMLString(String htmlString)/*-{
var win = $wnd.open("_blank", "Print", "");
win.document.open("text/html", "replace");
win.document.write("<html><head></head><body>" + htmlString + "</body></html>");
win.document.close();
win.focus();
var headID = win.document.getElementsByTagName("head")[0];
var fileref = win.document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", "tables-min.css");
headID.appendChild(fileref);
win.print();
}-*/;
private void print() {
//get the list from the ColumnSortHandler, so it keeps the sorting on the screen
if (columnSortHandler.getList() == null || columnSortHandler.getList().isEmpty()) {
Window.alert("Nothing to print");
return;
}
SafeHtmlBuilder b = new SafeHtmlBuilder();
b.appendHtmlConstant("<table class=\"pure-table\">"
+ "<thead><tr><th>Timestamp</th><th>Type</th><th>User</th>"
+ "<th>Screen</th><th>Client</th></tr></thead>");
int count = 1;
for (Record r : columnSortHandler.getList()) {
b.appendHtmlConstant("<tr" + (count%2==0 ? ">" : " class='pure-table-odd'>"));
b.appendHtmlConstant("<td>");
b.appendEscaped(timestampFormat.format(timeStampColumn.getValue(r)));
b.appendHtmlConstant("</td>");
b.appendHtmlConstant("<td>");
b.appendEscaped(typeColumn.getValue(r));
b.appendHtmlConstant("</td>");
b.appendHtmlConstant("<td>");
b.appendEscaped(userColumn.getValue(r));
b.appendHtmlConstant("</td>");
b.appendHtmlConstant("<td>");
b.appendEscaped(screenColumn.getValue(r));
b.appendHtmlConstant("</td>");
b.appendHtmlConstant("<td>");
b.appendEscaped(clientColumn.getValue(r));
b.appendHtmlConstant("</td>");
b.appendHtmlConstant("</tr>");
count++;
}
b.appendHtmlConstant("</table>");
printHTMLString(b.toSafeHtml().asString());
}
To export XLS use the class below.
To print use gwt-print-it.
The class below do that without server side.
public class TableToExcel {
public static final <T> void save(final CellTable<T> table, String filename) {
final AnchorElement a = Document.get().createAnchorElement();
a.setHref("data:application/vnd.ms-excel;base64," + base64(table.getElement().getString()));
a.setPropertyString("download", (filename.endsWith(".xls") || filename.endsWith(".xlsx")) ? filename : filename + ".xls");
Document.get().getBody().appendChild(a);
Scheduler.get().scheduleEntry(new ScheduledCommand() {
#Override
public void execute() {
click(a);
a.removeFromParent();
}
});
}
private static native void click(Element elem) /*-{
elem.click();
}-*/;
public static native String base64(String data) /*-{
return btoa(data);
}-*/;
}
You can use getInnerHTML or getInnerText
for(int i=0; i<cellTable.getRowCount();i++)
{
TableRowElement rowElement = cellTable.getRowElement(i);
for(int j =0; j<rowElement.getCells().getLength(); j++)
{
//System.out.println( rowElement.getCells().getItem(j).getInnerHTML() );
System.out.println( rowElement.getCells().getItem(j).getInnerText() );
}
}
You could try the gwt-table-to-excel module.
The main point is that excel and other modern spreadsheet knows how to render an html table, so just open the dynamically built html table with the correct mime-type, and that's all.
I never used it, but the description sounds good.

Print generated pdf with GWT

I can open a generated jasper pdf report, in new tab with Window.open():
Window.open("report.pdf?traceCode=" + traceCode, "_BLANK", "");
If i want print the pdf file by clicking a button, what should i do? (Window.print() will print the entire page)
You can create your own class and on click of Print button in your application, by using native print method you can call Window.Print().
GWT UIObject can be used for this purpose and the method and it will be converted into string.
public class NTPrint {
/**
* If true, use a Timer instead to print the internal frame
*/
public static boolean USE_TIMER = true;
/**
* Time in seconds to wait before printing the internal frame when using Timer
*/
public static int TIMER_DELAY = 1;
public static native void it() /*-{
$wnd.print();
}-*/;
public static void it(String html) {
try{
buildFrame(html);
if (USE_TIMER) {
Timer timer = new Timer() {
public void run() {
printFrame();
}
};
timer.schedule(TIMER_DELAY * 1000);
} else {
printFrame();
}
}
catch (Throwable exc) {
CommonUtil.printStackTrace(exc);
Window.alert(exc.getMessage());
}
}
/**
* This method will be called when you pass Widget without style.
* #param uiObjects
*/
public static void it( UIObject...uiObjects) {
StringBuffer objString= new StringBuffer();
for(UIObject obj: uiObjects)
objString.append(obj.toString());
it("", objString.toString());
}
}
OnClick of Print button,
/**
* prints all forms
* #param documentInfo
*/
public static void printForms(List<FormTransaction> formTransactions, String documentInfo, List<CellTransaction> cellTransactionList, boolean isComment, Document document) {
String style = "<link rel='styleSheet' type='text/css' href='v4workflow/css/style.css'>"
+ "<link rel='styleSheet' type='text/css' href='v4workflow/css/gwtcontrols.css'>"
+ "<style type='text/css' media='print'>"
+ "#media print {"
+ ".footerText{font-size:13px; font-weight:normal;margin-top:0px;}"
+ ".break {page-break-after:always}"
+ "}" + "</style>";
List<UIObject> uiObjects = new ArrayList<UIObject>();
NTPrintLayout printLayout = new NTPrintLayout();
printLayout.setSelectedDocument(null);
uiObjects.add(createHeader());
NTPrint.it(style,uiObjects);
}
public static FlexTable createHeader(){
FlexTable flexTable = new FlexTable();
flexTable.setWidth("100%");
return flexTable;
}
You won't be able to call the print() method (or any other method for that matter) of the window object if that window contains non-HTML page.
Let the user download the file.
Refer this also

GWT - button inside v3 google maps infowindow

I am trying to figure out how to propagate events for components inside google maps InfoWindow.
I create anchor or a button and want to handle click event on any of those.
I have found solutions described here
and
here
but those both are using google maps wrappers for gwt.
I would like to avoid those libraries.
QUESTION:
Do you know any way how can I propagate those events from info window to some GWT panel which wraps google maps?
Based on code found here:
http://gwt-maps3.googlecode.com/svn/trunk/src/com/googlecode/maps3/client/
I have created this class that solves problem with using no external library (you have to take Only InfoWindowJSO source from link given)
And then instead passing InnerHtml as string to setContent... you just pass Widget element.
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;
public class InfoWindow
{
static class FakePanel extends ComplexPanel
{
public FakePanel(Widget w)
{
w.removeFromParent();
getChildren().add(w);
adopt(w);
}
#Override
public boolean isAttached()
{
return true;
}
public void detachWidget()
{
this.remove(0);
}
}
/** */
InfoWindowJSO jso;
/** If we have a widget, this will exist so we can detach later */
FakePanel widgetAttacher;
/** Keep track of this so we can get it again later */
Widget widgetContent;
/** */
public InfoWindow()
{
this.jso = InfoWindowJSO.newInstance();
}
/** */
public InfoWindow(InfoWindowOptions opts)
{
this.jso = InfoWindowJSO.newInstance(opts);
}
/** Detaches the handler and closes */
public void close()
{
this.detachWidget();
this.jso.close();
}
/** Detaches the content widget, if it exists */
private void detachWidget()
{
if (this.widgetAttacher != null)
{
this.widgetAttacher.detachWidget();
this.widgetAttacher = null;
}
}
/** */
public void open(JavaScriptObject map)
{
this.jso.open(map);
}
public void open(JavaScriptObject map, JavaScriptObject marker)
{
this.jso.open(map, marker);
}
/** */
public void setOptions(InfoWindowOptions value)
{
this.jso.setOptions(value);
}
/** */
public void setContent(String value)
{
this.widgetContent = null;
this.detachWidget();
this.jso.setContent(value);
}
/** */
public void setContent(Element value)
{
this.widgetContent = null;
this.detachWidget();
this.jso.setContent(value);
}
/** */
public void setContent(Widget value)
{
this.widgetContent = value;
this.detachWidget();
this.jso.setContent(value.getElement());
if (this.widgetAttacher == null)
{
// Add a hook for the close button click
this.jso.addListener("closeclick", new Runnable() {
#Override
public void run()
{
detachWidget();
}
});
this.widgetAttacher = new FakePanel(value);
}
else if (this.widgetAttacher.getWidget(0) != value)
{
this.widgetAttacher.detachWidget();
this.widgetAttacher = new FakePanel(value);
}
}
/** #return the widget, if a widget was set */
public Widget getContentWidget()
{
return this.widgetContent;
}
/** */
public JavaScriptObject getPosition()
{
return this.jso.getPosition();
}
/** */
public void setPosition(JavaScriptObject value)
{
this.jso.setPosition(value);
}
/** */
public int getZIndex()
{
return this.jso.getZIndex();
}
/** */
public void setZIndex(int value)
{
this.jso.setZIndex(value);
}
/** */
public void addListener(String whichEvent, Runnable handler)
{
this.jso.addListener(whichEvent, handler);
}
}
A. Browser events bubble all the way to the top of the DOM tree. You can attach your click handlers to a widget that is parent to both the maps InfoWindow and your widget. Then, when a user clicks on your button, you need to check for the source of event to make sure it came from your button.
public void onClick(final ClickEvent event) {
Element e = Element.as(event.getNativeEvent().getEventTarget());
// check if e is your button
}
B. You can create a regular GWT button, attach a ClickHandler to it. Do not put it inside the InfoWindow: place it on top it using absolute positioning and a higher z-index.
I use the static value nextAnchorId to uniquely generate IDs for each InfoWindow, and when the InfoWindow is ready (usually when you call infoWindow.open(map);), I get the anchor by element ID and add my click handler to it. This is kind of what Manolo is doing, but this implementation doesn't require gwtquery, which means that I can run my code in Super Dev Mode.
private static int nextAnchorId = 1;
public InfoWindow makeInfo() {
InfoWindowOptions infoWindowOptions = InfoWindowOptions.create();
FlowPanel infoContentWidget = new FlowPanel();
final String theAnchorId_str = "theAnchor" + nextAnchorId;
HTML theAnchor = new HTML("<a id=\"" + theAnchorId_str + "\">Click me!</a>");
infoContentWidget.add(theAnchor);
infoWindowOptions.setContent(infoContentWidget.getElement());
InfoWindow infoWindow = InfoWindow.create(infoWindowOptions);
infoWindow.addDomReadyListenerOnce(new InfoWindow.DomReadyHandler() {
#Override
public void handle() {
com.google.gwt.user.client.Element muffinButton = (com.google.gwt.user.client.Element) Document.get().getElementById(theAnchorId_str);
DOM.sinkEvents(muffinButton, Event.ONCLICK);
DOM.setEventListener(muffinButton, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
Window.alert("You clicked on the anchor!");
// This is where your click handling for the link goes.
}
});
}
});
nextAnchorId++;
return infoWindow
}
A very simple solution is to use gwtquery:
Identify the anchor in the map you want to add the click handler and define a css selector for that (for instance id=my_link)
Use gquery to locate it and to add the event.
$('#my_link').click(new Function() {
public boolean f(Event e) {
[...]
return false; //false means stop propagation and prevent default
}
});
Note that gwtquery is not a wrapper of jquery but an entire gwt implementation of its api, so including it in your project will not overload it, and the compiler will pick up just the stuff you use.

GWT 2.2.0 PopupPanel autoHide on TOUCH Events

PopupPanel is a class within GWT written (akhem) a long time ago (which is why it sucks so much) that allows for showing popups with content. One of the options is autoHide where if there's a certain event outside of the popup it closes the popup. It works well on anything EXCEPT Safari Mobil (SM). Reason is SM doesn't fire click events on touch. It fires touch events. PopupPanel is hard coded to look for ClickEvents.
Specifically, the code says:
case Event.ONMOUSEDOWN:
...
if (!eventTargetsPopupOrPartner && autoHide) {
hide(true);
...
Obviously this isn't complete and it should also include Event.ONTOUCHSTART
Problem is, all the methods and fields are private so I cannot add this functionality. That's a big boo-boo on the part of GWT team but not really a concern since I could just make my own class and copy contents of PopupPanel. The big problem is that nativeEventPreview doesn't capture Touch Events!
I tried adding the following to Event Preview the following:
private static NativePreviewHandler nativePreviewHandler = new NativePreviewHandler() {
public void onPreviewNativeEvent(NativePreviewEvent event) {
Event nativeEvent = Event.as(event.getNativeEvent());
switch (nativeEvent.getTypeInt()) {
case Event.ONTOUCHSTART:
case Event.ONMOUSEDOWN:
EventTarget target = nativeEvent.getEventTarget();
if (!Element.is(target) || !popup.getElement().isOrHasChild(Element.as(target))) {
popup.hide();
} break;
}
}
};
Where 'popup' is the PopupPanel I'm trying to get to close on outside touch events.
Sad thing is it works for mouse down when testing in any other browser on Earth, but not on iPad.
Another thing I tried was adding a TouchStartHandler to the glass of the PopupPanel (the gray looking thing behind it). I war hoping that I could catch the touch events that way, but I was unable to get events to fire on glass since it's attached to DOM in some funny way. My code:
private static class ProperPopupPanel extends PopupPanel {
public ProperPopupPanel() {
super();
}
void setHideOnGlassTouch() {
setGlassEnabled(true);
TouchableLabeThatDoesntCrashOnWrap glass = new TouchableLabeThatDoesntCrashOnWrap(getGlassElement());
glass.addTouchStartHandler(new TouchStartHandler() {
#Override
public void onTouchStart(TouchStartEvent event) {
hide();
}
});
glass.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
hide();
}
});
}
private class TouchableLabeThatDoesntCrashOnWrap extends Label {
public TouchableLabeThatDoesntCrashOnWrap(Element element) {
super(element);
super.onAttach();
}
}
}
In my mind this should work, but it doesn't. I don't know why. Any ideas or suggestions are welcome. Thanks.
Not enough GWT users here... well I made my own class that adds touch handlers through JSNI ...
/**
* Overwrite of the usual PopupPanel with a modification that this one
* works well on touch-enabled browsers.
* #author McTrafik
*/
public class ProperPopupPanel extends PopupPanel {
////////////////////////////////////////////////////////////
/////////// OVERRIDES //////////////////////////////////////
////////////////////////////////////////////////////////////
public ProperPopupPanel() {
super();
setTouchListener();
}
#Override
public void hide() {
super.hide();
removeTouchListener();
}
#Override
public void show() {
super.show();
addTouchListener();
}
////////////////////////////////////////////////////////////
/////////// NANDLERS ///////////////////////////////////////
////////////////////////////////////////////////////////////
protected JavaScriptObject touchHandler;
/**
* Handle a touch event that happened while the popup is open.
* #param event - The event to handle
*/
protected void handleTouchEvent(Event event) {
// Check to see if the events should be firing in the first place.
if (!isShowing()) {
removeTouchListener();
return;
}
// Check if the event happened within the popup
EventTarget target = event.getEventTarget();
if (!Element.is(target) || !getElement().isOrHasChild(Element.as(target))) {
// Stop event if the popup is modal
if (isModal()) event.preventDefault();
// Close the popup if the event happened outside
if (isAutoHideEnabled()) {
hide(true);
removeTouchListener();
}
}
};
/**
* Create a touchHandler that knows how to point to this instance.
* Without it there's a cast exception that happens.
*/
protected native void setTouchListener() /*-{
var caller = this;
this.#[package].ProperPopupPanel::touchHandler = function(event) {
caller.#[package].ProperPopupPanel::handleTouchEvent(Lcom/google/gwt/user/client/Event;)(event);
}
}-*/;
/**
* Add a touch listener that will listen to touch events.
*/
protected native void addTouchListener() /*-{
$doc.addEventListener(
"touchstart",
this.#[package].ProperPopupPanel::touchHandler,
true
);
$doc.addEventListener(
"MozTouchDown",
this.#[package].ProperPopupPanel::touchHandler,
true
);
}-*/;
/**
* Remove the touch listeners
*/
protected native void removeTouchListener() /*-{
$doc.removeEventListener(
"touchstart",
this.#[package].ProperPopupPanel::touchHandler,
true
);
$doc.removeEventListener(
"MozTouchDown",
this.#[package].ProperPopupPanel::touchHandler,
true
);
}-*/;
}

Multiple pages tutorial in Google Web Toolkit (GWT)

I just started learning Google Web Toolkit (GWT). How do I make different HTML pages in my GWT application?
For example, I want to create an application for a book store. In this application I'll have three pages:
Home pages where I will welcome the user and offer the user books
Page to browse books by categories and view details (use GWT widgets)
Check out books online.
Of course there could be other pages like the user's details, add new book, etc.
So, what is the best way of making different pages in GWT and how can I make navigation from page to page? Are there any examples or tutorials? Or do I even need to create different pages when I can create a whole application in one page?
What I usually do in situations like this is design the webpage framework first. I'll have a div for the header, side menu and footer. I'll also have a div in my HTML for the main content.
Example:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name='gwt:module' content='org.project.package.Core=org.project.package.Core'>
</head>
<body>
<!-- Load the JavaScript code for GWT -->
<script language="javascript" src="ui/org.project.package.ui.Core.nocache.js"></script>
<!-- For some unknown reason in Internet Explorer you have to have cellpadding/spacing ON THE ELEMENT and not on the STYLE if it is in the body tag like this -->
<table id="wrapper" cellpadding="0" cellspacing="0" style="width: 100%;height: 100%;">
<!-- Header row -->
<tr style="height: 25%;">
<td colspan="2" id="header"></td>
</tr>
<!-- Body row and left nav row -->
<tr style="height: 65%;">
<td id="leftnav"></td>
<td id="content"></td>
</tr>
<!-- Footer row -->
<tr style="height: 10%;">
<td colspan="2" id="footer"></td>
</tr>
</table>
<!-- This iframe handles history -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
</body>
</html>
(If you like <div> based layouts, feel free to use those instead.)
Then you build your entry point (in my case Core.java) as you normally would, setting each of the elements as need be.
RootPanel.get("header").add(new Header());
RootPanel.get("leftnav").add(new NavigationMenu());
RootPanel.get("footer").add(new Footer());
It is, of course, possible to have a static footer and header, but that's neither here nor there.
I also have an abstract class called "Content". Content objects extend "Composite" and will have various methods for simplifying the creation and layout of a new page. Every page that I build for this application, be it a help screen, search screen, shopping cart, or anything else, is of type Content.
Now, what I do is create a class called "ContentContainer". This is a singleton that is responsible for managing the "content" element. It has one method "setContent" that accepts objects of type "Content". It then basically removes anything within the "content" <td> and replaces it with whatever widget (Composite) you assign via the "setContent" method. The setContent method also handles history and title bar management. Basically the ContentContainer serves to aggregate all the various points of binding that you might have to make if each page content had to "know" about all the functions it must perform.
Finally, you need a way to get to that page, right? That's simple:
ContentContainer.getInstance().setContent(new Search());
Put the above in an on-click event somewhere and you're golden.
The only things that your other widgets need to be bound to is the ContentContainer and the type of Content that they are adding.
The downsides that I can see to ChrisBo's approach are that you've got a list that has to be maintained of tokens -> pages. The other downside I can see is that I don't see how you can have an actual history system with this method.
One thing it does offer over my approach is that all page choices are pretty centralized. I'd use some sort of Enum or at least a static class with String values to prevent myself from mongling up links.
In either case, I think the point can be summed up as this: swap the content of some central page element based on what your user clicks actions your user(s) perform.
I would use the HyperLink and History class. The good thing about the Hyperlink class is, that it sets this token(e.g.#foobar), and all you have to do, is catch the event, that is fired when the value of the token is changed(ValueChangeEvent). In the eventHandler you would then replace the pages.
Example:
address of welcome Page: www.yourpage.com/#home
on this page would be a link to the "browse book"-page, when the link is clicked the new address would be something like this: www.yourpage.com/#browse
And here is the code:
public class MainEntryPoint implements EntryPoint, ValueChangeHandler {
VerticalPanel panel = new VerticalPanel();
Label label=new Label();
public void onModuleLoad() {
Hyperlink link1 = new Hyperlink("books", "browse");
Hyperlink link2 = new Hyperlink("user details", "details");
panel.add(link1);
panel.add(link2);
panel.add(label);
RootPanel.get().add(panel);
History.addValueChangeHandler(this);
//when there is no token, the "home" token is set else changePage() is called.
//this is useful if a user has bookmarked a site other than the homepage.
if(History.getToken().isEmpty()){
History.newItem("home");
} else {
changePage(History.getToken());
}
}
public void onValueChange(ValueChangeEvent event) {
changePage(History.getToken());
}
public void changePage(String token) {
if(History.getToken().equals("browse")) {
label.setText("Here would be some books");
} else if (History.getToken().equals("details")) {
label.setText("Here would be the user details");
} else {
label.setText("Welcome page");
}
}
}
Awesome! I combined Chris R.'s answer with Chris Boesing's to come up with this:
This is the 'index' start page
public class Index implements EntryPoint, ValueChangeHandler<String> {
public void onModuleLoad() {
History.addValueChangeHandler(this);
if (History.getToken().isEmpty()) History.newItem("index");
Composite c = new Login();
FlowControl.go(c);
}
public void onValueChange(ValueChangeEvent<String> e) {
FlowControl.go(History.getToken());
}
}
This is the controller, or ContentContainer according to Chris R.
public class FlowControl {
private static FlowControl instance;
private FlowControl() {}
public static void go(Composite c) {
if (instance == null) instance = new FlowControl(); // not sure why we need this yet since everything is static.
RootPanel.get("application").clear();
RootPanel.get("application").getElement().getStyle().setPosition(Position.RELATIVE); // not sure why, but GWT throws an exception without this. Adding to CSS doesn't work.
// add, determine height/width, center, then move. height/width are unknown until added to document. Catch-22!
RootPanel.get("application").add(c);
int left = Window.getClientWidth() / 2 - c.getOffsetWidth() / 2; // find center
int top = Window.getClientHeight() / 2 - c.getOffsetHeight() / 2;
RootPanel.get("application").setWidgetPosition(c, left, top);
History.newItem(c.getTitle()); // TODO: need to change and implement (or override) this method on each screen
}
public static void go(String token) {
if (token == null) go(new Login());
if (token.equals("cart")) go(new Cart());
if (token.equals("login")) go(new Login());
// Can probably make these constants in this class
}
Then you can pepper Hyperlinks and Buttons anywhere throughout your code. (Have not tried Hyperlinks yet.)
Button submit = new Button("Submit");
submit.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
FlowControl.go(new MyScreen());
}
});
I added a div to my HTML
<!-- This is where the application will reside within. It is controlled by FlowControl class. -->
<div id="application"></div>
And now all screens must call initWidget() in the constructor instead of adding to RootPanel, since it is a Composite class now, like
initWidget(myPanel); // all composites must call this in constructor
If you want it to be FULL AJAXified (like a desktop app) of course you'd only need one page. Then just change the contents of the body depending on the link.
Also, there is a google group for GWT that is very very active, and I know this has been asked before there, you just need to use the "search" feature.
GWT Multipage - simple framework for multi-page-GWT-applications.
You can use MVP patern.
Here is mi simple library https://code.google.com/p/gwt-simple-mvp/wiki/GettingStarted .
And you can split code to more js files. https://code.google.com/p/gwt-spliting/
I used Chloe S. answer (combining Chris R.'s answer with Chris Boesing's) to build this App Controller for a working GWT Web App. The version in production is tested (and working %100) but this redacted version below will need to be modified to integrate with your own app (start by renaming the page keys to your menu items).
AppController.java:
/**
* This App Controller utilizes two static inner-classes (Pages and External)
* to manage and server multiple pages with multiple sub-page (through their presenters)
* via String key constants which also serve as the literal text for the menu items.
*
* Pages are added as menu commands in their respective views:
* // Add menu items to the menu with commands:
* menuItems.put(Pages.PAGE1, mainMenu.addItem(Pages.PAGE1, new Command() {
* public void execute() {
* History.newItem(Pages.PAGE1);
* }
* }));
*
* Pages are fired as History tokens (from entry point java class):
*
* **
* * Receives history events and pushes them to the AppController using a deferred command.
* * Changes the cursor to show waiting.
* * #param the value change token
* *
* public void onValueChange(ValueChangeEvent<String> e) {
* // check token to cover first historical "back" navigation:
* if(!History.getToken().isEmpty()) {
* AppController.waitCursor.execute(); // cursor is reset in page attach method
* }
* Scheduler.get().scheduleDeferred(new ScheduledCommand() {
* public void execute() {
* AppController.go(History.getToken());
* }
* });
* }
*
* Wait cursors are implemented as CSS:
*
* body.wait, body.wait * {
* cursor: wait !important;
* }
*
* NOTE: This page swapping implementation technique (based on the StackOverflow solution
* found here: [http://stackoverflow.com/questions/1061705/multiple-pages-tutorial-in-google-web-toolkit-gwt][1])
* differs from the obtuse and ancient 2010 GWT framework documentation in that the App Controller manages / handles
* adding the widget to the container, and therefore all the Presenters must implement the
* "AppControlPresenter" or "AppControlContainerPresenter" interface to give it access to their containers.
* (thus eliminating "public void go(final HasWidgets container);" method in all presenter architecture except for 'MainAppPresenter')
* There is also no event bus; static method calls are used for any needed interactivity.
*
* Includes a popup for pages still under construction.
*/
package com.;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
/**
*
*/
public class AppController {
/** */
public final static String DEFAULT_INITIAL_PAGE1_SUB_PAGE = Pages.PAGE_1A;
/** Singleton instance for the AppController */
private static AppController instance = new AppController();
/** Presenter for the main app */
private static MainAppPresenter mainAppPresenter;
/** container for the different views */
private static LayoutPanel container;
/** sub-container for the different sub-views */
private static LayoutPanel page1Container;
/** */
private static DecoratedPopupPanel popup;
/** constant for Style-Dependent names for menu items (see menu-style.css) */
public final static String MENU_ACTIVE_STYLE = "active";
/** constant for Style-Dependent class name in css */
public final static String CURSOR_WAIT_CLASS = "wait";
/** */
public final static String POPUP_DEMO_ID = "popupDemo";
/** */
public final static int DEMOP_POPUP_VERTICAL_OFFSET = 0;
/** */
public final static String POPUP_DEMO_STATEMENT = "<span class='text'>This page is under construction</span>"
+ "<span class='char'>…</span>";
/** */
public static ScheduledCommand waitCursor = new ScheduledCommand() {
#Override
public void execute() {
AppController.waitCursor(true);
}
};
/** */
public static ScheduledCommand normalCursor = new ScheduledCommand() {
#Override
public void execute() {
AppController.waitCursor(false);
}
};
/** Flag for determining if the page was reloaded */
private static boolean reloaded = false;
private static final LoginServiceAsync loginRpcService = GWT.create(LoginService.class);
/**
* Called on the resize event to set the position of the demo popup
* window to be adjusted to the correct dimensions (size and positoin)
* regardless of screen size.
*/
private static ScheduledCommand resetPopupDimensions = new ScheduledCommand() {
#Override
public void execute() {
if(!UNDER_CONSTRUCTION || popup == null) {
return;
}
int demoWidth = Math.round(Window.getClientWidth() / MainApp.PHI),
demoYPosition = Window.getClientHeight() / 2 - Math.round(popup.getOffsetHeight() / 2);
popup.setWidth(String.valueOf(demoWidth) + "px");
if(popup.getOffsetWidth() >= Window.getClientWidth()) {
popup.setWidth("100%");
popup.setPopupPosition(0, demoYPosition);
} else {
popup.setPopupPosition(Window.getClientWidth() / 2 - (popup.getOffsetWidth() / 2), demoYPosition);
}
}
};
/** */
private static final String LOGIN_OBJECT_NAME = "Login Presenter Object";
/**
* static inner-class for external websites
*/
public static class External {
/** The frame to contain the website */
private static Frame frame;
/** */
public static final String EXTERNAL_URL_1 = "http://";
/** */
public static final String EXTERNAL_URL_2 = "http://";
/**
* #returns true if the name of the token is equal to one of the URLs
* #param token the name to check
*/
public static boolean has(String token) {
return token.equalsIgnoreCase(EXTERNAL_URL_1) ||
token.equalsIgnoreCase(EXTERNAL_URL_2);
}
/**
* Gets the external Frame object
* #param url
* #return Frame
*/
public static Frame get(String url) {
if(frame == null) {
frame = new Frame(url);
frame.addAttachHandler(new AttachEvent.Handler() {
#Override
public void onAttachOrDetach(AttachEvent event) {
// hide the popup:
showPopup(false);
Scheduler.get().scheduleFinally(resetPopupDimensions);
Scheduler.get().scheduleFinally(normalCursor);
}
});
}
else if(!frame.getUrl().equalsIgnoreCase(url)) {
frame.setUrl(url);
}
return frame;
}
}
/**
* static inner-class for holding pages activated by the app's main menu commands
*/
public static class Pages {
/** */
public static final String PAGE1 = "foo";
/** */
public static final String PAGE2 = "bar";
/** */
public static final String PAGE_1A = "baz";
/** */
public static final String PAGE_1B = "qux";
/** */
public static String lastPage;
/** */
public static String lastPage1SubPage;
/** */
public static String unsavedMessage;
/** */
private static HashMap<String, AppControlPresenter> pageMap;
/** */
private static AppControlPresenter presenter;
/** */
private static Composite view;
/**
* initializes the hashmap of pages
*/
public static void init() {
pageMap = new HashMap<String, AppControlPresenter>();
}
/**
* #returns true if the name of the token is equal to one of the pages
* #param token the name to check
*/
public static boolean has(String token) {
return token.equalsIgnoreCase(PAGE1) ||
token.equalsIgnoreCase(PAGE2) ||
token.equalsIgnoreCase(PAGE_1A);
}
/**
* Gets the correct page container to display as a Composite
* #param page the token name of the page
* #return Composite page
*/
public static Composite get(String page) {
view = null;
presenter = null;
if(page.equalsIgnoreCase(PAGE1)) {
if(pageMap.get(PAGE1) == null) {
pageMap.put(PAGE1, new Page1Presenter(PAGE1));
page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
}
presenter = pageMap.get(PAGE1);
lastPage = page;
mainAppPresenter.setCurrentMenuItem(page);
}
else if(page.equalsIgnoreCase(PAGE_1A) ||
page.equalsIgnoreCase(PAGE_1B) {
if(pageMap.get(PAGE1) == null) {
pageMap.put(PAGE1, new Page1Presenter(PAGE1));
page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
}
presenter = pageMap.get(PAGE1);
lastPage1SubPage = page;
view = ((AppControlContainerPresenter)presenter).setCurrentPage(page);
}
else if(page.equalsIgnoreCase(PAGE2)) {
if(pageMap.get(PAGE2) == null) {
pageMap.put(PAGE2, new Page2Presenter(PAGE2));
}
presenter = pageMap.get(PAGE2);
lastPage = PAGE2;
mainAppPresenter.setCurrentMenuItem(page);
}
else if(External.has(page)) {
throw new Error("App Controller Error -- Use 'External' inner-class for: " + page);
}
else {
throw new Error("App Controller Error -- Page name not found: " + page);
}
if(view == null) {
view = (Composite)presenter.view();
}
view.addAttachHandler(new AttachEvent.Handler() {
#Override
public void onAttachOrDetach(AttachEvent event) {
AppController.showPopup(false);
presenter.updateAttachOrDetach(event);
Scheduler.get().scheduleFinally(resetPopupDimensions);
Scheduler.get().scheduleFinally(normalCursor);
}
});
return view;
}
/**
* Gets the current AppControlPresenter for the last page.
* #returns the current AppControlPresenter
*/
public static AppControlPresenter getCurrentPresenter() {
return presenter;
}
/**
* Gets an AppControlPresenter from the pageMap.
* #param token the name of the presenter
* #returns the AppControlPresenter
*/
public static AppControlPresenter getPresenter(String token) {
return pageMap.get(token);
}
/**
* Returns true if the page is already loaded.
* #param token name of the page
*/
public static boolean alreadyLoaded(String token) {
MainApp.debug(1, "[already loaded: " + presenter.toString() + " (token: " + token + ")");
return presenter.toString().equalsIgnoreCase(token);
}
/**
* Returns true if the page is visible
* #param page the token name of the page
*/
public static boolean isVisible(String page) {
UIObject component = pageMap.get(page).view();
return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
}
/**
* Returns true if the page is visible
* #param presenter the AppControlPresenter instance
*/
public static boolean isVisible(AppControlPresenter presenter) {
UIObject component = presenter.view();
return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
}
/**
* Returns true if the application has unsaved data.
* Iterates through all the pages and checks each presenter.
*/
public static boolean unsavedData() {
if(pageMap.isEmpty()) return false;
boolean unsaved = false;
for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
AppControlPresenter presenter = entry.getValue();
if(presenter != null && presenter.unsavedData()) {
MainApp.debug(1, "(!) " + presenter.toString() + " has unsaved data");
unsavedMessage = presenter.dataDescription();
unsaved = true;
break; // just need to know one exists for now (window closing event)
}
}
return unsaved;
}
/**
* Called on a resize event on the window. Iterates through all the pages
* and tells their presenters to resize their content.
*/
public static void resize() {
for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
AppControlPresenter presenter = entry.getValue();
if(presenter != null && isVisible(presenter)) {
presenter.resize();
}
}
}
} //end class Pages
/**
* #returns true if the history token is equal to any of the pages in the app
*/
public static boolean hasHistory() {
String token = History.getToken();
return External.has(token) || Pages.has(token);
}
/**
* Starts the login view at the root layout level
*/
public static void goLoginScreen() {
//check for reload:
if(hasHistory()) {
MainApp.debug(1, "(!) AppController has History on Login");
reloaded = true;
}
else {
reloaded = false;
}
RootLayoutPanel.get().clear();
RootLayoutPanel.get().add(new LoginPresenter(LOGIN_OBJECT_NAME).view());
}
/**
* #returns the last "Page1" page
*/
public static String getLastPage1Page() {
if(Pages.lastPage1SubPage == null || Pages.lastPage1SubPage.isEmpty()) {
Pages.lastPage1SubPage = DEFAULT_INITIAL_PAGE1_SUB_PAGE;
}
return Pages.lastPage1SubPage;
}
/**
* Tells the app to start with the Page1 page.
* #param username the username of the person logged-in
*/
public static void goMainApp(String username) {
//hide the login background:
RootPanel.getBodyElement().getStyle().setProperty("background", "none");
mainAppPresenter = new MainAppPresenter(username);
RootLayoutPanel.get().clear();
mainAppPresenter.go(RootLayoutPanel.get());
//get the center panel:
container = mainAppPresenter.getContainer();
//check for reload:
//NOTE: the token will be empty if the user refreshes
// and navigates all the way back to the zero-state
// from the login screen.
//NOTE: this logic may change after user-persistence is implemented
if(hasHistory() || History.getToken().isEmpty()) {
// reset the reloaded flag:
reloaded = false;
if(History.getToken().isEmpty()) {
//land on the first page:
History.newItem(AppController.Pages.PAGE1);
}
else {
MainApp.debug(2, "(!) AppController has History on reload: " + History.getToken());
History.fireCurrentHistoryState();
}
}
else {
//land on the first page:
History.newItem(AppController.Pages.PAGE1);
}
}
/**
*
*/
public static void checkIfSessionActive() {
loginRpcService.loginFromSession(new AsyncCallback<LoginSummary>() {
#Override
public void onFailure(Throwable throwable) {
goLoginScreen();
}
#Override
public void onSuccess(LoginSummary loginSummary) {
if (loginSummary.getErrorString() != null)
goLoginScreen();
else
goMainApp(loginSummary.getUser().getName());
}
});
}
/**
*
*/
public static void sessionLogout() {
DialogBoxWidget.confirm(200,
"Logout",
"Are you sure you want to log out?",
new ConfirmDialogCallback() {
#Override
public void onAffirmative() {
loginRpcService.logout(new AsyncCallback<Void>() {
#Override
public void onFailure(Throwable throwable) {
goLoginScreen();
}
#Override
public void onSuccess(Void aVoid) {
goLoginScreen();
}
});
}
#Override
public void onCancel() {
}
});
}
/**
* Shows or hides the "Under Construction" popup if UNDER_CONSTRUCION is true.
* #param show true to show and false to hide
*/
public static void showPopup(boolean show) {
if(MainApp.UNDER_CONSTRUCTION && popup != null) {
if(show) {
popup.show();
}
else {
popup.hide();
}
}
}
/**
* Called by every history event fired (including the back and forward buttons).
* Ignores the login and empty index historically.
* #param token the name of the page to load
*/
public static void go(String token) {
if(reloaded) {
normalCursor.execute();
}
if(token == null || token.isEmpty() || reloaded == true) return;
MainApp.debug("<history changed> - AppController.go()-> " + token);
// build the popup message for all unfinished pages:
if(MainApp.UNDER_CONSTRUCTION) {
if(popup == null) {
popup = new DecoratedPopupPanel(false);
popup.ensureDebugId(POPUP_DEMO_ID);
popup.addStyleDependentName(POPUP_DEMO_ID);
popup.setWidget(new HTML(new Image("images/workingman.png") + POPUP_DEMO_STATEMENT + new Image("images/workingmanFLIP.png")));
}
}
// check token for which page to return:
if(token.equalsIgnoreCase(External.EXTERNAL_URL_1)) {
MainAppPresenter.clearActiveMenuItems();
setExternalContentURL(External.get(token));
}
else if(token.equalsIgnoreCase(External.EXTERNAL_URL_2)) {
MainAppPresenter.clearActiveMenuItems();
setExternalContentURL(External.get(token));
}
else if(token.equalsIgnoreCase(Pages.PAGE1)) {
setContent(Pages.get(Pages.PAGE1));
setPage1Content(Pages.get(getLastPage1Page()));
}
else if(token.equalsIgnoreCase(Pages.PAGE_1A) ||
token.equalsIgnoreCase(Pages.PAGE_1B)) {
setContent(Pages.get(Pages.PAGE1));
setPage1Content(Pages.get(token));
}
else if(token.equalsIgnoreCase(Pages.PAGE2)) {
setContent(Pages.get(Pages.PAGE2));
}
else { // default behavior for a page not described:
MainApp.debug(2, "(!) Unknown page: " + token);
setContent(Pages.get(token));
}
}
/**
* Called by MainApp on a window resize event.
* #param e the ResizeEvent
*/
public static void resize(ResizeEvent e) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
if(mainAppPresenter != null) {
mainAppPresenter.resize();
}
Pages.resize();
Scheduler.get().scheduleFinally(resetPopupDimensions);
}
});
}
/**
* Changes the cursor to "wait" or "auto" depending on the parameter
* #param wait true to set the cursor to waiting
*/
private static void waitCursor(Boolean wait) {
if(wait) {
RootPanel.getBodyElement().addClassName(CURSOR_WAIT_CLASS);
}
else {
RootPanel.getBodyElement().removeClassName(CURSOR_WAIT_CLASS);
}
}
/**
* Private Constructor which initializes the Pages object.
*/
private AppController() {
Pages.init();
}
/**
* Sets the content of the main app container to one of the "Pages."
* #param c the Composite widget to be added
*/
private static void setContent(Composite c) {
container.clear();
container.add(c.asWidget());
}
/**
* Sets the content of the main app container an external URL.
* #param f the Frame by which external web sites are added
*/
private static void setExternalContentURL(Frame f) {
container.clear();
container.add(f);
// must reset the width and height every time:
f.getElement().getStyle().setWidth(100, Unit.PCT);
f.getElement().getStyle().setHeight(100, Unit.PCT);
}
/**
* Sets the content of the Page1 container to one of the sub pages.
* #param c the Composite widget to be added
*/
private static void setPage1Content(Composite c) {
page1Container.clear();
page1Container.add(c.asWidget());
}
}
AppControlPresenter.java:
package com.*;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.user.client.ui.Composite;
/**
* Base interface for all 'Presenters' used by AppController.java
* NOTE: classes that implement this interface do not launch the presenter's view
* into the provided container; rather, the view is retrieved and used by the
* AppController instance by calling the 'view()' method
*/
public interface AppControlPresenter {
/**
* Gets the view (for use in AppController.java)
*/
public Composite view();
/**
* Indicates if current search data is present and unsaved.
* #returns true to if a search is still active
*/
public boolean unsavedData();
/**
* Called on resize event to notify presenters with visible
* components that need resizing for different screen sizes.
* #returns true if elements were resized
*/
public boolean resize();
/**
* Called on attachEvents to tell the presenter to update.
* #param event the AttachEvent
*/
public void updateAttachOrDetach(AttachEvent event);
/**
* Gets the message to display for unsaved data.
* #returns a message String describing the data
*/
public String dataDescription();
/**
* Gets a fully qualified name for use in comparisons
* #return the name of this presenter used by the <code>AppController</code>
*/
public String toString();
}
AppControlContainerPresenter.java:
package com.*;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.LayoutPanel;
/**
*/
public interface AppControlContainerPresenter extends AppControlPresenter {
/**
*
* #return
*/
public LayoutPanel getContentPane();
/**
*
* #param pageName
* #return
*/
public Composite setCurrentPage(String pageName);
}
Add a module for each page you have that needs the GWT functionality. Reuse your components.