Thursday, 8 March 2012

Printing in Flex, with runtime images

Printing in Flex is a topic that comes up from time to time, and is met by a shudder from most people. Whilst it is nice to have some direct printing capability within the Flash Runtime and API set, it is far from friendly to work with.

FlexPrintJob provides a nice abstraction on top of the core Flash Printing API, allowing Flex visual components to be printed directly. This can make life easier when it comes to complex layouts and content, as it is not necessary to work with low-level drawing APIs. However, do note that Flex components need to be added to the display list, otherwise certain dependent resources will not be available that are required by FlexPrintJob to function correctly.

The general premise is to call FlexPrintJob.start() which begins the print job, and presents the end user with a print dialog. This method is synchronous, and processing will halt until the user has made a selection (do note, however, that the Flash API places a 15 second maximum time between calling FlexPrintJob methods, and I experienced timeout issues when spending too much time working with an Acrobat Distiller browse dialog). After this point, FlexPrintJob.addObject() is called for each page in the print job. At its conclusion, FlexPrintJob.send() is called to pass the job to the native print spooler.

The problem I encountered recently involved the printing of images, which did not make use of embedded content. I'll demonstrate with a brief example. The printable UI may look like:

PrintUI.mxml
<s:Group>
   <s:Image id="image" />
</s:Group>

PrintMediator.as
[Inject]
public var printUI: PrintUI;

private var _imageData: ByteArray;

override protected function onRegister(  ): void
{
   addContextListener( ApplicationEvent.PRINT, onPrint, ApplicationEvent );
}

private function onPrint( event: ApplicationEvent ): void
{
   var flexPrintJob: FlexPrintJob = new FlexPrintJob(  );
   flexPrintJob.start();
   flexPrintJob.printBitmap = false;

   printUI.image.source = _imageData;

   flexPrintJob.addObject( printUI );

   flexPrintJob.send(  );
}

So, what happens? Ideally, the printed output should contain a representation of the image contained in the ByteArray. Unfortunately, we'll get a blank page. Why is this? It comes down to the way that Image handles a ByteArray. Whilst the data is available client-side, it still needs to be validated by the image parser. Consequently, when the .source setter is called, the value is stored in the Image instance, but it is not processed until a later stage - one which is not in the current call-stack. Consequently, by the time processing has reached the FlexPrintJob.addObject() method, the Image data has not been converted into its visual representation.

Fortunately (although not simply), the Image API provides events to track the progress of loading and parsing. Thus, problem is resolved by listening for the FlexEvent.READY event, which is fired when the image has been processed and displayed. The downside to this is that a simple synchronous function (like the one presented above), must become asynchronous, as indicated below:

PrintMediator.as
[Inject]
public var printUI: PrintUI;

private var _imageData: ByteArray;

private var _flexPrintJob: FlexPrintJob;

override protected function onRegister(  ): void
{
   addContextListener( ApplicationEvent.PRINT, onPrint, ApplicationEvent );
   printUI.image.addEventListener( FlexEvent.READY, onImageReady );
}

private function onPrint( event: ApplicationEvent ): void
{
   var flexPrintJob: FlexPrintJob = new FlexPrintJob(  );

   _flexPrintJob.start();
   _flexPrintJob.printBitmap = false;

   printUI.image.source = imageData;
}

private function onImageReady( event: FlexEvent ): void
{
   _flexPrintJob.addObject( printUI );

   _flexPrintJob.send(  );
}
Obviously these examples have been simplified for the purposes of display.

Monday, 23 January 2012

Calling LiveCycle processes via Remoting, involving Documents

One nice aspect of Adobe's LiveCycle is the ability to easily invoke a process using Flex Remoting. For 'primitive' types, such as Number, XML and String, it's a straight-forward case of passing them to the Remoting call. However, in some instances, it is necessary to pass a LiveCycle Document instance to a Remoting call. The recommended means of doing this involves a two-step process, where the Document is uploaded to the LiveCycle server, which returns a DocumentReference. This reference is passed to the Remoting call and LiveCycle marries everything up on its end.

I encountered an issue with this recently in an enterprise environment, due to the way DocumentReference works. After the Document has been stored on the server, LiveCycle constructs a URL pointing to it - based on the application URL of the Flex application that performed the upload. In our client's case, the application URL does not resolve inside their firewall. This meant that when LiveCycle attempted to load the Document based on the DocumentReference, it was unable to locate it.

