Wednesday, September 28, 2011

Creating a contact book using Flex, AIR and SQLite database: Part 4

In this tutorial we will add some little changes to polish up our contact book application.

Our application can function properly and ready to use, however, if you are going to distribute this to other people other than yourself, you will need to keep in mind that when they first open your program, they will see it for the first time. That means they might need some time to orient in your application. That is why it is important to make your application more available and intuitively understandable by the people (usually, when testing final versions of programs/games I make, I ask friends or family members to test them and give me criticism before I make it available to the public).

If the user finds your application confusing to use, they may not bother to give it a second chance. Thus, it is important to add some finishing touches after you're done with functionality of your application, these little changes might include rephrasing some labels, rearranging elements on the screen, restricting something to avoid errors.

Putting logical restrictions even in the most obvious elements of your applications is important. For example, it is obvious that the user's phone number can't contain letter characters, however, it is not guaranteed that your user understands that, and that may raise additional problems in the future. For example, if you made it possible to call phones using your application and the user has "i will fill this later" in his Phone field, your application will break, because it is not possible to call that 'number'.

Here's what we are going to do:

  • Add a 'Clear fields' button
  • Rearrange/rename some buttonsfor better user intuitivity
  • Set some fields required

The last objective in that list needs some detailed explanation. My goal is to set the first name and last name fields AND any contact field required (3 fields in total). This means that in order to create or save changes to an item, the user needs to enter their First name, Last name, and at least one of the contact fields (Phone, Email or Phone and Email). Why is this needed? Because it is a contact book, therefore it is logical that the user only adds people with at least some contacts there.

Alright, let's go coding according to the plan. The first thing we need to do is add a Clear fields button, which will empty all the input fields when pressed.

<s:Button id="b_clear" label="Clear fields" click="clearFields();" />

The clearFields() function is pretty simple:

private function clearFields():void {
t_fname.text = t_lname.text = t_phone.text = t_email.text = "";
}

Now, the arrangement. I decided to put all the buttons under the form fields, and put them in this order: Create new item, Clear fields, Save changes (note that I renamed this one) and Delete item. It is important that the Create button is the top one, because that's the one the user will most probably click at start. Another thing I need to do, is show that the first 2 fields are required by adding the little red stars next to the fields.

This is what the application will look like:



Let's get to coding that. We simply arrange the buttons in the Form container as we want. To add the red star symbols, we need to set the 'required' property of the respective field object to true.

<s:HGroup width="500">
<s:List id="contactList" width="290" height="300" dataProvider="{listData}" labelFunction="getFullName" change="onChange();"/>
<s:VGroup horizontalAlign="center">
<mx:Form>
<mx:FormItem label="First name" required="true"><s:TextInput id="t_fname"/></mx:FormItem>
<mx:FormItem label="Last name" required="true"><s:TextInput id="t_lname"/></mx:FormItem>
<mx:FormItem label="Phone"><s:TextInput id="t_phone" restrict="0-9 " /></mx:FormItem>
<mx:FormItem label="Email"><s:TextInput id="t_email"/></mx:FormItem>
<mx:FormItem>
<s:Button id="b_new" label="Create new item" click="createNew();" />
<s:Button id="b_clear" label="Clear fields" click="clearFields();" />
<s:Button id="b_save" label="Save changes" enabled="false" click="onSave();" />
<s:Button id="b_delete" label="Delete item" enabled="false" click="onDelete();" />
</mx:FormItem>
</mx:Form>
</s:VGroup>
</s:HGroup>

Now, the most complex part - setting the fields required. Remember, that we need to set the first 2 fields as always required, and then make the user fill at least one more contact field. You'll need some knowledge about validators in Flex (read my article here) here.

Pay attention from now on: we are going to make 2 StringValidators, with sources for t_fname and t_lname and their required properties set to true. Group these Validators in an Array and call it nameValidators. We are going to use this Array of validators to check if the fields were filled in. We do that by using the Validator class to validate this array, and if there are more than 0 validator errors, then it means that at least one of the fields are empty.

On top of all this, we will add another check. We will create 2 more validators - PhoneNumberValidator and EmailValidator, source them to t_phone and t_email, set 'required' property to true. When performing the fields check, we will need to check if these two validators say that the t_phone and t_email fields are invalid. If they are BOTH invalid, this means they are both either empty, or contain incorrect characters/have incorrect formatting/etc.

This means, that our big check looks like this:

