Tuesday, November 28, 2006

How to keep db connections alive in FDS/Hibernate

Struggled with a good title on that one. Anyway... here's the deal. I noticed a while back that if i left my fds server running over night, and in the morning tried to open the sample CRM application, well, it would fail. Fail so badly in fact I'd have to restart or redeploy to get it working again. I'd get an error in the fds log that looked something like this
SEVERE: Communications link failure due to underlying exception:
** BEGIN NESTED EXCEPTION **
java.net.SocketException
MESSAGE: Software caused connection abort: socket write error
Communications link failure due to underlying exception:
...

So yesterday I decided to see if I could fix this. It turned out to be a very very long day. Here are the lessons I learned.

Lesson 1: By default, mySQL terminates db connections after 8 hours of inactivity
Here are the default mySQL settings:

#The number of seconds the server waits for activity on a connection before closing it
wait_timeout=28800
#The number of seconds the server waits for activity on an interactive connection before closing it.
interactive_timeout=28800
For the remainder of my testing I changed these values to 300 in the my.ini file so that my connections would close after only 5 minutes of activity. A much shorter timeframe for testing! So the initial problem is that once mySql closes down the connection, Hibernate does not natively manage this connection and reopen it. If you connect directly with a jdbc connection you may run into the same issue. The trick is to use some kind of connection pool manager to keep the connections alive... the question remains, how can we achieve this?

Lesson 2: Using App Server JNDI datasources gives you connection pooling
In jrun or jboss, it is possible to create jndi datasources. These will then be managed by the application server. I created a jndi datasource for the crm database that comes with FDS which I recreated a while back in mySQL. Creating this datasource involved adding the following configuration to my jrun-resources.xml in the \fds2\JRun4\servers\default\SERVER-INF folder:
<data-source>
<dbname>crm</dbname>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://127.0.0.1:3306/crm</url>
<username>username</username>
<password>password</password>
<encrypted>false</encrypted>
<encryption-class>jrun.security.JRunCrypterForTwofish</encryption-class>
<native-results>true</native-results>
<remove-on-exceptions>true</remove-on-exceptions>
<pool-statements>true</pool-statements>
<initial-connections>1</initial-connections>
<connection-timeout>1200</connection-timeout>
<pool-retry>30</pool-retry>
<transaction-timeout>20</transaction-timeout>
<cache-enabled>false</cache-enabled>
<cache-size>5</cache-size>
<cache-refresh-interval>30</cache-refresh-interval>
<jndi-name>crm</jndi-name>
<poolname>Pool</poolname>
<minimum-size>0</minimum-size>
<maximum-size>2147483647</maximum-size>
<user-timeout>20</user-timeout>
<skimmer-frequency>420</skimmer-frequency>
<shrink-by>5</shrink-by>
<maximum-soft>true</maximum-soft>
<debugging>false</debugging>
<disable-pooling>false</disable-pooling>
<description />
<isolation-level>READ_COMMITTED</isolation-level>
</data-source>
The key thing to note here is the jndi-name I've given this datasource. In Jboss I added a file called mysql-ds.xml to my deploy folder which looks like this:
<datasources> 
<local-tx-datasource>
<jndi-name>crm</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>user</user-name>
<password>password</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<idle-timeout-minutes>4</idle-timeout-minutes>
</local-tx-datasource>
</datasources>
I've kept the jndi names the same in both jrun and jboss so that it's easy for me to deploy my apps from one to the other. I ran some tests after I did this. Hibernate - still using it's own connection - would still fail permanently after the connection timed out. Yet the crm app that was configured to use jdbc/jndi directly (ConnectionHelper.java):
javax.naming.Context context = new javax.naming.InitialContext();
return ((DataSource) context.lookup("java:crm")).getConnection();
would fail once and then reconnect successfully. It looked like I was on the right track, now it was time to reconfigure Hibernate.

Lesson 3: Hibernate is not recommended for connection pooling
I had to configure Hibernate to take advantage of my jndi datasources. Here's what the hibernate.cfg.xml looks like for both jrun and jboss:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:crm</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
[...]
</session-factory>
</hibernate-configuration>
I saved my changes, restarted the server, ran my app, waited for the timeout, ran the app again, and even though it hiccupped on the first try, the connection was reestablished on the second try. But why this initial hiccup? Shouldn't there be some way to configure it so that even this initial error can be avoided? I still had much work to do!

