Creating a servlet with ServletPathServlet and ServletPathDispatcher in ATG 9

Imagine that you want to create a servlet in ATG 9 that processes a POST request from an external website. Normal ATG Form Handlers are not of any use here because they use dynamic url parameters to be invoked. What you need is a plain old Java servlet. You can create one in ATG using two methods:

Option 1: What you would do in any other Application server without ATG

  • Create a class that extends javax.servlet.http.HttpServlet
  • Implement the doPost() and doGet() methods.
  • Add servlet mapping to web.xml

The disadvantage of the above method is that if you need access to the DynamoHttpRequest, DynamoHttpResponse objects, you need to extract them from the request using atg.servlet.ServletUtil.

public class ExampleServlet extends HttpServlet
{

    public doGet(HttpServletRequest request, HttpServletResponse response) {
       DynamoHttpServletRequest dRequest = ServletUtil.getDynamoRequest(request);
       DynamoHttpServletResponse dResponse= dRequest.getResponse();
      ...
     }
   public doPost(HttpServletRequest request, HttpServletResponse response) {
      ...
    }
}
web.xml
 <servlet>
     <servlet-name>exampleServlert</servlet-name>
     <servlet-class>com.spltech.co.uk.ExampleServlet</servlet-class>
 </servlet>

<servlet-mapping>

 <servlet-name>exampleServlet</servlet-name>

 <url-pattern>/exampleServlet</url-pattern>

 </servlet-mapping>

Option 2: The ATG Way – Using ServletPathServlet and ServletPathDispatcher

If you are developing in ATG, chances are is that you want to do things the ATG way. Here is how you do it:

  • Create a property file for /atg/dynamo/servlet/pipeline/ServletPathServlet. Override the servletPaths property and add the path that you want the servlet to be called for (e.g. /complete-payment).
  • Create a property file for /atg/dynamo/servlet/pipeline/ServletPathDispatcher. Override the dispatcherServiceMap property and add the mapping between the path that you added in the previous step and the ATG component that you want to be called.
  • Create a class that extends DynamoServlet. Override the service(DynamoHttpServletRequest req, DynamoHttpServletResponse resp) method and place here the code that does the business logic for your servlet.
  • Restart ATG and try to access your servlet at /dyn/complete-payment. In this case dyn is the default prefix added to all servlet paths called by the ServletPathDispatcher.

ServletPathServlet.properties:

##############################
#
# This splits the pathInfo of an incoming request into
# a servletPath/pathInfo combination, if the pathInfo
# starts with one of the servletPaths.  You can later
# dispatch off one of these paths using the
# ServletPathDispatcher component
#

servletPaths+=
			/example-servlet

ServletPathDispatcher.properties:

#
# This dispatcher will send any requests with a
# servletPath of "/exittracking" to the ExitTracking
# component.  If you add more servletPaths here, you
# should also remember to add those paths to the
# ServletPathServlet
#
dispatcherServiceMap+=
					/example-servlet=/uk/co/spltech/servlet/ExampleServlet

ExampleServlet.java:

/**
 * Example Servlet  
 * @author spltech
 *
 */
public class ExampleServlet extends DynamoServlet {
    public void service(DynamoHttpServletRequest req, DynamoHttpServletResponse res)
        throws ServletException, IOException
    { 
       ....
    }
}

ExampleServlet.properties:

$class=spltech.commerce.order.purchase.PaymentResponseServlet
$scope=global
...

Integrating Adobe Flex with Struts 2 and JBOSS Using BlazeDS

The first step of this process will be to able to run BlazeDS on JBOSS without struts 2. If that is not working yet for you please do that first.
The main reason that Struts 2 and BlazeDS out of the box fail to work together is because most configurations of Struts 2 intercept all HTTP requests to a web application. This is because of this piece of configuration in web.xml:

<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
 <filter-name>struts2</filter-name>
 <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
 </filter>

This is a java filter that intercepts all requests and responses. This is a problem because most instructions on how to setup BlazeDS ask you declare the following servlet in your web.xml:

<!-- MessageBroker Servlet -->
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<display-name>MessageBrokerServlet</display-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>

However this will never work with Struts 2 unless you restrict the url pattern to something less than /*. Filters seem to be executed always before any servlet declared in web.xml.

To solve this problem, instead of declaring the MessageBrokerServlet as a servlet lets declare it as filter in the filter chain. Filters declared in a filter chain are always executed in the order that they are declared in web.xml. For that reason make sure that this filter is always called before the Struts 2 filter(FilterDispatcher). If our filter determines that there is an incoming request that needs to be handled by BlazeDS, we call the MessageBrokerServlet, and we prevent any more filters in the chain to be called. If the request is not for BlazeDS the filter will not be called.

package uk.co.spltech.web.filter;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.mock.web.MockServletConfig;

import flex.messaging.MessageBrokerServlet;

/**
* The MessageBrokerServlet has a lower priority than Filters, regardless of the url-pattern
* we use. For that reason I created a filter that directly extends the MessageBrokerServlet but
* behaves as a filter. This might be quite dodgy but that's the only way I found to integrate struts
* 2 and Adobe Flex/BlazeDS. Anyone who knows a simpler way please email me at info at spltech.co.uk
*
* @author Armindo Cachada
*
*/
public class FlexFilter  extends MessageBrokerServlet implements Filter{

public void destroy() {
super.destroy();
}

/**
* If this method is called the parent service method of the MessageBrokerServlet is called,
* which does whatever BlazeDS needs to do to communicate with the flex client. Note Here
* that any subsequent filter will not be called because I am not invoking filterChain.doFilter/
* That is on purpose, because if it does, the normal struts 2 action mapping mechanism will be called.
*
*/
public void doFilter(ServletRequest servletrequest,
ServletResponse servletresponse, FilterChain filterchain)
throws IOException, ServletException {
this.service((HttpServletRequest)servletrequest, (HttpServletResponse)servletresponse);
}

/** Note the use here of MockServletConfig. This utility class is available in the spring framework. It is  meant
to be used for testing but I am actually giving it a real purpose :)
*/
public void init(FilterConfig filterconfig) throws ServletException {
System.out.println("filter called");
MockServletConfig servletConfig = new MockServletConfig(filterconfig.getServletContext());
Enumeration filterParameters = filterconfig.getInitParameterNames();

while (filterParameters.hasMoreElements()){
String filterParameter = (String)filterParameters.nextElement();
System.out.println("Found parameter: " + filterParameter);
String value =filterconfig.getInitParameter(filterParameter);
servletConfig.addInitParameter(filterParameter, value);

}
super.init(servletConfig);
}

}




