Grails itself provides a feature called scaffolding that allows to build extremely easily a simple working application skeleton from constraints defined on the domain classes.
Doing the same with Flex is a bit more complex because there are two runtimes involved: the Flash client and the Java server. There are thus three main possible options:
After this short introduction, let's see how this works with the latest release of gdsflex (0.7.1).
First we create a new Grails application, once again a book manager (maybe next time I'll find some more original example). Assuming you have grails 1.1.1 installed, type from the command line :
grails create-app gdsexample2
cd gdsexample2
grails install-plugin gdsflex
As before, the main part is to define the domain model. Let's start this time by a simple Author class.
grails create-domain-class com.myapp.Author
Author.groovy
package com.myapp
class Author {
static constraints = {
name(size:0..50, blank:false)
birthDate(format:'DD/MM/YYYY')
picture(nullable:true, widget:'image')
}
String uid
String name
Date birthDate
java.sql.Blob picture
}
This is a relatively simple domain class, you can notice the constraints that drive Grails scaffolding. So let's add a simple controller :
grails create-controller com.myapp.Author
AuthorController.groovy
package com.myapp
@org.granite.tide.annotations.TideEnabled
class AuthorController {
def index = { redirect(action:list, params:params) }
def scaffold = Author
}
Then run :
grails generate-flex-app
grails mxmlc
grails run-app
And go to http://localhost:8080/gdsexample2/gdsexample2.swf.
You should have a basic working Flex application that allows to create and edit authors. So what happened here :
This is a simple example, but we can see how the constraints have been used : the Grails constraints in Author.groovy have been translated to the following block in Author.as :
public static const meta_constraints:Array = [
{ property: "name",
blank: "false", size: "0..50"
},
{ property: "birthDate",
format: "DD/MM/YYYY"
},
{ property: "picture",
widget: "image"
}
]
Even if you don't want to use the GraniteDS UI builder framework, you could still use these as3 constraints from any custom framework.
You can check that the blank and size constraints have been mapped to Flex client-side validators, and that the date format has been used in the author table. The 'image' widget is simply a component that appears in edit mode and allows to upload an image from Flex.
Now that we have seen a basic domain class, let's see how it works with associations.
grails create-domain-class com.myapp.Book
grails create-domain-class com.myapp.Chapter
grails create-controller com.myapp.Book
Book.groovy
package com.myapp
class Book {
static constraints = {
title(size:0..100, blank:false)
category(inList:["Fiction", "Non-fiction", "Biography"])
author()
description(size:0..2000, widget:"textArea")
chapters()
}
String uid
String title
String category
Author author
Setchapters
String description
static hasMany = [ chapters:Chapter ]
static mapping = {
author fetch:"join"
chapters cascade:"all-delete-orphan"
}
}
Chapter.groovy
package com.myapp
class Chapter {
static constraints = {
title(size:0..50, blank:false)
}
String uid
Book book
String title
String summary
static mapping = {
book fetch:"join"
}
}
BookController.groovy
package com.myapp
@org.granite.tide.annotations.TideEnabled
class BookController {
def index = { redirect(action:list, params:params) }
def scaffold = Book
}
An important thing is that ManyToOne associations must be marked fetch:"join" or lazy:false. GraniteDS does not support transparent lazy loading for single ended associations, and most likely never will (it would generate unacceptable network and database traffic, one http request and one SQL query for each uninitialized entity). Lazy collections are supported, so OneToMany and ManyToMany associations do not have to be marked lazy:false.
Let's regenerate the Flex application to add the Book menu to the main mxml.
grails generate-flex-app
grails mxmlc
You can notice that the main mxml has been overwritten. This is quite annoying if you made some changes to the generated file but the previous file is backuped and in general you would have added the entity manually in the mxml. If you have a look to the mxml, only two lines have been added for the new entity :
<mx:LinkButton label="Books" width="100%" textAlign="left"
click="mainStack.selectedChild = bookUI" />
<ui:EntityUI id="bookUI"
entityClass="{Book}"
width="100%" height="100%"/>
The EntityUI is a Flex component and can just be used anywhere in a Flex application (provided the Tide framework initialization has been done correctly).
We can now create and edit books, and see how many-to-one and one-to-many associations are displayed in the generated application.
As you eventually tried to add chapters, you will see that it does not work. There are indeed two minor changes to do manually in the AS3 classes to help the UI builder make the link between the associated oneToMany entities: initialize the Chapters collection in the Book.as class, and add a constructor in Chapter.as :
public function Book():void {
chapters = new ArrayCollection();
}
public function Chapter(book:Book = null):void {
this.book = book;
}
Once these changes are made, just refresh the browser and check that you can add chapters to your books.
Now we're almost finished, we'll just add a final improvement to this simple app:
grails html-wrapper
As the command name indicates, this will generate a html wrapper and allow the application to use Flex deep linking, and to use Grails url mapping on the flex application now located at flexindex.gsp.
Now you can go to http://localhost:8080/gdsexample2/flexindex.gsp#book/list to access the book list directly.
The documentation on the plugin page gives more information on what constraints are supported by the builder and how to override the behaviour of the generated application. Anyway the sources of the UI builder are also present in the grails-app/views/flex folder and can be used as a template to build your customized UI.
It should be easy to add security and data push to the generated application by following the previous blog entry here.
Hopefully this new feature of the gdsflex Grails plugin is a useful tool to get started quickly with Flex and GraniteDS and to quickly build prototype applications.
As always, feedback and comments are welcome.