{{galleryItem.title}}
{{galleryItem.content}}
and then explicitly use the java specific types. A similar but different situation exists with java.util.List and the scala List classes (e.g. scala.collections.immutable.List) although they have the same name they have a completely different purpose (e.g. the Scala list is not necessarily intended to be created, and manipulated like the Java list); the equivalent of the java list is the recommended Scala ListBuffer
import java.util.{List => JList }
import org.slf4j.Logger
import scala.collection.JavaConversions._
class FooPage {
@Property
private var catPieces:JList[ArtPiece] = _
// explicitly declaring the returning types as the Java types
def onPassivate():JList[String]= {
// and using the Scala provided conversions
return seqAsJavaList(List(category,subCategory))
}
}
class Foo {
@Inject
var logger:Logger = _
@Inject
var pm:PersistenceManager = _
}
Yet, when I accidentally did the same for some page properties at weird points the application would start failing (on application reload with Tapestry's live class reloading) until in pages I replaced the approach w/ Tapestry's @Property annotation (although they're supposed to do the same it's quirky w/ BeanProperty)
import scala.reflect.BeanProperty
import javax.jdo.annotations.Persistent;
class PersistentFoo {
@BeanProperty
@Persistent
var title = ""
}
import org.apache.tapestry5.annotations.Property;
class FooPage {
@Property
private var category:String = _
}
[1,2,3].join(",");
"Blah blah $fooVar"
foo?.bar
List(1,2,3).reduceLeft(_ + "," + _)
def ?[A](block: => A) =... and usage ...
try { block } catch {
case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null
case e => throw e
}
val a = ?(b.c.d.e)
One of the major upsides of using Tapestry 5 is the much touted live class and template reloading. Up until recently, if you followed my previous post on working with Tapestry 5 and NetBeans, you probably ended w/ a workable solution, but still not ideal , as the live template and class reloading wasn't exactly working as expected. As a result, whenever you wanted to see the changes that you made in the live app (after running mvn jetty:run) you had to do the following:
mvn compile resources:resources
.........
-----------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Wed May 13 03:16:32 EDT 2009
[INFO] Final Memory: 16M/71M
The issue here was that NetBeans (in 6.1 and prior) did not support CopyOnSave or CompileOnSave properly in Maven projects (it did for NetBeans native projects, so if you had set up a NetBeans native project w/ explicit jar dependencies, etc it would work fine). The effect of running the above command was to compile your changes, and copy the compiled classes and modified resources into your <outputDirectory> (typically target/classes) . So, the 18 seconds above are not exactly something to lose sleep over, but it's still not the same like having the immediate Grails(or Rails)-like immediate feedback loop (that is, "Ctrl-S->Alt-Tab to browser->F5", which is "Save->Switch to Browser->Refresh").
In any case, help is on the way.
In the most recent version of NetBeans (in the 6.7 daily builds ), the issues w/ CopyOnSave support has been fixed (well, almost fixed, see the NetBeans IssuZilla issue), and now it transparently copies your modified resource files to target/[app-name]/WEB-INF/classes. Thus, with just a minor tweak, you can accomplish a Tapestry 5 Nirvana.
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
Now, this is a bit of a hack. Basically, we're telling maven to use the target/[app-name]/WEB-INF/classes to do the initial and any subsequent builds, which is where both the classes from src/main (and whereever else) and src/main/resources end up. The trick here is that this is the same directory that "mvn package" uses, and it is also the same directory that NetBeans uses for the Compile-on-save functionality. Basically, when you make changes to your page template sin src/main/resources (and after you've run your app in Tomcat once), NetBeans continues to compile the classes and copy the modified resources from src/main/resources and drop them into the target/[app-name]/WEB-INF/classes.
Considering that this is indeed a a hack, I filed a patch for NetBeans to properly support this T5 setup in Maven project. However, what got into 6.7 is only the fix to properly copy resources into target/[app-name]/WEB-INF/classes (and not in target/classes). The develoeper on the issue has some other ideas on how this should go, hopefully the full fix will go into the NetBeans version after 6.7. In the meantime, either use this little hack, or I'll probably try to repackage my fix as a standalone plugin to support this out of the box.
mvn archetype:create
-DarchetypeGroupId=org.apache.tapestry
-DarchetypeArtifactId=quickstart
-DgroupId=org.apache.tapestry
-DartifactId=tutorial1
-DpackageName=org.apache.tapestry.tutorial
grails create-app
Welcome to Grails 1.0 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /usr/local/java/grails
Application name not specified. Please enter:
FooApp
---------------
[propertyfile] Updating property file: /home/polrtex/temp/FooApp/application.properties
Created Grails Application at /home/polrtex/temp/FooApp
public ComponentTemplate findTemplate(IRequestCycle cycle, IComponent component, Locale locale) { }
public ComponentTemplate(char[] templateData, TemplateToken[] tokens) {}
<module id="id" version="0.0.1" package="package">
<implementation service-id="tapestry.page.SpecificationResolverDelegate">
<invoke-factory>
<construct class="com.troymaxventures.grapestry.framework.ViewsSpecificationResolverDelegate">
<set property="pagePath" value="grails-app/controllers"/>
<set property="componentPath" value="grails-app/views"/>
</construct>
</invoke-factory>
</implementation>
<implementation service-id="tapestry.parse.TemplateSourceDelegate">
<invoke-factory>
<construct class="com.troymaxventures.grapestry.framework.ViewsTemplateSourceDelegate">
<set property="grailsAppPath" value="WEB-INF"/>
<set-service property="parser" service-id="tapestry.parse.TemplateParser" />
<set-object property="contextRoot" value="infrastructure:contextRoot" />
<set-service property="componentSpecificationResolver" service-id="tapestry.page.ComponentSpecificationResolver" />
<set-object property="componentPropertySource" value="infrastructure:componentPropertySource" />
<set-object property="rootOverride" value="app-property:grapestry-webapp-root-override" />
</construct>
</invoke-factory>
</implementation>
</module>
grails create-plugin
to set up the basic directory structure, etc.
class Grapestry2GrailsPlugin {
def version = 0.1
def dependsOn = [:]
// This removes the Grails standard controllers plugin, which means that standard Grails actions and such would not work anymore.
def evicts=['controllers']
def doWithSpring = {
// TODO Implement runtime spring config (optional)
}
def doWithApplicationContext = { applicationContext ->
// TODO Implement post initialization spring config (optional)
}
def doWithWebDescriptor = { xml ->
def servlets = xml.servlet[0]
servlets + {
servlet {
'servlet-name'('tapestryapplication')
'servlet-class'('org.apache.tapestry.ApplicationServlet')
'init-param' {
'param-name'('org.apache.tapestry.disable-caching')
'param-value'('true')
}
'init-param' {
'param-name'('org.apache.tapestry.application-specification')
'param-value'('tapestryapplication.application')
}
'load-on-startup'(1)
}
}
def mappings = xml.'servlet-mapping'[0]
mappings + {
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('/app')
}
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('*.html')
}
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('*.direct')
}
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('*.sdirect')
}
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('*.svc')
}
'servlet-mapping' {
'servlet-name'('tapestryapplication')
'url-pattern'('/assets/*')
}
}
def filter = xml.filter[0]
filter + {
'filter-name'('redirect')
'filter-class'('org.apache.tapestry.RedirectFilter')
}
def filterMapping = xml.'filter-mapping'[0]
filterMapping + {
'filter-name'('redirect')
'url-pattern'('/')
}
}
def doWithDynamicMethods = { ctx ->
// TODO Implement additions to web.xml (optional)
}
def onChange = { event ->
// TODO Implement code that is executed when this class plugin class is changed
// the event contains: event.application and event.applicationContext objects
}
def onApplicationChange = { event ->
// TODO Implement code that is executed when any class in a GrailsApplication changes
// the event contain: event.source, event.application and event.applicationContext objects
}
}
package com.troymaxventures.grapestry.pages;
/**
*
* @author akochnev
*/
import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.html.BasePage;
public abstract class Home extends BasePage {
@Persist
public abstract int getCounter();
public abstract void setCounter(int counter);
public void doClick(int increment) {
int counter = getCounter();
counter += increment;
setCounter(counter);
}
public void doClear() {
setCounter(0);
}
public void saveSomething() {
/*
def b = new Foo(name:"Foo",url:"http://foo.bar.baz")
b.save()
println "Saved Bookmark2: " + Foo.get(1)
*/
println "Called saveSomething"
}
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My First Tapestry Page</title>
</head>
<body>
<h1>My First Tapestry Page 3</h1>
Date: <div jwcid="@Insert" value="ognl:new java.util.Date()">June 26 2005</div>
<p>
The current value is:
<span style="font-size:xx-large"><span jwcid="@Insert" value="ognl:counter">37</span></span>
</p>
<p>
<a href="#" jwcid="clear@DirectLink" listener="listener:doClear">clear counter</a>
</p>
<p>
<a href="#" jwcid="@PageLink" page="Home">refresh</a>
</p>
<p>
<a href="#" jwcid="by1@DirectLink" listener="listener:doClick" parameters="ognl:1">increment counter by 1</a>
</p>
<p>
<a href="#" jwcid="by5@DirectLink" listener="listener:doClick" parameters="ognl:5">increment counter by 5</a>
</p>
<p>
<a href="#" jwcid="by10@DirectLink" listener="listener:doClick" parameters="ognl:10">increment counter by 10</a>
</p>
<p>
<a href="#" jwcid="saveSomething@DirectLink" listener="listener:saveSomething" >Save Something</a>
</p>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<page-specification class="com.troymaxventures.grapestry.pages.Home" >
<!--property name="counter" persist="true" /-->
</page-specification>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<application name="tapestryapplication">
<meta key="org.apache.tapestry.page-class-packages" value="com.troymaxventures.grapestry.pages"/>
</application>