Add the following to your filter chain in web.xml:

<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>

<filter>
<filter-name>flex</filter-name>
<filter-class>uk.co.spltech.web.filter.FlexFilter</filter-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
</filter>

This solution did the job for me. Please let me know if it also works for you!

Native Drag and Drop in Adobe Air with example

In this tutorial I will teach you how to create a simple adobe air application with native drag and drop support using a file system manager as example, that can copy/move files dragged to/from the OS desktop.

What does it mean to create an application with native drag and drop support?
The standard drag & drop functionality provided in Adobe Flex only allows you to drag & drop interactive objects within the Flex application itself. However with Adobe Air, using the NativeDragManager allows you to drag & drop objects between the OS, or from other applications installed in your operating system and the Air app.

For example in Adobe Air you can create a simple mp3 player which accepts mp3 files dragged directly from the desktop into the mp3 player window. Or like in the example included you can create an adobe air app that copies files between the OS desktop and the air app window.

Native drag and drop, like the standard flex drag and drop is divided has 3 phases – initiation, dragging and dropping.

The initiation is when the user clicks on an interactive component while keeping the mouse button pressed. In this case the component that the user clicks on is the drag initiator. Any air component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the native drag & drop operation. The event handler will then create a Clipboard object which contains data related to the object being dragged. For example if you are dragging a file between two components, the Clipboard object will contain a reference to a File object. Once the Clipboard object is constructed the NativeDragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the Clipboard object, the third argument is an optional drag image/proxy image that can be displayed while dragging the object on the screen(e.g. a semi-transparent version of the drag initiator).

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a nativeDragEnter event is raised. The event handler that handles this event should check whether the Clipboard object contains data that is of an acceptable format.  If that’s the case then NativeDragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

If the user actually releases the mouse while hovering over a drop target and that target has accepted the drag object by invoking NativeDragManager.acceptDragDrop(), the nativeDragDrop event is dispatched. The event handler registered with this event then does what it is supposed to do, like copying a file from the desktop to a destination directory, or play an mp3 file.

Below is the source code for our Air File Manager with drag and drop support:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" width="1200" height="400">
<mx:Script>
<![CDATA[
import mx.events.DragEvent;
import mx.events.CloseEvent;
import mx.core.DragSource;
import mx.controls.Alert;
import mx.managers.DragManager;
import mx.core.UIComponent;
import mx.core.IUIComponent;
import mx.managers.CursorManager;
import flash.desktop.NativeDragManager;

/**
* This function handles the case where the user drags in an icon from the desktop
*
*/
private function handleNativeDragEnter(event:NativeDragEvent):void {

NativeDragManager.dropAction = NativeDragActions.COPY;
if(event.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)){
NativeDragManager.acceptDragDrop(InteractiveObject(event.currentTarget)); //'this' is the receiving component
}

}

/**
* We signal the start of the drag
*
*/
private function handleMouseDown(event:MouseEvent):void {
var fs:FileSystemDataGrid =FileSystemDataGrid(event.currentTarget);
if (fs.selectedItem != null) {
var transferObject:Clipboard = createClipboard(fs.selectedItem as File);
NativeDragManager.doDrag(InteractiveObject(event.target),
transferObject,
null,
new Point(0,0));

}
}

public function createClipboard( sourceFile:File):Clipboard {
var transfer:Clipboard = new Clipboard();
transfer.setData(ClipboardFormats.FILE_LIST_FORMAT,
new Array(sourceFile),
false);
// Standard file list format
return transfer;
}

/**
* In this case we copy the file to the folder specified by the fs
*
*/
private function handleNativeDragDrop(event:NativeDragEvent):void {
var files:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT, ClipboardTransferMode.ORIGINAL_PREFERRED) as Array;
var fs:FileSystemDataGrid = event.currentTarget as FileSystemDataGrid;

/**
* copies each file to the file system
*/
for each (var file:File in files) {

var fileDest:File = fs.directory.resolvePath(file.name)
file.addEventListener(Event.COMPLETE, refreshFileSystems);
// if the user has pressed the shift key then he just
// wants to copy the file
if (event.shiftKey)
{
this.copyFile(file, fileDest);

}
else {
this.moveFile(file, fileDest);
}

}

}

