Posts in Category "Developer Tips"

How to create a dashed rectangle in Flash

If you were ever in the need of drawing a dashed rectangle in Flash, you find that it’s not so simple as using a DashedStroke. I think this is certainly a missing feature in Flash. After spending hours looking for existing code I decided to write it myself.

The first very simple approach is to create a BitmapData with the required dash-gap pattern and use it as lineBitmapStyle. This works quite well but you don’t get the nice corners.

Ugly Corners

Ugly Corners

So you really need to draw the lines yourself. The trick here is to constraint the lines to start with a dash in each corner. You need to calculate the number of gaps needed for the length of the lines and then distribute any excess pixels evenly. This is rather simple to do if you use floating point precision while drawing the lines. Of course this is easy because we’re only drawing horizontal and vertical lines and don’t support rounded corners.

Nice Corners

Nice Corners

Having created the dashed rectangle element, it’s easy to create a skin for the BorderContainer. Just create a new skin as a copy of the original BorderContainerSkin and replace its bgRect with the DashedRect. This little exercise is left for the reader :-)

Here’s the code for drawing the rectangle:

<!-- DashedRect.mxml -->
<?xml version="1.0" encoding="utf-8"?>
<s:Rect xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx">
  <fx:Script>
    <![CDATA[
      import flash.display.Graphics;

      private var _dash:int = 6;

      private var _gap:int = 6;

      public function set gap(i:int):void {
        _gap = i;
      }

      public function set dash(i:int):void {
        _dash = i;
      }

      override protected function draw(g:Graphics):void {
        var xx:int = drawX + width;
        var yy:int = drawY + height;
        var x:int, y:int, gx:Number, gy:Number;

        var nGapsH:Number = Math.floor(width / (_gap + _dash));
        var nGapsV:Number = Math.floor(height / (_gap + _dash));
        var gw:Number = nGapsH == 0 ? 0 : (width-((nGapsH + 1) * _dash)) / nGapsH;
        var gh:Number = nGapsV == 0 ? 0 : (height-((nGapsV + 1) * _dash)) / nGapsV;

        // top line
        g.moveTo(drawX, drawY);
        x = gx = drawX;
        while (x < xx) {
          x = Math.min(x + _dash, xx);
          g.lineTo(x, drawY);
          gx+=_dash + gw;
          x = gx;
          g.moveTo(x, drawY);
        }
        // right line
        g.moveTo(xx, drawY);
        y = gy = drawY;
        while (y < yy) {
          y = Math.min(y + _dash, yy);
          g.lineTo(xx, y);
          gy+=_dash + gh;
          y = gy;
          g.moveTo(xx, y);
        }
        // bottom line
        g.moveTo(xx, yy);
        x = gx = xx;
        while (x > drawX) {
          x = Math.max(x - _dash, drawX);
          g.lineTo(x, yy);
          gx-=_dash + gw;
          x = gx;
          g.moveTo(x, yy);
        }
        // left line
        g.moveTo(drawX, yy);
        y = gy = yy;
        while (y > drawY) {
          y = Math.max(y - _dash, drawY);
          g.lineTo(drawX, y);
          gy-=_dash + gh;
          y = gy;
          g.moveTo(drawX, y);
        }
      }

    ]]>
  </fx:Script>
</s:Rect>

 

And here’s how to use it:

<local:DashedRect x="0" y="0" width="100%" height="100%" dash="6" gap="6">
  <local:stroke>
    <s:SolidColorStroke color="0x000000" weight="2"/>
  </local:stroke>
</local:DashedRect>

Flash Builder Tooling with CAF Tiles using Flex SDK 3.6

1 Introduction

Even though the ADEP tooling doesn’t officially support building CAF tiles that use Flex SDKs earlier than 4.5.0, it is nevertheless possible to do so while taking advantage of many of the features it provides, if you do a little of the work yourself. The following features of the tooling can still be used with 3.6 Tiles, once they have been properly set up:

  • Some automatic catalog entry generation
  • Catalog and Application deployment

The things you give up include:

  • Automatic gxml generation for library projects
  • Code assist in library projects
  • Tile creation wizard

2 General ideas

