Monday, January 12, 2009

View PDF Files in Flash using PHP

I'm currently working on how to view PDF files in Flash. The first problem was that of merging or appending PDF files using PHP. This problem was solved using FPDF http://www.fpdf.org/ with FPDI http://www.setasign.de/products/pdf-php-solutions/fpdi/ and the following example from FPDI http://www.setasign.de/products/pdf-php-solutions/fpdi/demos/concatenate-fake/.

Now that we have the ability to concatenate PDF files using PHP now onto the converting PDF files to SWF Flash files with a viewer similar to the one at Scribd. Looking into this has led me to many resources. I think I'm going to use swftools with the pdf2swf from http://www.swftools.org/. This comes with a viewer and I found the source code for FDVIEW here http://www.code4net.com/2005/11/05/fdview/ in a comment at the bottom with an example here http://www.code4net.com/archives/000114.html#114.

With these resources I'm confident that I can use or polish the FDVIEW to achieve the PDF viewing in Flash in a clean way.

I'll keep you posted.

Monday, April 28, 2008

Enable the Back Button with dsHistory and prototype in an AJAX Application


Introduction


A common problem with AJAX applications is that they break the back button functionality from a user's perspective. While coding GiveGroup Craig Tran and I ran into this problem. Our AJAX application uses the Zend Framework and the prototype JavaScript library. We looked into a few options to enable the back button support, but they did not seem to blend with our implementation well until we looked into the dsHistory library.

We decided to use dsHistory because it allows us to push function calls with an argument object onto a history stack that will be popped off when the user hits the back button. This worked very well in our implementation because we could pass the div to update, the controller and action pair to call, and the options array into a function, push it onto the history stack, and then call the Ajax.Updater method to fetch the new content. When the user hits the back button those old calls and their arguments are popped off the stack and ran again emulating returning to the previous page.

In this example I'm going to try to keep the code as simple as possible to illustrate the technique we used with dsHistory and prototype. Due to this it will feel a little contrived, but will give you a good start into seeing how to put it into your own application.

Requirements:

  1. PHP 5 Server ( I would recommend XAMMP if you are new )
  2. The dsHistory JavaScript library version 1
  3. The prototype JavaScript library version 1.6
  4. A modern browser
First we will look at the server side code, then at the client site code.

Server Side - index.php

The server side index.php will parse the requested action and serve back the base index page if an action or index was requested. If a specific action was requested it will serve back just an html snippet to load in the mainContent div.

<?php
// array of allowed actions
$actionFilter = array( 'index', 'welcome', 'content1', 'content2' );

// get the requested action, if none exists serve the index page
$action = isset($_GET['action']) ? $_GET['action'] : 'index';

// if an unsupported action is requested return an error message
if( !in_array( $action, $actionFilter ) ){
$action = 'error';
}

// based on the action return some content
switch( $action ){
case 'welcome':
echo "This is the initial state of the application.";
echo "<br /><a href=\"javascript:navigate('content1');\">next page</a>";
break;
case 'content1':
echo "This is content 1.";
echo "<br /><a href=\"javascript:navigate('content2');\">next page</a>";
break;
case 'content2':
echo "This is content 2.";
break;
case 'index':
echo file_get_contents('content.htm');
break;
default:
echo "An unsupported action was requested.";
break;
}

?>

Client Side - content.htm

The html coded for our client side is very simple. In the body it consists of a single div with an id of 'mainContent'. This div will hold the results returned by our server side PHP script. You will notice that when the page first loads we call navigate('welcome') this will then call updateContent with everything the Ajax.Updater needs to know. The dsHistory script will store the function call with the arguments onto the history stack and we have just set our base state. From this point the back button will not do anything on the browser until the next call since dsHistory sees the first entry as what it needs to do to bring your application back to its initial state. You can think of this as everything you would have to do to setup the home page.

As the user clicks the 'next page' links the server will serve the appropriate html snippets and dsHistory will manage the stack. Notice that the back and forward buttons work like the user would expect.
<html>
<head>
<title>Fix the Back Button with AJAX, dsHistory, and prototype</title>
<script type="text/javascript" language="javascript" src="prototype-1.6.0.2.js"></script>
<script type="text/javascript" language="javascript" src="dshistory.compressed.js"></script>
<SCRIPT>
<!-- Begin to hide script contents from old browsers.

function navigate( page ){
var params = "action=" + page;

// in a real application we would have an action, but for our
// purposes we'll just use a parameter
var args = {
target:'mainContent',
action: '',
options: { method: 'get', parameters: params }
};

updateContent( args );
}

function updateContent( args, historyObject ) {
// store the function that loaded the 'page' onto the stack.
// the history object helps us to know if this was called from
// history because if it was we shouldn't push it on the stack.
if (!historyObject || !historyObject.calledFromHistory) {
dsHistory.addFunction( updateContent, this, args );
}
// update the page with prototype
new Ajax.Updater(args['target'], args['action'], args['options']);
}

// End the hiding here. -->
</SCRIPT>
</head>
<body onLoad="navigate('welcome');">
<div id='mainContent'></div>
</body>
</html>
Extending the Example

With GiveGroup we had some "pages" that required multiple AJAX calls to form. We had a mainContent and subContent divs and we would sometimes change both based on user interaction and we wanted to treat that set of function calls as a "page" for the user. If you study the example you can see how it would be easy to add multiple function calls to our implementation on the dsHistory stack. All you would need to do is modify the args in updateContent to be an array of the current objects that they are now, and loop over them and call Ajax.Updater for each one. This way when the args are pushed onto the dsHistory stack they represent one or many AJAX updates to update multiple divs at once.

We could also extend the example to include JSON responses. We needed this functionality in GiveGroup as well. To keep "pages" on the stack that are simple JSON responses from the server make another function similar to updateContent called getResponse and call Ajax.Request instead of Ajax.Updater with your params. Make sure to the callback function to process your JSON response.

You could also use dsHistory to push the server responses onto the stack instead of the server requests creating a client side cache so that when the user hits the back and forward buttons the server is not called at all. You would have to see if this is appropriate for your application, and perhaps we will cover it in a future blog post.

- Zebulon ( Zeb ) Evans