Flex and Javascript - Simple Interaction

By Paranoid Ferret | 2008-03-26

This tutorial will show you how to call javascript functions from flex and flex functions from javascript. There is a really nice example of the use of this on Google Finance, as well as a number of other google sites.

The application below gives you a little taste of what can be done between javascript and flex. You can add people to the flex application using the small form on the bottom right through javascript, and if you select a person in the flex app and click the "Javascript Display" button you will get the info back out to the small form on the top right.



Data coming into Javascript
Name: 
Age: 
Sex: 
Data sending from Javascript
Name:
Age:
Sex:

The very first thing to do is to set up a basic flex application - the code below represents close to the simplest one possible. This sets up an application with specified height and width and also adds the view source option to the movie, with the source file specified by "viewSourceURL" attribute. 

Code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" width="482" height="348"
viewSourceURL="../files/JSTutorial.mxml">
</mx:Application>

 The next step in the process is to setup a simple flex interface with no functionality built in. We put a panel into our application and give it a title. Next we add the DataGrid object which we will bind our data to. In the DataGrid we add a couple columns (DataGridColumn), take notice to the dataField names as they will define the keys for our incoming data. So in this case our data coming in should have values from 3 keys: "Name", "Age" and "Sex". We also add a button, which will later be used to call a Javascript function on our page. Lastly we add a label to give us a quick status update when sending the data to Javascript. We also give each of the ui components an id so that we can refer to them later. All this code goes inside the application tags.

Code:
<mx:Panel id="pnlMain" x="10" y="10" width="462" height="328" 
layout="absolute" title="Simple Javascript Interaction">
<mx:DataGrid id="dgPeople" x="10" y="10" width="422"
height="229">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="Name"/>
<mx:DataGridColumn headerText="Age" dataField="Age"/>
<mx:DataGridColumn headerText="Sex" dataField="Sex"/>
</mx:columns>
</mx:DataGrid>
<mx:Button x="10" y="256" label="JavaScript Display"
id="butJSDisplay" />
<mx:Label x="149" y="260" id="lblMessage"/>
</mx:Panel>

At this point you should be able to compile and run to see your small interface.

Ok, next up after this is just getting some data into our DataGrid and we are going to do this with a small bit of Actionscript. First, we attach a function call to our DataGrid's initialize event. So our new DataGrid opening tag looks like the one below.

Code:
<mx:DataGrid id="dgPeople" x="10" y="10" initialize="initDG()" 
width="422" height="229">

  Now to go along with the event we need to write the actual function that it is referring to. First thing we need to do is add a script tag to our application. Then we can start writing the function. We also add an import call so that we can use ArrayCollection.

Inside the function "initDG" we do a little bit of data creation. We create an array for the data, and then push a few items onto the array - we use {key1: "value1", key2: "value2"...} syntax to add an associative array for each item. Now we need to make sure our keys match the ones we used for our DataGridColumns so the DataGrid knows what columns to associate with what values. Next thing done is to create an ArrayCollection to bind to and stick the array in it. And last thing we do is bind our dgPeople (DataGrid) to it and set the initial selected index. So this gives us the following code for our script and this code goes right below the application opening tag.

Code:
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
public function initDG():void
{
var people:Array = new Array();
people.push({Name: "Charlie", Age: "23", Sex: "Male"});
people.push({Name: "Brandon", Age: "23", Sex: "Male"});
people.push({Name: "Mike", Age: "23", Sex: "Male"});
people.push({Name: "Caroline", Age: "23", Sex: "Female"});
var peopleCollection:ArrayCollection =
new ArrayCollection(people);
dgPeople.dataProvider = peopleCollection;
dgPeople.selectedIndex = 0;
}
]]>
</mx:Script>