The main thing you need to do to get started is to undo some of the work that the File->New->ADEP project wizard does for you. You give up all the features of tooling if you don’t ADEP-enable a project, so it’s better to go through the wizard as though creating a 4.5-based project, and then change the settings to make it a 3.6-based project instead. We’ll cover the two main types of tile projects, Module tiles and UIComponent tiles. There are other ways to get the tooling to help, such as telling a 3.6-based non-ADEP project to generate its output into a 4.5-based, ADEP-enabled project, but I won’t cover them here.

3 Creating a 3.6 Module tile project

Let’s start with Module tiles, since they’re the simplest, and are the recommended approach. Throughout this tutorial, we’ll use the default catalog, but all the steps work equally well with any catalog.

Begin by launching FlashBuilder, and ensure that you have all the normal ADEP-related parts set up (AEP_SDKS pointed at the right place, for instance). Then create a new ADEP-project, by choosing File->New Flex Project for ADEP Experience Services. Configure it as you would normally configure a 4.5 tile, but on the first panel of the wizard, choose the 3.6 Flex SDK instead. Accept all the default swc libraries that are added to the build path, even though they’re for 4.5, you’ll remove them after the project is set up. Once your project is ready, open the project build path pane, and remove all the library entries that mention “ux”, “riacore” and “dataservices”. Unfortunately, one of the swc directories just removed has a couple of swcs you still need in addition to many you don’t, so choose “Add SWC…” and enter “${AEP_SDKS}/riaservices/riacore/10.0.0.0/flex/security_api.swc”, and again for security_domain_library.swc.  Then, edit the remaining entries that mention 4.5 and point them at 3.6 instead.  Your build path should now look like this:

Finally, in the Flex Compiler pane, remove the additional compiler argument “-includes=mx.managers.systemClasses.MarshallingSupport”. Your project is now mostly “3.6-ready”, and if you’re in a hurry, you can skip the next step, though in a production environment you’ll need to come back and do it.

The wizard creates a sequence of RSL failover entries that ensure that your application, when it loads Flex SDK swz and swf files, gets them first from the server that served up the application. However, the URLs the wizard uses are for the 4.5 SDK, not the 3.6 one. The wizard will have set the default framework linkage for your project to “Use SDK default”, which is most likely RSL, so the RSL failover paths do need to be edited. Go to the Flex Build Path pane for the project, and open the list of the Flex 3.6 SDK components. The list should include playerglobal.swc, framework.swc, etc. The ones that are available on the CRX server need to have their entries modified. These are:

  • framework.swc
  • datavisualization.swc
  • rpc.swc

Each one needs to have the following failover pattern (replacing “framework” with “datavisualization” and “rpc”):

/content/mosaic/viewer/flex_sdks/flex_sdk_3.6.0.16995/framework_3.6.0_16995.swz
/content/mosaic/viewer/flex_sdks/flex_sdk_3.6.0.16995/framework_3.6.0_16995.swf
framework_3.6.0_16995.swz
framework_3.6.0_16995.swf

http://fpdownload.adobe.com/pub/swz/flex/3.6.0.16995/framework_3.6.0.16995.swz

Next, create a new MXML module in that project. The File->New->Composite Application Tile wizard expects to with with a 4.5 project, and will refuse to create a tile in a 3.6 project, so you’ll have to create the module with the File->New MXML Module dialog instead. Then add the Tile metadata to your module, so it looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
           layout="absolute" width="400" height="300">
  <mx:Metadata>
    [Tile]
  </mx:Metadata>
  <mx:Button label="Tile1 button"/>
</mx:Module>

Since the project is ADEP-enabled, you should also now have a new entry in your catalog that looks like this:

<tile:TileClass fitContent="true" height="300" label="Tile1" name="Tile1" width="600">
  <ct:Metadata>
    <ct:Description/>
  </ct:Metadata>
  <tile:Content contentType="application/x-shockwave-flash" flexSDKVersion="3.6.0"
                loadAs="module" uri="${catalogURL}/tiles/Tile1/Tile1.swf"/>
</tile:TileClass>

The tooling does know that your project uses Flex SDK 3.6, and it successfully puts that information in the catalog entry, there should be no need to edit it by hand.

Now create a new CAF application, either in this project or a separate one, using the File->New->Composite Application, and add a reference to this tile:

<tile:TileReference catalog="${catalog}" name="Tile1" label="Tile1"
                    optional="true" width="100%" height="100%" />

