Flex on Grails, Take 2: Part 3

At the end of the second article in this series, we ended up with a working application but it was not really ready for the real world because it had one major flaw: the URL of the AMF endpoint was hardcoded in the client in such a way that it was impossible to change after compilation and very hard to handle several environments (dev, test, prod). The solution to that problem is to integrate dependency injection into the mix.

Now there are a lot of such frameworks for Flex/ActionScript applications, including Parsley, Swiz, Cairngorm, etc. But I’ve never been a big fan of those big MVC frameworks that impose their own interpretation of the MVC pattern and completely limit the initial capabilities of Flex itself. For me, the Flex framework itself is clean enough that you don’t need all that overhead and it’s better to use a non-intrusive framework like Spring ActionScript. So that’s what we are going to do.

First off, go download the latest release of Spring ActionScript with its dependencies. Unzip this distribution and copy all four as3commons libraries under lib/ into the “libs” directory of your Flex project. Do the same with spring-actionscript-core-1.1.swc under dist/. You should now have 5 SWC’s in your libs folder:

Now, like its Java big brother, Spring ActionScript configures itself thanks to an XML application context file. And we are going to configure our AMF channel and our channel set in that file. We don’t want to embed that file into our Flex application because if we do so, we won’t be able to change it dynamically. And we want its content to be synchronized with the current environment. That’s why we will use a little trick here.

First, in your Grails application, create a new controller called org.epseelon.todolist.controllers.ApplicationcontextController. To do that, you can use the following command: “grails create-controller org.epseelon.todolist.controllers.Applicationcontext”. You can also use IntelliJ’s shortcuts: right-click the grails-app/controllers directory and choose “New Grails Controller”:

Controller Creation

And in the dialog that appears, enter “org.epseelon.todolist.controllers.Applicationcontext”:

New Grails Controller

Now you can leave the ApplicationcontextController class as it is. But if you created your controller with IntelliJ IDEA, you should have a new index.gsp file under grails-app/views/applicationcontext. We are going to modify that file and use the fact that Grails can render XML templates and HTML templates in the same way:

<%@ page import="org.codehaus.groovy.grails.commons.ConfigurationHolder" contentType="text/xml;charset=UTF-8" %>
<objects>
    <object id="amfChannel" class="mx.messaging.channels.AMFChannel" scope="singleton">
        <constructor-arg value="my-amf"></constructor-arg>
        <constructor-arg value="${ConfigurationHolder.config.grails.serverURL}/messagebroker/amf"></constructor-arg>
        <property name="id" value="amfChannel"></property>
        <property name="pollingEnabled" value="false"></property>
    </object>

    <object id="channelSet" class="mx.messaging.ChannelSet" scope="singleton">
        <method-invocation name="addChannel">
            <arg>
                <ref>amfChannel</ref>
            </arg>
        </method-invocation>
    </object>
</objects>

The first thing to notice here is the content type of this GSP, which is text/xml and not text/html. We also import ConfigurationHolder which will allow us to user the environment-specific server URL as defined in grails-app/conf/Config.groovy without having to load it from the controller. Our AMF endpoint URL is now configured in the second argument of the constructor for the first bean. What this application context does is that it configures an AMF channel and a channel set that we will be able to inject into our Flex application. But you can already test this controller by starting your Grails application and going to http://localhost:8080/todolist/applicationcontext.xml. You might wonder where this extension comes from: Grails uses the extension as an indication of the context type that is expected as a response, which makes it possible get different response formats from the same controller action just by changing the extension. Here we are just using the fact that by default, an empty action renders a GSP view with the same name as the action (index) and the content type declared in the GSP corresponds to the one requested.

The next step is to load that application context into our Flex application. To do that, let’s add a couple of event handlers to our s:Application element:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			   xmlns:services="services.*"
			   minWidth="955" minHeight="600"
			   currentState="{channelSet != null ? (channelSet.authenticated ? 'loggedIn' : 'loggedOut') : 'loggedOut'}"
			   preinitialize="preinitializeHandler(event)"
			   applicationComplete="applicationCompleteHandler(event)">
	<fx:Script>
		<![CDATA[
			...
			import org.springextensions.actionscript.context.support.XMLApplicationContext;
			
			[Bindable]
			private var channelSet:ChannelSet;
			
			private var contextURL:String;

			protected function preinitializeHandler(event:FlexEvent):void {
				contextURL = parameters['ContextURL'];
			}

			protected function applicationCompleteHandler(event:FlexEvent):void {
				var applicationContext:XMLApplicationContext = new XMLApplicationContext();
				applicationContext.addConfigLocation(contextURL);
				applicationContext.addEventListener(Event.COMPLETE, contextLoadedHandler);
				applicationContext.load();
			}
			
			protected function contextLoadedHandler(event:Event):void{
				channelSet = (event.target as XMLApplicationContext).getObject("channelSet");
				reloadTasks();
			}

                        ...
		]]>
	</fx:Script>
	...
