By Michael Givens | Article Rating: |
|
December 25, 2006 09:15 AM EST | Reads: |
24,505 |
By the time you read this, another MAX will be in the history books. In fact, with a record attendance of around 3,500 developers and designers, this MAX in Vegas will also be in the record books.
Along with the usual great conference sessions, this year there was a special event referred to as the "BarCamp - The unconference event" called MAXUP. I presented a MAXUP demo, A Flex 2 Signature Panel at MAXUP, where I showed the audience how easy it was to combine a Flex 2 UI for capturing a mouse-created signature with the ActionSript 3 Drawing API and a remote object call to a ColdFusion component. Leveraging work that James Ward blogged about on cayambe.com, I retooled the back-end as a ColdFusion CFC (I make remote object calls to the CFC's functions), and made some minor UI changes to create a signature panel that could be used for online NDA agreements or e-signatures.
Adobe Flex 2 Builder allowed me to lay out very quickly a simple UI where a user can use a mouse (or even better a drawing pen) to write out messages (See Figure 1).
The signature pad captures a visitor's IP address via a remote object call to a CFC method getVisitorIP() during the Flex application's initialize event:
MXML:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize=" roSign.getVisitorIP()"
creationComplete="setColor()" styleName="plain" viewSourceURL="srcview/index.html" horizontalAlign="center"
verticalAlign="top">
<mx:RemoteObject id="roSign"
destination="ColdFusion"
source="flex2_training.components.signature"
fault="mx.controls.Alert.show(event.fault.faultDetail.toString(), 'Alert');"
showBusyCursor="true">
<mx:method name="getVisitorIP" result="my_IP_handler(event.result)"/>
<mx:method name="doUpload" result="my_CFC_handler(event.result)"/>
</mx:RemoteObject>
CFC:
<cffunction name="getVisitorIP" displayname=
"Get IP Address" hint="Returns Visitor's' IP address" access="remote" returntype="string">
<cfset sIP = "#CGI.Remote_Addr#">
<cfreturn sIP />
</cffunction>
Clicking the 'Accept' button triggers the click event that calls the remote object doUpload and corresponding CFC method:
MXML:
<mx:RemoteObject id="roSign"
destination="ColdFusion"
source="flex2_training.components.signature"
fault="mx.controls.Alert.show(event.fault.faultDetail.toString(), 'Alert');"
showBusyCursor="true">
<mx:method name="getVisitorIP" result="my_IP_handler(event.result)"/>
<mx:method name="doUpload" result="my_CFC_handler(event.result)"/>
</mx:RemoteObject>
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import flash.events.Event;
private var sIP:String;
private function doSave():void {
var bd:BitmapData = new BitmapData(canvas.width,canvas.height);
bd.draw(canvas);
var ba:ByteArray = PNGEnc.encode(bd); // See the PNGEnc.as code
roSign.doUpload(ba,sIP); // pass the ByteArray and the Visitor's IP address
}
]]>
</mx:Script>
CFC:
<cffunction name="doUpload" displayname=
"Save Signature" hint="Saves a PNG Signature" access="remote" output="false" returntype="any">
<cfargument name="sigbytes" required="true" type="binary">
<cfargument name="ip_suffix" required="true" type="string">
<cfscript>
var myUUID = "";
var SigFileName = "";
</cfscript>
<!--- create a FileOutputStream --->
<cfobject type="java" action="CREATE" class="java.io.FileOutputStream" name="oStream">
<cfscript>
myUUID = CreateUUID(); // create a unique id
// call init method, passing in the full path to the desired jpg
oStream.init(expandPath("../converted_pngs/signature_#arguments.ip_suffix#_#myUUID#.png"));
oStream.write(arguments.sigbytes);
oStream.close();
SigFileName = getSigFileName("#arguments.ip_suffix#_#myUUID#");
</cfscript>
<cfreturn SigFileName />
</cffunction>
Once the doUpload method is complete, the event.result is returned (the dynamic file name [including both an IP address and a UUID] of the stored PNG image file) and assigned to a Flex string variable, SigFileName:
MXML:
<mx:Script>
<![CDATA[
........ abbreviated for space saving ........
private var SigFileName:String;
private function my_CFC_handler(result:Object):void {
SigFileName = result.toString();
btnView.enabled = true;
}
]]>
</mx:Script>
Clicking on the 'View' button triggers the click event and the function doView(), which opens the page and the saved PNG image as shown in Figure 2:
MXML:
<mx:Script>
<![CDATA[
........ abbreviated for space saving ........
private function doView():void {
var u:URLRequest = new URLRequest("../welcome.cfm?sig=" + SigFileName + ".png");
navigateToURL(u,"_self");
}
]]>
</mx:Script>
The complete code listing is available at: http://webcfmx.no-ip.info/flextraining/signature/bin/srcview/index.html
PNGEnc.as:
package {
import flash.geom.*;
import flash.display.*;
import flash.utils.*;
public class PNGEnc {
public static function encode(img:BitmapData):ByteArray {
// Create output byte array
var png:ByteArray = new ByteArray();
// Write PNG signature
png.writeUnsignedInt(0x89504e47);
png.writeUnsignedInt(0x0D0A1A0A);
// Build IHDR chunk
var IHDR:ByteArray = new ByteArray();
IHDR.writeInt(img.width);
IHDR.writeInt(img.height);
IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA
IHDR.writeByte(0);
writeChunk(png,0x49484452,IHDR);
// Build IDAT chunk
var IDAT:ByteArray= new ByteArray();
for(var i:int=0;i < img.height;i++) {
// no filter
IDAT.writeByte(0);
var p:uint;
if ( !img.transparent ) {
for(var j:int=0;j < img.width;j++) {
p = img.getPixel(j,i);
IDAT.writeUnsignedInt(
uint(((p&0xFFFFFF) << 8)|0xFF));
}
} else {
for(var k:int=0;k < img.width;k++) {
p = img.getPixel32(k,i);
IDAT.writeUnsignedInt(
uint(((p&0xFFFFFF) << 8)|
(p>>>24)));
}
}
}
IDAT.compress();
writeChunk(png,0x49444154,IDAT);
// Build IEND chunk
writeChunk(png,0x49454E44,null);
// return PNG
return png;
}
private static var crcTable:Array;
private static var crcTableComputed:Boolean = false;
private static function writeChunk(png:ByteArray,
type:uint, data:ByteArray):void {
if (!crcTableComputed) {
crcTableComputed = true;
crcTable = [];
for (var n:uint = 0; n < 256; n++) {
var c:uint = n;
for (var k:uint = 0; k < 8; k++) {
if (c & 1) {
c = uint(uint(0xedb88320) ^
uint(c >>> 1));
} else {
c = uint(c >>> 1);
}
}
crcTable[n] = c;
}
}
var len:uint = 0;
if (data != null) {
len = data.length;
}
png.writeUnsignedInt(len);
var p:uint = png.position;
png.writeUnsignedInt(type);
if ( data != null ) {
png.writeBytes(data);
}
var e:uint = png.position;
png.position = p;
var d:uint = 0xffffffff;
for (var i:int = 0; i < (e-p); i++) {
d = uint(crcTable[
(d ^ png.readUnsignedByte()) &
uint(0xff)] ^ uint(d >>> 8));
}
d = uint(d^uint(0xffffffff));
png.position = e;
png.writeUnsignedInt(d);
}
}
}
MAXUP was a great success. Although somewhat nervous before my presentation, I was grinning ear-to-ear afterwards from Adobe's Ted Patrick's encouragement. He said, "Mike, I like the way you used AMF to pass the byte-array to the server-side code." Thanks, Ted. (Figure 3)
Reflecting on how much easier it was to create this example compared to the two-month-long project of doing it with the Flash 7 IDE/Flex 1.5/CFMX 6.1/Apache Batik, I am simply amazed by Flex 2's versatility and efficiency in getting the job done quickly. If you still haven't had a chance to try out the Flex 2-CFMX 7.0.2 dynamic duo, I hope this example helps lure you in a bit further.
Published December 25, 2006 Reads 24,505
Copyright © 2006 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Michael Givens
Mike Givens is the CTO of U Saw It Enterprises, a Web technology consulting firm based in Marietta, GA. He is an Adobe Corporate Champion known to share his experience and evangelism of all things Adobe. Certified in both ColdFusion 5 and as an Advanced CFMX Developer, he has been using ColdFusion since the days of Allaire Spectra. For the last 11 years, he has been seen with his head down - deep in the code.
![]() |
Todd Cullen 12/25/06 01:48:45 PM EST | |||
Great Article! I've always thought it'd be a nice feature to capture someones signature digitally. I had no idea it would be that easy, relatively speaking. Thanks for the post. |
Apr. 20, 2018 03:45 AM EDT Reads: 1,817 |
By Elizabeth White ![]() Apr. 20, 2018 12:45 AM EDT Reads: 4,613 |
By Liz McMillan ![]() Apr. 19, 2018 11:00 PM EDT Reads: 9,806 |
By Liz McMillan ![]() Apr. 19, 2018 10:45 PM EDT Reads: 22,344 |
By Maria C. Horton ![]() Apr. 19, 2018 09:30 PM EDT Reads: 13,523 |
By Elizabeth White ![]() Apr. 19, 2018 08:30 PM EDT Reads: 1,002 |
By Elizabeth White ![]() Apr. 19, 2018 07:00 PM EDT Reads: 5,941 |
By Pat Romanski Apr. 19, 2018 02:00 PM EDT Reads: 2,042 |
By Pat Romanski Apr. 19, 2018 01:45 PM EDT Reads: 1,144 |
By Pat Romanski Apr. 19, 2018 01:30 PM EDT Reads: 2,063 |
By Elizabeth White Apr. 19, 2018 01:30 PM EDT Reads: 1,563 |
By Liz McMillan Apr. 19, 2018 01:15 PM EDT Reads: 1,584 |
By Pat Romanski ![]() Apr. 19, 2018 12:45 PM EDT Reads: 5,221 |
By Liz McMillan ![]() Apr. 19, 2018 12:45 PM EDT Reads: 3,642 |
By Yeshim Deniz Apr. 19, 2018 12:30 PM EDT Reads: 3,889 |
By Yeshim Deniz Apr. 19, 2018 12:15 PM EDT Reads: 4,863 |
By Liz McMillan ![]() Apr. 19, 2018 11:00 AM EDT Reads: 7,012 |
By Yeshim Deniz ![]() Apr. 19, 2018 08:30 AM EDT Reads: 1,838 |
By Liz McMillan Apr. 19, 2018 08:15 AM EDT Reads: 2,463 |
By Yeshim Deniz Apr. 19, 2018 08:00 AM EDT Reads: 3,003 |