Dear Wiki user, You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for change notification.
The following page has been changed by DanielJue: http://wiki.apache.org/tapestry/Tapestry5GWTIntegration ------------------------------------------------------------------------------ = Tapestry 5 GWT Integration = - + [http://www.phy6.net/downloads/T5GWT/myapp.zip Download the Source code and Project] This article is on making use of both of these technologies. - + [http://www.phy6.net/downloads/T5GWT/myapp.png] ==== Reader Requirements ==== *Basic knowledge of T5 features *Know how to create the hello world archetype of both T5 and GWT. *very basic Maven knowledge + *Suggest you read through the first GWT tutorial, linked from the wiki page. ==== Technologies Used ==== @@ -32, +33 @@ - === More in a bit === + === Lets get you set up === + You can download the whole project using the link above. Here's the POM for you to look at: - What I will show here: + '''full pom.xml''' - A single project using WTP, includes Tapestry 5 and GWT in one project. - Modifying the POM and Web XML for new entrypoints. - Maven and or CMD file to execute gwt builds - Multiple simple components on one page - Multiple instances of the same RPC component on one page! + {{{ + <project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.example</groupId> + <artifactId>myapp</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>war</packaging><!-- + First use clean compile gwt:compile, and afterwards you can just run the compile.cmd that gets generated. + Remember to refresh your source directory before serving this, since the GWT compilation outputs into a source dir! + --><name>myapp Tapestry 5 Application</name> + <dependencies> + <!-- GWT deps (from central repo) --> + <dependency> + <groupId>com.google.gwt</groupId> + <artifactId>gwt-servlet</artifactId> + <version>${gwtVersion}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>com.google.gwt</groupId> + <artifactId>gwt-user</artifactId> + <version>${gwtVersion}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.gwt</groupId> + <artifactId>gwt-dev</artifactId> + <version>${gwtVersion}</version> + <classifier>${platform}-libs</classifier> + <type>zip</type> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.gwt</groupId> + <artifactId>gwt-dev</artifactId> + <version>${gwtVersion}</version> + <classifier>${platform}</classifier> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.14</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tapestry</groupId> + <artifactId>tapestry-core</artifactId> + <version>${tapestry-release-version}</version> + </dependency> + <!-- + A dependency on either JUnit or TestNG is required, or the surefire + plugin (which runs the tests) will fail, preventing Maven from + packaging the WAR. Tapestry includes a large number of testing + facilities designed for use with TestNG (http://testng.org/), so it's + recommended. + --> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <version>5.1</version> + <classifier>jdk15</classifier> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <finalName>myapp</finalName> + <resources> + <resource> + <directory>src/main/resources</directory> + <includes> + <include>**</include> + </includes> + <excludes> + <exclude>hibernatecfg/*</exclude> + <exclude>hibernate.cfg.xml</exclude> + <exclude>log4j.properties</exclude> + <exclude>spy.properties</exclude> + <exclude>version.properties</exclude> + <exclude>**/.svn/**</exclude> + </excludes> + </resource> + <resource> + <directory>src/main/java</directory> + <includes> + <include>**/*</include> + </includes> + <excludes> + <exclude>**/*.java</exclude> + <exclude>**/.svn/**</exclude> + </excludes> + </resource> + </resources> + <plugins> + <!-- configure the GWT-Maven plugin --> + <plugin> + <groupId>com.totsp.gwt</groupId> + <artifactId>maven-googlewebtoolkit2-plugin</artifactId> + <version>2.0-beta26</version> + <configuration> + <logLevel>INFO</logLevel> + <compileTargets> + <value>se.pmdit.tutorial.t5gwt.gwt.Sample</value> + <value>se.pmdit.tutorial.t5gwt.gwt.ComplexSample</value> + <value>se.pmdit.tutorial.t5gwt.gwt.StockWatcher</value> + </compileTargets> + <runTarget>/myapp/Start</runTarget> + <style>DETAILED</style> + <noServer>false</noServer> + <extraJvmArgs>-Xmx512m</extraJvmArgs> + <!-- + this parameter is VERY important with automatic mode - has to + match the version in your declared deps + --> + <!-- + if this is set incorrect, or left out and default does not match + (default is 1.5.3) you will have mysterious errors + --> + <gwtVersion>${gwtVersion}</gwtVersion> + <!-- This puts the GWT output (javascripts) into the source tree --> + <output>src/main/webapp</output > + </configuration> + <executions> + <execution> + <goals> + <!--<goal>mergewebxml</goal> + --><!--<goal>i18n</goal> + --> + <goal>compile</goal> + <!--<goal>test</goal> + --> + </goals> + </execution> + </executions> + </plugin> + <!-- Use the dependency plugin to unpack gwt-dev-PLATFORM-libs.zip --> + <!-- + (this is a replacement for the old "automatic" mode - useful if you + don't have GWT installed already, or you just want a maven way to + handle gwt deps) + --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>unpack</id> + <phase>compile</phase> + <goals> + <goal>unpack</goal> + </goals> + <configuration> + <artifactItems> + <artifactItem> + <groupId>com.google.gwt</groupId> + <artifactId>gwt-dev</artifactId> + <version>${gwtVersion}</version> + <classifier>${platform}-libs</classifier> + <type>zip</type> + <overWrite>false</overWrite> + <outputDirectory>${settings.localRepository}/com/google/gwt/gwt-dev/${gwtVersion}</outputDirectory> + </artifactItem> + </artifactItems> + </configuration> + </execution> + </executions> + </plugin> + <!-- + If you want to use the target/web.xml file mergewebxml produces, + tell the war plugin to use it. Also, exclude what you want from the + final artifact here. + --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <configuration><!-- + <webXml>src/main/webapp/WEB-INF/web.xml</webXml> + --><warSourceExcludes>.gwt-tmp/**</warSourceExcludes> + </configuration> + </plugin> + <!-- tell the compiler we can use 1.5 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.5</source> + <target>1.5</target> + </configuration> + </plugin> + </plugins> + </build> + <!-- profiles (with activation per platform) --> + <profiles> + <profile> + <id>gwt-dev-windows</id> + <properties> + <platform>windows</platform> + </properties> + <activation> + <activeByDefault>true</activeByDefault> + <os> + <family>windows</family> + </os> + </activation> + </profile> + <profile> + <id>gwt-dev-mac</id> + <properties> + <platform>mac</platform> + </properties> + <activation> + <activeByDefault>false</activeByDefault> + <os> + <family>mac</family> + </os> + </activation> + </profile> + <profile> + <id>gwt-dev-linux</id> + <properties> + <platform>linux</platform> + </properties> + <activation> + <activeByDefault>false</activeByDefault> + <os> + <name>linux</name> + </os> + </activation> + </profile> + </profiles> + <reporting> + <!-- + Adds a report detailing the components, mixins and base classes + defined by this module. + --> + <plugins> + <plugin> + <groupId>org.apache.tapestry</groupId> + <artifactId>tapestry-component-report</artifactId> + <version>${tapestry-release-version}</version> + <configuration> + <rootPackage>org.example.myapp</rootPackage> + </configuration> + </plugin> + </plugins> + </reporting> + <repositories> + <!-- + This can be commented out when the tapestry-release-version is a not + a snapshot. The non-snapshot Tapestry artifacts are distributed + through the central repository at ibiblio. + --> + <repository> + <id>tapestry-snapshots</id> + <url>http://tapestry.formos.com/maven-snapshot-repository/ + </url> + </repository> + <repository> + <id>codehaus.snapshots</id> + <url>http://snapshots.repository.codehaus.org + </url> + </repository> + <repository> + <id>OpenQA_Release</id> + <name>OpenQA Release Repository</name> + <url>http://archiva.openqa.org/repository/releases/ + </url> + </repository> + <repository> + <id>gwt-maven</id> + <url> http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url> + </repository> + </repositories> + <pluginRepositories> + <!-- + As above, this can be commented out when access to the snapshot + version of a Tapestry Maven plugin is not required. + --> + <pluginRepository> + <id>tapestry-snapshots</id> + <url>http://tapestry.formos.com/maven-snapshot-repository/ + </url> + </pluginRepository> + <pluginRepository> + <id>gwt-maven-plugins</id> + <url> http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url> + </pluginRepository> + </pluginRepositories> + <properties> + <gwtVersion>1.5.3</gwtVersion> + <tapestry-release-version>5.0.18</tapestry-release-version> + </properties> + </project> + }}} - Source code link! The project exists already! + For now, I manually make additions to the web.xml if a servlet mapping is needed. So far this example only uses one, for the Stock Prices. + + '''web.xml''' + {{{ + <?xml version="1.0" encoding="UTF-8"?> + <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> + <session-config> + <session-timeout> 30 </session-timeout> + </session-config> + <welcome-file-list> + <welcome-file>Start</welcome-file> + </welcome-file-list> + <display-name>Tapestry 5 With GWT</display-name> + <context-param> + <param-name>tapestry.app-package</param-name> + <param-value>se.pmdit.tutorial.t5gwt.tapestry</param-value> + </context-param> + <filter> + <filter-name>app</filter-name> + <filter-class>org.apache.tapestry5.TapestryFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>app</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + <servlet> + <servlet-name>stock</servlet-name> + <servlet-class>se.pmdit.tutorial.t5gwt.gwt.server.StockPriceServiceImpl</servlet-class> + </servlet> + <servlet-mapping> + <servlet-name>stock</servlet-name> + <url-pattern>/se.pmdit.tutorial.t5gwt.gwt.StockWatcher/stockPrices</url-pattern> + </servlet-mapping> + </web-app> + }}} + + + + === Adding GWT Targets in the POM === + Notice that for each entrypoint you want to have compiled, you add it here. + {{{ + ... + <compileTargets> + <value>se.pmdit.tutorial.t5gwt.gwt.Sample</value> + <value>se.pmdit.tutorial.t5gwt.gwt.ComplexSample</value> + <value>se.pmdit.tutorial.t5gwt.gwt.StockWatcher</value> + </compileTargets> + ... + }}} + + + === The StockWatcher Entrypoint === + {{{ + package se.pmdit.tutorial.t5gwt.gwt.client; + + import com.google.gwt.core.client.EntryPoint; + import com.google.gwt.i18n.client.Dictionary; + import com.google.gwt.user.client.Timer; + import com.google.gwt.user.client.ui.RootPanel; + /** + * + * @author djue + * + */ + public class StockWatcher implements EntryPoint { + + private static final int REFRESH_INTERVAL = 5000; // ms + + public void onModuleLoad() { + + // Find all the instances in this DOM where this entry point should be + // created. + // The dictionary is created by + Dictionary gwtComponents = Dictionary.getDictionary("gwtComponents"); + if (gwtComponents != null) { + String str[] = gwtComponents.get("stockwatcher").split(","); + for (int i = 0; i < str.length; i++) { + createUniqueModule(str[i]); + } + } + + } + + /** + * @param string + */ + private void createUniqueModule(String string) { + + // // add the main panel to the HTML element with the id "stockList" + // RootPanel.get("stockList"+string).add(mainPanel); + + final StockWatcherPanel swp = new StockWatcherPanel(); + // add the main panel to the HTML element with the id "stockList" + RootPanel.get("stockList" + string).add(swp); + + // setup timer to refresh list automatically + Timer refreshTimer = new Timer() { + public void run() { + swp.refreshWatchList(); + } + }; + refreshTimer.scheduleRepeating(REFRESH_INTERVAL); + + // move cursor focus to the text box + swp.newSymbolTextBox.setFocus(true); + + } + + } + + }}} + + + === The StockWatcherComponent === + {{{ + /* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + + package se.pmdit.tutorial.t5gwt.tapestry.components; + + import org.apache.tapestry5.Asset; + import org.apache.tapestry5.ComponentResources; + import org.apache.tapestry5.RenderSupport; + import org.apache.tapestry5.annotations.Environmental; + import org.apache.tapestry5.annotations.IncludeJavaScriptLibrary; + import org.apache.tapestry5.annotations.Path; + import org.apache.tapestry5.annotations.Property; + import org.apache.tapestry5.ioc.annotations.Inject; + + /** + * + * @author djue + */ + @IncludeJavaScriptLibrary("context:se.pmdit.tutorial.t5gwt.gwt.StockWatcher/se.pmdit.tutorial.t5gwt.gwt.StockWatcher.nocache.js") + public class StockWatcherComponent extends GWTEntryPointComponent { + @Environmental + private RenderSupport renderSupport; + @Inject + private ComponentResources resources; + + @Inject + @Path("context:se.pmdit.tutorial.t5gwt.gwt.StockWatcher/images/GoogleCode.png") + @Property + private Asset banner; + + void setupRender() { + renderSupport.addScript("GWTComponentController.add('stockwatcher','" + resources.getCompleteId() + "')"); + } + // If you wanted you could output the DIV from here, then you would not + // need the template file. + } + }}} + + Make special note of this line: + + {{{ + ... + renderSupport.addScript("GWTComponentController.add('stockwatcher','" + resources.getCompleteId() + "')"); + ... + }}} + It's kind of like adding it to a HashMap<String,String>. (It's not really a HashMap, but later your Entrypoint will look up values associated with a key; The key will be 'stockwatcher', and the values will be the T5 generated ids) + + === The StockWatcherComponent.tml === + Notice the div where the id becomes stockList+component id. This allows a unique and independent stock list to be created. + {{{ + <div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> + Stock Watcher Component + <div id="${componentResources.completeId}"></div> + <img src="${banner}" /> + <h1>Stock Watcher</h1> + <div id="stockList${componentResources.completeId}"></div> + </div> + }}} + Note that this: + {{{ + ... + <div id="${componentResources.completeId}"></div> + ... + }}} + Is the same id that you added in the "HashMap" above. + + + == General Tips == + + In general it can be very easy to get GWT working with Tapestry. The complexity comes in when you want GWT to be a component, and you consider that a component may exist many times on one page. (think of dialog boxes). In order for the components to behave properly, you must somehow provide a unique ID to the Javascript object that GWT will create. + + In your GWT code, make the Entrypoint class do as little as possible. I've read this elsewhere as a GWT mini-pattern, but it helps alot here! Even references to your GWT services can be put into the separate class that your Entrypoint will instantiate. In fact the Entrypoints I show here do pretty much the same thing: + * They load a dictionary and find all the values for a certain "key" + * For each value, a new instance of something is created + + For example, there will be a String like this: + {{{ + mydialog,mydialog1,mydialog2 + }}} + These are the unique id's that Tapestry will create for you. Some included Javascript (from the other GWT tutorial) will copy these unique id's into a Javascript variable. The compiled GWT Javascript will then read that list of id's in your Entrypoint (as shown later) + + So instead of having all kinds of code in your entrypoint class, refactor it into a separate class that your entrypoint will add to the RootPanel. + + == Some unusual things about this == + + The GWT is configured in the POM to output it's compiled directories into your src/webapp. + (If someone knows a way to use Maven Assembly or an Ant copy that will work better, please add it to this wiki) + This will require a refresh of your source dir after compilation, or Eclipse won't see the GWT compiled code. + If Eclipse does not see the new files, then your Tomcat instance won't see it either if you are launching Tomcat from Eclipse. + + I have not included the gwt compiled directories in the Zip file, because they should be generated for you when you run: + {{{ + mvn clean compile gwt:compile + }}} + + clean will delete the Target directory + compile will take care of Java compilation like your T5 code + gwt:compile will take care of your GWT generated code (the Javascript) + + You could also launch tomcat afterwards using Maven, but I find this less thank friendly. + + You can leave "Build Automatically" checked in your Eclipse project settings, and Tomcat should restart when you make changes on the Tapestry side. But you'll need to restart Tomcat otherwise. + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
