Offline AIR application example

The example here describes how to build an offline AIR application that has a one-to-one relationship between the Customer and Address objects in the database. You can use this example as a basis to build offline AIR applications for the other relationship types.

Client-side (AIR application) code

Create a folder called "onetoone" in your AIR project and add the ActionScript class files: Customer.as and Address.as with code that could be something like the following:

Customer.as

package onetoone
{
[Bindable]
[RemoteClass(alias="AIRIntegration.custome r")]
[Entity]

public class Customer
{
[Id]
public var cid:int;
public var name: String;
[OneToOne(cascadeType='ALL',fetchType="EAGER")]
[JoinColumn(name="add_id",referencedColumnName="aid")]

public var address:Address;
}
}

Address.as

package onetoone
{
[Bindable]
[RemoteClass(alias="AIRIntegration.address")]
[Entity]

public class Address
{
[Id]
public var aid:int;
public var street:String;
}
}

MainApplication.mxml

Add code like the following in the MainApplication.mxml file to perform CRUD operations on the database.

Note: For Customer.as and Address.as ActionScript classes, global variables have been declared.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplic ation xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="init()">
<mx:Script>

<![CDATA[
import mx.collections.ArrayCollection;
import mx.rpc.AsyncToken;
import mx.controls.Alert;

import coldfusion.air.events.*;
import coldfusion.air.*;

import onetoone.Address;
import onetoone.Customer;

private var session:Session;
private var dbFile:File;

private var cusColl:ArrayCollection;
private var syncmanager:SyncManager;
private var add:Address;//global variable for address.as
private var cus:Customer; //global variable for customer.as

private function init():void
{
// Provide Credentials for Server side Connection and CFC
syncmanager = new SyncManager();
syncmanager.cfPort = 80;
syncmanager.cfServer = "localhost";
// Path of the Server side CFC from CF webroot
syncmanager.syncCFC = "AIRIntegration.cusManager";
// This handler is called when any conflict occurs while
writing back changes on the server side
syncmanager.addEventListener(ConflictEvent.CONFLICT, conflictHandler);
// Fetch Server side DB data onto Client SQLite DB while
starting the App
var token:AsyncToken= syncmanager.fetch("fetch");
token.addResponder(new mx.rpc.Responder(fetchSuccess, fetchFault));
}

private function conflictHandler(event:ConflictEvent):void
{
Alert.show("conflict man!");
var conflicts:ArrayCollection = event.result as ArrayCollection;
// Accept Server data and write it to client side SQLite DB
var token:SessionToken
= session.keepAllServerObjects(conflicts);
token.addResponder(new
mx.rpc.Responder(conflictSuccess, conflictFault));
}

private function conflictSuccess(event:SessionResultEvent):void
{
Alert.show("conflict resolved");
}

private function fetchSuccess(event:SyncResultEvent):void
{
var cus:Array = event.result as Array;
cusColl = new ArrayCollection(cus);
// Open a Session for the client side SQLite DB
dbFile = File.userDirectory.resolvePath("onetoonesync.db");
var sessiontoken:SessionToken
=syncmanager.openSession(dbFile,017915);
sessiontoken.addResponder(new
mx.rpc.Responder(connectSuccess,connectFault));
}

private function connectSuccess(event:SessionResultEvent):void
{
session = event.sessionToken.session;
if(cusColl.length > 0)
{
// This operation saves it to the AIR SQLite DB
var savetoken:SessionToken
= session.saveCache(cusColl);
savetoken.addResponder(new
mx.rpc.Responder(saveCacheSuccess, savefault));
}
else
{
Alert.show("No data available from Server to save on local DB");
}
}

private function saveCacheSuccess(event:SessionResultEvent):void
{
Alert.show("Data saved on client Sqlite DB from Server");

/*
A new Insert is tried here. Note that this is not a complete user interface
application. Otherwise, typically, users need to provide inputs to populate
the Customer/Address Objects
*/
var cus:Customer = new Customer();
cus.cid=12;
cus.name="New Customer";
var add:Address = new Address();
add.aid = 14;
add.street = 'New Address';
cus.address = add;

/*
INSERT the new Records, this is first saved in client side SQLite DB.
On the Commit operation this new record is saved in the Server side DB
Notice that although you are saving the Customer object here,
this operation saves even the binded Address Object also,
as both the entities are CASCADED inside Customer Class
*/
var savetoken:SessionToken = session.save(cus);
savetoken.addResponder(new mx.rpc.Responder(savesuccess, savefault));
}

private function savesuccess(event:SessionResultEvent):void
{
Alert.show("Customer was Inserted Successfully");
// Load some otehr Customer(ex: id=11) so that we can perform Update on
that Customer
var loadtoken:SessionToken =session.loadByPK(Customer,{cid:11});
loadtoken.addResponder(new mx.rpc.Responder(loadCustomer,loadFault))
}
private function loadCustomer(event:SessionResultEvent):void
{
var cus1:Customer = event.result as Customer;
cus1.name = "UpdateCustomerName";
var add1:Address = new Address;
add1.aid = 22;
add1.street = 'UpdatedCustomerAddress';
cus1.address = add1;
/*
Let's call update now and save it to Client side SQLite DB
*/
var savetoken:SessionToken = session.update(cus1);
savetoken.addResponder(new mx.rpc.Responder(updateSuccess,updatefualt));
}

private function updateSuccess(event:SessionResultEvent):void
{
Alert.show("Customer was updated Successfully");
/*
Let's Load another Customer(for example, with id 128) to perform a Delete operation on that
*/
var loadtoken:SessionToken = session.loadByPK(Customer,{cid:128});
loadtoken.addResponder(new mx.rpc.Responder(loadCustomerForDelete,loadFault));
}

private function loadCustomerForDelete(event:SessionResultEvent):void
{
// pass the loaded customer to remove function
var removetoken:SessionToken = session.remove(event.result);
removetoken.addResponder(new mx.rpc.Responder(removeSuccess,removeFault));
}
private function removeSuccess(event:SessionResultEvent):void
{
Alert.show("Customer was deleted Successfully");
}

private function commit():void
{
/*
Until now, you have performed Insert/Update/Delete operation on Customer/Address
entities on the client side SQLite DB. Now use the Commit opertaion to
send them to the Server.
*/
var committoken:SessionToken = session.commit();
committoken.addResponder(new mx.rpc.Responder(commitSuccess, commitFault));
}

private function commitSuccess(event:SessionResultEvent):void
{
Alert.show("Server has been updated with local changes");
/*
Now that you have completed all the operations, you can close the SQLite DB
connection/session. It is a good practice to Close the session,
after you complete all the operations.
*/
var closetoken:SessionToken
= session.close();
closetoken.addResponder(new
mx.rpc.Responder(sessionclosesuccess, sessionclosefault));
}
private function sessionclosesuccess(event:SessionResultEvent):void
{
Alert.show("Session Close Success");
}

// Fault Handlers
private function fetchFault(event:SyncFaultEvent):void
{
Alert.show("fetch fault" + event.toString());
}
private function conflictFault(event:SessionFaultEvent):void
{
Alert.show("conflict not resolved");
}
private function connectFault(event:SessionFaultEvent):void
{
Alert.show("connect failure" + event.toString());
}
private function sessionclosefault(event:SessionFaultEvent):void
{
Alert.show("Session Close Failed::"+event.error);
}
private function removeFault(event:SessionFaultEvent):void
{
Alert.show("Delete Operation Failed::"+event.error);
}
private function commitFault(event:SessionFaultEvent):void
{
Alert.show("Commit Failed::"+event.error);
}
private function loadFault(event:SessionFaultEvent):void
{
Alert.show("Load Failed::"+event.error);
}
private function updatefualt(event:SessionFaultEvent):void
{
Alert.show("update fault"+event.error);
}
private function savefault(event:SessionFaultEvent):void
{
Alert.show("Save Fault::"+event.error);
}
]]>
</mx:Script>
<mx:Button click="commit()" name="commitbutton"
label="Commit/write local data to Server">
</mx:Button>
</mx:WindowedApplication>

