Home  >  

Getting Real with LCDS 3, Part 2

Author photo
AddThis Social Bookmark Button

In Part 1, we experienced the joy of model driven development and built a complete LCDS 3 backend using the new Modeler plugin. Amazingly, no Java was needed to create a full production-ready backend. In Part 2, we will build a complete Flex 4 frontend while exercising some of the cooler client-side LCDS 3 features along the way. And once again, I won't skimp on the details.

Recall that our "real" application is a blog application consisting of authors, posts, and tags. Authors write posts, posts have tags, and like all blogging applications users come to read posts. The frontend will be limited to adequate, so all the fancy skins and effects will be saved for a future article. Lastly, many of the frontend techniques we discussed belowe are generally applicable to any asynchronous backend, not just LCDS.

Avoiding Async Hell

The asynchronous nature of the backend can make writing the frontend a significant challenge. The implementation details can really bog down even experienced developers, but more importantly, the user experience can suffer if the correct steps are not taken. Thankfully, there are a couple of tools and techniques that can help.

  1. CallResponder - The new CallResponder class is a must when handling the results from asynchronous calls. You simply set its token property to the AsyncToken returned by the backend service, and then bind the CallResponder's lastResult property to some UI component's dataProvider. Now, when a call to the backend eventually returns some data, the binding will fire and the data will be displayed.
  2. Binding - As mentioned in the CallResponder use case above, the pervasive use of binding is one of the best ways to avoid async hell. Nowadays, with the explosion of Flex frameworks, it's very easy to make the wrong decision and get locked into a heavily event-driven framework that makes something simple like binding hard to do. Just say no. Save yourself a lot of pain and just bind to CallResponder's lastResult property. Additionally, other enterprise performance techniques such as paging, lazy loading associations, or using partially hydrated objects are best handed with binding.
  3. LCDS Data Management - The magic of Data Management in LCDS can help you avoid a lot of trouble re-querying the backend when something changes. For example, imagine you have the front page of your blog which lists the five most recent posts, and then you publish a new post. With Data Management on your side, there is literally nothing to do. When the new post is sent to the server, Data Management figures out all of the fill functions that need to be changed and pushes any updates down to the client (actually all clients in a multiuser application). The client receives the pushed changes, updates the lastResult property of the relevant CallResponders, fires the bindings, and updates your entire app automatically including the front page and its list of the five most recent posts. For the rest of this article, I will refer to this as the "Data Management Magic" because that's what it feels like to the developer. Without LCDS, you need to handle the creation success event yourself, figure out all the places in your entire app that need updating, re-issue all of those queries, handle all the updated results, et cetera.

Using these techniques from the start will keep the code manageable and keep your users happy.

Paper Prototype

While it would be nice to map out a complete set of use cases for our sample app, and maybe even some UX wireframes, that's just not going to happen. Instead, we'll use a paper prototype (which I drew all by myself, with a pencil, the horror!) to guide our frontend construction. Our blog application will have three major sections: a list of posts, a list of authors, and a full admin.

As an example of our paper prototype (and my poor drawing skills), here is the first "page" of our blog showing, as you would expect in any blog, a list of posts:

Digging a little deeper into the details of this particular page, we see our three sections across the top, with our current Posts "page" highlighted. Next, a small area to filter our posts by author name or tag name. Lastly, a large scrolling area showing all posts with the most recent at the top. Each posts is further broken down into title, content, author, tags, etc. Anyone that is reading this article has read a blog before so hopefully our application should feel very familiar. I won't review the other sections of the paper prototype, but you can download the pdf if you can't get enough of my pencil scribblings.

The goal of this frontend is two fold: explore some of the cool client-side features of LCDS (and asynchronous backends in general) and build something different from the typical Master-Detail demo using forms and DataGrids.

Application Architecture

Simple and straightforward is probably the best description for the architecture of the sample application. No frameworks other than Flex 4. The application and its code will demonstrate one way to write an LCDS frontend, but I will avoid any assertions that this is The Way. I'll create custom components as necessary to keep things modular, but I won't even attempt to follow any kind of MVC pattern so most MXML files will have some code in the script block.

At last, we've arrived at my favorite part: the code. Here is the main application (minus the script block and some design attributes):

