From deab49ceef1b036c7918c3103282e0be13710980 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Mon, 14 May 2018 15:24:59 -0400 Subject: [PATCH] LP#1721575: Batch Actions In the Public Catalog The public catalog now displays checkboxes on the bibliographic and metarecord constituents results pages. Selecting one or more titles by using the checkboxes will dynamically add those title to the temporary list, which is now renamed the basket. Above the results lists there is now a bar with a select-all checkbox, a link to the basket management page that also indicates the number of of titles in the basket, and a link to remove from the basket titles that are selected on the currently displayed results page. The search bar now includes an icon of a basket and displays the number of titles currently in the basket. Next to that icon is a menu of basket actions. The basket actions available are Place Hold, Print Title Details, Email Title Details, Add Cart to Saved List, and Clear Cart. In the web staff client, the basket actions also include Add Cart to Bucket. When an action is selected from this menu, the user is given an opportunity to confirm the action and to optionally empty the basket when the action is complete. The action is applied to all titles in the basket. Clicking on the basket icon brings the user to a page listing the titles in the basket. From there, the user can select specific records to request, print, email, add to a list, or remove from the basket. The list of actions on the record details page now provides separate links for adding the title to a basket or to a permanent list. The permanent list management page in the public catalog now also includes batch print and email actions. Additional information ++++++++++++++++++++++ * The checkboxes do not display on the metarecord results page, as metarecords currently cannot be put into baskets or lists. * The checkboxes are displayed only if Javascript is enabled. However, users can still add items to the basket and perform batch actions on the basket and on lists. * A template `config.tt2` setting, `ctx.max_basket_size`, can be used to set a soft limit on the number of titles that can be added to the basket. If this limit is reached, checkboxes to add more records to the basket are disabled unless existing titles in the basket are removed first. The default value for this setting is 500. Developer notes +++++++++++++++ This patch adds the the public catalog two routes that return JSON rather than HTML: * `GET /eg/opac/api/mylist/add?record=45` * `GET /eg/opac/api/mylist/delete?record=45` The JSON response is a hash containing a mylist key pointing to the list of bib IDs of contents of the basket. The record parameter can be repeated to allow adding or removing records as an atomic operation. Note that this change also now available to `/eg/opac/mylist/{add,delete}` More generally, this adds a way for EGWeb context loaders to specify that a response should be emitted as JSON rather than rendering an HTML page using `Template::Toolkit`. Specifically, if the context as munged by the context loader contains a `json_response` key, the contents of that key will to provide a JSON reponse. The `json_response_cookie` key, if present, can be used to set a cookie as part of the response. Template Toolkit processing is bypassed entirely when emitting a JSON response, so the context loader would be entirely reponsible for localization of strings in the response meant for direct human consumption. Signed-off-by: Galen Charlton Signed-off-by: Kathy Lussier Signed-off-by: Bill Erickson --- .../perlmods/lib/OpenILS/WWW/EGCatLoader.pm | 19 +- .../lib/OpenILS/WWW/EGCatLoader/Account.pm | 69 ++++- .../lib/OpenILS/WWW/EGCatLoader/Container.pm | 237 +++++++++++++-- .../lib/OpenILS/WWW/EGCatLoader/Record.pm | 66 +++- .../src/perlmods/lib/OpenILS/WWW/EGWeb.pm | 13 + Open-ILS/src/sql/Pg/950.data.seed-values.sql | 6 +- .../XXXX.data.bre_format_title_fix.sql | 86 ++++++ Open-ILS/src/templates/opac/advanced.tt2 | 1 + Open-ILS/src/templates/opac/browse.tt2 | 1 + Open-ILS/src/templates/opac/css/style.css.tt2 | 61 +++- Open-ILS/src/templates/opac/mylist.tt2 | 5 +- Open-ILS/src/templates/opac/mylist/clear.tt2 | 21 ++ Open-ILS/src/templates/opac/mylist/email.tt2 | 29 ++ Open-ILS/src/templates/opac/mylist/print.tt2 | 29 ++ Open-ILS/src/templates/opac/myopac/lists.tt2 | 26 +- .../src/templates/opac/parts/anon_list.tt2 | 21 +- .../templates/opac/parts/bookbag_actions.tt2 | 3 - Open-ILS/src/templates/opac/parts/cart.tt2 | 30 ++ Open-ILS/src/templates/opac/parts/config.tt2 | 5 + .../src/templates/opac/parts/css/colors.tt2 | 1 + Open-ILS/src/templates/opac/parts/header.tt2 | 8 +- Open-ILS/src/templates/opac/parts/js.tt2 | 10 + .../src/templates/opac/parts/place_hold.tt2 | 9 +- .../templates/opac/parts/record/summary.tt2 | 43 ++- .../src/templates/opac/parts/result/table.tt2 | 69 +++-- .../src/templates/opac/parts/searchbar.tt2 | 1 + Open-ILS/src/templates/opac/parts/topnav.tt2 | 2 +- Open-ILS/src/templates/opac/record/email.tt2 | 4 + Open-ILS/src/templates/opac/record/print.tt2 | 4 + Open-ILS/src/templates/opac/results.tt2 | 4 +- Open-ILS/src/templates/opac/temp_warn.tt2 | 6 +- Open-ILS/web/images/add-to-cart.png | Bin 0 -> 617 bytes Open-ILS/web/images/cart-md.png | Bin 0 -> 7541 bytes Open-ILS/web/images/cart-sm.png | Bin 0 -> 762 bytes .../js/ui/default/opac/record_selectors.js | 284 ++++++++++++++++++ .../js/ui/default/staff/cat/catalog/app.js | 49 ++- .../OPAC/Batch_Actions.adoc | 76 +++++ 37 files changed, 1177 insertions(+), 121 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.bre_format_title_fix.sql create mode 100644 Open-ILS/src/templates/opac/mylist/clear.tt2 create mode 100644 Open-ILS/src/templates/opac/mylist/email.tt2 create mode 100644 Open-ILS/src/templates/opac/mylist/print.tt2 create mode 100644 Open-ILS/src/templates/opac/parts/cart.tt2 create mode 100644 Open-ILS/web/images/add-to-cart.png create mode 100644 Open-ILS/web/images/cart-md.png create mode 100644 Open-ILS/web/images/cart-sm.png create mode 100644 Open-ILS/web/js/ui/default/opac/record_selectors.js create mode 100644 docs/RELEASE_NOTES_NEXT/OPAC/Batch_Actions.adoc diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm index d3aecaa0b0..eeeba4a078 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -36,7 +36,8 @@ use constant COOKIE_PHYSICAL_LOC => 'eg_physical_loc'; use constant COOKIE_SSS_EXPAND => 'eg_sss_expand'; use constant COOKIE_ANON_CACHE => 'anoncache'; -use constant ANON_CACHE_MYLIST => 'mylist'; +use constant COOKIE_CART_CACHE => 'cartcache'; +use constant CART_CACHE_MYLIST => 'mylist'; use constant ANON_CACHE_STAFF_SEARCH => 'staffsearch'; use constant DEBUG_TIMING => 0; @@ -129,7 +130,13 @@ sub load { } (undef, $self->ctx->{mylist}) = $self->fetch_mylist unless - $path =~ /opac\/my(opac\/lists|list)/; + $path =~ /opac\/my(opac\/lists|list)/ || + $path =~ m!opac/api/mylist!; + + return $self->load_api_mylist_retrieve if $path =~ m|opac/api/mylist/retrieve|; + return $self->load_api_mylist_add if $path =~ m|opac/api/mylist/add|; + return $self->load_api_mylist_delete if $path =~ m|opac/api/mylist/delete|; + return $self->load_api_mylist_clear if $path =~ m|opac/api/mylist/clear|; return $self->load_simple("home") if $path =~ m|opac/home|; return $self->load_simple("css") if $path =~ m|opac/css|; @@ -146,7 +153,8 @@ sub load { return $self->load_mylist_add if $path =~ m|opac/mylist/add|; return $self->load_mylist_delete if $path =~ m|opac/mylist/delete|; return $self->load_mylist_move if $path =~ m|opac/mylist/move|; - return $self->load_mylist if $path =~ m|opac/mylist|; + return $self->load_mylist_print if $path =~ m|opac/mylist/doprint|; + return $self->load_mylist if $path =~ m|opac/mylist| && $path !~ m|opac/mylist/email| && $path !~ m|opac/mylist/doemail|; return $self->load_cache_clear if $path =~ m|opac/cache/clear|; return $self->load_temp_warn_post if $path =~ m|opac/temp_warn/post|; return $self->load_temp_warn if $path =~ m|opac/temp_warn|; @@ -190,6 +198,11 @@ sub load { $self->apache->headers_out->add("cache-control" => "no-store, no-cache, must-revalidate"); $self->apache->headers_out->add("expires" => "-1"); + if ($path =~ m|opac/mylist/email|) { + (undef, $self->ctx->{mylist}) = $self->fetch_mylist; + } + $self->load_simple("mylist/email") if $path =~ m|opac/mylist/email|; + return $self->load_mylist_email if $path =~ m|opac/mylist/doemail|; return $self->load_email_record if $path =~ m|opac/record/email|; return $self->load_place_hold if $path =~ m|opac/place_hold|; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm index 027948b1e8..d78b59bf5f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -934,7 +934,8 @@ sub handle_hold_update { $url = $self->ctx->{proto} . '://' . $self->ctx->{hostname} . $self->ctx->{opac_root} . '/myopac/holds'; foreach my $param (('loc', 'qtype', 'query')) { if ($self->cgi->param($param)) { - $url .= ";$param=" . uri_escape_utf8($self->cgi->param($param)); + my @vals = $self->cgi->param($param); + $url .= ";$param=" . uri_escape_utf8($_) foreach @vals; } } } @@ -1421,6 +1422,10 @@ sub attempt_hold_placement { $bses->kill_me; } + + if ($self->cgi->param('clear_cart')) { + $self->clear_anon_cache; + } } # pull the selected formats and languages for metarecord holds @@ -2400,7 +2405,8 @@ sub load_myopac_bookbags { foreach my $param (('loc', 'qtype', 'query', 'sort', 'offset', 'limit')) { if ($self->cgi->param($param)) { - $url .= ";$param=" . uri_escape_utf8($self->cgi->param($param)); + my @vals = $self->cgi->param($param); + $url .= ";$param=" . uri_escape_utf8($_) foreach @vals; } } @@ -2473,7 +2479,7 @@ sub load_myopac_bookbags { } -# actions are create, delete, show, hide, rename, add_rec, delete_item, place_hold +# actions are create, delete, show, hide, rename, add_rec, delete_item, place_hold, print, email # CGI is action, list=list_id, add_rec/record=bre_id, del_item=bucket_item_id, name=new_bucket_name sub load_myopac_bookbag_update { my ($self, $action, $list_id, @hold_recs) = @_; @@ -2490,11 +2496,22 @@ sub load_myopac_bookbag_update { my @add_rec = $cgi->param('add_rec') || $cgi->param('record'); my @selected_item = $cgi->param('selected_item'); my $shared = $cgi->param('shared'); + my $move_cart = $cgi->param('move_cart'); my $name = $cgi->param('name'); my $description = $cgi->param('description'); my $success = 0; my $list; + # bail out if user is attempting an action that requires + # that at least one list item be selected + if ((scalar(@selected_item) == 0) && (scalar(@hold_recs) == 0) && + ($action eq 'place_hold' || $action eq 'print' || + $action eq 'email' || $action eq 'del_item')) { + my $url = $self->ctx->{referer}; + $url .= ($url =~ /\?/ ? '&' : '?') . 'list_none_selected=1' unless $url =~ /list_none_selected/; + return $self->generic_redirect($url); + } + # This url intentionally leaves off the edit_notes parameter, but # may need to add some back in for paging. @@ -2503,7 +2520,8 @@ sub load_myopac_bookbag_update { foreach my $param (('loc', 'qtype', 'query', 'sort')) { if ($cgi->param($param)) { - $url .= "$param=" . uri_escape_utf8($cgi->param($param)) . ";"; + my @vals = $cgi->param($param); + $url .= ";$param=" . uri_escape_utf8($_) foreach @vals; } } @@ -2518,15 +2536,29 @@ sub load_myopac_bookbag_update { $list->pub($shared ? 't' : 'f'); $success = $U->simplereq('open-ils.actor', 'open-ils.actor.container.create', $e->authtoken, 'biblio', $list); - if (ref($success) ne 'HASH' && scalar @add_rec) { + if (ref($success) ne 'HASH') { $list_id = (ref($success)) ? $success->id : $success; - foreach my $add_rec (@add_rec) { - my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new; - $item->bucket($list_id); - $item->target_biblio_record_entry($add_rec); - $success = $U->simplereq('open-ils.actor', - 'open-ils.actor.container.item.create', $e->authtoken, 'biblio', $item); - last unless $success; + if (scalar @add_rec) { + foreach my $add_rec (@add_rec) { + my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new; + $item->bucket($list_id); + $item->target_biblio_record_entry($add_rec); + $success = $U->simplereq('open-ils.actor', + 'open-ils.actor.container.item.create', $e->authtoken, 'biblio', $item); + last unless $success; + } + } + if ($move_cart) { + my ($cache_key, $list) = $self->fetch_mylist(0, 1); + foreach my $add_rec (@$list) { + my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new; + $item->bucket($list_id); + $item->target_biblio_record_entry($add_rec); + $success = $U->simplereq('open-ils.actor', + 'open-ils.actor.container.item.create', $e->authtoken, 'biblio', $item); + last unless $success; + } + $self->clear_anon_cache; } } $url = $cgi->param('where_from') if ($success && $cgi->param('where_from')); @@ -2537,7 +2569,8 @@ sub load_myopac_bookbag_update { } elsif($action eq 'place_hold') { - # @hold_recs comes from anon lists redirect; selected_itesm comes from existing buckets + # @hold_recs comes from anon lists redirect; selected_items comes from existing buckets + my $from_basket = scalar(@hold_recs); unless (@hold_recs) { if (@selected_item) { my $items = $e->search_container_biblio_record_entry_bucket_item({id => \@selected_item}); @@ -2550,13 +2583,21 @@ sub load_myopac_bookbag_update { my $url = $self->ctx->{opac_root} . '/place_hold?hold_type=T'; $url .= ';hold_target=' . $_ for @hold_recs; + $url .= ';from_basket=1' if $from_basket; foreach my $param (('loc', 'qtype', 'query')) { if ($cgi->param($param)) { - $url .= ";$param=" . uri_escape_utf8($cgi->param($param)); + my @vals = $cgi->param($param); + $url .= ";$param=" . uri_escape_utf8($_) foreach @vals; } } return $self->generic_redirect($url); + } elsif ($action eq 'print') { + my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@selected_item); + return $self->load_mylist_print($temp_cache_key); + } elsif ($action eq 'email') { + my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@selected_item); + return $self->load_mylist_email($temp_cache_key); } else { $list = $e->retrieve_container_biblio_record_entry_bucket($list_id); diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm index df7b79fffc..6b076720ee 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm @@ -10,17 +10,17 @@ my $U = 'OpenILS::Application::AppUtils'; # Retrieve the users cached records AKA 'My List' # Returns an empty list if there are no cached records sub fetch_mylist { - my ($self, $with_marc_xml) = @_; + my ($self, $with_marc_xml, $skip_sort) = @_; my $list = []; - my $cache_key = $self->cgi->cookie((ref $self)->COOKIE_ANON_CACHE); + my $cache_key = $self->cgi->cookie((ref $self)->COOKIE_CART_CACHE); if($cache_key) { $list = $U->simplereq( 'open-ils.actor', 'open-ils.actor.anon_cache.get_value', - $cache_key, (ref $self)->ANON_CACHE_MYLIST); + $cache_key, (ref $self)->CART_CACHE_MYLIST); if(!$list) { $cache_key = undef; @@ -59,7 +59,7 @@ sub fetch_mylist { # Leverage QueryParser to sort the items by values of config.metabib_fields # from the items' marc records. - if (@$list) { + if (@$list && !$skip_sort) { my ($sorter, $modifier) = $self->_get_bookbag_sort_params("anonsort"); my $query = $self->_prepare_anonlist_sorting_query($list, $sorter, $modifier); my $args = { @@ -73,19 +73,40 @@ sub fetch_mylist { return ($cache_key, $list, $marc_xml); } +sub load_api_mylist_retrieve { + my $self = shift; + + # this has the effect of instantiating an empty one if need be + my ($cache_key, $list) = $self->fetch_mylist(0, 1); + + $self->ctx->{json_response} = { + mylist => [ map { 0 + $_ } @$list ], # force integers + }; + $self->ctx->{json_reponse_cookie} = + $self->cgi->cookie( + -name => (ref $self)->COOKIE_CART_CACHE, + -path => '/', + -value => ($cache_key) ? $cache_key : '', + -expires => ($cache_key) ? undef : '-1h' + ); + + return Apache2::Const::OK; +} + +sub load_api_mylist_clear { + my $self = shift; + + $self->clear_anon_cache; + + # and return fresh, empty cart + return $self->load_api_mylist_retrieve(); +} # Adds a record (by id) to My List, creating a new anon cache + list if necessary. sub load_mylist_add { my $self = shift; - my $rec_id = $self->cgi->param('record'); - my ($cache_key, $list) = $self->fetch_mylist; - push(@$list, $rec_id); - - $cache_key = $U->simplereq( - 'open-ils.actor', - 'open-ils.actor.anon_cache.set_value', - $cache_key, (ref $self)->ANON_CACHE_MYLIST, $list); + my ($cache_key, $list) = $self->_do_mylist_add(); # Check if we need to warn patron about adding to a "temporary" # list: @@ -96,30 +117,181 @@ sub load_mylist_add { return $self->mylist_action_redirect($cache_key); } -sub load_mylist_delete { +sub load_api_mylist_add { my $self = shift; - my $rec_id = $self->cgi->param('record'); - my ($cache_key, $list) = $self->fetch_mylist; - $list = [ grep { $_ ne $rec_id } @$list ]; + my ($cache_key, $list) = $self->_do_mylist_add(); + + $self->ctx->{json_response} = { + mylist => [ map { 0 + $_ } @$list ], # force integers + }; + $self->ctx->{json_reponse_cookie} = + $self->cgi->cookie( + -name => (ref $self)->COOKIE_CART_CACHE, + -path => '/', + -value => ($cache_key) ? $cache_key : '', + -expires => ($cache_key) ? undef : '-1h' + ); + + return Apache2::Const::OK; +} + +sub _do_mylist_add { + my $self = shift; + my @rec_ids = $self->cgi->param('record'); + + my ($cache_key, $list) = $self->fetch_mylist(0, 1); + push(@$list, @rec_ids); $cache_key = $U->simplereq( 'open-ils.actor', 'open-ils.actor.anon_cache.set_value', - $cache_key, (ref $self)->ANON_CACHE_MYLIST, $list); + $cache_key, (ref $self)->CART_CACHE_MYLIST, $list); + + return ($cache_key, $list); +} + +sub load_mylist_delete { + my $self = shift; + + my ($cache_key, $list) = $self->_do_mylist_delete; return $self->mylist_action_redirect($cache_key); } +sub load_api_mylist_delete { + my $self = shift; + + my ($cache_key, $list) = $self->_do_mylist_delete(); + + $self->ctx->{json_response} = { + mylist => [ map { 0 + $_ } @$list ], # force integers + }; + $self->ctx->{json_reponse_cookie} = + $self->cgi->cookie( + -name => (ref $self)->COOKIE_CART_CACHE, + -path => '/', + -value => ($cache_key) ? $cache_key : '', + -expires => ($cache_key) ? undef : '-1h' + ); + + return Apache2::Const::OK; +} + +sub _do_mylist_delete { + my $self = shift; + my @rec_ids = $self->cgi->param('record'); + + my ($cache_key, $list) = $self->fetch_mylist(0, 1); + foreach my $rec_id (@rec_ids) { + $list = [ grep { $_ ne $rec_id } @$list ]; + } + + $cache_key = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.anon_cache.set_value', + $cache_key, (ref $self)->CART_CACHE_MYLIST, $list); + + return ($cache_key, $list); +} + +sub load_mylist_print { + my $self = shift; + + my $cache_key = shift // $self->cgi->cookie((ref $self)->COOKIE_CART_CACHE); + + if (!$cache_key) { + return $self->generic_redirect; + } + + my $url = sprintf( + "%s://%s%s/record/print/%s", + $self->ctx->{proto}, + $self->ctx->{hostname}, + $self->ctx->{opac_root}, + $cache_key, + ); + + my $redirect = $self->cgi->param('redirect_to'); + $url .= '?redirect_to=' . uri_escape_utf8($redirect); + my $clear_cart = $self->cgi->param('clear_cart'); + $url .= '&is_list=1'; + $url .= '&clear_cart=1' if $clear_cart; + + return $self->generic_redirect($url); +} + +sub load_mylist_email { + my $self = shift; + + my $cache_key = shift // $self->cgi->cookie((ref $self)->COOKIE_CART_CACHE); + + if (!$cache_key) { + return $self->generic_redirect; + } + + my $url = sprintf( + "%s://%s%s/record/email/%s", + $self->ctx->{proto}, + $self->ctx->{hostname}, + $self->ctx->{opac_root}, + $cache_key, + ); + + my $redirect = $self->cgi->param('redirect_to'); + $url .= '?redirect_to=' . uri_escape_utf8($redirect); + my $clear_cart = $self->cgi->param('clear_cart'); + $url .= '&is_list=1'; + $url .= '&clear_cart=1' if $clear_cart; + + return $self->generic_redirect($url); +} + +sub _stash_record_list_in_anon_cache { + my $self = shift; + my @rec_ids = @_; + + my $cache_key = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.anon_cache.set_value', + undef, (ref $self)->CART_CACHE_MYLIST, [ @rec_ids ]); + return $cache_key; +} + sub load_mylist_move { my $self = shift; my @rec_ids = $self->cgi->param('record'); my $action = $self->cgi->param('action') || ''; - return $self->load_myopac_bookbag_update('place_hold', undef, @rec_ids) - if $action eq 'place_hold'; - my ($cache_key, $list) = $self->fetch_mylist; + + unless ((scalar(@rec_ids) > 0) || + ($self->cgi->param('entire_list') && scalar(@$list) > 0)) { + my $url = $self->ctx->{referer}; + $url .= ($url =~ /\?/ ? '&' : '?') . 'cart_none_selected=1'; + return $self->generic_redirect($url); + } + + if ($action eq 'place_hold') { + if ($self->cgi->param('entire_list')) { + @rec_ids = @$list; + } + return $self->load_myopac_bookbag_update('place_hold', undef, @rec_ids); + } + if ($action eq 'print') { + my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@rec_ids); + return $self->load_mylist_print($temp_cache_key); + } + if ($action eq 'email') { + my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@rec_ids); + return $self->load_mylist_email($temp_cache_key); + } + if ($action eq 'new_list') { + my $url = $self->apache->unparsed_uri; + $url =~ s!/mylist/move!/myopac/lists!; + return $self->generic_redirect($url); + } + return $self->mylist_action_redirect unless $cache_key; my @keep; @@ -128,9 +300,14 @@ sub load_mylist_move { $cache_key = $U->simplereq( 'open-ils.actor', 'open-ils.actor.anon_cache.set_value', - $cache_key, (ref $self)->ANON_CACHE_MYLIST, \@keep + $cache_key, (ref $self)->CART_CACHE_MYLIST, \@keep ); + if ($action eq 'delete' && scalar(@keep) == 0) { + my $url = $self->cgi->param('orig_referrer') // $self->ctx->{referer}; + return $self->generic_redirect($url); + } + if ($self->ctx->{user} and $action =~ /^\d+$/) { # in this case, action becomes list_id $self->load_myopac_bookbag_update('add_rec', $self->cgi->param('action')); @@ -151,7 +328,7 @@ sub clear_anon_cache { my $self = shift; my $field = shift; - my $cache_key = $self->cgi->cookie((ref $self)->COOKIE_ANON_CACHE) or return; + my $cache_key = $self->cgi->cookie((ref $self)->COOKIE_CART_CACHE) or return; $U->simplereq( 'open-ils.actor', @@ -170,14 +347,16 @@ sub mylist_action_redirect { if( my $anchor = $self->cgi->param('anchor') ) { # on the results page, we want to redirect # back to record that was affected - $url = $self->ctx->{referer}; + $url = $self->cgi->param('redirect_to') // $self->ctx->{referer}; $url =~ s/#.*|$/#$anchor/; - } + } else { + $url = $self->cgi->param('redirect_to') // $self->ctx->{referer}; + } return $self->generic_redirect( $url, $self->cgi->cookie( - -name => (ref $self)->COOKIE_ANON_CACHE, + -name => (ref $self)->COOKIE_CART_CACHE, -path => '/', -value => ($cache_key) ? $cache_key : '', -expires => ($cache_key) ? undef : '-1h' @@ -209,7 +388,7 @@ sub mylist_warning_redirect { return $self->generic_redirect( $base_url, $self->cgi->cookie( - -name => (ref $self)->COOKIE_ANON_CACHE, + -name => (ref $self)->COOKIE_CART_CACHE, -path => '/', -value => ($cache_key) ? $cache_key : '', -expires => ($cache_key) ? undef : '-1h' @@ -222,6 +401,12 @@ sub load_mylist { (undef, $self->ctx->{mylist}, $self->ctx->{mylist_marc_xml}) = $self->fetch_mylist(1); + # get list of bookbags in case user wants to move cart contents to + # one + if ($self->ctx->{user}) { + $self->_load_lists_and_settings; + } + return Apache2::Const::OK; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm index 2e48ce394c..c071c2452b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm @@ -509,13 +509,40 @@ sub get_hold_copy_summary { sub load_print_record { my $self = shift; - my $rec_id = $self->ctx->{page_args}->[0] + my $rec_or_list_id = $self->ctx->{page_args}->[0] or return Apache2::Const::HTTP_BAD_REQUEST; - $self->{ctx}->{bre_id} = $rec_id; + my $is_list = $self->cgi->param('is_list'); + my $list; + if ($is_list) { + + $list = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.anon_cache.get_value', + $rec_or_list_id, (ref $self)->CART_CACHE_MYLIST); + + if(!$list) { + $list = []; + } + + { # sanitize + no warnings qw/numeric/; + $list = [map { int $_ } @$list]; + $list = [grep { $_ > 0} @$list]; + }; + } else { + $list = $rec_or_list_id; + $self->{ctx}->{bre_id} = $rec_or_list_id; + } + $self->{ctx}->{printable_record} = $U->simplereq( 'open-ils.search', - 'open-ils.search.biblio.record.print', $rec_id); + 'open-ils.search.biblio.record.print', $list); + + if ($self->cgi->param('clear_cart')) { + $self->clear_anon_cache; + } + $self->ctx->{'redirect_to'} = $self->cgi->param('redirect_to'); return Apache2::Const::OK; } @@ -523,14 +550,41 @@ sub load_print_record { sub load_email_record { my $self = shift; - my $rec_id = $self->ctx->{page_args}->[0] + my $rec_or_list_id = $self->ctx->{page_args}->[0] or return Apache2::Const::HTTP_BAD_REQUEST; - $self->{ctx}->{bre_id} = $rec_id; + my $is_list = $self->cgi->param('is_list'); + my $list; + if ($is_list) { + + $list = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.anon_cache.get_value', + $rec_or_list_id, (ref $self)->CART_CACHE_MYLIST); + + if(!$list) { + $list = []; + } + + { # sanitize + no warnings qw/numeric/; + $list = [map { int $_ } @$list]; + $list = [grep { $_ > 0} @$list]; + }; + } else { + $list = $rec_or_list_id; + $self->{ctx}->{bre_id} = $rec_or_list_id; + } + $U->simplereq( 'open-ils.search', 'open-ils.search.biblio.record.email', - $self->ctx->{authtoken}, $rec_id); + $self->ctx->{authtoken}, $list); + + if ($self->cgi->param('clear_cart')) { + $self->clear_anon_cache; + } + $self->ctx->{'redirect_to'} = $self->cgi->param('redirect_to'); return Apache2::Const::OK; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm index 8dc09cb8d2..0d2fe261b4 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm @@ -81,6 +81,19 @@ sub handler_guts { $stat = Apache2::Const::OK; } return $stat unless $stat == Apache2::Const::OK; + + # emit context as JSON if handler requests + if ($ctx->{json_response}) { + $r->content_type("application/json; charset=utf-8"); + $r->headers_out->add("cache-control" => "no-store, no-cache, must-revalidate"); + $r->headers_out->add("expires" => "-1"); + if ($ctx->{json_reponse_cookie}) { + $r->headers_out->add('Set-Cookie' => $ctx->{json_reponse_cookie}) + } + $r->print(OpenSRF::Utils::JSON->perl2JSON($ctx->{json_response})); + return Apache2::Const::OK; + } + return Apache2::Const::DECLINED unless $template; my $text_handler = set_text_handler($ctx, $r); diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index c2eb7f6809..46e9bf6374 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -11772,11 +11772,12 @@ Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] Subject: Bibliographic Records Auto-Submitted: auto-generated -[% FOR cbreb IN target %][% title = '' %] +[% FOR cbreb IN target %] [% FOR item IN cbreb.items; bre_id = item.target_biblio_record_entry; bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); title = title _ part.textContent; END; @@ -11820,11 +11821,12 @@ $$
    - [% FOR cbreb IN target %][% title = '' %] + [% FOR cbreb IN target %] [% FOR item IN cbreb.items; bre_id = item.target_biblio_record_entry; bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); title = title _ part.textContent; END; diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.bre_format_title_fix.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.bre_format_title_fix.sql new file mode 100644 index 0000000000..68e47cb8a8 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.bre_format_title_fix.sql @@ -0,0 +1,86 @@ +BEGIN; + +UPDATE action_trigger.event_definition +SET template = +$$ +[%- USE date -%] +[%- SET user = target.0.owner -%] +To: [%- params.recipient_email || user.email %] +From: [%- params.sender_email || default_sender %] +Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] +Subject: Bibliographic Records +Auto-Submitted: auto-generated + +[% FOR cbreb IN target %] +[% FOR item IN cbreb.items; + bre_id = item.target_biblio_record_entry; + + bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; + FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); + title = title _ part.textContent; + END; + + author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent; + item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value'); + publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent; + pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent; + isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent; + issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent; + upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent; +%] + +[% loop.count %]/[% loop.size %]. Bib ID# [% bre_id %] +[% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%] +[% IF issn %]ISSN: [% issn _ "\n" %][% END -%] +[% IF upc %]UPC: [% upc _ "\n" %] [% END -%] +Title: [% title %] +Author: [% author %] +Publication Info: [% publisher %] [% pubdate %] +Item Type: [% item_type %] + +[% END %] +[% END %] +$$ +WHERE hook = 'biblio.format.record_entry.email' +-- from previous stock definition +AND MD5(template) = 'ee4e6c1b3049086c570c7a77413d46c1'; + +UPDATE action_trigger.event_definition +SET template = +$$ +
    + +
      + [% FOR cbreb IN target %] + [% FOR item IN cbreb.items; + bre_id = item.target_biblio_record_entry; + + bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'}); + title = ''; + FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]'); + title = title _ part.textContent; + END; + + author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent; + item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value'); + publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent; + pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent; + isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent; + %] + +
    1. + Bib ID# [% bre_id %] ISBN: [% isbn %]
      + Title: [% title %]
      + Author: [% author %]
      + Publication Info: [% publisher %] [% pubdate %]
      + Item Type: [% item_type %] +
    2. + [% END %] + [% END %] +
    +
    +$$ +WHERE hook = 'biblio.format.record_entry.print' +-- from previous stock definition +AND MD5(template) = '9ada7ea8417cb23f89d0dc8f15ec68d0'; diff --git a/Open-ILS/src/templates/opac/advanced.tt2 b/Open-ILS/src/templates/opac/advanced.tt2 index de86006f49..d2549e94ba 100644 --- a/Open-ILS/src/templates/opac/advanced.tt2 +++ b/Open-ILS/src/templates/opac/advanced.tt2 @@ -16,6 +16,7 @@ [% l('Browse the Catalog')%] + [% INCLUDE 'opac/parts/cart.tt2' %]
