Using CouchDB from Air applications with CouchDB Flex

November 3rd, 2009 by Andrea Franz

In the last weeks I played a lot with CouchDB and I love it. I always used it from ruby but yesterday I tried to use it with flex. I started a new Actionscript project called CouchDB Flex, you can browse the source from github. The implementation is inspired by couchrest, but it’s asynchronous because I use URLLoader internally. Since the flash player doesn’t support the PUT and DELETE HTTP methods, you must create an AIR project if you want to try the library. You can find a compiled swc here, put it in your libs folder and you are ready to go. Here a small example, but it’s still in development, and a lot of functionality are not implemented yet.

CouchdbFlexExample.mxml: the main mxml file, it’s just a window with a textarea that will display the data loaded from CouchDB.

<?xml version="1.0" encoding="utf-8"?>
<c:App xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:c="com.gravityblast.couchdbflexexample.*">
  <s:TextArea id="textarea" width="100%" height="100%" text="{log}"/>			
</c:App>

com/gravityblast/couchdbflexexample/Contact.as: the model I’ll save on CouchDB. By default the model has a name and an email, but I can add more properties since it’s a dynamic class.

package com.gravityblast.couchdbflexexample
{
  import com.gravityblast.couchdb.Document;
 
  public dynamic class Contact extends Document
  {		
    public var name:String;
    public var email:String;
 
    public function Contact()
    {
      super();
    }
  }
}

com/gravityblast/couchdbflexexample/App.as: the main application, where I create a new Contact Document, and I load all the contacts using a view created with the Design and View classes.

package com.gravityblast.couchdbflexexample
{
  import com.gravityblast.couchdb.CouchDb;
  import com.gravityblast.couchdb.Database;
  import com.gravityblast.couchdb.Design;
  import com.gravityblast.couchdb.View;
  import com.gravityblast.couchdb.events.CouchRestEvent;
  import com.gravityblast.couchdb.events.DocumentEvent;
  import com.gravityblast.couchdb.events.ViewEvent;
 
  import mx.events.FlexEvent;
 
  import spark.components.TextArea;
  import spark.components.WindowedApplication;
 
  public class App extends WindowedApplication
  {		
    private var couchdb:CouchDb;
    private var database:Database;
 
    [Bindable]
    public 	var log:String;
    public 	var textarea:TextArea;
 
    public function App()
    {			
      this.couchdb = new CouchDb();
      this.database = this.couchdb.database("couchdb-flex-example");
      this.log = "";
      this.addEventListener(FlexEvent.CREATION_COMPLETE, this.creationCompleteHandler);
      super();
    }
 
    private function creationCompleteHandler(event:FlexEvent):void
    {			
      this.database.create(this.databaseCreateHandler);	
    }
 
    private function databaseCreateHandler(event:CouchRestEvent):void
    {
      trace(event.data);
      if (event.json.ok || event.json.error.toString() == "file_exists")
      {
        var map:String = "function(doc) {if (doc['couchdb-flex-type'] == 'com.gravityblast.couchdbflexexample::Contact') emit(null, doc);}";
        var view:View = new View("all", map);
        var design:Design = new Design("contacts");
        design.addView(view);
        design.save(this.designSaveHandler);	
      }
    }
 
    private function designSaveHandler(event:DocumentEvent):void
    {
      trace(event.data);									
      if (event.json.ok || event.json.error == "conflict")			
        this.createContacts();
      else
        trace("Error saving design document: " + event.data)			
    }
 
    private function createContacts():void
    {
      var contact:Contact = new Contact();
      contact.name    = "jack";
      contact.email   = "jack@example.com";
      contact.address = "Jack Street";
      contact.save(this.contactSaveHandler);			
    }
 
    private function contactSaveHandler(event:DocumentEvent):void
    {			
      trace(event.data);
      var contact:Contact = event.document as Contact;
      contact.address = "Jack Street updated at " + (new Date()).toString();
      contact.save(this.contactUpdateHandler);			
    }
 
    private function contactUpdateHandler(event:DocumentEvent):void
    {			
      trace(event.data);
      View.load("contacts", "all", {}, this.contactsLoadHandler);
    }
 
    private function contactsLoadHandler(event:ViewEvent):void
    {					
      for each (var contact:Contact in event.documents)
      {
        for (var property:String in contact.attributes)
        {
          this.log += property + ": " + contact[property] + "\n";	
        }
        this.log += "-------------\n";
      }
    }
  }
}

