LP# 1086934 - TPAC: Complete column sorting in some screens
authorDan Pearl <dpearl@cwmars.org>
Wed, 14 Nov 2012 17:14:11 +0000 (12:14 -0500)
committerJason Stephenson <jstephenson@mvlc.org>
Fri, 1 Feb 2013 14:59:44 +0000 (09:59 -0500)
(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 <dpearl@cwmars.org>
Conflicts:

Open-ILS/src/templates/opac/parts/misc_util.tt2

Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/templates/opac/css/style.css.tt2
Open-ILS/src/templates/opac/myopac/circ_history.tt2
Open-ILS/src/templates/opac/myopac/circs.tt2
Open-ILS/src/templates/opac/myopac/holds.tt2
Open-ILS/src/templates/opac/parts/misc_util.tt2
Open-ILS/src/templates/opac/parts/myopac/base.tt2
Open-ILS/src/templates/opac/parts/myopac/column_sort_support.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/topnav.tt2

index 337ee52..60fed8f 100644 (file)
@@ -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;
index 3da8ea4..7bd7505 100644 (file)
@@ -1481,3 +1481,9 @@ a.preflib_change {
     padding: 0;
     vertical-align: middle;
 }
+
+.sort_deemphasize {
+    font-weight: lighter;
+    font-size: 70%;
+}
+
index 9b6d78d..d47f583 100644 (file)
@@ -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;
     
     <div id="acct_checked_tabs">
         <div class="align">
-            <a href='[% mkurl('circs') %]'>[% l("Current Items Checked Out") %]</a>
+            <a href='[% mkurl('circs',{},1) %]'>[% l("Current Items Checked Out") %]</a>
         </div>
         <div class="align selected">
             <a href="#">[% l("Check Out History") %]</a>
         </div>
     </div>
 
+    [% 
+    # 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;
+    %]
+
     <div class="header_middle">
         <span class="float-left">[% l('Previously Checked Out Items') %]</span>
         <span class='float-left' style='padding-left: 10px;'>
@@ -24,7 +37,7 @@
                 [% IF offset == 0 %] class='invisible' [% END %]><span class="nav_arrow_fix">&#9668;</span>[% l('Previous') %]</a>
             [%# TODO: get total to prevent paging off then end of the list.. %]
             <a href='[% mkurl('circ_history', {limit => limit, offset => (offset + limit)}) %]'
-               [% IF ctx.circs.size < limit %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">&#9658;</span></a>
+               [% IF no_next %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">&#9658;</span></a>
         </span>
         <span class="float-left">
             <form action="[% mkurl(ctx.opac_root _ '/myopac/circ_history/export') %]" method="POST">
         <table id="acct_checked_main_header">
             <thead>
                 <tr>
-                    <th>[% l('Title / Author') %]</th>
-                    <th>[% l('Checkout Date') %]</th>
-                    <th>[% l('Due Date') %]</th>
-                    <th>[% l('Date Returned') %]</th>
-                    <th>[% l('Barcode') %]</th>
-                    <th>[% l('Call Number') %]</th>
+                    <th>[% sort_head("sort_title", "Title") %]</th>
+                    <th>[% sort_head("author", "Author") %]</th>
+                    <th>[% sort_head("checkout", "Checkout Date") %]</th>
+                    <th>[% sort_head("due", "Due Date") %]</th>
+                    <th>[% sort_head("returned", "Date Returned") %]</th>
+                    <th>[% sort_head("barcode", "Barcode") %]</th>
+                    <th>[% sort_head("callnum", "Call Number") %]</th>
                 </tr>
             </thead>
             <tbody>
-                [% 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; %]
                     <tr>
                         <td>
-                            <a href="[% mkurl(ctx.opac_root _ '/record/' _ circ.circ.target_copy.call_number.record.id) %]" 
-                                name="[% l('Catalog record') %]">[% attrs.title | html %]</a>
-                            [% IF attrs.author %] /
+                            <a href="[% mkurl(ctx.opac_root _ '/record/' _ 
+                                circ.circ.target_copy.call_number.record.id, {}, 1) %]"
+                                name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+                                [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %]</span>
+                                [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %]</a>
+                        </td>
+                        <td>
                             <a href="[% mkurl(ctx.opac_root _ '/results',
-                                {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
-                            )%]">[% attrs.author | html %]</a>
-                            [% END %]
+                                {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')},
+                                1
+                            ) %]">[% circ.ATTRS.author | html %]</a>
                         </td>
                         <td>
                             [% date.format(ctx.parse_datetime(circ.circ.xact_start),DATE_FORMAT); %]
                             [% date.format(ctx.parse_datetime(circ.circ.due_date),DATE_FORMAT); %]
                         </td>
                         <td>