</s:Application>

We add 2 variables, contextURL et channelSet. We also modified the currentState attribute of our application to consider the possibility that channelSet might not be initialized yet when currentState is determined. In the preinitialization handler, we load the contextURL, which is the URL of our applicationcontext.xml file from a mysterious parameters map. We will see later where this parameter comes from. When the application is completely loaded, we bootstrap Spring ActionScript by creating an XMLApplicationContext configured with the asynchronously-loaded application context. And when the context is completely loaded, we manually inject the channelSet bean into our channelSet variable, and since we are now connected to the server, we can reload the tasks.

Of course, we also have to update our TodoListService declaration to remove the manually configured channel set and replace it with this Spring ActionScript injected one:

<services:TodoListService id="todoListService" 
	fault="Alert.show(event.fault.faultString + 'n' + event.fault.faultDetail)" 
	showBusyCursor="true" channelSet="{channelSet}">
</services:TodoListService>

Now, let’s get back to this mysterious contextURL parameter. Once again, we don’t want the location of our applicationcontext.xml file to be hardcoded into our application, otherwise we are just moving the problem. So the idea here is to use what is to inject a flash variable from the HTML file that embeds our Flex application. We will take this opportunity to get rid of the default HTML template generated by Flash Builder since chances are we will want to integrate our application into a GSP anyway. So now copy the content of web-app/Main.html into grails-app/views/index.gsp and modify it like the following:

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
    <!--
    Smart developers always View Source.

    This application was built using Adobe Flex, an open source framework
    for building rich Internet applications that get delivered via the
    Flash Player or to desktops via Adobe AIR.

    Learn more about Flex at http://flex.org
    // -->
    <head>
        <title>Todo List</title>
        <meta name="google" value="notranslate">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<!-- Include CSS to eliminate any default margins/padding and set the height of the html element and
		     the body element to 100%, because Firefox, or any Gecko based browser, interprets percentage as
			 the percentage of the height of its parent container, which has to be set explicitly.  Fix for
			 Firefox 3.6 focus border issues.  Initially, don't display flashContent div so it won't show
			 if JavaScript disabled.
		-->
        <style type="text/css" media="screen">
			html, body	{ height:100%; }
			body { margin:0; padding:0; overflow:auto; text-align:center;
			       background-color: #ffffff; }
			object:focus { outline:none; }
			#flashContent { display:none; }
        </style>

		<!-- Enable Browser History by replacing useBrowserHistory tokens with two hyphens -->
        <!-- BEGIN Browser History required section -->
        <link rel="stylesheet" type="text/css" href="history/history.css" />
        <script type="text/javascript" src="history/history.js"></script>
        <!-- END Browser History required section -->

        <script type="text/javascript" src="swfobject.js"></script>
        <script type="text/javascript">
            <!-- For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection. -->
            var swfVersionStr = "10.0.0";
            <!-- To use express install, set to playerProductInstall.swf, otherwise the empty string. -->
            var xiSwfUrlStr = "playerProductInstall.swf";
            var flashvars = {ContextURL:"applicationcontext.xml"};
            var params = {};
            params.quality = "high";
            params.bgcolor = "#ffffff";
            params.allowscriptaccess = "sameDomain";
            params.allowfullscreen = "true";
            var attributes = {};
            attributes.id = "Main";
            attributes.name = "Main";
            attributes.align = "middle";
            swfobject.embedSWF(
                "Main.swf", "flashContent",
                "100%", "100%",
                swfVersionStr, xiSwfUrlStr,
                flashvars, params, attributes);
			<!-- JavaScript enabled so display the flashContent div in case it is not replaced with a swf object. -->
			swfobject.createCSS("#flashContent", "display:block;text-align:left;");
        </script>
    </head>
    <body>
        <!-- SWFObject's dynamic embed method replaces this alternative HTML content with Flash content when enough
			 JavaScript and Flash plug-in support is available. The div is initially hidden so that it doesn't show
			 when JavaScript is disabled.
		-->
        <div id="flashContent">
        	<p>
	        	To view this page ensure that Adobe Flash Player version
				10.0.0 or greater is installed.
			</p>
			<script type="text/javascript">
				var pageHost = ((document.location.protocol == "https:") ? "https://" :	"http://");
				document.write("<a href='http://www.adobe.com/go/getflashplayer'><img src='"
								+ pageHost + "www.adobe.com/images/shared/download_buttons/get_flash_player.gif' alt='Get Adobe Flash player' /></a>" );
			</script>
        </div>

       	<noscript>
            <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%" id="Main">
                <param name="FlashVars" value="ContextURL=applicationcontext.xml" />
                <param name="movie" value="Main.swf" />
                <param name="quality" value="high" />
                <param name="bgcolor" value="#ffffff" />
                <param name="allowScriptAccess" value="sameDomain" />
                <param name="allowFullScreen" value="true" />
                <!--[if !IE]>-->
                <object type="application/x-shockwave-flash" data="Main.swf" width="100%" height="100%">
                    <param name="quality" value="high" />
                    <param name="bgcolor" value="#ffffff" />
                    <param name="allowScriptAccess" value="sameDomain" />
                    <param name="allowFullScreen" value="true" />
                <!--<![endif]-->
                <!--[if gte IE 6]>-->
                	<p>
                		Either scripts and active content are not permitted to run or Adobe Flash Player version
                		10.0.0 or greater is not installed.
                	</p>
                <!--<![endif]-->
                    <a href="http://www.adobe.com/go/getflashplayer">
                        <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash Player" />
                    </a>
                <!--[if !IE]>-->
                </object>
                <!--<![endif]-->
            </object>
	    </noscript>
   </body>
</html>

The first modification is on line 41: var flashvars = {ContextURL:”applicationcontext.xml”};

The second modification is on line 79:

Both of these lines to the same thing, but the second one is used when javascript is disabled in the browser. Those lines assign a value of “applicationcontext.xml” that is the URL of our application context configuration file relative to the url of our main page. And that’s the ContextURL flash variable that is then retrieved in the Flex application. The loop is now complete.

Now we can disable HTML Template generation in Flash Builder: right-click your Flash Builder project and choose Properties:

FB Project Properties

In Flex Compiler section, uncheck “Generate HTML wrapper file”:

HTML wrapper generation option

Click OK and confirm the next dialog box. Don’t worry again, nothing will be deleted.

Now you can go back to your Grails application and delete the following file: web-app/Main.html

If you restart your Grails application and go to http://localhost:8080/todolist, you should now see our todo list with our 3 test tasks loaded. You can even log in and create a new task.

The full code of this tutorial is available on GitHub.

For now, I don’t see what I could add to this sample application. I’m going to use it myself as a starting point for some real world project of mine and I’ll see if I discover new things that are worth sharing. Make good use of this and enjoy this great combination of highly productive technologies.

7 comments

  1. Many thanks to Sebastien and Burt for this great job! Came the blessed time when the plugin started to work well. =)
    Is it possible to use this plugin to initialize MessageService in BlazeDS to send Messages to Flex-clients?
    Sincerely, Manque.

  2. Thank you Sebastien, you should write a book. Have been working with Flex/Blaze/Spring Integration/Spring Actionscript for two years now. Have been through the issues of Blaze not working with Groovy classes remotely and then Groovy not compiling in Roo (because of the AJ compiler). This should improve my efficiency a bunch.

    Rick Holland

  3. Thanks Sebastien. I’m trying to extend your module to allow service-crud-templating, where ideally you would only have to annotate the domain class and then automatically have the service and methods needed by Flash Builder’s Data Management exposed for the annotated domain class – by using an (possibly) extendable template service.

    Any chance you would like to help me on that? I’ll have to warn you though: I’m a bloody beginner to grails plugin programming. 😉

  4. Also, I just tried installing from git, and found a dependency conflict, in that acl requires spring security 1.0.1, whereas your plugin requires 1.1.2.

    When importing as plugin project to spring tool suite, this results ambiguity, e.g.

    [groovyc] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.1.2grails-appconfDefaultSecurityConfig.groovy: -1: Invalid duplicate class definition of class DefaultSecurityConfig : The sources C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.1.2grails-appconfDefaultSecurityConfig.groovy and C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.0.1grails-appconfDefaultSecurityConfig.groovy are containing both a class of the name DefaultSecurityConfig.
    [groovyc] @ line -1, column -1.C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.1.2grails-appservicesgrailspluginsspringsecuritySpringSecurityService.groovy: 29: Invalid duplicate class definition of class grails.plugins.springsecurity.SpringSecurityService : The sources C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.1.2grails-appservicesgrailspluginsspringsecuritySpringSecurityService.groovy and C:Usersjohn.cunliffe.grails1.2.3projectsgrails-blazedspluginsspring-security-core-1.0.1grails-appservicesgrailspluginsspringsecuritySpringSecurityService.groovy are containing both a class of the name grails.plugins.springsecurity.SpringSecurityService.
    [groovyc] @ line 29, column 1.
    [groovyc] class SpringSecurityService {
    [groovyc] ^
    [groovyc]

  5. i know it’s probably a little too late but… I have a problem when configuring a custon exception Translator. All of a sudden all controllers are unreachable (give 404 error). Any hints on why this might be happening? Here’s my resources.groovy code:

    (…)

    // myExceptionTranslator(com.exceptions.MyExceptionTranslator) {}

    defaultMessageTemplate(MessageTemplate)

    xmlns flex: ‘http://www.springframework.org/schema/flex’
    flex.’message-broker'{
    flex.’exception-translator'(ref: “myExceptionTranslator”)
    }

    (…)

Leave a Reply to Tweets that mention Flex on Grails, Take 2: Part 3 | Epseelon -- Topsy.com Cancel reply