Ok you again should be able to compile and run the application, and now there should be data in your DataGrid. Next up is sending this data to Javascript. This is pretty easy to do. To start off, we need to add a click event to our button to call a Actionscript function. Below is the new modified button tag. Now to make this button work we need to add that Actionscript function. This starts to get into the fun stuff now - to call Javascript functions in Flex we us the External Interface api. You can learn more information about it on the Flex Documentation Site . Basically it allows us to build wrappers to call Javascript functions from Flex and vice versa. We add our Actionscript function in the Script tags and we also need to add an import statement for "flash.external.*". There is not much to the function itself: first we check if there is a External Interface available (basically this makes sure we are in a html page). If we are we can make the magic happen, with a call to ExternalInterface.call we can send information to a Javascript function called "displayPerson" whose argument is the selectedItem in our DataGrid. Now this should send an array of data of the selected item to Javascript. To learn more about what can be sent to Javascript check out this Adobe Documentation page. Last we update the status label saying that we sent the data. Otherwise, if the external interface is not available we display an error message in the label. Now our script tag looks like this:

Code:
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import flash.external.*;
public function initDG():void
{
var people:Array = new Array();
people.push({Name: "Charlie", Age: "23", Sex: "Male"});
people.push({Name: "Brandon", Age: "23", Sex: "Male"});
people.push({Name: "Mike", Age: "23", Sex: "Male"});
people.push({Name: "Caroline", Age: "23", Sex: "Female"});
var peopleCollection:ArrayCollection =
new ArrayCollection(people);
dgPeople.dataProvider = peopleCollection;
dgPeople.selectedIndex = 0;
}
public function jsDisplayPerson():void
{
if (ExternalInterface.available) {
ExternalInterface.call("displayPerson",
dgPeople.selectedItem);
lblMessage.text = "Data Sent!";
} else
lblMessage.text = "Error sending data!";
}
]]>
</mx:Script>

Now we get to write a bit of javascript code to display the person that is being sent to us from flex. So in a set of Javscript tags we create our displayPerson function - remember, the name has to match perfectly to what we told ExternalInterface.call function in flex to call. The first thing we do in our Javascript function is make sure we are not getting a null object and if it is null we display an alert. Then we just use the object passed as a javascript object and reference the appropriate datagrid columns using javascript object property syntax. All I do with them is display the values in a few table cells, but at this point you can do anything with the data that you want. So in our html script tags we get some code like this:

Code:
function displayPerson(person)
{
if(person == null){
alert("Please select a person, or maybe I screwed up.");
}
else{
document.getElementById('nameDisplay').innerHTML =
person.Name;
document.getElementById('ageDisplay').innerHTML = person.Age;
document.getElementById('sexDisplay').innerHTML = person.Sex;
}
}

So then if we add a little bit of html that looks like what I have below the javascript from above should work and populate a few table cells. You should be able to embed the flex movie, but notice we need to have the allowScriptAccess property in our object and embed tags for the movie before the "Javascript Display" button works. You can check the source of this page if you need more info.

Code:
<table>
<tr>
<td>Name:</td>
<td id="nameDisplay" style="width:150px;"> </td>
</tr>
<tr>
<td>Age:</td>
<td id="ageDisplay" style="width:150px;"> </td>
</tr>
<tr>
<td>Sex:</td>
<td id="sexDisplay" style="width:150px;"> </td>
</tr>
</table>

Now you should be able to call any javascript functions from your Flex applications using the above technique. The next thing we are going to do is setup our flex application so that flex functions can be called by javascript via ExternalInterface. The first thing we need to do is add some code to our application startup to initialize what flex functions will be accessible through external calls. We add code to our application tag to fire a function on the initialize event. The following code is the new application tag, it simply calls an actionscript function when the initialize event is fired.

Code:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
layout="absolute" width="482" height="348"
initialize="initApp()"
viewSourceURL="../files/JSTutorial.mxml">

Now we can write the initApp actionscript function. This function will check if the ExternalInterface is available to keep from getting errors when running in the regular 'ole flash player. Then it adds a callback for an actionscript function, this function will be referred to externally as "addPerson" (the first argument to addCallback) and will map to the internal function called addPerson (the second argument to addCallback). The initApp function should then look like the code below, which is added in our actionscript script tags.

Code:
public function addPerson(name:String, age:String,
sex:String):void
{
(dgPeople.dataProvider as ArrayCollection).addItem(
{Name: name, Age: age, Sex: sex});
}

Now that we have our mxml and actionscript written we can create some html and javascript to use our new external flex function. The javascript function we are going to make will just grab a few values from a couple input items on our html page and then we will call the flex function using those values as arguments. In order to call the flex function, we use another function called getFlexApp('FlexJSTutorial'). I will go over this in just a second. So what we add to our javascript tag is the following.