/**
* Refreshes the file system data grid after a copy/move operation
*
*/
private function refreshFileSystems(event:Event):void
{
fs1.refresh();
fs2.refresh();

}

/**
* Moves the file to a new location
*
*/
private function moveFile(fileOrig:File, fileDest:File):void {
if (!fileDest.exists) {
fileOrig.moveToAsync(fileDest);
}
else {
mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this,
function(event:CloseEvent):void {
// overwrite file
if (event.detail== Alert.YES) {
fileOrig.moveToAsync(fileDest, true);
}
else {
fs1.refresh();
fs2.refresh();
}
});
}

}

/**
* Copies the file to a new location
*
*/
private function copyFile(fileOrig:File, fileDest:File):void {
if (!fileDest.exists) {
fileOrig.copyToAsync(fileDest);
}
else {
mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this,
function(event:CloseEvent):void {
// overwrite file
if (event.detail== Alert.YES) {
fileOrig.copyToAsync(fileDest, true);
}
else {
fs1.refresh();
fs2.refresh();
}
});
}

}

/**
* Checks that there is at least one item selected and asks the user whether
* he really wants to delete the file.
*
*/
private function handleDelete(fs:FileSystemDataGrid):void {
mx.controls.Alert.show("Do you really want to delete this file? Please be aware that the delete actually deletes... not part of the demo.","Delete confirmation",Alert.YES|Alert.CANCEL,
this, function(event:CloseEvent):void {
deleteConfirmation(event, fs );
});
}

/**
* If the user really wants to delete the file we do what they ask
*
*/
private function deleteConfirmation(event:CloseEvent, fs:FileSystemDataGrid):void {
if (event.detail==Alert.YES) {
if (fs.selectedItem != null) {
var file:File= fs.selectedItem as File;
if (file.isDirectory) {
file.deleteDirectory(false);
}
else {
file.deleteFile();

}
fs.refresh();
}
}
}

]]>
</mx:Script>

<mx:Panel layout="vertical" height="100%" title="Copy From">
<mx:ControlBar >
<mx:FileSystemHistoryButton label="Back"  click="fs1.navigateBack()" enabled="{fs1.canNavigateBack}" itemClick="fs1.navigateBack(event.index)" dataProvider="{fs1.backHistory}"/>
<mx:FileSystemHistoryButton label="Forward" enabled="{fs1.canNavigateForward}" dataProvider="{fs1.forwardHistory}" itemClick="fs1.navigateForward(event.index)" dragStart="mx.controls.Alert.show('Drag Start')"/>
<mx:Button label="Up" enabled="{fs1.canNavigateUp}" click="{fs1.navigateUp()}"/>
<mx:Button label="Delete" enabled="{fs1.selectedItem!= null}" click="handleDelete(fs1)"/>

</mx:ControlBar>
<mx:FileSystemDataGrid id="fs1"  nativeDragEnter="handleNativeDragEnter(event)" mouseDown="handleMouseDown(event)" directory="{File.documentsDirectory}" nativeDragDrop="handleNativeDragDrop(event)">

</mx:FileSystemDataGrid>
</mx:Panel>
<mx:Panel layout="vertical" height="100%" title="Copy To">
<mx:ControlBar >
<mx:FileSystemHistoryButton label="Back" click="fs2.navigateBack()" enabled="{fs2.canNavigateBack}" itemClick="fs2.navigateBack(event.index)" dataProvider="{fs2.backHistory}"/>
<mx:FileSystemHistoryButton label="Forward" enabled="{fs2.canNavigateForward}" dataProvider="{fs2.forwardHistory}" itemClick="fs2.navigateForward(event.index)"/>
<mx:Button label="Up" enabled="{fs2.canNavigateUp}" click="{fs2.navigateUp()}"/>
<mx:Button label="Delete" enabled="{fs2.selectedItem!= null}"/>
</mx:ControlBar>
<mx:FileSystemDataGrid id="fs2" nativeDragEnter="handleNativeDragEnter(event)"  mouseDown="handleMouseDown(event)" nativeDragDrop="handleNativeDragDrop(event)"  directory="{File.applicationStorageDirectory}">

</mx:FileSystemDataGrid>
</mx:Panel>
</mx:WindowedApplication>




Click here to download the file manager with native drag and drop support

Adding Drag and Drag functionality in Adobe Air

In this tutorial I will teach you step by step how to use the out of the box drag & drop functionality provided by flex list controls.

Just some basic theory before we go into code. The drag & drop process has three stages – initiation, dragging and dropping.

The initiation is when the user clicks on a flex component and keeps the button pressed. In this case the component that the user clicks on is the drag initiator. Any flex component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the drag & drop operation. The event handler will then create a DragSource object which contains data related to the object being dragged. For example if you are dragging a file between two components, the DragSource object will contain a reference to a File object. Once the DragSource is constructed the DragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the DragSource object and the third argument is a reference to the mouse event that called the event handler. There is a fourth optional argument that allows to specify a drag proxy that may be used to represent the object being dragged on the screen(e.g. a semi-transparent version of the drag initiator)

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a dragEnter event is raised. The event handler that handles this event should check whether the DragSource object contains data that is of an acceptable format. If that’s the case then DragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

In this tutorial I will teach you step by step how to use the out of the box drag & drop functionality provided by flex list controls.

Just some basic theory before we go into code. The drag & drop process has three stages – initiation, dragging and dropping.