<?xml version="1.0" encoding="utf-8"?>
<s:Application ...>
    ...

    <s:states>
        <s:State name="posts"/>
        <s:State name="authors"/>
        <s:State name="admin"/>
    </s:states>

    <s:ButtonBar id="nav" change="navHandler()">
        <s:ArrayList source="['Posts','Authors','Admin']" />
    </s:ButtonBar>

    <comps:Posts   id="posts"   includeIn="posts" />
    <comps:Authors id="authors" includeIn="authors" />
    <comps:Admin   id="admin"   includeIn="admin" />
</s:Application>

We have: some states, a ButtonBar, and some custom components (one for each state). As a developer, one of my favorite enhancements in Flex 4 is the improved state syntax. Here, I'm using states as "pages" with one custom "page" component per state and a simple UI element (in this case a ButtonBar) to provide navigation. Page states, as I like to call them, are a nice architectural element because they provide fairly clean modularity for the application. States also act as a great place holder for animation that can easily be added later in the development cycle via traditional state transitions.

Displaying a List of Posts

Our blog application has two read-only sections. One displays a lists of posts, and the other displays a list of authors. At the lowest level, each section is backed by a simple query to the backend. We then proceed with the familiar gymnastics of CallResponder plus binding to wire the data to the UI. I'm going to rely heavily on the Flex 4 List component to get the data to the user. Grids are cool for tabular data, but I find that List is the enterprise developer's best friend. It is skinnable, supports both virtualization and selection, and offers the maximum flexibility. The combination of flexibility and performance is hard to ignore, especially in an enterprise setting where I need to get data to my users in an actionable fashion that is also performant.

Here is a simplified version of the Posts custom component showing just the essentials:

<?xml version="1.0" encoding="utf-8"?>
<s:Group ...
        xmlns:dms="dms.*"
        creationComplete="complete()">

    <fx:Script>
        <![CDATA[
            public function complete():void {
                 getPosts.token = postService.getAllSorted();
             }
        ]]>
    </fx:Script>

    <fx:Declarations>
        <s:CallResponder id="getPosts"/>
        <dms:PostService id="postService" />
    </fx:Declarations>

    <s:List
            dataProvider="{getPosts.lastResult}"
            labelField="title"
            itemRenderer="components.PostRenderer"/>
</s:Group>

Now it all starts to come together. Our component does the work of querying the backend when the component is first created. We instantiate the PostService in MXML, and then call getAllSorted() in the creationComplete event handler. Recall that getAllSorted() is a custom criteria-based filter to return a list of all posts sorted by publication date (what I called a sort filter in Part 1). Our CallResponder is also instantiated via MXML, and its token property is set to the AsyncToken returned by the query. We then bind the CallResponder's lastResult property to our list. Lastly, the rendering of individual posts in the list is described by PostRenderer, a custom ItemRenderer implementation, which we'll get to in a moment.

When we add functionality to search for posts by author name or tag name, the UI gets a little more complicated but the backend query logic is almost trivial due to the custom filter support we added to our model in Part 1. Depending on the value of the filter DropDownList, we simply query a different fill function on PostService:

public function complete():void {
     if (filterType.selectedIndex == 1) {
          getPosts.token = postService.getByAuthorNameFuzzy('%' + filterInput.text + '%');
      } else if (filterType.selectedIndex == 2) {
          getPosts.token = postService.getByTagNameExact(filterInput.text);
      } else {
          getPosts.token = postService.getAllSorted();
      }
}

The really cool part is that Data Management services of LCDS make the current filter live at all times. When something changes, LCDS will push any updates to the client automatically, lastResult will change, binding will fire, and the display will be updated.

Displaying a Post

We use a custom ItemRenderer to format each post for display inside the list. The parent List control does the work of handling mouse events and converting them to state changes in the underlying renderer. All we have to do is declare the required states in our custom ItemRenderer, and then we get to take total control over the display of the post's data.

Here is the custom PostRenderer code abbreviated to the essentials:

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer ...>

    <s:states>
        <s:State name="normal"/>
        <s:State name="hovered"/>
        <s:State name="selected"/>
    </s:states>

    <fx:Declarations>
        <mx:DateFormatter id="df" formatString="MMMM D, YYYY" />
    </fx:Declarations>

    <s:Rect left="0" right="0" top="0" bottom="0">
        <s:fill>
            <s:SolidColor color="{contentBackgroundColor}"
                    color.hovered="{rollOverColor}"
                    color.selected="{selectionColor}" />
        </s:fill>
    </s:Rect>

    <s:VGroup>
        <s:Label id="labelDisplay" maxDisplayedLines="1" maxDisplayedLines.selected="3" />
        <s:Label maxDisplayedLines="1"
                text="{[
                    'by ' + data.author.name,
                    df.format(data.pubDate),
                    data.daysOld + (data.daysOld == 1 ? ' day' : ' days') + ' ago',
                    data.wordCount + ' words'
                ].join(' | ')}" />
        <s:Label text="{data.content}" maxDisplayedLines="3" maxDisplayedLines.selected="40" />
    </s:VGroup>
