Thursday, October 6, 2011

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

In this tutorial we are going to start working on the Backup feature for our contact book application.

The first thing we need to do change the existing Backup button to two buttons - one for loading backups, and the other one for creating them. I have decided to split this feature into two buttons, because saving a backup will just require a single click on the Create button, while loading would require the user to select a backup file from a list of existing available backups.

In this tutorial we will just create all the necessary interface elements, so that next time we can focus on the functionality.

As I said, we are going to have 2 backup buttons instead of 1 now. Their labels will be "Create backup" and "Load backup" and call the openBackup() and createBackup() functions on click:

<mx:FormItem label="Data transfer">
<s:Button label="Export contacts" click="exportXML();" />
<s:Button label="Import contacts" click="importXML();" />
<s:Button label="Create backup" click="createBackup();" />
<s:Button label="Load backup" click="openBackup();" />
</mx:FormItem>

The createBackup() function will only throw a popup window for now, which will say that the backup file was created:

private function createBackup():void{
Alert.show("Backup file (" + new Date().toDateString() + ") successfully created!", "Database backup");
}

The openBackup() function will also display a popup, but this time a more complicated one. It will not be an Alert window, but rather a TitleWindow, so that we can fill it with content.

We are going to create a TitleWindow object called backupWindow in the declarations tags. We already know that the name will be backupWindow, so we can create the openBackup() function right now.

We use the PopUpManager class to call static addPopUp() and centerPopUp() functions:

private function openBackup():void{
PopUpManager.addPopUp(backupWindow, this, true);
PopUpManager.centerPopUp(backupWindow);
}

This will display the popup when needed. Let's create a function that will close the popup:

private function closeBackup():void{
PopUpManager.removePopUp(backupWindow);
}

Now we can get to creating and populating the window in the Declarations tags. Create a TitleWindow object, set its id, size, title and add a listener for the close event. Direct it to the existing closeBackup() function.

<s:TitleWindow id="backupWindow" width="500" height="370" close="closeBackup();" title="Database backup">

</s:TitleWindow>

Inside we can create a few labels giving some information on the Backup feature, a DataGrid that will hold the backup entries, a button to load a backup and a numeric scroller to let the user edit the backup entry limit:

<s:TitleWindow id="backupWindow" width="500" height="370" close="closeBackup();" title="Database backup">
<s:VGroup x="10" y="10" width="480" height="350">
<s:Label width="480" text="Here you can load database backups that were previously saved. Once loaded, all existing contacts in the database will be overwritten with the data as it was when the backup was made."/>
<mx:DataGrid id="backupGrid" width="480" height="170">
<mx:columns>
<mx:DataGridColumn headerText="Creation date" dataField="date" />
<mx:DataGridColumn headerText="Amount of contacts" dataField="length" />
</mx:columns>
</mx:DataGrid>
<s:Button enabled="false" label="Load selected backup file" />
<s:Label width="480" text="You can set a limit to backup entries that can be stored at the same time. If the limit is exceeded, the oldest entry is deleted, creating room for a new backup entry."/>
<mx:FormItem label="Backup limit"><s:NumericStepper id="backupStepper" minimum="1" maximum="10" value="{backupLimit}" /></mx:FormItem>
</s:VGroup>
</s:TitleWindow>

Remember to declare this backupLimit variable in the beginning of the script and make it bindable:

[Bindable]
private var backupLimit:int = 3;

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="850" height="560"
   creationComplete="init();"
   title="Flex contact book"
   >
   
<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.events.Event;
import flash.events.SQLEvent;
import flash.data.SQLMode;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.geom.Rectangle;
import flash.net.FileFilter;
import flash.net.Responder;
import flash.data.SQLStatement;
import flash.printing.PrintJob;
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
import mx.controls.Alert;
import flash.data.SQLResult;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.printing.FlexPrintJob;
import mx.printing.PrintDataGrid;
import mx.validators.Validator;
import mx.core.FlexGlobals;
import flash.filesystem.FileMode;
import mx.managers.PopUpManager;

