I touched on this subject a few weeks ago in part 2 of my DWF-related AU handout, but thought I'd come back and describe in more depth some of the fun (although a more accurate word might be "difficulty" :-) I had solving this problem.
I've been playing around with web services and XML since SaaS was still known as ASP, and have tried to stay up-to-date with the technology as best I can. I'm really a client-side programmer, all things considered: I've created some server-side code, but have mostly involved myself with desktop-oriented programming and creation of samples that developers can execute locally, rather than having to set up a host environment.
So when working with web services, I really want to provide a simple application that can be run locally and connects to these services via simple, understandable client code.
The problem I wanted to solve with my AU demo was fairly simple (at least so I thought): for a particular DWF file I wanted to create a page linking to - and embedding - Freewheel views of the DWF's various sheets. The logical way to do this was to use the DWFRender web service to query the number of sheets contained in the DWF, and then create a page using DHTML that had some content for each of these sheets.
The logic seemed sound, and I created a nice prototype with hardcoded sections for the two sheets of my DWF file. Here's the HTML code:
<html>
<!-- saved from url=(0017)http://localhost/ -->
<head>
<title>Freewheel</title>
<style>
legend { font-size: 10pt;
font-family: Calibri, Arial;
color: black; }
button { font-size: 10pt; }
input { font-size: 10pt;
font-family: Calibri, Arial; }
</style>
</head>
<body bgcolor="#E6E6E6">
<table width="620">
<tr>
<td>
<fieldset>
<legend>Sheet 1</legend>
<table>
<tr>
<td valign="middle">
<fieldset>
<legend>Clickable Thumbnail</legend>
<a
href="http://freewheel.labs.autodesk.com/dwf.aspx?sec=1&dwf=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf"
target="New"
>
<img
frameborder="0"
height="150"
width="200"
scrolling="no"
src="http://freewheel.labs.autodesk.com/dwfImage.aspx?page=1&width=200&height=150&path=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf" /
>
</a>
</fieldset>';
</td>
<td>
<iframe
frameborder="0"
height="300"
width="400"
scrolling="no"
src="http://freewheel.labs.autodesk.com/dwf.aspx?sec=1&dwf=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf">
</iframe>
</td>
</tr>
</table>
</fieldset>
</td>
</tr>
<table>
<table width="620">
<tr>
<td>
<fieldset>
<legend>Sheet 2</legend>
<table>
<tr>
<td valign="middle">
<fieldset>
<legend>Clickable Thumbnail</legend>
<a
href="http://freewheel.labs.autodesk.com/dwf.aspx?sec=2&dwf=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf"
target="New"
>
<img
frameborder="0"
height="150"
width="200"
scrolling="no"
src="http://freewheel.labs.autodesk.com/dwfImage.aspx?page=2&width=200&height=150&path=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf"/>
</a>
</fieldset>';
</td>
<td>
<iframe
frameborder="0"
height="300"
width="400"
scrolling="no"
src="http://freewheel.labs.autodesk.com/dwf.aspx?sec=2&dwf=http://through-the-interface.typepad.com/presentations/DWF/solids.dwf">
</iframe>
</td>
</tr>
</table>
</fieldset>
</td>
</tr>
<table>
</body>
</html>
And here's what it looked like when displayed:
So far, so good.
To generate the DHTML code for each of the sections, which could then be called in a loop once we knew the number of sheets, was also pretty straightforward:
var freewheel =
'http://freewheel.labs.autodesk.com';
function createViews(sheets)
{
var container = document.getElementById('views');
var htm = '<table width="620"><tr><td>';
for(i=1; i<= sheets; i++)
{
htm += '<fieldset><legend>Sheet ' + i + '</legend>'
htm += '<table><tr><td valign="middle">';
htm += '<fieldset><legend>Clickable Thumbnail</legend>'
htm += '<a href="' + freewheel + '/dwf.aspx?'
htm += 'sec=' + i + '&dwf=' + path + '" target="New">';
htm += '<img frameborder="0" height="150" ';
htm += 'width="200" scrolling="no" src="' + freewheel;
htm += '/dwfImage.aspx?page=' + i + '&';
htm += 'width=200&height=150&path=' + path + '"/>'
htm += '</a></fieldset>';
htm += '</td><td>';
htm += '<iframe frameborder="0" height="300" ';
htm += 'width="400" scrolling="no" src="' + freewheel;
htm += '/dwf.aspx?sec=' + i + '&dwf=' + path + '">';
htm += '</iframe>';
htm += '</td></tr>';
htm += '</table></fieldset>';
}
htm += '</td></tr><table>';
container.innerHTML = htm;
}
The only challenge remaining was to call our DWFRender web service from out JavaScript code.
Back in the old days I used a component from Microsoft to call web services from HTML & JavaScript, called the Web Service Behavior, or webservice.htc. This seemed to work fine, but is no longer supported, so I thought I'd find a more modern approach, something from the AJAX bandwagon, perhaps. AJAX is "Asynchronous JavaScript and XML", and is the latest & greatest technology for developing smart yet thin applications. Or so the buzz goes.
After searching around a while, I ended up trying a couple of approaches. The first was based a technique provided by IBM. It took me a while to get the code to run, and even then it wasn't getting the result I wanted. So I implemented another approach using a technique shown at The Code Project.
Once again it didn't return anything helpful, but then I noticed this statement on the page:
Please note that many browsers do not allow cross-domain calls for security reasons.
I researched this for some time, and realised that my JavaScript code was hitting a browser security problem. It seems that cross-domain SOAP requests are an issue: if your web-page is considered to be in one domain, it will not call out across to a web service hosted on another domain. Even the Web Service Behavior I'd used in the past didn't solve the problem (thinking back I'd been calling web services hosted in the same domain, so it hadn't been an issue I'd come across).
The security problem is actually with the XMLHTTP component, which is at the core of AJAX and is now implemented by various (probably all, but I'm no expert) browsers.
There are a few ways around this limitation: it seems that most commonly developers implement server-based code that calls across to the problematic domain, basically provided a local cache of the results. In terms of clarifying the problem and spelling out the alternatives, I found this site extremely helpful, as well as this follow-up post.
Anyway, it turns out that for the purposes of my demo I could cobble something that worked together based on fairly simply client-side code. I didn't go to the effort of implementing a cross-browser solution, as the main purpose is to demonstrate calling the Freewheel web service and not to solve the problem of World (well, Internet) Peace.
There were two points that I hadn't realised were issues. The first was the Mark of the Web (MOTW). I had added this to my HTML header sometime in the past, to gain Internet Exporer's trust for it not to bother me with requests to approve the running of ActiveX Controls:
<!-- saved from url=(0017)http://localhost/ -->
Removing this was part of the trick - the other part was to adjust my browser settings to allow cross-domain access:
That then allowed my client code to run and actually get results. Here's the HTML code for the page:
<html>
<head>
<title>Freewheel</title>
<style>
legend { font-size: 10pt;
font-family: Calibri, Arial;
color: black; }
button { font-size: 10pt; }
input { font-size: 10pt;
font-family: Calibri, Arial; }
</style>
<script type="text/javascript">
var freewheel =
'http://freewheel.labs.autodesk.com';
var http_request = false;
var xmldoc;
var path;
function makeRequest(url)
{
try
{
http_request =
new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
http_request =
new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e)
{ }
}
if (http_request)
{
http_request.onreadystatechange = alertContents;
http_request.open('GET', url, true);
http_request.send(null);
}
}
function alertContents()
{
if (http_request.readyState == 4)
{
if (http_request.status == 200)
{
var string = http_request.responseText;
var xmldoc;
xmldoc = http_request.responseXML;
var secs =
xmldoc.getElementsByTagName(
"unsignedInt"
)[0].firstChild.nodeValue;
createViews(secs);
}
else
{
alert('There was a problem with the request.');
}
}
}
function replaceAll(strText, strTarget, strSubString)
{
var intIndexOfMatch = strText.indexOf( strTarget );
while (intIndexOfMatch != -1)
{
strText = strText.replace( strTarget, strSubString )
intIndexOfMatch = strText.indexOf( strTarget );
}
return( strText );
}
function createViews(sheets)
{
var container = document.getElementById('views');
var htm = '<table width="620"><tr><td>';
for(i=1; i<= sheets; i++)
{
htm += '<fieldset><legend>Sheet ' + i + '</legend>'
htm += '<table><tr><td valign="middle">';
htm += '<fieldset><legend>Clickable Thumbnail</legend>'
htm += '<a href="' + freewheel + '/dwf.aspx?'
htm += 'sec=' + i + '&dwf=' + path + '" target="New">';
htm += '<img frameborder="0" height="150" ';
htm += 'width="200" scrolling="no" src="' + freewheel;
htm += '/dwfImage.aspx?page=' + i + '&';
htm += 'width=200&height=150&path=' + path + '"/>'
htm += '</a></fieldset>';
htm += '</td><td>';
htm += '<iframe frameborder="0" height="300" ';
htm += 'width="400" scrolling="no" src="' + freewheel;
htm += '/dwf.aspx?sec=' + i + '&dwf=' + path + '">';
htm += '</iframe>';
htm += '</td></tr>';
htm += '</table></fieldset>';
}
htm += '</td></tr><table>';
container.innerHTML = htm;
}
function OnGenerateSheetViews()
{
path = document.getElementById('URL').value;
var url = freewheel + '/dwfrender.asmx/sectionCount?path=';
var dwf = path;
dwf = replaceAll(dwf, '/', '%2F');
dwf = replaceAll(dwf, ':', '%3A');
url += dwf;
makeRequest(url);
}
</script>
</head>
<body bgcolor="#E6E6E6">
<input
type="text"
id="URL"
size="60"
value="http://through-the-interface.typepad.com/presentations/DWF/solids.dwf" /
>
<input
id="GenerateSheetViews"
type="button"
value="Generate Sheet Views"
onclick="return OnGenerateSheetViews()" /
>
<div id="views"></div>
</body>
</html>
You'll notice that the page is nearly all script: the static elements are minimal - just somewhere to enter the URL of our DWF file and a <div> for our DHTML tags to be squirted into.
When the page loads there's not a great deal to see:
And it generates our sample pretty successfully:
The application comes into its own when we run it with a DWF with lots of sheets: the sample file, http://freewheel.autodesk.com/sample/Hotel5.dwf, has 29 sheets in case you really want to put it through its paces. :-)
Additional note:
A colleague kindly reminded me about something I had meant to mention. The code in this post makes use of the Autodesk Labs Freewheel server (http://freewheel.labs.autodesk.com). When you're developing applications based on Freewheel, you should really use the production Freewheel server (http://freewheel.autodesk.com), wherever possible. I used the Labs version (which is hosted on a single server, rather than being on a load-balanced, production-capable server farm) simply because it was able to render the specific DWF I was hosted in my site: fixes are generally rolled out first on the Labs server and migrate over time to the production server, as you'd expect, and in this case my DWF wasn't yet viewable via the production system.
To adjust the above code to use the production server, simply change the value of the "freewheel" variable:
var freewheel =
'http://freewheel.autodesk.com';