ACME portal

This page explains how the simple example of ACME Portal works. ACME Portal is a trivial hard-coded page for selecting how to compare four documents.

This "mashup" example shows how TextFlow could be integrated with an existing version-based document management portal. It allows the user to first select which version they want to work with as the "original", and then pick a number of other versions to compare with the original.

Screen shot of ACME document portal

Try out the ACME Portal

ACME Portal - Documentation

Note: some of the code segments below have been simplified for instructional purposes. The actual HTML source for ACME Portal contains more code to improve the appearance of the page.

index.html

This file is a simple HTML form with four hard-coded versions shown to the user. It also loads index.js which provides some simple logic to the form.

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title></title>
<script src="index.js" language="javascript" />
</head>
<body>
<CENTER>
<H1>ACME Document Portal</H1>
</CENTER>

<form name="myform" onsubmit="return false;">
<div align="center"><br>
<H2>Version list for: BusinessPlan.doc</H2>
  <TABLE BORDER=0>
  <TR>
    <TD>Original</TD>
    <TD>Compare</TD>
    <TD width="75" align="center">Version</TD>
    <TD width="150"  align="left">Author</TD>
    <TD>Date</TD>
  </TR>
 <TR>
    <TD><input type="radio"  onclick="disable(myform.diff4); update()"
      name="baseline" value="4"/></TD>
    <TD><input type="checkbox" onclick="update()" name="diff4" value="4"/></TD>
    <TD align="center">4</TD>
    <TD>Tomer Shalit</TD>
    <TD>2 days ago</TD>
 </TR>
 <TR>
    <TD><input type="radio"  onclick="disable(myform.diff3) ; update()"
      name="baseline" value="3"/></TD>
    <TD><input type="checkbox" onclick="update()" name="diff3" value="3"/></TD>
    <TD align="center">3</TD>
    <TD>Anna Westerberg</TD>
    <TD>2 days ago</TD>
  </TR>
  <TR>
    <TD><input type="radio"  onclick="disable(myform.diff2) ; update()"
      name="baseline" value="2"/></TD>
    <TD><input type="checkbox" onclick="update()" name="diff2" value="2"/></TD>
    <TD align="center">2</TD>
    <TD>Mark Dixon</TD>
    <TD>3 days ago</TD>
  </TR>
  <TR>
    <TD><input type="radio"onclick="disable(myform.diff1) ; update()"
      name="baseline" value="1" checked="true" /></TD>
    <TD><input type="checkbox" disabled="true" onclick="update()"
      name="diff1" value="1" /></TD>
    <TD align="center">1</TD>
    <TD>Tomer Shalit</TD>
    <TD>4 days ago</TD>
  </TR>
</TABLE>
<INPUT type="submit" name="button" value="Merge"
  onclick="launchFlash()"  disabled='true'/>
</div>
</form>

</body> </html>

index.js

A few simple JavaScript functions control the radio toggling effects that ensure that only one document is selected as the "original" and that it cannot be selected as a document to be compared with.

// When a version has been selected as the original, update the form elements
// so that the original cannot be selected for comparison, but the others can.
function disable(id) {
  myform.diff1.disabled=false;
  myform.diff2.disabled=false;
  myform.diff3.disabled=false;
  myform.diff4.disabled=false;
  id.disabled=true;
  id.checked=false;
}

// Update the status of the submit button
function update() {
  myform.button.disabled=!( myform.diff1.checked | myform.diff2.checked |
                            myform.diff3.checked | myform.diff4.checked );
}

// Concatenate a verison id, id, to the string s, making the list comma
// separated if it already contains elements.
function addDiff( s, id ) {
  if ( s!="" ) return s+","+id;
  else return d;
}

// Launch TextFlowAPI in a separate pop-up window.
function launchFlash() {
  var base="";
  for( var i=0; i < myform.baseline.length; i++ ) {
    if ( myform.baseline[i].checked )
      base=myform.baseline[i].value;
  }
  var diff="";
  if ( myform.diff1.checked ) diff=addDiff( diff, '1' );
  if ( myform.diff2.checked ) diff=addDiff( diff, '2' );
  if ( myform.diff3.checked ) diff=addDiff( diff, '3' );
  if ( myform.diff4.checked ) diff=addDiff( diff, '4' );

  // open TextFlow in a new window with correct arguments
  window.open( 'TextFlowAPI.html?TextFlowBaseVersionId='+base+
               '&TextFlowCompareVersionIdList='+diff, 'TextFlow', 
               'toolbar=no,scrollbars=no,status=no,menubar=no,'+
               'copyhistory=no,location=no,width=1270,height=770' );
}