private var connection:SQLConnection;
[Bindable]
private var listData:ArrayCollection;
private var receivedXML:XML = new XML();
[Bindable]
private var backupLimit:int = 3;

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 {
var tempSort:Sort;
if (listData!=null) {
tempSort = listData.sort;
}
listData = new ArrayCollection(evt.data);
listData.sort = tempSort;
listData.refresh();
}

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 onChange():void {
b_save.enabled = true;
b_delete.enabled = true;
t_fname.text = contactList.selectedItems[0].fname;
t_lname.text = contactList.selectedItems[0].lname;
t_phone.text = contactList.selectedItems[0].phone;
t_email.text = contactList.selectedItems[0].email;
if (contactList.selectedItems.length > 1) {
b_save.enabled = false;
}
}

private function onDelete():void {
for (var i:int = 0; i < contactList.selectedItems.length; i++){
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "DELETE FROM contacts WHERE id=" + contactList.selectedItems[i].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.selectedItems[0].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;
}
}

private function doSearch():void{
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT id, fname, lname, phone, email FROM contacts WHERE fname LIKE @search OR lname LIKE @search OR phone LIKE @search OR email LIKE @search ORDER BY id;";
stat.parameters["@search"] = "%" + t_search.text + "%";
stat.execute( -1, new Responder(onSelected));
b_save.enabled = false;
b_delete.enabled = false;
}

private function showAll():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 printData(dataP:Object):void{
var pJob:FlexPrintJob = new FlexPrintJob();
var pGrid:PrintDataGrid = new PrintDataGrid();

var col1:DataGridColumn = new DataGridColumn();
col1.dataField = "fname";
col1.headerText = "First name";
var col2:DataGridColumn = new DataGridColumn();
col2.dataField = "lname";
col2.headerText = "Last name";
var col3:DataGridColumn = new DataGridColumn();
col3.dataField = "phone";
col3.headerText = "Phone";
var col4:DataGridColumn = new DataGridColumn();
col4.dataField = "email";
col4.headerText = "Email";

if (pJob.start()) {
addElement(pGrid);

pGrid.width = pJob.pageWidth;
pGrid.height = pJob.pageHeight;

pGrid.dataProvider = dataP;
pGrid.columns = [col1, col2, col3, col4];

pJob.addObject(pGrid);

pJob.send();

removeElement(pGrid);
}
}

private function exportXML():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(doExport));
b_save.enabled = false;
b_delete.enabled = false;
}

private function doExport(evt:SQLResult):void {
var xmlStr:String = "";
xmlStr += "<?xml version='1.0' encoding='UTF-8'?>\n";
xmlStr += "<contacts>\n"
for (var i:int = 0; i < evt.data.length; i++) {
xmlStr += "<contact fname='" + evt.data[i].fname + "' lname='" + evt.data[i].lname + "' phone='" + evt.data[i].phone + "' email='" + evt.data[i].email + "'/>\n"
}
xmlStr += "</contacts>\n"

var myFile:File = new File();
myFile.browseForSave("Save XML file:");
myFile.addEventListener(Event.SELECT, fileSave);

function fileSave(evt:Event):void {
myFile.nativePath+=".xml"
var stream:FileStream = new FileStream();
stream.open(myFile, FileMode.WRITE);
stream.writeUTFBytes(xmlStr);
stream.close();
}
}

private function importXML():void{
var myFile:File = new File();
myFile.browseForOpen("Import XML file", [new FileFilter("XML Files", "*.xml")]);
myFile.addEventListener(Event.SELECT, fileLoad);

function fileLoad(evt:Event):void{
if(myFile.extension=="xml"){
var stream:FileStream = new FileStream();
stream.open(myFile, FileMode.READ);
receivedXML = XML(stream.readUTFBytes(stream.bytesAvailable));
stream.close();

var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "DELETE FROM contacts";
stat.execute( -1, new Responder(handleXML));
}else {
Alert.show("You can only import XML files!", "Error");
}
}
}

