a78e9f44c630238b365ec7ff14cb4c1a98eec07a
[evergreen-equinox.git] / Open-ILS / web / opac / skin / default / js / rdetail.js
1
2 /* */
3
4 detachAllEvt('common', 'run');
5 attachEvt("common", "run", rdetailDraw);
6 attachEvt("rdetail", "recordDrawn", rdetailBuildStatusColumns);
7 attachEvt("rdetail", "recordDrawn", rdetailBuildInfoRows);
8 attachEvt("rdetail", "recordDrawn", rdetailGetPageIds);
9
10 /* Per-skin configuration settings */
11 var rdetailLocalOnly = true;
12 var rdetailShowLocal = true;
13 var rdetailShowCopyLocation = true;
14 var rdetailGoogleBookPreview = true;
15 var rdetailDisplaySerialHoldings = true;
16 var rdetailEnableRefWorks = false;
17 var rdetailRefWorksHost = 'http://www.refworks.com';
18
19 /* vars vars vars */
20 var record = null;
21 var cp_statuses = null;
22 var recordsCache = [];
23
24 var copyRowParent = null;
25 var copyRow = null;
26 var statusRow = null;
27 var numStatuses = null;
28 var defaultCN;
29 var callnumberCache = {};
30 var globalCNCache = {};
31 var localTOC;
32 var cachedRecords;
33 var _statusPositions = {};
34 var opac_strings;
35
36 var nextContainerIndex;
37
38 var nextRecord;
39 var prevRecord;
40
41 var rdetailPrev = null;
42 var rdetailNext = null;
43 var rdetailStart = null;
44 var rdetailEnd = null;
45
46 var mfhdDetails = [];
47 var orgHiding = false;
48
49 if(location.href.match(/&place_hold=1/)) {
50     // prevent load flicker between canvases
51     hideMe(dojo.byId('canvas_main'));
52 }
53
54 /* serials are currently the only use of Dojo strings in the OPAC */
55 if (rdetailDisplaySerialHoldings) {
56         dojo.require("dijit.Menu");
57         dojo.require("dijit.form.Button");
58         dojo.requireLocalization("openils.opac", "opac");
59         opac_strings = dojo.i18n.getLocalization("openils.opac", "opac");
60 }
61
62 function rdetailReload() {
63         var args = {};
64         args[PARAM_LOCATION] = getNewSearchLocation();
65         args[PARAM_DEPTH] = depthSelGetDepth();
66         goTo(buildOPACLink(args));
67 }
68
69 /* looks to see if we have a next and/or previous record in the
70    record cache, if so, set up the nav links */
71 function rdetailSetPaging(ids) {
72
73         cachedRecords = {};
74         cachedRecords.ids = ids;
75
76         for( var i = 0; i < cachedRecords.ids.length; i++ ) {
77                 var rec = cachedRecords.ids[i];
78                 if( rec == getRid() ) {
79                         if( i > 0 ) prevRecord = cachedRecords.ids[i-1];
80                         if( i < cachedRecords.ids.length - 1 )
81                                 nextRecord = cachedRecords.ids[i+1];
82                         break;
83                 }
84         }
85
86         $('np_offset').appendChild(text(i + 1));
87         $('np_count').appendChild(text(getHitCount()));
88
89         if(prevRecord) {
90                 unHideMe($('np_table'));
91                 unHideMe($('np_prev'));
92                 unHideMe($('np_start'));
93                 rdetailPrev = function() { _rdetailNav(prevRecord); };
94                 rdetailStart = function() { _rdetailNav(cachedRecords.ids[0]); };
95         }
96
97         if(nextRecord) {
98                 unHideMe($('np_table'));
99                 unHideMe($('np_next'));
100                 unHideMe($('np_end'));
101                 rdetailNext = function() { _rdetailNav(nextRecord); };
102                 rdetailEnd = function() { _rdetailNav(cachedRecords.ids[cachedRecords.ids.length-1]); };
103         }
104
105         runEvt('rdetail', 'nextPrevDrawn', i, cachedRecords.ids.length);
106 }
107
108
109 function _rdetailNav(id, offset) {
110         var args = {};
111         args[PARAM_RID] = id;
112         goTo(buildOPACLink(args));
113 }
114
115 function rdetailHandlePlaceHold() {
116     function reload() {
117         location.href = location.href.replace(/&place_hold=1/, '');
118     }
119     attachEvt("common", "holdUpdated", reload);
120     attachEvt("common", "holdUpdateCanceled", reload);
121     attachEvt("common", "loginCanceled", reload);
122     holdsDrawEditor({record:getRid(), type: 'T'});
123 }
124
125 function rdetailDraw() {
126
127     if(new CGI().param('place_hold')) {
128         rdetailHandlePlaceHold();
129         return;
130     }
131
132         detachAllEvt('common','depthChanged');
133         detachAllEvt('common','locationUpdated');
134         attachEvt('common','depthChanged', rdetailReload);
135         attachEvt('common','locationUpdated', rdetailReload);
136         attachEvt('common','holdUpdated', rdetailReload);
137         attachEvt('common','holdUpdateCanceled', rdetailReload);
138
139         copyRowParent = G.ui.rdetail.cp_info_row.parentNode;
140         copyRow = copyRowParent.removeChild(G.ui.rdetail.cp_info_row);
141         statusRow = G.ui.rdetail.cp_status.parentNode;
142         statusRow.id = '__rdsrow';
143
144         G.ui.rdetail.cp_info_local.onclick = rdetailShowLocalCopies;
145         G.ui.rdetail.cp_info_all.onclick = rdetailShowAllCopies;
146
147         if(getLocation() == globalOrgTree.id())
148                 hideMe(G.ui.rdetail.cp_info_all);
149
150     if(getRid()) {
151
152             var req = new Request(FETCH_RMODS, getRid());
153             req.callback(_rdetailDraw);
154             req.send();
155
156     } else { // No record ID was specified
157
158        // If we have an ISBN in the URL, let's try to find that record
159        // This allows direct linking by ISBN.
160        // Note, this uses the first record it finds
161        if(getRtype() == RTYPE_ISBN) { 
162             var req = new Request(FETCH_ADV_ISBN_RIDS, getAdvTerm() );
163             req.callback(
164                 function(r) {
165                     var blob = r.getResultObject();
166                     if(blob && blob.count > 0) 
167                         RID = blob.ids[0]; 
168                     var req2 = new Request(FETCH_RMODS, getRid());
169                     req2.callback(_rdetailDraw);
170                     req2.send();
171                 }
172             );
173             req.send();
174         }
175     }
176
177
178         if (rdetailDisplaySerialHoldings && (
179         isXUL() || !fetchOrgSettingDefault(
180             getLocation(), "opac.fully_compressed_serial_holdings")
181         )
182     ) {
183                 var req = new Request(FETCH_MFHD_SUMMARY, getRid());
184                 req.callback(_holdingsDraw);
185                 req.send();
186                 if (isXUL()) {
187                         var here = findOrgUnit(getLocation());
188                         dojo.place("<div id='mfhd_ad_menu'></div>", "rdetail_details_table", "after");
189                         var mfhd_add = new dijit.Menu({style:"float: right;"});
190                         new dijit.MenuItem({onClick:function(){
191                                 var req = new Request(CREATE_MFHD_RECORD, G.user.session, 1, here.id(), getRid());
192                                 var res = req.send();
193                                 alert(dojo.string.substitute(opac_strings.CREATED_MFHD_RECORD, [here.name()]));
194                         }, label:opac_strings.CREATE_MFHD}).placeAt(mfhd_add);
195                         mfhd_add.placeAt(mfhd_ad_menu);
196                 }
197         }
198
199         detachAllEvt("result", "idsReceived");
200         G.evt.result.hitCountReceived = [];
201         G.evt.result.recordReceived = [];
202         G.evt.result.copyCountsReceived = [];
203         G.evt.result.allRecordsReceived = [];
204
205     if(isXUL()) 
206         unHideMe($('rdetail_show_orders'));
207 }
208
209 function rdetailGetPageIds() {
210         attachEvt("result", "idsReceived", rdetailSetPaging );
211         resultFetchAllRecords = true;
212         rresultCollectIds(true);
213 }
214
215
216 function buildunAPISpan (span, type, id) {
217         var cgi = new CGI();
218         var d = new Date();
219
220         addCSSClass(span,'unapi-id');
221
222         span.setAttribute(
223                         'title', 'tag:' + cgi.server_name + ',' +
224                         d.getFullYear() + ':' + type + '/' + id
225                         );
226 }
227
228 function rdetailViewMarc(r,id) {
229         hideMe($('rdetail_extras_loading'));
230         $('rdetail_view_marc_box').innerHTML = r.getResultObject();
231
232         var div = elem('div', { "class" : 'hide_me' });
233         var span = div.appendChild( elem('abbr') );
234
235         buildunAPISpan( span, 'biblio-record_entry', record.doc_id() );
236
237         $('rdetail_view_marc_box').insertBefore(span, $('rdetail_view_marc_box').firstChild);
238 }
239
240 function rdetailForeignItems(r,id) {
241         hideMe($('rdetail_extras_loading'));
242     var tbody = $('rdetail_foreign_items_tbody');
243
244     var robj = r.getResultObject(); /* { 'records' : [ mvr1, mvr2, ...], 'items' : [ acp1, acp2, ... ] } */
245
246     for (var i = 0; i < robj.records.length; i++) {
247         var args = {};
248         args.page = RDETAIL;
249         args[PARAM_OFFSET] = 0;
250         args[PARAM_RID] = robj.records[i].doc_id();
251         var row = elem('tr'); tbody.appendChild(row);
252         var td1 = elem('td'); row.appendChild(td1);
253         var title = elem(
254             'a',
255             {
256                 'href' : buildOPACLink(args),
257                 'class' : 'classic_link'
258             },
259             robj.records[i].title()
260         );
261         td1.appendChild(title);
262         var td2 = elem('td',{},robj.records[i].author()); row.appendChild(td2);
263         var td3 = elem('td'); row.appendChild(td3);
264         var details = elem(
265             'a',
266             {
267                 'href' : 'javascript:void(0)',
268                 'class' : 'classic_link'
269             },
270             'Copy Details'
271         );
272         details.onclick = function(idx){
273             return function() {
274                 cpdBuild( tbody, row, robj.records[idx], null, 1, 0, 1,
275                     dojo.filter( robj.items, function(item) { return item.call_number().record() == robj.records[idx].doc_id(); } ) );
276             };
277         }(i);
278         td3.appendChild(details);
279     }
280 }
281
282
283
284 function rdetailShowLocalCopies() {
285         rdetailShowLocal = true;
286         rdetailBuildInfoRows();
287         hideMe(G.ui.rdetail.cp_info_local);
288         unHideMe(G.ui.rdetail.cp_info_all);
289         hideMe(G.ui.rdetail.cp_info_none); 
290 }
291
292 function rdetailShowAllCopies() {
293
294         rdetailShowLocal = false;
295         rdetailBuildInfoRows();
296         hideMe(G.ui.rdetail.cp_info_all);
297         unHideMe(G.ui.rdetail.cp_info_local);
298         hideMe(G.ui.rdetail.cp_info_none); 
299 }
300
301 function OpenMarcEditWindow(pcrud, rec) {
302         /*
303            To run in Firefox directly, must set signed.applets.codebase_principal_support
304            to true in about:config
305          */
306         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
307         win = window.open('/xul/server/cat/marcedit.xul'); // XXX version?
308         dojo.require('openils.PermaCrud');
309
310         win.xulG = {
311                 "record": {"marc": rec.marc(), "rtype": "sre"},
312                 "save": {
313                         "label": opac_strings.SAVE_MFHD_LABEL,
314                         "func": function(xmlString) {
315                                 rec.marc(xmlString);
316                                 rec.edit_date('now');
317                                 rec.ischanged(true);
318                                 pcrud.update(rec);
319                         }
320                 },
321         'lock_tab' : typeof xulG != 'undefined' ? (typeof xulG['lock_tab'] != 'undefined' ? xulG.lock_tab : undefined) : undefined,
322         'unlock_tab' : typeof xulG != 'undefined' ? (typeof xulG['unlock_tab'] != 'undefined' ? xulG.unlock_tab : undefined) : undefined
323         };
324 }
325
326 function loadMarcEditor(recId) {
327         var pcrud = new openils.PermaCrud({"authtoken": G.user.session});
328         var rec = pcrud.retrieve("sre", recId);
329         if (rec) {
330                 OpenMarcEditWindow(pcrud, rec);
331         }
332 }
333
334 /*
335  * This function could be written much more intelligently
336  * Limited brain power means that I'm brute-forcing it for now
337  */
338 function _holdingsDraw(h) {
339         holdings = h.getResultObject();
340         if (!holdings) { return null; }
341
342         dojo.forEach(holdings, _holdingsDrawMFHD);
343
344         // Populate XUL menus
345         if (isXUL()) {
346                 runEvt('rdetail','MFHDDrawn');
347         }
348 }
349
350 function _holdingsDrawMFHD(holdings, entryNum) {
351
352         var here = findOrgUnit(getLocation());
353         if (getDepth() > 0 || getDepth === 0 ) {
354                 while (getDepth() < findOrgDepth(here))
355                 here = findOrgUnit( here.parent_ou() );
356                 if (!orgIsMine(findOrgUnit(here), findOrgUnit(holdings.owning_lib()))) {
357                         return null;
358                 }
359         }
360
361         var hb = holdings.basic_holdings();
362         var hba = holdings.basic_holdings_add();
363         var hs = holdings.supplement_holdings();
364         var hsa = holdings.supplement_holdings_add();
365         var hi = holdings.index_holdings();
366         var hia = holdings.index_holdings_add();
367         var ho = holdings.online();
368         var hm = holdings.missing();
369         var hinc = holdings.incomplete();
370         var hloc = holdings.location() || 'MFHD';
371
372         if (    hb.length == 0 && hba.length == 0 && hs.length == 0 &&
373                 hsa.length == 0 && hi.length == 0 && hia.length == 0 &&
374                 ho.length == 0 && hm.length == 0 && hinc.length == 0
375         ) {
376
377                 if (isXUL()) {
378                         /* 
379                          * If we have a record, but nothing to show for it, then the
380                          * record is likely empty or corrupt. This gives cataloguers a
381                          * chance to add holdings or correct the record
382                          */
383                         hb = ['PLACEHOLDER'];
384                 } else {
385                         return null;
386                 }
387         }
388
389         // Show entryNum + 1 in staff client for better menu correlation
390         // Maybe this should be holdings.sre_id() instead? (which could get long after time)
391         var entryNumString = '';
392         if (isXUL()) {
393                 var entryNumInc = entryNum + 1;
394                 entryNumString = ' [Entry #'+entryNumInc+'] ';
395         }
396
397         var refNode;
398         if (entryNum > 0) {
399                 refNode = 'rdetail_holdings_table_' + (entryNum - 1);
400         } else {
401                 refNode = 'rdetail_details_table';
402         }
403
404         dojo.place("<table style='width: 100%;' id='rdetail_holdings_table_"+entryNum+"'><caption id='mfhdHoldingsCaption" + entryNum + "' class='rdetail_header color_1'>" +
405                 dojo.string.substitute(opac_strings.HOLDINGS_TABLE_CAPTION, [hloc]) + entryNumString +
406                 "</caption><tbody id='rdetail_holdings_tbody_" + entryNum +
407                 "'></tbody></table>", refNode, "after"
408         );
409         if (hb.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.BASIC_HOLDINGS, hb); }
410         if (hba.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.BASIC_HOLDINGS_ADD, hba); }
411         if (hs.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.SUPPLEMENT_HOLDINGS, hs); }
412         if (hsa.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.SUPPLEMENT_HOLDINGS_ADD, hsa); }
413         if (hi.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.INDEX_HOLDINGS, hi); }
414         if (hia.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.INDEX_HOLDINGS_ADD, hia); }
415         if (ho.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.ONLINE_VOLUMES, ho); }
416         if (hm.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.MISSING_VOLUMES, hm); }
417         if (hinc.length > 0) { _holdingsDrawMFHDEntry(entryNum, opac_strings.INCOMPLETE_VOLUMES, hinc); }
418
419         if (isXUL()) {
420                 mfhdDetails.push({ 'id' : holdings.sre_id(), 'label' : hloc, 'entryNum' : entryNum, 'owning_lib' : holdings.owning_lib() });
421                 dojo.require('openils.Event');
422                 dojo.require('openils.PermaCrud');
423                 var mfhd_edit = new dijit.Menu({});
424                 new dijit.MenuItem({onClick: function(){loadMarcEditor(holdings.sre_id())}, label:opac_strings.EDIT_MFHD_RECORD}).placeAt(mfhd_edit, "first");
425                 new dijit.MenuItem({onClick:function(){
426                         // Avoid accidental deletion of MFHD records
427                         if (!confirm(opac_strings.DELETE_MFHD_CONFIRM)) {
428                                 return;
429                         }
430                         var pcrud = new openils.PermaCrud({"authtoken": G.user.session});
431                         var mfhd_rec = pcrud.retrieve("sre", holdings.sre_id());
432                         if (mfhd_rec) {
433                                 pcrud.eliminate(mfhd_rec);
434                                 alert(dojo.string.substitute(opac_strings.DELETED_MFHD_RECORD, [holdings.sre_id()]));
435                         }
436                 }, label:opac_strings.DELETE_MFHD}).placeAt(mfhd_edit, "last");
437                 // new dijit.MenuItem({onClick:function(){alert("Edit properties " + holdings.sre_id());}, label:opac_strings.EDIT_PROPERTIES}).placeAt(mfhd_edit, "last");
438                 var mfhd_mb = new dijit.form.DropDownButton({dropDown: mfhd_edit, label:opac_strings.EDIT_MFHD_MENU, style:"float:right"});
439                 mfhd_mb.placeAt("mfhdHoldingsCaption" + entryNum, "last");
440                 mfhd_edit.startup();
441         }
442 }
443
444 function _holdingsDrawMFHDEntry(entryNum, entryName, entry) {
445         var flatEntry = entry.toString().replace(/,/g, ', ');
446         dojo.place("<tr><td> </td><td nowrap='nowrap' class='rdetail_desc'>" + entryName + "</td><td class='rdetail_item'>" + flatEntry + "</td></tr>", "rdetail_holdings_tbody_" + entryNum, "last");
447 }
448
449 function _rdetailDraw(r) {
450         record = r.getResultObject();
451
452         runEvt('rdetail', 'recordRetrieved', record.doc_id());
453
454         G.ui.rdetail.title.appendChild(text(record.title()));
455         buildSearchLink(STYPE_AUTHOR, record.author(), G.ui.rdetail.author);
456         G.ui.rdetail.isbn.appendChild(text(cleanISBN(record.isbn())));
457         G.ui.rdetail.edition.appendChild(text(record.edition()));
458         G.ui.rdetail.pubdate.appendChild(text(record.pubdate()));
459         G.ui.rdetail.publisher.appendChild(text(record.publisher()));
460         $('rdetail_physical_desc').appendChild(text(record.physical_description()));
461         r = record.types_of_resource();
462         if(r) {
463                 G.ui.rdetail.tor.appendChild(text(r[0]));
464                 setResourcePic( G.ui.rdetail.tor_pic, r[0]);
465         }
466         G.ui.rdetail.abstr.appendChild(text(record.synopsis()));
467
468         try{
469                 if(record.isbn()) {
470                         if(ENABLE_ADDED_CONTENT_ATTRIB_LINKS) {
471                                 unHideMe($('rdetail.jacket_attrib_div'));
472                                 var href = $('rdetail.jacket_attrib_link').getAttribute('href') +cleanISBN(record.isbn());
473                                 $('rdetail.jacket_attrib_link').setAttribute('href', href);
474                         }
475                         rdetailCheckForGBPreview();
476
477                 } else {
478                         hideMe($("rdetail.jacket_attrib_div"));
479                         hideMe($("rdetail_img_link"));
480                 }
481         } catch(E) {}
482
483
484         // see if the record has any external links 
485         var links = record.online_loc();
486         for( var i = 0; links && links.length > 0 && i < links.length; i = i + 3 ) {
487                 var href = links[i];
488                 // avoid matching "HTTP: The Complete Reference"
489                 if( href.match(/https?:\/|ftps?:\/|mailto:/i) ) {
490                         unHideMe($('rdetail_online_row'));
491                         // MODS can contain a display label (used for the text of the link)
492                         // as well as a note about the URL; many legacy systems conflate the
493                         // two and generate MARC records that expect the note to be used as
494                         // the text of the link, with no display label; here's the canonical
495                         // format:
496                         //
497                         // 856 40 $uhttp://localhost$yDisplay label$zPublic note
498                         //
499                         // Note that the MARC21slim2MODS XSL concatenates $3 and $y together
500                         // (as $y was defined later in MARC21's life as the display label)
501                         var displayLabel = '' + links[i+1];
502                         var note = '' + links[i+2];
503                         if(!displayLabel || displayLabel.match(/https?:\/|ftps?:\/|mailto:/i)) {
504                                 if(!note || note.match(/https?:\/|ftps?:\/|mailto:/i)) {
505                                         displayLabel = href;
506                                 } else {
507                                         displayLabel = note;
508                                 }
509                         }
510                         $('rdetail_online').appendChild(elem('a', {href:href,'class':'classic_link'}, displayLabel));
511                         if (note && note != displayLabel) {
512                                 $('rdetail_online').appendChild(elem('span', {'class':'url_note'}, ' - ' + note));
513                         }
514                         $('rdetail_online').appendChild(elem('br'));
515                 }
516         }
517
518         // Fill in our unAPI ID, if anyone cares
519         var abbrs = document.getElementsByTagName('abbr');
520         var span;
521         for (var i = 0; i < abbrs.length; i++) {
522                 if (abbrs[i].getAttribute('name') == 'unapi') {
523                         span = abbrs[i];
524                         break;
525                 }
526         }
527         buildunAPISpan( span, 'biblio-record_entry', record.doc_id() );
528
529         $('rdetail_place_hold').onclick = function() {
530         var src = location.href;
531
532         if(forceLoginSSL && src.match(/^http:/)) {
533
534             src = src.replace(/^http:/, 'https:');
535
536             if(!src.match(/&place_hold=1/)) {
537                 src += '&place_hold=1';
538             }
539
540             location.href = src;
541
542         } else {
543             holdsDrawEditor({record:record.doc_id(), type:'T'});
544         }
545     }
546
547
548         var RW = $('rdetail_exp_refworks');
549         if (RW && rdetailEnableRefWorks) {
550
551                 var here = (findOrgUnit(getLocation())).name();
552                 var org_name = here.replace(" ", "+");
553                 var cgi = new CGI();
554
555                 RW.setAttribute(
556                         'href',
557                         rdetailRefWorksHost + '/express/expressimport.asp?vendor='
558                         + org_name
559                         + '&filter=MARC+Format&database=All+MARC+Formats&encoding=65001&url=http%3A%2F%2F'
560                         + cgi.server_name + '/opac/extras/supercat/marctxt/record/'
561                         + record.doc_id()
562                );
563
564                 RW.setAttribute('target', 'RefWorksMain');
565
566                 unHideMe($('rdetail_exp_refworks_span'));
567         }
568
569         $('rdetail_img_link').setAttribute('href', buildISBNSrc(cleanISBN(record.isbn()), 'large'));
570         G.ui.rdetail.image.setAttribute("src", buildISBNSrc(cleanISBN(record.isbn())));
571         runEvt("rdetail", "recordDrawn");
572         recordsCache.push(record);
573
574         rdetailSetExtrasSelector();
575
576         var breq = new Request(FETCH_BRE, [getRid()]);
577         breq.callback( rdetailCheckDeleted );
578         breq.send();
579
580         //resultBuildCaches( [ record ] );
581         //resultDrawSubjects();
582         //resultDrawSeries();
583
584         // grab added content 
585
586     // Proxied through Evergreen AddedContent module
587         acCollectData(cleanISBN(record.isbn()), rdetailhandleAC);
588
589     var currentISBN = cleanISBN(record.isbn());
590
591     // Not proxied, cross-site javascript
592
593     // ChiliFresh
594     if (chilifresh && chilifresh != '(none)' && currentISBN) {
595         $('chilifreshReviewLink').setAttribute('id','isbn_'+currentISBN);
596         $('chilifreshReviewResult').setAttribute('id','chili_review_'+currentISBN);
597         unHideMe($('rdetail_reviews_link'));
598         unHideMe($('rdetail_chilifresh_reviews'));
599         try {
600             chili_init();
601         } catch(E) {
602             console.log(E + '\n');
603             hideMe($('rdetail_reviews_link'));
604             hideMe($('rdetail_chilifresh_reviews'));
605         }
606     }
607
608     // Novelist
609     if (novelist && currentISBN) {
610         unHideMe($('rdetail_novelist_link'));
611     }
612
613     // Multi-Home / Foreign Items / Peer Bibs
614     var req = new Request( TEST_PEER_BIBS, record.doc_id() );
615     req.callback(function(r){
616         var test = r.getResultObject();
617         if (test == "1") {
618             unHideMe($('rdetail_foreign_items_link'));
619         }
620     }); 
621     req.send();
622 }
623
624
625
626 function rdetailCheckDeleted(r) {
627         var br = r.getResultObject()[0];
628         if( isTrue(br.deleted()) ) {
629                 hideMe($('rdetail_place_hold'));
630                 $('rdetail_more_actions_selector').disabled = true;
631                 unHideMe($('rdetail_deleted_exp'));
632         }
633 }
634
635 function rdetailSetExtrasSelector() {
636         if(!grabUser()) return;
637         unHideMe($('rdetail_more_actions'));
638
639         var req = new Request( 
640                         FETCH_CONTAINERS, G.user.session, G.user.id(), 'biblio', 'bookbag' );
641         req.callback(rdetailAddBookbags);
642         req.send();
643 }
644
645 function rdetailAddBookbags(r) {
646
647         var containers = r.getResultObject();
648         var selector = $('rdetail_more_actions_selector');
649         var found = false;
650         var index = 3;
651         doSelectorActions(selector);
652
653         for( var i = 0; i < containers.length; i++ ) {
654                 found = true;
655                 var container = containers[i];
656                 insertSelectorVal( selector, index++, container.name(), 
657                                 "container_" + container.id(), rdetailAddToBookbag,  1 );
658         }
659
660         nextContainerIndex = index;
661 }
662
663 var _actions = {};
664 function rdetailNewBookbag() {
665         var name = prompt($('rdetail_bb_new').innerHTML,"");
666         if(!name) return;
667
668         var id;
669         if( id = containerCreate( name ) ) {
670                 alert($('rdetail_bb_success').innerHTML);
671                 var selector = $('rdetail_more_actions_selector');
672                 insertSelectorVal( selector, nextContainerIndex++, name, 
673                                 "container_" + id, rdetailAddToBookbag, 1 );
674                 setSelector( selector, 'start' );
675         }
676 }
677
678
679 function rdetailAddToBookbag() {
680         var selector = $('rdetail_more_actions_selector');
681         var id = selector.options[selector.selectedIndex].value;
682         setSelector( selector, 'start' );
683
684         if( containerCreateItem( id.substring(10), record.doc_id() )) {
685                 alert($('rdetail_bb_item_success').innerHTML);
686         }
687 }
688
689
690 var rdetailMarcFetched = false;
691 var rdetailForeignItemsFetched = false;
692 function rdetailShowExtra(type, args) {
693
694         hideMe($('rdetail_copy_info_div'));
695         hideMe($('rdetail_summary_div'));
696         hideMe($('rdetail_reviews_div'));
697         hideMe($('rdetail_toc_div'));
698         hideMe($('rdetail_anotes_div'));
699         hideMe($('rdetail_excerpt_div'));
700         hideMe($('rdetail_preview_div'));
701         hideMe($('rdetail_marc_div'));
702         hideMe($('cn_browse'));
703         hideMe($('rdetail_cn_browse_div'));
704         hideMe($('rdetail_novelist_div'));
705         hideMe($('rdetail_foreign_items_div'));
706         hideMe($('rdetail_notes_div'));
707
708         removeCSSClass($('rdetail_copy_info_link'), 'rdetail_extras_selected');
709         removeCSSClass($('rdetail_viewcn_link'), 'rdetail_extras_selected');
710         removeCSSClass($('rdetail_summary_link'), 'rdetail_extras_selected');
711         removeCSSClass($('rdetail_reviews_link'), 'rdetail_extras_selected');
712         removeCSSClass($('rdetail_toc_link'), 'rdetail_extras_selected');
713         removeCSSClass($('rdetail_excerpt_link'), 'rdetail_extras_selected');
714         removeCSSClass($('rdetail_preview_link'), 'rdetail_extras_selected');
715         removeCSSClass($('rdetail_anotes_link'), 'rdetail_extras_selected');
716         removeCSSClass($('rdetail_annotation_link'), 'rdetail_extras_selected');
717         removeCSSClass($('rdetail_viewmarc_link'), 'rdetail_extras_selected');
718         removeCSSClass($('rdetail_novelist_link'), 'rdetail_extras_selected');
719         removeCSSClass($('rdetail_foreign_items_link'), 'rdetail_extras_selected');
720
721         switch(type) {
722
723                 case "copyinfo": 
724                         unHideMe($('rdetail_copy_info_div')); 
725                         addCSSClass($('rdetail_copy_info_link'), 'rdetail_extras_selected');
726                         break;
727
728         case "summary":
729             addCSSClass($('rdetail_summary_link'), 'rdetail_extras_selected');
730             unHideMe($('rdetail_summary_div'));
731             break;
732
733                 case "reviews": 
734                         addCSSClass($('rdetail_reviews_link'), 'rdetail_extras_selected');
735                         unHideMe($('rdetail_reviews_div')); 
736                         break;
737
738                 case "excerpt": 
739                         addCSSClass($('rdetail_excerpt_link'), 'rdetail_extras_selected');
740                         unHideMe($('rdetail_excerpt_div'));
741                         break;
742
743                 case "preview": 
744                         addCSSClass($('rdetail_preview_link'), 'rdetail_extras_selected');
745                         unHideMe($('rdetail_preview_div'));
746                         rdetailDisplayGBPreview();
747                         break;
748
749                 case "anotes": 
750                         addCSSClass($('rdetail_anotes_link'), 'rdetail_extras_selected');
751                         unHideMe($('rdetail_anotes_div'));
752                         break;
753
754                 case "toc": 
755                         addCSSClass($('rdetail_toc_link'), 'rdetail_extras_selected');
756                         unHideMe($('rdetail_toc_div'));
757                         break;
758
759                 case "marc": 
760                         addCSSClass($('rdetail_viewmarc_link'), 'rdetail_extras_selected');
761                         unHideMe($('rdetail_marc_div')); 
762                         if(rdetailMarcFetched) return;
763                         unHideMe($('rdetail_extras_loading'));
764                         rdetailMarcFetched = true;
765                         var req = new Request( FETCH_MARC_HTML, record.doc_id() );
766                         req.callback(rdetailViewMarc); 
767                         req.send();
768                         break;
769
770                 case "novelist": 
771                         addCSSClass($('rdetail_novelist_link'), 'rdetail_extras_selected');
772                         unHideMe($('rdetail_novelist_div')); 
773                         break;
774
775                 case "foreign_items": 
776                         addCSSClass($('rdetail_foreign_items_link'), 'rdetail_extras_selected');
777                         unHideMe($('rdetail_foreign_items_div')); 
778             if(rdetailForeignItemsFetched) return;
779                         unHideMe($('rdetail_extras_loading'));
780             rdetailForeignItemsFetched = true;
781                         var req = new Request( FETCH_PEER_BIBS, record.doc_id() );
782                         req.callback(rdetailForeignItems); 
783                         req.send();
784                         break;
785
786                 case 'cn':
787                         addCSSClass($('rdetail_viewcn_link'), 'rdetail_extras_selected');
788                         unHideMe($('rdetail_cn_browse_div'));
789                         rdetailShowCNBrowse(defaultCN, getLocation(), null, true);
790                         break;
791
792         }
793 }
794
795 function rdetailVolumeDetails(args) {
796         var row = $(args.rowid);
797         var tbody = row.parentNode;
798         cpdBuild( tbody, row, record, args.cn, args.org, args.depth, args.copy_location );
799         return;
800 }
801
802 function rdetailBuildCNList() {
803
804         var select = $('cn_browse_selector');
805         var index = 0;
806         var arr = [];
807         for( var cn in callnumberCache ) arr.push( cn );
808         arr.sort();
809
810         if( arr.length == 0 ) {
811                 hideMe($('rdetail_cn_browse_select_div'));
812                 return;
813         }
814
815         for( var i = 0; i < arr.length; i++ ) {
816                 var cn = arr[i];
817                 var opt = new Option(cn);
818                 select.options[index++] = opt;
819         }
820         select.onchange = rdetailGatherCN;
821 }
822
823 function rdetailGatherCN() {
824         var cn = getSelectorVal($('cn_browse_selector'));
825         rdetailShowCNBrowse( cn, getLocation(), getDepth(), true );
826         setSelector( $('cn_browse_selector'), cn );
827 }
828
829
830 function rdetailShowCNBrowse( cn, loc, depth, fromOnclick ) {
831
832         if(!cn) {
833                 unHideMe($('cn_browse_none'));
834                 hideMe($('rdetail_cn_browse_select_div'));
835                 return;
836         }
837
838         unHideMe($('rdetail_cn_browse_select_div'));
839         rdetailBuildCNList();
840         setSelector( $('cn_browse_selector'), cn );
841         hideMe($('rdetail_copy_info_div'));
842         hideMe($('rdetail_reviews_div'));
843         hideMe($('rdetail_summary_div'));
844         hideMe($('rdetail_toc_div'));
845         hideMe($('rdetail_marc_div'));
846         unHideMe($('rdetail_cn_browse_div'));
847         unHideMe($('cn_browse'));
848         if( !rdetailLocalOnly && ! fromOnclick ) depth = findOrgDepth(globalOrgTree);
849         cnBrowseGo(cn, loc, depth);
850 }
851
852 function rdetailhandleAC(data) {
853
854         if( data.summary.html ) {
855                 $('rdetail_summary_div').innerHTML = data.summary.html;
856                 unHideMe($('rdetail_summary_link'));
857         }
858
859         if( data.reviews.html ) {
860                 $('rdetail_review_container').innerHTML = data.reviews.html;
861                 unHideMe($('rdetail_reviews_link'));
862         }
863
864         if( data.toc.html ) {
865                 $('rdetail_toc_div').innerHTML = data.toc.html;
866                 unHideMe($('rdetail_toc_link'));
867         }
868
869         if( data.excerpt.html ) {
870                 $('rdetail_excerpt_div').innerHTML = data.excerpt.html;
871                 unHideMe($('rdetail_excerpt_link'));
872         }
873
874         if( data.anotes.html ) {
875                 $('rdetail_anotes_div').innerHTML = data.anotes.html;
876                 unHideMe($('rdetail_anotes_link'));
877         }
878 }
879
880 function rdetailShowReviews(r) {
881         hideMe($('rdetail_extras_loading'));
882         var res = r.getResultObject();
883         var par = $('rdetail_reviews_div');
884         var template = par.removeChild($('rdetail_review_template'));
885         if( res && res.length > 0 ) {
886                 unHideMe($('rdetail_reviews_link'));
887                 for( var i = 0; i < res.length; i++ ) {
888                         var rev = res[i];       
889                         if( rev.text && rev.info ) {
890                                 var node = template.cloneNode(true);
891                                 $n(node, 'review_header').appendChild(text(rev.info));
892                                 $n(node, 'review_text').appendChild(text(rev.text));
893                                 par.appendChild(node);
894                         }
895                 }
896         }
897 }
898
899
900 function rdetailShowTOC(r) {
901         hideMe($('rdetail_extras_loading'));
902         var resp = r.getResultObject();
903         if(resp) {
904                 unHideMe($('rdetail_toc_link'));
905                 $('rdetail_toc_div').innerHTML = resp;
906         }
907 }
908
909 function rdetailBuildInfoRows() {
910         var req;
911         var method = FETCH_COPY_COUNTS_SUMMARY;
912         if (rdetailShowCopyLocation)
913                 method = FETCH_COPY_LOCATION_COUNTS_SUMMARY;
914
915         if( rdetailShowLocal ) 
916                 req = new Request(method, record.doc_id(), getLocation(), getDepth())
917         else
918                 req = new Request(method, record.doc_id());
919         req.callback(_rdetailBuildInfoRows);
920         req.send();
921 }
922
923 function _rdetailRows(node) {
924
925         if( rdetailShowLocal && getLocation() != globalOrgTree.id() ) {
926                 var loc = findOrgUnit(getLocation());
927                 if( node ) {
928                         if( !orgIsMine(node, loc) && !orgIsMine(loc,node) ) return;
929                 } else {
930             var kids = globalOrgTree.children();
931             if (kids) {
932                         for( var i = 0; i < kids.length; i++ ) {
933                                 var org = findOrgUnit(kids[i]);
934                                 if( orgIsMine(org, loc) ) {
935                                         node = org;
936                                         break;
937                                 }
938                 }
939                         }
940                 } 
941         }
942
943         if(!node && findOrgType(globalOrgTree.ou_type()).can_have_vols())
944                 node = globalOrgTree;
945
946
947         /* don't show hidden orgs */
948
949         if(node) {
950
951                 if(!isXUL() && !isTrue(node.opac_visible())) return;
952
953                 if (orgHiding) {
954                         if (isTrue( findOrgType(node.ou_type()).can_have_vols() )) {
955                                 if ( ! orgIsMine( orgHiding.org, node, orgHiding.depth ) ) {
956                                         return;
957                                 }
958                         }
959                 }
960
961                 var row = copyRow.cloneNode(true);
962                 row.id = "cp_info_" + node.id();
963
964                 var libtd = findNodeByName( row, config.names.rdetail.lib_cell );
965                 var cntd  = findNodeByName( row, config.names.rdetail.cn_cell );
966                 var cpctd = findNodeByName( row, config.names.rdetail.cp_count_cell );
967                 var actions = $n(row, 'rdetail_actions_cell');
968
969                 var p = libtd.getElementsByTagName('a')[0];
970                 libtd.insertBefore(text(node.name()), p);
971                 libtd.setAttribute("style", "padding-left: " + ((findOrgDepth(node) - 1)  * 9) + "px;");
972
973                 if(!findOrgType(node.ou_type()).can_have_vols()) {
974
975                         row.removeChild(cntd);
976                         row.removeChild(cpctd);
977                         row.removeChild(actions);
978                         row.setAttribute('novols', '1');
979
980                         libtd.setAttribute("colspan", numStatuses + 3 );
981                         libtd.colSpan = numStatuses + 3;
982                         addCSSClass(row, 'copy_info_region_row');
983                 } 
984
985                 copyRowParent.appendChild(row);
986
987         } else { node = globalOrgTree; }
988
989     var kids = node.children();
990     if (kids) {
991         for( var c = 0; c < kids.length; c++ ) 
992                 _rdetailRows(kids[c]);
993     }
994 }
995
996 function rdetailCNPrint(orgid, cn) {
997         var div = cpdBuildPrintWindow( record, orgid);
998         var template = div.removeChild($n(div, 'cnrow'));
999         var rowNode = $("cp_info_" + orgid);
1000         cpdStylePopupWindow(div);
1001         openWindow(div.innerHTML);
1002 }
1003
1004 var localCNFound = false;
1005 var ctr = 0;
1006 function _rdetailBuildInfoRows(r) {
1007
1008         if (rdetailShowCopyLocation)
1009                 unHideMe( $n( $('rdetail_copy_info_table'), 'rdetail_copylocation_header' ) );
1010
1011         removeChildren(copyRowParent);
1012
1013         orgHiding = checkOrgHiding();
1014
1015         _rdetailRows();
1016
1017         var summary = r.getResultObject();
1018         if(!summary) return;
1019
1020         var found = false;
1021         for( var i = 0; i < summary.length; i++ ) {
1022
1023                 var arr = summary[i];
1024                 globalCNCache[arr[1]] = 1;
1025                 var thisOrg = findOrgUnit(arr[0]);
1026                 var rowNode = $("cp_info_" + thisOrg.id());
1027                 if(!rowNode) continue;
1028
1029                 if(rowNode.getAttribute("used")) {
1030
1031                         if( rowNode.nextSibling ) {
1032                                 sib = rowNode.nextSibling;
1033                                 o ='cp_info_'+thisOrg.id()+'_';
1034                                 /* push the new row on as the last row for this org unit */
1035                                 while( sib && sib.id.match(o) ) {
1036                                         sib = sib.nextSibling;
1037                                 }
1038                                 if(sib)
1039                                         rowNode = copyRowParent.insertBefore(copyRow.cloneNode(true), sib);
1040                                 else
1041                                         rowNode = copyRowParent.appendChild(copyRow.cloneNode(true));
1042                         } else {
1043                                 rowNode = copyRowParent.appendChild(copyRow.cloneNode(true));
1044                         }
1045
1046                         var n = findNodeByName( rowNode, config.names.rdetail.lib_cell );
1047                         n.appendChild(text(thisOrg.name()));
1048                         n.setAttribute("style", "padding-left: " + ((findOrgDepth(thisOrg) - 1)  * 9) + "px;");
1049                         rowNode.id = "cp_info_" + thisOrg.id() + '_' + (++ctr); 
1050
1051                 } else {
1052                         rowNode.setAttribute("used", "1");
1053                 }
1054
1055                 var cpc_temp = rowNode.removeChild(
1056                                 findNodeByName(rowNode, config.names.rdetail.cp_count_cell));
1057
1058                 var statuses = arr[2];
1059                 var cl = '';
1060                 if (rdetailShowCopyLocation) {
1061                         cl = arr[2];
1062                         statuses = arr[3];
1063                 }
1064
1065
1066                 rdetailApplyStatuses(rowNode, cpc_temp, statuses);
1067
1068                 var isLocal = false;
1069                 if( orgIsMine( findOrgUnit(getLocation()), thisOrg ) ) { 
1070                         found = true; 
1071                         isLocal = true; 
1072                         if(!localCNFound) {
1073                                 localCNFound = true;
1074                                 defaultCN = arr[1];
1075                         }
1076                 }
1077
1078                 //if(isLocal) unHideMe(rowNode);
1079                 unHideMe(rowNode);
1080
1081                 rdetailSetPath( thisOrg, isLocal );
1082                 rdetailBuildBrowseInfo( rowNode, arr[1], isLocal, thisOrg, cl );
1083
1084                 if( i == summary.length - 1 && !defaultCN) defaultCN = arr[1];
1085         }
1086
1087         if(!found) unHideMe(G.ui.rdetail.cp_info_none);
1088 }
1089
1090 function rdetailBuildBrowseInfo(row, cn, local, orgNode, cl) {
1091
1092         if(local) {
1093                 var cache = callnumberCache[cn];
1094                 if( cache ) cache.count++;
1095                 else callnumberCache[cn] = { count : 1 };
1096         }
1097
1098         var depth = getDepth();
1099         if( !local ) depth = findOrgDepth(globalOrgTree);
1100
1101         $n(row, 'rdetail_callnumber_cell').appendChild(text(cn));
1102
1103         if (rdetailShowCopyLocation) {
1104                 var cl_cell = $n(row, 'rdetail_copylocation_cell');
1105                 cl_cell.appendChild(text(cl));
1106                 unHideMe(cl_cell);
1107         }
1108
1109         _debug('setting action clicks for cn ' + cn);
1110
1111         var dHref = 'javascript:rdetailVolumeDetails('+
1112                         '{copy_location : "'+cl.replace(/\"/g, '\\"')+'", rowid : "'+row.id+'", cn :"'+cn.replace(/\"/g, '\\"')+'", depth:"'+depth+'", org:"'+orgNode.id()+'", local: '+local+'});';
1113
1114         var bHref = 'javascript:rdetailShowCNBrowse("' + cn.replace(/\"/g, '\\"') + '", '+orgNode.id()+', "'+depth+'");'; 
1115
1116         unHideMe( $n(row, 'details') )
1117                 $n(row, 'details').setAttribute('href', dHref);
1118         unHideMe( $n(row, 'browse') )
1119                 $n(row, 'browse').setAttribute('href', bHref);
1120
1121         if(isXUL()) {
1122                 unHideMe($n(row, 'hold_div'));
1123                 $n(row, 'hold').onclick = function() {
1124                         var req = new Request(FETCH_VOLUME_BY_INFO, cn, record.doc_id(), orgNode.id());
1125                         req.callback(
1126                                         function(r) {
1127                                         var vol = r.getResultObject();
1128                                         holdsDrawEditor({type: 'V', volumeObject : vol});
1129                                         }
1130                                     );
1131                         req.send();
1132                 };
1133         }
1134 }
1135
1136 // sets the path to org as 'active' and displays the path if it's local 
1137 function rdetailSetPath(org, local) {
1138         if( findOrgDepth(org) == 0 ) return;
1139         var row = $("cp_info_" + org.id());
1140         row.setAttribute("hasinfo", "1");
1141         unHideMe(row);
1142         rdetailSetPath(findOrgUnit(org.parent_ou()), local);
1143 }
1144
1145 //Append all the statuses for a given summary to the 
1146 //copy summary table 
1147 function rdetailApplyStatuses( row, template, statuses ) {
1148         for( var j in _statusPositions ) {
1149                 var stat = _statusPositions[j];
1150                 var val = statuses[stat.id()];
1151                 var nn = template.cloneNode(true);
1152                 if(val) nn.appendChild(text(val));
1153                 else nn.appendChild(text(0));
1154                 row.appendChild(nn);
1155         }
1156 }
1157
1158 //Add one td (creating a new column) to the copy summary
1159 //table for each opac_visible copy status
1160 function rdetailBuildStatusColumns() {
1161
1162         rdetailGrabCopyStatuses();
1163         var parent = statusRow;
1164         var template = parent.removeChild(G.ui.rdetail.cp_status);
1165
1166         var i = 0;
1167         for( i = 0; i < cp_statuses.length; i++ ) {
1168
1169                 var c = cp_statuses[i];
1170                 if( c && isTrue(c.opac_visible()) ) {
1171                         var name = c.name();
1172                         _statusPositions[i] = c;
1173                         var node = template.cloneNode(true);
1174                         var data = findNodeByName( node, config.names.rdetail.cp_status);
1175
1176                         data.appendChild(text(name));
1177                         parent.appendChild(node);
1178                 }
1179         }       
1180
1181         numStatuses = 0;
1182         for(x in _statusPositions) numStatuses++; 
1183 }
1184
1185 function rdetailGrabCopyStatuses() {
1186         if(cp_statuses) return cp_statuses;
1187         var req = new Request(FETCH_COPY_STATUSES);
1188         req.send(true);
1189         cp_statuses = req.result();
1190         cp_statuses = cp_statuses.sort(_rdetailSortStatuses);
1191 }
1192
1193 function _rdetailSortStatuses(a, b) {
1194         return parseInt(a.id()) - parseInt(b.id());
1195 }
1196
1197 /**
1198  * Check for a Google Book preview after the main page loads
1199  */
1200 function rdetailCheckForGBPreview() {
1201         if (!rdetailGoogleBookPreview) return;
1202         dojo.addOnLoad(function() {
1203                 searchForGBPreview( cleanISBN(record.isbn()) );
1204         });
1205 }
1206
1207 /**
1208  *
1209  * @param {DOM object} isbn The form element containing the input parameters "isbns"
1210  */
1211 function searchForGBPreview( isbn ) {
1212         dojo.require("dojo.io.script");
1213         dojo.io.script.get({"url": "https://www.google.com/jsapi"});
1214         dojo.io.script.get({"url": "http://books.google.com/books", "content": { "bibkeys": isbn, "jscmd": "viewapi", "callback": "GBPreviewCallback"}});
1215 }
1216
1217 /**
1218  * This function is the call-back function for the JSON scripts which 
1219  * executes a Google book search response.
1220  *
1221  * XXX I18N of text needed
1222  *
1223  * @param {JSON} GBPBookInfo is the JSON object pulled from the Google books service.
1224  */
1225 function GBPreviewCallback(GBPBookInfo) {
1226         var GBPreviewDiv = document.getElementById("rdetail_preview_div");
1227         var GBPBook;
1228
1229         for ( i in GBPBookInfo ) {
1230                 GBPBook = GBPBookInfo[i];
1231         }
1232
1233         if ( !GBPBook ) {
1234                 return;
1235         }
1236
1237         if ( GBPBook.preview != "noview" ) {
1238                 // Add a button below the book cover image to load the preview.
1239                 GBPBadge = document.createElement( 'img' );
1240                 GBPBadge.src = 'http://books.google.com/intl/en/googlebooks/images/gbs_preview_button1.gif';
1241                 GBPBadge.title = $('rdetail_preview_badge').innerHTML;
1242                 GBPBadge.style.border = 0;
1243                 GBPBadgelink = document.createElement( 'a' );
1244                 GBPBadgelink.href = 'javascript:rdetailShowExtra("preview");';
1245                 GBPBadgelink.appendChild( GBPBadge );
1246                 $('rdetail_image_cell').appendChild( GBPBadgelink );
1247                 $('rdetail_preview_div').style.height = 600;
1248
1249                 /* Display the "Preview" tab in the Extras section */
1250                 unHideMe( $('rdetail_preview_link' ) );
1251         }
1252 }
1253
1254 /**
1255  *  This is called when the user clicks on the 'Preview' link.  We assume
1256  *  a preview is available from Google if this link was made visible.
1257  *
1258  * XXX I18N of Google Book Preview language attribute needed
1259  */
1260 function rdetailDisplayGBPreview() {
1261         unHideMe($('rdetail_extras_loading'));
1262         GBPreviewPane = $('rdetail_preview_div');
1263         if ( GBPreviewPane.getAttribute('loaded') == null ||
1264                 GBPreviewPane.getAttribute('loaded') == "false" ) {
1265                 google.load("books", "0", {"callback" : rdetailGBPViewerLoadCallback, "language": "en"} );
1266                 GBPreviewPane.setAttribute('loaded', 'true');
1267         }
1268 }
1269
1270 function rdetailGBPViewerLoadCallback() {
1271         hideMe($('rdetail_extras_loading'));
1272         var GBPViewer = new google.books.DefaultViewer(document.getElementById('rdetail_preview_div'));
1273         GBPViewer.load('ISBN:' + cleanISBN(record.isbn()) );
1274
1275 }
1276
1277 function rdetailDrawExpandedHoldings(anchor, bibid, type) {
1278     var offsets = {"basic": 0, "index": 0, "supplement": 0};
1279     var limit = 10; /* XXX give user control over this? */
1280     var target_id = "holding_type_" + type;
1281     var target = dojo.byId(target_id);
1282
1283     anchor.innerHTML = "[-]";
1284     anchor.oldonclick = anchor.onclick;
1285     anchor.onclick = function() {
1286         anchor.onclick = anchor.oldonclick;
1287         anchor.innerHTML = "[+]";
1288         dojo.empty(target);
1289     };
1290
1291     function _load() {
1292         dojo.empty(target);
1293         fieldmapper.standardRequest(
1294             ["open-ils.serial",
1295                 "open-ils.serial.received_siss.retrieve.by_bib.atomic"], {
1296                 "params": [bibid, {"offset": offsets[type], "limit": limit}],
1297                 "async": true,
1298                 "oncomplete": function(r) {
1299                     try {
1300                         if (msg = r.recv().content()) { /* sic, assignment */
1301                             if (!msg.length) return;
1302                             offsets[type] += msg.length;
1303                             var table = dojo.create("table", null, target);
1304                             dojo.forEach(
1305                                 msg, function(o) {
1306                                     var tr = dojo.create("tr", null, table);
1307                                     dojo.create(
1308                                         "td", {
1309                                             "innerHTML": o.issuance.label(),
1310                                             "style": {"paddingLeft": "3em"}
1311                                         }, tr
1312                                     );
1313
1314                                     if (!o.has_units) return;
1315                                     /* can't place holds if no units */
1316                                     var td = dojo.create("td", null, tr);
1317                                     dojo.create(
1318                                         "a", {
1319                                             "href":"javascript:void(0);",
1320                                             "style": {"marginLeft": "1.5em"},
1321                                             "onclick": function() {
1322                                                 holdsDrawEditor({
1323                                                     "type": "I",
1324                                                     "issuance": o.issuance.id()
1325                                                 });
1326                                             },
1327                                             "innerHTML": "[" +
1328                                                 opac_strings.PLACE_HOLD + "]"
1329                                         }, td
1330                                     );
1331                                 }
1332                             );
1333                             if (msg.length == limit) {
1334                                 dojo.create("br", null, target);
1335                                 dojo.create(
1336                                     "a", {
1337                                         "href": "javascript:void(0);",
1338                                         "innerHTML":
1339                                             "[" + opac_strings.MORE + "]",
1340                                         "onclick": _load
1341                                     }, target
1342                                 );
1343                             }
1344                         }
1345                     } catch (E) {
1346                         void(0);
1347                     }
1348                 }
1349             }
1350         );
1351     }
1352     _load();
1353 }