Learning Flex 4 | O'Reilly Media

Archive for the ‘AIR’ Category

Google Search Application in Flex 4

with one comment

Unfortunately, the Yahoo! web services, which we consumed using the ASTRA Flex library and introduced near pages 232-233, are deprecated and discontinued, devaluing the book I realize many of you bought with hard-earned money. In light of this reversal, I applied myself to replacing the YahooSearch example with something comparable.

This alternative arrangement consumes the Google Search API, which is simplified into callable methods exposed through googleas3api.swc—an ActionScript library package created by Joris Timmerman (my apologies if I am incorrect about the creator and maintainer). Because googleas3api is an ActionScript library and not a Flex library, we can’t instantiate  the service component in MXML like we did with the Yahoo! search component (but to be honest, I didn’t even try so maybe I am wrong about that), so this alternate example uses more ActionScript and looks a little different. On the bright side, I believe that is good for you; on the other hand, it will require more of your intuition at timesto understand the similarities and differences between the original YahooSearch code illustrated in the book, which will fail at runtime (i.e. when you perform a search), and this GoogleSearch variation, which looksdifferent in code, but performs as you would expect.

Application: GoogleSearch.mxml

The application requires two supporting libraries—as3corelib.swc and googleas3api.swc. Make sure to download these, unzip if necessary, and copy their SWC files into the “libs” folder of your Flex 4 project.

<?xml version="1.0" encoding="utf-8"?>
<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"
 creationComplete="onComplete();"
 defaultButton="{searchButton}">
 
 <fx:Script>
  <![CDATA[
   /**
    * This project requires two reference libraries.
    * 
    * as3corelib.swc:
    *   https://github.com/mikechambers/as3corelib
    *   (Downloads > Download Packages > as3corelib-93.zip)
    *   Find the .swc file in the extracted "lib" folder.
    *
    * googleas3api.swc:
    *   http://code.google.com/p/googleas3api
    *   (Downloads > googleas3api.swc)
    *
    * Copy/paste or drag/drop these into the "libs" folder
    * for this Flex 4 project.
    */
   import be.boulevart.google.ajaxapi.search.GoogleSearchResult;
   import be.boulevart.google.ajaxapi.search.web.GoogleWebSearch;
   import be.boulevart.google.events.GoogleAPIErrorEvent;
   import be.boulevart.google.events.GoogleApiEvent;
   
   import mx.collections.ArrayCollection;
   
   
   private var googleSearch:GoogleWebSearch;
   
   private var searchResultAC:ArrayCollection;
   
   
   
   private function onComplete():void
   {
    googleSearch = new GoogleWebSearch();
    googleSearch.addEventListener(
       GoogleApiEvent.WEB_SEARCH_RESULT, searchResult);
    googleSearch.addEventListener(
       GoogleAPIErrorEvent.API_ERROR, searchFault);
   }
   
   private function onSearch():void
   {
    // See the API library example here..
    // http://code.google.com/p/googleas3api
    // "en" means English
    // ..try "de", which is Danish, etc..
    googleSearch.search(queryTI.text, 0, "en");
    
    // you can also leave the string out, which may
    // allow Google to introspect by IP and "guess"
    // the correct language (I am supposing this):
    //  googleSearch.search(queryTI.text, 0);
   }
   
   private function searchResult(event:GoogleApiEvent):void
   {
    // Cast the event.data response as GoogleSearchResult
    // to enable code completion and compile-time error checking.
    var searchResult:GoogleSearchResult;
    searchResult = event.data as GoogleSearchResult;
    
    // searchResult.results is an Array, so we can pass it
    // into a new ArrayCollection and use it as a dataprovider.
    searchResultAC = new ArrayCollection(searchResult.results);
    resultsList.dataProvider = searchResultAC;
   }
   
   private function searchFault(event:GoogleAPIErrorEvent):void
   {
    // Catch search faults and trace to the console.
    trace(String(event.responseDetails));
   }
  ]]>
 </fx:Script>
 
 <s:VGroup left="10" right="10" top="10" bottom="10">
  <s:Label text="Google Search:" fontWeight="bold"/>
  <s:HGroup>
   <mx:FormItem label="Query:" fontWeight="bold">
    <s:TextInput id="queryTI" width="350"/>
   </mx:FormItem>
   <mx:FormItem>
    <s:Button id="searchButton" label="Search"
        click="onSearch();"/>
   </mx:FormItem>
  </s:HGroup>
  <s:List id="resultsList" width="100%" height="100%"
     itemRenderer="SearchItemRenderer"/>
 </s:VGroup>
 
</s:Application>

