GraniteDS Flex Plugin

  • Authors: null
20 votes
Dependency:
compile ":gdsflex:0.9.0"

 Documentation

Summary

Installation

Installation

Use this from the project folder:

grails install-plugin gdsflex

The plugin provides two main scripts :

  • the AS3 generator :
grails gas3
  • The Flex compilation
grails mxmlc

A simple example project can be downloaded here. It demonstrates most features of the plugin, and is described in details in this blog entry.

Plugin history

0.9.0 (28/10/2011)

  • Upgraded GDS libs to 2.3.0
  • Fixed mxmlc compilation broken in Grails 1.3.x
  • Added support for Flex 4.5+
  • See full release notes here

0.8.5 (21/01/2011)

  • Upgraded GDS libs to 2.2.0 SP1
  • Fixed issue with services-config.xml when using Flash builder with external Flex project

0.8.4 (15/11/2010)

  • Fixed stupid blocking bug when no security plugin is installed (J. Fletcher)
  • Added excludedClasses option to exclude classes from gas3 generation
  • Hackich workaround to fix typesafe remoting to services after class reloading
  • Updated GDS libs to pre-2.2.0.final

0.8.3 (08/10/2010)

  • Improved interoperability between html and Flex authentication
  • Support for controller authorization with @Secured / requestmap
  • autoCompileFlex option not enforced in production mode (J. Fletcher)

0.8.2 (17/09/2010)

  • Updated GDS libs to 2.2 RC2
  • Fixed support for transactional services proxied with cglib
  • Fixed generation of typesafe as3 proxies for services
  • Repackaging of generator integration to prevent classloading issues

0.8.1 (02/08/2010)

  • Updated GDS libs (with critical bug fix on Spring Security integration)
  • Fixed issue with compiler not including libraries from web-app/WEB-INF/flex/libs and support optional flex_libs folder for Flex libraries.
  • Added support for generation of typesafe client proxies for Grails services

0.8.0 (05/07/2010)

  • Updated GDS lib to GDS 2.2 beta1
  • Support for Grails 1.2+ and 1.3+
  • Support for Flex 4+, complete rewrite of the Flex compilation part of the plugin and various new options
  • Removed built-in Flex SDK to allow use of any Flex SDK version
  • Support for spring-security-core plugin

0.7.2 (27/09/2009)

  • Fixed plugin compilation on JDK 5
  • Updated GDS libs

0.7.1 (20/08/2009)

  • Fixed support for controllers with package names in Flex application generator

0.7 (11/08/2009)

  • Improved as3 template for domain classes including Grails constraints and association information
  • Added support for byte[] and Blob properties
  • Added scaffolding templates for gdsflex-enabled controllers (installed with grails install-flex-templates), including support for file upload/download from Flex
  • Added html-wrapper script to generate an html wrapper (web-app/app.html or grails-app/views/flexindex.gsp)
  • Support for Flex deep linking with [Path] annotation in Tide components
  • Generation of a Flex application from the domain classes with grails generate-flex-app using client-side dynamic UI builder

0.6.2 (16/07/2009)

  • Fixed as3 template for domain classes
  • Fixed support of gas3 extraClasses

0.6.1 (13/07/2009)

  • Updated GDS libraries to simplify Grails externalization
  • Fixed broken support for GAE
  • Fixed serialization of inherited domain classes
  • Improved fix for GORM events
  • Generation of Grails constraints in the AS3 classes

0.6 (09/07/2009)

  • Upgrade to GraniteDS 2.0 SP1 libraries
  • Restored Flex autoCompile
  • Initial support for app-engine plugin and deployment to Google App Engine
  • Completely refactored handling of Grails domain classes

0.5 (22/06/2009)

  • Upgrade to GraniteDS 2.0 GA libraries(see more details)
  • add mxmlc command:for example,grails mxmlc main.mxml or grails mxmlc main or grails mxmlc(will find all application and modules)
  • remove the auto-compilation for mxml/as etc.