</s:ItemRenderer>

Reading the code, we first declare our states, initialize a date formatter, draw a background rectangle that changes for hover and selection, and lastly, display the actual data. When a post is selected, we use the new state syntax to expand the title and content Labels by increasing the number of lines displayed via the maxDisplayedLines property (remember Label can be multiline in Flex 4). There are also some non-obvious LCDS bits that deserve attention. First, recall that both daysOld and wordCount are derived properties of a Post. daysOld is computed by the server and sent over the wire, but wordCount is computed locally in ActionScript via a custom getWordCount() method we added to the Post extension class in Part 1. Second, recall that both sides of the author-to-posts relationship is eagerly loaded, so data.author is a fully hydrated Author entity and we can say data.author.name without the need to go back to the server. Third, recall that posts-to-tags is lazy loaded, exactly the opposite of the author association. Therefore, we do need to go back to the server to retrieve all the tags on a post.

Item Pending Errors

In order to display all the tags on a post, we must go back to the server to hydrate the tags collection. The first time we say post.tags, LCDS immediately queries the server, but unfortunately the data doesn't arrive immediately because the backend is asynchronous. Instead, we get an ItemPendingError that tells us, "Hey, waiting for data." The standard way of handling this error is to catch it and attach an IResponder that gets called when the data does arrive.

Here is the code that we must add to the script block of PostRenderer:

override public function set data(value:Object):void {
     super.data = value;
 
     if (value != null && value is Post) {
          var post:Post = value as Post;
  
          if (post.tags != null && post.tags.length > 0) {
               try {
                    //prefetch all the tags
                    post.tags.toArray();
                    pendingLoadTags(null, post.tags);
                } catch (ipe:ItemPendingError) {
                    ipe.addResponder(new ItemResponder(pendingLoadTags, pendingFault, post.tags));
                }
           }
      }
}

private function pendingLoadTags(result:Object, token:Object = null):void {
     //by now all tags have been fetched, so wire up to UI for display
     tagList.dataProvider = token as IList;
}

private function pendingFault(error:Object, token:Object = null):void {
     trace('fault');
}

The parent List component passes in each post to the ItemRenderer's data property, so we need to override data's setter method. This method is called for each post in the list of posts, but it's also called by the virtualization recycling process. In the setter, the key is to call a function that forces the entire lazy collection to load all in one shot. In the above code, I'm using post.tags.toArray() to force all the tags for the given post to load. Subsequent calls for the same data are typically satisfied by the client-side cache, avoiding a trip to the server. Unfortunately, subsequent calls no longer throw an ItemPendingError, which necessitates a very icky pattern in the code. Note that we call IResponder's success handler twice, once when we actually see the error and once in the last line of the try block but with a null result parameter. In the handler, pendingLoadTags(), we ignore the result parameter, and instead use the pass-through token parameter to set the dataProvider of a UI component. Logically, the code execution forks at the try-catch block, but recombines at the pendingLoadTags() method. Yuck!

Now we need to get the tags on the screen, so we add this block of code to the bottom of the PostRenderer's VGroup:

<s:HGroup>
    <s:Label text="Tags:" />
    <s:DataGroup id="tagList">
        <s:layout>
            <s:HorizontalLayout gap="5"/>
        </s:layout>
        <s:itemRenderer>
            <fx:Component>
                <s:DataRenderer>
                    <s:Label id="tagName" text="{data.name}" />
                </s:DataRenderer>
            </fx:Component>
        </s:itemRenderer>
    </s:DataGroup>
</s:HGroup>

Here we use the much lighter weight DataGroup and DataRenderer to do the work of printing a list of tag names. This is another good performance tip that's often forgotten: use the minimum weight component possible. And it is also another great enhancement coming with Flex 4: the new Group and DataGroup are very light weight.

The takeaway here should be that eager loading is awesome from the ease of development perspective, but you must understand the performance penalty. If you can afford it, I would recommend always starting with eager loading all my associations and dialing back to lazy loading as performance demands.

