09 February 2010

GWT Wrapper for Flex components

I am working on a project that integrates a Flex-based graph component into a GWT application. Using Flash / Flex and GWT in combination is tricky when you want to call both Flash/Flex from GWT and GWT from Flash/Flex, because there are two bridges involved: the GWT Java Script Native Interface (JSNI) and the Flex external interface.

This tutorial is the first part of a series of blog posts about using Flex components in GWT with the library gwt2swf:

1. GWT Wrapper for Flex components
2. Notifying GWT when a Flex widget is loaded [Demo, Source Code]
3. Handling Flex events in GWT [Demo, Source Code]

I will explain how to create a GWT adapter around a Flex component that enables calling Flex from GWT. The code will use the library gwt2swf. This post will be pretty strait forward and matches the gwt2swf docs for most part, but will include sample GWT and Flex code. Essentially, I will create a subclass of SWFWidget (part of gwt2swf) that wraps all the Flex specifics and can be used from GWT. This approach enables the use of several swf widgets from GWT at the same time.

To create a sample Flex component, I used the following steps:

1. Create a new Flex Project (e.g. in Flex Builder)

2. Add a creationComplete handler (here: init) to the mxml header

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="init()">


3. Remove the padding from the Flex component & set the background color in the mxml header

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="init()"
backgroundColor="0xffffff"
paddingBottom="0" paddingLeft="0"
paddingRight="0" paddingTop="0">


4. Add the init method and a addJsCallback method with code that I shamelessly stole from my colleague Chris Callendar, who helped me getting this to work and who has an awesome Flex blog, http://flexdevtips.blogspot.com/.

public static function addJSCallback(jsFunctionName:String, flexFunction:Function):void {
try {
if (ExternalInterface.available) {
ExternalInterface.addCallback(jsFunctionName, flexFunction);
}
} catch (error:SecurityError) {
trace("Couldn't add javascript callback: " + error);
}
}


5. Add a text field to the flex component as an example element. We will change the contents of the text field from GWT. The width is set to 100%, so that the widget resizes based on the surrounding HTML element. For other widgets, e.g. a table, you might want to set the height to 100% as well.

<mx:Text id="textWidget" width="100%"/>


6. Add a displayText function and register it as a callback for JavaScript access under the name "displayText" in the init() method.

public function displayText(text:String):void {
textWidget.text = text;
}

private function init():void {
addJSCallback("displayText", displayText);
}


7. The complete .mxml file should look similar to this by now:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="init()"
backgroundColor="0xffffff" paddingBottom="0" paddingLeft="0"
paddingRight="0" paddingTop="0">
<mx:Script>
<![CDATA[
public function displayText(text:String):void {
textWidget.text = text;
}

private function init():void {
addJSCallback("displayText", displayText);
}

public static function addJSCallback(jsFunctionName:String, flexFunction:Function):void {
try {
if (ExternalInterface.available) {
ExternalInterface.addCallback(jsFunctionName, flexFunction);
}
} catch (error:SecurityError) {
trace("Couldn't add javascript callback: " + error);
}
}
]]>
</mx:Script>
<mx:Text id="textWidget" width="100%"/>
</mx:Application>


8. Build the Flex project and copy the generated .swf file into the GWT project. It should be under the 'public' folder below the folder that contains the .gwt.xml file, e.g. in my case as 'src/de/larsgrammel/blog/flexgwt/public/Test.swf'

That's it on the Flex side. Here is the list of steps I followed on the GWT side:

1. Download gwt2swf from http://sourceforge.net/projects/gwt2swf/ (I use version 0.6.0 in the example presented here)

2. Extract the gwt2swf.jar and add it project's buildpath

3. Add gwt2swf to your project's .gwt.xml file:

<inherits name='pl.rmalinowski.gwt2swf.GWT2SWF' />


4. Create a subclass of SWFWidget (here: SampleFlexWrapperWidget) that sets swf file by specifying the swf source path in the constructor call. The first part of the swf source path is the module name, the rest the path below the 'public' directory including the .swf name.

public SampleFlexWrapperWidget(int width, int height) {
super("flexgwtintegration/Test.swf", width, height);
}


5. Add a displayText method to that class as a public interface on the Java side. This method calls a private static native method that resolves the SWF DOM element and calls the exposed Flex method:

private static native void _displayText(String swfID, String text) /*-{
$doc.getElementById(swfID).displayText(text);
}-*/;

public void displayText(String text) {
_displayText(getSwfId(), text);
}


6. The wrapper should look similar to this by now:

public class SampleFlexWrapperWidget extends SWFWidget {

private static native void _displayText(String swfID, String text) /*-{
$doc.getElementById(swfID).displayText(text);
}-*/;

public SampleFlexWrapperWidget(int width, int height) {
super("flexgwtintegration/Test.swf", width, height);
}

public void displayText(String text) {
_displayText(getSwfId(), text);
}

}


7. The wrapper can now be used in GWT, e.g. in the entry point class:

public class FlexGWTIntegration implements EntryPoint {

public void onModuleLoad() {
final Button sendButton = new Button("Send");
final TextBox nameField = new TextBox();
nameField.setText("GWT User");

final SampleFlexWrapperWidget flexWidget1 = new SampleFlexWrapperWidget(100, 50);
final SampleFlexWrapperWidget flexWidget2 = new SampleFlexWrapperWidget(100, 50);

RootPanel.get().add(nameField);
RootPanel.get().add(sendButton);

RootPanel.get().add(flexWidget1);
RootPanel.get().add(flexWidget2);

nameField.setFocus(true);
nameField.selectAll();

class MyHandler implements ClickHandler, KeyUpHandler {
public void onClick(ClickEvent event) {
displayTextInFlex();
}

public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
displayTextInFlex();
}
}

private void displayTextInFlex() {
flexWidget1.displayText(nameField.getText());
flexWidget2.displayText(nameField.getText() + " 2nd version");
}
}

MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
nameField.addKeyUpHandler(handler);
}
}


Using this approach, you can use the SWF wrapper inside GWT like any other GWT widget, except for a small loading delay. I will explain how to deal with delayed loading, events from Flex and others integration issues in future blog posts - stay tuned.

23 comments:

James Ward said...

This is awesome! Thanks for posting this!

lgrammel said...

Thanks, I am glad you liked it!

Chris Pollard said...

A nice solution to this problem.
Especially as for the product I work on I have control over both the GWT and SWF code.

Alas gwt2swf didn't appear early enough for me to use, so I created my own (inelegant) solution.

Steve Buikhuizen said...

Hi Lars, this could be a big timesaver for me. I have my own solution but its broken in IE8. Before I try out your wrapper, can you tell me what you think of this?

http://code.google.com/p/swfobject/

I'm curious to hear your opinion on which is better. Obviously yours is more GWT-esque but any other insights?

lgrammel said...

@Steve

I took a quick look at swfobject and it looks like a JavaScript wrapper for SWF code to me. I think this would be the way to go if your software is mainly written in javascript. gwt2swf is a GWT wrapper, which IMHO would be the better alternative if you write GWT code, because there would be less of a difference between using regular GWT widgets and Flash widgets.

kirru said...

this is exactly what i am looking for and doing a similar stuff like you. It is awesome :)

Mike said...

Thanks for the great tutorial!
Unfortunately I cannot get the javascript interface working. I followed your tutorial but always get the following error:

17:15:30.806 [ERROR] [gwt2swf] Uncaught exception escaped
com.google.gwt.core.client.JavaScriptException: (TypeError): mywf.sendXML is not a function

In this case "sendXml" is the method exposed via javascript. Do you have andy ideas, thoughts what I might be doing wrong? Thanks a lot!

lgrammel said...

@Mike JavaScript is case sensitive. I think using either only sendXML or sendXml in both the Flex and the GWT/Jsni code might fix this problem :-)

kavita said...

Thanx for ur such a wonderfull tutorial.but i m getting some error in dat..... plz help me.


[ERROR] Uncaught exception escaped
com.google.gwt.core.client.JavaScriptException: (TypeError): Object doesn't support this property or method
number: -2146827850
Thanx in advance!!!!

Mike said...

@lgrammel: Thanks for the tip. I double checked the spelling and everything seems fine. When I load the flash widget in a html page the JS interaction works fine (this is the original version, I have to migrate this to a gwt app). Do I have to do something special to enable the JavaScript access via GWT? I noticed that in the original html file there is an argument to the AC_FL_RunContent() call: 'allowScriptAccess','always'. Could this be something? As far as I know this has been generated when exporting via the Adobe FlashBuilder (or similar).

thanks a lot, Mike

lgrammel said...

@Mike
@kavita

Without seeing the actual source code, it is hard to figure out cause of the problems. It might be that the Java or the Flex methods are not correctly exposed as JavaScript, or that some method names are misspelled.

@Mike
There is nothing special you need to activate. The 'allowscriptaccess' parameter is automatically set by the gwt2swf library.

kavita said...

had pasted ur code only but d diff is dat i m using gwt-windows 1.7.0.1 inspite of gwt 1.4.is this problem arise due to this...........!!!!

lgrammel said...

The GWT Flex integration was tested with GWT 2.0 - there might be issues with older releases of GWT.

Mike said...

I was able to fix my problem with the 'sendXML is not a function' error: The problem was that the gwt code tries to call the method on the flash widget BEFORE this is completely initialized.

A workaround for this is to use the javascript setTimeout method or a GWT Timer to delay the execution for 1sec or so. Then it worked for me.

I am aware that in part 2 there is a much nicer solution presented, but because I cannot modify the swf using setTimeout was my last resort to make it work. Or does anyone have a better idea?

lgrammel said...

@Mike Unfortunately, things are way more difficult when you don't have control over the swf file. Your solution sounds like a solid workaround to me - the only thing that could be problematic are different loading times depending on download speed and swf size, so you might need to catch errors and retry the delayed execution in a loop.

Scott said...

Hi Lars,

Great steps! I was able to combine a Eclipse GWT and Flex project into one. There's two sources locations in the Eclipse project: src an flex_src. src contains the Java GWT client and server code, and flex_src contains the code in the swiff. This allowed me to save the step in your directions where you copied the SWF file into the GWT project and it also allows debugging the Java server, GWT client code, and Flex code simultaneousness.

said...

Thanks for your detail tutorial.

SanQi said...

Thanks for your detail tutorial.It helped me a lot.

nino said...

Hello there, I m Alain a software ingeneer based in Munich.I m the author of the library located at :http://code.google.com/p/gwt4air/
Just wanted to let u know that i used a similar to build a Java API for FLEX so one could write a Flex application entirely in Java using GWT. Maybe you can check it out and let me what you think.
Regards,

Alain

Houcem eddine Berrayana HomePage said...

That's exactly what i've been looking for. Thank you for your sharing

GwtCodeGuru said...

Thanx for ur such a wonderfull tutorial.It help me alot but one problem arise..........
when swf(flex) call in gwt eclipse project it call successfully but when we deploy in tomcat server(war file) swf not load.any guess.......

lgrammel said...

@GwtCodeGuru The first thing that comes to mind is checking that the flash file is included in the war, and that it is accessible via the browser URL. However, I don't know enough about the latest Tomcat versions to make a better guess. I suggest posting the question with a few more details and explanation on Stackoverflow, which is a great place for such things.

GwtCodeGuru said...

ok.Thanks for reply.....