Code:
function addPerson()
{
var name = document.getElementById('txtName').value;
var age = document.getElementById('txtAge').value;
var sex = document.getElementById('selSex').value;
getFlexApp('FlexJSTutorial').addPerson(name, age, sex);
}

So now you are wondering about this getFlexApp function - well this is the function that actually returns us the flex app that is on the page. I originally had an issue getting the embedded object but found the suggested solution on Adobe's site here. This function takes into account various types of browsers. The one thing you need to do is make sure your object and embed tags for your flash movie have an id and name. These are required for getting the flex application. So the following function goes in the javascript tag:

Code:
// This function returns the appropriate reference, 
// depending on the browser.
function getFlexApp(appName) {
if (navigator.appName.indexOf ("Microsoft") !=-1) {
return window[appName];
} else {
return document[appName];
}

The last thing we are going to do is create the html inputs that we use in our javascript addPerson function. This includes two input text boxes and one input select with two options one for "Male" and one for "Female". Nothing special about these, also we have a button which we use to call our javascript function when the onclick event occurs. I put all these in a table for a little bit of organization:

[code] <table style="border-spacing:5px;"> <tr> <td style="border-style:none;padding:0px;">Name:</td> <td style="border-style:none;padding:0px;"> <input id="txtName" type="text" /> </td> </tr> <tr><td style="border-style:none;padding:0px;">Age:</td> <td style="border-style:none;padding:0px;"> <input id="txtAge" type="text" /> </td> </tr> <tr><td style="border-style:none;padding:0px;">Sex:</td> <td style="border-style:none;padding:0px;"> <select id="selSex" style="width:100px;"> <option value="Male">Male</option> <option value="Female">Female</option> </select> </td> </tr> <tr> <td colspan="2" style="border-style:none;padding:0px;"> <input type="button" id="butAddPerson" onclick="addPerson()" value="Add Person" /> </td> </tr> </table> [/code]

And that about wraps it all up. If you have any questions at all please leave them in a comment, and I will try and help you out. Below is the full code for the mxml file.

[code] <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="482" height="348" initialize="initApp()" viewSourceURL="../files/JSTutorial.mxml"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import flash.external.*; public function initDG():void { var people:Array = new Array(); people.push({Name: "Charlie", Age: "23", Sex: "Male"}); people.push({Name: "Brandon", Age: "23", Sex: "Male"}); people.push({Name: "Mike", Age: "23", Sex: "Male"}); people.push({Name: "Caroline", Age: "23", Sex: "Female"}); var peopleCollection:ArrayCollection = new ArrayCollection(people); dgPeople.dataProvider = peopleCollection; dgPeople.selectedIndex = 0; } public function addPerson(name:String, age:String, sex:String):void { (dgPeople.dataProvider as ArrayCollection).addItem( {Name: name, Age: age, Sex: sex}); } public function initApp():void { if (ExternalInterface.available) ExternalInterface.addCallback("addPerson", addPerson); } public function jsDisplayPerson():void { if (ExternalInterface.available) { ExternalInterface.call("displayPerson", dgPeople.selectedItem); lblMessage.text = "Data Sent!"; } else lblMessage.text = "Error sending data!"; } ]]> </mx:Script> <mx:Panel id="pnlMain" x="10" y="10" width="462" height="328" layout="absolute" title="Simple Javascript Interaction"> <mx:DataGrid id="dgPeople" x="10" y="10" initialize="initDG()" width="422" height="229"> <mx:columns> <mx:DataGridColumn headerText="Name" dataField="Name"/> <mx:DataGridColumn headerText="Age" dataField="Age"/> <mx:DataGridColumn headerText="Sex" dataField="Sex"/> </mx:columns> </mx:DataGrid> <mx:Button x="10" y="256" label="JavaScript Display" id="butJSDisplay" click="jsDisplayPerson()"/> <mx:Label x="149" y="260" id="lblMessage"/> </mx:Panel> </mx:Application> [/code]

Lots of thanks and credit to the folks over at http://blog.paranoidferret.com/ for this tutorial!