diff --git a/Open-ILS/src/templates/opac/browse.tt2 b/Open-ILS/src/templates/opac/browse.tt2 index ea27b7d71e..dbaaa35886 100644 --- a/Open-ILS/src/templates/opac/browse.tt2 +++ b/Open-ILS/src/templates/opac/browse.tt2 @@ -37,6 +37,7 @@ id="home_adv_search_link">[%l('Advanced Search')%] [% l('Browse the Catalog') %] + [% INCLUDE 'opac/parts/cart.tt2' %]
diff --git a/Open-ILS/src/templates/opac/css/style.css.tt2 b/Open-ILS/src/templates/opac/css/style.css.tt2 index e176ed712a..31bd8171fd 100644 --- a/Open-ILS/src/templates/opac/css/style.css.tt2 +++ b/Open-ILS/src/templates/opac/css/style.css.tt2 @@ -1030,6 +1030,56 @@ tr.result_table_row > td.result_table_pic_header { width: 78px; } +/* styles for selecting records in the results set */ +.result_table_row_selected { + background-color: [% css_colors.item_selected %]; +} +#selected_records_summary, #clear_basket { + margin-left: 5em; +} + +/* styles for the basket */ +#record_basket { + [% IF rtl == 't' -%] + float: left; + margin-left: 5em; + [% ELSE; %] + float: right; + margin-right: 5em; + [% END; %] +} +#record_basket_icon { + [% IF rtl == 't' -%] + float: left; + margin-left: 2em; + [% ELSE; %] + float: right; + margin-right: 2em; + [% END; %] + position: relative; +} +#record_basket_count_floater { + background-color: [% css_colors.accent_lighter %]; + position: absolute; + top: -3px; + right: -3px; /* relative to icon, so don't want to adjust for RTL */ + z-index: 2; + border-radius: 50%; +} +#record_basket_count_floater a { + text-decoration: none; +} +#basket_actions { + [% IF rtl == 't' -%] + float: left; + [% ELSE; %] + float: right; + [% END; %] +} +#basket_actions select { + border-color: rgb(169, 169, 169); +} + .result_number { [% IF rtl == 't' -%] padding-right: 1em; @@ -2399,19 +2449,20 @@ a.preflib_change { border-color: [% css_colors.border_dark %]; border-width: 1px; border-style: solid; + z-index: 1; } .popmenu li:hover li { float: none; } .popmenu li:hover li a { - background-color: [% css_colors.primary %]; - color: [% css_colors.accent_ultralight %]; + background-color: [% css_colors.primary %] !important; + color: [% css_colors.accent_ultralight %] !important; } .popmenu li li a:hover { - background-color: [% css_colors.accent_ultralight %]; - color: [% css_colors.primary %]; + background-color: [% css_colors.accent_ultralight %] !important; + color: [% css_colors.primary %] !important; } -/* Styles for the temporary list entry. */ +/* Styles for the basket entry. */ .popmenu li:hover li[class~="temporary"] a { background-color: [% css_colors.primary %]; color: [% css_colors.accent_ultralight %]; diff --git a/Open-ILS/src/templates/opac/mylist.tt2 b/Open-ILS/src/templates/opac/mylist.tt2 index 50578008b7..25cb8b1760 100644 --- a/Open-ILS/src/templates/opac/mylist.tt2 +++ b/Open-ILS/src/templates/opac/mylist.tt2 @@ -4,7 +4,7 @@ INCLUDE "opac/parts/topnav.tt2"; ctx.metalinks.push(''); ctx.page_title = l("Record Detail") %] -