The initiation is when the user clicks on a flex component and keeps the button pressed. In this case the component that the user clicks on is the drag initiator. Any flex component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the drag & drop operation. The event handler will then create a DragSource object which contains data related to the object being dragged. For example if you are dragging a file between two components, the DragSource object will contain a reference to a File object. Once the DragSource is constructed the DragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the DragSource object and the third argument is a reference to the mouse event that called the event handler. There is a fourth optional argument that allows to specify a drag proxy that may be used to represent the object being dragged on the screen(e.g. a semi-transparent version of the drag initiator)

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a dragEnter event is raised. The event handler that handles this event should check whether the DragSource object contains data that is of an acceptable format.  If that’s the case then DragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

Fortunately for list controls most of this work is already done for you. Implementing drag & drop is very simple:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal">
     <mx:ArrayCollection id="source">
            <mx:Array>
            <mx:Object singer="Michael Jackson" />
            <mx:Object singer="Jennifer Lopez" />
            <mx:Object singer="Janet Jackson" />
            <mx:Object singer="Paul Mcarthney" />
        </mx:Array>
    </mx:ArrayCollection>

   <mx:ArrayCollection id="destination">
      <mx:Array>
          <mx:Object singer="Sheryl Crowe"/>
      </mx:Array>
   </mx:ArrayCollection>
   <mx:DataGrid dataProvider="{source}" dragEnabled="true" dragMoveEnabled="true"/>
   <mx:DataGrid dataProvider="{destination}" dropEnabled="true"/>
</mx:Application>

In the example above by setting dragEnabled to <b>true</b> allows the rows inside the data grid to be dragged. For any dragged rows to be accepted by the other data grid we need to set dropEnabled to true on that target DataGrid. We also set dragMoveEnabled to true to enable moving rows between data grids.

Ok, now let’s see how we can add drag & drop support to our file system manager. The steps required to do this are:

Step 1

We need to set the dragEnabled and the dropEnabled properties to true on both FileSystemDataGrid. The reason we do on both is because we want to allow the user to copy or move files in both directions: from left to right and right to left.

Step 2

We need to override the default handler for the DragEvent.DRAG_DROP event. This is because the standard drag & drop behavior for the FileSystemDataGrid isn’t quite what we need. In this event handler we need to call event.preventDefault() so that the default event handler is not called. The event handler inspects what file is specified inside the DragSource object carried by the event and performs the copy/move operation as specified by the key that user pressed. If the user just drags a file between the two data grids while pressing the shift key a move file operation is performed. Otherwise the default is to do a copy operation. If the file operation goes is successful we refresh the contents of two FileSystemDataGrid’s, otherwise an alert box is shown.

Here is the code:

 /**
  * Determines what object was dropped in the data grid
  */
 private function handleDragDrop(event:DragEvent):void {
 	// we do this because we want to override the default drag and drop
 	// behaviour implemented in the DataGrid
 	event.preventDefault();
 	var dragSource:DragSource=event.dragSource;
 	if (dragSource.hasFormat("items")) {
 		var items:Array = dragSource.dataForFormat("items") as Array ;
 		for each (var item:File in items) {
 			// copy this file to the directory indicated by fs2
 			// make sure that the origin data grid is not the same as the dest
 			if (event.dragInitiator!= event.target) {
 				var fileDest:File = FileSystemDataGrid(event.target).directory.resolvePath(item.name)

 				var dataGrid:FileSystemDataGrid=event.currentTarget as FileSystemDataGrid;
 				item.addEventListener(Event.COMPLETE, refreshFileSystems);
 				// if the user has pressed the shift key then he just
 				// wants to copy the file
 				if (event.shiftKey)
 				{
 					this.copyFile(item, fileDest);

 				}
 				else {
 					this.moveFile(item, fileDest);
 				}

 			}
 		}
 	}
 }

Click here to download the File System Manager Air app with Drag and Drop support


It is very cool to drag and drop a file from one file system data grid to another. However wouldn’t it be even cooler to be able to drag and drop files from the operating system desktop to Air file manager? That kind of drag & drop requires native drag & drop support. If you are interested in native drag and drop support then read my post on Native Drag and Drop in Adobe Air with example

How to apply effects to components inside containers with automated layout

In this tutorial I will explain how to apply a tween effect to a component inside a container with automated layout.

Containers like VBox, HBox, Panel(except for layout=absolute), Grid, … automatically position items for you.

Lets try to apply a Move effect to a Button inside a VBox:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="500" width="500">
<mx:Script>
 <![CDATA[
     /**
     * This method runs an effect on a button to move it
     * to the right side.
     * 
     */
  public function moveButton(button:Button):void {
   this.moveEffect.target= button;
   this.moveEffect.play();
  }
 ]]>
</mx:Script>
 <mx:VBox id="box" clipContent="false" backgroundAlpha="1" backgroundColor="red">
  <mx:Button id="button1" label="Button 1" click="moveButton(button1)"/>
  <mx:Button id="button2" label="Button 2" click="moveButton(button2)"/>
  <mx:Button id="button3" label="Button 3" click="moveButton(button3)"/>
 </mx:VBox>
  <mx:Sequence id="moveEffect">
   <mx:Move duration="5000" xBy="300" yBy="300"/>
   <mx:Move duration="5000" xBy="-300" yBy="-300"/>
  </mx:Sequence>
</mx:Application>

Click here to run our example