0.4 (19/05/2009)

  • Upgrade to GraniteDS 2.0 RC1 libraries
  • Changed Flex compilation to compile in background in the grails-app/views/flex folder instead of using the web compiler
  • Support for pure GORM domain classes without JPA annotations

0.3.2 (13/04/2009)

  • fix the runtime error on windows

0.3.1 (12/04/2009)

  • Add domain support for JPA jar(configured in GraniteDSConfig.groovy:as3Config.domainJar)
  • Compile(incrementally) the mxml file from HTTP request to grails pacakage and monitor the modifying
  • mxml files are now put into grails-app/views/mxml,the swf files are compiled into views/swf

0.3 (06/04/2009)

  • Add support for Spring security authorizations (similar to ifAllGranted / ifAnyGranted tags)
  • Add support for Gravity server push and multiuser data updates
  • More info here

0.2 (26/03/2009)

  • Bug fixes in GDS core in handling of Grails controllers
  • Support for Spring validation errors
  • Web compiler now detects changes on ActionScript files

0.1 (16/03/2009)

  • Initial release

Description

GraniteDS Flex Plugin

This plugin provides integration between Grails and Adobe Flex using Granite Data Services

GraniteDS

GraniteDS is an open source project that provides integration between Adobe Flex and Java frameworks. It has had full support Hibernate and Spring for a long time, so implementing the integration with Grails is quite natural. The plugin is currently a beta version and has been tested with Grails 1.2.2+ and Grails 1.3.1+.

Features

  • Automatic configuration: the various parts required in web.xml and other Flex/GraniteDS configuration files are automatically setup by the plugin. GraniteDS 2.2.0.RC1 Java and Flex libraries are included.
  • Automatic generation of the Flex domain classes: the embedded Gas3 generator automatically generates as3 classes from the Groovy model. Gas3 is registed as a compilation event listener, so there is no manual operation needed in most cases. Even if it is recommended to use JPA annotations for the Groovy entities, pure GORM entities are now supported since 0.4.
  • On-the-fly Flex compilation: the Flex OEM compiler is automatically triggered in background when changing any mxml/as/css in the grails-app/views/flex folder (this folder is now configurable). The swf will be built in grails-app/views/swf. The main mxml file must have the same name than the Grails application.
  • Includes the GraniteDS Tide client framework: it gives full support for transparent lazy loading of collections, paged data and other data related features.
  • Grails services and controllers can be exposed as Flex remoting destinations with a simple annotation: no particular configuration is needed to expose a Grails component to Flex, just use the org.granite.tide.annotations.TideEnabled. Newly deployed or redeployed services are automatically exposed without any manual operation.
  • Grails controllers results can be used with Flex data binding: the model variables returned by controller components are available as Tide context variables and can easily be bound to the Flex UI.
  • Includes the html-wrapper Flex task to generate an html page embedding the swf application.
  • Flex application generator to get started quickly with a simple CRUD application.
  • Integration with Spring security: remote calls can be secured and credentials are propagated to Grails. The Identity Flex component can be used to show/hide parts of the UI depending on the user access rights. Both the acegi and spring-security-core plugins are now supported since 0.8.0.
  • Support for Gravity server push: server push with Jetty continuations or Tomcat Comet support can be enabled and data can be exchanged between Flex clients or from Grails to Flex.

Installation

Use this from the project folder:

grails install-plugin gdsflex

Getting started tutorial

In this short tutorial, we are going to create a simple Flex application that list people in a DataGrid.

First create a Grails application and install the plugin :

grails create-app example

cd example

grails install-plugin gdsflex

Now create a Person domain class, a Grails controller (using scaffolding so we can enter test data) and a Grails service that we will access from Flex.

grails create-domain-class Person

grails create-controller Person

grails create-service Person

Edit grails-app/domain/example/Person.groovy :