Lesson 4: Make sure to have the latest mysql jdbc drivers installed
It turns out that Jboss jndi datasources can be configured to actively ping db connections and keep them alive. Yet I kept getting errors after configuring my datasource to do this 'pinging', and it turned out I was running older jdbc drivers. I upgraded to the latest ones and the errors went away. Here's the jboss datasource file with the ping features enabled:
<datasources> 
<local-tx-datasource>
<jndi-name>crm</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>user</user-name>
<password>password</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<idle-timeout-minutes>4</idle-timeout-minutes>
<exception-sorter-class-name>
com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
</exception-sorter-class-name>
<valid-connection-checker-class-name>
com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
</valid-connection-checker-class-name>

</local-tx-datasource>
</datasources>
Since my production environment is Jboss, and it had already been a long day, I decided to not worry about figuring out how to do the same in JRun. If anyone out there knows this, please leave a comment!

Lesson 5: Hibernate must be told that it's connection is now being managed
At this point i was able to retrieve data without error, despite connection time outs. When I went to update a record, however, I got a cool new error:
java.sql.SQLException: You cannot commit during a managed transaction!
So, I did some digging and changed my hibernate.cfg.xml to this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:crm</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="transaction.manager_lookup_class">
org.hibernate.transaction.JBossTransactionManagerLookup
</property>

[...]
</session-factory>
</hibernate-configuration>
As you can see this is specific to Jboss. There are several other TransactionManagerLookups available that you can read about here

Summary
I spent alot of time on this yesterday, so this blog might seem a bit scarce and a bit rushed. Basically all the configuration files are here for you to use and customize. If you have questions, please ask. Again, this is blogged here for my benefit as well as yours. I would not want to have to redo this from scratch 6 months from now!

Thursday, November 23, 2006

Installing FDS with JBoss and IIS

Here's the initial steps.


  1. Download Jboss and run locally on my dev box. Install Flex Data Services. Unzip the FDS Admin, Flex and Samples files into folders of the same name with a .war extension, and drop these into the C:\Java\Jboss4\server\default\deploy folder - I find unzipping them first let's me work on the files more easily.

  2. Configure mySql support. Drop a copy of the mysql-connector.jar in the C:\Java\Jboss4\server\default\lib folder. Create a datasource xml file called mysql-ds.xml in the C:\Java\Jboss4\server\default\deploy folder, looks like this:
    <datasources> 
    <local-tx-datasource>
    <jndi-name>crm</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/crm</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>user</user-name>
    <password>password</password>
    </local-tx-datasource>
    </datasources>

  3. Test the sample apps locally. This included Hibernate apps and all the sample apps. Also, a little flex app I wrote that uses remote calls and javamail to send email notifications.

Once I was confident that Jboss was running all the flex apps flawlessly, the next challenge was figuring out how to get jboss to run as a windows service. I downloaded the zip from this website, ensured that my JAVA_HOME and JBOSS_HOME system variables were set properly, namely without a trailing backslash, then ran "install default" from a command line. It successfully created a JBoss service, which worked great. I was now ready to move my stuff to my IIS Server.

I basically zipped up the jboss and javaservice folders and ftp'ed them up to the server. While it was uploading I installed the Java SDK, mySql, mySqltools and FDS on the server, in the same folders as on my local dev box. I setup my system variables JAVA_HOME and JBOSS_HOME, copied my data over to the mySQL data folder on the server. When the upload was done I unzipped my jboss and javaservice folders into my java folder, again keeping it exactly like my local install configuration. I ran jboss manually and tested all the sample apps. Then I installed it as a service, ran it and tested again.

The next test was hitting the sample applications from the web, outside my firewall. I could get the apps to launch however I was not getting any kind of connection... probably because my ISP blocks most ports by default (/sigh), including the default rtmp ports defined in the services-config.xml. So I went into the configuration files and added my-http to all the channels as the failover second channel. I restarted jboss just to be safe ( I find that hotdeployment on jboss seems to randomly end in out of memory errors!) and the apps did start working.

