Tapestry 5 and GWT - part 2
Putting it all together
|
Now when we have Tapestry 5 (T5) and GWT in the same project
we want to put the GWT component in a T5 component
which will make it easy to work with in the T5 application.
If you haven't already, create the ...tapestry.components package. Then create Dialog.tlm and an empty Dialog.java class for the component. Now unfortunately we can't just take the meta module and script tags, put it in a component and have it working. Well, we could actually do that if we just ever wanted one GWT component running in a page at any one time, but let's instead add support for several instances. |
Soon the project will look like this... |
Several instances of a GWT widget
GWT can render an instance of a class at a div with an id like this: <div id="gwt-component-id"></div> This comes in handy when placing out GWT components at a certain location in the page, in our case inside the T5 component.In the java class the code RootPanel.get("gwt-component-id").add(new TestWidget()); is used to create an instance of TestWidget at that spot.
This is all and well, but it would be really annoying to have to manually give each T5 component a parameter with a unique string.
Instead of manually doing this we can use the unique id T5 already created for the component itself and send that to GWT so GWT can render an instance of it's class at each id.
Sending T5 runtime ids to GWT
T5 can supply the component's id with the ${componentResources.completeId} property. That means that the above div tag can get it's component's id this way: <div id="${componentResources.completeId}"></div>However, GWT can't scan all ids of all tags. It can however get data from for example a javascript array on the host page with the Dictionary class.
First we need to add the T5 component id to the javascript array. This can be done with a javascript singleton.
gwtsupport.js
var gwtComponents = new Array();
var GWTComponentController = function() {
return {
add : function(key, value) {
array = gwtComponents[key];
if( array == null ) {
array = new Array();
gwtComponents[key] = array;
}
array[array.length] = value;
}
}
}();
Place the file in the js directory and add the js file
to the Start page with
<script type="text/javascript" src="js/gwtsupport.js"></script>
BTW, remember to change the path in the Start page so
the GWT code can be found in the js directory as well.
Now we can use the singleton array to store all ids of components on the page by calling it's add function:
Dialog.tml
<div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
The Dialog component
<script type="text/javascript">
GWTComponentController.add("se.pmdit.tutorial.t5gwt.gwt"
, "${componentResources.completeId}")
;</script>
<div id="${componentResources.completeId}"></div>
</div>
Note that it's storing the ids as a comma separated
string with the module id as key, at least that's the
way Dictionary will give it back to us in GWT.
This way it is possible to have several
modules on the same page since they have different keys.
Then use the Dictionary class to read from the known array
GwtComponentEntryPoint.java
public class GwtComponentEntryPoint implements EntryPoint, ClickListener {
public GwtComponentEntryPoint() {
}
public void onModuleLoad() {
Dictionary gwtComponents = Dictionary.getDictionary("gwtComponents");
if (gwtComponents != null) {
String str[] = gwtComponents.get("se.pmdit.tutorial.t5gwt.gwt").split(",");
for (int i = 0; i < str.length; i++) {
createDialog(str[i]);
}
}
}
public void createDialog(String gwtComponentId) {
Button b = new Button("Open The Dialog!");
b.addClickListener(this);
RootPanel.get(gwtComponentId).add(b);
}
public void onClick(Widget sender) {
new TheDialog().show();
}
}
How nice. Now we can place several components in a page
and it's all working like a charm... right?
Moving things around
Well... we're not quite finished yet. Let's for examle use the Activate/Passivate functionallity of T5.Start.java
public class Start {
private String message;
public void onActivate(String message) {
this.message = message;
}
public String onPassivate() {
return this.getMessage();
}
public String getMessage() {
return message;
}
}
T5 cleverly accepts and creates nice and short URLs
automaticly. For example with the use of onActivate we
can open the Start page and sending it a perfectly readable
parameter with
http://localhost:8080/Tapestry5WithGWT/Start/ABC
which the method onActivate will receive as "ABC".
Print it on the page with ${message} to have a look at it.
A very nice feature indeed, but the GWT components won't
work any longer since the link and script includes are
trying to find js/ and css/ which won't be
accessible via that path at all times since our context
can change. However, T5 of course has a solution for this.
Assets
To be able to bring the css and javascript files with us through the pages we can use Assets. It's a nice little feature that let's us reference those resources in the the generated pages and components. There's just a little bit of code for this:
@Inject @Path("context:js/")
private Asset jsPath;
public Asset getJsPath() {
return jsPath;
}
We can also use the @Include* annotations. For example
adding the @IncludeStylesheet("context:css/main.css")
to the Start.java will include the css file correctly.
GwtSupport
Now we have two parts, some code which needs to be included once and the components that potentially can be added several times.I've moved the "run once" stuff to a T5 component called GwtSupport, this way I can include that one component on each T5 page where I want to use GWT components.
GwtSupport.tml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<meta name='gwt:module' content='${jsPath}=se.pmdit.test.newgwt2.Main'/>
<script type="text/javascript" src="${jsPath}/gwt.js"></script>
<script type="text/javascript" src="${jsPath}/main.js"></script>
</div>
GwtSupport.java
public class GwtSupport {
@Inject @Path("context:js/")
private Asset jsPath;
public Asset getJsPath() {
return jsPath;
}
}
The new lean Start page
Now look here how clean and nice the Start page can be when we use the T5 GwtSupport component:Start.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<link type="text/css" rel="stylesheet" href="css/main.css"/>
</head>
<body>
<t:gwtSupport/>
The Start page<br/>
${message}
<t:dialog/>
<t:dialog/>
</body>
</html>
And then we got a nice little dialog which we can open
from two separate GWT buttons.
http://localhost:8080/Tapestry5WithGWT/Start/ABC