package example

class Person {

String uid

String firstName

String lastName }

Edit grails-app/controllers/example/PersonController.groovy :

package example

class PersonController {

def scaffold = Person }

Edit grails-app/services/example/PersonService.groovy :

package example

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleService {

static transactional = true

def list() { return Person.list() } }

Our Grails application is ready. You can start it with grails run-app and check that it works at http://localhost:8080/example/. Now let's add the Flex part :

Create a grails-app/views/flex/example.mxml file :

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" preinitialize="Spring.getInstance().initApplication()"> <mx:Script> import mx.collections.ArrayCollection; import org.granite.tide.spring.Spring; import org.granite.tide.spring.Context; import org.granite.tide.events.TideResultEvent; import example.Person;

private var tideContext:Context = Spring.getInstance().getSpringContext();

private function listPeople():void { tideContext.personService.list(listResult); }

private function listResult(event:TideResultEvent):void { people = event.result as ArrayCollection; }

private var dummyPerson:Person;

[Bindable] public var people:ArrayCollection = new ArrayCollection(); </mx:Script>

<mx:Button label="List people" click="listPeople()"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

Now you have to run the AS3 generator to create the AS3 domain classes, and compile the Flex application :

grails gas3

grails mxmlc

Start the application with

grails run-app
and go to http://localhost:8080/example/person to create some entries in the html UI. Then go to http://localhost:8080/example/example.swf and check that you can list the entries you just created.

Now we are going to see each part in more details.

Flex compilation

The GDS plugin requires that you have a Flex SDK installed somewhere. By default it will use the one defined by the environment variable FLEX_HOME, this setting can be overriden in grails-app/conf/BuildConfig.groovy with the flex.sdk property (flex.sdk = "/home/work/flex_sdk_4_0"). The plugin uses the Flex OEM compiler to automatically compile .mxml and .as files present in the grails-app/views/flex folder. The swf will be built in grails-app/views/swf and gdsflex contains a simple servlet that maps all incoming requests for *.swf to this folder. Some settings can be defined in a grails-app/conf/GraniteDSConfig.groovy file :

as3Config {
    // Enable automatic compilation (should be false when using Flex/Flash Builder)
    autoCompileFlex = true
    // Override source folder (relative to Grails application path)              
    srcDir = "./grails-app/flex"
    // List of extra files to compile as separate swfs.
    modules = [ "myModule.mxml", "myStylesheet.css" ]
    // Path of an external jar containing domain classes to scan (for Gas3 generation)
    domainJar = null
    // Qualified names of extra Java/Groovy classes to generate to Flex classes                        
    extraClasses = []
    // Qualified names of Java classes to exclude from gas3 generation                       
    excludeClasses = []                 
}

The Flex compilation automatically includes all swc libraries from folders flex_libs and web-app/WEB-INF/flex/libs.

Once compiled, requesting http://localhost:8080/myApp/myApp.swf will look for an existing swf in grails-app/views/swf/myApp.swf. ActionScript classes for the domain model are generated in grails-app/views/flex (or the configured source folder) from the Groovy domain classes.

Using Flex Builder

When using Flex Builder, you probably don't want to have the plugin do its own Flex compilation so you have to set autoCompileFlex = false (see previous paragraph). Then you should build the swf in grails-app/views/swf so it can be picked up by the plugin swf servlet.

You have to link your Flex application with the libraries granite.swc and granite-essentials.swc that you can copy to your project (they are available in the plugin sources, for example ~/.grails/1.3.5/projects/example/plugins/gdsflex-0.8.4/src/flex/libs). You also have to use the compiler option -services to reference the remoting configuration file in web-app/WEB-INF/flex/services-config.xml and the option -context-root /example to define the web app context path. Lastly you need to use the -keep-as3-metadata option to keep the necessary Flex annotations in the compiled swf.

In summary, your Flex compilation options should look like :