Now all the samples apps are setup by default to compile on the server - but my goal was to host the flex apps in IIS and not the app server. To test this, I had written another flex application that was set to compile locally in Flexbuilder. The advantage of this approach was that technically the swf files generated should be hostable on any webserver. On my local machine the app ran fine, but copying it up to the server gave me a channel connect error. The culprit here was the services-config.xml. By default the port used by any flex app is the same port the app is being served up on uri="http://{server.name}:{server.port}/{/{context.root}/messagebroker/http". On my machine IIS is running on 80, and jboss is configured to listen on 8800. I hardcoded the config by replacing {server.port} with 8800, recompiled my app and then uploaded it to my IIS folder. And that did the trick!

Tuesday, November 21, 2006

Getting Effects to play when transitioning back

Simple little task. Click a button [State:Login], have it fade out, change viewstate, have new panel fade in [State:Register]. Works like a charm. Now, click button on panel, have panel fade out, change viewstate, have button fade back in. Also do the same thing with the WipeUp and WipeDown effect.

Problem. Transitions to the Register state work fine, but transitions back to the original Login State don't. The panel just disappears, no fade, and then the button [State:Login] fades back in nicely. It appears that the controls in the Register State are removed by default before any effects can play visually on them. Using a Sequence and setting the duration of the Panel fade to 10 seconds proves that the effect does play, since it delays the fade in of the button [State:Login], however the control is no longer visible because we're no longer in the Register State.

I realize this is probably confusing... but those of you that have come across this same problem will recognize it. I poured over the LiveDocs but it took just alot of troubleshooting different ideas before I got it working. I've posted a working demo in my Flex Projects column to the right. As usual, right click on the demo to get at the source.

Sunday, November 19, 2006

The deal with embedded fonts, buttons and Rotate

Tonight I struggled with a problem I've faced before, only last time I didn't blog or write down my solution anywhere. I'm on a quest to create a vertical Accordion. Now the fastest way to do this would be to simply apply a rotate effect to it. Here's the code:

private function setAccordion():void{
var rot:Rotate = new Rotate();
rot.angleTo = 270;
rot.duration = 1;
rot.originX = acc.width/2; //need it to rotate around it's center
rot.originY = acc.height/2; // -- " --
rot.target = acc; //acc is my Accordion control
rot.play();
}

I figured this part out a while ago. Without the originX and originY properties set, your control can disappear completely, especially if it's x and y are set to 0. I am currently calling this function from Initialize and have not encountered any issues. What I was having a problem with is that my Accordion header text would disappear during the rotation. Now I know that if I embed a font:
<mx:Style>
@font-face {
src:url("lucidaGrande.swf");
fontFamily: "Lucida Grande";
}
Application{
fontFamily: "Lucida Grande";
}
</mx:Style>
then the text of my controls should remain intact thoughout the rotation. But this didn't work. I could clearly see that the font in my Accordion Header was not the embedded font, nor was it the default system font. I dropped a button on my application and saw the same behaviour. It wasn't till I added a label that I saw the embedded font appear.

I tried quite a few things... but in the end it turned out that the above Styles only applied to controls that display text with a default fontWeight of Normal. This would explain why the AccordionHeader, which I believe extends Button, and the Button controls didn't render the embedded font. I modified my css:
<mx:Style>
@font-face {
src:url("lucidaGrande.swf");
fontFamily: "Lucida Grande";
fontWeight: bold;
}
@font-face {
src:url("lucidaGrande.swf");
fontFamily: "Lucida Grande";
fontWeight: normal;
} javascript:void(0)
Publish
Application{
fontFamily: "Lucida Grande";
}
</mx:Style>
and everything worked just beautifully. Of course, now I need to figure out how to rotate the canvas' within the Accordion back to their original position. And figure out how to keep the Accordion in the same place, and why there's a vertical scroll bar now... ack. It never ends...

On a side note, I also stumbled upon a property called
rotation
. This is a nice shortcut alternative to the rotate.play, however without the benefit of being able to set originX and originY.

Friday, November 17, 2006

Flex to .Net via Flex Data Services - Part Two

In part two of this series I will focus on creating java classes that consume .net webservices. Again, I just want to warn the java savvy folks out there that some of the things I cover are just common sense, and that this is geared more towards folks who have never touched java.

To complete this exercise you will need to download and install the following:


Create a working directory. In this directory create a batch file called run.bat and paste the following text in and save (be sure to remove any linebreaks from the last 3 lines).