SearchItemRenderer.mxml

The ItemRenderer used in the application is created using the same approach described in pages 235-238. However, note the use of the Ternary Operator to create a shorthand if..then conditional statement; this is described on page 354 as an inline if..then for use in MXML code, but here I’m demonstrating its use in an ActionScript-only context.

Note: To work with the application code, above, this custom ItemRenderer needs to be created in the default package (i.e. the local package), the same folder as GoogleSearch.mxml.

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    autoDrawBackground="true"
    creationComplete="onComplete();">
 <fx:Script>
  <![CDATA[
   import be.boulevart.google.ajaxapi.search.web.data.GoogleWebItem;
   import flashx.textLayout.conversion.TextConverter;
   
   private function onComplete():void
   {
    // Cast the data record as GoogleWebItem to expose
    // code commpletion and enjoy tighter compile-time
    // error-checking. We could use object notation
    // on the data variable like this..
    //   urlLabel.text = "From: " + data.url;
    // but casting the object is preferred.
    var item:GoogleWebItem = data as GoogleWebItem;
    
    urlLabel.text = "From: " + item.url;
    
    // Create "content teaser" with no more than 100 chars..
    var teaserString:String=item.title + " | " + item.content;
    
    // Shorthand if..then using the ternary operator..
    // See pg. 354 for background on this syntax.
    teaserString = (teaserString.length>99)
        ? teaserString.substr(0,99)+"..[more].."
        : teaserString ;
    
    contentTA.textFlow = TextConverter.importToFlow(teaserString,
          TextConverter.TEXT_FIELD_HTML_FORMAT);
    
    callLater(invalidateDisplayList);
    callLater(validateNow);
         
   }
   
   private function onClick(event:MouseEvent, url:String):void
   {
    var urlRequest:URLRequest = new URLRequest(url);
    navigateToURL(urlRequest, "_blank");
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%" gap="2" paddingTop="2" paddingBottom="2">
  
  <s:Label id="urlLabel" fontWeight="bold" buttonMode="true"
     click="onClick(event, data.url);"/>
  
  <s:TextArea id="contentTA" width="100%" heightInLines="1"
     verticalScrollPolicy="off"
     editable="false" selectable="false"
     contentBackgroundAlpha="0"
     borderVisible="false"/>
 </s:VGroup>
</s:ItemRenderer>

Advertisements

Written by elrobis

29 September 2011 at 10:06 am

Posted in AIR, Flex 4

Load PDF Content into an AIR Application

with 6 comments

It’s deceptively simple to load a PDF file into an AIR appliction. Basically, point an HTMLLoader at a URLRequest—which can take a local file path—add the HTMLLoader to a UIComponent, then add the UIComponent to a container.

In this case, I added the UIComponent directly to the application, but it stands to reason you could put it in any container.

Flex 4 / AIR Application Code

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication title="PDF Viewer"
      xmlns:fx="http://ns.adobe.com/mxml/2009"
      xmlns:s="library://ns.adobe.com/flex/spark"
      xmlns:mx="library://ns.adobe.com/flex/mx"
      height="600" width="525"
      backgroundColor="#000000">

<s:Button label="Load The PDF" click="loadPdf(path)" top="4" left="4"/>

<s:Button label="Close The PDF" click="closePdf()" top="4" right="4"/>

<fx:Script>
<![CDATA[
  import mx.core.FlexGlobals;
  import mx.core.UIComponent;

  private var pdfUIC:UIComponent;

  // double forward-slashes necessary for win file paths..
  private var path:String = "C:\\pdf_files\\original\\filename.pdf";

  private function loadPdf(fullPathToPDF:String):void
  {
    var pdfRequest:URLRequest = new URLRequest(fullPathToPDF);
    var pdfHTMLLoader:HTMLLoader = new HTMLLoader();

    // 1) load the PDF into an HTMLLoader (Air-only)
    pdfHTMLLoader.height = FlexGlobals.topLevelApplication.height-70;
    pdfHTMLLoader.width = FlexGlobals.topLevelApplication.width-60;
    pdfHTMLLoader.load(pdfRequest);

    // 2) add the HTMLLoader to a UIComponent
    pdfUIC = new UIComponent();
    pdfUIC.addChild(pdfHTMLLoader);
    pdfUIC.name = "pdfPrintPreview";

    // 3) add the UIComponent to the application
    pdfUIC.y = 33;
    pdfUIC.x = (FlexGlobals.topLevelApplication.width - pdfHTMLLoader.width) / 2;
    this.addElement(pdfUIC);
  }

  // destroy the PDF, call garbage collector..
  private function closePdf():void
  {
    if(pdfUIC)
    {
      this.removeElement(pdfUIC);
      
      // make the object a candidate for garbage collection..
      pdfUIC = null;
      
      flash.system.System.gc();
    }
  }

]]>
</fx:Script>

</s:WindowedApplication>

Lo! The sample application running in Debug mode..

Load a PDF into Adobe AIR Using Flex 4

Load a PDF into Adobe AIR Using Flex 4

Written by elrobis

2 July 2011 at 6:41 am

Posted in ActionScript 3, AIR, Flex 4

Create an AIR Application to Write Your XML

with 6 comments

I keep finding myself wishing we spent more time in the book discussing Adobe AIR, so I decided to add some extra content here.  Of course, if you run into any questions or concerns, feel free to post them here.

The following application demonstrates a few key uses for AIR’s File() class, particularly:

1) Open a dialog for browsing the file system
2) Create a new file in the file system
3) Use the FileStream() class to write content into a file

