JSF 2
Dependency:
compile "org.grails.plugins:jsf2:0.1"
Summary
Installation
grails install-plugin jsf2
When jsf2-plugin is starting, a copy of the generated web.xml is done into /web-app/web-inf/web.xml. This is due to a tomcat plugin bug when fetching webXmlLocation property. Do a backup of your file or include it in your src/template/web.xml.
Description
Following JSF-ICEfaces plugin...
Based on icefaces plugin work. It prepares the background for ICEfaces 2 plugin but I preferred to split implementation from standard in order to allow developper to make their own JSF framework integration.JSF-2 integration
- Bean By convention (no need for faces-config)
- Bean access to controller parameters (session,request,params...)
- Beans dynamic methods for navigation redirect(action?,bean?,view?,uri?,url?) and render(action?,view?,bean?),
- Automatic bean and service properties resolution
- 'void init()' called at bean initialization if present
- 'void dispose()' called at bean destruction if present
- JSF Extended request scope ('view')
- Access to web.xml configuration via Jsf2Config.groovy
- Access to faces-config generated in web-app/faces-config.xml
- Converters for Locale, Closure, Currency, Timezone
- i18n ready, fast access with #{m['key']}
- create-bean script
- Hibernate session managed from view rendering to view response
- Execute groovy code in EL expression ( #{gtag.groov[' def i = 2; 3.times{ i++ }; i; ']} )
- Support JSF2 Components - @ManagedBean ...
Extra methods for beans :
- Support tagLibraries methods
- Support controllers properties
- redirect(action?,bean?,view?,uri?,url?),
- render(action?,view?,bean?)
- facesMessage(summary?, details?, severity?)
Sample code
Use of create-bean package.Class will do this :package appclass DummyBean{ def static scope = 'view' //['view' (jsf2special request scope),'request','session','globalSession' (portlet),'conversation'(swf),'flow'(swf)] def property = "hi" def oldProperty = "bye" //def otherBean (autowired bean) void init(){ //called when bean starts } void dispose(){ //called when bean stop } /*String action() { }*/ void say(javax.faces.event.ActionEvent ae) { // render "test2.xhtml" change view // redirect "test.xhtml" redirect view, forcing page scoped bean clean } void change(javax.faces.event.ActionEvent ae) { def changed = property property = oldProperty oldProperty = changed } }
<?xml version='1.0' encoding='utf-8'?> <f:view xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <html> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> </h:head> <h:body> <h:outputScript name="jsf.js" library="javax.faces" target="body" /> <h:form> <h:panelGroup id="prop"> <h:outputText value="#{dummyBean.property}"/> <h:commandButton value="say #{dummyBean.property}" actionListener="#{dummyBean.say}"/> <h:commandButton value="prepare #{dummyBean.oldProperty}" actionListener="#{dummyBean.change}"> <f:ajax render="prop"/> </h:commandButton> </h:panelGroup> </h:form> </h:body> </html> </f:view>
Navigation by convention
Here some equivalents methods<h:commandLink action="/bean/test.iface" /> <h:commandLink action="/bean/test" /> <h:commandLink action="test" /> <h:commandLink action="test.xhtml" />
render action:"/bean/test.xhtml" //and the equivalents seen before render "/bean/test.xhtml" // == render action:* render view:"test" // == /bean/test.xhtml render bean:"bean2" // /bean2/index.xhtml render bean:"bean2",view:"test" :// /bean2/test.xhtml
redirect url:"www.google.fr" redirect action:"test" render action:"test?faces-redirect=true" <h:commandLink action="test?faces-redirect=true" />
Greets
- Thanks to Grails team http://www.grails.org
- This is a contribution offered by my consulting company doc4web : http://www.doc4web.com
Success story: using jsf plugin with PrimeFaces and Grails 1.3.7
Update dependencies
These informations are for the plugin version 0.1dependencies { provided 'org.hibernate:hibernate-core:3.3.1.GA', 'javax.servlet:servlet-api:2.5', 'com.sun.faces:jsf-api:2.1.2', 'com.sun.faces:jsf-impl:2.1.2' compile 'javax.servlet:jstl:1.2' }
Patches
These informations are for the plugin version 0.1GrailsResourceResolver
There is a small bug using this plugin and install a deployable WAR. The implementation uses the default GroovyPageResourceLoader which is not available creating and deploy the WAR file.Try this fix in the constructor of GrailsResourceResolver of the plugin:public GrailsResourceResolver() { System.out.println("GrailsResourceResolver: constructor"); // Bugfix: ResourceResolver only during Development, SB, 2011-11-24 if (ApplicationHolder.getApplication().getMainContext() .containsBean(GroovyPageResourceLoader.BEAN_ID)) { System.out.println("GrailsResourceResolver: get groovyPageResourceLoader"); this.resourceLoader = (GroovyPageResourceLoader) ApplicationHolder .getApplication().getMainContext() .getBean(GroovyPageResourceLoader.BEAN_ID); } else if (ApplicationHolder.getApplication().getMainContext() .containsBean("groovyPageResourceLoaderJSF")) { System.out.println("GrailsResourceResolver: get groovyPageResourceLoaderJSF"); this.resourceLoader = (GroovyPageResourceLoader) ApplicationHolder .getApplication().getMainContext() .getBean("groovyPageResourceLoaderJSF"); } }
<bean id="groovyPageResourceLoaderJSF" class="org.codehaus.groovy.grails.web.pages.GroovyPageResourceLoader"> <property name="baseResource"> <value>/WEB-INF/grails-app/views</value> </property> </bean>
Debug resource resolver
If you have problems receiving resources on your JSF pages like images or stylesheets try to debug the GrailsResourceResolver:/** * Bugfix: Get Resource through resourceLoader OR servletContextLoader * * @param uri * @return */ private Resource getResourceWithinContext(String uri) { System.out.println("GrailsResourceResolver: getResourceWithinContext: " + uri); if (resourceLoader != null) { System.out.println("GrailsResourceResolver: GroovyPageResourceLoader is active"); if (Environment.getCurrent().isReloadEnabled() && Metadata.getCurrent().isWarDeployed()) { return resourceLoader.getResource(uri); } Resource r = servletContextLoader.getResource(uri); if (r.exists()) return r; return resourceLoader.getResource(uri); } else { System.out.println("GrailsResourceResolver: trying to get resource through context"); Resource r = servletContextLoader.getResource(uri); if (r.exists()) { return r; } } System.out.println("GrailsResourceResolver: trying Spring MainContext"); Resource r = ApplicationHolder.getApplication().getMainContext().getResource(uri); if (r.exists()) { return r; } System.out.println("GrailsResourceResolver: trying fixed base path with Spring MainContext"); r = ApplicationHolder.getApplication().getMainContext().getResource("/WEB-INF/grails-app/views" + uri); if (r.exists()) { return r; } System.out.println("GrailsResourceResolver: couldn't get resource"); throw new IllegalStateException( "ResourceResolver not initialised correctly, no [resourceLoader] specified!"); }
Using message bundles
JSF was not able to resolve the grails bundles.Use this workaround:Place the bundles in the src/java folder of your project.e.g.<grails-project>/src/java/i18n/message_en_GB.properties
<application> … <locale-config> <default-locale>en_GB</default-locale> </locale-config> <resource-bundle> <base-name>i18n.messages</base-name> <var>msg</var> </resource-bundle> … </application>
<h:outputText value="#{msg['site.title']}" />
Using PrimeFaces
Add the dependecy to your grails configuration:e.g.compile 'org.primefaces:primefaces:3.0.1'
Tips and Tricks
Default Index
To set the default start to a JSF page I used a simple grails controller with a redirect:class HomeController {
def index = {
redirect(uri:"/login.xhtml")
}
}
"/"(controller: 'home', action: 'index')