[% l('Temporary List') %]

+

[% l('Basket') %]

[% INCLUDE "opac/parts/searchbar.tt2" %]
@@ -13,7 +13,8 @@ [% IF ctx.mylist.size; INCLUDE "opac/parts/anon_list.tt2"; ELSE %] -
[% l("You have not created a list yet."); %]
+
[% l("The basket is empty."); %]
+ [% END %]
diff --git a/Open-ILS/src/templates/opac/mylist/clear.tt2 b/Open-ILS/src/templates/opac/mylist/clear.tt2 new file mode 100644 index 0000000000..7795c485b4 --- /dev/null +++ b/Open-ILS/src/templates/opac/mylist/clear.tt2 @@ -0,0 +1,21 @@ +[%- PROCESS "opac/parts/header.tt2"; + PROCESS "opac/parts/misc_util.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Confirm Clearing of Basket") %] +

[% l('Confirm Clearing of Basket') %]

+ [% INCLUDE "opac/parts/searchbar.tt2" %] +
+
+

[% l('Please confirm that you want to remove all [_1] titles from the basket.', ctx.mylist.size) %] +

+ + + +
+
+
+
+[%- END %] diff --git a/Open-ILS/src/templates/opac/mylist/email.tt2 b/Open-ILS/src/templates/opac/mylist/email.tt2 new file mode 100644 index 0000000000..04a7a3bdea --- /dev/null +++ b/Open-ILS/src/templates/opac/mylist/email.tt2 @@ -0,0 +1,29 @@ +[%- PROCESS "opac/parts/header.tt2"; + PROCESS "opac/parts/misc_util.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Confirm Basket Email") %] +