Background

The application presented below builds upon various topics covered in Learning Flex 4.  I’m assuming you’ve become comfortable with XML basics. If you need some extra info, chapters 4, 11, 12, and 16 discuss using XML data in Flex applications.  Appendix C (PHP Basics) presents a solution for first concatenating XML as a text string on the server, then receiving that text later in the Flex application as an XML object.  This exercise builds upon these concepts.

The use case for the following application is a scenario where you require fairly dynamic XML data, but you’ve decided it’s impractical to handle XML generation on the web server.  To support the book, I considered the case of the PhotoGallery application (Chapt 12), which relies on some ready-made XML to reference the image content.  While manually typing the XML for about a dozen photos isn’t a big problem, this would quickly become tedious if you were working with a hundred images.  Similarly, the task would consume a lot of time if you frequently recreate or revise the XML.  With this use case in mind, it may be desirable to write a program that automates the XML writing for you.  And hey, if nothing else, writing a 100-line AIR program to handle a programming scenario is generally more fun than writing a couple hundred lines of XML.  :)

Here’s the gist:   When you browse to a folder in your local file system, the AIR app will inspect the files in that directory and build an XML file required for the PhotoGallery application (i.e. photos.xml).  While likely you’d only want to run this utility on a folder of images intended for the PhotoGallery, the app will ensure you’re only adding references to PNG, JPG, and GIF files–this could be helpful in case there was an unassuming LOG file looming about, marooned in the otherwise clean and purposed images folder.

What to Look For

Selecting a directory in the file system:

The function getDirectory()  shows how to use the browseForDirectory() method on a File() object to select and access file objects as directories via the Flex UI.

Creating a New (XML) File and Writing its Content:

The function saveXmlFile() demonstrates how to create a new File() object and use the FileStream() class to write data into the new File(). 

Notice how we use the nativePath property of the selected directory (file.nativePath), followed by the operating system’s separator symbol (File.separator, as a static property), followed by a file name provided by the user, followed by the desired file extension (.xml)–all concatenated together–to establish where to save the new XML file (newXMLFile).

Notice also that the FileStream() object must open() the new file (newXMLFile) in FileMode.WRITE in order to access it for modification purposes.  The FileStream() method writeUTFBytes() is called to pass the concatenated XML string into the new file.  When finished, the close() method is called on the FileStream() to finalize the data input.

The Try..Catch Block!

The Try..Catch block (see the saveXMLFile() function) is good stuff.  It works like this:  First, in the Try section, you try some code that you suspect capable of throwing unanticipated errors; this section should be programmed to handle the task completely, as you’re assuming it will work more often than not.  Next, you follow the Try with a Catch section that allows your program to gracefully catch any “surprise!”, unanticipated errors that might happen when attempting to process the function.  In this case, our Try section ends with an Alert box that notifies us if the XML file is created sucessfully.  Alternatively, our Catch section creates an Alert box notifying the user of the exact error message (er.message) in hopes that it may be detailed enough for the user to consider what caused the error then followup with a successful attempt.

Caveat:  It is too easy to abuse the Try..Catch system as an all-purpose error handling tactic.  Good practice dictates using Try..Catch only when you want to account for unknowns that may throw a wrench, or a chainsaw, or some other mess-making boobalatosomitch into your function.  The Try..Catch system is fairly “expensive” as far as system resources go; plus, it’s just wise to think about your code and handle your use case with a true understanding instead of an attitude that could be epitomized by the phrase “well, I really hope this works, but just in case it doesn’t I’ll Try it and Catch it.”  ….please, just don’t do that.

The String class’s split() method–someString.split(“.”)