private function handleXML(evt:SQLResult):void {
for each(var cont:Object in receivedXML.contact) {
insertXML(cont.@fname, cont.@lname, cont.@phone, cont.@email);
}
showAll();
}

private function insertXML(fn:String, ln:String, ph:String, em:String):void{
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"] = fn;
stat.parameters["@lname"] = ln;
stat.parameters["@phone"] = ph;
stat.parameters["@email"] = em;
stat.execute( -1, new Responder(selectItems));
}

private function openBackup():void{
PopUpManager.addPopUp(backupWindow, this, true);
PopUpManager.centerPopUp(backupWindow);
}

private function createBackup():void{
Alert.show("Backup file (" + new Date().toDateString() + ") successfully created!", "Database backup");
}

private function closeBackup():void{
PopUpManager.removePopUp(backupWindow);
}

]]>
</fx:Script>

<fx:Declarations>
<s:TitleWindow id="backupWindow" width="500" height="370" close="closeBackup();" title="Database backup">
<s:VGroup x="10" y="10" width="480" height="350">
<s:Label width="480" text="Here you can load database backups that were previously saved. Once loaded, all existing contacts in the database will be overwritten with the data as it was when the backup was made."/>
<mx:DataGrid id="backupGrid" width="480" height="170">
<mx:columns>
<mx:DataGridColumn headerText="Creation date" dataField="date" />
<mx:DataGridColumn headerText="Amount of contacts" dataField="length" />
</mx:columns>
</mx:DataGrid>
<s:Button enabled="false" label="Load selected backup file" />
<s:Label width="480" text="You can set a limit to backup entries that can be stored at the same time. If the limit is exceeded, the oldest entry is deleted, creating room for a new backup entry."/>
<mx:FormItem label="Backup limit"><s:NumericStepper id="backupStepper" minimum="1" maximum="10" value="{backupLimit}" /></mx:FormItem>
</s:VGroup>
</s:TitleWindow>

<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">
<mx:AdvancedDataGrid id="contactList" width="570" height="520" dataProvider="{listData}" change="onChange();" allowMultipleSelection="true">
<mx:columns>
<mx:AdvancedDataGridColumn dataField="fname" headerText="First name"/>
<mx:AdvancedDataGridColumn dataField="lname" headerText="Last name"/>
<mx:AdvancedDataGridColumn dataField="phone" headerText="Phone"/>
<mx:AdvancedDataGridColumn dataField="email" headerText="Email"/>
</mx:columns>
</mx:AdvancedDataGrid>
<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 selected" enabled="false" click="onDelete();" />
</mx:FormItem>
<mx:FormItem/>
<mx:FormItem label="Search"><s:TextInput id="t_search"/></mx:FormItem>
<mx:FormItem>
<s:HGroup>
<s:Button id="b_search" label="Search" click="doSearch();" />
<s:Button id="b_showall" label="Show all" click="showAll();" />
</s:HGroup>
</mx:FormItem>
<mx:FormItem/>
<mx:FormItem label="Printing">
<s:Button label="Print selected" enabled="{(contactList.selectedItems.length>0)?true:false}" click="printData(contactList.selectedItems);" />
<s:Button label="Print all" click="printData(contactList.dataProvider);" />
</mx:FormItem>
<mx:FormItem/>
<mx:FormItem label="Data transfer">
<s:Button label="Export contacts" click="exportXML();" />
<s:Button label="Import contacts" click="importXML();" />
<s:Button label="Create backup" click="createBackup();" />
<s:Button label="Load backup" click="openBackup();" />
</mx:FormItem>
</mx:Form>
</s:VGroup>
</s:HGroup>

</s:WindowedApplication>



Next time we'll handle functionality.

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 4
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 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