Flextrine Tutorial – CRUD in a simple Flex 4 address book: Updating entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

Once an entity is in an EntityRepository (either because it was loaded from the database or it was persisted) it is known as a MANAGED entity.  This means that Flextrine will constantly watch the entity to see if anything changes, and if so will mark the entity for updating on the next flush().

Therefore updating entities is extremely simple – as long as the entity is managed there is literally nothing to do 🙂

Lets create an editor for the ContactGroups (in the same directory as Main.mxml):

ContactGroupEditor.mxml

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

   2: <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"

   3:          xmlns:s="library://ns.adobe.com/flex/spark"

   4:          xmlns:mx="library://ns.adobe.com/flex/mx">

   5:     

   6:     <fx:Script>

   7:         <![CDATA[

   8:         import vo.ContactGroup;

   9:         

  10:         [Bindable]

  11:         public var contactGroup:ContactGroup;

  12:         

  13:         ]]>

  14:     </fx:Script>

  15:     

  16:     <mx:Form labelWidth="150" width="100%" height="100%">

  17:         

  18:         <mx:FormHeading label="Edit group" />

  19:         

  20:         <mx:FormItem label="Name">

  21:             <s:TextInput text="@{contactGroup.name}" />

  22:         </mx:FormItem>

  23:         

  24:     </mx:Form>

  25:     

  26: </s:Group>

The component takes a contactGroup and provides a TextInput that edits its name attribute using Flex 4 two-way databinding.  In fact you need to be careful with two-way databinding on some components (e.g. DateChooser) as it can cause unnecessary updates, but it works fine for Spark’s TextInput.

Now we need to add the ContactGroupEditor into Main.mxml:

   1: ...

   2: <s:VGroup width="100%" height="100%">

   3:             <!-- The editors -->

   4:             <local:ContactGroupEditor contactGroup="{tree.selectedItem as ContactGroup}" enabled="{tree.selectedItem is ContactGroup}" />

   5: ...

And that’s it for the group editor.  When we select a ContactGroup in the tree, the ContactGroupEditor becomes enabled, and the selected item is passed to the editor’s contactGroup attribute.  Flex updates the contactGroup’s name attribute when we change it in the editor and Flextrine automatically picks up on the change and marks the entity for updating.  On the next flush() the changes will be written to the database.

Now for the ContactEditor.

ContactEditor.mxml

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

   2: <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"

   3:          xmlns:s="library://ns.adobe.com/flex/spark"

   4:          xmlns:mx="library://ns.adobe.com/flex/mx">

   5:     

   6:     <fx:Script>

   7:         <![CDATA[

   8:         import org.davekeen.flextrine.orm.EntityManager;

   9:         import vo.ContactGroup;

  10:         import vo.Contact;

  11:         

  12:         [Bindable]

  13:         public var contact:Contact;

  14:         

  15:         ]]>

  16:     </fx:Script>

  17:     

  18:     <mx:Form labelWidth="150" width="100%" height="100%">

  19:         

  20:         <mx:FormHeading label="Edit contact" />

  21:         

  22:         <mx:FormItem label="Name">

  23:             <s:TextInput text="@{contact.name}" />

  24:         </mx:FormItem>

  25:         

  26:         <mx:FormItem label="Telephone number">

  27:             <s:TextInput text="@{contact.telephoneNumber}" />

  28:         </mx:FormItem>

  29:         

  30:         <mx:FormItem label="Birthday">

  31:             <mx:DateChooser selectedDate="{contact.birthday}" change="contact.birthday = event.currentTarget.selectedDate" />

  32:         </mx:FormItem>

  33:         

  34:         <mx:FormItem label="Group">

  35:             <s:DropDownList dataProvider="{EntityManager.getInstance().getRepository(ContactGroup).entities}"

  36:                             selectedItem="{contact.contactGroup}"

  37:                             change="contact.setContactGroup(event.currentTarget.selectedItem);"

  38:                             labelField="name" />

  39:         </mx:FormItem>

  40:         

  41:     </mx:Form>

  42:     

  43: </s:Group>

