quarta-feira, 21 de abril de 2010

Develop an AIR Contact app in Flex Part 1

final


STORE ALL YOUR CONTACTS IN A USEFUL AIR APPLICATION BY LEARNING ADD, EDIT AND DELETE DATA TECHNIQUES WITHIN AN SQLITE DATABASE.


Developers can now take their RIA concepts out of the browser and onto the desktop. There are so many concepts feasible in an AIR application and with the ability to store data in a local database, programs we have long been familiar with can now be rebuilt with your own added flair. This tutorial will show you how to build a simplified version of Apple’s Address Book using SQLite to store contact information. The UI will be created using MXML components with ActionScript classes to control all functionality. We urposely aimed to use a variety of techniques in the code to handle states, communicate data, etc, so we would recommend studying the source for further insight. To complete this tutorial, you will need to use the source files to access all of the code; certain scripts are covered in each step. In addition, the guide is split over two parts with next month’s final text including the PDF pages and final files from here.


01 Download required software

step1

Download the latest AIR runtime from http://get.adobe.com/air/ and follow the installation instructions provided by Adobe. If you do not have Flex Builder 3 installed, visit www.adobe.com/products/flex/ to download the 60-day free trial and install as instructed. Now we are ready to start setting up our project.


02 New Flex project

step2

To fire up Flex, select File>New>Flex Project. Enter ‘ContactManager’ as the project name, check Default Location and check the Desktop application (runs in Adobe AIR). Ensure Application Server Type is set to None and click Next. Keep the default output folder and click Next, once again keeping all the defaults. Click Finish.


03 Construct main component

Within the opening WindowedApplication tag in ContactManager.mxml, enter the code as below. Enter the second set of text above to set some basic styling for the contact manager. Create four new folders in the SRC directory: ‘components’, ‘events’, ‘SQLite’ and ‘VO’. Add the provided contacts database from the CD to the SRC folder.


<mx:WindowedApplication xmlns:mx=”http://www.adobe.com/2006/mxml”

layout=”absolute”

width=”450”

height=”360”

showFlexChrome=”false”

verticalScrollPolicy=”off”

horizontalScrollPolicy=”off”>

<mx:Style>

TitleWindow

{

cornerRadius: 0;

roundedBottomCorners: false;

headerColors: #ffffff, #999999;

footerColors: #cccccc, #999999;

borderThicknessLeft: 0;

borderThicknessTop: 0;

borderThicknessBottom: 0;

borderThicknessRight: 0;

headerHeight: 34;

shadowDistance: 0;

dropShadowColor: #333333;

titleStyleName: “contactTitle”;}

.contactTitle

{

color: #333333;

fontFamily: Verdana;

fontSize: 12;

fontWeight: bold;}

</mx:Style>


04 Contact value object

Right-click the VO folder and create a new ActionScript class named ‘ContactVO’ and uncheck Generate constructor from superclass. Enter the code below. This custom value object will ensure correct data typing of contact details is used throughout this. The [Bindable] meta-data tag assigns instances of this class to MXML components.



package vo

{

[Bindable]

public class ContactVO

{

public var contactID:int;

public var firstName:String;

public var lastName:String;

public var email:String;

public var mobileTel:String;

public var homeTel:String;

public var address:String;

public function ContactVO(){}

}

}


05 Build a UI wrapper

Right-click the components folder, select New>MXML Component. Set the file name as ‘ContactWrapper’ and base it on a TitleWindow. Clear width and height values and define layout as absolute. Within the opening TitleWindow tag, ensure the code is as below. Within the opening and closing TitleWindow tags, create a new <mx:Script> tag.


<mx:TitleWindow xmlns:mx=”http://www.adobe.com/2006/mxml”

xmlns:view=”components.*”

layout=”absolute”

title=”AIR Contact Manager”

showCloseButton=”true”>


06 Add the wrapper

In ContactManager.mxml, ensure the code now looks like above. The main two changes here are a new XMLNS reference to our component’s directory so we can add the ContactWrapper component, which is near the bottom of the script. Next, we added an onCreationComplete method initApp() that runs when the component is fully created.


<<mx:WindowedApplication xmlns:mx=”http://www.adobe.com/2006/mxml”

xmlns:view=”components.*” layout=”absolute”

width=”450” height=”360”

showFlexChrome=”false” verticalScrollPolicy=”off”

horizontalScrollPolicy=”off”

creationComplete=”initApp()”>

<mx:Script>

<![CDATA[private function initApp():void{}]]>

</mx:Script>

<view:ContactWrapper id=”contactWrapper”

width=”100%” height=”100%” x=”0” y=”0” />


07 Contact list

In the components folder, create a new MXML component extending Canvas named ‘ContactList’, adding the below code. This component will act as a simple list of all the contacts stored in our database. setName() concatenates the first and last name strings for display. <mx:Binding> binds items in the data grid to the variable selectedContact.