-locale en_US  -include-libraries flex_libs/granite-essentials.swc  
-services web-app/WEB-INF/flex/services-config.xml  -context-root /example  
-keep-as3-metadata=Bindable,Managed,ChangeEvent,NonCommittingChangeEvent,Transient,
Name,In,Out,Observer,ManagedEvent,Destroy,Id,Version

Exposing Grails services

Exposing a Grails service as a Flex destination just requires adding the org.granite.tide.annotations.TideEnabled annotation to the service:

class Person {

String uid

String firstName

String lastName }

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleService {

static transactional = true

def list() { return Person.list() } }

Note that the entity can be a GORM entity or a JPA entity. In both cases, it is highly recommended (but not mandatory) to have a persistent uid property that will be used as a common identifier between Flex, Hibernate and the database layers.

Using the service from Flex can be done using the Tide client API:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    preinitialize="Spring.getInstance().initApplication()">
    <mx:Script>
        import mx.collections.ArrayCollection;
        import org.granite.tide.spring.Spring;
        import org.granite.tide.spring.Context;
        import org.granite.tide.events.TideResultEvent;

private var tideContext:Context = Spring.getInstance().getSpringContext();

private function listPeople():void { tideContext.peopleService.list(listResult); }

private function listResult(event:TideResultEvent):void { people = event.result as ArrayCollection; }

private var dummyPerson:Person;

[Bindable] public var people:ArrayCollection = new ArrayCollection(); </mx:Script>

<mx:Button label="List people" click="listPeople()"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

There are a few important things here :

  • the preinitialize handler is used to register the application with the Tide framework
  • the Tide context allows to get client proxies to server components
  • tideContext.peopleService gets a client proxy to the PeopleService Grails service
  • the dummyPerson is necessary here to force the Flex compiler to include the Person class in the swf. If not present, you will get a serialization error. That variable will not be needed as soon as you use the Person class somewhere else.
  • the 'people' property needs to be public (or at least provide a setter), because Tide needs to merge the collection instance and ensure it remains the same instance throughout remote calls. If the property is private, there will be a degraded mode where the collection instance is not kept identical between calls.
The Tide framework can also inject references to client proxies in Flex components:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    preinitialize="Spring.getInstance().initApplication()">
    <mx:Script>
        import mx.collections.ArrayCollection;
        import org.granite.tide.Component;
        import org.granite.tide.spring.Spring;
        import org.granite.tide.events.TideResultEvent;

[In] public var peopleService:Component;

private function listPeople():void { peopleService.list(listResult); }

private function listResult(event:TideResultEvent):void { hello = event.result as ArrayCollection; }

private var dummyPerson:Person;

[Bindable] private var people:ArrayCollection = new ArrayCollection(); </mx:Script>

<mx:Button label="List people" click="listPeople()"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

Since 0.8.1, the plugin automatically generates typesafe ActionScript proxies for all services that you can use as follows :

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    preinitialize="Spring.getInstance().initApplication()">
    <mx:Script>
        import mx.collections.ArrayCollection;
        import org.granite.tide.Component;
        import org.granite.tide.spring.Spring;
        import org.granite.tide.events.TideResultEvent;

[Inject] public var myService:PeopleService;

private function listPeople():void { myService.list(listResult); }

private function listResult(event:TideResultEvent):void { hello = event.result as ArrayCollection; }

private var dummyPerson:Person;

[Bindable] private var people:ArrayCollection = new ArrayCollection(); </mx:Script>

<mx:Button label="List people" click="listPeople()"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

Note the use of Inject (+) instead of In (+) to indicate a typesafe injection. In (+) matches the service name, Inject (+) matches the service type, so the property can be named as you want and there are less risks to mispell the name as the Flex compiler will be able to detect the error. It's also useful when using Flex builder as you get support for autocompletion of service methods in the Flex application.

