Dec
10
2007

Gravl Week One: Webflowing an Ajax upload

If you haven’t started playing with the Webflow features of Grails, here’s a short demo of Gravl’s Ajax-powered blog upload feature to whet your appetite…

Webflow is one of those technologies that is *awesome* for one particular aspect of your application: wizard-like things that take multiple steps! If you don’t use a technology like Webflow, you end up writing a simple state engine, and filling your session with all sorts of stuff to keep track of where your objects are up to.

As it turns out, Steven Devijver, one of the Grails founders, and a nice guy, has written a great book on Spring Webflow, so it’s not surprising that the Grails Webflow integration is first class.

I’ve just finished implementing the “Upload Pebble” feature where I can upload a zip file straight into my Gravl blog engine. It’s got the Ajax goodness going on, of course, to keep you posted on how your upload is going:

Ajax Upload in Progress

The sweet looking progress bar is courtesy of Bramus.

Putting Ajax calls in the middle of a Webflow turns out to be a bit of a non-trivial operation, but before we get complex, let’s look at how the flow working in practice. Here’s a pic of how things are implemented at the moment:

Flow of the Ajax Progress Bar

And the implementation in the controller looks something like this:

   def fileUploadFlow = {

        showDialog {
            on("upload") {
                // slurp in the zip here omitted
                importService.setZipFile(zipfile)
                return [ziptype: importService.supportedTypes ]
            }.to "showImportDetails"
            on("cancel").to "headHome"
        }

        showImportDetails {
            on("cancel").to "headHome"
            on("upload") {
                // use our flow-scoped service
                importService.setBlogId(params.blogId)
                importService.setBlogType(params.blogType)
            }.to "ajaxUpload"
        }

        // display ajax upload form
        ajaxUpload {
            on("update").to("refreshResults")
            on("cancel").to "headHome"
        }

        // refresh the ajax progress
        refreshResults {
            action {
                def percent = importService.percentComplete()
                [ percentComplete: percent ]
            }
            on("success").to "_webflowForm"
        }

        // send a webform with the results, unusual
        // _naming allows us to reuse the form
        // as a gsp template in the initial ajaxUpload view
        _webflowForm {
            on("update").to("refreshResults")
            on("complete").to("finishedImport")
            on("cancel").to "headHome"
        }

        finishedImport()

        headHome {
            redirect(action: index)
        }

    }

Those “update” and “cancel” state match the “Update” button in my gsps. So Webflow *really* simplifies the process of going through the wizard with all the state tracking done for me for free.

Now during the flow I need to keep track of all those input elements, then I need to invoke the upload process in a background thread so I can keep things whirring in the background while updating my progress counters from the UI.

What to do? Wouldn’t it be great if I could scope a fresh Grails Service object to the flow itself (kinda like a stateful service bean)? Turn out you can, with a one-liner to my ImportService:

 static scope = "conversation"

Then I can just reference importService in my flows and I always get a scoped copy. Voila!

The only genuinely tricky bit is the Ajax section. I’m using Prototype’s fabulous PeriodicalUpdater to do the regular polling, but Webflow does its state tracking through a _flowExecutionKey variable. Somehow I needed to serialise that value in each Ajax request to ensure that my session state was always A1.

I found the best solution for me was to have the Ajax call return a simple HTML form (with the appropriate hidden elements) and place that into a DIV. Then I could just make use of Prototype’s serialize() to get things into my parameters. Here’s my final magic (apologies for formatting):



new Ajax.PeriodicalUpdater('webflowFormDiv', '<g:createLink action="fileUpload"/>' ,
    {
    method: 'get',
    parameters: $('webflowForm').serialize(true) ,
    frequency: 10,
    decay: 2,
    onSuccess: function() {
        // hackery to delay update till the webflowFormDiv is refreshed... I wish onComplete() worked...
        setTimeout("myJsProgressBarHandler.setPercentage('myProgressBar', $('currentProgress').getValue())",500)
        if ($('currentProgress').getValue() == '100') {
            window.location = '?_flowExecutionKey=' + $('flowExecutionKey').getValue() + '&_eventId_complete=true'
        }
    }
});

        

One little bit of nastiness checks if we’ve reached 100%, then redirects the window to the “complete” event using the magic _eventId_complete call. During those periodic updates, my Ajax remote function just returns a small form to keep track of progress and the current valueof the _flowExecutionKey which changes on each call… (more dodgy formatting)

<form action="<g:createLink action="fileUpload"/>" id="webflowForm">
    <input id="currentProgress" value="${percentComplete}">
    <g:submitButton name="update" value="Update"/>
    <input id="flowExecutionKey" name="_flowExecutionKey" value="${request.flowExecutionKey}" size="100"/>
</form>

Well that’s probably more than you ever wanted to know about implementing a periodic Ajax updater for blog uploads. Now I’ve got a ton of sample data, I’m better placed to move onto some fun basic blog engine stuff. I’m thinking tag clouds might be a fun next stop…

Stay tuned…

About the Author: Glen Smith

8 Comments + Add Comment

  • Hi !

    Thanks for this helpful entry !

    I was really struggling trying to integrate cleanly Ajax calls into my Grails flow … and, thanks to you, it’s working now ! :-)

    Regards.

  • Excellent stuff, as always. Would you mind shedding some light on the ImportService…?

  • Sure. Check out the source. It’s all “online”:http://code.google.com/p/gravl/ at Google Code.

  • so, i would like a package or a script or an archive,… to have a file upload progress bar (speed, time remaining, size, …) that can i put in my grails application, can you explain me how can i have this ?
    thks
    best regards

  • Sure there are full instructions on how to use the javascript progress bar at the “Bramus” link at the start of the article. For a grails example, check out the src for Gravl (link in the right sidebar).

  • thls a lot.
    But i’ve tried to download your grails example but on google code , section downloads there are no files, nothing to download, can you explain me please ?

  • Hi Julien, You need to download the code via SVN. There are instruction on the “Source” tab of the project. Good luck.

  • hi Glen,
    I’m devellopping an grails application which it i would like an upload page with progress bar, i’m a very newbie about grails so can you give me only your upload grails machine. If it possible can you make me a simple grails application only for upload please, or can you give me an exampl more simple please !!!! thks a lot ! best regards !

Leave a comment

Glen Smith

About Glen

Co-author Grails in Action