4 [% USE ExtendedAttributeTypes %]
10 [% SET search_results_block_id = 'searchresults' %]
12 [%# Display a simple form %]
13 [% BLOCK patron_search_filters_simple %]
14 <form method="get" class="patron_search_form">
15 <div class="hint">Enter patron card number or partial name:</div>
16 <input type="text" size="40" class="search_patron_filter" class="focus" autocomplete="off" />
17 <input type="submit" class="btn btn-primary" value="Search" />
21 [%# Display a complex patron search form %]
22 [%# - Search: input %]
23 [%# You can then pass a list of filters %]
24 [%# - branch: select library list %]
25 [%# - category: select patron category list %]
26 [%# - sort1: select patron sort1 field %]
27 [%# - sort2: select patron sort2 field %]
28 [%# - search_field: select patron field list %]
29 [%# - search_type: select 'contains' or 'starts with' %]
30 [%- searchtype = searchtype || Koha.Preference('DefaultPatronSearchMethod') -%]
31 [% BLOCK patron_search_filters %]
32 <form method="get" class="patron_search_form">
33 <fieldset class="brief">
34 <h3>Search for patron</h3>
37 <label for="search_patron_filter">Search:</label>
38 <input type="text" class="search_patron_filter" value="[% search_filter | html %]" class="focus" />
41 [% FOR f IN filters %]
45 <label for="branchcode_filter">Library:</label>
46 <select class="branchcode_filter">
47 [% SET libraries = Branches.all( only_from_group => 1 ) %]
48 [% IF libraries.size != 1 %]
49 <option value="">Any</option>
51 [% FOREACH l IN libraries %]
52 <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
58 <label for="categorycode_filter">Category:</label>
59 <select class="categorycode_filter">
60 <option value="">Any</option>
61 [% FOREACH category IN Categories.limited.unblessed %]
62 <option value="[% category.categorycode | html %]">[% category.description | html %]</option>
68 <label for="sort1_filter">Sort 1:</label>
69 [% PROCESS 'av-build-dropbox.inc' no_id => 1, name="sort1_filter", category="Bsort1", empty=1, size = 20 %]
73 <label for="sort2_filter">Sort 2:</label>
74 [% PROCESS 'av-build-dropbox.inc' no_id => 1, name="sort2_filter", category="Bsort2", empty=1, size = 20 %]
76 [% CASE 'search_field' %]
78 [% INCLUDE patron_fields_dropdown %]
80 [% CASE 'search_type' %]
82 <label for="searchtype_filter">Search type:</label>
83 <select name="searchtype" class="searchtype_filter">
84 [% IF searchtype == "starts_with" %]
85 <option value='starts_with' selected="selected">Starts with</option>
86 <option value="contains">Contains</option>
88 <option value='starts_with'>Starts with</option>
89 <option value="contains" selected="selected">Contains</option>
96 <fieldset class="action">
97 <input type="submit" class="btn btn-primary" value="Search" />
98 <input type="button" value="Clear" class="clear_search" />
104 [%# Display the table with: %]
105 [%# - At the top a hint about a possible filter %]
106 [%# - Browse by last name %]
108 [%# Get the following parameters: %]
109 [%# - filter: can be 'suggestions_managers', 'orders_managers', 'funds_owners', 'funds_users' or 'erm_users' to filter patrons on their permissions %]
110 [%# - table_id: the ID of the table %]
111 [%# open_on_row_click: See patron_search_js %]
112 [%# columns: See patron_search_js %]
113 [% BLOCK patron_search_table %]
115 [% UNLESS table_id %]
116 [% SET table_id = "memberresultst" %]
119 [% IF filter == 'suggestions_managers' %]
120 <div class="hint">Only staff with superlibrarian or suggestions_manage permissions are returned in the search results</div>
121 [% ELSIF filter == 'orders_managers' OR filter == 'baskets_managers' %]
122 <div class="hint">Only staff with superlibrarian or acquisitions permissions (or order_manage permission if granular permissions are enabled) are returned in the search results</div>
123 [% ELSIF filter == 'funds_owners' OR filter == 'funds_users' %]
124 <div class="hint">Only staff with superlibrarian or acquisitions permissions (or budget_modify permission if granular permissions are enabled) are returned in the search results</div>
125 [% ELSIF filter == 'erm_users' %]
126 <div class="hint">Only staff with superlibrarian or ERM permissions are returned in the search results</div>
131 [% SET alphabet = Koha.Preference('alphabet').split(' ') %]
132 [% UNLESS alphabet.size %]
133 [% alphabet = ['A' .. 'Z'] %]
135 [% FOREACH letter IN alphabet %]
136 <a href="#" class="filterByLetter">[% letter | html %]</a>
140 <h3 style="display: none;">Patrons found for: <span class="searchpattern"></span></h3>
142 <div id="[% table_id | html %]_search_results" style="display:none;">
144 <div class="info" class="dialog message" style="display: none;"></div>
145 <div class="error" class="dialog alert" style="display: none;"></div>
147 <input type="hidden" class="firstletter_filter" value="" />
148 [% IF open_on_row_click %]
149 <table id="[% table_id | html %]" class="selections-table">
151 <table id="[% table_id | html %]">
155 [% FOR column IN columns %]
157 [% CASE 'checkbox' %]<th class="noExport"></th>
158 [% CASE 'cardnumber' %]<th>Card</th>
159 [% CASE 'dateofbirth' %]<th>Date of birth</th>
160 [% CASE 'name' %]<th>Name</th>
161 [% CASE 'name-address' %]<th>Name</th>
162 [% CASE 'address' %]<th>Address</th>
163 [% CASE 'address-library' %]<th>Address</th>
164 [% CASE 'branch' %]<th data-filter="libraries">Library</th>
165 [% CASE 'category' %]<th data-filter="categories">Category</th>
166 [% CASE 'dateexpiry' %]<th>Expires on</th>
167 [% CASE 'borrowernotes' %]<th>Notes</th>
168 [% CASE 'phone' %]<th>Phone</th>
169 [% CASE 'checkouts' %]<th>Checkouts</th>
170 [% CASE 'account_balance' %]<th>Fines</th>
171 [% CASE 'action' %]<th class="noExport"> </th>
180 <!-- Patron preview modal -->
181 <div class="modal" id="patronPreview" tabindex="-1" role="dialog" aria-labelledby="patronPreviewLabel">
182 <div class="modal-dialog" role="document">
183 <div class="modal-content">
184 <div class="modal-body">
186 <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading
195 [%# Integrate all the JS code, outside of a script tag %]
196 [%# Get the following parameters: %]
197 [%# - redirect_if_one_result: Redirect to the patron if the search returns only one result, note that it will not redirect if filters of the DT are used (this is a feature) %]
198 [%# - redirect_url: The URL to use, the borrowernumber parameter will be added %]
199 [%# - redirect_if_attribute_equal: Name of the attribute to use for the redirect. Query using this attribute, before the normal search %]
200 [%# filter: Same as patron_search_table %]
201 [%# open_on_row_click: boolean, default off. Will allow to select a patron by clicking on the whole tr element %]
202 [%# columns: list of columns that will be displayed. Possible values are: 'checkbox', 'cardnumber', 'dateofbirth', 'address', 'name', 'name-address', 'branch', 'category', 'dateexpiry', 'borrowernotes, 'phone', 'checkouts', 'account_balance', 'action' %]
203 [%# preview_on_name_click: Open a modal window with patron's info when the name is clicked %]
204 [%# actions: list of buttons to display in the action column. Possible values are: 'select', 'add', 'edit', 'checkout' %]
205 [%# sticky_header and sticky_to: If we need a sticky header %]
206 [%# callback: name of the JS function that will be called when a patron is selected. Only work with action=select %]
207 [%# display_search_description: boolean, default off. Display the description of the search %]
208 [%# adjust_history: boolean, default off. Change the current url when a first letter is selected %]
209 [% BLOCK patron_search_js %]
211 [% IF redirect_if_one_result && !redirect_url %]
212 <script>console.log("Wrong call of patron_search_js - missing redirect_url");</script>
216 [% SET libraries = Branches.all %]
217 [% SET categories = Categories.limited.unblessed %]
218 var categories = [% To.json(categories) | $raw %].map(e => {
219 e['_id'] = e.categorycode.toLowerCase();
220 e['_str'] = e.description;
223 var categories_map = categories.reduce((map, e) => {
227 var libraries = [% To.json(libraries) | $raw %].map(e => {
228 e['_id'] = e.branchcode;
229 e['_str'] = e.branchname;
232 var libraries_map = libraries.reduce((map, e) => {
237 [% IF Koha.Preference('ExtendedPatronAttributes') %]
238 [% SET extended_attribute_types = [ ExtendedAttributeTypes.codes( staff_searchable => 1, searched_by_default => 1 ) ] %]
239 var extended_attribute_types = [% To.json(extended_attribute_types || []) | $raw %];
242 $(document).on("shown.bs.modal", function(){
243 $('select[name="sort1_filter"]').select2({allowClear:true});
244 $('select[name="sort2_filter"]').select2({allowClear:true});
245 }).on("hidden.bs.modal", function(){
246 ["sort1_filter", "sort2_filter"].forEach(function(item){
247 if( $('select[name=' + item + ']').data("select2") ){
248 $('select[name=' + item + ']').select2("destroy");
254 [% INCLUDE 'datatables.inc' %]
255 [% INCLUDE 'js-patron-get-age.inc' %]
256 [% INCLUDE 'js-patron-format.inc' %]
257 [% INCLUDE 'js-patron-format-address.inc' %]
258 [% IF sticky_header %]
259 [% Asset.js("lib/hc-sticky.js") | $raw %]
265 function get_patron_search_form(){
266 let parent_block = $("#[% search_results_block_id | html %]");
267 let patron_search_form = parent_block.siblings(".patron_search_form");
268 if ( !patron_search_form.length ) patron_search_form = $(".patron_search_form");
269 return patron_search_form;
274 let singleBranchMode = '[% singleBranchMode | html %]';
275 let logged_in_library_id = "[% Branches.GetLoggedInBranchcode | html %]";
276 [% IF do_not_defer_loading %]
277 let defer_loading = 0;
279 let defer_loading = 1;
282 [% IF adjust_history %]
283 /* popstate event triggered by forward and back button. Need to refresh search */
284 window.addEventListener('popstate', (event) => {
285 getSearchByLocation( false );
290 [% CASE 'suggestions_managers' %]
291 let patron_search_url = '/api/v1/suggestions/managers';
292 [% CASE 'baskets_managers' %]
293 let patron_search_url = '/api/v1/acquisitions/baskets/managers';
294 [% CASE 'funds_owners' %]
295 let patron_search_url = '/api/v1/acquisitions/funds/owners';
296 [% CASE 'funds_users' %]
297 let patron_search_url = '/api/v1/acquisitions/funds/users';
298 [% CASE 'erm_users' %]
299 let patron_search_url = '/api/v1/erm/users';
301 let patron_search_url = '/api/v1/patrons';
303 $(document).ready(function(){
305 let parent_block = $("#[% search_results_block_id | html %]");
306 let patron_search_form = get_patron_search_form();
308 parent_block.find(".info").hide();
309 parent_block.find(".error").hide();
311 // Build the aLengthMenu
313 [% Koha.Preference('PatronsPerPage') | html %], 10, 20, 50, 100, -1
315 jQuery.unique(aLengthMenu);
316 aLengthMenu.sort(function( a, b ){
317 // Put "All" at the end
320 } else if ( b == -1 ) {
323 return parseInt(a) < parseInt(b) ? -1 : 1;}
325 let aLengthMenuLabel = [];
326 $(aLengthMenu).each(function(){
328 // Label for -1 is "All"
329 aLengthMenuLabel.push(_("All"));
331 aLengthMenuLabel.push(this);
335 let additional_filters = {
337 let start_with = parent_block.find(".firstletter_filter").val()
338 if (!start_with) return "";
339 return { "like": start_with + "%" }
344 let search_type = patron_search_form.find(".searchtype_filter").val();
345 let search_fields = patron_search_form.find(".searchfieldstype_filter").val() || "standard";
346 let pattern = patron_search_form.find(".search_patron_filter").val();
348 filters = buildPatronSearchQuery(
351 search_type: search_type,
352 search_fields: search_fields,
353 ...(typeof extended_attribute_types != 'undefined' && {extended_attribute_types: extended_attribute_types})
357 let f_sort1 = patron_search_form.find("select[name='sort1_filter']").val();
363 let f_sort2 = patron_search_form.find("select[name='sort2_filter']").val();
374 [% UNLESS default_sort_column %]
375 [% default_sort_column = "name" %]
377 [% SET order_column_index = 0 %]
378 [% SET embed = ['extended_attributes'] %]
379 patrons_table = $("#[% table_id | html %]").kohaTable({
381 "url": patron_search_url,
382 "dataSrc": function ( json ) {
383 [% IF redirect_if_one_result %]
384 // redirect if there is only 1 result.
385 if ( first_draw && json.recordsFiltered == 1 ) {
386 let url = '[% redirect_url | url %]'.indexOf("?") != -1
387 ? '[% redirect_url | url %]&borrowernumber=' + json.data[0].patron_id
388 : '[% redirect_url | url %]?borrowernumber=' + json.data[0].patron_id;
389 document.location.href = url;
397 [% IF open_on_row_click OR preview_on_name_click OR remember_selections %]
398 "drawCallback": function( settings ) {
399 var api = this.api();
400 var data = api.data();
401 if ( data.length == 0 ) return;
403 [% IF open_on_row_click %]
404 $.each($(this).find("tbody tr"), function(index, tr) {
405 let url = "[% on_click_url | url %]&borrowernumber=" + data[index].patron_id;
406 $(tr).off('click').on('click', function() {
407 document.location.href = url;
408 }).addClass('clickable');
409 $(tr).find("a.patron_name").attr('href', url);
412 [% IF preview_on_name_click %]
413 $.each($(this).find("tbody tr"), function(index, tr) {
414 $(tr).find("a.patron_name").addClass("patron_preview");
417 [% IF remember_selections %]
422 "deferLoading": defer_loading,
424 [% FOR column IN columns %]
425 [% IF default_sort_column == column %]
426 [% order_column_index = loop.count - 1%]
429 [% CASE 'checkbox' %]
434 "render": function( data, type, row, meta ) {
435 return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' class='check" + data + " selection' name='borrowernumber' value='" + data + "' />";
438 [% CASE 'cardnumber' %]
440 "data": "cardnumber",
443 "render": function( data, type, row, meta ) {
444 let patron_id = encodeURIComponent(row.patron_id);
445 [% IF !open_on_row_click AND CAN_user_circulate_circulate_remaining_permissions %]
446 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>";
448 return escape_str(data);
453 [% CASE 'dateofbirth' %]
455 "data": "date_of_birth",
459 "render": function( data, type, row, meta ) {
460 return data ? "<span class=\"dateofbirth\">" + escape_str($date(data)) + "<span class=\"agehint\"> (" + _("%s years").format($get_age(data)) + ")</span></span>" : "";
465 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
468 "render": function( data, type, row, meta ) {
469 let r = '<div class="address"><ul>';
470 r += $format_address(row, { no_line_break: true, include_li: true });
475 [% CASE 'address-library' %]
477 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
480 "render": function( data, type, row, meta ) {
481 let r = '<div class="address"><ul>';
482 r += $format_address(row, { no_line_break: true, include_li: true });
484 r += " " + escape_str(libraries_map[row.library_id].branchname);
488 [% CASE 'name-address' %]
490 "data": "me.surname:me.firstname:me.middle_name:me.othernames:me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
493 "render": function( data, type, row, meta ) {
494 let patron_id = encodeURIComponent(row.patron_id);
496 [% IF ! open_on_row_click %]
497 r += "<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>";
499 r += $patron_to_html(row, { invert_name: 1 });
502 r += '<div class="address"><ul>';
503 r += $format_address(row, { no_line_break: true, include_li: true });
506 r += "<li>" + _("Email: ") + "<a href='mailto:" + encodeURIComponent(row.email) + "'>" + escape_str(row.email) + "</a></li>";
515 "data": "me.surname:me.firstname:me.middle_name:me.othernames",
518 "render": function( data, type, row, meta ) {
519 let patron_id = encodeURIComponent(row.patron_id);
520 [% IF ! open_on_row_click %]
521 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>";
523 return $patron_to_html(row, { invert_name: 1 });
529 "data": "library_id",
532 "render": function( data, type, row, meta ) {
533 let library_name = libraries_map[data].branchname
534 if( !singleBranchMode && data == logged_in_library_id ) {
535 return "<span class=\"currentlibrary\">" + escape_str(library_name) + "</span>";
537 return escape_str(library_name);
541 [% CASE 'category' %]
543 "data": "category_id",
546 "render": function( data, type, row, meta ) {
547 return escape_str(categories_map[data.toLowerCase()].description);
550 [% CASE 'dateexpiry' %]
552 "data": "expiry_date",
556 "render": function( data, type, row, meta ) {
557 return data ? escape_str($date(data)) : "";
560 [% CASE 'borrowernotes' %]
562 "data": "staff_notes",
565 [%# We don't escape here, we allow html tag in staff notes %]
572 "render": function( data, type, row, meta ) {
573 return escape_str(data);
576 [% CASE 'checkouts' %][% embed.push('checkouts+count', 'overdues+count') %]
581 "render": function( data, type, row, meta ) {
582 if ( row.overdues_count ) {
583 return "<span class='overdue'><strong>"+row.overdues_count + "</strong></span>" + " / " + row.checkouts_count;
585 return "0 / " + row.checkouts_count;
589 [% CASE 'account_balance' %][% embed.push('account_balance') %]
594 "render": function( data, type, row, meta ) {
595 let r = "<span style='text-align: right; display: block;'><a href=\"/cgi-bin/koha/members/boraccount.pl?borrowernumber="+row.patron_id+"\">";
596 let balance_str = row.account_balance || 0;
597 balance_str = balance_str.escapeHtml().format_price();
598 if ( row.account_balance < 0 ) {
599 // FIXME Format price here
600 r += "<span class='credit'>" + balance_str + "</span>";
601 } else if ( row.account_balance > 0 ) {
602 r += "<span class='debit'><strong>" + balance_str + "</strong></span>"
613 "data": function( row, type, val, meta ) {
615 let patron_id = encodeURIComponent(row.patron_id);
616 let action_node = "";
617 [% FOR action IN actions %]
620 action_node += '<a href="#" class="btn btn-default btn-xs select_user" data-borrowernumber="' + patron_id + '">' + _("Select") + '</a>';
622 action_node += '<a href="#" class="btn btn-default btn-xs add_user" data-borrowernumber="' + patron_id + '">' + _("Add") + '</a>';
624 action_node += '<a href="/cgi-bin/koha/members/memberentry.pl?op=edit_form&destination=circ&borrowernumber=' + patron_id + '" class="btn btn-default btn-xs"><i class="fa-solid fa-pencil" aria-hidden="true"></i> ' + _("Edit") + '</a>';
625 [% CASE 'checkout' %]
626 [% IF CAN_user_circulate_circulate_remaining_permissions %]
627 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>';
632 let patron_str = JSON.stringify(row);
633 let input_node = $('<input type="hidden" name="borrower_data'+ patron_id + '"/>');
634 $(input_node).val(patron_str);
635 action_node += $(input_node).prop('outerHTML');
643 [% UNLESS loop.last %],[% END %]
646 'embed': [% To.json(embed) | $raw %],
647 "order": [[ [% order_column_index | html %], "asc" ]],
649 'lengthMenu': [aLengthMenu, aLengthMenuLabel],
650 "pagingType": 'full_numbers',
651 "pageLength": [% Koha.Preference('PatronsPerPage') | html %],
652 [% IF sticky_header %]
653 "initComplete": function(settings, json) {
654 $("#[% sticky_header | html %]").show();
655 Sticky = $("#[% sticky_header | html %]");
657 stickTo: "#[% sticky_to | html %]",
658 stickyClass: "floating"
663 }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters);
665 patron_search_form.on('submit', filter);
666 patron_search_form.on('submit', update_search_type);
667 patron_search_form.on('submit', function(){
668 parent_block.find(".searchheader").show();
672 $(".filterByLetter").on("click",function(e){
674 filterByFirstLetterSurname($(this).text(), true);
676 patrons_table.on("click",".add_user",function(e){
678 var borrowernumber = $(this).data("borrowernumber");
679 var borrower_data = JSON.parse(patrons_table.find("input[name='borrower_data"+borrowernumber+"']").val());
680 modal_add_user( borrowernumber, $patron_to_html( borrower_data, { display_cardnumber: false, url: false } ) );
682 patrons_table.on("click",".select_user",function(e){
684 var borrowernumber = $(this).data("borrowernumber");
685 var borrower_data = JSON.parse(patrons_table.find("input[name='borrower_data"+borrowernumber+"']").val());
686 modal_select_user( borrowernumber, borrower_data );
687 $(this).closest(".modal").modal('hide');
690 patrons_table.on("click",".patron_preview", function( e ){
692 var borrowernumber = $(this).data("borrowernumber");
693 var page = "/cgi-bin/koha/members/moremember.pl?print=brief&borrowernumber=" + borrowernumber;
694 $("#patron_preview_modal").load( page + " div.container-fluid", function(){
695 $("#patron_preview_modal").find(".close").on("click", function(){
696 $('#patron_preview_modal').html(_("Loading...")).removeClass("show");
699 $("#patron_preview_modal").addClass("show");
702 $("#patronPreview").on('hidden.bs.modal', function (e) {
703 $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading");
706 patron_search_form.find(".clear_search").on("click",function(e){
709 patron_search_form.find(".searchpattern").parent().hide();
712 if ( !defer_loading ) {
713 patron_search_form.submit();
716 [% IF adjust_history %]
717 /* Initial page load does not trigger the popstate event, so we explicitly call this */
718 getSearchByLocation( false );
722 [% IF adjust_history %]
723 function getSearchByLocation( setstate ){
724 /* Check to see if the URL contains a search parameter */
725 if( location.search != ""){
726 var params = new URLSearchParams( location.search );
727 var firstletter = params.get("firstletter");
728 /* Check to see if search is a first letter param */
730 /* Trigger function to return search results by letter */
731 filterByFirstLetterSurname( firstletter, setstate );
737 function update_search_type(){
738 $("#searchtype").val($("#searchtype_filter").val());
741 function update_search_description(){
742 let parent_block = $("#[% search_results_block_id | html %]");
743 let patron_search_form = get_patron_search_form();
744 var searched = patron_search_form.find(".searchfieldstype_filter").find("option:selected").text();
745 let pattern = patron_search_form.find(".search_patron_filter").val();
747 if ( patron_search_form.find(".searchtype_filter").val() == 'starts_with' ) {
748 searched += _(" starting with ");
750 searched += _(" containing ");
752 searched += "'" + pattern + "'";
754 let firstletter_filter = parent_block.find(".firstletter_filter").val();
755 if ( firstletter_filter ) {
756 searched += _(" begins with ") + "'" + firstletter_filter +"'";
759 if ( patron_search_form.find(".categorycode_filter").val() ) {
760 searched += _(" with category ") + "'" + patron_search_form.find(".categorycode_filter option:selected").text() + "'";
762 if ( patron_search_form.find(".branchcode_filter").val() ) {
763 searched += _(" in library ") + patron_search_form.find(".branchcode_filter option:selected").text();
765 if ( patron_search_form.find("select[name='sort1_filter']").val() ) {
766 searched += _(" with sort1 ")
767 if ( patron_search_form.find("select[name='sort1_filter']") ) {
768 searched += patron_search_form.find("select[name='sort1_filter'] option:selected").text();
771 searched += paron_search_form.find("select[name='sort1_filter']").val();
774 if ( patron_search_form.find("select[name='sort2_filter']").val() ) {
775 searched += _(" with sort2 ")
776 if ( patron_search_form.find("select[name='sort2_filter']") ) {
777 searched += patron_search_form.find("select[name='sort2_filter'] option:selected").text();
780 searched += paron_search_form.find("select[name='sort2_filter']").val();
783 patron_search_form.find(".searchpattern").text(searched);
784 patron_search_form.find(".searchpattern").parent().show();
788 let parent_block = $("#[% search_results_block_id | html %]");
789 let patron_search_form = get_patron_search_form();
790 [% IF redirect_if_attribute_equal %]
791 let filter = patron_search_form.find(".search_patron_filter").val();
794 data: { cardnumber: filter, _match: 'exact' },
796 url: patron_search_url,
797 success: function(data) {
798 if ( data.length == 1 ) {
799 let url = '[% redirect_url | url %]'.indexOf("?") != -1
800 ? '[% redirect_url | url %]&borrowernumber=' + data[0].patron_id
801 : '[% redirect_url | url %]?borrowernumber=' + data[0].patron_id;
802 document.location.href = url;
807 alert( _("An error occurred. Check the logs for details.") );
812 parent_block.find(".firstletter_filter").val('');
813 $("#[% table_id | html %]_search_results").show();
815 let table_dt = patrons_table.DataTable();
816 [% FOR c IN columns %]
819 let library_id = patron_search_form.find(".branchcode_filter").val() || "";
820 patrons_table.find('thead tr:eq(1) th[data-filter="libraries"] select').val(library_id);
821 table_dt.column([% loop.count - 1 %]).search(library_id ? '^'+library_id+'$' : '');
822 [% CASE 'category' %]
823 let category_id = patron_search_form.find(".categorycode_filter").val() || "";
824 patrons_table.find('thead tr:eq(1) th[data-filter="categories"] select').val(category_id.toLowerCase());
825 table_dt.column([% loop.count - 1 %]).search(category_id ? '^'+category_id+'$' : '');
829 first_draw = 1; // Only redirect if we are coming from here
831 [% IF display_search_description %]
832 update_search_description();
837 function clearFilters() {
838 let parent_block = $("#[% search_results_block_id | html %]");
839 let patron_search_form = get_patron_search_form();
840 patron_search_form.find(".searchfieldstype_filter option:first").prop("selected", true);
841 patron_search_form.find(".searchtype_filter option[value='[% searchtype | html %]']").prop("selected", true);
842 patron_search_form.find(".categorycode_filter option:first").prop("selected", true);
843 patron_search_form.find(".branchcode_filter option:first").prop("selected", true);
844 patron_search_form.find("select[name='sort1_filter']").val('').trigger("change");
845 patron_search_form.find("select[name='sort2_filter']").val('').trigger("change");
846 parent_block.find(".firstletter_filter").val('');
847 patron_search_form.find(".search_patron_filter").val('');
848 [% IF adjust_history %]
849 /* remove any search string added by firstletter search */
850 history.pushState( {}, null, window.location.href.split("?" )[0]);
852 $("#[% table_id | html %]_search_results").hide();
853 [% IF display_search_description %]
854 update_search_description();
858 // User has clicked on a letter
859 function filterByFirstLetterSurname(letter, setstate ) {
860 let parent_block = $("#[% search_results_block_id | html %]");
861 parent_block.find(".firstletter_filter").val(letter);
863 $("#[% table_id | html %]_search_results").show();
865 [% IF adjust_history %]
867 history.pushState( null, null, "?firstletter=" + letter );
871 patrons_table.DataTable().draw();
872 [% IF display_search_description %]
873 update_search_description();
877 // modify parent window owner element
878 function modal_add_user(borrowernumber, borrowername) {
879 [%# Note that add_user could sent data instead of borrowername too %]
880 let parent_block = $("#[% search_results_block_id | html %]");
881 parent_block.find(".info").hide();
882 parent_block.find(".error").hide();
883 if ( add_user(borrowernumber, borrowername) < 0 ) {
884 parent_block.find(".error").html(_("Patron '%s' is already in the list.").format(borrowername)).show();
886 parent_block.find(".info").html(_("Patron '%s' added.").format(borrowername)).show();
889 function modal_select_user(borrowernumber, data) {
890 if ( document.getElementById("selected_patron_id") ) {
891 document.getElementById("selected_patron_id").value = borrowernumber;
894 [% callback | html %](borrowernumber, data);
896 select_user(borrowernumber, data);
905 [% BLOCK patron_search_modal %]
906 [% UNLESS patron_search_modal_id %]
907 [% patron_search_modal_id = "patron_search_modal" %]
909 [% UNLESS table_id %]
910 [% table_id = "memberresultst" %]
913 [% search_results_block_id = patron_search_modal_id _ '_searchresults' %]
915 <div id="[% patron_search_modal_id | html %]" class="modal modal-full" tabindex="-1" role="dialog" aria-labelledby="patronSearchLabel" aria-hidden="true" data-backdrop="">
916 <div class="modal-dialog" role="document">
917 <div class="modal-content">
918 <div class="modal-header">
919 <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
920 <h4 class="modal-title" id="patronSearchLabel">[% modal_title | html %]</h4>
922 <div class="modal-body">
923 [% PROCESS patron_search_filters filters => ['branch','category','sort1','sort2'] %]
925 <div id="[% search_results_block_id | html %]"> <!-- FIXME removed style from #searchresults, is that bad? -->
926 [% IF columns.grep('checkbox').size %]
927 <div class="searchheader fh-fixedHeader" style="display:none;">
929 <a href="#" class="btn btn-link select_all"><i class="fa fa-check"></i> Select all</a>
931 <a href="#" class="btn btn-link clear_all"><i class="fa fa-remove"></i> Clear all</a>
932 <button class="add-selected" class="btn btn-sm btn-default" type="submit">Add selected patrons</button>
936 [% PROCESS patron_search_table table_id => table_id, columns => columns %]
939 <div class="modal-footer">
940 <a href="#" class="btn btn-default cancel" data-dismiss="modal" aria-hidden="true">Close</a>
945 <div id="patron_preview_modal" class="basicModal"></div>
948 $(document).ready(function() {
949 let parent_block = $("#[% search_results_block_id | html %]");
950 parent_block.find(".select_all").on("click",function(e){
952 parent_block.find(".selection").prop("checked", true).change();
954 parent_block.find(".clear_all").on("click",function(e){
956 parent_block.find(".selection").prop("checked", false).change();
958 parent_block.find(".searchheader").hide();
959 parent_block.find(".clear_search").on("click",function(e){$("#searchheader").hide();});
961 parent_block.find('.add-selected').on('click', function(e) {
964 parent_block.find('tr:has(.selection:checked) .add_user').each(function(){
965 var borrowernumber = $(this).data('borrowernumber');
966 var firstname = $(this).data('firstname');
967 var surname = $(this).data('surname');
968 add_user( borrowernumber, firstname + ' ' + surname );
971 parent_block.find('.info').html(_("%s Patrons added.").format(counter)).show();