Read the value of the HTMLLoader.pdfCapability
property to check that the user has a version of Adobe Reader or
Adobe Acrobat 8.1 (or newer) installed.
The HTMLLoader.pdfCapability
property is set to one of the following constants of the HTMLPDFCapability
class:
HTMLPDFCapability.STATUS_OK
HTMLPDFCapability.ERROR_INSTALLED_READER_NOT_FOUND
HTMLPDFCapability.ERROR_INSTALLED_READER_TOO_OLD
HTMLPDFCapability.ERROR_PREFERRED_READER_TOO_OLD
If the property has the value of the constant HTMLPDFCapability.STATUS_OK
, then you are sure
that PDF content can be loaded into the HTMLLoader
object. It also can happen that the
user does not have the right version of Adobe Reader installed. If the
installed Adobe Reader version is too old, then the constant ERROR_INSTALLED_READER_TOO_OLD
is returned
when you call the HTMLLoader.pdfCapability
property. If the user
has different versions of Adobe Reader installed but the one that is set
up to handle PDF content is older than Adobe Reader 8.1, the constant
ERROR_PREFERRED_READER_TOO_OLD
is
returned. If the user has no version of Adobe Reader installed at all,
then the HTMLLoader
object will not
be able to display any PDF content.
Working with PDF content on Windows involves one other important caveat. If Adobe Acrobat or Adobe Reader version 7.x (or newer) is opened on the user’s system, AIR references that specific opened version even if another version (8.1 or newer, for example) is installed on that system also. In this case, your PDF content will not load in your AIR application. When your PDF content is not loading within an acceptable time frame, it’s a best practice to inform the user that Adobe Acrobat must be closed while running your application.
The following code detects whether a user can display
PDF content in an AIR application. If the STATUS_OK
code is returned, then the
application can display the content; if it isn’t, then you must trace
the error code that corresponds to the HTMLPDFCapability
error
object.
if(HTMLLoader.pdfCapability == HTMLPDFCapability.STATUS_OK) { trace("The user is able to display PDF content !"); } else { trace("PDF cannot be displayed. Error code:", HTMLLoader.pdfCapability); }
The following code detects whether a user can display
PDF content in an AIR application. If the STATUS_OK
code is returned, then the
application can display the content; if it isn’t, then you need to
trace the error code that corresponds to the HTMLPDFCapability
error
object.
if(air.HTMLLoader.pdfCapability == air.HTMLPDFCapability.STATUS_OK) { air.trace("The user is able to display PDF content "); } else { air.trace("PDF cannot be displayed. Error code:", HTMLLoader.pdfCapability); }
Create an HTMLLoader
instance,
set the dimensions, and load the path of a PDF.
Loading and rendering PDF content in your AIR application are
identical to loading and rendering HTML. All you need to do is specify
the path to the actual PDF when requesting the content to load into the
HTMLLoader
instance.
The following code snippet loads a PDF document using
the HTMLLoader
class. The HTMLLoader
class loads a URLRequest
object that points to the actual
PDF document.
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" height="900" width="700" horizontalAlign="left"> <mx:Script> <![CDATA[ private function loadPDF():void { var request:URLRequest = new URLRequest("test.pdf"); var pdf:HTMLLoader = new HTMLLoader(); //set the dimensions pdf.height = 800; pdf.width = 600; //call the load method pdf.load(request); //add the HTMLLoader object to the display list to make it visible container.addChild(pdf); } ]]> </mx:Script> <mx:VBox> <mx:Label text="Dipsplay 'test.pdf'" /> <mx:Button label="Load" click="loadPDF()" /> </mx:VBox> <mx:Spacer id="container" /> </mx:WindowedApplication>
If you want to load a PDF in your AIR application using
HTML, you follow similar steps for loading a PDF document into a
browser. For example, you can load a PDF into the top-level HTML
content of a window, into an object
tag, into a frame, or into an iframe
.
This is the HTML code for loading a PDF into an iframe
:
<html> <body> <h1>PDF test</h1> <iframe id="pdfFrame" width="100%" height="100%" src="test.pdf"/> </body> </html>
This is the HTML code for loading a PDF into an object
tag:
<object id="PDFObj" data="test.pdf" type="application/pdf" width="100%" height="100%"/>
In your AIR application, load an HTML file (wrapper) that holds your PDF. Use JavaScript in your wrapper to communicate in both directions with the embedded JavaScript in your PDF file.
Communicating with the PDF has many advantages. If a PDF document has, for example, a form, you can simply execute a JavaScript function when a user clicks a button in the form. You also can control page navigation with custom buttons in your PDF or can control the magnification of the document, to name only a few possibilities.
JavaScript handles the communication between your AIR application and the PDF document. Your AIR application loads an HTML file, and that HTML file loads the PDF file. In this way, you can easily send messages to the PDF or receive messages from the PDF in an AIR application. Figure 6-1 diagrams a simple communication flow.
As you can see, the AIR application first loads an HTML file (as you did in Loading a PDF Document), and that HTML file loads the actual PDF document. If you want to communicate with the loaded PDF, however, you need to set up some JavaScript in the PDF document. This is possible through the JavaScript extensions for Adobe Acrobat. (You can find full details on the JavaScript extensions for Adobe Acrobat in the Acrobat Developer Center at http://www.adobe.com/devnet/acrobat/javascript.html.)
If you want to send messages to the PDF (for example, “Next Page”), then you have to make sure the PDF can react to these messages. You need Adobe Acrobat Professional to add JavaScript to the PDF that can react to the messages you send. This kind of JavaScript is called document-level JavaScript.
Every PDF document has a HostContainer
object. This object manages the communication between a PDF
document and a corresponding host container that the document is
contained within, such as an HTML page. You specify the host container
for a document using the document’s hostContainer
property.
The HostContainer
object has a
messageHandler
property that is
invoked when JavaScript in the host container calls the postMessage
method. This property should
reference a notification object that has three methods:
onMessage
: A method that is
called in response to postMessage
onError
: A method that is
called in response to an error
onDisclose
: A method that
is called to determine whether the host application is permitted to
send messages to the document
The following code is a typical host container message handler:
this.hostContainer.messageHandler = { onMessage: function(aMessage) { for(var i = 0; i < aMessage.length; i++) console.println(aMessage[i]); }, onError: function(error, aMessage){ } , onDisclose: function (cURL,cDocumentURL){return true;} };
The this
keyword in the first
line of the code points to the Doc
object of the document. The Doc
object provides the interface between a PDF document and the JavaScript
interpreter. It provides methods and properties for accessing a PDF
document. For this example, use the pageNum
property of the Doc
object to jump from one page to
another.
To add the JavaScript to the PDF, you need Adobe Acrobat. Create a new PDF document, and choose Advanced→Document Processing→Document JavaScripts. In the JavaScript Functions dialog box, type onMessage as the script name, and then click the Add button.
In the JavaScript Editor window, type the following code:
function myOnMessage(aMessage) { if (aMessage.length==1) { switch(aMessage[0]) { case "PageUp": pageNum--; break; case "PageDn": pageNum++; break; default: app.alert("Unknown message: " + aMessage[0]); } } else { app.alert("Message from hostContainer: " + aMessage); } } function myOnDisclose(cURL,cDocumentURL) { return true; } function myOnError(error, aMessage) { app.alert(error); } var msgHandlerObject = new Object(); msgHandlerObject.onMessage = myOnMessage; msgHandlerObject.onError = myOnError; msgHandlerObject.onDisclose = myOnDisclose; this.hostContainer.messageHandler = msgHandlerObject;
When you save the PDF file, be sure to hide the menu bar, toolbars, and window controls. By doing so, you restrict the user’s navigation options to the controls in your Flex interface. You can set these values in the Document Properties dialog box.
Now you are ready to load the PDF in an AIR application. Because AIR applications can show HTML pages, embed the PDF in an HTML wrapper:
<html> <body> <object id="PDFObj" data="PDF_COM_with_AIR.pdf" type="application/pdf" width="100%" height="100%"/> </body> </html>
You can now load the HTML file with the Flex HTML component:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" width="900" height="700" layout="vertical"> <mx:HTML id="pdfHtml" location="PDF_COM_with_AIR.html" width="100%" height="100%" /> </mx:WindowedApplication>
If you run your application now, you will see your PDF document rendered in the AIR application.
If you add, for example, two buttons in the application, you can
control the paging by sending the right message String
to the PDF document:
<mx:Button id="btn3" label="<" click="sendMessage('PageUp')" width="35" enabled="false" /> <mx:Button id="btn4" label=">" click="sendMessage('PageDn')" width="35" enabled="false" />
When the user clicks the buttons, the sendMessage
function sends commands to the PDF
object using the postMessage
method
of the PDF object. The sendMessage
function in your script block looks like this:
private function sendMessage(message:String):void { var pdfObj:Object = pdfHtml.htmlLoader.window.document.getElementById("PDFObj"); pdfObj.postMessage([message]); }
The document-level JavaScript in the PDF file has a hostContainer.messageHandler
object defined,
and that object knows how to handle the incoming messages. In this way,
you can communicate with a PDF document from inside an AIR
application.
You’ll now learn how you can communicate from inside the PDF to the AIR application. It is actually a similar technique, as you will see in the next example.
You want to communicate from a loaded PDF document to your AIR application.
In your AIR application, load an HTML file (wrapper) that holds your PDF. Use JavaScript in your wrapper to communicate in both directions with the embedded JavaScript in your PDF file.
A simple form-processing example demonstrates communication from a PDF document to its hosting AIR environment. This example loads a PDF document, which includes a form and a submit button, into an AIR application. When the user fills in the form and clicks the submit button, the form data is sent to the hosting AIR application, and a customized “thank you” message is displayed to the user.
ActionScript code in an AIR application cannot directly communicate with JavaScript in the PDF. ActionScript can communicate with the JavaScript in the HTML page, however, and that JavaScript code can communicate with the JavaScript in the loaded PDF file.
Figure 6-2 shows the simple PDF contact form.
When the user clicks the button, the JavaScript code gets the
values from the respective fields in the PDF form and stores them in an
Array
called arrMsg
. By calling the postMessage
method, the data is sent to the
hostContainer
, which is the
JavaScript in the HTML page that loads the PDF:
this.hostContainer.postMessage(arrMsg);
The complete JavaScript code on the click
action of the submit button in the PDF
looks like this:
var strName = this.getField("nameField").value.toString(); var strSurname = this.getField("surnameField").value.toString(); var strNumber = this.getField("numberField").value.toString(); var strStreet = this.getField("streetField").value.toString(); var strCity = this.getField("cityField").value.toString(); var strPostalcode = this.getField("postalcodeField").value.toString(); var strCountry = this.getField("countryField").value.toString(); var strEmailAddress = this.getField("EmailAddressField").value.toString(); var boNewsLetter = this.getField("newsletterCheck").value; var arrMsg = null; try{ arrMsg = new Array(); arrMsg=[strName,strSurname,strNumber,strStreet,strCity,strPostalcode,strCountry,str EmailAddress,boNewsLetter]; }catch(e){ arrMsg = ["Error"]; } console.println("Msg: " + arrMsg.length + " : " + arrMsg); try{ this.hostContainer.postMessage(arrMsg); }catch(e){ }
To receive the data in the HTML page, use the same messageHandler
system as you did in Communicating from AIR to PDF. The complete HTML page code
looks like this:
<html> <head> <title>Communicate from PDF to AIR</title> <script type="text/javascript" > function initialize() { var PDFObject = document.getElementById("PDF"); PDFObject.messageHandler = { onMessage: function(aMessage) { alert(aMessage); return true; }, onError: function(error, aMessage) { alert("!error!"); } }; } </script> </head> <body onLoad="initialize()"> <object id="PDF" height=90% width=90% type="application/pdf" data="ContactForm.pdf"></object> </div> </body> </html>
When you are developing AIR applications with HTML and
JavaScript/Ajax, you are done with the communication. In the previous
JavaScript block, the aMessage
parameter passed to the methods in the messageHandler
is an Array
containing all the data from the PDF
form you might need to access.
If you are developing with Flex (ActionScript), you need to go one step further. Figure 6-3 shows the application’s layout.
When the user clicks the Load PDF button, the HTML page is loaded into the HTML control; this happens by executing the following function:
private function loadPdf(path:String):void{ var url:URLRequest = new URLRequest(path); pdfContainer.addEventListener(Event.COMPLETE,completeHandler); pdfContainer.htmlLoader.load(url); }
In the previous code, the pdfContainer
is a reference to the HTML
control hosting the PDF document, and the completeHandler
is where the application will
talk with the global JavaScript object for the content loaded into the
HTML control. You can get a reference to that JavaScript object by using
the htmlLoader.window
property of the
HTML control (here called pdf
). When
you have the reference, you have a good level of control; for example,
you can create a new function at runtime in the JavaScript of your HTML
page and set that function to “point to” a function defined in
ActionScript. So, the completeHandler
looks like this:
private function completeHandler(event:Event):void{ pdf.htmlLoader.window.storeValues = storeValues; }
Because you couple the storeValues
JavaScript function to the
storeValues
ActionScript function,
when you define a function call for the storeValues
function in the JavaScript of the
HTML page, the ActionScript version of the function will be used.
The final JavaScript function in the HTML page looks like this:
<script type="text/javascript" >
function initialize()
{
var PDFObject = document.getElementById("PDF");
PDFObject.messageHandler =
{
onMessage: function(aMessage)
{
storeValues(aMessage);
return true;
},
onError: function(error, aMessage)
{
alert("!error!");
}
};
}
</script>
Now you can also write the storeValues
function in ActionScript:
private function storeValues(fieldArray:Object):void{ //get the values from the sent object var thanksStr:String = "THANKS FOR FILLING IN THE FORM " + fieldArray[0] +", " +fieldArray[1]; currentState='thanks'; thanksLabel.text = thanksStr; }
This function switches application states and shows a customized
“thank you” message to the user. The fieldArray
contains all the data from the
contact form. As you can see, the fieldArray
is typed as a simple Object
. This is done because the fieldArray
has a special type that you can
easily check as follows:
trace(flash.utils.getQualifiedClassName(fieldArray));
This trace
statement returns
the following result:
flash.html::__HTMLScriptArray
In ActionScript you are not able to define this type, so you must
type the incoming Array
as an
Object
or eventually type it as an
asterisk (fieldArray: *
).
The complete Flex code for this example looks like this:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" > <mx:states> <mx:State name="thanks"> <mx:RemoveChild target="{pdf}"/> <mx:RemoveChild target="{hbox1}"/> <mx:RemoveChild target="{vbox1}"/> <mx:AddChild position="lastChild"> <mx:Label textAlign="center" id="thanksLabel" fontSize="18" width="100%" x="0" y="233"/> </mx:AddChild> </mx:State> </mx:states> <mx:Script> <![CDATA[ private function loadPdf(path:String):void{ var url:URLRequest = new URLRequest(path); pdfContainer.addEventListener(Event.COMPLETE,completeHandler); pdfContainer.htmlLoader.load(url); } private function completeHandler(event:Event):void{ pdfContainer.htmlLoader.window.storeValues = storeValues; } private function storeValues(fieldArray:Object):void{ //get the values from the sent object var thanksStr:String = "THANKS FOR FILLING IN THE FORM " + fieldArray[0] +", " +fieldArray[1]; currentState='thanks'; thanksLabel.text = thanksStr; } ]]> </mx:Script> <mx:VBox width="100%" height="100%" id="vbox1"> <mx:HBox width="100%" height="50" id="hbox1" horizontalAlign="center"> <mx:Button label="Load PDF" click="loadPdf('DocRight.htm')" width="80" height="50"/> </mx:HBox> <mx:HTML id="pdfContainer" width="100%" height="75%" /> </mx:VBox> </mx:WindowedApplication>