From 5d7124ada1714f17c26c3c6be0047ad9422fa9f4 Mon Sep 17 00:00:00 2001 From: dbs Date: Mon, 16 Mar 2009 01:59:01 +0000 Subject: [PATCH] Backport preview, TOC, performer notes to rel_1_4 git-svn-id: svn://svn.open-ils.org/ILS-Contrib/conifer/branches/rel_1_4@186 6d9bc8c9-1ec2-4278-b937-99fde70a366f --- src/perlmods/OpenILS/Utils/ModsParser.pm | 492 +++++++++++ web/opac/skin/default/js/rdetail.js | 916 +++++++++++++++++++++ web/opac/skin/default/xml/index.xml | 22 + .../skin/default/xml/rdetail/rdetail_extras.xml | 121 +++ 4 files changed, 1551 insertions(+) create mode 100644 src/perlmods/OpenILS/Utils/ModsParser.pm create mode 100644 web/opac/skin/default/js/rdetail.js create mode 100644 web/opac/skin/default/xml/index.xml create mode 100644 web/opac/skin/default/xml/rdetail/rdetail_extras.xml diff --git a/src/perlmods/OpenILS/Utils/ModsParser.pm b/src/perlmods/OpenILS/Utils/ModsParser.pm new file mode 100644 index 0000000000..d37eb880c9 --- /dev/null +++ b/src/perlmods/OpenILS/Utils/ModsParser.pm @@ -0,0 +1,492 @@ +package OpenILS::Utils::ModsParser; +use strict; use warnings; + +use OpenSRF::EX qw/:try/; +use XML::LibXML; +use XML::LibXSLT; +use Time::HiRes qw(time); +use OpenILS::Utils::Fieldmapper; +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Logger qw/$logger/; +use Data::Dumper; + +my $parser = XML::LibXML->new(); +my $xslt = XML::LibXSLT->new(); +my $mods_sheet; + +# ---------------------------------------------------------------------------------------- +# XPATH for extracting info from a MODS doc +my $isbn_xpath = "//mods:mods/mods:identifier[\@type='isbn']"; +my $resource_xpath = "//mods:mods/mods:typeOfResource"; +my $pub_xpath = "//mods:mods/mods:originInfo//mods:dateIssued[1]|" . + "//mods:mods/mods:originInfo//mods:dateIssued[\@encoding='marc']"; +my $tcn_xpath = "//mods:mods/mods:recordInfo/mods:recordIdentifier"; +my $pub_place_xpath = "//mods:mods/mods:originInfo//mods:place//mods:placeTerm[\@type='text']"; +my $publisher_xpath = "//mods:mods/mods:originInfo//mods:publisher"; +my $edition_xpath = "//mods:mods/mods:originInfo//mods:edition[1]"; +my $abstract_xpath = "//mods:mods/mods:abstract"; +my $related_xpath = ""; +my $online_loc_xpath = "//mods:location/mods:url"; +my $physical_desc = "(//mods:physicalDescription/mods:form|//mods:physicalDescription/mods:extent|". + "//mods:physicalDescription/mods:reformattingQuality|//mods:physicalDescription/mods:internetMediaType|". + "//mods:physicalDescription/mods:digitalOrigin)"; +my $extent_xpath = "//mods:physicalDescription/mods:extent"; +my $toc_xpath = "//mods:tableOfContents"; +my $performers_xpath = "//mods:note[\@type='performers']"; + +my $xpathset = { + + title => { + abbreviated => + "//mods:mods/mods:titleInfo[mods:title and (\@type='abbreviated')]", + translated => + "//mods:mods/mods:titleInfo[mods:title and (\@type='translated')]", + uniform => + "//mods:mods/mods:titleInfo[mods:title and (\@type='uniform')]", + proper => + "//mods:mods/mods:titleInfo[mods:title and not (\@type)]", + any => + "//mods:mods/mods:titleInfo", + }, + + author => { + corporate => + "//mods:mods/mods:name[\@type='corporate']/*[local-name()='namePart']". + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", + personal => + "//mods:mods/mods:name[\@type='personal']/*[local-name()='namePart']". + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", + conference => + "//mods:mods/mods:name[\@type='conference']/*[local-name()='namePart']". + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", + other => + "//mods:mods/mods:name[\@type='personal']/*[local-name()='namePart']", + any => + "//mods:mods/mods:name/*[local-name()='namePart'][1]", + }, + + subject => { + + topic => + "//mods:mods/mods:subject/*[". + " local-name()='geographic'". + " or local-name()='name'". + " or local-name()='temporal'". + " or local-name()='topic'". + "]/parent::mods:subject", + +# geographic => +# "//mods:mods/*[local-name()='subject']/*[local-name()='geographic']", +# name => +# "//mods:mods/*[local-name()='subject']/*[local-name()='name']", +# temporal => +# "//mods:mods/*[local-name()='subject']/*[local-name()='temporal']", +# topic => +# "//mods:mods/*[local-name()='subject']/*[local-name()='topic']", + }, + #keyword => { keyword => "//mods:mods/*[not(local-name()='originInfo')]", }, + + series => { + series => "//mods:mods/mods:relatedItem[\@type='series']/mods:titleInfo" + } +}; +# ---------------------------------------------------------------------------------------- + + + +sub new { return bless( {}, shift() ); } + +sub get_field_value { + + my( $self, $mods, $xpath ) = @_; + + my @string; + + my $root = $mods->documentElement; + $root->setNamespace( "http://www.loc.gov/mods/v3", "mods", 1 ); + + try { + # grab the set of matching nodes + my @nodes = $root->findnodes( $xpath ); + for my $value (@nodes) { + + # grab all children of the node + my @children = $value->childNodes(); + my @child_text; + for my $child (@children) { + next unless( $child->nodeType != 3 ); + + if($child->childNodes) { + my @a; + for my $c (@{$child->childNodes}){ + push @a, $c->textContent; + } + push(@child_text, join(' ', @a)); + + } else { + push(@child_text, $child->textContent); + } + + } + if(@child_text) { + push(@string, \@child_text); + } + + if( !@child_text ) { + push(@string, $value->textContent ); + } + } + } otherwise { + $logger->info("MODS-izing failure: ".shift()); + $logger->info("Failed MODS xml: ".$root->toString); + $logger->info("Failed MODS xpath: $xpath"); + }; + return @string; +} + +=head +sub _modsdoc_to_values { + my( $self, $mods ) = @_; + my $data = {}; + for my $class (keys %$xpathset) { + $data->{$class} = {}; + for my $type(keys %{$xpathset->{$class}}) { + my @value = $self->get_field_value( $mods, $xpathset->{$class}->{$type} ); + if( $class eq "subject" ) { + push( @{$data->{$class}->{$type}}, @value ); + } else { + $data->{$class}->{$type} = $value[0]; + } + } + } + return $data; +} +=cut + +sub modsdoc_to_values { + my( $self, $mods ) = @_; + my $data = {}; + + { + my $class = "subject"; + $data->{$class} = {}; + for my $type(keys %{$xpathset->{$class}}) { + my @value = $self->get_field_value( $mods, $xpathset->{$class}->{$type} ); + for my $arr (@value) { + push( @{$data->{$class}->{$type}}, $arr); + } + } + } + + { + my $class = "title"; + $data->{$class} = {}; + for my $type(keys %{$xpathset->{$class}}) { + my @value = $self->get_field_value( $mods, $xpathset->{$class}->{$type} ); + for my $arr (@value) { + if( ref($arr) ) { + $data->{$class}->{$type} = shift @$arr; + + my $t = lc($data->{$class}->{$type}); + if($t and $t =~ /^l[eoa]s|l[ae]|el|the|un[ae]?|an?\s?$/o ) { + my $val = shift @$arr || ""; + $data->{$class}->{$type} .= " $val" if $data->{$class}->{$type}; + $data->{$class}->{$type} = " $val" unless $data->{$class}->{$type}; + } + + for my $t (@$arr) { + $data->{$class}->{$type} .= ' : ' if ($data->{$class}->{$type} =~ /\w\s*$/o); + $data->{$class}->{$type} .= " $t"; + } + } else { + $data->{$class}->{$type} = $arr; + } + } + $data->{$class}->{$type} =~ s/\s+/ /go if ($data->{$class}->{$type}); + } + } + + { + my $class = "author"; + $data->{$class} = {}; + for my $type(keys %{$xpathset->{$class}}) { + my @value = $self->get_field_value( $mods, $xpathset->{$class}->{$type} ); + $data->{$class}->{$type} = $value[0]; + } + } + + { + my $class = "series"; + $data->{$class} = {}; + for my $type(keys %{$xpathset->{$class}}) { + my @value = $self->get_field_value( $mods, $xpathset->{$class}->{$type} ); + for my $arr (@value) { + if( ref($arr) ) { + push(@{$data->{$class}->{$type}}, join(" ", @$arr)); + } else { + push( @{$data->{$class}->{$type}}, $arr ); + } + } + } + + } + + return $data; +} + + + + +# --------------------------------------------------------------------------- +# Grabs the data 'we want' from the MODS doc and returns it in hash form +# --------------------------------------------------------------------------- +sub mods_values_to_mods_slim { + my( $self, $modsperl ) = @_; + + my $title = ""; + my $author = ""; + my $subject = []; + my $series = []; + + my $tmp = $modsperl->{title}; + + + if(!$tmp) { $title = ""; } + else { + ($title = $tmp->{proper}) || + ($title = $tmp->{translated}) || + ($title = $tmp->{abbreviated}) || + ($title = $tmp->{uniform}) || + ($title = $tmp->{any}); + } + + $tmp = $modsperl->{author}; + if(!$tmp) { $author = ""; } + else { + ($author = $tmp->{personal}) || + ($author = $tmp->{corporate}) || + ($author = $tmp->{conference}) || + ($author = $tmp->{other}) || + ($author = $tmp->{any}); + } + + $tmp = $modsperl->{subject}; + if(!$tmp) { $subject = {}; } + else { + for my $key( keys %{$tmp}) { + push(@$subject, @{$tmp->{$key}}) if ($tmp->{$key}); + } + my $subh = {}; + for my $s (@$subject) { + if(defined($subh->{$s})) { $subh->{$s->[0]}++ } else { $subh->{$s->[0]} = 1;} + } + $subject = $subh + } + + $tmp = $modsperl->{'series'}; + if(!$tmp) { $series = []; } + else { $series = $tmp->{'series'}; } + + + return { series => $series, title => $title, + author => $author, subject => $subject }; +} + + + +# --------------------------------------------------------------------------- +# Initializes a MARC -> Unified MODS batch process +# --------------------------------------------------------------------------- + +sub start_mods_batch { + + my( $self, $master_doc ) = @_; + + if(!$master_doc) { + $self->{master_doc} = undef; + return; + } + + if(!$mods_sheet) { + my $xslt_doc = $parser->parse_file( + OpenSRF::Utils::SettingsClient->new->config_value(dirs => 'xsl') . "/MARC21slim2MODS32.xsl"); + $mods_sheet = $xslt->parse_stylesheet( $xslt_doc ); + } + + + my $xmldoc = $parser->parse_string($master_doc); + my $mods = $mods_sheet->transform($xmldoc); + + $self->{master_doc} = $self->modsdoc_to_values( $mods ); + $self->{master_doc} = $self->mods_values_to_mods_slim( $self->{master_doc} ); + + ($self->{master_doc}->{isbn}) = + $self->get_field_value( $mods, $isbn_xpath ); + + $self->{master_doc}->{type_of_resource} = + [ $self->get_field_value( $mods, $resource_xpath ) ]; + + ($self->{master_doc}->{tcn}) = + $self->get_field_value( $mods, $tcn_xpath ); + + ($self->{master_doc}->{pubdate}) = + $self->get_field_value( $mods, $pub_xpath ); + + my @pub_place = $self->get_field_value( $mods, $pub_place_xpath ); + my @publisher = $self->get_field_value( $mods, $publisher_xpath ); + + if (@pub_place && @publisher) { + ($self->{master_doc}->{publisher}) = $pub_place[0] . " : " . $publisher[0]; + } elsif (@pub_place) { + ($self->{master_doc}->{publisher}) = $pub_place[0]; + } elsif (@publisher) { + ($self->{master_doc}->{publisher}) = $publisher[0]; + } else { + ($self->{master_doc}->{publisher}) = undef; + } + + ($self->{master_doc}->{edition}) = + $self->get_field_value( $mods, $edition_xpath ); + + ($self->{master_doc}->{performer_notes}) = + $self->get_field_value( $mods, $performers_xpath ); + +# ------------------------------ + # holds an array of [ link, title, link, title, ... ] + $self->{master_doc}->{online_loc} = []; + for my $url ($mods->findnodes($online_loc_xpath)) { + push(@{$self->{master_doc}->{online_loc}}, $url->textContent); + push(@{$self->{master_doc}->{online_loc}}, $url->getAttribute('displayLabel') || ''); + push(@{$self->{master_doc}->{online_loc}}, $url->getAttribute('note') || ''); + } + + ($self->{master_doc}->{synopsis}) = + $self->get_field_value( $mods, $abstract_xpath ); + + $self->{master_doc}->{physical_description} = []; + push(@{$self->{master_doc}->{physical_description}}, + $self->get_field_value( $mods, $physical_desc ) ); + $self->{master_doc}->{physical_description} = + join( ' ', @{$self->{master_doc}->{physical_description}}); + + ($self->{master_doc}->{toc}) = $self->get_field_value($mods, $toc_xpath); + + ($self->{master_doc}->{extent}) = + $self->get_field_value($mods, $extent_xpath); + +} + + + +# --------------------------------------------------------------------------- +# Takes a MARCXML string and adds it to the growing MODS doc +# --------------------------------------------------------------------------- +sub push_mods_batch { + my( $self, $marcxml ) = @_; + + my $xmldoc = $parser->parse_string($marcxml); + my $mods = $mods_sheet->transform($xmldoc); + + my $xmlperl = $self->modsdoc_to_values( $mods ); + $xmlperl = $self->mods_values_to_mods_slim( $xmlperl ); + + # for backwards compatibility, remove the array part when all is decided + if(ref($xmlperl->{subject}) eq 'ARRAY' ) { + for my $subject( @{$xmlperl->{subject}} ) { + push @{$self->{master_doc}->{subject}}, $subject; + } + } else { + for my $subject ( keys %{$xmlperl->{subject}} ) { + my $s = $self->{master_doc}->{subject}; + if(defined($s->{$subject})) { $s->{$subject}++; } else { $s->{$subject} = 1; } + } + } + + push( @{$self->{master_doc}->{type_of_resource}}, + $self->get_field_value( $mods, $resource_xpath )); + + if(!($self->{master_doc}->{isbn}) ) { + ($self->{master_doc}->{isbn}) = + $self->get_field_value( $mods, $isbn_xpath ); + } +} + + +# --------------------------------------------------------------------------- +# Completes a MARC -> Unified MODS batch process and returns the perl hash +# --------------------------------------------------------------------------- +sub init_virtual_record { + my $record = Fieldmapper::metabib::virtual_record->new; + $record->subject([]); + $record->types_of_resource([]); + $record->call_numbers([]); + return $record; +} + +sub finish_mods_batch { + my $self = shift; + + return undef unless $self->{master_doc}; + + my $perl = $self->{master_doc}; + my $record = init_virtual_record(); + + # turn the hash into a fieldmapper object + #(my $title = $perl->{title}) =~ s/\[.*?\]//og; + #(my $author = $perl->{author}) =~ s/\(.*?\)//og; + my $title = $perl->{title}; + my $author = $perl->{author}; + + my @series; + for my $s (@{$perl->{series}}) { + push @series, (split( /\s*;/, $s ))[0]; + } + + # uniquify the types of resource + my $rtypes = $perl->{type_of_resource}; + my %hash = map { ($_ => 1) } @$rtypes; + $rtypes = [ keys %hash ]; + + $record->title($title); + $record->author($author); + + $record->doc_id($perl->{doc_id}); + $record->isbn($perl->{isbn}); + $record->pubdate($perl->{pubdate}); + $record->publisher($perl->{publisher}); + $record->tcn($perl->{tcn}); + + $record->edition($perl->{edition}); + + $record->subject($perl->{subject}); + $record->types_of_resource($rtypes); + $record->series(\@series); + + $record->online_loc($perl->{online_loc}); + $record->synopsis($perl->{synopsis}); + $record->physical_description($perl->{physical_description}); + $record->toc($perl->{toc}); + $record->performer_notes($perl->{performer_notes}); + $record->extent($perl->{extent}); + + $self->{master_doc} = undef; + return $record; +} + + + diff --git a/web/opac/skin/default/js/rdetail.js b/web/opac/skin/default/js/rdetail.js new file mode 100644 index 0000000000..c2bd09410e --- /dev/null +++ b/web/opac/skin/default/js/rdetail.js @@ -0,0 +1,916 @@ +/* */ + + +detachAllEvt('common', 'run'); +attachEvt("common", "run", rdetailDraw); +attachEvt("rdetail", "recordDrawn", rdetailBuildStatusColumns); +attachEvt("rdetail", "recordDrawn", rdetailBuildInfoRows); +attachEvt("rdetail", "recordDrawn", rdetailGetPageIds); + +var record = null; +var cp_statuses = null; +var recordsCache = []; + +var copyRowParent = null; +var copyRow = null; +var statusRow = null; +var numStatuses = null; +var defaultCN; +var callnumberCache = {}; +var rdetailLocalOnly = true; +var globalCNCache = {}; +var localTOC; +var cachedRecords; +var _statusPositions = {}; + +var rdetailShowLocal = true; +var rdetailShowCopyLocation = true; +var googleBookPreview = true; + + +var nextContainerIndex; + +function rdetailReload() { + var args = {}; + args[PARAM_LOCATION] = getNewSearchLocation(); + args[PARAM_DEPTH] = depthSelGetDepth(); + goTo(buildOPACLink(args)); +} + +var nextRecord; +var prevRecord; + +var rdetailPrev = null; +var rdetailNext = null; +var rdetailStart = null; +var rdetailEnd = null; + + + +/* looks to see if we have a next and/or previous record in the +record cache, if so, set up the nav links */ +function rdetailSetPaging(ids) { + + cachedRecords = {}; + cachedRecords.ids = ids; + + for( var i = 0; i < cachedRecords.ids.length; i++ ) { + var rec = cachedRecords.ids[i]; + if( rec == getRid() ) { + if( i > 0 ) prevRecord = cachedRecords.ids[i-1]; + if( i < cachedRecords.ids.length - 1 ) + nextRecord = cachedRecords.ids[i+1]; + break; + } + } + + $('np_offset').appendChild(text(i + 1)); + $('np_count').appendChild(text(getHitCount())); + + if(prevRecord) { + unHideMe($('np_table')); + unHideMe($('np_prev')); + unHideMe($('np_start')); + rdetailPrev = function() { _rdetailNav(prevRecord); }; + rdetailStart = function() { _rdetailNav(cachedRecords.ids[0]); }; + } + + if(nextRecord) { + unHideMe($('np_table')); + unHideMe($('np_next')); + unHideMe($('np_end')); + rdetailNext = function() { _rdetailNav(nextRecord); }; + rdetailEnd = function() { _rdetailNav(cachedRecords.ids[cachedRecords.ids.length-1]); }; + } + + runEvt('rdetail', 'nextPrevDrawn', i, cachedRecords.ids.length); +} + + +function _rdetailNav(id, offset) { + var args = {}; + args[PARAM_RID] = id; + goTo(buildOPACLink(args)); +} + +function rdetailDraw() { + + detachAllEvt('common','depthChanged'); + detachAllEvt('common','locationUpdated'); + attachEvt('common','depthChanged', rdetailReload); + attachEvt('common','locationUpdated', rdetailReload); + attachEvt('common','holdUpdated', rdetailReload); + attachEvt('common','holdUpdateCanceled', rdetailReload); + + copyRowParent = G.ui.rdetail.cp_info_row.parentNode; + copyRow = copyRowParent.removeChild(G.ui.rdetail.cp_info_row); + statusRow = G.ui.rdetail.cp_status.parentNode; + statusRow.id = '__rdsrow'; + + G.ui.rdetail.cp_info_local.onclick = rdetailShowLocalCopies; + G.ui.rdetail.cp_info_all.onclick = rdetailShowAllCopies; + + if(getLocation() == globalOrgTree.id()) + hideMe(G.ui.rdetail.cp_info_all); + + var req = new Request(FETCH_RMODS, getRid()); + req.callback(_rdetailDraw); + req.send(); + + detachAllEvt("result", "idsReceived"); + G.evt.result.hitCountReceived = []; + G.evt.result.recordReceived = []; + G.evt.result.copyCountsReceived = []; + G.evt.result.allRecordsReceived = []; +} + +function rdetailGetPageIds() { + attachEvt("result", "idsReceived", rdetailSetPaging ); + resultFetchAllRecords = true; + rresultCollectIds(true); +} + + +function buildunAPISpan (span, type, id) { + var cgi = new CGI(); + var d = new Date(); + + addCSSClass(span,'unapi-id'); + + span.setAttribute( + 'title', 'tag:' + cgi.server_name + ',' + + d.getFullYear() + ':' + type + '/' + id + ); +} + +function rdetailViewMarc(r,id) { + hideMe($('rdetail_extras_loading')); + $('rdetail_view_marc_box').innerHTML = r.getResultObject(); + + var div = elem('div', { "class" : 'hide_me' }); + var span = div.appendChild( elem('abbr') ); + + buildunAPISpan( span, 'biblio-record_entry', record.doc_id() ); + + $('rdetail_view_marc_box').insertBefore(span, $('rdetail_view_marc_box').firstChild); +} + + +function rdetailShowLocalCopies() { + rdetailShowLocal = true; + rdetailBuildInfoRows(); + hideMe(G.ui.rdetail.cp_info_local); + unHideMe(G.ui.rdetail.cp_info_all); + hideMe(G.ui.rdetail.cp_info_none); +} + +function rdetailShowAllCopies() { + + rdetailShowLocal = false; + rdetailBuildInfoRows(); + hideMe(G.ui.rdetail.cp_info_all); + unHideMe(G.ui.rdetail.cp_info_local); + hideMe(G.ui.rdetail.cp_info_none); +} + + +function _rdetailDraw(r) { + record = r.getResultObject(); + + runEvt('rdetail', 'recordRetrieved', record.doc_id()); + + G.ui.rdetail.title.appendChild(text(record.title())); + buildSearchLink(STYPE_AUTHOR, record.author(), G.ui.rdetail.author); + G.ui.rdetail.isbn.appendChild(text(cleanISBN(record.isbn()))); + G.ui.rdetail.edition.appendChild(text(record.edition())); + G.ui.rdetail.pubdate.appendChild(text(record.pubdate())); + G.ui.rdetail.publisher.appendChild(text(record.publisher())); + if (record.extent()) { + $('rdetail_physical_desc').appendChild(text(record.extent())); + } else { + $('rdetail_physical_desc').appendChild(text(record.physical_description())); + } + r = record.types_of_resource(); + if(r) { + G.ui.rdetail.tor.appendChild(text(r[0])); + setResourcePic( G.ui.rdetail.tor_pic, r[0]); + } + G.ui.rdetail.abstr.appendChild(text(record.synopsis())); + + try{ + if(record.isbn()) { + if(ENABLE_ADDED_CONTENT_ATTRIB_LINKS) { + unHideMe($('rdetail.jacket_attrib_div')); + var href = $('rdetail.jacket_attrib_link').getAttribute('href') +cleanISBN(record.isbn()); + $('rdetail.jacket_attrib_link').setAttribute('href', href); + } + rdetailCheckForGBPreview(); + + } else { + hideMe($("rdetail.jacket_attrib_div")); + hideMe($("rdetail_img_link")); + } + } catch(E) {} + + + // see if the record has any external links + var links = record.online_loc(); + for( var i = 0; links && links.length > 0 && i < links.length; i = i + 3 ) { + var href = links[i]; + // avoid matching "HTTP: The Complete Reference" + if( href.match(/https?:\/|ftps?:\/|mailto:/i) ) { + unHideMe($('rdetail_online_row')); + // MODS can contain a display label (used for the text of the link) + // as well as a note about the URL; many legacy systems conflate the + // two and generate MARC records that expect the note to be used as + // the text of the link, with no display label; here's the canonical + // format: + // + // 856 40 $uhttp://localhost$yDisplay label$zPublic note + // + // Note that the MARC21slim2MODS XSL concatenates $3 and $y together + // (as $y was defined later in MARC21's life as the display label) + var displayLabel = '' + links[i+1]; + var note = '' + links[i+2]; + if(!displayLabel || displayLabel.match(/https?:\/|ftps?:\/|mailto:/i)) { + if(!note || note.match(/https?:\/|ftps?:\/|mailto:/i)) { + displayLabel = href; + } else { + displayLabel = note; + } + } + $('rdetail_online').appendChild(elem('a', {href:href,'class':'classic_link'}, displayLabel)); + if (!note && note != displayLabel) { + $('rdetail_online').appendChild(elem('span', {'class':'url_note'}, ' - ' + note)); + } + $('rdetail_online').appendChild(elem('br')); + } + } + + // Fill in our unAPI ID, if anyone cares + var abbrs = document.getElementsByTagName('abbr'); + var span; + for (var i = 0; i < abbrs.length; i = i + 1) { + if (abbrs[i].getAttribute('name') == 'unapi') { + span = abbrs[i]; + break; + } + } + buildunAPISpan( span, 'biblio-record_entry', record.doc_id() ); + + $('rdetail_place_hold').setAttribute( + 'href','javascript:holdsDrawEditor({record:"'+record.doc_id()+'",type:"T"});'); + + $('rdetail_img_link').setAttribute('href', buildISBNSrc(cleanISBN(record.isbn()), 'large')); + G.ui.rdetail.image.setAttribute("src", buildISBNSrc(cleanISBN(record.isbn()))); + runEvt("rdetail", "recordDrawn"); + recordsCache.push(record); + + rdetailSetExtrasSelector(); + + var breq = new Request(FETCH_BRE, [getRid()]); + breq.callback( rdetailCheckDeleted ); + breq.send(); + + resultBuildCaches( [ record ] ); + resultDrawSubjects(); + resultDrawSeries(); + + // grab added content + acCollectData(cleanISBN(record.isbn()), rdetailhandleAC); +} + + + +function rdetailCheckDeleted(r) { + var br = r.getResultObject()[0]; + if( isTrue(br.deleted()) ) { + hideMe($('rdetail_place_hold')); + $('rdetail_more_actions_selector').disabled = true; + unHideMe($('rdetail_deleted_exp')); + } +} + +function rdetailSetExtrasSelector() { + if(!grabUser()) return; + unHideMe($('rdetail_more_actions')); + + var req = new Request( + FETCH_CONTAINERS, G.user.session, G.user.id(), 'biblio', 'bookbag' ); + req.callback(rdetailAddBookbags); + req.send(); +} + +function rdetailAddBookbags(r) { + + var containers = r.getResultObject(); + var selector = $('rdetail_more_actions_selector'); + var found = false; + var index = 3; + doSelectorActions(selector); + + for( var i = 0; i != containers.length; i++ ) { + found = true; + var container = containers[i]; + insertSelectorVal( selector, index++, container.name(), + "container_" + container.id(), rdetailAddToBookbag, 1 ); + } + + nextContainerIndex = index; +} + +var _actions = {}; +function rdetailNewBookbag() { + var name = prompt($('rdetail_bb_new').innerHTML,""); + if(!name) return; + + var id; + if( id = containerCreate( name ) ) { + alert($('rdetail_bb_success').innerHTML); + var selector = $('rdetail_more_actions_selector'); + insertSelectorVal( selector, nextContainerIndex++, name, + "container_" + id, rdetailAddToBookbag, 1 ); + setSelector( selector, 'start' ); + } +} + + +function rdetailAddToBookbag() { + var selector = $('rdetail_more_actions_selector'); + var id = selector.options[selector.selectedIndex].value; + setSelector( selector, 'start' ); + + if( containerCreateItem( id.substring(10), record.doc_id() )) { + alert($('rdetail_bb_item_success').innerHTML); + } +} + + +var rdetailMarcFetched = false; +function rdetailShowExtra(type, args) { + + hideMe($('rdetail_copy_info_div')); + hideMe($('rdetail_reviews_div')); + hideMe($('rdetail_toc_div')); + hideMe($('rdetail_anotes_div')); + hideMe($('rdetail_performer_notes_div')); + hideMe($('rdetail_excerpt_div')); + hideMe($('rdetail_preview_div')); + hideMe($('rdetail_marc_div')); + hideMe($('cn_browse')); + hideMe($('rdetail_cn_browse_div')); + hideMe($('rdetail_notes_div')); + + removeCSSClass($('rdetail_copy_info_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_viewcn_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_reviews_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_toc_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_excerpt_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_preview_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_anotes_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_performer_notes_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_annotation_link'), 'rdetail_extras_selected'); + removeCSSClass($('rdetail_viewmarc_link'), 'rdetail_extras_selected'); + + switch(type) { + + case "copyinfo": + unHideMe($('rdetail_copy_info_div')); + addCSSClass($('rdetail_copy_info_link'), 'rdetail_extras_selected'); + break; + + case "reviews": + addCSSClass($('rdetail_reviews_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_reviews_div')); + break; + + case "excerpt": + addCSSClass($('rdetail_excerpt_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_excerpt_div')); + break; + + case "preview": + addCSSClass($('rdetail_preview_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_preview_div')); + rdetailDisplayGBPreview(); + break; + + case "anotes": + addCSSClass($('rdetail_anotes_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_anotes_div')); + break; + + case "performer_notes": + addCSSClass($('rdetail_performer_notes_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_performer_notes_div')); + break; + + case "toc": + addCSSClass($('rdetail_toc_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_toc_div')); + break; + + case "marc": + addCSSClass($('rdetail_viewmarc_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_marc_div')); + if(rdetailMarcFetched) return; + unHideMe($('rdetail_extras_loading')); + rdetailMarcFetched = true; + var req = new Request( FETCH_MARC_HTML, record.doc_id() ); + req.callback(rdetailViewMarc); + req.send(); + break; + + case 'cn': + addCSSClass($('rdetail_viewcn_link'), 'rdetail_extras_selected'); + unHideMe($('rdetail_cn_browse_div')); + rdetailShowCNBrowse(defaultCN, getLocation(), null, true); + break; + + } +} + +function rdetailVolumeDetails(args) { + var row = $(args.rowid); + var tbody = row.parentNode; + cpdBuild( tbody, row, record, args.cn, args.org, args.depth ); + return; +} + + +function rdetailBuildCNList() { + + var select = $('cn_browse_selector'); + var index = 0; + var arr = []; + for( var cn in callnumberCache ) arr.push( cn ); + arr.sort(); + + if( arr.length == 0 ) { + hideMe($('rdetail_cn_browse_select_div')); + return; + } + + for( var i in arr ) { + var cn = arr[i]; + var opt = new Option(cn); + select.options[index++] = opt; + } + select.onchange = rdetailGatherCN; +} + +function rdetailGatherCN() { + var cn = getSelectorVal($('cn_browse_selector')); + rdetailShowCNBrowse( cn, getLocation(), getDepth(), true ); + setSelector( $('cn_browse_selector'), cn ); +} + + +function rdetailShowCNBrowse( cn, loc, depth, fromOnclick ) { + + if(!cn) { + unHideMe($('cn_browse_none')); + hideMe($('rdetail_cn_browse_select_div')); + return; + } + + unHideMe($('rdetail_cn_browse_select_div')); + rdetailBuildCNList(); + setSelector( $('cn_browse_selector'), cn ); + hideMe($('rdetail_copy_info_div')); + hideMe($('rdetail_reviews_div')); + hideMe($('rdetail_toc_div')); + hideMe($('rdetail_marc_div')); + unHideMe($('rdetail_cn_browse_div')); + unHideMe($('cn_browse')); + if( !rdetailLocalOnly && ! fromOnclick ) depth = findOrgDepth(globalOrgTree); + cnBrowseGo(cn, loc, depth); +} + +function rdetailhandleAC(data) { + + if( data.reviews.html ) { + $('rdetail_review_container').innerHTML = data.reviews.html; + unHideMe($('rdetail_reviews_link')); + } + + if( data.toc.html ) { + $('rdetail_toc_div').innerHTML = data.toc.html; + unHideMe($('rdetail_toc_link')); + } else if( record.toc() ) { + $('rdetail_toc_div').innerHTML = record.toc(); + unHideMe($('rdetail_toc_link')); + } + + if( data.excerpt.html ) { + $('rdetail_excerpt_div').innerHTML = data.excerpt.html; + unHideMe($('rdetail_excerpt_link')); + } + + if( data.anotes.html ) { + $('rdetail_anotes_div').innerHTML = data.anotes.html; + unHideMe($('rdetail_anotes_link')); + } + + if( record.performer_notes() ) { + $('rdetail_performer_notes_div').innerHTML = record.performer_notes(); + unHideMe($('rdetail_performer_notes_link')); + } +} + +function rdetailShowReviews(r) { + hideMe($('rdetail_extras_loading')); + var res = r.getResultObject(); + var par = $('rdetail_reviews_div'); + var template = par.removeChild($('rdetail_review_template')); + if( res && res.length > 0 ) { + unHideMe($('rdetail_reviews_link')); + for( var i = 0; i != res.length; i++ ) { + var rev = res[i]; + if( rev.text && rev.info ) { + var node = template.cloneNode(true); + $n(node, 'review_header').appendChild(text(rev.info)); + $n(node, 'review_text').appendChild(text(rev.text)); + par.appendChild(node); + } + } + } +} + + +function rdetailShowTOC(r) { + hideMe($('rdetail_extras_loading')); + var resp = r.getResultObject(); + if(resp) { + unHideMe($('rdetail_toc_link')); + $('rdetail_toc_div').innerHTML = resp; + } +} + + +function rdetailBuildInfoRows() { + var req; + if( rdetailShowLocal ) + req = new Request(FETCH_COPY_COUNTS_SUMMARY, record.doc_id(), getLocation(), getDepth()) + else + req = new Request(FETCH_COPY_COUNTS_SUMMARY, record.doc_id()); + req.callback(_rdetailBuildInfoRows); + req.send(); +} + +function _rdetailRows(node) { + + if( rdetailShowLocal && getLocation() != globalOrgTree.id() ) { + + var loc = findOrgUnit(getLocation()); + + if( node ) { + if( !orgIsMine(node, loc) ) return; + + } else { + + for( var i = 0; i < globalOrgTree.children().length; i++ ) { + var org = findOrgUnit(globalOrgTree.children()[i]); + if( orgIsMine(org, loc) ) { + node = org; + break; + } + } + } + } + + if(!node && findOrgType(globalOrgTree.ou_type()).can_have_vols()) + node = globalOrgTree; + + + /* don't show hidden orgs */ + + if(node) { + + if(!isXUL() && !isTrue(node.opac_visible())) return; + + var row = copyRow.cloneNode(true); + row.id = "cp_info_" + node.id(); + + var libtd = findNodeByName( row, config.names.rdetail.lib_cell ); + var cntd = findNodeByName( row, config.names.rdetail.cn_cell ); + var cpctd = findNodeByName( row, config.names.rdetail.cp_count_cell ); + var actions = $n(row, 'rdetail_actions_cell'); + + var p = libtd.getElementsByTagName('a')[0]; + libtd.insertBefore(text(node.name()), p); + libtd.setAttribute("style", "padding-left: " + ((findOrgDepth(node) - 1) * 9) + "px;"); + + if(!findOrgType(node.ou_type()).can_have_vols()) { + + row.removeChild(cntd); + row.removeChild(cpctd); + row.removeChild(actions); + row.setAttribute('novols', '1'); + + libtd.setAttribute("colspan", numStatuses + 3 ); + libtd.colSpan = numStatuses + 3; + addCSSClass(row, 'copy_info_region_row'); + } + + copyRowParent.appendChild(row); + + } else { node = globalOrgTree; } + + for( var c in node.children() ) + _rdetailRows(node.children()[c]); +} + +function rdetailCNPrint(orgid, cn) { + var div = cpdBuildPrintWindow( record, orgid); + var template = div.removeChild($n(div, 'cnrow')); + var rowNode = $("cp_info_" + orgid); + cpdStylePopupWindow(div); + openWindow(div.innerHTML); +} + +var localCNFound = false; +var ctr = 0; +function _rdetailBuildInfoRows(r) { + + removeChildren(copyRowParent); + + _rdetailRows(); + + var summary = r.getResultObject(); + if(!summary) return; + + var found = false; + for( var i = 0; i < summary.length; i++ ) { + + var arr = summary[i]; + globalCNCache[arr[1]] = 1; + var thisOrg = findOrgUnit(arr[0]); + var rowNode = $("cp_info_" + thisOrg.id()); + if(!rowNode) continue; + + if(rowNode.getAttribute("used")) { + + if( rowNode.nextSibling ) { + sib = rowNode.nextSibling; + o ='cp_info_'+thisOrg.id()+'_'; + /* push the new row on as the last row for this org unit */ + while( sib && sib.id.match(o) ) { + sib = sib.nextSibling; + } + if(sib) + rowNode = copyRowParent.insertBefore(copyRow.cloneNode(true), sib); + else + rowNode = copyRowParent.appendChild(copyRow.cloneNode(true)); + } else { + rowNode = copyRowParent.appendChild(copyRow.cloneNode(true)); + } + + var n = findNodeByName( rowNode, config.names.rdetail.lib_cell ); + n.appendChild(text(thisOrg.name())); + n.setAttribute("style", "padding-left: " + ((findOrgDepth(thisOrg) - 1) * 9) + "px;"); + rowNode.id = "cp_info_" + thisOrg.id() + '_' + (++ctr); + + } else { + rowNode.setAttribute("used", "1"); + } + + var cpc_temp = rowNode.removeChild( + findNodeByName(rowNode, config.names.rdetail.cp_count_cell)); + + rdetailApplyStatuses(rowNode, cpc_temp, arr[2]); + + var isLocal = false; + if( orgIsMine( findOrgUnit(getLocation()), thisOrg ) ) { + found = true; + isLocal = true; + if(!localCNFound) { + localCNFound = true; + defaultCN = arr[1]; + } + } + + //if(isLocal) unHideMe(rowNode); + unHideMe(rowNode); + + rdetailSetPath( thisOrg, isLocal ); + rdetailBuildBrowseInfo( rowNode, arr[1], isLocal, thisOrg ); + + if( i == summary.length - 1 && !defaultCN) defaultCN = arr[1]; + } + + if(!found) unHideMe(G.ui.rdetail.cp_info_none); +} + + +function rdetailBuildBrowseInfo(row, cn, local, orgNode) { + + if(local) { + var cache = callnumberCache[cn]; + if( cache ) cache.count++; + else callnumberCache[cn] = { count : 1 }; + } + + var depth = getDepth(); + if( !local ) depth = findOrgDepth(globalOrgTree); + + $n(row, 'rdetail_callnumber_cell').appendChild(text(cn)); + + _debug('setting action clicks for cn ' + cn); + + var dHref = 'javascript:rdetailVolumeDetails('+ + '{rowid : "'+row.id+'", cn :"'+cn+'", depth:"'+depth+'", org:"'+orgNode.id()+'", local: '+local+'});'; + + var bHref = 'javascript:rdetailShowCNBrowse("' + cn + '", '+orgNode.id()+', "'+depth+'");'; + + $n(row, 'details').setAttribute('href', dHref); + $n(row, 'browse').setAttribute('href', bHref); + + if(isXUL()) { + unHideMe($n(row, 'hold_div')); + $n(row, 'hold').onclick = function() { + var req = new Request(FETCH_VOLUME_BY_INFO, cn, record.doc_id(), orgNode.id()); + req.callback( + function(r) { + var vol = r.getResultObject(); + holdsDrawEditor({type: 'V', volumeObject : vol}); + } + ); + req.send(); + }; + } +} + + +// sets the path to org as 'active' and displays the path if it's local +function rdetailSetPath(org, local) { + if( findOrgDepth(org) == 0 ) return; + var row = $("cp_info_" + org.id()); + row.setAttribute("hasinfo", "1"); + unHideMe(row); + rdetailSetPath(findOrgUnit(org.parent_ou()), local); +} + + + + +//Append all the statuses for a give summary to the +//copy summary table +function rdetailApplyStatuses( row, template, statuses ) { + for( var j in _statusPositions ) { + var stat = _statusPositions[j]; + var val = statuses[stat.id()]; + var nn = template.cloneNode(true); + if(val) nn.appendChild(text(val)); + else nn.appendChild(text(0)); + row.appendChild(nn); + } +} + + +var _statusPositions = {}; + +//Add one td (creating a new column) to the copy summary +//table for each opac_visible copy status + +function rdetailBuildStatusColumns() { + + rdetailGrabCopyStatuses(); + var parent = statusRow; + var template = parent.removeChild(G.ui.rdetail.cp_status); + + var i = 0; + for( i = 0; i < cp_statuses.length; i++ ) { + + var c = cp_statuses[i]; + if( c && isTrue(c.opac_visible()) ) { + var name = c.name(); + _statusPositions[i] = c; + var node = template.cloneNode(true); + var data = findNodeByName( node, config.names.rdetail.cp_status); + + data.appendChild(text(name)); + parent.appendChild(node); + } + } + + numStatuses = 0; + for(x in _statusPositions) numStatuses++; +} + +function rdetailGrabCopyStatuses() { + if(cp_statuses) return cp_statuses; + var req = new Request(FETCH_COPY_STATUSES); + req.send(true); + cp_statuses = req.result(); + cp_statuses = cp_statuses.sort(_rdetailSortStatuses); +} + +function _rdetailSortStatuses(a, b) { + return parseInt(a.id()) - parseInt(b.id()); +} + +/** + * XXX Need to adopt a more typical approach to showing loading status + */ +function rdetailCheckForGBPreview() { + + if (!googleBookPreview) return; + var GBPp = document.createElement('p'); + GBPp.appendChild( document.createTextNode('Loading... ' ) ); + GBPp.id = 'loading'; + $('rdetail_preview_div').appendChild(GBPp); + searchForGBPreview( cleanISBN(record.isbn()) ); + +} + +/** + * + * @param {DOM object} query The form element containing the + * input parameters "isbns" + */ +function searchForGBPreview( isbn ) { + + // Delete any previous Google Booksearch JSON queries. + var GBPJsonScript = document.getElementById("GBPJsonScript"); + if (GBPJsonScript) { + GBPJsonScript.parentNode.removeChild(GBPJsonScript); + } + + // Add a script element with the src as the user's Google Booksearch query. + // JSON output is specified by including the alt=json-in-script argument + // and the callback function is also specified as a URI argument. + var GBPScriptElement = document.createElement("script"); + + GBPScriptElement.setAttribute("id", "GBPJsonScript"); + GBPScriptElement.setAttribute("src", + "http://books.google.com/books?bibkeys=" + + isbn + "&jscmd=viewapi&callback=GBPreviewCallback"); + GBPScriptElement.setAttribute("type", "text/javascript"); + + // make the request to Google booksearch + document.documentElement.firstChild.appendChild(GBPScriptElement); +} + +/** + * This function is the call-back function for the JSON scripts which + * executes a Google book search response. + * + * XXX I18N of text needed + * + * @param {JSON} booksInfo is the JSON object pulled from the Google books service. + */ +function GBPreviewCallback(GBPBookInfo) { + // Clear any old data to prepare to display the Loading... message. + var GBPreviewDiv = document.getElementById("rdetail_preview_div"); + var GBPBook; + + for ( i in GBPBookInfo ) { + GBPBook = GBPBookInfo[i]; + } + + if ( !GBPBook ) { + return; + } + + if ( GBPBook.preview != "noview" ) { + if ( GBPBook.preview == 'full' ) { + setText( $('rdetail_preview_link'), 'Full Text' ); + $('rdetail_preview_link_a').title = 'See the full text of this book.'; + } + + // Add a button below the book cover image to load the preview. + GBPBadge = document.createElement( 'img' ); + GBPBadge.src = 'http://books.google.com/intl/en/googlebooks/images/gbs_preview_button1.gif'; + GBPBadge.title = 'Show a preview of this book from Google Book Search'; + GBPBadge.style.border = 0; + GBPBadgelink = document.createElement( 'a' ); + GBPBadgelink.href = 'javascript:rdetailShowExtra("preview");'; + GBPBadgelink.appendChild( GBPBadge ); + $('rdetail_image_cell').appendChild( GBPBadgelink ); + + unHideMe( $('rdetail_preview_link' ) ); + $('rdetail_preview_div').style.height = 600; + } +} + +/** + * This is called when the user clicks on the 'Preview' link. We assume + * a preview is available from Google if this link was made visible. + * + * XXX I18N of Google Book Preview language attribute needed + */ +function rdetailDisplayGBPreview() { + GBPreviewPane = $('rdetail_preview_div'); + if ( GBPreviewPane.getAttribute('loaded') == null || + GBPreviewPane.getAttribute('loaded') == "false" ) { + google.load("books", "0", {"callback" : rdetailGBPViewerLoadCallback, "language": "en"} ); + GBPreviewPane.setAttribute('loaded', 'true'); + } +} + +function rdetailGBPViewerLoadCallback() { + var GBPViewer = new google.books.DefaultViewer(document.getElementById('rdetail_preview_div')); + GBPViewer.load('ISBN:' + cleanISBN(record.isbn()) ); + +} diff --git a/web/opac/skin/default/xml/index.xml b/web/opac/skin/default/xml/index.xml new file mode 100644 index 0000000000..81db882cab --- /dev/null +++ b/web/opac/skin/default/xml/index.xml @@ -0,0 +1,22 @@ + + + + + + + +]> + + + + + + + + + + + diff --git a/web/opac/skin/default/xml/rdetail/rdetail_extras.xml b/web/opac/skin/default/xml/rdetail/rdetail_extras.xml new file mode 100644 index 0000000000..5fb679bc91 --- /dev/null +++ b/web/opac/skin/default/xml/rdetail/rdetail_extras.xml @@ -0,0 +1,121 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
&common.loading;
+ + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ &rdetail.extras.call.null; +
+ +
+ &rdetail.extras.call.local; + +
+ + +
+ + + +
+
-- 2.11.0