<< Tapestry 5 and GWT - part 1 | Home | Tomcat 6 Windows service >>

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

Continue to the source code post



Re: Tapestry 5 and GWT - part 2

Hi, Can you please post the source code? Getting the project structure right seems to be the hardest part. Also the cleancode article seems to have disappeared from the web.

Re: Tapestry 5 and GWT - part 2

I can certainly do that. I'll have a look at that this weekend.

Re: Tapestry 5 and GWT - part 2

Source is available here now: http://www.pmdit.se/blog/2008/10/08/tapestry_5_and_gwt_part_2_5.html Thanks for pointing out that the Google cache link was dead. I've updated it with the Internet Archive instead.

Re: Tapestry 5 and GWT - part 2

And how do you pass the Tapestry Hibernate session to GWT ?

Re: Tapestry 5 and GWT - part 2

I've actually not done that yet, so I can't really answer. However I got a few ideas I'm going to try out. I'm currenlty a bit busy, but I'll add a third tutorial when I got it working.

Add a comment Send a TrackBack