The contact editor is marginally more complicated, but still fairly simple.  A few points:

  • Note is that as mentioned above we don’t use two-way databinding on any component apart from TextInput.
  • Although it is perfectly legal to retrieve the singleton EntityManager in the ContactEditor as we have done in the DropDownList dataProvider, in a real application we probably wouldn’t do this as it breaks encapsulation.  I’ve done it here for the sake of simplicity 🙂

Finally we need to add the editor to Main.mxml:

   1: ...

   2:  

   3: <s:VGroup width="100%" height="100%">

   4:     <!-- The editors -->

   5:     <local:ContactGroupEditor contactGroup="{tree.selectedItem as ContactGroup}" enabled="{tree.selectedItem is ContactGroup}" />

   6:     <mx:HRule width="100%" />

   7:     <local:ContactEditor contact="{tree.selectedItem as Contact}" enabled="{tree.selectedItem is Contact}" />

   8: ...

An that’s it!  A simple but fully functioning, database aware Flex application.

And now, the thrilling conclusion.

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Deleting entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

Deleting entities is very simple.  Update the delete button MXML tag to call onDeleteClick() when clicked, and to only be enabled if there is a selection in the tree.

   1: <s:Button label="Delete" click="onDeleteClick()" enabled="{tree.selectedItem != null}" />

And implement the onDeleteClick() method:

   1: private function onDeleteClick():void {

   2:     em.remove(tree.selectedItem);

   3:     

   4:     if (tree.selectedItem is Contact)

   5:         tree.selectedItem.contactGroup.removeContact(tree.selectedItem);

   6:     

   7: }

Notice that we maintain the bi-directional association if the deleted item is a contact by removing it from its associated contactGroup.

And that’s it :)  The entity will be removed from its entity repository (updating the tree via databinding) and on the next flush() it will be removed from the database.

The final step in our little address book is to allow the user to update existing entities.

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Creating new entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

To add new entities to the repository we use the EntityManager.persist() method.  Let’s link up the Add group and Add contact buttons and get them to create and persist new entities.

Modify the Button MXML tags for Add Group and Add Contact so that they call addNewGroup() and addNewContact() when they are clicked.  We can only add contacts within a group, so we will also make sure that you can only click addNewContact() if a ContactGroup is selected in the tree.

   1: <s:Button label="New group" click="onNewGroupClick()" />

   2: <s:Button label="New contact" click="onNewContactClick()" enabled="{tree.selectedItem is ContactGroup}" />

Firstly we’ll make the onNewGroupClick() function.  It really is as simple as creating a new ContactGroup(), assigning some properties and calling EntityManger.persist().

   1: private function onNewGroupClick():void {

   2:     var contactGroup:ContactGroup = new ContactGroup();

   3:     contactGroup.name = "New group";

   4:     em.persist(contactGroup);

   5:     

   6:     // Select the new ContactGroup in the tree

   7:     tree.selectedItem = contactGroup;

   8: }

And that’s it :)  When the user clicks Add Group a new ContactGroup entity is created, a name is assigned to it and it is persisted to Flextrine.  This means that the new contactGroup is added to entities in the ContactGroup entity repository (triggering databinding and updating the tree), and the new entity is marked for insertion into the database.  Note that Flextrine doesn’t actually do anything with the database when we call persist, it just notes that we need to insert it on the next flush – we’ll worry about flushing after we have created onNewContactClick().

onNewContactClick() is very similar:

   1: private function onNewContactClick():void {

   2:     var selectedContactGroup:ContactGroup = tree.selectedItem as ContactGroup;

   3:     

   4:     var contact:Contact = new Contact();

   5:     contact.name = "New contact";

   6:     

   7:     contact.contactGroup = selectedContactGroup;

   8:     selectedContactGroup.contacts.addItem(contact);

   9:     

  10:     em.persist(contact);

  11:     

  12:     // Select the new Contact in the tree

  13:     tree.selectedItem = contact;

  14: }

There is one important subtlety here.  Just like JPA, Flextrine does not automatically handle both ends of a bi-directional association, and its the developer’s job to manage the domain model.  Let’s talk about this in more detail.  Our association between ContactGroup and Contact is OneToMany bi-directional.  This means that a ContactGroup has many Contacts, and a Contact has one ContactGroup.  Therefore when we create a new Contact we need to do the following:

  • Set newContact.contactGroup to selectedContactGroup
  • Add newContact to selectedContactGroup.contacts