Blog in Action: Posts

Now let's look at some screenshots of our application in action. To get some sample data for our blog application, I scrapped the InsideRIA.com RSS feed just before AdobeMAX. Here is the Posts page showing a list of posts:

And here we've selected the first post in the list (which caused the content area to expand):

Sorting Locally

Moving on to the list of authors, we want to have an alphabetized list of authors that expands on selection to show all their posts. You can review the paper prototype (pdf), or just take my word for it. To get a sorted list of authors, we could quickly switch over the the Modeler, add a new sort filter to the Author entity, and redeploy. But sometimes you might want to handle sorting or filtering locally in Flex.

Here is a version of the Authors custom component showing the local sort by author name:

<?xml version="1.0" encoding="utf-8"?>
<s:Group ...
        xmlns:dms="dms.*"
        creationComplete="complete()">

    <fx:Script>
        <![CDATA[
            import ...

            [Bindable] private var _authors:ArrayCollection;

            public function complete():void {
                 getAuthors.token = authorService.getAll();
             }

            private function authorSort(e:ResultEvent):void {
                 _authors = e.result as ArrayCollection;
 
                 //sort collection by author name
                 var sortByName:Sort = new Sort();
                 sortByName.fields = [new SortField('name',true)];
                 _authors.sort = sortByName;
                 _authors.refresh();
             }
        ]]>
    </fx:Script>

    <fx:Declarations>
        <s:CallResponder id="getAuthors" result="authorSort(event)"/>
        <dms:AuthorService id="authorService" />
    </fx:Declarations>

    <s:List
            dataProvider="{_authors}"
            labelField="name"
            itemRenderer="components.AuthorRenderer"/>
</s:Group>

We use the result event handler on CallResponder MXML tag to know when the author data arrives. The authorSort() function follows the standard way to sort an ArrayCollection using Sort and SortField. The sorted authors are then bound to the List component. The code is nothing special, but I wrote it to illustrate the differences between sorting/filtering on the server using custom filters in LCDS and sorting/filtering locally in Flex. Our local sort still depends on the getAll() fill which is managed by LCDS and any changes will be automatically pushed to the client, alphabetized, and displayed. So, we didn't lose the managed nature of the list of authors, but we did lose the ability to have LCDS page our data.

The takeaway here is to be very wary of local collections operations. Thankfully, LCDS makes server-side sorting/filtering so unbelievably easy there is no reason not to keep this logic on the server.

Blog in Action: Authors

The rest of the code in the authors section closely follows the posts section minus the ItemPendingError stuff because the author-to-posts association is eagerly loaded. Since the code doesn't teach us anything new, I won't go into the details. Instead, I'll just jump to some screenshots.

Here is the Authors page showing the lists of authors and how many posts they've written:

And here we've selected the first author, causing a list of all their posts to be revealed:

Building the Admin

The Admin section provides access to the full set of CRUD operations for all entities in the system. I skipped all security, so unlike your typical blog application the admin is accessible to all users. The UI for the sections repeats the page states pattern used in the main application. So, within the Admin section, you can toggle between each of the three entities: author, post, and tag.

Here is the Admin component whittled down to the UI essentials:

<?xml version="1.0" encoding="utf-8"?>
<s:Group ...>

    <s:states>
        <s:State name="author" />
        <s:State name="post" />
        <s:State name="tag" />
    </s:states>

    <s:ButtonBar id="nav" ...>
        <s:ArrayList source="['Author','Post','Tag']" />
    </s:ButtonBar>

    <s:Group>
        <s:Button label="Add {nav.selectedItem}" 
                click="addHandler()" />

        <s:List id="list"
                changing="selectionChangingHandler(event)"
                labelField="name"
                labelField.post="title"
                dataProvider.author="{TypeUtility.convertToCollection(getAuthors.lastResult)}"
                dataProvider.post="{TypeUtility.convertToCollection(getPosts.lastResult)}"
                dataProvider.tag="{TypeUtility.convertToCollection(getTags.lastResult)}"
                itemRenderer="components.AdminRenderer" />
    </s:Group>

    <s:Group visible="false">
        <forms:AuthorForm includeIn="author"
                save="saveHandler(event)" close="closeHandler()" />
        <forms:PostForm includeIn="post"
                authors="{TypeUtility.convertToCollection(getAuthors.lastResult)}"
                tags="{TypeUtility.convertToCollection(getTags.lastResult)}"
                save="saveHandler(event)" close="closeHandler()" />
        <forms:TagForm includeIn="tag"
                save="saveHandler(event)" close="closeHandler()" />
    </s:Group>