set AXIS_HOME=C:\Java\axis-1_4
set ANT_HOME=C:\Java\ant
set JAVA_HOME=C:\java\jdk1.5.0_09\
path = %path%;c:\java\ant\bin;c\java\jkd1.5.0_09\bin;
set CLASSPATH=.;%AXIS_HOME%\lib\axis.jar;%AXIS_HOME%\lib\commons-discovery-0.2.jar;%AXIS_HOME%\lib\commons-logging-1.0.4.jar;%AXIS_HOME%\lib\jaxrpc.jar;%AXIS_HOME%\lib\saaj.jar;%AXIS_HOME%\lib\wsdl4j-1.5.1.jar


This batch file will need to be run each time you open a command prompt. Alternatively, if you are going to be doing this alot, you can add these variables and paths to your Environment Variables.

Next you will need to create another file called build.xml in the same folder, with the following text:
<?xml version="1.0"?>
<!-- Build file for our first application -->
<project name="Ant test project" default="build" basedir=".">
<target name="build" >
<javac srcdir="." destdir="." debug="true" includes="**/*.java"/>
</target>
</project>

I'm not going to spend a great deal of time explaining the structure of this file or on how Ant works... my brother, a java guru, was kind enough to help out with this. Because I'm not using any Java IDE yet, I have to use Ant to compile my classes for me. This is the simplest build file I could muster, and it basically tells Ant to compile all my java source code into .class files, and put them in the same folder.

Finally we're ready to start playing with the webservice. Open a command prompt window and navigate to your working folder. You will need to have completed and successfully tested thet .net Webservice in Part One before you can continue here. Type in:
java org.apache.axis.wsdl.WSDL2Java http://localhost/WebService1/Service1.asmx?WSDL
and hit enter. You might see some warnings but no errors. Please ensure the URL of the webservice coincides with your own configuration!

If the command worked... you should now see a new folder structure in your working folder, called org/tempuri. Using Explorer, take a look inside the folder and you will see a bunch of .java files. These build the core classes for accessing your .net webservice. The next step is to build a java console application that will use these classes to call a webservice method. Create a text file in the org\tempuri\ folder called GetEmployees.java and paste in the following code:
package org.tempuri;
import java.util.*;
public class GetEmployees{
public static void main(String [] args){
try{
Employee[] employees;
Service1Locator sloc = new Service1Locator();
Service1Soap sport = sloc.getService1Soap();
employees = sport.getEmployees();
System.out.println("Number of Records: " + employees.length);
List list = Arrays.asList(employees);
for (int i=0; i<employees.length; i++ ){
System.out.println(employees[i].getLastName());
}
}
catch(Exception e){
System.out.println(e.getMessage());}
}
}
Save this file, then go back to your command prompt window and type in Ant, hit Enter. Hopefully you will see the words "BUILD SUCCESSFUL". Finally let's test out our little java application. Type in java org.tempuri.GetEmployees. You should see a list of lastnames. If so, then you have successfully completed part 2.

I would like to mention that even doing this a 3rd time myself I still encountered a multitude of errors. If you do have problems, just post it here and I'll help as best I can.

Flex to .Net via Flex Data Services - Part One

This article, promised a long time ago, half done, has been lying in my documents folder for some time now, just collecting dust. I decided to get back at it and get this badboy blogged. Because of time constraints I'm just going to briefly skim over this first part. Those of you that are familiar with .Net webservices should find most of the code self explanatory.

A few things worthy of mention. I had a heck of time getting the .net webservice to work properly with Java Axis - it took a lot of tweaking the method attributes before it worked. My primary goal was to make sure my webservice was not passing back simply a collection of generic Objects, but rather a collection of say Employee Objects. Employee objects would also then be recognized as such by the Java client, which makes coding on the Java side somewhat easier.

You can find the code HERE. It's a zip file containing the webservice I will be using thoughout this series of articles. I've kept most of the default namespaces and left the code fairly simple. For this project I am using the Employees table in the Northwind databaes and am connecting using a trusted connection.

Please download the code and get the webservice up and running. In the next part I'll show you how to consume the webservice using a Java client.

Monday, November 13, 2006

DataGrid Search with Highlighted Matches

Today's challenge: Give the user the ability to search a Datagrid and then simply highlight the matching rows in the Datagrid. The first problem was figuring out how to programmatically highlight specific rows in a datagrid, and after some googling I stumbled upon Mike Nimer's blog entry CustomRowColorDataGrid Component. He has basically extended the DataGrid to allow for a way to manipulate row appearance based on data, and he's taken it one step further than other examples by externalizing the function from the custom class. Very cool.