In this way we maintain both sides of the bi-directional association.  If we were to change the contactGroup of an existing contact we would need to add another step:

  • Remove contact from its current contactGroup.contacts
  • Set contact.contactGroup to selectedContactGroup
  • Set newContact.contactGroup to selectedContactGroup

Let’s encapsulate this functionality within Contact and ContactGroup by creating ContactGroup.addContact, ContactGroup.removeContact and Contact.setContactGroup.  These are called association helpers and its very likely that future versions of Flextrine will auto-generate these for you.  However, for now we’ll code them by hand.

ContactGroup.as

   1: package vo {

   2:     import mx.collections.ArrayCollection;

   3:     

   4:     [RemoteClass(alias="vo/ContactGroup")]

   5:     [Entity]

   6:     public class ContactGroup extends ContactGroupEntityBase {

   7:         

   8:         public function get children():ArrayCollection {

   9:             return contacts;

  10:         }

  11:         

  12:         public function addContact(contact:Contact):void {

  13:             contact.setContactGroup(this);

  14:         }

  15:         

  16:         public function removeContact(contact:Contact):void {

  17:             contacts.removeItemAt(contacts.getItemIndex(contact));

  18:         }

  19:         

  20:     }

  21:  

  22: }

Contact.as

   1: package vo {

   2:     import mx.collections.ArrayCollection;

   3:     

   4:     [RemoteClass(alias="vo/Contact")]

   5:     [Entity]

   6:     public class Contact extends ContactEntityBase {

   7:         

   8:         public function setContactGroup(contactGroup:ContactGroup):void {

   9:             if (this.contactGroup) this.contactGroup.removeContact(this);

  10:             this.contactGroup = contactGroup;

  11:             if (contactGroup) contactGroup.contacts.addItem(this);

  12:         }

  13:         

  14:     }

  15:  

  16: }

And now we can change onNewContactClick() to simply read:

   1: private function onNewContactClick():void {

   2:     var selectedContactGroup:ContactGroup = tree.selectedItem as ContactGroup;

   3:     

   4:     var contact:Contact = new Contact();

   5:     contact.name = "New contact";

   6:     selectedContactGroup.addContact(contact);

   7:     em.persist(contact);

   8:     

   9:     // Select the new Contact in the tree

  10:     tree.selectedItem = contact;

  11: }

These complications are only relevant for bi-directional association.  If, for example, ContactGroup has many Contacts, but ContactGroups don’t know anything about their Contacts this would be a ManyToOne uni-directional association and all we would need to do would be to set contactGroup on the Contact to maintain the domain model.

Flushing

At this point nothing has actually been written to the database – all that has happened is that Flextrine has marked our persisted objects for insertion.  In order to tell Flextrine to execute its queue of operations against the database we use EntityManager.flush().  This will create new entities, update changed entities and delete removed entities in the database.

One thing to consider is if for some reason the flush fails (for example if we were to try and delete a ContactGroup without deleting its child Contacts) the client will be out of sync with what is in the database so we are going to add a responder to flush() that will simply reload all the data fresh from the database in case of an error.

Note that once the flush() has completely successfully our persisted ContactGroup and Contact entities will have their id fields automatically updated with the auto increment id from the database.

Update the Save button to call onSaveClick():

   1: <s:Button label="Save" click="onSaveClick()" />

And implement the method and the responders:

   1: private function onSaveClick():void {

   2:     enabled = false;

   3:     

   4:     em.flush().addResponder(new AsyncResponder(onSaveResult, onSaveFault));

   5: }

   6:  

   7: private function onSaveResult(result:Object, token:Object):void {

   8:     enabled = true;

   9: }

  10:  

  11: private function onSaveFault(result:Object, token:Object):void {

  12:     enabled = true;

  13:     

  14:     em.clear();

  15:     em.getRepository(ContactGroup).loadAll();

  16: }

Notice that if there is an error we call EntityManager.clear() before reloading the data – this empties out all the current entities in the repositories.

In the next section of the tutorial we’ll see how we can delete entities.