[% l('Confirm Basket Email') %]

+ [% INCLUDE "opac/parts/searchbar.tt2" %] +
+
+ [% IF ctx.mylist.size %] +

[% l('Please confirm that you want to email the [_1] titles in the basket.', ctx.mylist.size) %] +

+ + + +
+ + +
+ [% ELSE %] +
[% l("The basket is empty."); %]
+ + [% END %] +
+
+
+[%- END %] diff --git a/Open-ILS/src/templates/opac/mylist/print.tt2 b/Open-ILS/src/templates/opac/mylist/print.tt2 new file mode 100644 index 0000000000..ac53a2106a --- /dev/null +++ b/Open-ILS/src/templates/opac/mylist/print.tt2 @@ -0,0 +1,29 @@ +[%- PROCESS "opac/parts/header.tt2"; + PROCESS "opac/parts/misc_util.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Confirm Basket Printing") %] +

[% l('Confirm Basket Printing') %]

+ [% INCLUDE "opac/parts/searchbar.tt2" %] +
+
+ [% IF ctx.mylist.size %] +

[% l('Please confirm that you want to print the [_1] titles in the basket.', ctx.mylist.size) %] +

+ + + +
+ + +
+ [% ELSE %] +
[% l("The basket is empty."); %]
+ + [% END %] +
+
+
+[%- END %] diff --git a/Open-ILS/src/templates/opac/myopac/lists.tt2 b/Open-ILS/src/templates/opac/myopac/lists.tt2 index 12f8003d7a..3f765172b8 100644 --- a/Open-ILS/src/templates/opac/myopac/lists.tt2 +++ b/Open-ILS/src/templates/opac/myopac/lists.tt2 @@ -59,6 +59,19 @@ + [% IF ctx.mylist.size %] + + + + + + + + + [% END %]   @@ -76,7 +89,11 @@ -