<<?xml version=”1.0” encoding=”utf-8”?>

<mx:Canvas xmlns:mx=”http://www.adobe.com/2006/mxml”>

<mx:Metadata>

[Event(name=”select”, type=”flash.events.Event”)]

</mx:Metadata>

<mx:Script>

<![CDATA[

import mx.controls.dataGridClasses.DataGridColumn;

import vo.ContactVO;

public static const SELECT:String = ‘select’;

[Bindable] public var selectedContact:ContactVO;

private function setName( item:Object, column:DataGridColumn ):String

{

return String( item.firstName +” “+ item.lastName );}

public function deSelect():void{

contact_dg.selectedIndex = -1;}]]>

</mx:Script>

<mx:Binding source=”contact_dg.selectedItem as ContactVO” destina

tion=”selectedContact”/>

<mx:DataGrid id=”contact_dg” width=”100%” height=”100%”

click=”if ( contact_dg.selectedIndex != -1) dispatchEvent(

new Event( SELECT, true ) );”>

<mx:columns>

<mx:DataGridColumn labelFunction=”setName” headerText=”Name” />

</mx:columns></mx:DataGrid></mx:Canvas>




08 Contact details


In the components folder, create a new MXML component extending Canvas named ‘ContactDetails’, adding the below code. This displays all the contact data when selected in the ContactList. This component has two visual states, one to view the data and one to edit the data. State change is controlled by the Boolean variable ‘edit’.


i<?xml version=”1.0” encoding=”utf-8”?>

<mx:Canvas xmlns:mx=”http://www.adobe.com/2006/mxml”

verticalScrollPolicy=”off”

horizontalScrollPolicy=”off”

currentState=”{ ( edit ) ? ‘edit_view’ : ‘’ }”>

<mx:states>

<mx:State name=”edit_view”>

<mx:RemoveChild target=”{ first_txt }” />

<mx:RemoveChild target=”{ last_txt }” />

<mx:RemoveChild target=”{ email_txt }” />

<mx:RemoveChild target=”{ mobile_txt }” />

<mx:RemoveChild target=”{ home_txt }” />

<mx:AddChild position=”lastChild”>

<mx:TextInput id=”first_in” text=”{ contact.firstName }” width=”95”

x=”69” y=”10” />

</mx:AddChild><mx:AddChild position=”lastChild”>

<mx:TextInput id=”last_in” text=”{ contact.lastName }” width=”95”

x=”174” y=”10” />

</mx:AddChild><mx:AddChild position=”lastChild”>

<mx:TextInput id=”email_in” text=”{ contact.email }” width=”200”

x=”69” y=”50” />

</mx:AddChild><mx:AddChild position=”lastChild”>

<mx:TextInput id=”mobile_in” text=”{ contact.mobileTel }” width=”200”

x=”69” y=”90” />

</mx:AddChild><mx:AddChild position=”lastChild”>

<mx:TextInput id=”home_in” text=”{ contact.homeTel }” width=”200”

x=”69” y=”130” />

</mx:AddChild></mx:State></mx:states>

<!– Transition for the screen –>

<mx:transitions>

<mx:Transition id=”fadeTransition” fromState=”*” toState=”*”>

<mx:Parallel target=”{ this }”>

<mx:Dissolve duration=”600”/>

</mx:Parallel></mx:Transition>

</mx:transitions>

<!– First name –>

<mx:Text id=”first_txt”

text=”{ contact.firstName }” fontWeight=”bold”

fontSize=”14” x=”66” y=”10”/>

<!– Last name –>

<mx:Text id=”last_txt”

text=”{ contact.lastName }” fontWeight=”bold”

fontSize=”14” x=”{ first_txt.x + first_txt.width + 3 }”

y=”10”/>

<!– Label = email –>

<mx:Label text=”email”

color=”#666666” fontSize=”12” x=”23” y=”50”/>

<!– Label = mobile –>

<mx:Label text=”mobile”

color=”#666666” fontSize=”12”

x=”15” y=”90”/>

<!– Label = home –>

<mx:Label text=”home”

color=”#666666” fontSize=”12”

x=”21” y=”130”/>

<!– Label = address –>


09 Populating contact details

Still in ContactDetails, enter the below code. Data will be parsed into this component as a custom contact value object, with each visual component referencing the object for data to display. To save the contact-edited information, the entered text in each TextInput component is assigned to its corresponding variable in the value object.


<mx:Script>