The second part is homegrown. I basically have a Hits Array and I use a cursor to search the DataProvider for matches. Any matched record is added to my Hits array. Then when the CustomRowColorDataGrid calls my rowColorFunction, it checks to see whether the item of the current row is contained within my Hits Array, and passes back the highlighted color.

Here's a link to the sample code. Right click the app to get the source!

Thursday, November 09, 2006

Sizing Blues - Using Canvas to keep it under Control

I find a spend a great deal of time trying to get all the components of my application to play nice with one another. But every now and then, I'll get that nasty vertical or horizontal scroll bar on my application, when I'm quite sure that I've been using percentages for widths and heights throughout.

So then the search begins, from the top layer, the application, I start to give different background colors to different containers, and each time I color a container I assign it a fixed size. Step by step, layer by layer, I will eventually find the component that isn't playing along. Most of the time it's either some form controls where the sum of their sizes is causing the percentage amounts to be ignored, or it's an image that's too big.

One of the more common pitfalls is adding a form container to an HBox or VBox. In this case, if the controls are too wide or too tall, the form will simply push out the rest of the containers in the app. Of course I could constrain the size of the form or the container of the form, however then I risk losing the cool auto-grow features of flex. Yet if the screen size is too small, I'd like the scrollbars to be restricted to the container closest to the source of the error.

What seems to work best, I find, is wrapping controls that risk causing this distortion inside a Canvas control. So, in other words, instead of:
<mx:HBox width="100%"><mx:Form width="100%'>...
I would suggest using:
<mx:HBox width="100%"><mx:Canvas width="100%"><mx:Form width="100%">...
The Canvas will popup scroll bars and contain the distortion, but will also autogrow with the app.

You can find an example of a little app I wrote that demonstrates this concept here. Right click on the app to get the source code.

Wednesday, November 08, 2006

Channel.Connect.Failed in locally compiled FB App

Just a quick post today. Recently I've had to switch over to building FDS Flex Projects in Flexbuilder that are set to "compile application locally in Flexbuilder". This is just in case the next update to Flex only covers Flex and not FDS - upgrading my fds projects will be so much easier.

Now for the most part things work fine but the other day a fellow coder asked for my help getting a remote object call (<mx:RemoteObject>) working. The call worked fine if he opened the .mxml file but would fail if he opened either the .html wrapper or the .swf directly. Since the only real difference between those two scenarios is where the compilation occurs, I figured it had to be a compile line argument missing from the Flexbuilder project configuration.

In case anyone else runs into this, just make sure your compiler arguments include the -context-root /contextroot parameter... for example this would be -context-root /flex in http://localhost:8700/flex/myApp/main.html

More information about this can be found in the docs here.

Tuesday, November 07, 2006

ListCollectionView: Different perspectives on data

In a recent project one of the challenges was to display data to the user in a variety of different ways with little overhead. In the .net world we used DataViews to create multiple perspectives on the same DataSet. This included sorting and filtering the data without needing multiple copies of the data in memory. When I first started snooping around in Flex I figured there had to be a way to get the same functionality, and soon I found the ListCollectionView.

As a side note, I'll soon start putting running samples up here with source code, just been too lazy to ftp them up to my server. Anyway let's dig in to the code. First we have a handy dandy xml data file:

<?xml version="1.0" encoding="UTF-8"?>
<items>
<item name="Item 1" quantity="2" price="20.00" size="small"
rebate="yes" discontinued="yes" />
<item name="Item 2" quantity="4" price="10.00" size="medium"
rebate="no" discontinued="yes" />
<item name="Item 3" quantity="4" price="25.00" size="small"
rebate="no" discontinued="no"/>
<item name="Item 4" quantity="1" price="5.00" size="extra-large"
rebate="no" discontinued="yes" />
<item name="Item 5" quantity="3" price="30.00" size="small"
rebate="yes" discontinued="yes" />
<item name="Item 6" quantity="4" price="17.00" size="medium"
rebate="no" discontinued="yes" />
<item name="Item 7" quantity="1" price="12.00" size="extra-large"
rebate="yes" discontinued="no"/>
<item name="Item 8" quantity="4" price="22.00" size="extra-large"
rebate="yes" discontinued="yes" />
</items>
Then we have to load the xml into an arraycollection, however several of the boolean properties will need to be reworked during the data load. Here's what that looks like:
<mx:HTTPService id="itemConn" url="items.xml"
useProxy="false" result="resultItemHandler(event)"/>
...