In the example above we set clipContent to false to enable the button to be visible outside the VBox container after it moves.

To start the move effect you need to click in one of the buttons. You will then see the button you clicked move to the right bottom corner and then return to its original position. This example is not completely correct, but it works for this simple example.

To understand why it might not work lets add a Resize effect to the VBox to go along with the button Move effect:

<mx:Parallel id="moveEffect">
 <mx:Sequence>
  <mx:Move duration="5000" xBy="300" yBy="300"/>
  <mx:Move duration="5000" xBy="-300" yBy="-300"/>
 </mx:Sequence>
 <mx:Sequence>
  <mx:Resize target="{box}" duration="5000" heightBy="300" widthBy="300"/>
  <mx:Resize target="{box}" duration="5000" heightBy="-300" widthBy="-300"/>
 </mx:Sequence>
</mx:Parallel>

At the same time that we move the button to the right bottom corner and then back to its origin, we also increase the size of the VBox and then decrease it back to its original size. Note here the use of Sequence and Parallel. These two effects allow us to run a list of child effects in sequence or in parallel as you probably guessed.

Click here to try the example above

Did you notice anything wrong with the example above? Now the Move effect doens’t work anymore. This is because every time we change the size of the VBox, Flex needs to re-position the items inside the VBox. Because of this the x and y values of the button are re-calculated thus ruining our Move effect.
The solution to this problem is to set autoLayout=”false” immediately before the start of the effect:

<mx:VBox id="box" clipContent="false" backgroundAlpha="1" backgroundColor="red">
 <mx:Button id="button1" label="Button 1" click="moveButton(button1)"/>
 <mx:Button id="button2" label="Button 2" click="moveButton(button2)"/>
 <mx:Button id="button3" label="Button 3" click="moveButton(button3)"/>
</mx:VBox>




Click here to see the full working solution in action

Creating a File Manager in Adobe Air

In my previous post I taught you how to create a simple Adobe Air application. This time we are going to be a bit more ambitious. I want to show how easy is to create an Adobe Air application. So let’s create a simple file manager with drag & drop capabilities using out of the box Adobe Air components.

Before we go into the code lets just briefly look at the out of the box components that are available to build a file manager:

  • FileSystemDataGrid – The file system data grid is an out of the box Air component that extends DataGrid and allows you to view the contents of a directory providing information such as file name, file type, size and creation date. This will be one of the main components that we will use in this file manager.
  • FileSystemHistoryButton – The file system history button is a PopupButton that allows to go back or forward in the history of selected directories for a file system. The button click event allows to navigate to the immediate ancestor or successor directory in the history of selections. While the combo box enables jumping to any previous selected directory.
  • FileSystemTree – The file system tree is a component that displays the contents of the file system as a tree. The FileSystemTree extends the out of the box Tree component in flex and inherits all the associated behavior.

Using the components above let’s create a very simple file system navigator:

<mx:Panel layout="vertical" height="100%" title="Copy From">
	<mx:ControlBar >
			 <mx:FileSystemHistoryButton label="Back"  click="fs1.navigateBack()" enabled="{fs1.canNavigateBack}" itemClick="fs1.navigateBack(event.index)" dataProvider="{fs1.backHistory}"/>
			 <mx:FileSystemHistoryButton label="Forward" enabled="{fs1.canNavigateForward}" dataProvider="{fs1.forwardHistory}" itemClick="fs1.navigateForward(event.index)" dragStart="mx.controls.Alert.show('Drag Start')"/>
			 <mx:Button label="Up" enabled="{fs1.canNavigateUp}" click="{fs1.navigateUp()}"/>
			 <mx:Button label="Delete" enabled="{fs1.selectedItem!= null}" click="handleDelete(fs1)"/>

	</mx:ControlBar>
	<mx:FileSystemDataGrid id="fs1" directory="{File.documentsDirectory}" >

	</mx:FileSystemDataGrid>
 </mx:Panel>

We use the FileSystemHistoryButton to go back or forward in the history of previous selected directories.

Notice that there are two event handlers that we need to register. The first event handler handles the click event and is used if the user simply clicks on the button. In that case FileSystemDataGrid.navigateBack() or FileSystemDataGrid.navigateForward() is called. This causes the file system data grid to go back to the previously selected directory.
The second event handler handles itemClick event. This is the event that is raised if the user selects one of the directories in the popup menu. So in this case the event handler calls FileSystemDataGrid.navigateBack(event.index) or FileSystemDataGrid.navigateForward(event.index) where event.index is the index of the menu item that the user selected.

The FileSystemDataGrid.canNavigateBack() and FileSystemDataGrid.canNavigateForward() functions indicate whether we have reached a dead-end in the history of directory selections. In that case the Forward and Back buttons will be disabled.

The Up button when clicked navigates to the parent directory, by calling FileSystemDataGrid.navigateUp(). Similarly FileSystemDataGrid.canNavigateUp() indicates whether we have reached the root directory. In that case the Up button will be disabled.

Any decent file manager should allow you to copy, move or delete a file. Let’s add these operations to our file manager:

Deleting files

 /**
    * Checks that there is at least one item selected and asks the user whether
    * he really wants to delete the file. 
    * 
    */
    private function handleDelete(fs:FileSystemDataGrid):void {
 		mx.controls.Alert.show("Do you really want to delete this file? Please be aware that the delete actually deletes... not part of the demo.","Delete confirmation",Alert.YES|Alert.CANCEL, 
 		this, function(event:CloseEvent):void {
 			 deleteConfirmation(event, fs );
 		});
     }
 		
    /**
       * If the user really wants to delete the file we do what they ask
       * 
       */
       private function deleteConfirmation(event:CloseEvent, fs:FileSystemDataGrid):void {
 	     if (event.detail==Alert.YES) {
 			if (fs.selectedItem != null) {
 			var file:File= fs.selectedItem as File;
 			if (file.isDirectory) {
 				file.deleteDirectory(false);
 			}
 			else { 						
 				file.deleteFile();	
 			}
 			fs.refresh();
 		}
 	}	
 }

 

Deleting a file is irreversible, therefore it is always a good idea to double check that the user really wants to delete the file by using an Alert dialog box.
If the user confirms the deletion of the file then we get the current selected file by calling FileSystemDataGrid.selectedItem. A File object represents a path to a directory or to a file. By calling File.isDirectory() we know whether the file is a directory or not.

  • If the File object points to a directory we call File.deleteDirectory(false), where false indicates that we do not want to delete the directory if it contains any files. Note that this delete is done synchronously meaning that the user interface will block until the deletion is complete. If you don’t want this to happen call File.deleteDirectoryAsync() instead and register event handlers for Event.COMPLETE and IOErrorEvent.IO_ERROR
  • If the File object points to a file, in that case we call File.deleteFile() and that deletes the file.

Copying Files

  /**
    * Copies the file to a new location
    * 
    */
    private function copyFile(fileOrig:File, fileDest:File):void {
 	// once copying completes we want to refresh both file system data grids to reflect
       // any changes
       fileOrig.addEventListener(Event.COMPLETE, refreshFileSystems);

      if (!fileDest.exists) {
 		fileOrig.copyToAsync(fileDest);
 	}
 	else {
 		mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this, 
 				function(event:CloseEvent):void {
 					// overwrite file
 					if (event.detail== Alert.YES) {
 						fileOrig.copyToAsync(fileDest, true);
 					}
 					else {
 						fs1.refresh();
 						fs2.refresh();
 					}
 				});
 			}
 		}

Copying a file can be done using File.copyTo() or File.copyToAsync(). File.copyTo() copies a file from one location to another but blocks until the copying finishes. On the other hand the File.copyToAsync() method returns immediately and performs the copy in the background. But in this case one should register event handlers for Event.COMPLETE and Event.PROGRESS so that we know when the copy operation is finished.

The first argument of File.copyTo() or File.copyToAsync() is the destination directory/file to copy to. There is an optional overwrite argument that you can use to specify whether you want to overwrite the destination file if one exists. If you set it to false and a file is present an IOError exception or IOErrorEvent is thrown.

Moving Files

 	/**
 	 * Moves the file to a new location
 	 * 
 	 */
 	private function moveFile(fileOrig:File, fileDest:File):void {
  		fileOrig.addEventListener(Event.COMPLETE, refreshFileSystems);
                if (!fileDest.exists) {
 			fileOrig.moveToAsync(fileDest);
 		}
 		else {
 			mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this, 
 			function(event:CloseEvent):void {
 				// overwrite file
 				if (event.detail== Alert.YES) {
 					fileOrig.moveToAsync(fileDest, true);
 				}
 				else {
 					fs1.refresh();
 					fs2.refresh();
 				}
 			});
 		}
 
 	}

Moving a file can be done using File.moveTo() or File.moveToAsync(). File.moveTo() moves a file from one location to another but blocks until the move operation is finished. On the other hand the File.moveToAsync() method returns immediately and performs the move operation in the background. But in this case one should register event handlers for Event.COMPLETE and Event.PROGRESS so that we know when the move operation is finished.

The first argument of File.moveTo() or File.moveToAsync() is the destination directory/file to move to. There is an optional overwrite argument that you can use to specify whether you want to overwrite the destination file if one exists. If you set it to false and a file is present an IOError exception or IOErrorEvent is thrown.




Click here to download the example AirFileManager

Next we want to add drag & drop support to this file system manager. So that we don’t always have to click the buttons. If you are interested in how you can add that kind of functionality then read my tutorial on Adding Drag and Drag functionality in Adobe Air

Generating a random number in actionscript 3

Generating a random number in actionscript 3 is very similar to java.
To generate a random number betwen 0 and 100:

var number:Number = Math.round(Math.random() * 100);

Where Math.random() generates a random number between 0 and 1 and Math.round(), rounds the result to the nearest integer.
If you want to generate a number within a given range [start, end]:

var number:Number =Math.round( start +  Math.random() * (end - start));



Creating your first Adobe Air Application

Adobe Air is a great extension to the Adobe Flex framework that allows almost anyone to create rich internet applications for the desktop. With Adobe Air the same applications will run on Microsoft Windows, Mac OS X and Linux with pretty much no operating system dependent code. There are some exceptions though but that we will discuss later.

This tutorial is the first part of a tutorial series on how to build a File Manager using Adobe Air’s out of the box components. This file manager will have Drag & Drop capabilities, within the file manager itself, to copy or move files between directories. It will have native drag & drop capabilities to copy files from the desktop directly to the file manager. Or the other way around. Finally we will also add a navigational menu to the file manager to demonstrate some more useful out of the box Adobe Air components.

But before we go into all that let’s create our first “Hello World” Adobe Air application.

Step 1

