Using Evergreen APIs for client-side bibliographic data rendering
or Replacing result_detail.xml
The following is a rough example of one mechanism available in Evergreen for displaying bibliographic data outside of the stock OPAC code.
<html> <head> <title>This is only a test</title> <script type="text/javascript" src='/js/dojo/dojo/dojo.js'></script> </head> <body>
First, load Dojo, which is included in Evergreen 1.4.0 and beyond.
<table border=1> <tr> <th>title</th> <td type="opac/slot-data+marcxml" query="datafield[tag=245] subfield"> <script type='opac/slot-format'> if (item.getAttribute('code') == 'a') { return '<b>' + dojox.data.dom.textContent(item) + '</b>'; } else { return dojox.data.dom.textContent(item); } </script> </td> </tr>
Now we lay out some template slots that the bib data will be plugged into. This requires some extra explanation, I think.
First, we need to mark the containing element (<td>
here) in some way so that we can find it. We do this by giving it a non-standard type
attribute with a value of opac/slot-data-marcxml
. This attribute is used in two ways. First, opac/slot-data
says that this element will be used as a place to deposit data needed for constructing this "opac". Second, +marcxml
tells the query processor that it should use the marcxml
form of the bibliographic record. Other formats would include marcxml-full
(which includes holdings information), mods
, oai_dc
and other formats supported by the Evergreen unAPI service.
Next we need to tell the query processor what to extract from the XML returned by the unapi service. We add a query
attribute to the container element and set its value to the dojo.query() we need to find the relevant data in the format selected by the type
attribute.
Finally, we may need to format the data in some special way. We do this by providing a chunk of Javascript that knows how to take the output of the dojo.query()
call, one node at a time, and do something special with it. Each node is run through this code, and the return values for all nodes passing through this code are joined with a space and used to replace the HTML content of the containing element.
Two things to note about this <script>
tag: it's type
attribute and the assumed environment of the code within the block.
By setting the type
attribute to opac/slot-format
we tell the browser to ignore the content of the element (browsers will only process untyped <script>
tags, or those with a type of language/javascript
(more or less)) and we let the formatting processor find the code. We place this specialized <script>
inside the container element so that it is specifically associated with that bit of data.
The block of code within this <script>
tag can assume that it will be passed a parameter called item
and that this is a node coming from the nodelist produced by the relevant dojo.query()
call.
<tr> <th>mods title</th> <td type="opac/slot-data+mods" query="titleInfo:not([type])"/> </tr>
Here we see an example of a format other than marcxml
, and a more complex dojo.query()
.
<tr> <th>author</th> <td type="opac/slot-data" query="datafield[tag=100]"/> </tr>
If no format is given, marcxml
is assumed.
<tr> <th>abstract</th> <td type="opac/slot-data+marcxml" query="datafield[tag=520]"/> </tr> <tr> <th>subjects</th> <td type="opac/slot-data+marcxml" query="datafield[tag=650]"> <script type='opac/slot-format'> return '<span>' + dojo.query( 'subfield', item ).map( function(sf){ return '<b>' + sf.getAttribute('code') + ':</b> ' + dojox.data.dom.textContent(sf); } ).join(' >>> ') + '</span><br/>'; </script> </td> </tr>
Here we see that by selecting nodes in the middle of the DOM tree (<datafield>
sits between <record>
and <subfield>
in marcxml
) we can process groups of values together, allowing richer rendering and interpretation of entire sections of information instead of simple, discrete data.
<tr> <th>items</th> <td> <ul type="opac/slot-data+marcxml-full" query="*[barcode]"> <script type='opac/slot-format'> return '<li>' + item.getAttribute('barcode') + '</li>'; </script> </ul> </td> </tr> </table>
And, finally, here's one way for dealing with the holidngs info appended to the bib by adding -full to the end of the format specifier.
<script>
dojo.addOnLoad( function() {
var all_slots = dojo.query('*[type^=opac/slot-data]');
var rec = location.href.split('?')[1];
var slots = {};
all_slots.forEach(function(s){
var datatype = 'marcxml';
if (s.getAttribute('type').indexOf('+') > -1)
datatype = s.getAttribute('type').split('+').reverse()[0];
if (!slots[datatype]) slots[datatype] = [];
slots[datatype].push(s);
});
for (var datatype in slots) {
_flesh_by_type_and_rec(slots[datatype], datatype, rec);
}
});
This bit of Javascript sets the wheels in motion. Here's what it does at page load time:
- Find all elements with a
type
attribute starting withopac/slot-data
- Get the target bib record id from the query section of the URL
- Group slots by their format
- For each format group, process all the slots
How does that processing happen? Read on.
function _flesh_by_type_and_rec (slots,datatype,rec) { dojo.xhrGet({ url: '/opac/extras/unapi?id=tag:acq.open-ils.org:biblio-record_entry/' + rec + '/-&format=' + datatype, handleAs: 'xml', load: function (bib) { dojo.forEach(slots, function (slot) { var slot_handler = dojo.query( 'script[type=opac/slot-format]', slot ).orphan().map( function(x){return x.textContent || x.innerText || x.innerHTML} ).join(''); if (slot_handler) slot_handler = new Function('item', slot_handler); else slot_handler = new Function('item','return dojox.data.dom.textContent(item);'); slot.innerHTML += dojo.query( slot.getAttribute('query'), bib ).map(slot_handler).join(' '); delete(slot_handler); }); } }); } </script> </body> </html>
This function takes the datatype and record id and makes an XHR request to the Evergreen unAPI service to retrieve the record in the required format. When the record arrives, it loops over each data slot using this datatype and looks for one or more script elements with a type
attribute of opac/slot-format
. If found, it takes the text content of these and concatenates these and uses that as the body of a processing function to which each node found by passing the query
attribute a dojo.query()
call. If no specialized <script>
element is found, a default handler is used which returns the entire text content of the supplied node. The output of this handler is then concatenated and used to replace all existing content in the opac/slot-data
container node.
And that's it.