Your 3.6-based module tile is now ready for use. You can right-click on the application file and choose Composite Application Framework->Deploy Composite Application, and then navigate to its URL in your browser.

4 Creating a 3.6 UIComponent tile

Follow these steps to create a new Composite Application tile or tiles from a Flex Library project, or to augment an existing Flex Library project so parts of it work as tiles and can be included in a Composite Application.

4.1 Creating the UIComponent project and tile

If you already have a Flex Library project that has a component or components in it that you want to expose as Tiles, skip this step and proceed to the next one, where you’ll modify the existing project to make an existing component into a Tile.

Otherwise, begin by creating a new Flex Library project, File->New->Flex Library project, making sure to choose Flex SDK 3.6. Then create a new component in that project, with File->New->MXML Component. For this example, base your component on mx:Button, and add a label to it so we will recognize it when we see it:

<?xml version="1.0" encoding="utf-8"?>
<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" label="3.6 Button Component">
</mx:Button>

So far, there is nothing special about this component. In the next steps, we’ll make it into a Composite Application tile.

4.2 Modifying an existing Library project to make it into a Tile

There are three primary steps to take in order to convert an existing Flex component into a tile. The first is to mark the component as a tile, and then provide it with the necessary libraries to allow it to communicate with the rest of the Composite Application Framework. Second, since a Library project normally only produces a SWC file and CAF needs a SWF to load, we have to create a wrapper project that includes the SWC. Normally, this step is handled by the ADEP tooling, but it’s not supported for 3.6-based projects, so we have to do this step manually. Finally, we have to create a gxml file so that CCF can load the tile.

4.2.1 Prepare the Library Project

Begin by adding the needed library directories to the project. They are:

  • ${AEP_SDKS}/riaservices/mosaic/10.0.0.0/flex3.6.0/frameworks/libs
  • ${AEP_SDKS}/riaservices/gravity/10.0.0.0/flex/3.6.0/libs
  • ${AEP_SDKS}/riaservices/gravity/10.0.0.0/flex/core/libs

Then switch the framework linkage to RSL, which will reduce the size of the tile, and add the following to the Flex Compiler options: -locale en_US -keep-as3-metadata+=Tile,Application,ContextBind,SecurityManager

Finally, update the RSL failovers as described in the earlier section.

You are now ready to add CAF-specific constructs to the project, specifically the [Tile] annotation. If you are using the extended button from the previous step, your new source code would look like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:Metadata>
  [Tile]
</mx:Metadata>
<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" label="3.6 Button Component">
</mx:Button>

4.2.2 Create the wrapper project

Create a new ADEP-enabled project, with File->New->Flex Project for ADEP-Experience services, make sure it’s set to use Flex SDK 3.6, and complete the dialog as you did for the section “Creating a 3.6 Module tile project”. Also as in the earlier section, remove the unused libraries, and update the necessary ones from 4.5 to 3.6. Then make this wrapper project depend on the Library project.

Adjust the RSL failovers, switch the project to RSL linkage, and add the compiler options you added earlier to the Library project. Then create a new MXML Module called, for example, “ComponentWrapper.mxml”. This file will be very simple, it serves only to wrap the component in a particular way to enable it to be loaded by CAF:

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
           width="400" height="300">
	<mx:Script>
          MyButton;
	</mx:Script>
</mx:Module>

MyButton would be the name of the component you created earlier, or the name of the existing component. If the component is not in the default package, you’d do something slightly different:

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
           width="400" height="300">
	<mx:Script>
          include com.acme.MyButton;MyButton;
	</mx:Script>
</mx:Module>

Finally, we have to create the GXML file. This will go in the “mosaic” folder, which is created automatically when you create an ADEP-enabled project. The file should be named “MyButton-com-acme.gxml”, if MyButton is in the com.acme package, or just “MyButton.gxml” if it’s in the default package. The content should be as below:

<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns="http://ns.adobe.com/gravity/manifest/1.0">
  <bundle location="ComponentWrapper.swf" parentDomain="flex3.6.0">
    <beans xmlns="http://ns.adobe.com/gravity/di/1.0">
      <bean id="MyButtonAdaptedBeanFactory" lazyInit="true" scope="prototype">
        <constructorArg ref="gravityContainer.getBean"/>
        <constructorArg>
          <array>
            <bean lazyInit="true">
              <constructorArg value="MyButtonUIService"/>
            </bean>
          </array>
        </constructorArg>
      </bean>      

      <service interface="com.adobe.gravity.ui.IUIServiceFactory"
               ref="MyButtonModuleUIServiceFactory">
        <serviceProperties>
          <entry key="purpose" valueRef="MyButtonPurpose"/>
          <entry key="description" valueRef="MyButtonDescription"/>
        </serviceProperties>
      </service>

      <bean id="MyButtonModuleUIServiceFactory" scope="prototype">
        <constructorArg ref="MyButtonAdaptedBeanFactory"/>
      </bean>

      <bean id="MyButtonCreator" factoryMethod="createWrapper" scope="prototype">
        <constructorArg ref="gravityBundleContext" index="0"/>
        <constructorArg value="com.adobe.threesix.components.MyButton" index="1"/>
      </bean>

      <bean id="MyButtonPurpose" lazyInit="true">
        <constructorArg value="${catalog}/com.adobe.threesix.components.MyButton"/>
      </bean>

      <bean id="MyButtonDescription" lazyInit="true">
        <constructorArg value="MyButton's UI Service"/>
      </bean>
      <bean id="MyButtonUIService" parent="MyButtonCreator"
            lazyInit="true" scope="prototype">
      </bean>
    </beans>
    <flexModule sdk="3.6.0"/>
  </bundle>

</manifest>

It may be instructive to compare this gxml file with one that the Tooling generates automatically for a Library component tile built with SDK 4.5. You may see several differences, but only some of them are significant. Many of the “id” attributes serve only as identifiers within the gxml, and need only match each other where they appear, but their actual names are not significant. However, there are a couple of instances where the values of strings are important:

  • The bundle element’s location attribute must name the swf built by the project.
  • The constructorArg value attribute in the “Creator” bean and the “Purpose” bean must have the fully qualified class name of the wrapped component that you marked as a Tile.

Even though this tile is ADEP-enabled, an entry in the catalog will not be generated, because nothing directly inside the project was marked with the [Tile] annotation. Therefore, we need to add an entry manually to the catalog. In this case, it will refer to the gxml file we created, rather than the swf. The entry should look like this:

<tile:TileClass fitContent="true" height="300" width="600"
                label="com.acme.MyButton" name="com.acme.MyButton">
  <ct:Metadata>
    <ct:Description/>
  </ct:Metadata>
  <tile:Content contentType="application/xml" flexSDKVersion="3.6.0" loadAs="module"
                uri="${catalogURL}/tiles/com.acme.MyButton/com.acme.MyButton.gxml"/>
</tile:TileClass>

You can now add a reference to this tile in your application:

<tile:TileReference catalog="MixedSDKS"
   name="com.acme.MyButton" label="My Button" optional="true" width="100%" height="100%" />

You’re finished!

 

Submit a file using POST request in CRX

This simple example shows how we can submit a file using a POST request to a JSP from an html form. I used CRXDE for creating the project.

Create a new project in CRXDE by right clicking on the Project Navigator page and selecting Build->Create Project. Give a project name and the package name that you will be using. I will be using simplePOSTRequest as the project name.

  • A new folder is created under apps with the name simplePOSTRequest. In CRXDE Lite you will find under the content node that a new node is created with the name “simplePOSTRequest”. On the node the slingResourceType property is set which points to simplePOSTRequest/components/sample.
  • In simplePOSTRequest/components/sample, under the apps folder you will find a components folder with html.jsp page already created. This is the page that is invoked when you go to http://localhost:7402/simplePOSTRequest.html.

Now modify the html page and add a simple html form which takes a file as in input and has a submit button.  Save the project.

Now, go to http://localhost:7402/simplePOSTRequest.html and select a file and click upload. You will get a 200 OK status message which means that the request was successful (See image below).

We did not have any POST request handling logic at this point, so where did the file go? If you go to CRXDE Lite, you will see under the simplePOSTRequest node in content node that a new node is created. The name of the node is the name we specified for the File input type in the html form.

If we want to create a JSP file and want the POST request to be redirected to that JSP create a POST.jsp file under apps/simplePOSTRequest/components/sample.