Create a new project in Flex Builder but this time select the radio button that says ‘Desktop Application’

Depending on how you named your project you will see two files in your project. For example if you named it AirFileManager you will see an AirFileManager.mxml file and a AirFileManager-app.xml. The mxml file is the file where you will put the initial code.
Notice the WindowedApplication top level node that is created. This is the Adobe Air equivalent of the Application component in Flex. The main difference being that a WindowedApplication is not restricted to run in a browser, it runs on a desktop, and provides some extra features.

AirFileManager-app.xml file is an application descriptor file where you will configure certain parameters related to your Adobe Air Application. Parameters like transparency, system chrome, name, filename, copyright, window initial size and icon will be configured in this file.

Step 2

Add the following code:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
 <mx:Button label="Click me" click="mx.controls.Alert.show('I have just created my first Adobe Air App!')"/> 
</mx:WindowedApplication>

Step 3

Execute the Air app by clicking on the run method.

Congratulations! You have just created & executed your first Adobe Air app. But don’t go out to celebrate yet… that was just to break the ice.

Let’s make some modifications to the application descriptor.
Notice the systemChrome and transparency property. The systemChrome property tells Adobe Air whether to use the operating system native chrome or not. The transparency property is used to determine if the application window will be transparent or opaque. Note however that transparency is only supported if you set the systemChrome to none. If you set systemChrome to standard the operating system chrome is used and therefore Adobe Air can’t support transparency as it is outside its control.
Note that the default value for the systemChrome is standard.

To test this feature add a backgroundAlpha of 0.5 to the WindowedApplication component.
And modify AirFileManager-app.xml to:

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.5">
 <!-- The application identifier string, unique to this application. Required. -->
 <id>AirFileManager</id>

 <!-- Used as the filename for the application. Required. -->
 <filename>AirFileManager</filename>

 <!-- The name that is displayed in the AIR application installer. 
      May have multiple values for each language. See samples or xsd schema file. Optional. -->
 <name>AirFileManager</name>

 <!-- An application version designator (such as "v1", "2.5", or "Alpha 1"). Required. -->
 <version>v1</version>

 <!-- Settings for the application's initial window. Required. -->
 <initialWindow>
  <content>[This value will be overwritten by Flex Builder in the output app.xml]</content>
  <systemChrome>none</systemChrome>
               <transparent>true</transparent>
 </initialWindow>
</application>

Run the Adobe Air application and see the difference in the application window. It should look transparent now.

Deploying Adobe Air Applications

Now that you have created your first Adobe Air application, you should package it into an AIR file. If you are using Flex Builder:

  1. Right click your project folder and select Export
  2. In the next screen choose Release Build and click Next
  3. If you want to include the source code with your release click on Enable View Source and then click Next.
  4. In this screen you are asked for a digital certificate to include with your Adobe Air application. You can either specify a digital signature provided by a third party like Verisign or Thawte. If you don’t have one then click Create to create your own self-signed digital certificate. Although you can can create your own digital certificate, it is not the best option as it doesn’t provide assurance to the users that your application is safe or that it hasn’t been tampered.
  5. Type your digital certificate password and click next
  6. Select the files you want to include in your package and then click finish.
  7. Your AIR installation package should have been created in the same directory as your project. Use this file to distribute your application.

That will be all for now. In the next part of this tutorial we will start adding the file manager components with which we will create a simple but yet powerful file manager in Adobe Air.




Click here to go to the next part of our tutorial.

The DisplayObject Blend Mode

The blend mode controls what happens when one display object is on top of another. The default setting in Adobe Flex is that the pixels of a display object override those that are displayed in the background. However you can change this behavior by modifying the blendMode setting. Read the language reference entry for DisplayObject. There you will find a detailed description of each blend mode.
In a nutshell the different blend modes available are:

  • BlendMode.NORMAL
  • BlendMode.LAYER
  • BlendMode.MULTIPLY
  • BlendMode.SCREEN
  • BlendMode.LIGHTEN
  • BlendMode.DARKEN
  • BlendMode.DIFFERENCE
  • BlendMode.DARKEN
  • BlendMode.OVERLAY
  • BlendMode.ADD
  • BlendMode.SUBTRACT
  • BlendMode.INVERT
  • BlendMode.ALPHA
  • BlendMode.ERASE
  • BlendMode.SHADE

Use the following flex code to try the different blend modes:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:core="*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" height="600" width="600" horizontalAlign="left">
 <mx:ArrayCollection id="blendModes">
  <mx:Array>
   <mx:Object label="Normal" data="normal"/>
   <mx:Object label="Layer" data="layer"/>
   <mx:Object label="Multiply" data="multiply"/>
   <mx:Object label="Screen" data="screen"/>
   <mx:Object label="Lighten" data="lighten"/>
   <mx:Object label="Darken" data="darken"/>
   <mx:Object label="Difference" data="difference"/> 
   <mx:Object label="Overlay" data="overlay"/>
   <mx:Object label="ADD" data="add"/>
   <mx:Object label="Subtract" data="subtract"/>
   <mx:Object label="Invert" data="invert"/>
   <mx:Object label="Alpha" data="alpha"/>
   <mx:Object label="Erase" data="erase"/>
  </mx:Array>
 </mx:ArrayCollection>
 <mx:Canvas height="200" width="400">
  <mx:VBox height="100" width="100" backgroundColor="{color1.selectedColor}" backgroundAlpha="1" blendMode="{blendMode1.selectedItem.data}" />
  <mx:VBox height="100" width="100" backgroundColor="{color2.selectedColor}" backgroundAlpha="1"  x="50" y="50" blendMode="{blendMode2.selectedItem.data}"/>
 </mx:Canvas>
 <mx:Panel title="Blend Modes" layout="horizontal">
  <mx:ComboBox id="blendMode1" dataProvider="{blendModes}" />
  <mx:ComboBox id="blendMode2" dataProvider="{blendModes}"/>
 </mx:Panel>
 <mx:Panel title="Colors">
  <mx:ColorPicker id="color1"/>
  <mx:ColorPicker id="color2" selectedColor="blue"/>
 </mx:Panel>