One important thing when using the Tide framework is that the entities keep managed on the Flex client and can be considered as real JPA/Hibernate detached entities. Entities received as arguments for a service can be directly saved or updated with the GORM extra persistence methods save, update, merge and delete. It is recommended though to use 'merge' instead of 'update' or 'save' when updating an existing object.

Exposing Grails controllers

Grails controllers can also be exposed with the TideEnabled annotation. The model variables received from controller method will be bound to the Tide context as context variables.

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleController {

List people

def list = { people = Person.list() } }

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    preinitialize="Spring.getInstance().initApplication()">
    <mx:Script>
        import mx.collections.ArrayCollection;
        import org.granite.tide.Component;
        import org.granite.tide.spring.Spring;

[In] public var peopleController:Component;

private var dummyPerson:Person;

[Bindable] [In] public var people:ArrayCollection; </mx:Script>

<mx:Button label="List people" click="peopleController.list()"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

You can also transmit request parameters from the Flex client, they will be available in the controller params map.

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleController {

List people

def list = { people = Person.findByName(params.searchString) } }

You can also return the results using the controller model map result instead of instance properties :

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleController {

def list = { [ people: Person.findByName(params.searchString) ] } }

Note that could can return as many variables as you want in the map, or as properties.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    preinitialize="Spring.getInstance().initApplication()">
    <mx:Script>
        import mx.collections.ArrayCollection;
        import org.granite.tide.Component;
        import org.granite.tide.spring.Spring;

[In] public var peopleController:Component;

private var dummyPerson:Person;

[Bindable] [In] public var people:ArrayCollection = new ArrayCollection(); </mx:Script>

<mx:TextInput id="tiText"/> <mx:Button label="List people" click="peopleController.list({searchString: tiText.text})"/> <mx:DataGrid dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="firstName"/> <mx:DataGridColumn dataField="lastName"/> </mx:columns> </mx:DataGrid>

</mx:Application>

Since Grails 1.3, you have to specify the full qualified name of the controllers in the In (+) annotations when your controllers are not in the default package :

[In("myapp.peopleController")]
        public var peopleController:Component;

Choosing between using services and controllers is a matter of preference and application design. Using services requires a little more work on the client side, using controllers simplifies the integration but implies a very strong coupling between the Flex view and the Grails controller.

Using transparent lazy loading

Tide maintains a client image of the server persistence context. It is able to trigger initialization of lazy loaded collections when they are bound to a UI component. While this feature should not be overused because of the extra network traffic it can cause, it is very handy when dealing with master-detail elements or tree structures.

class Person {

String uid

String name

Set<Contact> contacts static hasMany = [contacts:Contact] static mapping = { contacts cascade:"all,delete-orphan" } }

class Contact {

String uid

Person person

String email }

<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    xmlns:gv="org.granite.tide.validators.*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
    preinitialize="Spring.getInstance().initApplication()">

<mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import org.granite.tide.spring.Spring; import org.granite.tide.events.TideResultEvent; import org.granite.tide.events.TideFaultEvent; import Person; import Contact;

[Bindable] [In] public var peopleController:Object;

[Bindable] [In] public var people:ArrayCollection;

public var dummyPerson:Person; public var dummyContact:Contact;

private function getList():void { peopleController.list(); } ]]> </mx:Script>

<mx:VBox width="100%"> <mx:HBox> <mx:Button id="bList" label="Get list" click="getList()"/> </mx:HBox>

<mx:HBox width="100%"> <mx:DataGrid id="dgPeople" dataProvider="{people}" change="dgContacts.dataProvider = dgPeople.selectedItem.contacts"> <mx:columns> <mx:DataGridColumn dataField="name"/> </mx:columns> </mx:DataGrid>

<mx:DataGrid id="dgContacts"> <mx:columns> <mx:DataGridColumn dataField="email"/> </mx:columns> </mx:DataGrid> </mx:HBox> </mx:VBox>

</mx:Application>

Using paged data

