display:inline-block;
}
-#dashboard {
+#dashboard, #dashboard_e {
margin-top: 1em;
height: 3em;
}
-#dashboard span.dash-align a {
+#dashboard span.dash-align a, #dashboard_e span.dash-align a {
font-weight: bold;
text-decoration: none;
}
#logout_link { left: 1px; }
-#dash_checked { color: [% css_colors.text_attention %]; }
-#dash_holds { color: [% css_colors.text_attention %]; }
-#dash_pickup { color: [% css_colors.text_goodnews %]; }
+#dash_checked, #dash_e_checked { color: [% css_colors.text_attention %]; }
+#dash_holds, #dash_e_holds { color: [% css_colors.text_attention %]; }
+#dash_pickup, #dash_e_pickup { color: [% css_colors.text_goodnews %]; }
/*
#dash_fines { color: [% css_colors.text_badnews %]; }
<div class="align">
<a href='[% mkurl('circs',{},1) %]'>[% l("Current Items Checked Out") %]</a>
</div>
+ [%- IF ebook_api.enabled %]
+ <div class="align">
+ <a href="[% mkurl('ebook_circs',{},1) %]">[% l("E-Items Currently Checked Out") %]</a>
+ </div>
+ [%- END %]
<div class="align selected">
<a href="#">[% l("Check Out History") %]</a>
</div>
<div class="align selected">
<a href="#">[% l("Current Items Checked Out") %]</a>
</div>
+ [%- IF ebook_api.enabled %]
+ <div class="align">
+ <a href="[% mkurl('ebook_circs',{},1) %]">[% l("E-Items Currently Checked Out") %]</a>
+ </div>
+ [%- END %]
<div class="align">
<a href="[% mkurl('circ_history',{},1) %]">[% l("Check Out History") %]</a>
</div>
--- /dev/null
+[% PROCESS "opac/parts/header.tt2";
+ PROCESS "opac/parts/misc_util.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
+ WRAPPER "opac/parts/myopac/base.tt2";
+ myopac_page = "ebook_circs" %]
+<h3 class="sr-only">[% l('E-Items Currently Checked Out') %]</h3>
+<div id='myopac_checked_div'>
+
+ <div id="acct_checked_tabs">
+ <div class="align">
+ <a href="[% mkurl('circ_history',{},1) %]">[% l("Current Items Checked Out") %]</a>
+ </div>
+ <div class="align selected">
+ <a href="#">[% l("E-Items Currently Checked Out") %]</a>
+ </div>
+ <div class="align">
+ <a href="[% mkurl('circ_history',{},1) %]">[% l("Check Out History") %]</a>
+ </div>
+ </div>
+
+ <div class="header_middle">
+ <span class="float-left">[% l('E-Items Currently Checked Out') %]</span>
+ </div>
+ <div class="clear-both"></div>
+ <div id="no_ebook_circs" class="warning_box hidden">[% l('You have no e-items checked out.') %]</div>
+ <div id='ebook_circs_main' class="hidden">
+ <table id="ebook_circs_main_table"
+ title="[% l('E-Items Currently Checked Out') %]"
+ class="table_no_border_space table_no_cell_pad item_list_padding">
+ <thead>
+ <tr>
+ <th>[% sort_head("sort_title", l("Title")) %]</th>
+ <th>[% sort_head("author", l("Author")) %]</th>
+ <th>[% l("Due Date") %]</th>
+ <th>[% l("Actions") %]</th>
+ </tr>
+ </thead>
+ <tbody id="ebook_circs_main_table_body"></tbody>
+ </table>
+ </div>
+</div>
+[% END %]
--- /dev/null
+[% PROCESS "opac/parts/header.tt2";
+ PROCESS "opac/parts/misc_util.tt2";
+ PROCESS "opac/parts/hold_status.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
+ WRAPPER "opac/parts/myopac/base.tt2";
+ myopac_page = "ebook_holds";
+ limit = (ctx.holds_limit.defined) ? ctx.holds_limit : 0;
+ offset = (ctx.holds_offset.defined) ? ctx.holds_offset : 0;
+ count = (ctx.holds_ids.size.defined) ? ctx.holds_ids.size : 0;
+%]
+<h3 class="sr-only">[% l('My E-Item Holds') %]</h3>
+<div id='myopac_holds_div'>
+
+ <div id="acct_holds_tabs">
+ <div class="align">
+ <a href='[% mkurl('holds', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Items on Hold") %]</a>
+ </div>
+ <div class="align selected">
+ <a href='#'>[% l("E-Items on Hold") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('ebook_holds_ready', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items Ready for Checkout") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('hold_history', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Holds History") %]</a>
+ </div>
+ </div>
+
+ <div class="header_middle">
+ <span class="float-left">[% l('E-Items on Hold') %]</span>
+ </div>
+ <div class="clear-both"></div>
+ <div id="no_ebook_holds" class="warning_box hidden">[% l('You have no e-item holds.') %]</div>
+ <div id='ebook_holds_main' class="hidden">
+ <table id="ebook_holds_main_table"
+ title="[% l('E-Items on Hold') %]"
+ class="table_no_border_space table_no_cell_pad item_list_padding">
+ <thead>
+ <tr>
+ <th>[% sort_head("sort_title", l("Title")) %]</th>
+ <th>[% sort_head("author", l("Author")) %]</th>
+ <th>[% l("Expire Date") %]</th>
+ <th>[% l("Status") %]</th>
+ <th>[% l("Actions") %]</th>
+ </tr>
+ </thead>
+ <tbody id="ebook_holds_main_table_body"></tbody>
+ </table>
+ </div>
+</div>
+[% END %]
--- /dev/null
+[% PROCESS "opac/parts/header.tt2";
+ PROCESS "opac/parts/misc_util.tt2";
+ PROCESS "opac/parts/hold_status.tt2";
+ PROCESS "opac/parts/myopac/column_sort_support.tt2";
+ WRAPPER "opac/parts/myopac/base.tt2";
+ myopac_page = "ebook_holds_ready";
+ limit = (ctx.holds_limit.defined) ? ctx.holds_limit : 0;
+ offset = (ctx.holds_offset.defined) ? ctx.holds_offset : 0;
+ count = (ctx.holds_ids.size.defined) ? ctx.holds_ids.size : 0;
+%]
+<h3 class="sr-only">[% l('E-Items Ready for Checkout') %]</h3>
+<div id='myopac_holds_div'>
+
+ <div id="acct_holds_tabs">
+ <div class="align">
+ <a href='[% mkurl('holds', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Items on Hold") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('ebook_holds', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items on Hold") %]</a>
+ </div>
+ <div class="align selected">
+ <a href='#'>[% l("E-Items Ready for Checkout") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('hold_history', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Holds History") %]</a>
+ </div>
+ </div>
+
+ <div class="header_middle">
+ <span class="float-left">[% l('E-Items Ready for Checkout') %]</span>
+ </div>
+ <div class="clear-both"></div>
+ <div id="no_ebook_holds" class="warning_box hidden">[% l('You have no e-item holds ready to be checked out.') %]</div>
+ <div id='ebook_holds_main' class="hidden">
+ <table id="ebook_holds_main_table"
+ title="[% l('E-Items Ready for Checkout') %]"
+ class="table_no_border_space table_no_cell_pad item_list_padding">
+ <thead>
+ <tr>
+ <th>[% sort_head("sort_title", l("Title")) %]</th>
+ <th>[% sort_head("author", l("Author")) %]</th>
+ <th>[% l("Expire Date") %]</th>
+ <th>[% l("Actions") %]</th>
+ </tr>
+ </thead>
+ <tbody id="ebook_holds_main_table_body"></tbody>
+ </table>
+ </div>
+</div>
+[% END %]
<div class="align">
<a href='[% mkurl('holds',{},['limit','offset']) %]'>[% l("Items on Hold") %]</a>
</div>
+ [% IF ebook_api.enabled %]
+ <div class="align">
+ <a href='[% mkurl('ebook_holds', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items on Hold") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('ebook_holds_ready', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items Ready for Checkout") %]</a>
+ </div>
+ [% END %]
<div class="align selected">
<a href="#">[% l("Holds History") %]</a>
</div>
<div class="align selected">
<a href='#'>[% l("Items on Hold") %]</a>
</div>
+ [% IF ebook_api.enabled %]
+ <div class="align">
+ <a href='[% mkurl('ebook_holds', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items on Hold") %]</a>
+ </div>
+ <div class="align">
+ <a href='[% mkurl('ebook_holds_ready', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("E-Items Ready for Checkout") %]</a>
+ </div>
+ [% END %]
<div class="align">
<a href='[% mkurl('hold_history', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Holds History") %]</a>
</div>
google_analytics.code = 'UA-9999999-99';
##############################################################################
+# Ebook API integration
+##############################################################################
+ebook_api.enabled = 'false';
+ebook_api.ebook_test.enabled = 'false';
+ebook_api.ebook_test.base_uris = [ 'http://example.com/ebookapi/t/' ];
+ebook_api.oneclickdigital.enabled = 'false';
+ebook_api.oneclickdigital.base_uris = [ 'http://example.oneclickdigital.com/Products/ProductDetail.aspx' ];
+ebook_api.overdrive.enabled = 'false';
+ebook_api.overdrive.base_uris = [ 'http://elm.lib.overdrive.com/' ];
+
+##############################################################################
# Enable "Forgot your password?" prompt at login
##############################################################################
reset_password = 'true';
--- /dev/null
+[%-
+# Display holdings/availability info from ebook API
+#
+# We require the following info:
+# - rec_id: internal ID for this record (rec.id in search results, ctx.bre_id in record summary)
+# - ebook_id: external ID for title (ISBN for OneClickdigital, unique identifier for OverDrive)
+# - vendor (oneclickdigital, overdrive)
+
+IF args.ebook_test_id;
+ ebook.ebook_id = args.ebook_test_id;
+ ebook.vendor = 'ebook_test';
+ELSIF args.oneclickdigital_id;
+ ebook.ebook_id = args.oneclickdigital_id;
+ ebook.vendor = 'oneclickdigital';
+ELSIF args.overdrive_id;
+ ebook.ebook_id = args.overdrive_id;
+ ebook.vendor = 'overdrive';
+END;
+
+IF ebook.ebook_id;
+
+ IF ctx.page == 'rresult';
+ ebook.rec_id = rec.id;
+ ELSE;
+ ebook.rec_id = ctx.bre_id;
+ END;
+
+# This div is hidden by default. The JS layer will unhide it, use the ebook_id
+# to retrieve holdings/availability info via the appropriate vendor API, and
+# overwrite the div's contents with that information.
+-%]
+<div id="[% ebook.rec_id %]" class="ebook_avail hidden">
+ <div id="[% ebook.ebook_id %]" class="[% ebook.vendor %]_avail">
+ <table id="[% ebook.rec_id %]_ebook_holdings" class="result_holdings_table hidden">
+ <thead>
+ <tr>
+ <th>[% l('Available Formats') %]</th>
+ <th>[% l('Status') %]</th>
+ </tr>
+ <tbody>
+ <tr>
+ <td id="[% ebook.rec_id %]_formats"></td>
+ <td id="[% ebook.rec_id %]_status"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+[% END %]
--- /dev/null
+<script type="text/javascript">
+dojo.addOnLoad(function() {
+
+ // detect ebooks on current page for each vendor
+ dojo.forEach(vendor_list, function(v) {
+ var vendor = new Vendor(v);
+ var ebook_nodes = dojo.query("." + v + "_avail");
+ console.log('found ' + ebook_nodes.length + ' ebooks on this page');
+
+ // we have ebooks for this vendor, so let's get availability info etc.
+ if (ebook_nodes.length > 0) {
+ checkSession(v, function(v,ses) {
+ ebook_nodes.forEach(function(node) {
+ var ebook = new Ebook(v, node.getAttribute("id"));
+ ebook.rec_id = node.parentNode.getAttribute("id");
+ vendor.ebooks.push(ebook);
+
+ ebook.getHoldings( function(holdings) {
+ if (typeof holdings.available !== 'undefined') {
+ var avail = holdings.available;
+ if (avail == 1) {
+ node.innerHTML = 'This title is available online.';
+ } else if (avail == 0) {
+ node.innerHTML = 'This title is not currently available.';
+ } else {
+ console.log(ebook.id + ' has bad availability: ' + avail);
+ }
+ } else {
+ if (holdings.formats.length > 0) {
+ var formats_ul = dojo.create("ul", null, ebook.rec_id + '_formats');
+ dojo.forEach(holdings.formats, function(f) {
+ dojo.create("li", { innerHTML: f }, formats_ul);
+ });
+ var status_node = dojo.byId(ebook.rec_id + '_status');
+ var status_str = holdings.copies_available + ' of ' + holdings.copies_owned + ' available';
+ status_node.innerHTML = status_str;
+ dojo.removeClass(ebook.rec_id + '_ebook_holdings', "hidden");
+ }
+ }
+ // unhide holdings/availability info now that it's populated
+ removeClass(node.parentNode, "hidden");
+ });
+ });
+ });
+ }
+ });
+
+});
+</script>
--- /dev/null
+[%
+# HTML display chunks
+progress_icon = '<img id="ebook_avail_spinner" src="/opac/images/progressbar_green.gif" alt="' _ l("Checking availability for this item...") _ '"/>'
+%]
+
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/opac/ebook_api/session.js"></script>
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/opac/ebook_api/ebook.js"></script>
+<script type="text/javascript">
+
+// translatable strings as JS variables
+var l_strings = {};
+l_strings.download = '[% l('Download') %]';
+l_strings.ready_for_checkout = '[% l('Ready for Checkout') %]';
+l_strings.suspended = '[% l('Suspended') %]';
+
+// give us cookies!
+dojo.require("dojo.cookie");
+
+// context org unit
+[% IF !ctx.page OR ctx.page != 'rresult';
+ PROCESS get_library;
+END %]
+var ou = [% loc_value %];
+
+// list of enabled vendors
+var vendor_list = [];
+[% IF ebook_api.ebook_test.enabled == 'true' %]
+vendor_list.push('ebook_test');
+[% END %]
+[% IF ebook_api.oneclickdigital.enabled == 'true' %]
+vendor_list.push('oneclickdigital');
+[% END %]
+[% IF ebook_api.overdrive.enabled == 'true' %]
+vendor_list.push('overdrive');
+[% END %]
+
+var cookie_registry = [ 'ebook_xact_cache' ];
+dojo.forEach(vendor_list, function(v) {
+ cookie_registry.push(v);
+});
+
+[% IF ctx.user %]
+// user- or login-specific vars
+var authtoken = '[% ctx.authtoken %]';
+var patron_id = '[% ctx.active_card %]'; // using barcode of active card as patron ID
+
+var myopac_page;
+[% IF myopac_page %]
+myopac_page = "[% myopac_page %]";
+[% END %]
+
+[% END %]
+
+// enforce removal of ebook API cookies on logout
+dojo.addOnLoad(function() {
+ var logout_handle = dojo.connect(dojo.byId('#logout_link'), 'onclick', function() {
+ dojo.forEach(cookie_registry, function(cookie) {
+ dojo.cookie(cookie, '', {path: '/', expires: '-1h'});
+ });
+ // When we switch to jQuery, use .one()
+ // instead of dojo's .connect() and .disconnect()
+ dojo.disconnect(logout_handle);
+ });
+});
+</script>
+
+[%- IF ctx.user %]
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/opac/ebook_api/relation.js"></script>
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/opac/ebook_api/loggedin.js"></script>
+[%- END %]
+
--- /dev/null
+<script type="text/javascript">
+var vendors_requiring_password = [];
+
+[% IF !loc_value; PROCESS get_library; END; %]
+[% IF ebook_api.overdrive.enabled == 'true'
+ AND loc_value
+ AND ctx.get_org_setting(loc_value, 'ebook_api.overdrive.password_required') %]
+vendors_requiring_password.push('overdrive');
+[% END %]
+
+dojo.addOnLoad(function() {
+ var handle = dojo.connect(dojo.byId('#login-form-box'), 'onclick', function(evt) {
+ // disconnect this event since it's one-time-only
+ // (when we switch to jQuery, we can use .one() here)
+ dojo.disconnect(handle);
+
+ // we cache the username (and password) for now, but will
+ // replace that with the patron's active barcode later
+ vendors_requiring_password.forEach(function(v) {
+ if (vendor_list.includes(v)) {
+ checkSession(v, function(v,ses) {
+ var username = dojo.byId('#username_field').value;
+ var password = dojo.byId('#password_field').value;
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.patron.cache_password',
+ params: [ ses, password ],
+ async: true,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('patron password has been cached');
+ return;
+ }
+ }
+ }).send();
+ });
+ }
+ });
+ });
+});
+</script>
want_dojo = 1;
END;
+ IF ebook_api.enabled == 'true';
+ want_dojo = 1;
+ END;
+
# Especially useful for image 'alt' tags and link title tags,
# where the content may need to be unique (making it longer)
# but should not exceed 75 chars for ideal screen reader support.
src="[% ctx.media_prefix %]/js/ui/default/opac/copyloc.js"></script>
[% END %]
+[% IF ebook_api.enabled == 'true' %]
+ [% INCLUDE "opac/parts/ebook_api/base_js.tt2" %]
+ [% INCLUDE "opac/parts/ebook_api/avail_js.tt2" IF (ctx.page == 'rresult' OR ctx.page == 'record') %]
+ [% INCLUDE "opac/parts/ebook_api/login_js.tt2" IF (ctx.page == 'login') %]
+[% END %]
+
<!-- provide a JS friendly org unit hash -->
<script type="text/javascript">
var aou_hash = {
END;
END;
+ IF ebook_api.overdrive.enabled == 'true';
+ FOR marc037 IN xml.findnodes('//*[@tag="037"]');
+ marc037_id = marc037.findnodes('./*[@code="a"]').textContent;
+ marc037_source = marc037.findnodes('./*[@code="b"]').textContent;
+ IF marc037_source.match('OverDrive') AND marc037_id;
+ args.overdrive_id = marc037_id;
+ LAST;
+ END;
+ END;
+ END;
+
# Extract the 856 URLs that are not otherwise represented by asset.uri's
args.online_res = [];
FOR node IN xml.findnodes('//*[@tag="856" and @ind1="4" and (@ind2="0" or @ind2="1")]');
res.note = '';
END;
args.uris.push(res);
+
+ IF ebook_api.ebook_test.enabled == 'true';
+ IF !args.ebook_test_id;
+ FOR base_uri IN ebook_api.ebook_test.base_uris;
+ IF res.href.match(base_uri);
+ args.ebook_test_id = res.href.remove(base_uri);
+ LAST;
+ END;
+ END;
+ END;
+ END;
+
+ IF ebook_api.oneclickdigital.enabled == 'true';
+ # A record might conceivably have multiple OneClickdigital URIs,
+ # but we use (the same) ISBN as the ebook ID in each case.
+ IF !args.oneclickdigital_id;
+ FOR base_uri IN ebook_api.oneclickdigital.base_uris;
+ IF res.href.match(base_uri);
+ # found a OneClickdigital URI, let's grab our ID and move on
+ args.oneclickdigital_id = clean_isbn;
+ LAST;
+ END;
+ END;
+ END;
+ END;
+
+ IF ebook_api.overdrive.enabled == 'true';
+ # Ideally we already have an OverDrive record ID from MARC 037 (see above).
+ # But for older records, it will be embedded in the URI in MARC 856.
+ IF !args.overdrive_id;
+ FOR base_uri IN ebook_api.overdrive.base_uris;
+ IF res.href.match(base_uri);
+ args.overdrive_id = res.href.remove('^.*/ContentDetails.htm\?ID=');
+ LAST;
+ END;
+ END;
+ END;
+ END;
END;
+
NEXT;
ELSE;
copies = volume.findnodes('./*[local-name()="copies"]/*[local-name()="copy"]');
<td class='td-right'>
<a href="[% mkurl(ctx.opac_root _ '/myopac/circs') %]"
title="[% l('View My Checked Out Items') %]">
- [% l("View All") %]
+ [% l("Items Currently Checked out ([_1])", ctx.user_stats.checkouts.total_out) %]
+ </a>
+ </td>
+ <td class="td-right hidden" id="acct_sum_ebook_circs">
+ <a href="[% mkurl(ctx.opac_root _ '/myopac/circs?e_items') %]"
+ title="[% l('View My Checked Out E-Items') %]">
+ [% l("E-Items Currently Checked out") %] (<span id="acct_sum_ebook_circ_total">-</span>)
</a>
</td>
</tr>
<td class='td-right'>
<a href="[% mkurl(ctx.opac_root _ '/myopac/holds') %]"
title="[% l('View My Holds') %]">
- [% l('View All') %]
+ [% l('Items Currently on Hold ([_1])', ctx.user_stats.holds.total) %]
+ </a>
+ </td>
+ <td class="td-right hidden" id="acct_sum_ebook_holds">
+ <a href="[% mkurl(ctx.opac_root _ '/myopac/holds?e_items') %]"
+ title="[% l('View My E-Items On Hold') %]">
+ [% l("E-Items Currently on Hold") %] (<span id="acct_sum_ebook_hold_total">-</span>)
</a>
</td>
</tr>
<td class='td-right'>
<a href="[% mkurl(ctx.opac_root _ '/myopac/holds', {available => 1}) %]"
title="[% l('View My Holds Ready for Pickup') %]">
- [% l('View All') %]
+ [% l('Items ready for pickup ([_1])', ctx.user_stats.holds.ready) %]
+ </a>
+ </td>
+ <td class="td-right hidden" id="acct_sum_ebook_holds_ready">
+ <a href="[% mkurl(ctx.opac_root _ '/myopac/holds?e_items&available=1') %]"
+ title="[% l('View My E-Items Ready for Pickup') %]">
+ [% l("E-Items ready for pickup") %] (<span id="acct_sum_ebook_hold_ready_total">-</span>)
</a>
</td>
</tr>
[%- END %]
[%- IF num_uris > 1 %]</ul>[% END %]
</div>
+[%
+IF ebook_api.enabled == 'true';
+ INCLUDE "opac/parts/ebook_api/avail.tt2";
+END;
+%]
[%- END %]
<div id="copy_hold_counts">
[%-
%]
[% END %] <!-- END detail_record_view -->
</table>
+ [%
+ IF ebook_api.enabled == 'true';
+ INCLUDE "opac/parts/ebook_api/avail.tt2";
+ END;
+ %]
[% PROCESS "opac/parts/result/copy_counts.tt2" %]
[% IF rec.user_circulated %]
<div class="result_item_circulated">
%]</span> [% l("Fines") %]</a>
</span>
</div>
+ <div id="dashboard_e" class="hidden">
+ <span class="dash-align">
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/ebook_circs')
+ %]"><span id="dash_e_checked">-</span> [% l("E-Items Checked Out") %]</a>
+ </span>
+ <span class="dash_divider">|</span>
+ <span class="dash-align">
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/ebook_holds')
+ %]"><span id="dash_e_holds">-</span> [% l("E-Items on Hold") %]</a>
+ </span>
+ <span class="dash_divider">|</span>
+ <span class="dash-align">
+ <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/ebook_holds_ready')
+ %]"><span id="dash_e_pickup">-</span> [% l("E-Items Ready for Checkout") %]</a>
+ </span>
+ </div>
</div>
[% END %]
</div>
--- /dev/null
+// define our classes
+function Vendor(name) {
+ this.name = name;
+ this.ebooks = [];
+}
+
+function Ebook(vendor, id) {
+ this.vendor = vendor;
+ this.id = id; // external ID for this title
+ this.rec_id; // bre.id for this title's MARC record
+ this.avail; // availability info for this title
+ this.holdings = {}; // holdings info
+}
+
+Ebook.prototype.getAvailability = function(callback) {
+ var ses = dojo.cookie(this.vendor);
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.title.availability',
+ params: [ ses, this.id ],
+ async: true,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('availability response: ' + resp.content());
+ this.avail = resp.content();
+ return callback(resp.content());
+ }
+ }
+ }).send();
+}
+
+Ebook.prototype.getHoldings = function(callback) {
+ var ses = dojo.cookie(this.vendor);
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.title.holdings',
+ params: [ ses, this.id ],
+ async: true,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('holdings response: ' + resp.content());
+ this.holdings = resp.content();
+ return callback(resp.content());
+ }
+ }
+ }).send();
+}
+
--- /dev/null
+/*
+ * variables defined in base_js.tt2:
+ *
+ * ou
+ * vendor_list = [ 'ebook_test' ]
+ * authtoken
+ * patron_id (barcode)
+ * myopac_page
+ * progress_icon (probably not done right)
+ *
+ * base_js.tt2 also "imports" dojo.cookie and a bunch of ebook_api JS
+ */
+
+// Array of objects representing this patron's relationship with a specific vendor.
+var relations = [];
+
+// Transaction cache.
+var xacts = {
+ checkouts: [],
+ holds_pending: [],
+ holds_ready: []
+};
+
+dojo.addOnLoad(function() {
+
+ dojo.forEach(vendor_list, function(v) {
+ var rel = new Relation(v, patron_id);
+ relations.push(rel);
+ });
+
+ // Pull patron transaction info from cache (cookie), if available.
+ // Otherwise, do a live lookup against all enabled vendors.
+ if (dojo.cookie('ebook_xact_cache')) {
+ getCachedTransactions();
+ addTotalsToPage();
+ addTransactionsToPage();
+ } else {
+ console.log('retrieving patron transaction info for all vendors');
+ dojo.forEach(relations, function(rel) {
+ checkSession(rel.vendor, function(ses) {
+ rel.getTransactions( function(r) {
+ addTransactionsToCache(r);
+ });
+ });
+ });
+ }
+
+});
+
+// Update current page with cross-vendor transaction totals.
+function addTotalsToPage() {
+ console.log('updating page with transaction totals');
+ updateDashboard();
+ updateMyAccountSummary();
+}
+
+// Update current page with detailed transaction info, where appropriate.
+function addTransactionsToPage() {
+ if (myopac_page) {
+ console.log('updating page with cached transaction details, if applicable');
+ if (myopac_page === 'ebook_circs')
+ updateCheckoutView();
+ if (myopac_page === 'ebook_holds')
+ updateHoldView();
+ if (myopac_page === 'ebook_holds_ready')
+ updateHoldReadyView();
+ }
+}
+
+function updateDashboard() {
+ console.log('updating dashboard');
+ var total_checkouts = (typeof xacts.checkouts === 'undefined') ? '-' : xacts.checkouts.length;
+ var total_holds_pending = (typeof xacts.holds_pending === 'undefined') ? '-' : xacts.holds_pending.length;
+ var total_holds_ready = (typeof xacts.holds_ready === 'undefined') ? '-' : xacts.holds_ready.length;
+ // update totals
+ dojo.byId('dash_e_checked').innerHTML = total_checkouts;
+ dojo.byId('dash_e_holds').innerHTML = total_holds_pending;
+ dojo.byId('dash_e_pickup').innerHTML = total_holds_ready;
+ // unhide ebook dashboard
+ dojo.removeClass('dashboard_e', "hidden");
+}
+
+function updateMyAccountSummary() {
+ if (myopac_page === 'main') {
+ console.log('updating account summary');
+ var total_checkouts = (typeof xacts.checkouts === 'undefined') ? '-' : xacts.checkouts.length;
+ var total_holds_pending = (typeof xacts.holds_pending === 'undefined') ? '-' : xacts.holds_pending.length;
+ var total_holds_ready = (typeof xacts.holds_ready === 'undefined') ? '-' : xacts.holds_ready.length;
+ // update totals
+ dojo.byId('acct_sum_ebook_circ_total').innerHTML = total_checkouts;
+ dojo.byId('acct_sum_ebook_hold_total').innerHTML = total_holds_pending;
+ dojo.byId('acct_sum_ebook_hold_ready_total').innerHTML = total_holds_ready;
+ // unhide display elements
+ dojo.removeClass('acct_sum_ebook_circs', "hidden");
+ dojo.removeClass('acct_sum_ebook_holds', "hidden");
+ dojo.removeClass('acct_sum_ebook_holds_ready', "hidden");
+ }
+}
+
+function updateCheckoutView() {
+ if (xacts.checkouts.length < 1) {
+ dojo.removeClass('no_ebook_circs', "hidden");
+ } else {
+ dojo.forEach(xacts.checkouts, function(x) {
+ var dl_link = '<a href="' + x.download_url + '">' + l_strings.download + '</a>';
+ var tr = dojo.create("tr", null, dojo.byId('ebook_circs_main_table_body'));
+ dojo.create("td", { innerHTML: x.title }, tr);
+ dojo.create("td", { innerHTML: x.author }, tr);
+ dojo.create("td", { innerHTML: x.due_date }, tr);
+ dojo.create("td", { innerHTML: dl_link}, tr);
+ // TODO: more actions (renew, checkin)
+ });
+ dojo.addClass('no_ebook_circs', "hidden");
+ dojo.removeClass('ebook_circs_main', "hidden");
+ }
+}
+
+function updateHoldView() {
+ var holds_pending = xacts.holds_pending;
+ var holds_ready = xacts.holds_ready;
+
+ // combine all holds into a single list, ready-for-checkout holds first
+ var holds = holds_ready.concat(holds_pending);
+
+ if (holds.length < 1) {
+ dojo.removeClass('no_ebook_holds', "hidden");
+ } else {
+ dojo.forEach(holds, function(h) {
+ var hold_status;
+ if (h.is_ready) {
+ hold_status = l_strings.ready_for_checkout;
+ } else if (h.is_frozen) {
+ hold_status = l_strings.suspended;
+ } else {
+ hold_status = h.queue_position + ' / ' + h.queue_size;
+ }
+ var tr = dojo.create("tr", null, dojo.byId('ebook_holds_main_table_body'));
+ dojo.create("td", { innerHTML: h.title }, tr);
+ dojo.create("td", { innerHTML: h.author }, tr);
+ dojo.create("td", { innerHTML: h.expire_date }, tr);
+ dojo.create("td", { innerHTML: hold_status }, tr);
+ dojo.create("td", null, tr); // TODO actions
+ });
+ dojo.addClass('no_ebook_holds', "hidden");
+ dojo.removeClass('ebook_holds_main', "hidden");
+ }
+}
+
+function updateHoldReadyView() {
+ var holds = xacts.holds_ready;
+ if (holds.length < 1) {
+ dojo.removeClass('no_ebook_holds', "hidden");
+ } else {
+ dojo.forEach(holds, function(h) {
+ var tr = dojo.create("tr", null, dojo.byId('ebook_holds_main_table_body'));
+ dojo.create("td", { innerHTML: h.title }, tr);
+ dojo.create("td", { innerHTML: h.author }, tr);
+ dojo.create("td", { innerHTML: h.expire_date }, tr);
+ dojo.create("td", null, tr); // TODO actions
+ });
+ dojo.addClass('no_ebook_holds', "hidden");
+ dojo.removeClass('ebook_holds_main', "hidden");
+ }
+}
+
+// deserialize transactions from cache, returning them as a JS object
+function getCachedTransactions() {
+ console.log('retrieving cached transaction details');
+ var cache_obj;
+ var current_cache = dojo.cookie('ebook_xact_cache');
+ if (current_cache) {
+ cache_obj = JSON.parse(current_cache);
+ xacts.checkouts = cache_obj.checkouts;
+ xacts.holds_pending = cache_obj.holds_pending;
+ xacts.holds_ready = cache_obj.holds_ready;
+ }
+ return cache_obj;
+}
+
+// add a single vendor's transactions to transaction cache
+function addTransactionsToCache(rel) {
+ console.log('updating transaction cache');
+ var v = rel.vendor;
+ var updated_xacts = {
+ checkouts: [],
+ holds_pending: [],
+ holds_ready: []
+ };
+ // preserve any transactions with other vendors
+ dojo.forEach(xacts.checkouts, function(xact) {
+ if (xact.vendor !== v)
+ updated_xacts.checkouts.push(xact);
+ });
+ dojo.forEach(xacts.holds_pending, function(xact) {
+ if (xact.vendor !== v)
+ updated_xacts.holds_pending.push(xact);
+ });
+ dojo.forEach(xacts.holds_ready, function(xact) {
+ if (xact.vendor !== v)
+ updated_xacts.holds_ready.push(xact);
+ });
+ // add transactions from current vendor
+ dojo.forEach(rel.checkouts, function(xact) {
+ updated_xacts.checkouts.push(xact);
+ });
+ dojo.forEach(rel.holds_pending, function(xact) {
+ updated_xacts.holds_pending.push(xact);
+ });
+ dojo.forEach(rel.holds_ready, function(xact) {
+ updated_xacts.holds_ready.push(xact);
+ });
+ // TODO sort transactions by date
+ // save transactions to cache
+ xacts = updated_xacts;
+ var new_cache = JSON.stringify(xacts);
+ dojo.cookie('ebook_xact_cache', new_cache, {path: '/'});
+ // update current page
+ addTotalsToPage();
+ addTransactionsToPage();
+}
+
--- /dev/null
+function Relation(vendor, patron_id) {
+ this.vendor = vendor;
+ this.patron_id = patron_id;
+ this.checkouts = [];
+ this.holds_pending = [];
+ this.holds_ready = [];
+}
+
+Relation.prototype.getCheckouts = function(callback) {
+ var ses = dojo.cookie(this.vendor);
+ var rel = this;
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.patron.get_checkouts',
+ params: [ authtoken, ses, rel.patron_id ],
+ async: false,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('retrieved checkouts for patron');
+ rel.checkouts = [];
+ dojo.forEach(resp.content(), function(checkout) {
+ rel.checkouts.push(checkout);
+ });
+ return callback(rel);
+ }
+ }
+ }).send();
+}
+
+Relation.prototype.getHolds = function(callback) {
+ var ses = dojo.cookie(this.vendor);
+ var rel = this;
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.patron.get_holds',
+ params: [ authtoken, ses, rel.patron_id ],
+ async: false,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('retrieved holds for patron');
+ dojo.forEach(resp.content(), function(hold) {
+ if (hold.is_ready === 1) {
+ rel.holds_ready.push(hold);
+ } else {
+ rel.holds_pending.push(hold);
+ }
+ });
+ return callback(rel);
+ }
+ }
+ }).send();
+}
+
+Relation.prototype.getTransactions = function(callback) {
+ var ses = dojo.cookie(this.vendor);
+ var rel = this;
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.patron.get_transactions',
+ params: [ authtoken, ses, rel.patron_id ],
+ async: false,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ console.log('retrieved holds for patron');
+ var xacts = resp.content();
+ dojo.forEach(xacts.checkouts, function(checkout) {
+ rel.checkouts.push(checkout);
+ });
+ dojo.forEach(xacts.holds, function(hold) {
+ if (hold.is_ready === 1) {
+ rel.holds_ready.push(hold);
+ } else {
+ rel.holds_pending.push(hold);
+ }
+ });
+ return callback(rel);
+ }
+ }
+ }).send();
+}
--- /dev/null
+// initialize an API session
+// XXX Are there any cases where checkSession does not suffice for this?
+function startSession(vendor, callback) {
+ console.log('starting ebook API session for ' + vendor);
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.start_session',
+ params: [ vendor, ou ],
+ async: false, // XXX
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ var ses = resp.content();
+ dojo.cookie(vendor, ses, {path: '/'});
+ return callback(vendor,ses);
+ }
+ }
+ }).send();
+}
+
+// validate or initialize API session
+// (check_session method will fallback to start_session if no session ID is provided)
+function checkSession(vendor, callback) {
+ var ses = dojo.cookie(vendor) || null;
+ if (ses == null)
+ return startSession(vendor,callback);
+ console.log('checking ebook API session for ' + vendor);
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.check_session',
+ params: [ ses, vendor, ou ],
+ async: false, // XXX
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ var new_ses = resp.content();
+ dojo.cookie(vendor, new_ses, {path: '/'});
+ return callback(vendor,new_ses);
+ }
+ }
+ }).send();
+}