-                            [% 
-                                IF circ.circ.checkin_time; 
+                            [% IF circ.circ.checkin_time; 
                                     date.format(ctx.parse_datetime(circ.circ.checkin_time),DATE_FORMAT); 
                                 ELSE; %]
                                 <span style='color:blue;'>*</span><!-- meh -->
index 8ff44ac..77d3a32 100644 (file)
@@ -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"  %]
 <div id='myopac_checked_div'>
@@ -9,7 +10,7 @@
             <a href="#">[% l("Current Items Checked Out") %]</a>
         </div>
         <div class="align">
-            <a href="[% mkurl('circ_history') %]">[% l("Check Out History") %]</a>
+            <a href="[% mkurl('circ_history',{},1) %]">[% l("Check Out History") %]</a>
         </div>
     </div>
 
                         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;}"
                     />
                 </th>
-                <th>[% l('Title / Author') %]</th>
-                <th>[% l('Renewals Left') %]</th>
-                <th>[% l('Due Date') %]</th>
-                <th>[% l('Barcode') %]</th>
-                <th>[% l('Call number') %]</th>
+                <th>[% sort_head("sort_title", "Title") %]</th>
+                <th>[% sort_head("author", "Author") %]</th>
+                <th>[% sort_head("renews", "Renewals Left") %]</th>
+                <th>[% sort_head("due", "Due Date") %]</th>
+                <th>[% sort_head("barcode", "Barcode") %]</th>
+                <th>[% sort_head("callnum", "Call number") %]</th>
             </tr>
             </thead>
             <tbody>
-                    [% 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; %]
                     <tr>
                         <td class="checkbox_column" valign="top">
                             <input type="checkbox" name="circ"
                                 [% IF circ.circ.renewal_remaining < 1 %] disabled="disabled" [% END %]
                                 value="[% circ.circ.id %]" />
                         </td>
-                        <td name="author">
+                        <td name="title">
                             <a href="[% mkurl(ctx.opac_root _ '/record/' _ 
-                                circ.circ.target_copy.call_number.record.id) %]"
-                                name="[% l('Catalog record') %]">[% attrs.title | html %]</a>
-                            [% IF attrs.author %] /
+                                circ.circ.target_copy.call_number.record.id, {}, 1) %]"
+                                name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+                                [%- circ.ATTRS.title.substr(0,circ.ATTRS.nonfiling_characters) | html %]</span>
+                                [%- circ.ATTRS.title.substr(circ.ATTRS.nonfiling_characters) | html %]</a>
+                        </td>
+                        <td name="author">
                             <a href="[% mkurl(ctx.opac_root _ '/results',
-                                {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
-                            ) %]">[% attrs.author | html %]</a>
-                            [% END %]
+                                {qtype => 'author', query => circ.ATTRS.author.replace('[,\.:;]', '')},
+                                1
+                            ) %]">[% circ.ATTRS.author | html %]</a>
                         </td>
                         <td name="renewals" align="center">
                             [% circ.circ.renewal_remaining %]
                             </span>
                         </td>
                     </tr>