if the first 2 validators have any errors, tell user to fill the fields, otherwise proceed:
if the phone and email validators say that those fields are invalid, tell the user to fill the fields, otherwise proceed:
check is completed and the user is allowed to save changes.

Where do we store this conditional check? In a function called fieldCheck(), which will return a Boolean - true if we allow the user to proceed, false if there were errors. Then we use the fieldCheck as a conditional in the createNew and onSave functions.

Now, the code itself.

The validator declarations:

<fx:Declarations>
<fx:Array id="nameValidators">
<mx:StringValidator source="{t_fname}" property="text" required="true" />
<mx:StringValidator source="{t_lname}" property="text" required="true" />
</fx:Array>
<mx:PhoneNumberValidator id="valid_phone" source="{t_phone}" property="text" required="true" />
<mx:EmailValidator id="valid_email" source="{t_email}" property="text" required="true" />
</fx:Declarations>

The fieldCheck function:

private function fieldsCheck():Boolean {
var validatorErrors:Array = Validator.validateAll(nameValidators);
if (validatorErrors.length > 0) {
Alert.show("You have to fill in first name, last name and at least 1 other contact field!", "Oops!");
return false;
}else if (valid_phone.validate(t_phone.text).type != "valid" && valid_email.validate(t_email.text).type != "valid") {
Alert.show("You have to fill in at least 1 contact field, other name first name and last name! (You need to have at least 3 fields filled in, including first name and last name.)", "Oops!");
return false;
}else{
return true;
}
}

The updated createNew and onSave functions with the fieldsCheck() in the conditional:

private function createNew():void {
if(fieldsCheck()){
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "INSERT INTO contacts (fname, lname, phone, email) VALUES (@fname, @lname, @phone, @email)";
stat.parameters["@fname"] = t_fname.text;
stat.parameters["@lname"] = t_lname.text;
stat.parameters["@phone"] = t_phone.text;
stat.parameters["@email"] = t_email.text;
stat.execute( -1, new Responder(selectItems));
}
}

private function onSave():void {
if(fieldsCheck()){
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "UPDATE contacts SET fname=@fname, lname=@lname, phone=@phone, email=@email WHERE id=" + contactList.selectedItem.id;
stat.parameters["@fname"] = t_fname.text;
stat.parameters["@lname"] = t_lname.text;
stat.parameters["@phone"] = t_phone.text;
stat.parameters["@email"] = t_email.text;
stat.execute( -1, new Responder(selectItems));
t_fname.text = t_lname.text = t_phone.text = t_email.text = "";
}
}

And we're done. Full code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx" 
   width="600" height="350"
   creationComplete="init();"
   title="Flex contact book"
   >
   
<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.events.SQLEvent;
import flash.data.SQLMode;
import flash.filesystem.File;
import flash.net.Responder;
import flash.data.SQLStatement;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import flash.data.SQLResult;
import mx.validators.Validator;

private var connection:SQLConnection;
[Bindable]
private var listData:ArrayCollection;

private function init():void{
var dbFile:File = File.applicationStorageDirectory.resolvePath("database.db");
connection = new SQLConnection();
connection.addEventListener(SQLEvent.OPEN, onOpen);
connection.openAsync(dbFile, SQLMode.CREATE);
}

private function onOpen(evt:SQLEvent):void{
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "CREATE TABLE IF NOT EXISTS contacts (id INTEGER PRIMARY KEY AUTOINCREMENT, fname TEXT, lname TEXT, phone TEXT, email TEXT)";
stat.execute(-1, new Responder(selectItems));
}

private function selectItems(evt:SQLResult):void{
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT id, fname, lname, phone, email FROM contacts ORDER BY id";
stat.execute( -1, new Responder(onSelected));
b_save.enabled = false;
b_delete.enabled = false;
}

private function onSelected(evt:SQLResult):void {
listData = new ArrayCollection(evt.data);
}

private function createNew():void {
if(fieldsCheck()){
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "INSERT INTO contacts (fname, lname, phone, email) VALUES (@fname, @lname, @phone, @email)";
stat.parameters["@fname"] = t_fname.text;
stat.parameters["@lname"] = t_lname.text;
stat.parameters["@phone"] = t_phone.text;
stat.parameters["@email"] = t_email.text;
stat.execute( -1, new Responder(selectItems));
}
}

private function getFullName(evt:Object):String{
return evt.fname + " " + evt.lname;
}