<![CDATA[

import vo.ContactVO;

[Bindable] public var contact:ContactVO;

[Bindable] public var edit:Boolean = false;

public var add:Boolean;

public function newContact():void{

contact = new ContactVO();

}

public function saveContact():ContactVO

{

contact.firstName = first_in.text;

contact.lastName = last_in.text;

contact.email = email_in.text;

contact.mobileTel = mobile_in.text;

contact.homeTel = home_in.text;

contact.address = address_txt.text;

return contact;

}

public function clear():void

{

newContact();

edit = false;

}]]></mx:Script>

<mx:Label text=”address”

color=”#666666” fontSize=”12”

x=”5” y=”170”/>

<!– Email –>

<mx:Text id=”email_txt”

text=”{ contact.email }” color=”#000000”

fontSize=”11” x=”66” y=”50”/>

<!– Mobile –>

<mx:Text id=”mobile_txt”

text=”{ contact.mobileTel }”

color=”#000000” fontSize=”11” x=”69” y=”91”/>

<!– Home –>

<mx:Text id=”home_txt”

text=”{ contact.homeTel }” color=”#000000”

fontSize=”11” x=”69” y=”131”/>

<!– Address –>

<mx:TextArea id=”address_txt”

text=”{ contact.address }”

color=”#000000” fontSize=”11” right=”40” left=”69”

height=”90” borderThickness=”{ ( edit ) ? 1 : 0 }”

wordWrap=”true” editable=”{ edit }” y=”171”/>

</mx:Canvas>


10 Add components to wrapper

Return to ContactWrapper and add the code below. Here we use the HDividedBox component to display our list and details custom components. This enables us to mimic Address Book and adjust the width of each component accordingly. A ControlBar is added to hold the buttons controlling the application’s add/edit and delete functionality.


<!– Contact components container –>

<mx:HDividedBox id=”contactContainer” x=”0” y=”0”

width=”100%” height=”100%”>

<view:ContactList id=”c_list” width=”30%” height=”100%” select=”displa

yContact()” />

<view:ContactDetails id=”c_details” width=”70%” height=”100%” />

</mx:HDividedBox>

<mx:ControlBar id=”c_controls” width=”100%” height=”37” y=”229”>

<mx:Button id=”addButton” height=”16” label=”add” click=”onClick(

ADD )” x=”0”/>

<mx:Button id=”deleteButton” height=”16” label=”delete”

click=”onClick( DELETE )”/>


<mx:Spacer width=”{ c_details.width + c_list.width - (

deleteButton.x + deleteButton.width + editButton.width + 20 ) }”/>

<mx:Button id=”editButton” height=”16” label=”{ (c_details.edit ) ?

‘save’ : ‘edit’ }”

enabled=”{ c_details.edit || c_list.contact_dg.selectedIndex != -1

}” click=”onClick( EDIT )”/></mx:ControlBar>


11 Handle window closure

TitleWindow has a close button available if showCloseButton is set as true. Enter the code below into the TitleWindow tag and within the mx:Script tags type: private static const CLOSE_WIN:String = ‘closeWin’. This will dispatch a named constant on clicking the close button. We will pick up this event in the ContactManager.


<mx:TitleWindow xmlns:mx=”http://www.adobe.com/2006/mxml”

xmlns:view=”components.*”

layout=”absolute”

title=”AIR Contact Manager”

showCloseButton=”true”

close=”{ dispatchEvent( new Event ( CLOSE_WIN ) ); }”>


12 Listen for close event

To listen out for the ContactWrapper dispatching our closeWin event modify the ContactWrapper tag as below and enter the closeWindow method within mx:Script tags. When the closeWin event is dispatched, the closeWindow method is fired calling the close() method in the NativeWindow class, and in turn closes the application window.


<view:ContactWrapper id=”contactWrapper”

width=”100%” height=”100%” x=”0” y=”0”

closeWin=”closeWindow( event )” />

private function closeWindow( e:Event ):void{

stage.nativeWindow.close();}


13 Transparent backgrounds

Now we have the basic visual framework in place, let’s build our back-end. In the SQLite

folder, create an ActionScript class to extend EventDispatcher named ‘SQLConnector’.

This class will handle asynchronous connections to our SQLite database and dispatch

SQLConnectionEvents for the application to progress and start querying the database.

Enter the code as listed below:


package sqlite{

import flash.data.SQLConnection;

import flash.events.*;

import flash.filesystem.File;

public class SQLConnector extends EventDispatcher{

public static const OPEN:String = ‘open’;

public static const CLOSE:String = ‘close’;

public static const ERROR:String = ‘error’;

private var _connection:SQLConnection;

private var _dbFileName:String;

private var _dbFile:File;

public function SQLConnector( name:String ){

_dbFileName = name;

_dbFile = File.applicationDirectory.resolvePath( _dbFileName );}

public function connect():void{

_connection = new SQLConnection();

_connection.addEventListener( SQLEvent.OPEN, onOpen );

_connection.addEventListener( SQLEvent.CLOSE, onClose );

_connection.addEventListener( SQLErrorEvent.ERROR, onError );

_connection.openAsync( _dbFile );}

public function disconnect():void{

_connection.close();}


public function get connection():SQLConnection{

return _connection;}

private function onOpen( e:SQLEvent ):void{

dispatchEvent( new Event( OPEN ) );}

private function onClose( e:SQLEvent ):void{

dispatchEvent( new Event( CLOSE ) );}

private function onError( e:SQLErrorEvent ):void{

dispatchEvent( new Event( ERROR ) );

}}}