Manipulating the DocumentReference on the client side was ruled out, due to the maintenance nightmare and security implications of having internal infrastructure details stored within the application itself. Resolving the issue on the server side was not a straightforward solution, as all processes involving Documents would have to be changed.

It turns out there's a simple solution; instead of performing a HTTP-based Document upload (which also has problems in a HTTPS context on non-IE browsers), it is possible to manually construct a DocumentReference instance, and pass in the file data by hand. For example:

var documentReference: DocumentReference = new DocumentReference(  );
documentReference.referenceType = DocumentReference.REF_TYPE_INLINE;
documentReference.bytes = yourByteArray;

Wednesday, 2 November 2011

Emplore: A Constraint Engine

I've started putting together a framework I'm code naming Emplore. It's a constraint engine that was born out of having data models being manipulated in multiple places in applications and a subsequent need to perform constraint checking in those places.

The general concept is that annotations will be used to decorate the model with constraint definitions. For example:

public class MyModel
{
  [Required]
  [ConstrainLength( minLength=1, maxLength=100 )]
  public var aString: String;

  [ConstrainRange( minValue=-123.4, maxValue=234.5 )]
  public var aNumber: Number;
}
The intention from here is that there will be functionality for automated validator generation and API-based constraint checking. Stay tuned for more.

Monday, 24 October 2011

Quick Tip: Flex 4.5 Form Spacing

Users of the new Flex 4.5 Form will notice that the vertical spacing between FormItems is quite significant. A quick fix to this is to alter the gap of the layout, e.g.:

<s:Form>
  <s:layout>
    <s:FormLayout gap="-14"/> 
  </s:layout>

...

</s:Form>

Do note, however, that this will cause layout issues when adding controls to the Form that are not wrapped in FormItems. An alternative solution is to roll a custom FormItem skin which alters the  row constraint declaration as follows:

<s:constraintRows>
  <!--- @private -->
  <s:ConstraintRow id="row1" baseline="maxAscent:0" height="100%"/>
</s:constraintRow>

instead of the default

<s:constraintRows>
  <!--- @private -->
  <s:ConstraintRow id="row1" baseline="maxAscent:10" height="100%"/>
</s:constraintRow>

Sunday, 25 September 2011

Quick Tip: Ignoring XML Namespaces

This is another small follow up to Common Mistakes: XML Namespaces and E4X: sometimes you may wish to completely ignore the namespace information, rather than explicitly traverse it. Consider the following example:

var xml: XML = <root xmlns:space="http://www.something.com">
 <space:tag>
  <node>Hello, world!</node>
 </space:tag>
</root>;

It is possible to reference node containing Hello, world! using the following:

var node: XML = xml.*::space.node;

Sunday, 14 August 2011

Quick Tip: Checking for XML nodes with a namespace

This is a small follow up to Common Mistakes: XML Namespaces and E4X: when working with namespaces, calling hasOwnProperty() may not work as expected. Consider the following example:

var xml: XML = <root xmlns:space="http://www.something.com">
 <space:tag>
  <node>Hello, world!</node>
 </space:tag>
</root>;

trace( xml.hasOwnProperty( "tag" ) );

In this case, hasOwnProperty() will return false as the 'tag' tag is part of the 'space' namespace. In order to overcome this, use the QName construct, e.g.:

xml.hasOwnProperty( new QName( "http://www.something.com", "tag" ) )

Alternatively, if you have declared a namespace previously, you can do the following:

namespace space = "http://www.something.com"; 

// ...

xml.hasOwnProperty( new QName( space, "tag" ) )

Thursday, 11 August 2011

Quick Tip: FlashBuilder 4.5 Premium Upgrade Download

Just a really quick one: Adobe's consumer site (in Australia, at least) does not have the option to allow the FlashBuilder 4 Premium to FlashBuilder 4.5 Premium Upgrade to be purchased as a download - it's only available as a shipped box. if you need it quicker than that, the solution is to purchase it from the Adobe Business site instead: http://volumelicensing.adobe.com/AU Note that a serial and download link is not provided immediately; after roughly 24 hours you will receive access to the Adobe Volume Licensing site where you can access these details.