TextFlowAPI.html

TextFlowAPI.html is more-or-less the standard automatically generated HTML file from Flex Builder with a few minor changes to register a number of callbacks used to monitor mouse-wheel and focus events. In addition to those we also pass in a number of parameters to the flash object using FlashVars to control which version to use as the original, and which to compare with.

AC_FL_RunContent(
                 "src", "TextFlowAPI",
                 "FlashVars","TextFlowDocumentId=100&TextFlowMode=API"+
                 "&TextFlowBaseVersionId="+TextFlowBaseVersionId+
                 "&TextFlowCompareVersionIdList="+TextFlowCompareVersionIdList,
                 "width", "100%",
                 "height", "100%",
                 "align", "middle",
                 "id", "TextFlowAPI",
                 "quality", "high",
                 "bgcolor", "#869ca7",
                 "name", "TextFlowAPI",                       
                 "allowScriptAccess","sameDomain",
                 "type", "application/x-shockwave-flash",
                 "pluginspage", "http://www.adobe.com/go/getflashplayer"
);

TextFlowAPI.js

Finally we implement the set of JavaScript functions required by TextFlow for integration.

getVersionList() should return the version list XML required by TextFlow. In a real-world example it would be dynamically generated from database data.

// returns an XML string containing version information.
// see versionlist.xml for an example
function getVersionList( docid )
{
  return decode_utf8( loadXMLDoc( "versionlist.xml" ) );
};

getVersionData() can either itself retrieve the document data and return it to TextFlow, or alternatively it can give TextFlow a URL from which to retrieve the document. The latter approach is slightly more efficient because then the TextFlow server can get the document directly from the host server rather than it going via the client first.

// getVersionData() converts a verid to a URL that can be used by the
// TextFlow server to retrieve the specified document. In a real-world
// case this URL would be a temporary URL located on the integrator's
// website.
function getVersionData( docid, verid )
{
  return "https://secure2.textflow.com/acme/RH"+verid+".doc";
}

getSaveFormat() and saveVersionData() work together to instruct TextFlow how to handle a save. getSaveFormat() determines which format to use for the data argument when calling saveVersionData().

saveVersionData() in this example takes a temporary URL locating the saved document on the TextFlow server and triggers a browser download of the file. A real-world example would send this URL back to the host server so that it can retrieve the document and add it to the host database.

// Tell TextFlow which format you want saves to be in. Acceptable values are
// "doc", "docx", "docurl" and "docxurl"
function getSaveFormat( docid ) {
  return "docurl";
}


function saveVersionData( docid, data )
{
  // should first do something with the data, depending on what format
  // it is in (as decided by getSaveFormat). For this demonstration we
  // just trigger a javascript download of the Word document:
  window.open(data,'Download');  

  // Then we need to return a new version entry for the version list
  // in the same format as the entries defined in versionlist.xml
  // Note that id must be unique.
  return decode_utf8( '<Version id="15" created="2009-01-05 01:10:42" name="Version5" type="docurl" />' );
}

onUnload() detects if the user has unsaved changes and warns them before tha page is unloaded.

function onUnload() {
  if ( TextFlowAPI.hasChanges() ) {
    return "Warning: Your document has not been saved. To save first, "+
      "please select Cancel and pick the 'Close' button from the "+
      "TextFlow toolbar.";
  }
}

Finally a number of helper functions that support the rest of the code.

function loadXMLDoc(url) {
  xmlhttp=GetXmlHttpObject();
  if (xmlhttp==null) {
    alert ("Your browser does not support XMLHTTP!");
    return;
  }
  xmlhttp.open("GET",url,false);
  xmlhttp.send(null);
  return xmlhttp.responseText;
}

function GetXmlHttpObject() {
  if (window.ActiveXObject) {
    return new ActiveXObject("Msxml2.XMLHTTP");
    // code for IE6, IE5
    return new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (window.XMLHttpRequest) {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    return new XMLHttpRequest();
  }
  return null;
}

function decode_utf8( s ) {
  return decodeURIComponent( escape( s ) );
}