Custom Package Previews with PandoAPI.js

While the Pando "Send-to-Web" preview is quick and easy, it doesn't always fit exactly as desired into an existing site design. If you'd like to design your own package previews, one easy way to do this is using the Pando Javascript API. The API makes the package metadata available to Javascript, so you need not write any server-side code to generate custom package previews. In addition to the information in the "Send-to-Web" preview, the Package Info Web Service also gives you real-time reports of the number of downloads and the expiration date.

For comparison, here is how to do previews in PHP.

Let's say we want to be able to display a package preview containing the thumbnail, title, number of times downloaded and number of days to expiration (or days since expiration). We want the title and thumbnail to link to the package URL only if it is not expired. We want it to look something like:

package preview sample

Building Target HTML and CSS

Since we'll be writing Javascript to generate our HTML preview, it helps to have a good sense of what we expect that output to look like before we start.

<table class="pandopackage"><tr>
  <td class="packagethumb" align="center" valign="center">
    <a href="[Package URL]"><img src="[Thumbnail URL]" alt=""></a>
  </td>
  <td class="packagemeta" valign="center">
    <span class="ptitle"><a href="[Package URL]">Title</a></span>
    <p>
    Downloaded <strong>N</strong> times<br>
    <span style="color: green;">Expires in X days</span><br>
    </p>
  </td>
</tr></table>

Note: For this example we're using a table based layout primarily because the Pando thumbnail is a variable size (maximum size is 100 pixels x 75 pixels, but it can be smaller in any dimension). If we want to center the thumbnail image both vertically and horizontally in a way that works in most browsers, a table cell is the most reliable way.

Accompanied with the following css, we get a nicely formatted image preview:

table.pandopackage {
border: 1px solid green;
float: left;
margin-right: 10px;
}
.pandopackage td.packagethumb {
width: 100px;
height: 75px;
text-align: center;
}
.pandopackage .packagethumb img {
border: 0;
vertical-align:middle;
}
.pandopackage td.packagemeta {
margin: 0 0 0 10px;
padding: 0;
width: 150px;
font-family: Verdana, Arial, sans-serif;
font-size: 11px;
}
.pandopackage .packagemeta span.ptitle {
font-family: 'Lucida Grande', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 0 1em 0 0;
}

Building the Callback Function

In order to generate the HTML with the appropriate metadata for our preview, we'll need a callback function for PandoAPI.js to call that can accept the packageInfo array and construct the appropriate HTML. The following callback function will calculate the number of days till/since expiration, determine whether the package is expired, generate a title and thumbnail string (only wrapped in an anchor tag if the image is not expired), get the number of package downloads and construct an HTML string that looks like our target HTML. You can follow along in the comments:

function printPackageCallBack(packageInfo) {
//Get number of days till expiration (negative = expired)
var days = Math.round( (new Date(packageInfo['expirationDate']) - new Date()) / 86400000);
//store expiration status
var expired = (days >= 0) ? 0 : 1;
//use absolute values of days for a readable string
days = Math.abs(days);
//get title from package info
var title = packageInfo['title'];

if (title.length > 30) {
title = title.substring(0,28) + '...';
}

//generate img tag with thumbnail from package info
var thumb = "<img src='"+ packageInfo['thumbnailURL'] +"' alt='' />";
//generate anchor tag from package URL; assumes packageKey is set
var packageA = "<a href='" + PandoAPI.getPackageURL(packageId,packageKey) + "'>";
//get appropriately pluralized X day(s) string
var daystring = days +' day'+ ( (days > 1) ? ('s') : ('') );
//get # of downloads from package info
var dl = packageInfo['downloads'];
//if package is not expired, link the title and thumbnail to the package URL
//  and set the expiration days string accordingly
if(!expired) {
title = packageA + title + '</a>';
thumb = packageA + thumb + '</a>';
daystring = '<span style="color: green;">Expires in '+ daystring +'</span><br />';
//otherwise, do not link the title/thumbnail, and set expiration string accordingly
} else if (!packageInfo['expirationDate']) {
daystring = '<span style="color: green;"><strong>Never expires</span></strong><br />'
title = packageA + title + '</a>';
thumb = packageA + thumb + '</a>';
} else {
daystring = '<span style="color: red;">Expired '+ daystring +' ago</span><br />';
}
//generate human-friendly Downloaded X times / Never Download string
if(dl > 0)
dlstring = 'Downloaded <strong>'+dl+'</strong> time'+( (dl > 1) ? ('s') : ('') );
else
dlstring = 'Never downloaded';
//write out the package preview
out = ("\r\n<table class='pandopackage'>\r\n<tr>\r\n\t<td valign='center' align='center'
  class='packagethumb'>\r\n\t\t"+ thumb + '\r\n\t</td>\r\n\t<td valign="center"
  class="packagemeta">\r\n\t\t<span class="ptitle">'+ title +'</span>\r\n\t\t<p>\r\n\t\t'
      + dlstring +'<br />\r\n\t\t'+ daystring +'\r\n\t\t</p>\r\n\t</td>\r\n</tr>\r\n</table>'
    );
document.write(out);
}

Now, if we call PandoAPI.getPacakgeInfo with a package id, key and the above callback function, it will write directly into our page:

<script type="text/javascript">
  PandoAPI.getPackageInfo('5AEDE982393976A10050F2EC7C20C3C5EFDE0BBB',
    printPackageCallBack,
    'C51E9F2767B6747A9C9841AF7EEB9CC0E967D5B37CEC05B8C9DF310A03958AD2');
</script>

A Touch of Elegance

That works fine, but document.writes are a little crude and the function call is a little tricky to construct correctly. Also, if the javascript fails to load (e.g., due to a temporary service outage), it could stop our entire page from loading from the point where we include the script. A common practice is to put remotely-dependent javascript code just above the </body> tag, so if it fails to load, it doesn't affect the rest of the page. However, if we do this, we could only write our package preview to the end of the page.

To make the function call to generate the preview a little simpler, we can write a wrapper function that accepts the package URL as an input (which is easy to acquire), parses out the package id and key and then invokes PandoAPI.getPackageInfo with the correct parameters. E.g.,

function parsePandoUrl(url) {
  var parts = url.split('?',2);
  var args = parts[1];
  parts = args.split('&');
  for(var i=0; i<parts.length; i++) {
    if(parts[i].match(/^id=[A-Z0-9]*/))
      packageId = parts[i].substring(3);
    if(parts[i].match(/^key=[A-Z0-9]*/))
      packageKey = parts[i].substring(4);
  }
}

function showPackage(url) {
  parsePandoUrl(url);
  PandoAPI.getPackageInfo(packageId,printPackageCallBack,packageKey);
}

Now we can display a package preview for a particular package URL, like so:

<script type="text/javascript">
showPackage('http://cache.pando.com/soapservices/package.pando?
  id=5AEDE982393976A10050F2EC7C20C3C5EFDE0BBB
  &key=C51E9F2767B6747A9C9841AF7EEB9CC0E967D5B37CEC05B8C9DF310A03958AD2');
</script>

And finally, in order to be able to call this function just before the closing <body> tag, we can change our call to document.write in the callback function to instead modify the "innerHTML" contents of an existing element on the page. For this example, we'll assume there's an empty <div> tag with the id of "packagedetails" somewhere on the page. We can then swap out document.write with:

document.getElementById('packagedetails').innerHTML = out;

Bring It All Together

Here is a stand-alone HTML implementation. [final custom javascript] [final custom css]. Feel free to use these on your web site as is, or extend them any way you like!

If you have questions, or want to share what you've come up with, post a comment below!