private function resultItemHandler(event:ResultEvent):void {
var source:ArrayCollection =
itemConn.lastResult.items.item as ArrayCollection;
var cursor:IViewCursor = source.createCursor();
var result:ArrayCollection = new ArrayCollection();
while (!cursor.afterLast){
var currentObj:Object = cursor.current;
var cv:String = currentObj["rebate"];
cv = cv.toUpperCase();
var newValue:Boolean = false;
if (cv == "YES" ){
newValue = true;}
currentObj["rebate"] = newValue;
cv = currentObj["discontinued"];
cv = cv.toUpperCase();
newValue = false;
if (cv == "YES" ){
newValue = true;}
currentObj["discontinued"] = newValue;
result.addItem(currentObj);
cursor.moveNext();
}
itemsIS = result;
}
So now we have an ArrayCollection of itemsIS, let's add a DataGrid and set the dataprovider to this collection:
<mx:DataGrid id="itemDG" dataProvider="{itemsIS}"
editable="false" width="100%" height="100%"/>
The next step requires a filtered view that will only show discontinued items, so we're going to add a LinkButton
<mx:LinkButton label="Discontinued" click="handleDisc()"/>
and then add the following code to our script section:
[Bindable]
private var itemsDiscontinued:ListCollectionView;
public function handleDisc():void{
itemsDiscontinued = new ListCollectionView(itemsIS);
itemsDiscontinued.filterFunction =
function ( item:Object):Boolean {return item.discontinued};
itemsDiscontinued.refresh();
itemDG.dataProvider = itemsDiscontinued;}
In this function we are creating a ListCollectionView that wraps our itemsIS collection. Then we create and set the filter function inline, and call a refresh(). Then we set the dataProvider of the datagrid to our ListCollectionView. You could add any number of "views" on the data in this way, and then let the user switch quickly between them.
Here's the code in action

Thursday, November 02, 2006

Flash 8, Flex 2, LocalConnection

Well today wasn't fun. I'm not huge into graphics or Flash for that matter. Sure I've done the occasional Flash movie but generally I stay away from things that are not code heavy. The challenge today was to create a clickable imagemap. Now from what I've been able to gather there's really no simple way to insert an Image in Flex and then draw different clickable regions on it. A good example of this would be a clickable interactive map, something we see very often. This little tutorial is directed at those of you that are as flash clueless as I am.

Ok let's get to it. The quickest way to do this is to create a clickable button in Flash 8 and then add some code to it:

1. Create a new Flash Document.
2. Choose File > Import to Library. This will import an image that will become a button. Locate the image and click Open. The image will be saved in the Library.
3. Find the image in the right Column list. Select the image with the Arrow tool. Drag it to your stage.
4. Right click on image, Choose Convert to Symbol from the popup menu. Name the symbol "button", choose Button from the Behavior list and click OK.
5. Right click on the image. Choose Actions from the pop-up menu.
6. This opens up the action pane. Now this next step is important, MAKE SURE you have Script Assist ON!
7. Paste in this code:

   outgoing_lc  = new LocalConnection();
outgoing_lc.send("lc_name", "helloFlex")
delete outgoing_lc;
8. If Script Assist is on you should see it wrap the code into a nice on (release) {}function.
9. File Export - Export Movie .. at bottom of properties dialog box make sure you change it to Access Network Only (this is default for Flex apps and both swf's must be the same or you get a nasty error). Call it test.swf.

That pretty much sums up the Flash part of this little exercise. Once you have your test.swf file, drag that into your Flex project. Then embed it in your mxml application as so:
<mx:SWFLoader source="test.swf" x="10" y="10"/>
Then in the script section add the following code:
private var fromSWF:LocalConnection;   
private function initApp() : void{
fromSWF = new LocalConnection();
fromSWF.client = this;
fromSWF.connect("lc_name");}
public function helloFlex() : void{
mx.controls.Alert.show("hello from flash");}
Save and Run. It should work. There's a few far more complex examples of this out there on the web like http://weblogs.macromedia.com/pent/archives/flex_2/index.cfm - scan down the page for "Using ActionScript 2 SWFs with Flex 2", which ultimately helped me figure this out. Again I like to break it down to its simplest form, and build everything else up from there.