</mx:Application>




Click here to try the example.

Integrate Adobe Flex and JBOSS using BlazeDS – Part II

In the first part of this tutorial I showed you an example on how to integrate flex with JBOSS using BlazeDS. In this example we used RemoteObject in Actionscript to create a new instance of uk.co.spltech.remote.RemoteService and call the callMe() method remotely. The method returned a list of strings that we used to populate a datagrid.
In the second part of this tutorial we will do something slightly more useful. This time we will be invoking an EJB 3.0 bean managed by the application server.

Step 1

To be able to invoke EJB 3.0 from flex you need to download and copy FlexEJBFactory.jar to $JBOSS_HOME/server/default/lib. Or you can copy it to a local directory in your project separate from JBOSS e.g. example7/jbosslib and add the following line to $JBOSS_SERVER/server/default/conf/jboss-service.xml:

<classpath codebase="/usr/java/eclipse/workspace/example7/jbosslib" archives="*"/>

Previously all the BlazeDS jars were located in the WAR:WEB-INF/lib directory. But this is not going to work now because we need to see the jars within the context of the entire EAR. For that reason we need to move all those jars to to $JBOSS_SERVER/server/default/lib(or to a local directory inside your project e.g. jbosslib):

flex-ejb-factory.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-opt.jar
flex-messaging-proxy.jar
flex-messaging-remoting.jar
backport-util-concurrent.jar

Step 2

Because we will be invoking EJB 3.0 components we need to change the build process so that it creates an EAR file instead of a WAR. We add the following lines to the createjars target:

<jar jarfile="build/example7.jar">
 <fileset dir="${build.classes.dir}">
    <include name="uk/co/spltech/remote/*.class" />
 </fileset>
       
    </jar>

    <zip zipfile="build/example7.ear">
 <zipfileset dir="build">
  <include name="example7.war" />
 </zipfileset>
 <zipfileset dir="${build.dir}/resources" prefix="META-INF">
  <include name="application.xml" />
 </zipfileset>
       <zipfileset dir="build">
  <include name="example7.jar" />
 </zipfileset>
     </zip>

Step 3

Add the new ejb3 factory to flex/services-config.xml:

<factories>
     <factory id="ejb3" class="com.adobe.ac.ejb.EJB3Factory"/>
 </factories>

Step 4

Inside the uk.co.spltech.remote package create the following classes:

package uk.co.spltech.remote;

import java.util.List;

/**
 * This is an example of an interface where you can
 * declare all the methods that you want to call remotely
 * 
 * @author Armindo Cachada
 *
 */
public interface RemoteService {
 /**
  * I am not doing anything useful except to just show that I can be invoked remotely
  * from Adobe Flex using RemoteObject.
  * 
  */
 public List<String> callMe();
}

And the implementation of the interface:

package uk.co.spltech.remote;

import java.util.ArrayList;
import java.util.List;

import javax.ejb.Local;
import javax.ejb.Stateless;

/**
 * This remote service is called using BlazeDS/LiveCycle DS from Flex
 * 
 * @author Armindo Cachada
 *
 */
@Stateless(name = "remoteService")
@Local(RemoteService.class)
public class RemoteServiceImpl implements RemoteService {

 
 /**
  * I am not doing anything useful except to just show that I can be invoked remotely
  * from Adobe Flex using RemoteObject.
  * 
  */
 public List<String> callMe() {
  System.out.println("I was invoked!");
  List<String> result = new ArrayList<String>();
  result.add("Michael Jackson");
  result.add("Beatles");
  result.add("Tina Turner");
  return result;
 
 }
}

Step 5

Add the new destination to flex/remoting-config.xml:

<destination id="remoteService" >
        <properties>
              <factory>ejb3</factory>
              <source>example7/remoteService/local</source>
              <scope>application</scope>
         </properties>
   </destination>

There are two differences in the destination declaration:

  • The factory node indicates that we want to use the EJB3Factory declared in services-config.xml
  • The source instead of referencing a class name is changed to reference an EJB3 bean via JNDI.

Step 6

We are going to use the same mxml code as the one for Part I of this tutorial but we need to change the flex compiler settings:

-services "/usr/java/eclipse/workspace/example7/resources/flex/services-config.xml" -context-root "/example7" -locale en_US

After you have followed all these steps you should be good to go!




Download source code for JBOSS+Adobe Flex+BlazeDS project Part II

To save bandwidth I didn’t include all the jars needed:

flex-ejb-factory.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-opt.jar
flex-messaging-proxy.jar
flex-messaging-remoting.jar
backport-util-concurrent.jar

Copy these jars to the jbosslib directory inside the project. Don’t forget to change jboss-service.xml to reference this directory. Otherwise nothing will work.

Good luck!