Server-side code

Create the following cfc files - Application.cfc, Customer.cfc, Address.cfc, and Cusmanager.cfc with code like the following. The AIR client interacts with the Cusmanager.cfc file, in which you specify the code to fetch and sync the data back to the server.

Application.cfc

<cfcomponent>
<cfset this.name = "OneTonOneExample">
<cfset this.datasource="testorm">
<cfset this.ormenabled="true">
<cfset this.ormsettings={dialect = "MicrosoftSQLServer"}>
</cfcomponent>

Customer.cfc

<cfcomponent persistent="true">
<cfproperty name="cid" fieldtype="id" >
<cfproperty name="name" >
<cfproperty name="address" fieldType='one-to-one'
CFC="address" fkcolumn='aid' cascade='all' >
</cfcomponent>

Address.cfc

<cfcomponent persistent="true">
<cfproperty name="aid" fieldtype="id" >
<cfproperty name="street" >
</cfcomponent>

Cusmanager.cfc

<cfcomponent implements="CFIDE.AIR.ISyncManager">
<!----Fetch method--->
<cffunction name="fetch" returnType="Array" access="remote">
<cfset cus = ArrayNew(1)>
<cfset cus = EntityLoad("customer")>
<cfreturn cus>
</cffunction>
<!----SYNC method--->
<cffunction name="sync" returntype="any">
<cfargument name="operations" type="array" required="true">
<cfargument name="clientobjects" type="array" required="true">
<cfargument name="originalobjects" type="array" required="false">

