Felipe Cypriano You are about to read it

10Dec/0920

Grails with ZK: Embedding ZUL in GSP

How about zk pages and gsp were so friends that we could use both together? Imagine the possibility to use Sitemesh to decorate your page or use UrlMapping to choose what the URL will be. Or you could move your project to ZK painlessly, you could update the pages one by one and the new code will work side by side.

ZKGrails plugin 0.7.6 has been released with this great new feature, you can use two simple tags to include any zul page in your GSP. To include the necessary css and javascript into head use <z:head/> and to insert the content anywhre you want in the body of your gsp you'll use <z:body/>.

To see it in practice let's change Grails Quick Start. Follow the steps described there and after you complete all the steps, go back here to update the list.gsp to use ZK Grid component. Don't forget to use Grails 1.1.2 and ZKGrails 0.7.6 at least.

Embedding ZK in GSP

Firstly create this file: grails-app/conf/BuildConfig.groovy and configure ZKGrails repository:

grails.plugin.repos.discovery.zkgrails = "http://zkgrails.googlecode.com/svn/plugins"
grails.plugin.repos.resolveOrder = ['zkgrails','default','core']

Now you can install ZKGrails version 0.7.6 by executing grails install-plugin zk 0.7.6. After installing ZK plugin, create the zul page we'll use: web-app/book/list.zul. You can let this file blank for now, before editing it we need to change automatic scaffolding by creating the the gsp pages  executing this command:

grails generate-views Book

The command will create 4 gsp files in grails-app/views/book: create.gsp, edit.gsp, list.gsp and show.gsp. We will change the list.gsp to instead of use simple HTML table we'll use ZK components. Now let's get back to our web-app/book/list.zul, the objective is to create a grid using zk components and to add a new functionality to delete all selected books. Open list.zul file and put the following code in it:

<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" arg0="./wnd"?>
<zk xmlns:n="http://www.zkoss.org/2005/zk/native">
    <window id="wnd" apply="${bookListComposer}">
        <listbox id="listBoxBooks" model="@{wnd$composer.booksModel}" checkmark="true" multiple="true"
                 fixedLayout="true" width="500px">
            <listhead>
                <listheader label="ID" sort="auto(id)" width="50px"/>
                <listheader label="Author" sort="auto(author)" width="225px"/>
                <listheader label="Title" sort="auto(title)" width="225px"/>
            </listhead>
            <listitem self="@{each=book}" value="@{book}">
                <listcell label="@{book.id}"/>
                <listcell label="@{book.author}"/>
                <listcell label="@{book.title}"/>
            </listitem>
        </listbox>
        <n:span class="buttons">
            <button id="btnDelete" sclass="delete" mold="os" label="Delete Selected"/>
        </n:span>
    </window>
</zk>

Next step is create the composer for this list.zul page, the composer is the thing that controls the page's behavior, just like a controller. Create a BookListComposer.groovy file in grails-app/composers, and this is the content:

import org.zkoss.zkgrails.*
import org.zkoss.zkplus.databind.BindingListModelList

class BookListComposer extends GrailsComposer {

    def wnd
    def listBoxBooks
    def booksModel
    def binder

    def afterCompose = {
        booksModel = new BindingListModelList([], true)
        reloadBooks()
        binder = wnd.getVariable("binder", true)
    }

    public void onClick_btnDelete() {
        if (listBoxBooks.selectedCount > 0) {
            listBoxBooks.selectedItems.each { listItem ->
                def book = listItem.value
                book.delete(flush: true)
            }
            reloadBooks()
        }
    }

    private def reloadBooks() {
        def books = Book.list()
        booksModel.clear()
        booksModel.addAll(books)
        binder?.loadAll()
    }

}

Now we only need to put the zul page in the list.gsp replacing the HTML <table> tag to <z:body />, the final code is this:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="layout" content="main" />
        <z:head />
        <title>Book List</title>
    </head>
    <body>
        <div class="nav">
            <span class="menuButton"><a class="home" href="${resource(dir:'')}">Home</a></span>
            <span class="menuButton"><g:link class="create" action="create">New Book</g:link></span>
        </div>
        <div class="body">
            <h1>Book List</h1>
            <g:if test="${flash.message}">
            <div class="message">${flash.message}</div>
            </g:if>

            <z:body />
        </div>
    </body>
</html>

As you can see - lines 5 and 19 - we only need to put the tags where we want it's content to be in the page. But you might be wondering where is the path to list.zul file? By default the same convention used by Grails' view, so for our example both tags - head and body - automatically resolves to /book/list.zul. And if you need to specify the files you want you can by setting the attribute zul, like:

<z:body zul="/path/to/file.zul" />

Now we can mashup our both favorite frameworks to make our projects look and behave even better.

# Update 1

Fixed a missing namespace on zul file; and the binder call on BookListComposer. Thanks André.

# Update 2

GitHub repository with full source code used to made this post: http://github.com/fmcypriano/embedding-zul-gsp

Comments (20) Trackbacks (0)
  1. Felipe I tried to follow your example, but an exception is thrown complaining about n:span in the list.zul file. What could cause this? I am using grails 1.2.0 final

  2. I don’t know why, but your example didn’t work for me. When I call the list.zul It throws an Exception Message: The prefix “n” for element “n:span” is not bound.
    Caused by: org.xml.sax.SAXParseException: The prefix “n” for element “n:span” is not bound.

    I’m using grails 1.2.0 final

  3. Hello André,

    I try to make the sample codes as simple as possible and in this case I’ve removed the namespace “n” from the xml. Try to update your zul file with this:

    <zk xmlns:n="http://www.zkoss.org/2005/zk/native">
        <!-- actual code here -->
    </zk>

    Best regards

  4. If want to know more about ZK’s native namespace read this article: http://www.zkoss.org/doc/devguide/ch07s17s02.html

  5. Thanks Felipe for answering… it works! But now there`s a new exception:

    Exception Message: Cannot invoke method loadAll() on null object
    Caused by: java.lang.NullPointerException: Cannot invoke method loadAll() on null object
    Class: BookListComposer

    If I comment the line “binder.loadAll()” in BookListComposer the application works, but I guess this line has it importance…

    Andre

  6. I was having issues with binder yesterday, on another code, and my solution was don’t call it on afterCompose phase. So, to fix your problem you could use some groovy magic and change the code to:

    binder?.loadAll()

    In afterCompose phase the binder won’t be called, but in the other events, like onClick it will. And it’s necessary to updated the listbox with the new data from the model.

    Regards

  7. Hi All,

    I am just wondering if people are yet mixing Grails and ZK into the same production applications and if so then why?

    rgds

    • @Simon, Grails and ZK are different kinds of frameworks that can complement each other. While ZK is a GUI framework Grails is a full stack one and one part of it is responsible for the GUI which is the GSP technology. I’m using both together because I can easily integrate the facilities of grails/groovy with ZK, and ZK have a lot of great built-in components. So with both frameworks my code is easier and richer.

  8. Hi,
    I’ve tried your example but gsp pages renders

    regexp (?m)(?s)(?i)\A.*\Q

    instead of the zk head and

    regexp (?m)(?s)(?i)\Q
    .* not found

    instead of the zk body…

    I’m new to grails but I’m pretty sure my code is correct
    Any suggestion?

    Thanks
    bye

    • Hello Matteo,

      I never seen these messages before. But I’ve just pushed the code I used to made this post to a github repository and you can access it here: http://github.com/fmcypriano/embedding-zul-gsp

      Compare your code with mine. If you find any bugs please don’t hesitate to tell me.

      Regards

      • Hi,
        I just tried your code but I got the same error (printed by line 42 @ zkgrails/src/groovy/org/zkoss/zkgrails/ZulResponder.groovy).
        I was using grails 1.2.0 and zkgrails-1.0M1, but I tried also with grails 1.1.2 and zkgrails 0.7.6, same error…

        Maybe it’s something wrong in my environment (groovy 1.7.0, JVM: 1.6.0_17, Mac Snow Leopard), I’ll investigate more.
        Thank you for your quick reply
        Regards

        • This is strange. The only difference I see between our environment is the groovy version, I do not have a standalone groovy installation. I’m using only the groovy that is bundled with Grails, which I think is 1.6.

        • Just tried with groovy 1.6, same error

    • I was able to reproduce this error, I’m checking the cause.

  9. Any chances to update the code using zkgrails 1.0-M2?

  10. Hello Felipe,

    I tried to run your example. Any idea about following error:

    ERROR org.zkoss – Failed to assign [model=] to
    Unable to find a setter named model that supports

    ERROR view.GroovyPageView – Error processing GroovyPageView: Error executing tag : java.lang.IndexOutOfBoundsException: index is out of range 0..-1 (index = 0) at C:/workspace/sts/mobillonline/grails-app/views/product/list.gsp:7
    org.codehaus.groovy.grails.web.taglib.exceptions.GrailsTagException: Error executing tag : java.lang.IndexOutOfBoundsException: index is out of range 0..-1 (index = 0) at C:/workspace/sts/mobillonline/grails-app/views/product/list.gsp:7
    at C__workspace_sts_mobillonline_grails_app_views_product_list_gsp$_run_closure1.doCall(C__workspace_sts_mobillonline_grails_app_views_product_list_gsp:24)
    at C__workspace_sts_mobillonline_grails_app_views_product_list_gsp$_run_closure1.doCall(C__workspace_sts_mobillonline_grails_app_views_product_list_gsp)
    at C__workspace_sts_mobillonline_grails_app_views_product_list_gsp.run(C__workspace_sts_mobillonline_grails_app_views_product_list_gsp:34)
    at org.zkoss.zkgrails.ZKGrailsPageFilter.obtainContent(ZKGrailsPageFilter.java:201)
    at org.zkoss.zkgrails.ZKGrailsPageFilter.doFilter(ZKGrailsPageFilter.java:140)
    at java.lang.Thread.run(Thread.java:595)
    Caused by: org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.IndexOutOfBoundsException: index is out of range 0..-1 (index = 0)
    … 6 more
    Caused by: java.lang.IndexOutOfBoundsException: index is out of range 0..-1 (index = 0)
    at org.zkoss.zkgrails.ZulResponse.(ZulResponse.groovy:42)
    at ZkTagLib.cacheZul(ZkTagLib.groovy:70)
    at ZkTagLib.this$2$cacheZul(ZkTagLib.groovy)
    at ZkTagLib$_closure1.doCall(ZkTagLib.groovy:9)
    … 6 more

    • It doesn’t seem to be a ZK problem. In ‘mobillonline/grails-app/views/product/list.gsp’ file on line 7 you’re trying to access an element index that doesn’t exists in the array or list.

      Read the error message you sent.

      • Thanks for replying,

        I think the following error is better to understand, this error is shown when I try to do a direct access to books/list.zul. When I try to access from books/list.gsp it seems that isn’t recognized. Thanks in advance.

        ERROR org.zkoss – Failed to assign [model=] to
        Unable to find a setter named model that supports
        ERROR [/mobillonline].[zkLoader] – Servlet.service() for servlet zkLoader threw exception
        java.lang.ClassCastException: Unable to find a setter named model that supports
        at org.zkoss.zk.ui.metainfo.Property.assign0(Property.java:283)
        at org.zkoss.zk.ui.metainfo.Property.assign(Property.java:173)
        at org.zkoss.zk.ui.metainfo.ComponentInfo.applyProperties(ComponentInfo.java:804)
        at org.zkoss.zk.ui.impl.AbstractUiFactory.newComponent(AbstractUiFactory.java:93)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreateChild0(UiEngineImpl.java:622)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreateChild(UiEngineImpl.java:593)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreate0(UiEngineImpl.java:537)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreate(UiEngineImpl.java:504)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreateChild0(UiEngineImpl.java:637)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreateChild(UiEngineImpl.java:593)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreate0(UiEngineImpl.java:537)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreateChild(UiEngineImpl.java:569)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreate0(UiEngineImpl.java:537)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execCreate(UiEngineImpl.java:504)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execNewPage0(UiEngineImpl.java:365)
        at org.zkoss.zk.ui.impl.UiEngineImpl.execNewPage(UiEngineImpl.java:286)
        at org.zkoss.zk.ui.http.DHtmlLayoutServlet.process(DHtmlLayoutServlet.java:237)
        at org.zkoss.zk.ui.http.DHtmlLayoutServlet.doGet(DHtmlLayoutServlet.java:159)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.
        processFilterChain(UrlMappingsFilter.java:282)
        at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:211)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.zkoss.zkgrails.ZKGrailsPageFilter.doFilter(ZKGrailsPageFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.springframework.orm.hibernate3.support.
        OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:198)
        at org.codehaus.groovy.grails.orm.hibernate.support.
        GrailsOpenSessionInViewFilter.doFilterInternal(GrailsOpenSessionInViewFilter.java:64)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.codehaus.groovy.grails.web.servlet.mvc.
        GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:65)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.codehaus.groovy.grails.web.filters.
        HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:66)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.springframework.web.filter.CharacterEncodingFilter.
        doFilterInternal(CharacterEncodingFilter.java:88)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.springframework.web.filter.DelegatingFilterProxy.
        invokeDelegate(DelegatingFilterProxy.java:237)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:595)


Leave a comment


 

No trackbacks yet.