From 227c269651d751b59a2b0d1505e8875e5f51ac7a Mon Sep 17 00:00:00 2001 From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> Date: Fri, 11 Feb 2022 14:03:36 +0100 Subject: [PATCH] Bug 30063: Make the main patron search use the REST API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To test: * Length menu (PatronsPerPage) * Query description * Highlight of the current library * sticky header - Does not work (?) If the table does not show when you submit the filter form, make sure you regenerated the compiled CSS. Signed-off-by: Séverine Queune <severine.queune@bulac.fr> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> --- .../prog/en/includes/patron-search-header.inc | 2 +- .../prog/en/includes/patron-search.inc | 311 ++++++++++++--- .../prog/en/includes/patronfields.inc | 2 +- .../prog/en/modules/members/member.tt | 376 ++---------------- .../prog/en/modules/members/search.tt | 4 +- members/member.pl | 4 + members/members-home.pl | 4 + 7 files changed, 296 insertions(+), 407 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search-header.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search-header.inc index 1aa4695213..e7f794d018 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search-header.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search-header.inc @@ -24,7 +24,7 @@ <p><label for="searchfieldstype">Search fields:</label> <select name="searchfieldstype" id="searchfieldstype"> [% pref_fields = Koha.Preference('DefaultPatronSearchFields').split(',') %] - [% default_fields = [ 'standard', 'surname', 'cardnumber', 'email', 'borrowernumber', 'userid', 'phone', 'address', 'dateofbirth', 'sort1', 'sort2' ] %] + [% default_fields = [ 'surname,firstname,othernames,cardnumber,userid', '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 %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc index ebe1d0c315..11fce49397 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc @@ -1,6 +1,8 @@ [% USE Koha %] +[% USE I18N %] [% USE Branches %] [% USE raw %] +[% USE Asset %] [% USE To %] [% BLOCK patron_search_filters_simple %] @@ -18,12 +20,11 @@ <ol> <li> <label for="search_patron_filter">Search:</label> - <div class="hint">Enter patron card number or partial name:</div> <input type="text" id="search_patron_filter" value="[% search_filter | html %]" class="focus" /> </li> - [% FOR column IN columns %] - [% SWITCH column %] + [% FOR f IN filters %] + [% SWITCH f %] [% CASE 'branch' %] <li> <label for="branchcode_filter">Library:</label> @@ -47,11 +48,43 @@ [% END %] </select> </li> + [% CASE 'search_field' %] + <li> + <label for="searchfieldstype_filter">Search field:</label> + <select name="searchfieldstype" id="searchfieldstype_filter"> + [% pref_fields = Koha.Preference('DefaultPatronSearchFields').split(',') %] + [% default_fields = [ 'surname,firstname,othernames,cardnumber,userid', '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> + [% CASE 'search_type' %] + <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> [% END %] [% END %] </ol> <fieldset class="action"> <input type="submit" value="Search" /> + <input type="button" value="Clear" id="clear_search" /> </fieldset> </fieldset> </form> @@ -78,12 +111,14 @@ [% END %] </div> + + <h3 style="display: none;">Patrons found for: <span id="searchpattern"></span></h3> + <div id="[% table_id | html %]_search_results" style="display:none;"> <div id="info" class="dialog message" style="display: none;"></div> <div id="error" class="dialog alert" style="display: none;"></div> - <input type="hidden" id="firstletter_filter" value="" /> [% IF open_on_row_click %] <table id="[% table_id | html %]" class="selections-table"> @@ -94,6 +129,7 @@ <tr> [% FOR column IN columns %] [% SWITCH column %] + [% CASE 'checkbox' %]<th class="noExport"></th> [% CASE 'cardnumber' %]<th>Card</th> [% CASE 'dateofbirth' %]<th>Date of birth</th> [% CASE 'name' %]<th>Name</th> @@ -166,17 +202,51 @@ [% INCLUDE 'js-patron-get-age.inc' %] [% INCLUDE 'js-patron-format.inc' %] [% INCLUDE 'js-patron-format-address.inc' %] + [% IF sticky_header %] + [% Asset.js("lib/hc-sticky.js") | $raw %] + [% END %] <script> var first_draw = 0; let patrons_table; + var Sticky; + var singleBranchMode = '[% singleBranchMode | html %]'; + let logged_in_library_id = "[% Branches.GetLoggedInBranchcode | html %]"; - $(document).ready(function(){ + /* popstate event triggered by forward and back button. Need to refresh search */ + window.addEventListener('popstate', (event) => { + getSearchByLocation( false ); + }); + $(document).ready(function(){ $("#info").hide(); $("#error").hide(); + // Build the aLengthMenu + var aLengthMenu = [ + [% Koha.Preference('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); + } + }); + let additional_filters = { surname: function(){ let start_with = $("#firstletter_filter").val() @@ -186,18 +256,23 @@ "-and": function(){ let filter = $("#search_patron_filter").val(); if (!filter) return ""; - [% SET search_fields = Koha.Preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid' %] - return [ - [% FOR search_field IN search_fields.split(',') %] - {"me.[% search_field | html %]":{"like":"%"+filter+"%"}}, - [% END %] - [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %] - { - "extended_attributes.value": { "like": "%" + filter + "%" }, + + let filters = []; + let search_type = $("#searchtype_filter").val() || "contain"; + let search_fields = $("#searchfieldstype_filter").val(); + if ( !search_fields ) { + search_fields = "[% Koha.Preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid' | html %]"; + } + search_fields.split(',').forEach(function(e,i){ + filters.push({["me."+e]:{"like":"%"+filter+(search_type == "contain" ? "%" : "" )}}); + }); + [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %] + filters.push({ + "extended_attributes.value": { "like": "%" + filter + (search_type == "contain" ? "%" : "" )}, "extended_attributes.code": extended_attribute_types - } - [% END %] - ]; + }); + [% END %] + return filters; } }; [% UNLESS default_sort_column %] @@ -223,7 +298,10 @@ [% IF redirect_if_one_result %] // redirect if there is only 1 result. if ( first_draw && json.recordsFiltered == 1 ) { - document.location.href = '[% redirect_url | url %]&borrowernumber=' + json.data[0].patron_id; + let url = '[% redirect_url | url %]'.indexOf("?") != -1 + ? '[% redirect_url | url %]&borrowernumber=' + json.data[0].patron_id + : '[% redirect_url | url %]?borrowernumber=' + json.data[0].patron_id; + document.location.href = url; return false; } first_draw = 0; @@ -231,6 +309,7 @@ return json.data; } }, + [% IF open_on_row_click OR preview_on_name_click %] "drawCallback": function( settings ) { var api = this.api(); var data = api.data(); @@ -244,12 +323,14 @@ }).addClass('clickable'); $(tr).find("a.patron_name").attr('href', url); }); - [% ELSE %] + [% END %] + [% IF preview_on_name_click %] $.each($(this).find("tbody tr"), function(index, tr) { $(tr).find("a.patron_name").addClass("patron_preview"); }); [% END %] }, + [% END %] "iDeferLoading": 0, "columns": [ [% FOR column IN columns %] @@ -259,20 +340,27 @@ [% SWITCH column %] [% CASE 'checkbox' %] { - "data": "borrowernumber", - "searchable": true, - "orderable": true, + "data": "patron_id", + "searchable": false, + "orderable": false, "render": function( data, type, row, meta ) { - return "<input type=\"checkbox\" name="data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : ""; - - return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' id='check" + data + "' class='selection' name='borrowernumber' value='" + data + "' />", + return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' id='check" + data + "' class='selection' name='borrowernumber' value='" + data + "' />"; } } [% CASE 'cardnumber' %] { "data": "cardnumber", "searchable": true, - "orderable": true + "orderable": true, + "render": function( data, type, row, meta ) { + let patron_id = encodeURIComponent(row.patron_id); + [% IF !open_on_row_click AND CAN_user_circulate_circulate_remaining_permissions %] + return "<a href=\"/cgi-bin/koha/circ/circulation.pl?borrowernumber=" + patron_id + "\" title=\"[% I18N.t("Check out") | html %]\" class=\"patron_name\" data-borrowernumber=\"" + patron_id + "\" style=\"white-space:nowrap\">" + escape_str(data) + "</a>"; + [% ELSE %] + return escape_str(data); + [% END %] + } + } [% CASE 'dateofbirth' %] { @@ -340,7 +428,11 @@ "orderable": true, "render": function( data, type, row, meta ) { let patron_id = encodeURIComponent(row.patron_id); + [% IF ! open_on_row_click %] return "<a href=\"/cgi-bin/koha/members/moremember.pl?borrowernumber=" + patron_id + "\" class=\"patron_name\" data-borrowernumber=\"" + patron_id + "\" style=\"white-space:nowrap\">" + $patron_to_html(row, { invert_name: 1 }) + "</a>"; + [% ELSE %] + return $patron_to_html(row, { invert_name: 1 }); + [% END %] } } [% CASE 'branch' %] @@ -349,7 +441,12 @@ "searchable": true, "orderable": true, "render": function( data, type, row, meta ) { - return escape_str(libraries_map[data].branchname); + let library_name = libraries_map[data].branchname + if( !singleBranchMode && data == logged_in_library_id ) { + return "<span class=\"currentlibrary\">" + escape_str(library_name) + "</span>"; + } else { + return escape_str(library_name); + } } } [% CASE 'category' %] @@ -426,11 +523,22 @@ "data": function( row, type, val, meta ) { let patron_id = encodeURIComponent(row.patron_id); - [% IF selection_type == 'select' %] - return '<a href="#" class="btn btn-default btn-xs select_user" data-borrowernumber="' + patron_id + '">Select</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" value=\''+JSON.stringify(row)+'\' />'; - [% ELSE %] - return '<a href="#" class="btn btn-default btn-xs add_user" data-borrowernumber="' + patron_id + '" data-firstname="' + encodeURIComponent(row.firstname) + '" data-surname="' + encodeURIComponent(row.surname) + '">Add</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" />'; + let action_node = ""; + [% FOR action IN actions %] + [% SWITCH action %] + [% CASE 'select' %] + action_node += '<a href="#" class="btn btn-default btn-xs select_user" data-borrowernumber="' + patron_id + '">Select</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" value=\''+JSON.stringify(row)+'\' />'; + [% CASE 'add' %] + action_node += '<a href="#" class="btn btn-default btn-xs add_user" data-borrowernumber="' + patron_id + '" data-firstname="' + encodeURIComponent(row.firstname) + '" data-surname="' + encodeURIComponent(row.surname) + '">Add</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" />'; + [% CASE 'edit' %] + action_node += '<a href="/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=' + patron_id + '" class="btn btn-default btn-xs"><i class="fa fa-pencil"></i> Edit</a>'; + [% CASE 'checkout' %] + [% IF CAN_user_circulate_circulate_remaining_permissions %] + action_node += '<a class="btn btn-default btn-xs" href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=' + patron_id + '"><i class="fa fa-barcode"></i> ' + _("Check out") + '</a>'; + [% END %] + [% END %] [% END %] + return action_node; }, "searchable": false, "orderable": false @@ -442,14 +550,24 @@ 'embed': [% To.json(embed) | $raw %], "order": [[ [% order_column_index | html %], "asc" ]], 'bAutoWidth': false, + 'lengthMenu': [aLengthMenu, aLengthMenuLabel], 'sPaginationType': 'full_numbers', - "iDisplayLength": [% Koha.Preference('PatronsPerPage') | html %], + "pageLength": [% Koha.Preference('PatronsPerPage') | html %], + [% IF sticky_header %] + "initComplete": function(settings, json) { + Sticky = $("#[% sticky_header | html %]"); + Sticky.hcSticky({ + stickTo: "#[% sticky_to | html %]", + stickyClass: "floating" + }); + }, + [% END %] }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters); $("#patron_search_form").on('submit', filter); $(".filterByLetter").on("click",function(e){ e.preventDefault(); - filterByFirstLetterSurname($(this).text()); + filterByFirstLetterSurname($(this).text(), true); }); $("body").on("click",".add_user",function(e){ e.preventDefault(); @@ -478,8 +596,57 @@ $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading"); }); + $("#clear_search").on("click",function(e){ + e.preventDefault(); + clearFilters(); + $("#searchpattern").parent().hide(); + }); + + if ( $("#search_patron_filter").val().length > 0 ) { + $("#patron_search_form").submit(); + } + + /* Initial page load does not 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 ); + } + } + } + + function update_search_description(){ + var searched = $("#searchfieldstype_filter").find("option:selected").text(); + if ( $("#search_patron_filter").val() ) { + if ( $("#searchtype_filter").val() == 'start_with' ) { + searched += _(" starting with "); + } else { + searched += _(" containing "); + } + searched += "'" + $("#search_patron_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); + $("#searchpattern").parent().show(); + } + function filter() { $("#firstletter_filter").val(''); $("#[% table_id | html %]_search_results").show(); @@ -500,50 +667,68 @@ table_dt.search(""); first_draw = 1; // Only redirect if we are coming from here table_dt.draw(); + update_search_description(); return false; } + function clearFilters() { + $("#searchfieldstype_filter option:first").prop("selected", true); + $("#searchtype_filter option[value='contain']").prop("selected", true); + $("#categorycode_filter option:first").prop("selected", true); + $("#branchcode_filter option:first").prop("selected", true); + $("#firstletter_filter").val(''); + $("#search_patron_filter").val(''); + /* remove any search string added by firstletter search */ + history.pushState( {}, null, window.location.href.split("?" )[0]); + $("#[% table_id | html %]_search_results").hide(); + update_search_description(); + } + // User has clicked on a letter - function filterByFirstLetterSurname(letter) { + function filterByFirstLetterSurname(letter, setstate ) { $("#firstletter_filter").val(letter); + $("#[% table_id | html %]_search_results").show(); + + if ( setstate ) { + history.pushState( null, null, "?firstletter=" + letter ); + } + patrons_table.DataTable().draw(); + update_search_description(); } // modify parent window owner element - [% IF selection_type == 'add' %] - function add_user(borrowernumber, borrowername) { - var p = window.opener; - // In one place (serials/routing.tt), the page is reload on every add - // We have to wait for the page to be there - function wait_for_opener () { - if ( ! $(opener.document).find('body').size() ) { - setTimeout(wait_for_opener, 500); + function add_user(borrowernumber, borrowername) { + var p = window.opener; + // In one place (serials/routing.tt), the page is reload on every add + // We have to wait for the page to be there + function wait_for_opener () { + if ( ! $(opener.document).find('body').size() ) { + setTimeout(wait_for_opener, 500); + } else { + [%# Note that add_user could sent data instead of borrowername too %] + $("#info").hide(); + $("#error").hide(); + if ( p.add_user(borrowernumber, borrowername) < 0 ) { + $("#error").html(_("Patron '%s' is already in the list.").format(borrowername)); + $("#error").show(); } else { - [%# Note that add_user could sent data instead of borrowername too %] - $("#info").hide(); - $("#error").hide(); - if ( p.add_user(borrowernumber, borrowername) < 0 ) { - $("#error").html(_("Patron '%s' is already in the list.").format(borrowername)); - $("#error").show(); - } else { - $("#info").html(_("Patron '%s' added.").format(borrowername)); - $("#info").show(); - } + $("#info").html(_("Patron '%s' added.").format(borrowername)); + $("#info").show(); } } - wait_for_opener(); } - [% ELSIF selection_type == 'select' %] - function select_user(borrowernumber, data) { - var p = window.opener; - [% IF callback %] - p.[% callback | html %](borrowernumber, data); - [% ELSE %] - p.select_user(borrowernumber, data); - [% END %] - window.close(); - } - [% END %] + wait_for_opener(); + } + function select_user(borrowernumber, data) { + var p = window.opener; + [% IF callback %] + p.[% callback | html %](borrowernumber, data); + [% ELSE %] + p.select_user(borrowernumber, data); + [% END %] + window.close(); + } </script> [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc index 7edc5c9082..6380e78ecd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc @@ -1,6 +1,6 @@ [%- BLOCK patron_fields -%] [%- SWITCH name -%] - [%- CASE 'standard' -%]<span>Standard</span> + [%- CASE 'surname,firstname,othernames,cardnumber,userid' -%]<span>Standard</span> [%- CASE 'borrowernumber' -%]<span>Borrowernumber</span> [%- CASE 'cardnumber' -%]<span>Card number</span> [%- CASE 'surname' -%]<span>Surname</span> diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt index b38378641a..016060065d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt @@ -6,6 +6,10 @@ [% USE Categories %] [% SET footerjs = 1 %] [% PROCESS 'patronfields.inc' %] +[% SET libraries = Branches.all %] +[% SET categories = Categories.all.unblessed %] +[% SET columns = ['cardnumber', 'name', 'dateofbirth', 'branch', 'category', 'dateexpiry', 'checkouts', 'account_balance', 'borrowernotes', 'action'] %] +[% PROCESS 'patron-search.inc' %] [% INCLUDE 'doc-head-open.inc' %] <title>Patrons[% IF ( searching ) %] › Search results[% END %] › Koha</title> [% INCLUDE 'doc-head-close.inc' %] @@ -53,13 +57,6 @@ [% 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>: @@ -67,10 +64,9 @@ </div> [% END %] - <div id="searchresults"> - <h3>Patrons found for: <span id="searchpattern">[% IF searchmember %] for '[% searchmember | html %]'[% END %]</span></h3> + <div id="patron_search_results"> [% IF CAN_user_tools_manage_patron_lists || CAN_user_borrowers_edit_borrowers %] - <div class="searchheader fh-fixedHeader" id="searchheader"> + <div class="searchheader fh-fixedHeader" id="searchheader" style="display:none;"> <div> <a href="#" class="btn btn-link" id="select_all"><i class="fa fa-check"></i> Select all</a> | @@ -103,26 +99,10 @@ </div> [% END %] - <table id="memberresultst"> - <thead> - <tr> - [% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %] - <th class="noExport"> </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 class="noExport"> </th> - </tr> - </thead> - <tbody></tbody> - </table> + [% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %] + [% columns.unshift('checkbox') | html %] + [% END %] + [% PROCESS patron_search_table table_id => 'memberresultst' columns => columns %] </div> </main> @@ -130,76 +110,7 @@ <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> + [% PROCESS patron_search_filters categories => categories, libraries => libraries, filters => ['search_field', 'search_type', 'category', 'branch'], search_filter => searchmember %] </aside> </div> <!-- /.col-sm-2.col-sm-pull-10 --> </div> <!-- /.row --> @@ -234,16 +145,8 @@ [% 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); @@ -313,10 +216,6 @@ patronListAdd(); }); - $(".filterByLetter").on("click",function(e){ - e.preventDefault(); - filterByFirstLetterSurname( $(this).text(), true ); - }); $("#select_all").on("click",function(e){ e.preventDefault(); $(".selection").prop("checked", true).change(); @@ -325,11 +224,6 @@ 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 %]"); @@ -347,161 +241,16 @@ $("#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); - } - }); + $("#searchheader").hide(); + $("#patron_search_form").on('submit', function(){$("#searchheader").show();}); + $("#clear_search").on("click",function(e){$("#searchheader").hide();}); // Apply DataTables on the results table - var columns_settings = [% TablesSettings.GetColumns( 'members', 'member', 'memberresultst', 'json' ) | $raw %]; + var table_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); + table_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.iTotalDisplayRecords == 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]); - [% IF circsearch == 1 %] - [% SET redirect = "circ/circulation" %] - [% ELSE %] - [% SET redirect = "members/moremember" %] - [% END %] - document.location.href="/cgi-bin/koha/[% redirect | $raw %].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 patronListAdd(){ @@ -536,52 +285,6 @@ return true; } - 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; - } - $('#memberresultst tbody').on('click','td',function(e){ var $checkbox = $(this).find("input[type=checkbox]"); if (e.target.type != "checkbox") { @@ -590,34 +293,27 @@ } }); - // 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> + + [% IF circsearch == 1 %] + [% SET redirect_url = '/cgi-bin/koha/circ/circulation.pl' %] + [% ELSE %] + [% SET redirect_url = '/cgi-bin/koha/members/moremember.pl' %] + [% END %] + [% PROCESS patron_search_js + table_id => 'memberresultst', + categories => categories, + libraries => libraries, + extended_attribute_types => attribute_type_codes, + columns => columns, + actions => ['edit', 'checkout'], + redirect_if_one_result => 1, + redirect_url => redirect_url, + sticky_header => "searchheader", + sticky_to => "patron_search_results", + %] + + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt index 0106acc4ce..ba77fb046e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt @@ -17,7 +17,7 @@ <div id="patron_search" class="yui-t7"> <div class="container-fluid"> - [% PROCESS patron_search_filters categories => categories, libraries => libraries, columns => columns, search_filter => searchmember %] + [% PROCESS patron_search_filters categories => categories, libraries => libraries, filters => ['branch', 'category'], search_filter => searchmember %] </form> [% PROCESS patron_search_table table_id => 'memberresultst' columns => columns %] @@ -28,7 +28,7 @@ </div> [% MACRO jsinclude BLOCK %] - [% PROCESS patron_search_js table_id => 'memberresultst', categories => categories, libraries => libraries, extended_attribute_types => attribute_type_codes, columns => columns, filter => filter, selection_type => selection_type, callback => callback %] + [% PROCESS patron_search_js table_id => 'memberresultst', categories => categories, libraries => libraries, extended_attribute_types => attribute_type_codes, columns => columns, filter => filter, actions => [selection_type], preview_on_name_click => 1, callback => callback %] [% END %] [% SET popup_window = 1 %] diff --git a/members/member.pl b/members/member.pl index b6ce83fb7a..bd85a3b613 100755 --- a/members/member.pl +++ b/members/member.pl @@ -29,6 +29,7 @@ use C4::Output qw( output_html_with_http_headers ); use CGI qw( -utf8 ); use Koha::List::Patron qw( GetPatronLists ); use Koha::Patrons; +use Koha::Patron::Attribute::Types; my $input = CGI->new; @@ -79,6 +80,9 @@ $template->param( PatronsPerPage => C4::Context->preference("PatronsPerPage") || 20, view => $view, circsearch => $circsearch, + attribute_type_codes => ( C4::Context->preference('ExtendedPatronAttributes') + ? [ Koha::Patron::Attribute::Types->search( { staff_searchable => 1 } )->get_column('code') ] + : [] ), ); output_html_with_http_headers $input, $cookie, $template->output; diff --git a/members/members-home.pl b/members/members-home.pl index d5efc9d6b2..e3b903f463 100755 --- a/members/members-home.pl +++ b/members/members-home.pl @@ -27,6 +27,7 @@ use Koha::Patron::Modifications; use Koha::Libraries; use Koha::List::Patron qw( GetPatronLists ); use Koha::Patron::Categories; +use Koha::Patron::Attribute::Types; my $query = CGI->new; @@ -72,6 +73,9 @@ $template->param( PatronAutoComplete => C4::Context->preference('PatronAutoComplete'), patron_lists => [ GetPatronLists() ], PatronsPerPage => C4::Context->preference("PatronsPerPage") || 20, + attribute_type_codes => ( C4::Context->preference('ExtendedPatronAttributes') + ? [ Koha::Patron::Attribute::Types->search( { staff_searchable => 1 } )->get_column('code') ] + : [] ), ); output_html_with_http_headers $query, $cookie, $template->output; -- 2.20.1