From 221b81a63b299f685e7952e47ad89d2d2ffcd2ff Mon Sep 17 00:00:00 2001 From: Dan Pearl Date: Wed, 14 Nov 2012 12:14:11 -0500 Subject: [PATCH] LP# 1086934 - TPAC: Complete column sorting in some screens (specifically holds, circs, and circs_history) An earlier LP issue #1010277 concerned the halfway implementation of the column sort facility, and was addressed at the time by ripping out any hint of column sort capability, among other cleanup issues. The sorting capability has now been implemented with the following functionality: * Clicking on the appropriate column heads now sorts the contents from "ascending" to "descending" to "no sort". (The "no sort" will restore the original list as presented to the patron.) * The sort indicator (an up or down arrow) is placed to the right of the column head, as appropriate. * The combined "Title/Author" column in the circ screens is now separated into two independently sortable columns (Title and Author). * Title sorting is done with the 'filing' characters (leading "the", "a", "an", and other langugage equivalents) removed. To clarify the behavior for the patron, the leading articles are rendered in a smaller font, so as to keep the main entry prominent. In addition to the filing characters removed for the sort, leading non-alphanumeric characters are ignored in the sort. This commit only affects those screens and columns that suggested column sorting capability. Signed-off-by: Dan Pearl Conflicts: Open-ILS/src/templates/opac/parts/misc_util.tt2 --- .../lib/OpenILS/WWW/EGCatLoader/Account.pm | 29 +++++- Open-ILS/src/templates/opac/css/style.css.tt2 | 6 ++ .../src/templates/opac/myopac/circ_history.tt2 | 114 +++++++++++++++++---- Open-ILS/src/templates/opac/myopac/circs.tt2 | 92 ++++++++++++++--- Open-ILS/src/templates/opac/myopac/holds.tt2 | 103 +++++++++++++------ Open-ILS/src/templates/opac/parts/misc_util.tt2 | 23 +++++ Open-ILS/src/templates/opac/parts/myopac/base.tt2 | 2 +- .../opac/parts/myopac/column_sort_support.tt2 | 38 +++++++ Open-ILS/src/templates/opac/parts/topnav.tt2 | 10 +- 9 files changed, 342 insertions(+), 75 deletions(-) create mode 100644 Open-ILS/src/templates/opac/parts/myopac/column_sort_support.tt2 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 337ee52f31..60fed8f037 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -619,7 +619,14 @@ sub load_myopac_holds { my $hold_handle_result; $hold_handle_result = $self->handle_hold_update($action) if $action; - my $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available, $limit, $offset); + my $holds_object; + if ($self->cgi->param('sort') ne "") { + $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available); + } + else { + $holds_object = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available, $limit, $offset); + } + if($holds_object->{holds}) { $ctx->{holds} = $holds_object->{holds}; } @@ -1143,7 +1150,22 @@ sub load_myopac_circ_history { $ctx->{circ_history_limit} = $limit; $ctx->{circ_history_offset} = $offset; - my $circ_ids = $e->json_query({ + my $circ_ids; + if ($self->cgi->param('sort') ne "") { # Defer limitation to circ_history.tt2 + $circ_ids = $e->json_query({ + select => { + au => [{ + column => 'id', + transform => 'action.usr_visible_circs', + result_field => 'id' + }] + }, + from => 'au', + where => {id => $e->requestor->id} + }); + + } else { + $circ_ids = $e->json_query({ select => { au => [{ column => 'id', @@ -1155,7 +1177,8 @@ sub load_myopac_circ_history { where => {id => $e->requestor->id}, limit => $limit, offset => $offset - }); + }); + } $ctx->{circs} = $self->fetch_user_circs(1, [map { $_->{id} } @$circ_ids]); return Apache2::Const::OK; diff --git a/Open-ILS/src/templates/opac/css/style.css.tt2 b/Open-ILS/src/templates/opac/css/style.css.tt2 index 3da8ea421c..7bd750540f 100644 --- a/Open-ILS/src/templates/opac/css/style.css.tt2 +++ b/Open-ILS/src/templates/opac/css/style.css.tt2 @@ -1481,3 +1481,9 @@ a.preflib_change { padding: 0; vertical-align: middle; } + +.sort_deemphasize { + font-weight: lighter; + font-size: 70%; +} + diff --git a/Open-ILS/src/templates/opac/myopac/circ_history.tt2 b/Open-ILS/src/templates/opac/myopac/circ_history.tt2 index 9b6d78d8ae..d47f583472 100644 --- a/Open-ILS/src/templates/opac/myopac/circ_history.tt2 +++ b/Open-ILS/src/templates/opac/myopac/circ_history.tt2 @@ -1,5 +1,6 @@ [% 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 = "circs" limit = ctx.circ_history_limit; @@ -10,13 +11,25 @@ + [% + # In the sorting case, the size is the size of ALL the circ items. In the non-sorting case, + # the size is simply the size of the chunk passed in. See the TODO below for the still-lingering + # bug. + sort_field = CGI.param('sort'); + IF (sort_field); + no_next = ctx.circs.size - offset <= limit; + ELSE; + no_next = ctx.circs.size < limit; + END; + %] +
[% l('Previously Checked Out Items') %] @@ -24,7 +37,7 @@ [% IF offset == 0 %] class='invisible' [% END %]>[% l('Previous') %] [%# TODO: get total to prevent paging off then end of the list.. %] limit, offset => (offset + limit)}) %]' - [% IF ctx.circs.size < limit %] class='invisible' [% END %] >[% l('Next') %] + [% IF no_next %] class='invisible' [% END %] >[% l('Next') %]
@@ -46,27 +59,91 @@ - - - - - - + + + + + + + - [% FOR circ IN ctx.circs; - attrs = {marc_xml => circ.marc_xml}; - PROCESS get_marc_attrs args=attrs; %] + [%# Copy the ctx.circs into a local array, then add a SORT field + that contains the value to sort on. Since we need the item attrs, + invoke it and save the result in ATTRS. + %] + [% + circ_items = ctx.circs; # Array assignment + + FOR circ IN circ_items; + circ.ATTRS = {marc_xml => circ.marc_xml}; + PROCESS get_marc_attrs args=circ.ATTRS; + + SWITCH sort_field; + + CASE "sort_title"; + circ.SORTING = circ.ATTRS.sort_title; + + CASE "author"; + circ.SORTING = circ.ATTRS.author; + + CASE "checkout"; + circ.SORTING = circ.circ.xact_start; + + CASE "due"; + circ.SORTING = circ.circ.due_date; + + CASE "returned"; + circ.SORTING = circ.circ.checkin_time; + + CASE "barcode"; + circ.SORTING = circ.circ.target_copy.barcode; + + CASE "callnum"; + circ.SORTING = circ.circ.target_copy.call_number.label; + + CASE; + sort_field = ""; + END; # SWITCH + END; #FOR circ + + IF (sort_field != "sort_title"); + deemphasize_class = ""; + ELSE; + deemphasize_class = " class=\"sort_deemphasize\""; + END; + + # Apply sorting to circ_items + IF (sort_field); + circ_items = circ_items.sort("SORTING"); + IF (CGI.param("sort_type") == "desc"); + circ_items = circ_items.reverse; + END; + + # Shorten the circ_items list per offset/limit/cout + hi = offset + limit - 1; + hi = hi > circ_items.max ? circ_items.max : hi; + + circ_items = circ_items.slice(offset, hi); + END; + + # circ_items list is now sorted. Traverse and dump the information. + + FOR circ IN circ_items; %] + - - - - + + + + + + - [% FOR circ IN ctx.circs; - attrs = {marc_xml => circ.marc_xml}; - PROCESS get_marc_attrs args=attrs; %] + [%# Copy the ctx.circs into a local array, then add a SORT field + that contains the value to sort on. Since we need the item attrs, + invoke it and save the result in ATTRS. + %] + [% + circ_items = ctx.circs; # Array assignment + + sort_field = CGI.param('sort'); # unless changed below... + + FOR circ IN circ_items; + circ.ATTRS = {marc_xml => circ.marc_xml}; + PROCESS get_marc_attrs args=circ.ATTRS; + + SWITCH sort_field; + + CASE "sort_title"; + circ.SORTING = circ.ATTRS.sort_title; + + CASE "author"; + circ.SORTING = circ.ATTRS.author; + + CASE "renews"; + circ.SORTING = circ.circ.renewal_remaining; + + CASE "due"; + circ.SORTING = circ.circ.due_date; + + CASE "barcode"; + circ.SORTING = circ.circ.target_copy.barcode; + + CASE "callnum"; + circ.SORTING = circ.circ.target_copy.call_number.label; + + CASE; + sort_field = ""; + END; # SWITCH + END; #FOR circ + + IF (sort_field != "sort_title"); + deemphasize_class = ""; + ELSE; + deemphasize_class = " class=\"sort_deemphasize\""; + END; + + # Apply sorting to circ_items + IF (sort_field); + circ_items = circ_items.sort("SORTING"); + IF (CGI.param("sort_type") == "desc"); + circ_items = circ_items.reverse; + END; + END; + + # circ_items list is now sorted. Traverse and dump the information. + + FOR circ IN circ_items; %] - + - [% END; + [% END; # FOR + END %]
[% l('Title / Author') %][% l('Checkout Date') %][% l('Due Date') %][% l('Date Returned') %][% l('Barcode') %][% l('Call Number') %][% sort_head("sort_title", "Title") %][% sort_head("author", "Author") %][% sort_head("checkout", "Checkout Date") %][% sort_head("due", "Due Date") %][% sort_head("returned", "Date Returned") %][% sort_head("barcode", "Barcode") %][% sort_head("callnum", "Call Number") %]
- [% attrs.title | html %] - [% IF attrs.author %] / + + [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %] + [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %] + [% attrs.author | html %] - [% END %] + {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')}, + 1 + ) %]">[% circ.ATTRS.author | html %] [% date.format(ctx.parse_datetime(circ.circ.xact_start),DATE_FORMAT); %] @@ -75,8 +152,7 @@ [% date.format(ctx.parse_datetime(circ.circ.due_date),DATE_FORMAT); %] - [% - IF circ.circ.checkin_time; + [% IF circ.circ.checkin_time; date.format(ctx.parse_datetime(circ.circ.checkin_time),DATE_FORMAT); ELSE; %] * diff --git a/Open-ILS/src/templates/opac/myopac/circs.tt2 b/Open-ILS/src/templates/opac/myopac/circs.tt2 index 8ff44ac7fe..77d3a32aee 100644 --- a/Open-ILS/src/templates/opac/myopac/circs.tt2 +++ b/Open-ILS/src/templates/opac/myopac/circs.tt2 @@ -1,5 +1,6 @@ [% 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 = "circs" %] @@ -60,32 +61,88 @@ onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'circ' && !inputs[i].disabled) inputs[i].checked = this.checked;}" /> - [% l('Title / Author') %][% l('Renewals Left') %][% l('Due Date') %][% l('Barcode') %][% l('Call number') %][% sort_head("sort_title", "Title") %][% sort_head("author", "Author") %][% sort_head("renews", "Renewals Left") %][% sort_head("due", "Due Date") %][% sort_head("barcode", "Barcode") %][% sort_head("callnum", "Call number") %]
+ [% attrs.title | html %] - [% IF attrs.author %] / + circ.circ.target_copy.call_number.record.id, {}, 1) %]" + name="[% l('Catalog record') %]"> + [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %] + [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %] + [% attrs.author | html %] - [% END %] + {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')}, + 1 + ) %]">[% circ.ATTRS.author | html %] [% circ.circ.renewal_remaining %] @@ -113,7 +170,8 @@
diff --git a/Open-ILS/src/templates/opac/myopac/holds.tt2 b/Open-ILS/src/templates/opac/myopac/holds.tt2 index 5b5a36ab6d..cb8924196c 100644 --- a/Open-ILS/src/templates/opac/myopac/holds.tt2 +++ b/Open-ILS/src/templates/opac/myopac/holds.tt2 @@ -1,6 +1,7 @@ [% 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 = "holds"; limit = ctx.holds_limit; @@ -14,7 +15,7 @@ [% l("Items on Hold") %] @@ -46,9 +47,6 @@ [% IF count <= limit + offset %] class='invisible' [% END %] >[% l('Next') %] - - [% l('Export List') %] -
@@ -91,30 +89,21 @@ [% l("Show only available") %] [% END %]     [% l("holds") %] - [% IF ctx.holds.size < 1 %]
[% l('No holds found.') %]
[% ELSE %] -
- -
- - - + + + @@ -123,9 +112,59 @@ - [% FOR hold IN ctx.holds; - attrs = {marc_xml => hold.marc_xml}; - PROCESS get_marc_attrs args=attrs; + + [%# Copy the ctx.holds into a local array, then add a SORT field + that contains the value to sort on. Since we need the item attrs, + invoke it and save the result in ATTRS. + %] + [% + hold_items = ctx.holds; + + sort_field = CGI.param('sort'); + + FOR hold IN hold_items; + hold.ATTRS = {marc_xml => hold.marc_xml}; + PROCESS get_marc_attrs args=hold.ATTRS; + + SWITCH sort_field; + + CASE "sort_title"; + hold.SORTING = hold.ATTRS.sort_title; + + CASE "author"; + hold.SORTING = hold.ATTRS.author; + + CASE "format"; + hold.SORTING = hold.ATTRS.format_label; + + CASE; + sort_field = ""; + END; # SWITCH + END; #FOR hold + + IF (sort_field != "sort_title"); + deemphasize_class = ""; + ELSE; + deemphasize_class = " class=\"sort_deemphasize\""; + END; + + # Apply sorting to hold_items + IF (sort_field != ""); + hold_items = hold_items.sort("SORTING"); + IF (CGI.param("sort_type") == "desc"); + hold_items = hold_items.reverse; + END; + + # Shorten the hold_items list per offset/limit/count + hi = offset + limit - 1; + hi = hi > hold_items.max ? hold_items.max : hi; + + hold_items = hold_items.slice(offset, hi); + END; + + # hold_items list is now sorted. Traverse and dump the information. + + FOR hold IN hold_items; ahr = hold.hold.hold %] @@ -134,26 +173,30 @@ diff --git a/Open-ILS/src/templates/opac/parts/misc_util.tt2 b/Open-ILS/src/templates/opac/parts/misc_util.tt2 index 9e2a054d9d..d1b5e3da54 100644 --- a/Open-ILS/src/templates/opac/parts/misc_util.tt2 +++ b/Open-ILS/src/templates/opac/parts/misc_util.tt2 @@ -120,6 +120,7 @@ titresults = xml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b" or @code="n" or @code="p"]'); titresults_content = []; FOR sub IN titresults; titresults_content.push(sub.textContent); END; + args.title = titresults_content.join(" "); # Avoid ugly trailing syntax on brief titles args.title = args.title | replace('[:;/]$', ''); @@ -132,6 +133,28 @@ args.titles.push(title.primary.value); END; args.title_extended = (args.titles.size) ? args.titles.0 : ''; + # Create a version of the title designed for sorted displays. + args.sort_title = args.title | upper; + + # If the title has a "non-filing characters" + # (to logically remove leading "The " for example) + # chop the title. Otherwise, chop until the first alphanumeric. + # BTW: Template Toolkit folds 1-element arrays to scalars! + title_node = xml.findnodes('//*[@tag="245"]'); + + args.nonfiling_characters = title_node.findvalue('@ind2'); + + IF (args.nonfiling_characters > 0); + args.sort_title = args.sort_title.substr(args.nonfiling_characters); + ELSE; + args.sort_title = args.sort_title.replace('^[^A-Z0-9]*',''); + END; + + # Provide correct spacing between the subfields + titsubs = xml.findnodes('//*[@tag="245"]/*[@code]'); + titsubs_content = []; + FOR sub IN titsubs; titsubs_content.push(sub.textContent); END; + args.title_extended = titsubs_content.join(" "); args.pubplaces = []; FOR sub IN xml.findnodes('//*[@tag="260"]/*[@code="a"]'); diff --git a/Open-ILS/src/templates/opac/parts/myopac/base.tt2 b/Open-ILS/src/templates/opac/parts/myopac/base.tt2 index a0be906792..2dc068da0f 100644 --- a/Open-ILS/src/templates/opac/parts/myopac/base.tt2 +++ b/Open-ILS/src/templates/opac/parts/myopac/base.tt2 @@ -23,7 +23,7 @@ ELSE; cls_which = "acct-tab-off"; END -%] - [% page.name; %] [% END %] diff --git a/Open-ILS/src/templates/opac/parts/myopac/column_sort_support.tt2 b/Open-ILS/src/templates/opac/parts/myopac/column_sort_support.tt2 new file mode 100644 index 0000000000..eb2bc497d0 --- /dev/null +++ b/Open-ILS/src/templates/opac/parts/myopac/column_sort_support.tt2 @@ -0,0 +1,38 @@ +[%# Produce a URL for a given field that cycles for sorting from + "nothing" to "ascending" to "descending" then back to "nothing". +%] +[% MACRO sort_url(field) + IF (CGI.param('sort') == field); + SWITCH CGI.param('sort_type'); + CASE "asc"; + mkurl('',{sort=>field, sort_type=>'desc'},1); + CASE "desc"; + mkurl('',{},1); + CASE; + mkurl('',{sort=>field, sort_type=>'asc'}, 1); + END; + ELSE; + mkurl('',{sort=>field, sort_type=>'asc'}, 1); + END; +%] +[%# SET click_sort = l("click to sort") %] +[%# SET click_sort = "title=\"$click_sort\"" %] + +[%# Produce arrows to indicate the sorting status of the column %] +[% MACRO sort_indicator(field) + IF (CGI.param('sort') == field); + SWITCH CGI.param('sort_type'); + CASE "asc"; +" "; + CASE "desc"; +" "; + END; + END; +%] + +[%# Column headers for sortable columns %] +[% MACRO sort_head(field, field_label) + BLOCK %] +[% l(field_label) %][%- sort_indicator(field) %] +[% END +%] diff --git a/Open-ILS/src/templates/opac/parts/topnav.tt2 b/Open-ILS/src/templates/opac/parts/topnav.tt2 index 832205d13c..ce78a1d24c 100644 --- a/Open-ILS/src/templates/opac/parts/topnav.tt2 +++ b/Open-ILS/src/templates/opac/parts/topnav.tt2 @@ -19,7 +19,7 @@ [% l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %] | - [% l('My Account') %]
[% l('Title') %][% l('Author') %][% l('Format') %][% sort_head("sort_title", "Title") %][% sort_head("author", "Author") %][% sort_head("format", "Format") %] [% l('Pickup Location') %] [% l('Activate') %] [% l('Cancel if not filled by') %]
- [% - title = attrs.title; - IF ahr.hold_type == 'P'; - title = l('[_1] ([_2])', title, hold.hold.part.label); - END; - %] - [% title | html %] + [% title = hold.ATTRS.title; + IF ahr.hold_type == 'P'; + title = l('[_1] ([_2])', title, hold.hold.part.label); + END; %] + + + [%- title.substr(0,hold.ATTRS.nonfiling_characters) | html %] + [%- title.substr(hold.ATTRS.nonfiling_characters) | html %]
[% attrs.author | html %] + {qtype => 'author', query => hold.ATTRS.author.replace('[,\.:;]', '')}, + 1 + ) %]">[% hold.ATTRS.author | html %]
- [% IF attrs.format_icon %] - [% attrs.format_label | html %] + [% IF hold.ATTRS.format_icon %] + [% hold.ATTRS.format_label | html %] [% END %]