</s:Group>

First, we define our states, one for each entity in the system. Next, we use a single List component for our display, but swap in different data for each state. Recall the original page states pattern used in the main application has a different custom component for each state. Here, we use the new state syntax to modify the List component's the dataProvider and labelField. Lastly, the rendered items are so similar that we can get away with using a single custom ItemRenderer to format the entities for display.

In the second Group container, which is invisible by default, we follow the original page states pattern exactly by using a different custom edit form per state. When the user adds or edits an entity, we simply toggle the visibility to make the form appear. Note that communication between the Admin component and the forms is handled by events (Add, Edit, Delete, Save, Close).

I'll cover the forms in more detail in a moment, but first here is a screenshot of the Admin page in action:

Generated Forms

One of the best client-side LCDS features is the ability to generate a complete Model Driven Form for any entity. The form is completely backed by LCDS and is capable of handling almost all model features including derived properties, validation, constraints, variant entities, and more. The generated code can be modified via a custom FreeMarker template. It can also be modified directly, but any modifications would be lost if the form were to be regenerated. The code is a fantastic learning tool, so even if you don't plan to use a Model Driven Forms, I highly recommend generating a few and staring at the code for a while.

Besides Model Driven Form, you can choose to generate a basic Flex form, which does nothing more than walk through all the simple properties in the given entity and generate the form tags and input fields based on each property's data type. For a couple of reasons, I chose to skip the Model Driven Form option and build custom forms myself. The main driver was to avoid having any LCDS-specific code in my forms. The result of this decision was very lean forms, since I'm not doing any validation either, but a slightly bloated script block in the Admin component.

Before we get into the code, let's check out a screenshot of the custom AuthorForm component in action:

As you can see, AuthorForm is trivial because name is the only editable property in Author. The user activates the edit form by clicking the Edit button next to the entity they wish to edit. This click generates a custom Edit event which bubbles out of the custom AdminRenderer and is handled by the parent Admin component. Since we use the form for both adding a new entity and editing an existing entity, we have two separate handlers to the form:

private function addHandler():void {
     if (currentState == 'author') {
          authorForm.author = new Author();
          authorForm.editing = false;
      } else if (currentState == 'post') {
          postForm.post = new Post();
          postForm.editing = false;
      } else if (currentState == 'tag') {
          tagForm.tag = new Tag();
          tagForm.editing = false;
      }
     editBox.visible = true;
}

private function editHandler(e:AdminEvent):void {
     if (currentState == 'author') {
          authorForm.author = e.entity as Author;
          authorForm.editing = true;
      } else if (currentState == 'post') {
          postForm.post = e.entity as Post;
          postForm.editing = true;
      } else if (currentState == 'tag') {
          tagForm.tag = e.entity as Tag;
          tagForm.editing = true;
      }
     editBox.visible = true;
}

In the addHandler(), we instantiate a new entity and set the edit flag to false, and in the editHandler(), we pass in the entity to be edited and set the edit flag to true.

And here is the AuthorForm's code:

<?xml version="1.0" encoding="utf-8"?>
<s:VGroup ...>

    <fx:Metadata>
        [Event(name="close", type="flash.events.Event")]
        [Event(name="save", type="events.AdminEvent")]
    </fx:Metadata>

    <fx:Script>
        <![CDATA[
            import ...

            [Bindable] public var editing:Boolean = true;
            [Bindable] public var author:Author;

            private function saveHandler():void {
                 author.name = nameTextInput.text;
                 dispatchEvent(new AdminEvent(AdminEvent.SAVE, author));
             }

            private function cancelHandler():void {
                 dispatchEvent(new Event(Event.CLOSE));
             }
        ]]>
    </fx:Script>

    <s:Label id="title" text="{(editing ? 'Edit' : 'Add')} Author" />

    <mx:Form>
        <mx:FormItem label="Name">
            <s:TextInput id="nameTextInput" text="{author.name}" />
        </mx:FormItem>
        <mx:FormItem>
            <s:HGroup>
                <s:Button label="Save" click="saveHandler()" />
                <s:Button label="Cancel" click="cancelHandler()" />
            </s:HGroup>
        </mx:FormItem>
    </mx:Form>
</s:VGroup>