Now if you click the upload button, you will see the new page will be invoked and no nodes will be created in the crx. You can access the form data from the HttpServletRequest or SlingHttpServletRequest object in POST.jsp.

Here is the POST.jsp that displays the submitted file details. (Click to enlarge)

 

Using REST To Access CRX Content in Flex

When building a Flex application that reads content from a CRX Repository, RESTful HTTP requests are an ideal pattern for reading both meta data that describes your content as well as the raw content bytes.

In this example we look at:

  1. Writing Flex application can browse and display many different types of content (images, documents etc) read from CRX using REST.
  2. After reading the actual content, how to display it in your Flex application using one of many new “UX Components” provided in the SDK for the Adobe Digital Enterprise Platform.

Consider a typical “master / detail” tree view application where the user browses and displays content:

(click to enlarge)

Let’s look at how such an application is built.

First the init() method used when the app starts up:


private function init():void 
{ 
    //Read CRX first time and load nodes 
     NodeService.execute(hostRoot, classificationsLoaded, loadFailed);
    classTree.dataDescriptor=new NodeDataDescriptor(); 
}

The “NodeService” Class above is used to make an HTTP Request to CRX to get back a list of content nodes


public class NodeService 
{
    public static function execute(
        path:String, resultHandler:Function, faultHandler:Function):void 
    { 
        var httpService:HTTPService = new HTTPService(); 
        httpService.url = path + ".2.json"; 
        httpService.method = HTTPRequestMessage.GET_METHOD;
        httpService.resultFormat = "text"; 
        httpService.addEventListener( ResultEvent.RESULT, resultHandler ); 
        httpService.addEventListener( FaultEvent.FAULT, faultHandler ); httpService.send(); 
    } 
} 

Next the JSON response is decoded into an array collection used for the nodes to be displayed in the tree. Each node will contain the properties (name, content type etc) of the node as well as a URL pointer to its content stream.


private function classificationsLoaded(e:ResultEvent):void 
{
    var resultStr:String = new String(e.message.body); 
    var jsondecoder:JSONDecoder = new JSONDecoder();
     var data:ObjectProxy = jsondecoder.decode(resultStr); 
    var tmpArr:Array = []; 
    var patternExclude:RegExp = /(sling:|jcr:|cq:)/i; 
    var patternInclude:RegExp = /(sling:Folder)/i; 
    for (var p:String in data) { 
        if(p.search(patternInclude)>0 || p.search(patternExclude)<0) { 
            var tmpObj:CustomNode = new CustomNode(); 
            tmpObj.name = p; mpObj.path = hostRoot + "/" + p; tmpObj.parent = null; 
            if(data[p] is ObjectProxy) tmpObj.hasChildren = containsChildren(data[p]); else
                tmpObj.hasChildren = false; tmpArr.push(tmpObj); 
         } 
    } 
    ac = new ArrayCollection(tmpArr); 

Finally a click handler for each node in the tree uses the node properties to supply data to a “UX Component” which can render the content within a boundary area in the Flex application. In this case either display the content or just display more tree nodes if the selected node is not a leaf node.

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:xoawv="com.adobe.ep.ux.webdocumentviewer.domain.*"
    ...

private function onItemClick(e:ListEvent): void {
    var tmpObj:CustomNode = classTree.selectedItem as CustomNode;
    if(!tmpObj.hasChildren){
        viewer.unload();
        viewer.url = tmpObj.path;
        viewer.contentType = "text/plain";
        viewer.loadDocument();
        }else {
            if (tmpObj) {
                if (!tmpObj.childrenLoaded) {
                    tmpObj.loadChildren();
                    tmpObj.addEventListener(
                      CustomNode.CHILDREN_LOADED, onItemChildrenLoaded);
                }
            }
        }
     } } ... <mx:HBox width="100%" height="100%" backgroundColor="#FFFFFF">
    <s:Panel width="20%" height="100%" title="CRX Structure">
        <mx:Tree id="classTree" dataProvider="{ac}" width="100%" height="100%"
            itemOpen="onItemOpen(event)" itemClick="onItemClick(event)" />
    </s:Panel>
        <mx:VBox  width="80%" height="100%" >
        <s:Panel width="100%" height="100%" title="Document Viewer">
        <xoawv:WebDocumentViewer id="viewer" width="100%" height="100%" />
    </s:Panel>
<mx:HBox width="100%">