<cfset conclits = ArrayNew(1)>
<cfset conflictcount = 1>

<cfloop index="i" from="1" to="#ArrayLen( operations )#">
<cfset operation = operations[i]>
<cfset clientobject = clientobjects[i]>
<cfset originalobject = originalobjects[i]>
<cfif operation eq "INSERT">
<cfset obj = ORMGetSession().merge(clientobject)>
<cfset EntitySave(obj)>
<cfelseif listfindnocase("UPDATE,DELETE",operation) neq 0>
<cfset serverobject = EntityLoadByPK("employee",originalobject.getcid())>
<cfif not isdefined('serverobject') >
<cflog text="CONFLICT::SERVER OBJECT NOT FOUND, RECORD MAY BE DELETED ALREADY">
<cfset conflict = CreateObject("component","CFIDE.AIR.conflict")>
<cfset conflict.clientobject = clientobject>
<cfset conflict.originalobject = originalobject>
<cfset conflict.operation = operation>
<cfset conflicts[conflictcount++] = conflict>
<cfcontinue>
</cfif>

<cfset isNotConflict = ObjectEquals(originalobject, serverobject)>
<cfif isNotConflict>

<cfif operation eq "UPDATE">
<cfset obj = ORMGetSession().merge(clientobject)>
<cfset EntitySave(obj)>
<cfelseif operation eq "DELETE">
<cfset obj = ORMGetSession().merge(originalobject)>
<cfset EntityDelete(obj)>
</cfif>

<cfelse><!----Conflict--->
<cflog text = "is a conflict">
<cfset conflict = CreateObject("component","CFIDE.AIR.conflict")>
<cfset conflict.serverobject = serverobject>
<cfset conflict.clientobject = clientobject>
<cfset conflict.originalobject = originalobject>
<cfset conflict.operation = operation>
<cfset conflicts[conflictcount++] = conflict>
<cfcontinue>
</cfif>
</cfif>
</cfloop>
<cfif conflictcount gt 1>
<cfreturn conflicts>
</cfif>
</cffunction>
</cfcomponent>

Adobe, Inc.

Get help faster and easier

New user?