As input, the form accepts an entity and an edit flag, and it output either a Save or Close event. When Save is clicked, we do the work of saving all for form data back to the entity. In this case, a single name text field is saved to the Author entity's name property. When we are saving an existing entity (aka we are in edit mode), the incoming entity is already managed by LCDS's Data Management service. So, the moment we say author.name = 'new name' the change is immediately sent to the server and triggers the intricate Data Management magic to begin. When we are adding, our entity is instantiated but not yet managed, so no server communication takes place. After all the properties have been updated, we dispatch the Save event which is handled in the parent Admin page. Since I wanted all the LCDS-specific code out of the forms, I chose communicate with the parent Admin page using the custom Save event.

Here is the code for the saveHandler() in the Admin page:

private function saveHandler(e:AdminEvent):void {
     if (currentState == 'author') {
          var author:Author = e.entity as Author;
          if (author.id == 0) {
               authorService.createAuthor(author);
           } else {
               authorService.updateAuthor(author);
           }
      } else if (currentState == 'post') {
          var post:Post = e.entity as Post;
          if (post.id == 0) {
               postService.createPost(post);
           } else {
               postService.updatePost(post);
           }
      } else if (currentState == 'tag') {
          var tag:Tag = e.entity as Tag;
          if (tag.id == 0) {
               tagService.createTag(tag);
           } else {
               tagService.updateTag(tag);
           }
      }
     editBox.visible = false;
}

We first distinguish between the type of entity being saved, and then determine if it is a new entity or an existing entity. Next, we call the appropriate createEntity() or updateEntity() method on the entity's service class. These two methods, along with deleteEntity(), are the core client-side CRUD API for LCDS. Both methods are a little different than what you might expect. The createEntity() method not only saves the data, but it also adds the entity to DataManagement, triggering the standard magic. The updateEntity() is also interesting because it is a basically no-op due to the fact that Data Management has already done all the work. If we dig down into the generated code for updateEntity(), we find that the update method doesn't do much besides commit the open transaction. But in our case, since we aren't using transactions in our client, nothing happens. Lastly, note that I've left out any kind of validation during the save process. Obviously, validation is required for every enterprise application ever developed, but I skipped it here only to maintain the readability in the code.

Updating a Many-to-Many Collection

The Author entity is fairly trivial, so let's look at something more challenging. The Post entity has more properties of different type, and most importantly it has two relationships, author and tags, that we need to edit in our form. Another reason skipping the Model Driven Form and choosing to build my own custom form was because I wanted total control over the many-to-many relationship between posts-and-tags.

Here is a screenshot of the more complicated PostForm component:

Focusing just on the tags collection, we see the PostForm UI uses a List with allowMultipleSection set to true plus a TileLayout. The many-to-many relationship is handled in two places in the code. First, when editing an existing post, we use a custom setter to initialize the selected tags. Second, when the form is saved, we have to do some extra work to update the collection.

Here is the getter and setter for passing in a Post entity to the form:

[Bindable]
public function get post():Post {
     return _post;
}
public function set post(value:Post):void {
     _post = value;
 
     //set the selected author in the author dropdown
     if (value.author != null) {
          authorDropDown.selectedIndex = authors.getItemIndex(value.author);
      }
 
     //set the selected tags in the tags list
     var selectedTags:Vector.<int> = new Vector.<int>();
     value.tags.toArray().forEach(
         function (tag:Tag, idx:int, items:Array):void {
              var idx:int = tags.getItemIndex(tag);
              selectedTags.push(idx);
          });
     tagsList.selectedIndices = selectedTags;
}

Recall from the Admin component above, the PostForm takes as input not just the Post entity and an edit flag, but also the list of all authors and the list of all tags. In the setter above, we walk the incoming post's tags and build up a list of selected tags to assign to the List component's selectedIndices property.

Here is the saveHandler():

private function saveHandler():void {
     post.title = titleTextInput.text;
     post.content = contentTextArea.text;
     post.pubDate = pubDateDateField.selectedDate;
     post.author = authorDropDown.selectedItem;
 
     var newTags:Vector.<Object> = tagsList.selectedItems;
     var skip:Array = [];
 
     //delete tags
     for each (var oldTag:Tag in post.tags.toArray()) {
          if (newTags.indexOf(oldTag) == -1) {
               var idx:int = post.tags.getItemIndex(oldTag);
               post.tags.removeItemAt(idx);
           } else {
               skip.push(oldTag);
           }
      }
 
     //add new tags
     newTags.forEach(
             function (tag:Object, idx:int, tags:Vector.<Object>):void {
                  if (skip.indexOf(tag) == -1) {
                       post.tags.addItem(tag);
                   }
              });
 
     dispatchEvent(new AdminEvent(AdminEvent.SAVE, post));
}