-                    [%  END;
+                    [%  END; # FOR
+
                     END %]
                 </table>
             </div>
index 5b5a36a..cb89241 100644 (file)
@@ -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 @@
             <a href='#'>[% l("Items on Hold") %]</a>
         </div>
         <div class="align">
-            <a href='[% mkurl('hold_history', {}, ['limit','offset','available']) %]'>[% l("Holds History") %]</a>
+            <a href='[% mkurl('hold_history', {}, ['limit','offset','available','sort','sort_type']) %]'>[% l("Holds History") %]</a>
         </div>
     </div>
 
@@ -46,9 +47,6 @@
                [% IF count <= limit + offset %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">&#9658;</span></a>
         </span>
 
-        <span style="float:right;">
-            <a class="hide_me" href="#">[% l('Export List') %]</a>
-        </span>
     </div>
     <div class="clear-both"></div>
     <div id='holds_main'>
                     <a href="[% mkurl('holds',{available => 1},['limit','offset']) %]">[% l("Show only available") %]</a>
                     [% END %] &nbsp; &nbsp;
                     [% l("holds") %]
-                    <select class="hide_me" id="holds_sort">
-                        <option value="">[% l('-- Sort By --') %]</option>
-                        <option value="title">[% l('Title') %]</option>
-                        <option value="pickup">[% l('PickUp Location') %]</option>
-                        <option value="status">[% l('Status') %]</option>
-                    </select>
                 </td>
             </tr>
         </table>
         [% IF ctx.holds.size < 1 %]
         <div class="warning_box">[% l('No holds found.') %]</div>
         [% ELSE %]
-        <div class="hide_me">
-            <select id="hold_pickup_lib_temp" name="hold_pickup_lib_sel" class="hide_me"></select>
-        </div>
         <table id="acct_holds_main_header" cellpadding='0' cellspacing='0' border='0'>
             <thead>
             <tr>
                 <th align="center">
                     <input type="checkbox" onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'hold_id' && !inputs[i].disabled) inputs[i].checked = this.checked;}"/>
                 </th>
-                <th>[% l('Title') %]</th>
-                <th>[% l('Author') %]</th>
-                <th>[% l('Format') %]</th>
+                <th>[% sort_head("sort_title", "Title") %]</th>
+                <th>[% sort_head("author", "Author") %]</th>
+                <th>[% sort_head("format", "Format") %]</th>
                 <th>[% l('Pickup Location') %]</th>
                 <th>[% l('Activate') %]</th>
                 <th>[% l('Cancel if not filled by') %]</th>
             </tr>
             </thead>
             <tbody id="holds_temp_parent">
-                [% 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 %]
                 <tr id="acct_holds_temp" name="acct_holds_temp"
                     class="acct_holds_temp[% ahr.frozen == 't' ? ' inactive-hold' : '' %]">
                     </td>
                     <td>
                         <div>
-                            [% 
-                                title = attrs.title;
-                                IF ahr.hold_type == 'P';
-                                    title = l('[_1] ([_2])', title, hold.hold.part.label);
-                                END;
-                            %]
-                            <a href="[% mkurl(ctx.opac_root _ '/record/' _ hold.hold.bre_id) %]">[% title | html %]</a>
+                            [% title = hold.ATTRS.title;
+                            IF ahr.hold_type == 'P';
+                               title = l('[_1] ([_2])', title, hold.hold.part.label);
+                            END; %]
+
+                            <a href="[% mkurl(ctx.opac_root _ '/record/' _ 
+                                hold.hold.bre_id, {}, 1) %]"
+                                name="[% l('Catalog record') %]"><span[%- deemphasize_class -%]>
+                                [%- title.substr(0,hold.ATTRS.nonfiling_characters) | html %]</span>
+                                [%- title.substr(hold.ATTRS.nonfiling_characters)   | html %]</a>
                         </div>
                     </td>
                     <td>
                         <div>
                             <a href="[% mkurl(ctx.opac_root _ '/results',