private function onChange():void {
b_save.enabled = true;
b_delete.enabled = true;
t_fname.text = contactList.selectedItem.fname;
t_lname.text = contactList.selectedItem.lname;
t_phone.text = contactList.selectedItem.phone;
t_email.text = contactList.selectedItem.email;
}

private function onDelete():void {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "DELETE FROM contacts WHERE id=" + contactList.selectedItem.id;
stat.execute( -1, new Responder(selectItems));
t_fname.text = t_lname.text = t_phone.text = t_email.text = "";
}

private function onSave():void {
if(fieldsCheck()){
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "UPDATE contacts SET fname=@fname, lname=@lname, phone=@phone, email=@email WHERE id=" + contactList.selectedItem.id;
stat.parameters["@fname"] = t_fname.text;
stat.parameters["@lname"] = t_lname.text;
stat.parameters["@phone"] = t_phone.text;
stat.parameters["@email"] = t_email.text;
stat.execute( -1, new Responder(selectItems));
t_fname.text = t_lname.text = t_phone.text = t_email.text = "";
}
}

private function clearFields():void {
t_fname.text = t_lname.text = t_phone.text = t_email.text = "";
}

private function fieldsCheck():Boolean {
var validatorErrors:Array = Validator.validateAll(nameValidators);
if (validatorErrors.length > 0) {
Alert.show("You have to fill in first name, last name and at least 1 other contact field!", "Oops!");
return false;
}else if (valid_phone.validate(t_phone.text).type != "valid" && valid_email.validate(t_email.text).type != "valid") {
Alert.show("You have to fill in at least 1 contact field, other name first name and last name! (You need to have at least 3 fields filled in, including first name and last name.)", "Oops!");
return false;
}else{
return true;
}
}

]]>
</fx:Script>

<fx:Declarations>
<fx:Array id="nameValidators">
<mx:StringValidator source="{t_fname}" property="text" required="true" />
<mx:StringValidator source="{t_lname}" property="text" required="true" />
</fx:Array>
<mx:PhoneNumberValidator id="valid_phone" source="{t_phone}" property="text" required="true" />
<mx:EmailValidator id="valid_email" source="{t_email}" property="text" required="true" />
</fx:Declarations>

<s:HGroup width="500">
<s:List id="contactList" width="290" height="300" dataProvider="{listData}" labelFunction="getFullName" change="onChange();"/>
<s:VGroup horizontalAlign="center">
<mx:Form>
<mx:FormItem label="First name" required="true"><s:TextInput id="t_fname"/></mx:FormItem>
<mx:FormItem label="Last name" required="true"><s:TextInput id="t_lname"/></mx:FormItem>
<mx:FormItem label="Phone"><s:TextInput id="t_phone" restrict="0-9 " /></mx:FormItem>
<mx:FormItem label="Email"><s:TextInput id="t_email"/></mx:FormItem>
<mx:FormItem>
<s:Button id="b_new" label="Create new item" click="createNew();" />
<s:Button id="b_clear" label="Clear fields" click="clearFields();" />
<s:Button id="b_save" label="Save changes" enabled="false" click="onSave();" />
<s:Button id="b_delete" label="Delete item" enabled="false" click="onDelete();" />
</mx:FormItem>
</mx:Form>
</s:VGroup>
</s:HGroup>

</s:WindowedApplication>

Hope this was helpful.

Thanks for reading!

Related:

Creating a contact book using Flex, AIR and SQLite database: Part 1
Creating a contact book using Flex, AIR and SQLite database: Part 2
Creating a contact book using Flex, AIR and SQLite database: Part 3
Creating a contact book using Flex, AIR and SQLite database: Part 5
Creating a contact book using Flex, AIR and SQLite database: Part 6
Creating a contact book using Flex, AIR and SQLite database: Part 7
Creating a contact book using Flex, AIR and SQLite database: Part 8
Creating a contact book using Flex, AIR and SQLite database: Part 9
Creating a contact book using Flex, AIR and SQLite database: Part 10
Creating a contact book using Flex, AIR and SQLite database: Part 11
Creating a contact book using Flex, AIR and SQLite database: Part 12
Creating a contact book using Flex, AIR and SQLite database: Part 13
Creating a contact book using Flex, AIR and SQLite database: Part 14
Creating a contact book using Flex, AIR and SQLite database: Part 15
Creating a contact book using Flex, AIR and SQLite database: Part 16

No comments:

Post a Comment