Wednesday, August 1, 2007

Another Sexy Flex Grails Example



Here is another (quick) Flex example which uses Grails on the back-end. It contains all CRUD operations in just one screen. Just follow the steps below to see it for yourself:


  1. Create a new grails application: grails create-app flexongrails

  2. Create a new Book domain class: grails create-domain-class Book
    Open the Book domain class and replace the code with:
    class Book { 
    String isbn
    String title
    String author
    Float price
    String format

    static constraints = {
    isbn(maxLength:20, unique:true)
    title(maxLength:50)
    author(maxLength:50)
    price(min:0F, max:999F, scale:2)
    format(inList:["Hardcover", "Paperback", "e-Book"])
    }
    }

  3. Create a new Book controller: grails create-controller Book
    Open the Book controller class and replace the code with:
    class BookController {

    def index = { redirect(action:list, params:params) }

    // the delete, save and update actions only accept POST requests
    // def allowedMethods = [delete:'POST', save:'POST', update:'POST']

    def list = {
    response.setHeader("Cache-Control", "no-store")
    def bookList = Book.list(params)
    render(contentType:"text/xml") {
    data {
    for(i in bookList) {
    book {
    id(i.id)
    isbn(i.isbn)
    title(i.title)
    author(i.author)
    price(i.price)
    format(i.format)
    }
    }
    }
    }
    }

    def save = {
    def book
    if(params.id) {
    book = Book.get(params.id)
    }
    else {
    book = new Book()
    }
    book.properties = params
    book.save()
    render ""
    }

    def delete = {
    def book = Book.get(params.id)
    if(book) {
    book.delete()
    }
    render ""
    }

    }


    What you can see here is that the list action return xml data which will be used by Flex. Important is setting the cache control in the response header. The save action will be used both creating as editing.

  4. Create a new Flex project (in Flex builder) and replace the code in the main mxml file with:
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="listService.send()">

    <mx:HTTPService id="listService" url="http://localhost:8080/flexongrails/book/list" useProxy="false" method="GET"/>
    <mx:HTTPService id="saveService" url="http://localhost:8080/flexongrails/book/save" useProxy="false" method="POST" result="listService.send()">
    <mx:request xmlns="">
    <id>{book_id.text}</id>
    <isbn>{isbn.text}</isbn>
    <title>{title.text}</title>
    <author>{author.text}</author>
    <price>{price.text}</price>
    <format>{format.text}</format>
    </mx:request>
    </mx:HTTPService>
    <mx:HTTPService id="deleteService" url="http://localhost:8080/flexongrails/book/delete" useProxy="false" method="POST" result="listService.send()">
    <mx:request xmlns="">
    <id>{dg.selectedItem.id}</id>
    </mx:request>
    </mx:HTTPService>

    <mx:NumberFormatter id="priceFormatter" precision="2"/>

    <mx:Script>
    <![CDATA[
    [Bindable]
    private var formatArray:Array = ["Hardcover", "Paperback", "e-Book"];

    private function clearForm():void {
    book_id.text = "";
    isbn.text = "";
    title.text = "";
    author.text = "";
    price.text = "";
    format.selectedIndex = 0;
    }

    private function formatPrice(item:Object, column:DataGridColumn):String {
    return priceFormatter.format(item.price);
    }
    ]]>
    </mx:Script>

    <mx:VDividedBox x="0" y="0" height="100%" width="100%" paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10">
    <mx:Panel width="100%" height="300" layout="absolute" title="Create/Update Book">
    <mx:Form x="10" y="10" width="930" height="200">
    <mx:FormItem label="ID">
    <mx:TextInput width="120" id="book_id" text="{dg.selectedItem.id}" enabled="false"/>
    </mx:FormItem>
    <mx:FormItem label="ISBN">
    <mx:TextInput width="220" id="isbn" text="{dg.selectedItem.isbn}" maxChars="20"/>
    </mx:FormItem>
    <mx:FormItem label="Title">
    <mx:TextInput width="320" id="title" text="{dg.selectedItem.title}" maxChars="50"/>
    </mx:FormItem>
    <mx:FormItem label="Author">
    <mx:TextInput width="320" id="author" text="{dg.selectedItem.author}" maxChars="50"/>
    </mx:FormItem>
    <mx:FormItem label="Price">
    <mx:TextInput width="120" id="price" text="{priceFormatter.format(dg.selectedItem.price)}"/>
    </mx:FormItem>
    <mx:FormItem label="Format" width="220">
    <mx:ComboBox id="format" selectedIndex="{formatArray.indexOf(dg.selectedItem.format)}">
    <mx:dataProvider>{formatArray}</mx:dataProvider>
    </mx:ComboBox>
    </mx:FormItem>
    </mx:Form>
    <mx:ControlBar horizontalAlign="right">
    <mx:Button label="New" click="clearForm()"/>
    <mx:Button label="Save" click="saveService.send(); clearForm()"/>
    </mx:ControlBar>
    </mx:Panel>
    <mx:Panel width="100%" height="444" layout="absolute" title="Book List">
    <mx:DataGrid x="0" y="0" width="100%" height="100%" id="dg" dataProvider="{listService.lastResult.data.book}">
    <mx:columns>
    <mx:DataGridColumn width="120" headerText="ID" dataField="id"/>
    <mx:DataGridColumn width="220" headerText="ISBN" dataField="isbn"/>
    <mx:DataGridColumn width="320" headerText="Title" dataField="title"/>
    <mx:DataGridColumn width="320" headerText="Author" dataField="author"/>
    <mx:DataGridColumn width="120" headerText="Price" dataField="price" labelFunction="formatPrice"/>
    <mx:DataGridColumn width="220" headerText="Format" dataField="format"/>
    </mx:columns>
    </mx:DataGrid>
    <mx:ControlBar horizontalAlign="right">
    <mx:Button label="Delete" click="deleteService.send()" enabled="{dg.selectedItem != null}"/>
    </mx:ControlBar>
    </mx:Panel>
    </mx:VDividedBox>

    </mx:Application>

  5. Run the Flex application


You now have a basic CRUD Flex application which uses Grails as back-end. The example is very basic and the Grails Book controller isn't responding back any possible validation errors; they just get absorbed. Also for a couple of row retrieving all the rows at once is no problem, but in real life it will me thousands and thousands of records, so also server side paging and ordering is needed. Well, this could be easily implemented in the Book controller (as it is basic Grails functionality) but most of the work will go in the Flex application I think...

For server-side paging/sorting a custom Flex component would be needed which remembers the current page and just submits the required paging and sorting fields to the serve-side. This is independent to Grails as it could be used by any server-side application. I guess I'm not the first one looking into this so I might find something on the internet.