The recent discussion about active intermediaries ( Sam Ruby, Phil Windley) sent me in an unexpected direction. What I meant to do was revisit some earlier writing on web proxies, email proxies, and SOAP routing, and try to draw some conclusions. Instead, I invented another bookmarklet.
Here was the problem. It's nice that I can now look up a book in my local library, but what if it's not in the collection? My library's OPAC (online public access catalog) enables you to ask the library to acquire a book, but the required fill-in form creates an activation threshold that I am rarely motivated to leap over.
The basic LibraryLookup bookmarklet is a kind of intermediary. It coordinates two classes of services -- Amazon/BN/isbn.nu/AllConsuming and your local library's OPAC -- to facilitate a lookup. I couldn't resist trying to create another intermediary that would facilitate a purchase request.
The solution I'll present here is less general than the basic lookup in several ways, but also interesting in several ways. Here are the ways in which it is less general:
Amazon-only. The basic lookup works with any site whose URL matches either /ISBN or isbn=ISBN. But to fill out a purchase request, more information is needed. This solution relies on Amazon-specific markup to find that information.
Innovative-only. The basic lookup works with any of four OPAC systems (and potentially others, as users discover and report the URI patterns that can enable them). But since I only have an account at my own library, which uses an Innovative OPAC, that's the only case I could try. Further, the solution is likely not to work with your Innovative OPAC. A little spelunking reveals that the /acquire function (e.g. http://your.library.baseurl/acquire) produces differently-constituted forms from one Innovative OPAC to the next. Sometimes name/password, sometimes PIN and library-card number, etc. Mine uses name and library-card number.
Nevertheless, here are the reasons I find the solution interesting.
It works. Specifically, it works for my library, but the geek-inclined should find it easy to adapt it to another Innovative OPAC, and -- presumably -- to other OPACs.
It's a simple but compelling demonstration of the JavaScript DOM.
It uses JavaScript to set and get Amazon cookies. I'm sure JS hackers take this for granted, but I've never had occasion to try it.
It's a live example of the technique (which Derek Robinson mentioned to me and Art Rhyno showed me) that removes the MSIE bookmarklet size limit.
Intermediating a library purchase
request
|
Here is the bookmarklet you can drag to your link toolbar: Please Acquire
Here is an Amazon page against which to test it: The Eighth Day of Creation: Makers of the Revolution in Biology.
Clicking the bookmarklet's link should bring up a screen like the one shown here. It's OK to click the button. I've neutered the script so it will just pop up a message rather than send the request. To unneuter it, rewrite the form's action= attribute to specify your OPAC's acquisition-request URL.
A few points to note in the code that follows:
Amazon's consistent use of the first META tag makes it very easy to pick out the book's title and author, like so:
var m0 = document.getElementsByTagName('META')[0];Gotta love that million-dollar markup!
var titleAuthor = m0.getAttribute('content');
There's no million-dollar markup for the publisher's name and date. Digging that out of the page is feasible, but much harder. I punt, in this case, by referring the librarian to the book's Amazon URL.
The setCookie script written into the generated form uses Amazon's domain. I'd never thought about this, but cookies are a two-way street. Amazon can use them to coordinate with me, but I can also use them to coordinate with Amazon. In this example, the generated form looks for Amazon cookies named MyLibraryUserName and MyLibraryUserID. If it finds them, it defaults two fields to their values. Otherwise, whatever you type there is remembered (via the onChange() handler) in Amazon cookies.
All in all, an instructive little exercise. This sort of technique won't replace active intermediaries, including the local kind that work at the level of HTTP or SMTP. Rather, it will complement them. Users need to be able to see, and approve, what intermediaries propose to do on their behalf. I like the idea of an interactive intermediary that prepares a connection between two services, previews it for the user, and then makes the connection.
Update
The script below contains a privacy bomb which, after a few minutes of reflection, I removed from the live version invoked by the bootloader. It's a fascinating scenario, actually:
You don't want Amazon to see your library-card number.
Don't store/send it at all. This, of course, eliminates most of the convenience of the solution.
Store/send an encrypted version.
You do want Amazon to see your library-card number. Does that sound crazy? Maybe not. Reasons I might trust Amazon with that information:
Because it could use it to co-ordinate my library activity with my Amazon activity, and make better-informed Amazon recommendations. In particular, Amazon could emphasize books known not to be available to me in my local library. This would certainly seem to be a fair quid-pro-quo for the use of that handy ISBN in its URI!
Because it could use it to offer me an email-notification service alerting me to overdue library books.
I find (2b) especially intriguing. It's not really in Amazon's interest for me to be aware of what's available in the local library, and it's not really in the library's interest for me to be made promptly aware of fines accumulating there. By yoking them together, I might be able to play the two services off against one another to my benefit -- and to theirs.
The bookmarklet's bootloader
javascript:void((function() {var%20element=document.createElement('script'); element.setAttribute('src', 'http://jonudell.net/udell/gems/acquire.js'); document.body.appendChild(element)})())
The script loaded by the bootloader
var setCookieScript = 'function setCookie(Name1, Value1) { var
expires = new Date(); expires.setFullYear(expires.getFullYear()+1);
var cookie = Name1 + \'=\' + escape(Value1) +
\';domain=amazon.com;path=/;expires=\' +expires.toGMTString();
alert(cookie); document.cookie = cookie; }';
function getCookie(Name)
{
var s = '; '+document.cookie+';';
var i = s.indexOf('; '+Name+'=');
if (i == -1)
{ return ''; }
else
{
i += 3 + Name.length;
var j = s.indexOf(';', i);
return unescape(s.substring(i, j));
}
}
var myLibraryUserID = getCookie('MyLibraryUserID');
var myLibraryUserName = getCookie('MyLibraryUserName');
var m0 = document.getElementsByTagName('META')[0];
var titleAuthor = m0.getAttribute('content');
var re = /(.+),\s*([^,]+)$/;
re.test(titleAuthor);
var title = RegExp.$1;
var author = RegExp.$2;
var win = window.open('','LibraryAcquisitionRequest',
'resizable=1,scrollable=1,width=600,height=400');
win.document.write('<html><head><title>Request
acquisition of: ' + titleAuthor + '</title><scr' +
'ipt>' + setCookieScript + '</scr' +
'ipt></head><body>');
win.document.write('<p>Request acquisition of: ' +
titleAuthor + '</p>');
win.document.write('<form name="acquire" method="post"
action="javascript:alert(\'Demonstration
only!\');"><table><tr><td
align="right">Author: </td>
<td><input name="author" value="' + author + '"
size="40" maxlength="255"></td>
</tr><tr><td
align="right">Title:</td> <td><input
name="title" value="' + title + '" size="40"
maxlength="255"></td>
</tr><tr><td align="right">Where/when
published:</td> <td><input
name="publish" value="See Amazon: ' + location.href + '" size="60"
maxlength="255"></td>
</tr><tr><td align="right">Where
mentioned:</td> <td><input
name="mention" value="Amazon" size="60"
maxlength="255"></td>
</tr><tr><td align="right">Other
info:</td> <td><input name="other"
value="Intermediated by the LibraryLookup project" size="40"
maxlength="255"></td>
<tr><tr><td align="right">Your
name:</td> <td><input name="name"
value="' + myLibraryUserName + '" size="40" maxlength="255"
onChange="javascript:setCookie
(\'MyLibraryUserName\',forms[0].name.value);"></td>
</tr> <tr><td align="right">14-digit
library card #:</td> <td><input
name="barcode" type="text" value="' + myLibraryUserID + '"
size="40" maxlength="40" /* use with caution!
onChange="javascript:setCookie
(\'MyLibraryUserID\',forms[0].barcode.value);"
*/></td> </tr><tr><td
align="left" colspan="2"><br><input
name="submit" type="submit" value="Ask library to acquire this
book"></td>
</tr></table></form><p></body></html>');
win.document.close();
Former URL: http://weblog.infoworld.com/udell/2002/12/30.html#a558