Koha/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt
Owen Leonard 87e53a3352 Bug 27725: Use JavaScript to set history state during patron search
This patch modifies the patron search page so that clicks on the "Browse
by last name" letters add an entry to the browser history, allowing the
user to click back and forth from results while preserving their search.

To test, apply the patch and go to Patrons in the staff interface.

- Click one of the "Browse by last name" letters.
- The table of search results should load the correct data.
- In the browser's location bar you should see a query string added to
  the URL, e.g. /cgi-bin/koha/members/members-home.pl?firstletter=Q
- Click another letter.
- Click the back button. You should be returned to the search results
  for your first letter choice.
- Clicking the forward button should work correctly as well.
- Other patron searches (header search, sidebar search) should continue
  to work as expected.

EDIT: Clear single-letter querystring history item if only one result
was returned.

EDIT 2: Fixed handling of history state changes so that forward and back
buttons work correctly.

Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

Bug 27725: (follow-up) Remove code for clearing search results

This patch removes code which cleared search results if there was no
first-letter search. It was unnecessary for the letter search
functionality and made all other searches fail.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2021-10-07 11:18:29 +02:00

589 lines
26 KiB
Text

[% USE raw %]
[% USE Asset %]
[% USE Koha %]
[% USE TablesSettings %]
[% USE Branches %]
[% USE Categories %]
[% SET footerjs = 1 %]
[% PROCESS 'patronfields.inc' %]
[% INCLUDE 'doc-head-open.inc' %]
<title>Patrons[% IF ( searching ) %] &rsaquo; Search results[% END %] &rsaquo; Koha</title>
[% INCLUDE 'doc-head-close.inc' %]
</head>
<body id="pat_member" class="pat">
[% INCLUDE 'header.inc' %]
[% INCLUDE 'patron-search.inc' %]
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
<ol>
<li>
<a href="/cgi-bin/koha/mainpage.pl">Home</a>
</li>
[% IF ( searching ) %]
<li>
<a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>
</li>
<li>
<a href="#" aria-current="page">
Search results
</a>
</li>
[% ELSE %]
<li>
<a href="#" aria-current="page">
Patrons
</a>
</li>
[% END %]
</ol>
</nav>
<div class="main container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-push-2">
<main>
[% IF CAN_user_tools_manage_patron_lists %]
<div id="patron_list_dialog" class="dialog alert">
Added <span class="patrons-length"></span> patrons to <a></a>.
</div>
[% END %]
[% INCLUDE 'patron-toolbar.inc' %]
[% INCLUDE 'noadd-warnings.inc' %]
<div class="browse">
Browse by last name:
[% FOREACH letter IN alphabet.split(' ') %]
<a href="#" class="filterByLetter">[% letter | html %]</a>
[% END %]
</div>
[% IF CAN_user_borrowers_edit_borrowers && pending_borrower_modifications %]
<div class="pending-info" id="patron_updates_pending">
<a href="/cgi-bin/koha/members/members-update.pl">Patrons requesting modifications</a>:
<span class="number_box"><a href="/cgi-bin/koha/members/members-update.pl">[% pending_borrower_modifications | html %]</a></span>
</div>
[% END %]
<div id="searchresults">
<h3>Patrons found for: <span id="searchpattern">[% IF searchmember %] for '[% searchmember | html %]'[% END %]</span></h3>
[% IF CAN_user_tools_manage_patron_lists || CAN_user_borrowers_edit_borrowers %]
<div class="searchheader fh-fixedHeader" id="searchheader">
<div>
<a href="#" id="select_all"><i class="fa fa-check"></i> Select all</a>
|
<a href="#" id="clear_all"><i class="fa fa-remove"></i> Clear all</a>
[% IF CAN_user_tools_manage_patron_lists %]
|
<span>
<label for="add_to_patron_list">Add selected patrons to:</label>
<select id="add_to_patron_list" name="add_to_patron_list">
<option value=""></option>
[% IF patron_lists %]
<optgroup label="Patron lists:">
[% FOREACH pl IN patron_lists %]
<option value="[% pl.patron_list_id | html %]">[% pl.name | html %]</option>
[% END %]
</optgroup>
[% END %]
<option value="new">[ New list ]</option>
</select>
<input type="text" id="new_patron_list" name="new_patron_list" id="new_patron_list" />
<input id="add_to_patron_list_submit" type="submit" class="submit" value="Save">
</span>
[% END %]
[% IF CAN_user_tools_manage_patron_lists && CAN_user_borrowers_edit_borrowers %]
|
[% END %]
[% IF CAN_user_borrowers_edit_borrowers %]
<button id="merge-patrons" type="submit">Merge selected patrons</button>
[% END %]
</div>
</div>
[% END %]
<table id="memberresultst">
<thead>
<tr>
[% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
<th>&nbsp;</th>
[% END %]
<th>Card</th>
<th>Name</th>
<th>Date of birth</th>
<th>Category</th>
<th>Library</th>
<th>Expires on</th>
<th>OD/Checkouts</th>
<th>Fines</th>
<th>Circ note</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</main>
</div> <!-- /.col-sm-10.col-sm-push-2 -->
<div class="col-sm-2 col-sm-pull-10">
<aside>
<form method="get" id="searchform">
<input type="hidden" id="firstletter_filter" value="" />
<fieldset class="brief">
<h3>Search patrons</h3>
<ol>
<li>
<label for="searchmember_filter">Search for:</label>
<input type="text" id="searchmember_filter" value="[% searchmember | html %]"/>
</li>
<li>
<label for="searchfieldstype_filter">Search field:</label>
<select name="searchfieldstype" id="searchfieldstype_filter">
[% pref_fields = Koha.Preference('DefaultPatronSearchFields').split(',') %]
[% default_fields = [ 'standard', 'surname', 'cardnumber', 'email', 'borrowernumber', 'userid', 'phone', 'address', 'dateofbirth', 'sort1', 'sort2' ] %]
[% search_options = default_fields.merge(pref_fields).unique %]
[% FOREACH s_o IN search_options %]
[% display_name = PROCESS patron_fields name=s_o %]
[% NEXT IF !display_name %]
[% IF searchfieldstype == s_o %]
<option selected="selected" value=[% s_o | html %]>[% display_name | $raw %]</option>
[% ELSE %]
<option value=[% s_o | html %]>[% display_name | $raw %]</option>
[% END %]
[% END %]
</select>
</li>
<li>
<label for="searchtype_filter">Search type:</label>
<select name="searchtype" id="searchtype_filter">
[% IF searchtype == "start_with" %]
<option value='start_with' selected="selected">Starts with</option>
<option value="contain">Contains</option>
[% ELSE %]
<option value='start_with'>Starts with</option>
<option value="contain" selected="selected">Contains</option>
[% END %]
</select>
</li>
<li>
<label for="categorycode_filter">Patron category:</label>
[% SET categories = Categories.all() %]
<select id="categorycode_filter">
<option value="">Any</option>
[% FOREACH cat IN categories %]
[% IF cat.categorycode == categorycode_filter %]
<option selected="selected" value="[% cat.categorycode | html %]">[% cat.description | html %]</option>
[% ELSE %]
<option value="[% cat.categorycode | html %]">[% cat.description | html %]</option>
[% END %]
[% END %]
</select>
</li>
<li>
<label for="branchcode_filter">Library:</label>
[% SET branches = Branches.all( selected => branchcode_filter, only_from_group => 1 ) %]
<select id="branchcode_filter">
[% IF branches.size != 1 %]
<option value="">Any</option>
[% END %]
[% PROCESS options_for_libraries libraries => branches %]
</select>
</li>
</ol>
<fieldset class="action">
<input type="submit" value="Search" />
<input type="button" value="Clear" id="clear_search" />
</fieldset>
</fieldset>
</form>
</aside>
</div> <!-- /.col-sm-2.col-sm-pull-10 -->
</div> <!-- /.row -->
[% MACRO jsinclude BLOCK %]
[% INCLUDE 'datatables.inc' %]
[% INCLUDE 'columns_settings.inc' %]
[% INCLUDE 'str/members-menu.inc' %]
[% Asset.js("lib/hc-sticky.js") | $raw %]
[% Asset.js("js/members-menu.js") | $raw %]
<script>
var singleBranchMode = '[% singleBranchMode | html %]';
var dtMemberResults;
var search = 1;
/* popstate event triggered by forward and back button. Need to refresh search */
window.addEventListener('popstate', (event) => {
getSearchByLocation( false );
});
$(document).ready(function() {
$('#merge-patrons').prop('disabled', true);
$('#memberresultst').on('change', 'input.selection', function() {
if ( $('.selection:checked').length > 1 ) {
$('#merge-patrons').prop('disabled', false);
} else {
$('#merge-patrons').prop('disabled', true);
}
});
$('#merge-patrons').on('click', function() {
var merge_patrons_url = 'merge-patrons.pl?' + $('.selection:checked')
.map(function() {
return "id=" + $(this).val()
}).get().join('&');
window.location.href = merge_patrons_url;
});
$('#add_to_patron_list_submit').prop('disabled', true);
$('#new_patron_list').hide();
$('#add_to_patron_list').change(function() {
var value = $('#add_to_patron_list').val();
if ( value == 'new' ) {
$('#new_patron_list').val('')
$('#new_patron_list').show();
$('#new_patron_list').focus();
} else if ( value ) {
$('#new_patron_list').hide();
$('#add_to_patron_list_submit').prop('disabled', false);
} else {
$('#new_patron_list').hide();
$('#add_to_patron_list_submit').prop('disabled', true);
}
});
$('#new_patron_list').on('input', function() {
if ( $('#new_patron_list').val() ) {
$('#add_to_patron_list_submit').prop('disabled', false);
} else {
$('#add_to_patron_list_submit').prop('disabled', true);
}
});
$("#add_to_patron_list_submit").on('click', function(e){
if ( $('#add_to_patron_list').val() == 'new' ) {
if ( $('#new_patron_list').val() ) {
$("#add_to_patron_list option").each(function() {
if ( $(this).text() == $('#new_patron_list').val() ) {
alert( _("You already have a list with that name!") );
return false;
}
});
} else {
alert( _("You must give your new patron list a name!") );
return false;
}
}
if ( $("#memberresultst input:checkbox:checked").length == 0 ) {
alert( _("You have not selected any patrons to add to a list!") );
return false;
}
var borrowernumbers = [];
$("#memberresultst").find("input:checkbox:checked").each(function(){
borrowernumbers.push($(this).val());
});
var data = {
add_to_patron_list: $("#add_to_patron_list").val(),
new_patron_list: $("#new_patron_list").val(),
borrowernumbers: borrowernumbers
};
$.ajax({
data: data,
type: 'POST',
url: '/cgi-bin/koha/svc/members/add_to_list',
success: function(data) {
$("#patron_list_dialog").show();
$("#patron_list_dialog > span.patrons-length").html(data.patrons_added_to_list);
$("#patron_list_dialog > a").attr("href", "/cgi-bin/koha/patron_lists/list.pl?patron_list_id=" + data.patron_list.patron_list_id);
$("#patron_list_dialog > a").html(data.patron_list.name);
if ( $('#add_to_patron_list').val() == 'new' ) {
var new_patron_list_added = $("<option>", {
value: data.patron_list.patron_list_id,
text: data.patron_list.name
});
$("#add_to_patron_list optgroup").append(new_patron_list_added);
$("#add_to_patron_list").val(data.patron_list.patron_list_id);
$("#new_patron_list").val('');
$('#add_to_patron_list').change();
}
},
error: function() {
alert("an error occurred");
}
});
return true;
});
$(".filterByLetter").on("click",function(e){
e.preventDefault();
filterByFirstLetterSurname( $(this).text(), true );
});
$("#select_all").on("click",function(e){
e.preventDefault();
$(".selection").prop("checked", true).change();
});
$("#clear_all").on("click",function(e){
e.preventDefault();
$(".selection").prop("checked", false).change();
});
$("#clear_search").on("click",function(e){
e.preventDefault();
clearFilters(true);
});
$("#searchform").on("submit", filter);
[% IF searchmember %]
$("#searchmember_filter").val("[% searchmember | html %]");
[% END %]
[% IF searchfieldstype %]
$("searchfieldstype_filter").val("[% searchfieldstype | html %]");
[% END %]
[% IF searchtype %]
$("#searchtype_filter").val("[% searchtype | html %]");
[% END %]
[% IF categorycode %]
$("#categorycode_filter").val("[% categorycode_filter | html %]");
[% END %]
[% IF branchcode %]
$("#branchcode_filter").val("[% branchcode_filter | html %]");
[% END %]
[% IF view != "show_results" %]
search = 0;
[% ELSE %]
$("#searchresults").show();
[% END %]
// Build the aLengthMenu
var aLengthMenu = [
[% PatronsPerPage | html %], 10, 20, 50, 100, -1
];
jQuery.unique(aLengthMenu);
aLengthMenu.sort(function( a, b ){
// Put "All" at the end
if ( a == -1 ) {
return 1;
} else if ( b == -1 ) {
return -1;
}
return parseInt(a) < parseInt(b) ? -1 : 1;}
);
var aLengthMenuLabel = [];
$(aLengthMenu).each(function(){
if ( this == -1 ) {
// Label for -1 is "All"
aLengthMenuLabel.push(_("All"));
} else {
aLengthMenuLabel.push(this);
}
});
// Apply DataTables on the results table
var columns_settings = [% TablesSettings.GetColumns( 'members', 'member', 'memberresultst', 'json' ) | $raw %];
[% UNLESS CAN_user_borrowers_edit_borrowers OR CAN_user_tools_manage_patron_lists %]
[%# Remove the first column if we do not display the checkbox %]
columns_settings.splice(0, 1);
[% END %]
dtMemberResults = KohaTable("memberresultst", {
'bServerSide': true,
'sAjaxSource': "/cgi-bin/koha/svc/members/search",
'fnServerData': function(sSource, aoData, fnCallback) {
if ( ! search ) {
return;
}
aoData.push({
'name': 'searchmember',
'value': $("#searchmember_filter").val()
},{
'name': 'firstletter',
'value': $("#firstletter_filter").val()
},{
'name': 'searchfieldstype',
'value': $("#searchfieldstype_filter").val()
},{
'name': 'searchtype',
'value': $("#searchtype_filter").val()
},{
'name': 'categorycode',
'value': $("#categorycode_filter").val()
},{
'name': 'branchcode',
'value': $("#branchcode_filter").val()
},{
'name': 'name_sorton',
'value': 'borrowers.surname borrowers.firstname'
},{
'name': 'cardnumber_sorton',
'value': 'borrowers.cardnumber',
},{
'name': 'dateofbirth_sorton',
'value': 'borrowers.dateofbirth',
},{
'name': 'dateexpiry_sorton',
'value': 'borrowers.dateexpiry',
},{
'name': 'category_sorton',
'value': 'categories.description',
},{
'name': 'branch_sorton',
'value': 'branches.branchname'
},{
'name': 'borrowernotes_sorton',
'value': 'borrowers.borrowernotes'
},{
'name': 'template_path',
'value': 'members/tables/members_results.tt',
});
$.ajax({
'dataType': 'json',
'type': 'POST',
'url': sSource,
'data': aoData,
'success': function(json){
// redirect if there is only 1 result.
if ( json.aaData.length == 1 ) {
var borrowernumber = json.aaData[0].borrowernumber;
/* Overwrite history state of firstletter search since only one result was returned; This prevents a loop upon clicking back */
history.replaceState( {}, null, window.location.href.split("?" )[0]);
document.location.href="/cgi-bin/koha/members/moremember.pl?borrowernumber="+borrowernumber;
return false;
}
fnCallback(json);
}
});
},
'aoColumns':[
[% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
{ 'mDataProp': 'dt_borrowernumber', 'bSortable': false },
[% END %]
{ 'mDataProp': 'dt_cardnumber' },
{ 'mDataProp': 'dt_name' },
{ 'mDataProp': 'dt_dateofbirth' },
{ 'mDataProp': 'dt_category' },
{
'mDataProp': function ( oObj ) {
if( !singleBranchMode && oObj.dt_branch == "[% Branches.GetLoggedInBranchname | html %]" ){
return "<span class=\"currentlibrary\">" + oObj.dt_branch + "</span>";
} else {
return oObj.dt_branch;
}
}
},
{ 'mDataProp': 'dt_dateexpiry' },
{ 'mDataProp': 'dt_od_checkouts', 'bSortable': false },
{ 'mDataProp': 'dt_fines', 'bSortable': false },
{ 'mDataProp': 'dt_borrowernotes' },
{ 'mDataProp': 'dt_action', 'bSortable': false, 'sClass': 'actions' }
],
'bFilter': false,
'bAutoWidth': false,
[% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
'aaSorting': [[2, 'asc']],
[% ELSE %]
'aaSorting': [[1, 'asc']],
[% END %]
"aLengthMenu": [aLengthMenu, aLengthMenuLabel],
'sPaginationType': 'full_numbers',
"iDisplayLength": [% PatronsPerPage | html %],
"bProcessing": true,
"initComplete": function(settings, json) {
Sticky = $("#searchheader");
Sticky.hcSticky({
stickTo: "#searchresults",
stickyClass: "floating"
});
}
}, columns_settings);
update_searched();
/* Initial page load doesn't trigger the popstate event, so we explicitly call this */
getSearchByLocation( false );
});
function getSearchByLocation( setstate ){
/* Check to see if the URL contains a search parameter */
if( location.search != ""){
var params = new URLSearchParams( location.search );
var firstletter = params.get("firstletter");
/* Check to see if search is a first letter param */
if( firstletter ){
/* Trigger function to return search results by letter */
filterByFirstLetterSurname( firstletter, setstate );
}
}
}
// Update the string "Results found ..."
function update_searched(){
var searched = $("#searchfieldstype_filter").find("option:selected").text();
if ( $("#searchmember_filter").val() ) {
if ( $("#searchtype_filter").val() == 'start_with' ) {
searched += _(" starting with ");
} else {
searched += _(" containing ");
}
searched += "'" + $("#searchmember_filter").val() + "'";
}
if ( $("#firstletter_filter").val() ) {
searched += _(" begins with ") + "'" + $("#firstletter_filter").val() +"'";
}
if ( $("#categorycode_filter").val() ) {
searched += _(" with category ") + "'" + $("#categorycode_filter").find("option:selected").text() + "'";
}
if ( $("#branchcode_filter").val() ) {
searched += _(" in library ") + $("#branchcode_filter").find("option:selected").text();
}
$("#searchpattern").text(searched);
}
// Redraw the table
function filter() {
$("#firstletter_filter").val('');
update_searched();
search = 1;
$("#searchresults").show();
dtMemberResults.fnDraw();
return false;
}
// User has clicked on the Clear button
function clearFilters(redraw) {
$("#searchform select").val('');
$("#firstletter_filter").val('');
$("#searchmember_filter").val('');
if(redraw) {
/* remove any search string added by firstletter search */
history.pushState( {}, null, window.location.href.split("?" )[0]);
update_searched();
search = 0;
$("#searchresults").hide();
dtMemberResults.fnDraw();
}
}
// User has clicked on a letter
function filterByFirstLetterSurname( letter, setstate ) {
clearFilters(false);
$("#firstletter_filter").val(letter);
if( setstate ){
history.pushState( null, null, "?firstletter=" + letter );
}
update_searched();
search = 1;
$("#searchresults").show();
dtMemberResults.fnDraw();
}
</script>
[% END %]
[% INCLUDE 'intranet-bottom.inc' %]