The split() method, which is available to String objects, is ultra-useful.  If you have a text String–in this case fileNameAndExt, which is the filename, followed by a dot (.), followed by the file’s extension (.png, .jpg, .gif, .xml, etc..)–you can call the String’s split() method to divide that text string into an Array of String objects as pieces separated by a delimiter.  For this example, the dot (.) is the delimiter. 

This code:
var fileNameArray:Array = new Array();
var fileNameAndExt:String = fileObj.name;      // “photo.jpg”
fileNameArray = fileNameAndExt.split(“.”);

Would create the following index values in the Array:
fileNameArray[0] = “photo”
fileNameArray[1] = “jpg”

Breaking apart the filename into its component portions helps us later when we’re concatenating together our XML text.

The Application Code

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication
 xmlns:fx="http://ns.adobe.com/mxml/2009"
 xmlns:s="library://ns.adobe.com/flex/spark"
 xmlns:mx="library://ns.adobe.com/flex/mx"
 minWidth="600" minHeight="400">

 <fx:Script>
  <![CDATA[
   import mx.controls.Alert;

   private var file:File = new File();
   private var xmlString:String;

   private function getDirectory():void{
    file.browseForDirectory("Select Image Directory");
    file.addEventListener(Event.SELECT, directorySelected);
   }

   private function directorySelected(event:Event):void{
    var filesArray:Array = new Array();
    filesArray = file.getDirectoryListing();

    // create the XML..
    xmlString = '';
    xmlString += '<?xml version="1.0" encoding="utf-8"?>\n';
    xmlString += '<photos>\n';

    // loop through file objects and add XML nodes..
    for each(var fileObj:File in filesArray){
     // make sure the file is not a directory, etc.,
     // or an image format not supported in Flash Player..
     if(((fileObj.isDirectory == false) &&
      (fileObj.isHidden == false) &&
      (fileObj.isPackage == false) &&
      (fileObj.isSymbolicLink == false)) &&
      ((fileObj.extension.toUpperCase() == "PNG") ||
      (fileObj.extension.toUpperCase() == "JPG") ||
      (fileObj.extension.toUpperCase() == "GIF"))){

      // separate "name" into filename AND extension..
      var fileNameAndExt:String = fileObj.name;
      var fileNameArray:Array = new Array();
      var fileName:String;
      var ext:String;
      var path:String;
      var thumbPathNameAndExt:String;
      var imagePathNameAndExt:String;

      fileNameArray = fileNameAndExt.split(".");
      fileName = fileNameArray[0].toString();
      ext = fileNameArray[1].toString();
      // of course, use your own URL here..
      path = "http://www.learningflex4.com/photos/";
      thumbPathNameAndExt = path + fileName + '_th.' + ext;
      imagePathNameAndExt = path + fileNameAndExt;

      xmlString += 
       '<photo credit=""\n' +
        '  title="' + fileName + '"\n' +
        '  thumb="' + thumbPathNameAndExt + '"\n' +
        '  image="' + imagePathNameAndExt + '"/>\n';
     }
    }

    // close the XML and show content in the TextArea..
    xmlString += '</photos>';
    textArea.text = xmlString;
   }

   private function saveXmlFile():void{
    try{
     var newXMLFile:File = new File();
     var fileStream:FileStream = new FileStream();
     var sep:String = File.separator;
     var xmlFilePath:String =
      file.nativePath + sep + fileNameTI.text + ".xml";

     newXMLFile.nativePath = xmlFilePath;
     fileStream.open(newXMLFile, FileMode.WRITE);
     fileStream.writeUTFBytes(xmlString);
     fileStream.close();
     Alert.show("XML Data File Created..", "XML-Builder");
    }

    catch(er:Error){
     trace(er.message);
     Alert.show("XML Data File FAILURE!!" + "\n\n" +
      er.message, "XML-Builder");
    }
   }
  ]]>
 </fx:Script>

 <s:layout>
  <s:VerticalLayout paddingTop="14" horizontalAlign="center"/>
 </s:layout>

 <s:Label text="XML-Builder for the PhotoGallery Application"/>
 <s:Button label="Select Folder to Parse" click="getDirectory()"/>
 <s:TextArea id="textArea" width="90%" height="75%"/>
 <s:HGroup verticalAlign="middle">
  <s:Label text="(provide filename WITHOUT extension)" fontStyle="italic"/>
  <s:TextInput id="fileNameTI"/>
  <s:Button label="Save File" click="saveXmlFile()"/>
 </s:HGroup>

</s:WindowedApplication>

Written by elrobis

7 December 2010 at 8:06 pm

Posted in AIR, Flex 4