Tide provides the PagedQuery client component that handles client data paging and interaction with a Grails service/controller that executes queries.

The service must implement a find method with the following signature:

Map<String, Object> find(
    Map<String, Object> filter, 	
        // map containing criteria
	int first, 				
        // Index of first requested element
	int max, 				
        // Max number of elements
	String order, 			
        // order field
	boolean desc			
        // true when descendent sorting
)

The returned map should contain at least 2 elements:

  • resultCount
  • resultList
And optionally when paged size needs to be defined server-side
  • firstResult (identical to first)
  • maxResults
import org.granite.tide.annotations.TideEnabled

@TideEnabled class PeopleService {

def find(filter, first, max, order, desc) { if (max == 0) max = 36

List resultList = Person.list(max: max, offset: first, sort: order, order: (desc ? "desc" : "asc") ) int resultCount = Person.count()

return [ resultList: resultList, resultCount: resultCount ] } }

<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    xmlns:gv="org.granite.tide.validators.*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
	preinitialize="Spring.getInstance().initApplication()">

<mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import org.granite.tide.spring.Spring; import org.granite.tide.spring.PagedQuery; import org.granite.tide.events.TideResultEvent; import org.granite.tide.events.TideFaultEvent; import Person; import Contact;

Spring.getInstance().addComponentWithFactory( "peopleService", PagedQuery, { maxResults: 36 });

[Bindable] [In] public var peopleService:PagedQuery;

private var dummyPerson:Person;

private function getList():void { people.fullRefresh(); } ]]> </mx:Script>

<mx:VBox width="100%"> <mx:HBox> <mx:Button id="bList" label="Get list" click="getList()" /> </mx:HBox>

<mx:HBox width="100%"> <mx:DataGrid id="dgPeople" dataProvider="{peopleService}"> <mx:columns> <mx:DataGridColumn dataField="name"/> </mx:columns> </mx:DataGrid> </mx:HBox> </mx:VBox>

</mx:Application>

  • The client PagedQuery component can be used as a data provider for Flex UI components.
Since 0.4, controllers can also be used as server providers for the PagedQuery component, even with scaffolding. In this case, the closure 'list' will be called :

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PersonController {

def list = { if (params.max == 0) params.max = 36

List resultList = Person.list(params) int resultCount = Person.count()

return [ resultList: resultList, resultCount: resultCount ] } }

Or

import org.granite.tide.annotations.TideEnabled

@TideEnabled class PersonController {

def scaffold = Person }

<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="*"
    xmlns:gv="org.granite.tide.validators.*"
    layout="vertical"
    backgroundGradientColors="[#0e2e7d, #6479ab]"
	preinitialize="Spring.getInstance().initApplication()">

<mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import org.granite.tide.spring.Spring; import org.granite.tide.spring.PagedQuery; import org.granite.tide.events.TideResultEvent; import org.granite.tide.events.TideFaultEvent; import Person; import Contact;

Spring.getInstance().addComponentWithFactory( "people", PagedQuery, { useGrailsController: true, remoteComponentName: "personController", maxResults: 36 } );

[Bindable] [In] public var people:PagedQuery;

private var dummyPerson:Person;

private function getList():void { people.fullRefresh(); } ]]> </mx:Script>

<mx:VBox width="100%"> <mx:HBox> <mx:Button id="bList" label="Get list" click="getList()" /> </mx:HBox>

<mx:HBox width="100%"> <mx:DataGrid id="dgPeople" dataProvider="{people}"> <mx:columns> <mx:DataGridColumn dataField="name"/> </mx:columns> </mx:DataGrid> </mx:HBox> </mx:VBox>

</mx:Application>

Scaffolding templates for gdsflex enabled controllers

The plugin comes with templates for Grails scaffolding that include the necessary actions for use from Flex : find, persist, merge, remove, upload and download. The templates can be installed with

grails install-flex-templates