We use a straightforward two-step process to update the tags collection on the current post. The code is a little convoluted, but I claim this is not my fault and I'll blame the limited set of array operations in AS3 instead. In the first step, we walk the original list of tags and delete any that are no longer associated with our post. We also build a list of unchanged tags (those tags present in both lists) to be used later. In the second step, we walk the new list of tags and add any that are not already associated with our post. One other interesting note: because the posts-to-tags relationship is bi-directional a single call to remoteItemAt() or addItem() is sufficient to update both sides of the relationship. As with the AuthorForm, when we are editing an existing Post entity, Data Management is doing the work of immediately sending any changes to the server.

Demo of the Demo

I had to write a demo of our application, so you (the reader) would be able to play with it. The full LCDS verison is mutli-client by default (awesome!), but the fact that one reader of this article would be seeing realtime changes made by another reader of this article is definitely not awesome. The demo is pure Flex 4 with a dummy backend. I also ripped out all the async code and ItemPendingError stuff. Other than that, it looks and functions pretty much the same as the "real" application.

Here it is (this is the demo and not backed by LCDS):


What Have We Done?

The end has arrived. Our Flex 4 frontend is functional and doesn't look too bad. Let's review our journey.

We have:

  1. Displayed data from an async backend: myService.getAll() -> CallResponder -> binding of lastResult -> List
  2. Handled ItemPendingErrors when accessing a lazy loaded association.
  3. Sorted/filtered our data: server-side via a sort filter and client-side using Sort and SortField
  4. Performed the full set of CRUD operations on all entities: createEntity(), updateEntity(), deleteEntity()
  5. Generated and customized entity edit forms, including editing a many-to-many relationship.
  6. Built a multi-client blog application with realtime server push.

Even though Part 2 is about the Flex 4 frontend, it's really all about the LCDS backend we build in Part 1. LCDS makes a lot of complicated client-side code disappear through its Data Management magic.

Lastly, we left out a couple of LCDS 3 client-side features, some intentionally, including model-driven forms, validation, custom form templates, styles, and some server-side functionality that has client-side ramifications like transactions, pagination, load-on-demand, and partial objects.

References

The Code

All code is available for download, including the blog application, the demo of the blog application, database scripts, the paper prototype, the UML model, and the Tomcat configuration files for the blog web application.


Read more from Justin Shacklette. Justin Shacklette's Atom feed

Comments

8 Comments

James Ward said:

Great article! But I get an error in the demo:
Error #2046: The loaded file did not have a valid signature.

Maybe RSLs causing that? I'm not sure.

James Ward said:

I cleared my Flash Player cache (rm -r ~/.adobe/Flash_Player on Linux) and now the demo works. Must have been something strange with some testing I had done in the past.

Antony Jukes said:

Brilliant mate, just what i needed, thankyou

Maxim Kachuroskiy said:

> The demo is pure Flex 4 with a dummy backend.
> I also ripped out all the async code and ItemPendingError stuff

Did you face problems with using LCDS and Flex 4 components (DataGrid, List)? Or it works fine as with Flex 2 or 3 components?

Justin said:

@Maxim: LCDS and Flex 4 work great together. The embedded demo is pure Flex 4 (minus the LCDS related stuff) only because I wanted to embed it in this article.

The download zip contains both the LCDS sample app and the pure Flex 4 demo of the sample app.

Emily said:

Thanks so much for putting this together! It was super easy to follow and contained very useful information.

John said:

I agreed, LCDS and Flex 4 work great. Thank you for your help!Thank you and My best regards! Some of you will already know more than others about johlynes. But it's important to learn as much as possible about it. So, the more information you can lay your hands on, the better it is.

Glenn said:

I have been looking for something like this for a very long time. This is the best Flex/LCDS guide that I have seen in a long time. I'm interested in how all the concepts come together (frontend, middleware, backend, build/deploy, printer ink), and I'll check it out. I know that it will help me very much!

Leave a comment


Type the characters you see in the picture above.


Tag Cloud

Technical Speakers

Who is the best technical speaker you have seen?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.