I’ll write more about the library as soon as it’s more stable.


Creating PDF with images using flex and AlivePDF

July 19th, 2009 by Andrea Franz

Today I’m working on an AIR application, and I need to create a PDF starting from a list of images. This PDF needs to have one image for each page. I used AlivePDF, a very useful ActionScript3 library for PDF generation, and I created a simple class that takes an array of image paths and creates the PDF I need.

Here the class I use:

package
{	
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
  import flash.net.URLLoader;
  import flash.net.URLLoaderDataFormat;
  import flash.net.URLRequest;
  import flash.utils.ByteArray;
 
  import org.alivepdf.pdf.PDF;	
  import org.alivepdf.display.Display;
  import org.alivepdf.images.ResizeMode;
  import org.alivepdf.layout.Layout;
  import org.alivepdf.layout.Orientation;
  import org.alivepdf.layout.Size;
  import org.alivepdf.layout.Unit;
  import org.alivepdf.saving.Method;
 
  public class PdfGenerator extends EventDispatcher
  {		  
    private var pdf:PDF;
    private var images:Array;		
    private var outFilePath:String;
    private var completedPages:int;
 
    public function PdfGenerator(images:Array, outFilePath:String)
    {
      this.outFilePath = outFilePath;
      this.images = images;
    }				
 
    public function generate():void
    {						
      this.completedPages = 0;
      this.pdf = new PDF(Orientation.LANDSCAPE, Unit.MM, Size.A4);
      this.nextPage();		
    }
 
    private function nextPage():void
    {
      this.completedPages < this.images.length ? this.loadImage(this.images[this.completedPages++]) : this.save();
    }
 
    private function loadImage(path:String):void
    {						
      var urlLoader:URLLoader = new URLLoader();			
      urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
      urlLoader.addEventListener(Event.COMPLETE, this.onImageLoadComplete);
      urlLoader.load(new URLRequest(path));						
    }
 
    private function onImageLoadComplete(event:Event):void
    {						
      this.pdf.addPage();
      this.pdf.addImageStream(ByteArray(event.target.data), 0, 0, 0, 0, 1, ResizeMode.FIT_TO_PAGE);
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, false, false, this.images.length, this.completedPages));
      this.nextPage();
    }
 
    private function save():void
    {				
      this.pdf.end();
      this.writeOutFile();												
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE));
    }
 
    private function writeOutFile():void
    {
      var fileStream:FileStream = new FileStream();								
      fileStream.open(new File(this.outFilePath), FileMode.WRITE);			
      fileStream.writeBytes(this.pdf.save(Method.LOCAL));
      fileStream.close();
    }
  }
}

Basically for each image it creates a new URLRequest and when the request is completed it adds a new page with the image. As you can see I also dispatch a custom Event called PdfGeneratorEvent. That because I need to create a PDF with a large number of images, and doing like this I can give a feedback to the user with a ProgressBar that display the generation progress.

package
{
  import flash.events.Event;
 
  public class PdfGeneratorEvent extends Event
  {			
    public static const PDF_GENERATOR_PAGE_COMPLETE:String = "PDF_GENERATOR_PAGE_COMPLETE";
    public static const PDF_GENERATOR_COMPLETE:String      = "PDF_GENERATOR_COMPLETE";			
 
    public var totalPages:int;
    public var completedPages:int;
 
    public function PdfGeneratorEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, totalPages:int=0, completedPages:int=0)
    {			
      super(type, bubbles, cancelable);
      this.totalPages     = totalPages;
      this.completedPages = completedPages;
    }
  }
}

And here the main file that uses this class:

public function createPdf(images:Array):void
{
  var generator:PdfGenerator = new PdfGenerator(images, "/path/to/my/test.pdf");
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE, this.onPdfComplete);
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, this.onPdfPageComplete);				
  generator.generate();				
}
 
public function onPdfPageComplete(event:PdfGeneratorEvent):void
{					
  progressBar.setProgress(event.completedPages, event.totalPages);
  progressBar.label = event.completedPages + " of " + event.totalPages;															
}
 
public function onPdfComplete(event:PdfGeneratorEvent):void
{											
  trace("completed")
}