Integration of html-wrapper Flex task

The plugin includes the html-wrapper task to generate a html file embedding the swf file. This is necessary for example to use the Flex browser integration and deep linking functionality.

grails html-wrapper

The html file is generated both in web-app/{appname}.html with all necessary files for the Flex wrapper (js, css...), and in grails-app/views/flexindex.gsp. The generated gsp can for example be defined as the target for Grails url mappings.

Support for deep linking

Tide now includes a simple plugin to easily handle deep linking. Tide components annotated with [Path] are marked as handlers for browser url changes and can take appropriate actions.

The plugin can be setup in the main application by :

import org.granite.tide.deeplinking.TideUrlMapping;
…
Spring.getInstance().addPlugin(TideUrlMapping.getInstance());

[Path("book")]
public class BookUI {

[Path("list")] public function listHandler():void { // Do something interesting, for example show the relevant list view } }

The listHandler method will be called when the browser url will be changed to '/book/list'.

Path mapping also supports variable mapping (similar to JAX-RS) :

[Path("book")]
public class BookUI {

[Path("show/{id}")] public function showHandler(id:Number):void { // Do something interesting, for example show the requested book } }

The variables are mapped as arguments to the handler method in the same order than the path mapping.

Flex application generator

Gdsflex includes a simple Flex application generator driven by the domain classes. The UI is dynamically built using the included Tide UI builder library.

The process to generate a Flex app is the following :

// Create and edit the domain classes
grails create-domain-class com.myapp.Domain1
grails create-domain-class com.myapp.Domain2
...

// Create and edit the corresponding controllers with scaffolding // (don't forget to add the @TideEnabled annotation) grails create-controller com.myapp.Domain1 grails create-controller com.myapp.Domain2 ...

// Generate the as3 domain classes grails gas3

// Generate the main Flex application grails generate-flex-app

// Compile the Flex application and optionally generate an html wrapper grails mxmlc grails html-wrapper

// Run the application grails run-app

The application can be accessed at http://localhost:8080/{myapp}/flexindex.gsp. The UI builder supports most Grails constraints on properties and the generated application is comparable in functionality to the html Grails scaffolding.

Currently supported relationships are :

  • oneToOne/manyToOne: Displayed as a ComboBox, requires a controller for the associated entity (to get the list of possible values).
  • bidirectional oneToMany: Displayed as an editable DataGrid, requires that the associated entity and owner implement particular constructors. For example Book hasMany => Chapter requires that Book has a constructor Book() that initializes the chapters collections with chapters = new ArrayCollection() and Chapter has a constructor Chapter(book:Book = null).
  • unidirectional oneToMany / manyToMany: Displayed as a DataGrid with a list of possible values that can be dragged in the grid, requires a controller for the associated entity
Lazy associations are supported for oneToMany and manyToMany relationships, but not for manyToOne or oneToOne that must be marked lazy:false.

The generated as3 constraints map contains everything that is present in the Grails ConstrainedProperty, but not all are currently used by the UI generator.

  • display:false => field not displayed
  • editable:false => field not editable (for now works only with oneToMany and manyToMany)
  • inCreate:false => field not present in create view
  • inEdit:false => field not present in edit view
  • inList:"[val1,val2,val3]" => use ComboBox with specified values for string fields
  • widget:"textArea" => use TextArea instead of TextInput for strings
  • widget:"image" => use Image instead of download button for ByteArray / Blob
  • format:"DD/MM/YYYY" => formatString in dateFormatter for Date fields
  • format: "2" => precision in numberFormatter for Number fields
The generated application includes support for deep linking and file upload/download with Flex FileReference and can be seen as an example of a generic CRUD application with Flex and GraniteDS.

Most parts of the generated application can be overriden by user-defined elements :

To override the global UI builder :

[Name("tideUIBuilder")]
    public class MyGlobalUIBuilder extends DefaultUIBuilder {

protected override function buildEditFormItem(property:Object, create:Boolean):EntityProperty { if (property.name == "someProperty") { var entityProperty:EntityProperty = new EntityProperty(); entityProperty.property = property.name; entityProperty.bound = false; … return entityProperty; } else return super.buildEditFormItem(property, create); } }

Or only for a particular entity (here for com.myapp.Author) :

[Name("com.myapp.author.tideUIBuilder")]
    public class AuthorUIBuilder extends DefaultUIBuilder {

protected override function buildEditFormItem(property:Object, create:Boolean):EntityProperty { if (property.name == "someProperty") { var entityProperty:EntityProperty = new EntityProperty(); entityProperty.property = property.name; entityProperty.bound = false; … return entityProperty; } else return super.buildEditFormItem(property, create); } }

More information and documentation on the UI builder will be available later.

Integrating with Spring security

The integration can be done with either the acegi plugin or the spring-security-core plugin. These plugins are automatically detected and GraniteDS is automatically configured to propagate the security credentials from Flex RemoteObject to Spring security.

More details and documentation on the use of the GraniteDS Tide framework for Spring can be found on the GDS site at http://www.graniteds.org/confluence/display/DOC20/6.+Tide+Data+Framework.

Data Push with Gravity

To get this working at development time, in Config.groovy set:

grails.tomcat.nio = true

Also: For Grails 1.3:

uninstall-plugin tomcat
install-plugin tomcatnio
For Grails 1.4: The NIO option should be committed to core, no need to install the plugin.

You also have to set the option gravityEnabled = true in the graniteConfig and otherwise follow the instructions in the GDS doco for Data Push, such as configuring the channel in services-config.xml. Also check out this basic Grails + GDS + Gravity tutorial.

Enabling NIO or APR at deployment time is dependant upon the application server you deploy to.

Deploying on Google App Engine

The gdsflex plugin is able to detect that the app-engine plugin (0.8.2 minimum) has been enabled for a project and defines an appropriate GraniteDS setup (support of DataNucleus JDO/JPA entities instead of Hibernate). To correctly run the application, it is though necessary to update manually the web-app/WEB-INF/flex/services-config.xml and remove the {context.root} from the channel endpoint : uri="http://{server.name}:{server.port}/graniteamf/amf" because GAE deploys in the root context.

Using Grails and Google App Engine is generally very far from a pleasant experience (often due to GAE), but the following recommendations can limit problems :

  • Always define the domain classes in packages (the default package will not work)
  • Use the Key class for primary keys
  • gdsflex requires that the primary key is named 'id' (like GORM domain classes)
  • Use preferably JDO than JPA (except if you like extreme S/M experiments).
Here is an example of a JDO entity suitable for gdsflex :

package org.gdsgaegrails.entity

import javax.jdo.annotations.* import com.google.appengine.api.datastore.Key

@PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true") class Task {

static constraints = { }

@PrimaryKey @Persistent(primaryKey="true", valueStrategy=IdGeneratorStrategy.IDENTITY) Key id

@Persistent String uid

@Persistent String description }

Options

If you create a file called GraniteDSConfig.groovy in your grails-app/conf folder you may put the following configuration in (defaults shown below):

graniteConfig {
    springSecurityAuthorizationEnabled = false
    springSecurityIdentityClass = Identity.class

gravityEnabled = false gravityServletClassName = "org.granite.gravity.tomcat.GravityTomcatServlet"

dataDispatchEnabled = false // see part 3 of // http://graniteds.blogspot.com/2010/07/flex-grails-crud-application-with.html for example }

as3Config { // a list of additional classes to generate with gas3 (e.g. non domain classes in the java or groovy folders). extraClasses = [] // the path of a jar containing additional classes to generate with gas3 domainJar = null // whether the plugin should compile your Flex project for you when the application runs. autoCompileFlex = true // where to output the generated as3 files. srcDir = "grails-app/views/flex" }