[% l("My Existing Lists") %]

+ [% IF CGI.param('from_basket'); %] +

[% l("... from basket") %]

+ [% INCLUDE "opac/parts/anon_list.tt2" %] + [% ELSE %] +

[% l("My Existing Basket and Lists") %]

[% INCLUDE "opac/parts/anon_list.tt2" %] [% IF ctx.bookbags.size %]
@@ -287,16 +304,22 @@
+
[% IF bbag.items.size %]
[%- INCLUDE "opac/parts/preserve_params.tt2"; %] + [% IF CGI.param('list_none_selected') %] + [% l('No items were selected') %] + [% END %]
[% END %] @@ -466,5 +489,6 @@ [% END %] [% END %] + [% END %] [% END %] diff --git a/Open-ILS/src/templates/opac/parts/anon_list.tt2 b/Open-ILS/src/templates/opac/parts/anon_list.tt2 index 9ec6d588a5..a834dabbda 100644 --- a/Open-ILS/src/templates/opac/parts/anon_list.tt2 +++ b/Open-ILS/src/templates/opac/parts/anon_list.tt2 @@ -1,9 +1,9 @@ [% IF ctx.mylist.size %]
-

[% l('Temporary List') %]

+

[% l('Basket') %]

- + [% INCLUDE "opac/parts/filtersort.tt2" mode='bookbag' id="anonsort" name="anonsort" value=CGI.param("anonsort") %]
-
+ + +
[%- INCLUDE "opac/parts/preserve_params.tt2"; %] + [% l('Clear entire basket when action complete') %] + [% IF CGI.param('cart_none_selected') %] + [% l('No items were selected') %] + [% END %]
- @@ -50,7 +59,7 @@ PROCESS get_marc_attrs args=attrs %]
- + [% attrs.title | html %]
    -
  • - [% l('Temporary List') %] -
  • [% IF default_list; label = (ctx.default_bookbag) ? ctx.default_bookbag : l('Default List'); class = (ctx.bookbags.size) ? "default divider" : "default"; diff --git a/Open-ILS/src/templates/opac/parts/cart.tt2 b/Open-ILS/src/templates/opac/parts/cart.tt2 new file mode 100644 index 0000000000..ae587bcb80 --- /dev/null +++ b/Open-ILS/src/templates/opac/parts/cart.tt2 @@ -0,0 +1,30 @@ +
    +
    + + +
    + +
    diff --git a/Open-ILS/src/templates/opac/parts/config.tt2 b/Open-ILS/src/templates/opac/parts/config.tt2 index b0ad747cdf..ac85bdfa3a 100644 --- a/Open-ILS/src/templates/opac/parts/config.tt2 +++ b/Open-ILS/src/templates/opac/parts/config.tt2 @@ -265,4 +265,9 @@ ctx.exclude_electronic_checkbox = 0; ############################################################################## ctx.hide_badge_scores = 'false'; +############################################################################## +# Maximum number of items allowed to be stored in a basket +############################################################################## +ctx.max_cart_size = 500; + %] diff --git a/Open-ILS/src/templates/opac/parts/css/colors.tt2 b/Open-ILS/src/templates/opac/parts/css/colors.tt2 index 85e00bdaf7..b8c5ca8712 100644 --- a/Open-ILS/src/templates/opac/parts/css/colors.tt2 +++ b/Open-ILS/src/templates/opac/parts/css/colors.tt2 @@ -32,6 +32,7 @@ button_text_shadow = "#555555", # medium grey table_heading = "#d8d8d8", # grey-blue mobile_header_text = "#fff", # white + item_selected = "#ddd", # grey (lighter) }; %] diff --git a/Open-ILS/src/templates/opac/parts/header.tt2 b/Open-ILS/src/templates/opac/parts/header.tt2 index de933e9e62..76b2314187 100644 --- a/Open-ILS/src/templates/opac/parts/header.tt2 +++ b/Open-ILS/src/templates/opac/parts/header.tt2 @@ -9,7 +9,7 @@ # Don't wrap in l() here; do that where this format string is actually used. SET HUMAN_NAME_FORMAT = '[_1] [_2] [_3] [_4] [_5]'; - is_advanced = CGI.param("_adv").size; + is_advanced = CGI.param("_adv").size || CGI.param("query").size; is_special = CGI.param("_special").size; # Check if we want to show the detail record view. Doing this @@ -61,6 +61,12 @@ cgi.delete_all(); END; + # some standing, hardcoded parameters to always clear + # because they're used for specific, transitory purposes + cgi.delete('move_cart_by_default'); + cgi.delete('cart_none_selected'); + cgi.delete('list_none_selected'); + # x and y are artifacts of using tags # instead of true submit buttons, and their values are never used. cgi.delete('x', 'y'); diff --git a/Open-ILS/src/templates/opac/parts/js.tt2 b/Open-ILS/src/templates/opac/parts/js.tt2 index 01fb9f943d..19ad6ff2b4 100644 --- a/Open-ILS/src/templates/opac/parts/js.tt2 +++ b/Open-ILS/src/templates/opac/parts/js.tt2 @@ -68,6 +68,12 @@ [%- END %] + + + [% IF ctx.page == 'place_hold' %] [%- END; # want_dojo -%] + +[%- IF ctx.max_cart_size; %] + +[%- END; %] diff --git a/Open-ILS/src/templates/opac/parts/place_hold.tt2 b/Open-ILS/src/templates/opac/parts/place_hold.tt2 index 099208c950..23eadde7a7 100644 --- a/Open-ILS/src/templates/opac/parts/place_hold.tt2 +++ b/Open-ILS/src/templates/opac/parts/place_hold.tt2 @@ -66,7 +66,8 @@ function maybeToggleNumCopies(obj) { SET some_holds_allowed = 0 IF some_holds_allowed == -1; ELSE; some_holds_allowed = 1; END; END %] - + + [% IF loop.first %] [% @@ -129,6 +130,7 @@ function maybeToggleNumCopies(obj) {

    [% END %] + [% END %] @@ -180,7 +182,7 @@ function maybeToggleNumCopies(obj) { [% END %] [% END %] [% INCLUDE "opac/parts/multi_hold_select.tt2" IF NOT (this_hold_disallowed AND hdata.part_required); %] - [% IF NOT metarecords.disabled %] + [% IF NOT metarecords.disabled AND ctx.hold_data.size == 1 %] [% IF CGI.param('hold_type') == 'T' AND hdata.record.metarecord AND !hdata.part_required %] [% bre_id = hdata.target.id %] @@ -276,6 +278,9 @@ function maybeToggleNumCopies(obj) { [% l('Enter date in MM/DD/YYYY format') %]

    + [% IF CGI.param('from_basket') %] +
    [% l('Clear basket?') %]
    + [% END %] diff --git a/Open-ILS/src/templates/opac/parts/record/summary.tt2 b/Open-ILS/src/templates/opac/parts/record/summary.tt2 index 223b0f34c3..3bc0ef5855 100644 --- a/Open-ILS/src/templates/opac/parts/record/summary.tt2 +++ b/Open-ILS/src/templates/opac/parts/record/summary.tt2 @@ -98,30 +98,45 @@ [%- END -%]
    - [% IF !ctx.is_staff %] - [% IF ctx.user; - INCLUDE "opac/parts/bookbag_actions.tt2"; - %] - [% ELSE; - operation = ctx.mylist.grep(ctx.bre_id).size ? "delete" : "add"; - label = (operation == "add") ? l("Add to my list") : l("Remove from my list"); + [% operation = ctx.mylist.grep('^' _ ctx.bre_id _ '$').size ? "delete" : "add"; + addhref = mkurl(ctx.opac_root _ '/mylist/add', {record => ctx.bre_id}, stop_parms); + delhref = mkurl(ctx.opac_root _ '/mylist/delete', {record => ctx.bre_id}, stop_parms); + label = (operation == "add") ? l("Add to Basket") : l("Remove from Basket"); %] - - - [% label %] + + + [% l("Add to basket") %] + + + + [% l("Remove from basket") %] - [% END %] - [% END %]
    [% IF ctx.mylist.size %] [%- IF ctx.user; %] - [% l('View My Lists') %][% l(' View My Lists') %] + [% l('View Basket') %][% l(' View Basket') %] [%- ELSE %] - [% l('View My Temporary List') %][% l(' View My Temporary List') %] + [% l('View My Basket') %][% l(' View My Basket') %] [%- END %] [% END %]
    +
    + [% IF !ctx.is_staff %] + [% IF ctx.user; + INCLUDE "opac/parts/bookbag_actions.tt2"; + END; + %] + [% END %] +
    [% l('Print / Email Actions Image') %] [% l('Print') %] / diff --git a/Open-ILS/src/templates/opac/parts/result/table.tt2 b/Open-ILS/src/templates/opac/parts/result/table.tt2 index 20da7de23c..8241e68262 100644 --- a/Open-ILS/src/templates/opac/parts/result/table.tt2 +++ b/Open-ILS/src/templates/opac/parts/result/table.tt2 @@ -30,6 +30,24 @@

    [% l('Search Results List') %]

    + [% IF !ctx.is_meta %] + + [% END %]
    @@ -82,11 +100,18 @@ add_parms.import( {query => ctx.naive_query_scrub(ctx.user_query)} ); END; + is_selected = ctx.mylist.grep('^' _ rec.id _ '$').size; %] - - + +
    [% - result_count; result_count = result_count + 1 - %].
    + [% IF !ctx.is_meta; %] + + [% END %] + [% result_count; result_count = result_count + 1 %]. + [% l('Book cover') %] [% IF !ctx.is_staff %] + [% + addhref = mkurl(ctx.opac_root _ '/mylist/add', + {record => rec.id, anchor => 'record_' _ rec.id}, 1); + delhref = mkurl(ctx.opac_root _ '/mylist/delete', + {record => rec.id, anchor => 'record_' _ rec.id}, 1); + %] + + + [% l("Add to basket") %] + + + + [% l("Remove from basket") %] + [% IF ctx.user; INCLUDE "opac/parts/bookbag_actions.tt2"; + END; %] - [% ELSE; - operation = ctx.mylist.grep(rec.id).size ? "delete" : "add"; - label = (operation == "add") ? l("Add to my list") : l("Remove from my list"); - title_label = (operation == "add") ? - l("Add [_1] to my list", attrs.title) : - l("Remove [_1] from my list", attrs.title); - href = mkurl(ctx.opac_root _ '/mylist/' _ operation, - {record => rec.id, anchor => 'record_' _ rec.id}, 1); - %] - - - [% label %] - - [% END %] [% END %] [% END %] diff --git a/Open-ILS/src/templates/opac/parts/searchbar.tt2 b/Open-ILS/src/templates/opac/parts/searchbar.tt2 index 0a5f62f44c..4cf34a393a 100644 --- a/Open-ILS/src/templates/opac/parts/searchbar.tt2 +++ b/Open-ILS/src/templates/opac/parts/searchbar.tt2 @@ -44,6 +44,7 @@ END; [% l('Advanced Search') %] [% l('Browse the Catalog') %] + [% INCLUDE 'opac/parts/cart.tt2' %] [% l('My Account') %] - [% l('My Lists') %] [% l('Logout') %] diff --git a/Open-ILS/src/templates/opac/record/email.tt2 b/Open-ILS/src/templates/opac/record/email.tt2 index 63520fd2b0..88cf88cd76 100644 --- a/Open-ILS/src/templates/opac/record/email.tt2 +++ b/Open-ILS/src/templates/opac/record/email.tt2 @@ -19,7 +19,11 @@ [% END %]
    + [% IF ctx.redirect_to %] +

    [ [% l("Return") %] ]

    + [% ELSE %]

    [ [% l("Back to Record") %] ]

    + [% END %]

    diff --git a/Open-ILS/src/templates/opac/record/print.tt2 b/Open-ILS/src/templates/opac/record/print.tt2 index 24cb94ee27..26c35432a8 100644 --- a/Open-ILS/src/templates/opac/record/print.tt2 +++ b/Open-ILS/src/templates/opac/record/print.tt2 @@ -22,7 +22,11 @@ [% END %]

    + [% IF ctx.redirect_to %] +

    [ [% l("Return") %] ]

    + [% ELSE %]

    [ [% l("Back to Record") %] ]

    + [% END %]
    diff --git a/Open-ILS/src/templates/opac/results.tt2 b/Open-ILS/src/templates/opac/results.tt2 index 4c3d6f6114..53b8dfd8c4 100644 --- a/Open-ILS/src/templates/opac/results.tt2 +++ b/Open-ILS/src/templates/opac/results.tt2 @@ -57,9 +57,9 @@ [% IF ctx.mylist.size %] [% END %] diff --git a/Open-ILS/src/templates/opac/temp_warn.tt2 b/Open-ILS/src/templates/opac/temp_warn.tt2 index 8aa978d3fd..8fdea970af 100644 --- a/Open-ILS/src/templates/opac/temp_warn.tt2 +++ b/Open-ILS/src/templates/opac/temp_warn.tt2 @@ -2,12 +2,12 @@ PROCESS "opac/parts/misc_util.tt2"; WRAPPER "opac/parts/base.tt2"; INCLUDE "opac/parts/topnav.tt2"; - ctx.page_title = l("Temporary List Warning") %] -

    [% l('Temporary List Warning') %]

    + ctx.page_title = l("Basket Warning") %] +

    [% l('Basket Warning') %]

    [% INCLUDE "opac/parts/searchbar.tt2" %]
    -

    [% l('You are adding to a temporary list.') %] +

    [% l('You are adding to a basket.') %] [% IF ctx.user ; l('This information will disappear when you logout, unless you save it to a permanent list.'); ELSE; diff --git a/Open-ILS/web/images/add-to-cart.png b/Open-ILS/web/images/add-to-cart.png new file mode 100644 index 0000000000000000000000000000000000000000..d1fcf6d29464da2a7f7152d76b930038ad2f09be GIT binary patch literal 617 zcmV-v0+#)WP)|WK~y-6wbj2%Rbd>*@z=Rs5>iAo2tl!+u}!0({0j<#?$FZE z){i9|n!4lJTAG438yp(WBDIP^O0+n51#P;JAcjraa6x(+?sM(&Tut=SXZiB^obUI2 zp677RbFvVEva)(@3}fiSdt55DY~ul1*bI5G*~Lh~S-o}*Z*Ut~*By^mz-K%QdC}+s zXZ2bIUvL?(@I9{hZLFzUcpU3EUZ9DaAuoPIHEF(!>v$CM;$5<(Mab_bj9DuWWtq+q}J}orSGA1MI zV8v57kEI^KOOY{^f(NwU*M%Oy3z0DZnV0up_W(OqRXqP6;L6D@X9sU3{wvl?J@eR2 z{BF!rt;gApwklXmf~S~Cd^6<5Ju;iZh)R@1a=+zel6rh=p363+`tpuL4V9g@eNlJ@I&oWY&81**fXB6 z4$kA1SgR!nTrK%?M`Y>D3V*-6S$xv|f1On!GG^fqL1~eh#5{;E00000NkvXXu0mjf D-y9EH literal 0 HcmV?d00001 diff --git a/Open-ILS/web/images/cart-md.png b/Open-ILS/web/images/cart-md.png new file mode 100644 index 0000000000000000000000000000000000000000..a18c7afd9ed4a4df5c4037fd1e636ba2c98a5fe9 GIT binary patch literal 7541 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 F{{gjtpwR#T literal 0 HcmV?d00001 diff --git a/Open-ILS/web/images/cart-sm.png b/Open-ILS/web/images/cart-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..b0273266d303e912d0cd640f0f8fa8630dc2302c GIT binary patch literal 762 zcmV%g}3 z8^9JY1HAIq{ptu{(xext0yp#iikvYA-1gS})XAR6z*Lzp0B?XF@_87jbTFC)YSKRg z+ybtOW(qvggPUeRrv3vk061^mNYmgn=>s0ndKv1#GjH8a@Vyxz0X_kPdMUygxGd%X z6QQ~YKW<^(E+zzEKs?@Mg;Fr^nzUbl^?xvG73-p@J=y{+dh7D<&kiA4i+=;KP?Wif zOfwE((qxc>J>PABA@OPkP-G2YsS5y0qOHB604v_Qy`tSkb0q}O+i(+j3cM}aT*VXM z3h+Kl7#`la(M7|K(Zj(ReQFVI!~X!M0#FNbb>KenXJZSv2P{Ww%Tn8l)aFF99^`8! zAiO?Ry>-ul@rcm0x9$Zn8L3T5Z8}mL7tLyrpDzKcL9$ocj}f8oL7TUhY4ryA)fhlO zfVb`|M@Q#zq9Iy`+9ALuu&C8M6^_mU4Nx{|QkH&UGa#B}m2U|EM^hEu^52y-ec0l0N;SK5#esc9&pZE=Sy&SYh61bbAYw%)~)}H sUY{Vqp_c}j0$u{UT{PYmkEty28$TP%&ERsooB#j-07*qoM6N<$f=|Lg^8f$< literal 0 HcmV?d00001 diff --git a/Open-ILS/web/js/ui/default/opac/record_selectors.js b/Open-ILS/web/js/ui/default/opac/record_selectors.js new file mode 100644 index 0000000000..6116b39a41 --- /dev/null +++ b/Open-ILS/web/js/ui/default/opac/record_selectors.js @@ -0,0 +1,284 @@ +;(function () { + + var rec_selector_block = document.getElementById("record_selector_block"); + var rec_selectors = document.getElementsByClassName("result_record_selector"); + var mylist_action_links = document.getElementsByClassName("mylist_action"); + var record_basket_count_el = document.getElementById('record_basket_count'); + var selected_records_count_el = document.getElementById('selected_records_count'); + var select_all_records_el = document.getElementById('select_all_records'); + var clear_basket_el = document.getElementById('clear_basket'); + var select_action_el = document.getElementById('select_basket_action'); + var do_basket_action_el = document.getElementById('do_basket_action'); + var mylist = []; + + function initialize() { + var req = new window.XMLHttpRequest(); + req.open('GET', '/eg/opac/api/mylist/retrieve'); + if (('responseType' in req) && (req.responseType = 'json')) { + req.onload = function (evt) { + var result = req.response; + handleUpdate(result); + syncPageState(); + } + } else { + // IE 10/11 + req.onload = function (evt) { + var result = JSON.parse(req.responseText); + handleUpdate(result); + syncPageState(); + } + } + req.send(); + } + initialize(); + + function syncPageState() { + var all_checked = true; + var legacy_adjusted = false; + [].forEach.call(rec_selectors, function(el) { + el.checked = mylist.includes(parseInt(el.value)); + if (el.checked) { + adjustLegacyControlsVis('checked', el.value); + } else { + all_checked = false; + adjustLegacyControlsVis('unchecked', el.value); + } + toggleRowHighlighting(el); + legacy_adjusted = true; + }); + if (!legacy_adjusted) { + [].forEach.call(mylist_action_links, function(el) { + if ('dataset' in el) { + if (el.dataset.action == 'delete') return; + // only need to do this once + var op = mylist.includes(parseInt(el.dataset.recid)) ? 'checked' : 'unchecked'; + adjustLegacyControlsVis(op, el.dataset.recid); + } + }); + } + if (select_all_records_el && rec_selectors.length) { + select_all_records_el.checked = all_checked; + } + checkMaxCartSize(); + } + + function handleUpdate(result) { + if (result) { + mylist = result.mylist; + if (selected_records_count_el) { + selected_records_count_el.innerHTML = mylist.length; + } + if (clear_basket_el) { + if (mylist.length > 0) { + clear_basket_el.classList.remove('hidden'); + } else { + clear_basket_el.classList.add('hidden'); + } + } + if (select_action_el) { + if (mylist.length > 0) { + select_action_el.removeAttribute('disabled'); + } else { + select_action_el.setAttribute('disabled', 'disabled'); + } + } + if (do_basket_action_el) { + if (mylist.length > 0) { + do_basket_action_el.removeAttribute('disabled'); + } else { + do_basket_action_el.setAttribute('disabled', 'disabled'); + } + } + if (record_basket_count_el) { + record_basket_count_el.innerHTML = mylist.length; + } + checkMaxCartSize(); + } + } + + function mungeList(op, rec, resync) { + console.debug('calling mungeList to ' + op + ' record ' + rec); + var req = new window.XMLHttpRequest(); + if (Array.isArray(rec)) { + var qrec = rec.map(function(rec) { + return 'record=' + encodeURIComponent(rec); + }).join('&'); + } else { + var qrec = 'record=' + encodeURIComponent(rec); + } + req.open('GET', '/eg/opac/api/mylist/' + op + '?' + qrec); + if (('responseType' in req) && (req.responseType = 'json')) { + req.onload = function (evt) { + var result = req.response; + handleUpdate(result); + if (resync) syncPageState(); + } + } else { + // IE 10/11 + req.onload = function (evt) { + var result = JSON.parse(req.responseText); + handleUpdate(result); + if (resync) syncPageState(); + } + } + req.send(); + } + + function adjustLegacyControlsVis(op, rec) { + if (op == 'add' || op == 'checked') { + var t; + if (t = document.getElementById('mylist_add_' + rec)) t.classList.add('hidden'); + if (t = document.getElementById('mylist_delete_' + rec)) t.classList.remove('hidden'); + } else if (op == 'delete' || op == 'unchecked') { + if (t = document.getElementById('mylist_add_' + rec)) t.classList.remove('hidden'); + if (t = document.getElementById('mylist_delete_' + rec)) t.classList.add('hidden'); + } + } + + function findAncestorWithClass(el, cls) { + while ((el = el.parentElement) && !el.classList.contains(cls)); + return el; + } + function toggleRowHighlighting(el) { + var row = findAncestorWithClass(el, "result_table_row"); + if (!row) return; + if (el.checked) { + row.classList.add('result_table_row_selected'); + } else { + row.classList.remove('result_table_row_selected'); + } + } + + function checkMaxCartSize() { + if ((typeof max_cart_size === 'undefined') || !max_cart_size) return; + var alertel = document.getElementById('hit_selected_record_limit'); + [].forEach.call(rec_selectors, function(el) { + if (!el.checked) el.disabled = (mylist.length >= max_cart_size); + }); + [].forEach.call(mylist_action_links, function(el) { + if ('dataset' in el && el.dataset.action == 'add') { + if (mylist.length >= max_cart_size) { + // hide the add link + el.classList.add('hidden'); + } else { + // show the add link unless the record is + // already in the cart + if (!mylist.includes(parseInt(el.dataset.recid))) el.classList.remove('hidden'); + } + } + }); + if (mylist.length >= max_cart_size) { + if (alertel) alertel.classList.remove('hidden'); + if (select_all_records_el && !select_all_records_el.checked) { + select_all_records_el.disabled = true; + } + } else { + if (alertel) alertel.classList.add('hidden'); + if (select_all_records_el) select_all_records_el.disabled = false; + } + } + + var all_checked = true; + [].forEach.call(rec_selectors, function(el) { + el.addEventListener("click", function() { + if (this.checked) { + mungeList('add', this.value); + adjustLegacyControlsVis('add', this.value); + } else { + mungeList('delete', this.value); + adjustLegacyControlsVis('delete', this.value); + } + toggleRowHighlighting(el); + }, false); + el.classList.remove("hidden"); + if (!el.checked) all_checked = false; + }); + if (select_all_records_el && rec_selectors.length) { + select_all_records_el.checked = all_checked; + } + if (rec_selector_block) rec_selector_block.classList.remove("hidden"); + + function deselectSelectedOnPage() { + [].forEach.call(rec_selectors, function(el) { + if (el.checked) { + el.checked = false; + adjustLegacyControlsVis('delete', el.value); + toggleRowHighlighting(el); + } + }); + } + + if (select_all_records_el) { + select_all_records_el.addEventListener('click', function() { + if (this.checked) { + // adding + var to_add = []; + [].forEach.call(rec_selectors, function(el) { + if (!el.checked) { + el.checked = true; + adjustLegacyControlsVis('add', el.value); + toggleRowHighlighting(el); + to_add.push(el.value); + } + }); + if (to_add.length > 0) { + mungeList('add', to_add); + } + } else { + // deleting + deselectSelectedOnPage(); + } + }); + } + + function clearCart() { + var req = new window.XMLHttpRequest(); + req.open('GET', '/eg/opac/api/mylist/clear'); + if (('responseType' in req) && (req.responseType = 'json')) { + req.onload = function (evt) { + var result = req.response; + handleUpdate(result); + syncPageState(); + } + } else { + // IE 10/11 + req.onload = function (evt) { + var result = JSON.parse(req.responseText); + handleUpdate(result); + syncPageState(); + } + } + req.send(); + } + + if (clear_basket_el) { + clear_basket_el.addEventListener('click', function() { + if (confirm(window.egStrings['CONFIRM_BASKET_EMPTY'])) { + clearCart(); + } + }); + } + + [].forEach.call(mylist_action_links, function(el) { + el.addEventListener("click", function(evt) { + var recid; + var action; + if ('dataset' in el) { + recid = el.dataset.recid; + action = el.dataset.action; + mungeList(action, recid, true); + evt.preventDefault(); + } + }); + }); + + if (do_basket_action_el) { + do_basket_action_el.addEventListener('click', function(evt) { + if (select_action_el.options[select_action_el.selectedIndex].value) { + window.location.href = select_action_el.options[select_action_el.selectedIndex].value; + } + evt.preventDefault(); + }); + } + +})(); diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js index 63a177e8de..b2435e9a86 100644 --- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js +++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js @@ -338,8 +338,26 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e } } - $scope.add_to_record_bucket = function() { - var recId = $scope.record_id; + $scope.add_cart_to_record_bucket = function() { + var cartkey = $cookies.get('cartcache'); + if (!cartkey) return; + egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.anon_cache.get_value', + cartkey, + 'mylist' + ).then(function(list) { + list = list.map(function(x) { + return parseInt(x); + }); + $scope.add_to_record_bucket(list); + }); + } + + $scope.add_to_record_bucket = function(recs) { + if (!angular.isArray(recs)) { + recs = [ $scope.record_id ]; + } return $uibModal.open({ templateUrl: './cat/catalog/t_add_to_bucket', backdrop: 'static', @@ -360,14 +378,18 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e ).then(function(buckets) { $scope.allBuckets = buckets; }); $scope.add_to_bucket = function() { - var item = new egCore.idl.cbrebi(); - item.bucket($scope.bucket_id); - item.target_biblio_record_entry(recId); - egCore.net.request( - 'open-ils.actor', - 'open-ils.actor.container.item.create', - egCore.auth.token(), 'biblio', item - ).then(function(resp) { + var promises = []; + angular.forEach(recs, function(recId) { + var item = new egCore.idl.cbrebi(); + item.bucket($scope.bucket_id); + item.target_biblio_record_entry(recId); + promises.push(egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.container.item.create', + egCore.auth.token(), 'biblio', item + )); + }); + $q.all(promises).then(function(resp) { $uibModalInstance.close(); }); } @@ -605,7 +627,12 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e $(doc).find('#hold_usr_input').val(barc); $(doc).find('#hold_usr_input').change(); }); - }) + }); + $(doc).find('#select_basket_action').on('change', function() { + if (this.options[this.selectedIndex].value && this.options[this.selectedIndex].value == "add_cart_to_bucket") { + $scope.add_cart_to_record_bucket(); + } + }); } } diff --git a/docs/RELEASE_NOTES_NEXT/OPAC/Batch_Actions.adoc b/docs/RELEASE_NOTES_NEXT/OPAC/Batch_Actions.adoc new file mode 100644 index 0000000000..cba31b6ea3 --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/OPAC/Batch_Actions.adoc @@ -0,0 +1,76 @@ +Batch Actions In the Public Catalog +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The public catalog now displays checkboxes on the bibliographic and +metarecord constituents results pages. Selecting one or more titles +by using the checkboxes will dynamically add those title to the +temporary list, which is now renamed the cart. + +Above the results lists there is now a bar with a select-all checkbox, +a link to the cart management page that also indicates the number of +of titles in the cart, and a link to remove from the cart titles that +are selected on the currently displayed results page. + +The search bar now includes an icon of a cart and displays the number +of titles currently in the cart. Next to that icon is a menu of cart +actions. + +The cart actions available are Place Hold, Print Title Details, +Email Title Details, Add Cart to Saved List, and Clear Cart. In the +web staff client, the cart actions also include Add Cart to Bucket. +When an action is selected from this menu, the user is given an +opportunity to confirm the action and to optionally empty the cart +when the action is complete. The action is applied to all titles +in the cart. + +Clicking on the cart icon brings the user to a page listing the +titles in the cart. From there, the user can select specific records +to request, print, email, add to a list, or remove from the cart. + +The list of actions on the record details page now provides separate +links for adding the title to a cart or to a permanent list. + +The permanent list management page in the public catalog now also +includes batch print and email actions. + +Additional information +++++++++++++++++++++++ +* The checkboxes do not display on the metarecord results page, as + metarecords currently cannot be put into carts or lists. +* The checkboxes are displayed only if Javascript is enabled. However, + users can still add items to the cart and perform batch actions on + the cart and on lists. +* A template `config.tt2` setting, `ctx.max_cart_size`, can be used to + set a soft limit on the number of titles that can be added to the + cart. If this limit is reached, checkboxes to add more records to the + cart are disabled unless existing titles in the cart are removed + first. The default value for this setting is 500. + +Developer notes ++++++++++++++++ + +This patch adds the the public catalog two routes that return JSON +rather than HTML: + +* `GET /eg/opac/api/mylist/add?record=45` +* `GET /eg/opac/api/mylist/delete?record=45` + +The JSON response is a hash containing a mylist key pointing to the list +of bib IDs of contents of the cart. + +The record parameter can be repeated to allow adding or removing +records as an atomic operation. Note that this change also now available +to `/eg/opac/mylist/{add,delete}` + +More generally, this adds a way for EGWeb context loaders to specify that +a response should be emitted as JSON rather than rendering an HTML +page using `Template::Toolkit`. + +Specifically, if the context as munged by the context loader contains +a `json_response` key, the contents of that key will to provide a +JSON reponse. The `json_response_cookie` key, if present, can be used +to set a cookie as part of the response. + +Template Toolkit processing is bypassed entirely when emitting a JSON +response, so the context loader would be entirely reponsible for +localization of strings in the response meant for direct human +consumption. -- 2.43.2