14 Set up connection

Return to the ContactManager and within the initApp() method, create a new instance of the SQLConnector ensuring that the class is imported. Parse in the constructor the named constant that has the database name as a ‘String’ and call the Connect method. Assign listeners for all class-dispatched events: OPEN, CLOSE and ERROR.


import sqlite.SQLConnector;

private static const DATABASE:String = “contacts”;

private function initApp():void

{

sqliteConnector = new SQLConnector( DATABASE );

sqliteConnector.connect();

sqliteConnector.addEventListener( SQLConnector.OPEN, onOpen );

sqliteConnector.addEventListener( SQLConnector.CLOSE, onClose );

sqliteConnector.addEventListener( SQLConnector.ERROR, onError );}

private function onOpen( e:Event ):void

{

}

private function onClose( e:Event ):void{

trace(“disconnection”);

}

private function onError( e:Event ):void{

trace(“connection error”);

}


15 Start querying

In the SQLite folder, create an ActionScript class to extend EventDispatcher named ‘SQLQueries’. This class will handle all the database queries and dispatch results for each query made. This class needs a reference to the SQLConnection, adds, edits and deletes, and has a method to build the table ‘records’ in the database.


package sqlite

{

import events.AllContactsEvent;

import flash.data.*;

import flash.errors.SQLError;

import flash.events.*;

import mx.collections.ArrayCollection;

import vo.ContactVO;

public class SQLQueries extends EventDispatcher{

public static const CONTACTS_ARR:String = ‘contactsArr’;

private static const EDIT:String = ‘edit’;

private static const DELETE:String = ‘delete’;

private var _connection:SQLConnection;

private var _statement:SQLStatement;

private var _data:ArrayCollection;

public function SQLQueries( conn:SQLConnection ){

_connection = conn;

buildTable();}

private function buildTable():void{

var _sql:String = “CREATE TABLE IF NOT EXISTS records ( contactID

INTEGER PRIMARY KEY AUTOINCREMENT, “ +


“firstName TEXT DEFAULT ‘’, “ +

“lastName TEXT DEFAULT ‘’, “ +

“email TEXT DEFAULT ‘’, “ +

“mobileTel TEXT DEFAULT ‘’, “ +

“homeTel TEXT DEFAULT ‘’, “ +

“address TEXT DEFAULT ‘’)”;

execute( _sql, buildTableResult );}

public function getContacts():void{

var _sql:String = “SELECT * FROM records”;

execute( _sql, getContactsResult );

}

private function execute( sql:String, _handler:

Function, params:ContactVO=null, action:String=”” ):void{

try

{

var handler:Function = _handler;

_statement = new SQLStatement();

_statement.sqlConnection = _connection;

_statement.text = sql;

if( params )

{

_statement.clearParameters();

if( action != “” ){

_statement.parameters[“:contactID”] = params.contactID;}

if( action != DELETE ){

_statement.parameters[“:firstName”] = params.firstName;

_statement.parameters[“:lastName”] = params.lastName;

_statement.parameters[“:email”] = params.email;

_statement.parameters[“:mobileTel”] = params.mobileTel;

_statement.parameters[“:homeTel”] = params.homeTel;

_statement.parameters[“:address”] = params.address;}}

_statement.addEventListener( SQLEvent.RESULT, handler );

_statement.execute();

}

catch ( e:SQLError )

{

trace(“Error with SQLStatement execution”);

}}

private function buildTableResult( e:SQLEvent ):void{

getContacts();}

private function getContactsResult( e:SQLEvent ):void{

var contacts:ArrayCollection = new ArrayCollection();

for each ( var c:Object in _statement.getResult().data ){

var contact:ContactVO = new ContactVO();

contact.contactID = c.contactID;

contact.firstName = c.firstName;

contact.lastName = c.lastName;

contact.email = c.email;

contact.mobileTel = c.mobileTel;

contact.homeTel = c.homeTel;

contact.address = c.address;

contacts.addItem( contact );}

var event:AllContactsEvent = new AllContactsEvent( contacts,

CONTACTS_ARR, true );

dispatchEvent( event );}}}


The code marathon continues in Part 2 - coming soon

"

Nenhum comentário:

Postar um comentário