-                                {qtype => 'author', query => attrs.author.replace('[,\.:;]', '')}
-                            ) %]">[% attrs.author | html %]</a>
+                                {qtype => 'author', query => hold.ATTRS.author.replace('[,\.:;]', '')},
+                                1
+                            ) %]">[% hold.ATTRS.author | html %]</a>
                         </div>
                     </td>
                     <td>
                         <div class="format_icon">
-                            [% IF attrs.format_icon %]
-                            <img title="[% attrs.format_label | html %]" alt="[% attrs.format_label | html %]" src="[% attrs.format_icon %]" />
+                            [% IF hold.ATTRS.format_icon %]
+                            <img title="[% hold.ATTRS.format_label | html %]" alt="[% hold.ATTRS.format_label | html %]" src="[% hold.ATTRS.format_icon %]" />
                             [% END %]
                         </div>
                     </td>
index 9e2a054..d1b5e3d 100644 (file)
         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('[:;/]$', '');
             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"]');
index a0be906..2dc068d 100644 (file)
@@ -23,7 +23,7 @@
                     ELSE;
                         cls_which = "acct-tab-off";
                     END -%]
-                <a href="[% mkurl(ctx.opac_root _ '/myopac/' _ page.url, {}, ['bbid', 'offset', 'limit']) %]"
+                <a href="[% mkurl(ctx.opac_root _ '/myopac/' _ page.url, {}, ['bbid', 'offset', 'limit','sort','sort_type']) %]"
                     class="[% cls_which %]">[% page.name; %]</a>
                 [% END %]
             </div>
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 (file)
index 0000000..eb2bc49
--- /dev/null
@@ -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";
+"&nbsp;<span class=\"column_sort_arrow\">&#9650;</span>";
+                CASE "desc";
+"&nbsp;<span class=\"column_sort_arrow\">&#9660;</span>";
+                END;
+        END;
+%]
+
+[%# Column headers for sortable columns %]
+[% MACRO sort_head(field, field_label) 
+   BLOCK %]
+<a href="[% sort_url(field) %]" [% click_sort %]>[% l(field_label) %]</a>[%- sort_indicator(field) %]
+[% END
+%]
index 832205d..ce78a1d 100644 (file)
@@ -19,7 +19,7 @@
                     [%  l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %]
                 </span>
                 <span class="dash_divider">|</span>
-                <a href="[% mkurl(ctx.opac_root _ '/myopac/main') %]" 
+                <a href="[% mkurl(ctx.opac_root _ '/myopac/main', {}, ['sort','sort_type']) %]" 
                     class="opac-button">[% l('My Account') %]</a>
 
                 <a href="[% mkurl(ctx.opac_root _ '/logout', {}, 1) %]"
             </div>
             <div id="dashboard">
                 <span class="dash-align">
-                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/circs')
+                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/circs', {}, ['sort','sort_type'])
                         %]"><span id="dash_checked">[% ctx.user_stats.checkouts.total_out
                         %]</span> [% l("Checked Out") %]</a>
                 </span>
                 <span class="dash_divider">|</span>
                 <span class="dash-align">
-                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds', {}, ['available'])
+                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds', {}, ['sort','sort_type'])
                         %]"><span id="dash_holds">[% ctx.user_stats.holds.total
                         %]</span> [% l("On Hold") %]</a>
                 </span>
                 <span class="dash_divider">|</span>
                 <span class="dash-align">
                     <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/holds',
-                        {available => 1}) %]"><span id="dash_pickup">[%
+                        {available => 1}, ['sort','sort_type']) %]"><span id="dash_pickup">[%
                         ctx.user_stats.holds.ready %]</span> [% l("Ready for Pickup") %]</a>
                 </span>
                 <span class="dash_divider">|</span>
                 <span class="dash-align">
-                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/main')
+                    <a class="dash-link" href="[% mkurl(ctx.opac_root _ '/myopac/main', {}, ['sort','sort_type'])
                         %]"><span id="dash_fines">[% money(ctx.user_stats.fines.balance_owed)
                         %]</span> [% l("Fines") %]</a>
                 </span>