Bug 35865: Add missing hint about permissions when adding managers to a basket
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / patron-search.inc
1 [% USE Koha %]
2 [% USE I18N %]
3 [% USE Branches %]
4 [% USE ExtendedAttributeTypes %]
5 [% USE raw %]
6 [% USE Asset %]
7 [% USE To %]
8
9 [%# Display a simple form %]
10 [% BLOCK patron_search_filters_simple  %]
11     <form id="patron_search_form">
12         <div class="hint">Enter patron card number or partial name:</div>
13         <input type="text" size="40" id="search_patron_filter" class="focus" autocomplete="off" />
14         <input type="submit" class="btn btn-primary" value="Search" />
15     </form>
16 [% END %]
17
18 [%# Display a complex patron search form %]
19 [%# - Search: input %]
20 [%# You can then pass a list of filters %]
21 [%# - branch: select library list %]
22 [%# - category: select patron category list %]
23 [%# - sort1: select patron sort1 field %]
24 [%# - sort2: select patron sort2 field %]
25 [%# - search_field: select patron field list %]
26 [%# - search_type: select 'contains' or 'starts with' %]
27 [%- searchtype = searchtype || Koha.Preference('DefaultPatronSearchMethod') -%]
28 [% BLOCK patron_search_filters %]
29     <form id="patron_search_form">
30         <fieldset class="brief">
31             <h3>Search for patron</h3>
32             <ol>
33                 <li>
34                     <label for="search_patron_filter">Search:</label>
35                     <input type="text" id="search_patron_filter" value="[% search_filter | html %]" class="focus" />
36                 </li>
37
38                 [% FOR f IN filters %]
39                     [% SWITCH f %]
40                     [% CASE 'branch' %]
41                         <li>
42                             <label for="branchcode_filter">Library:</label>
43                             <select id="branchcode_filter">
44                                 [% SET libraries = Branches.all( only_from_group => 1 ) %]
45                                 [% IF libraries.size != 1 %]
46                                     <option value="">Any</option>
47                                 [% END %]
48                                 [% FOREACH l IN libraries %]
49                                     <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
50                                 [% END %]
51                             </select>
52                         </li>
53                     [% CASE 'category' %]
54                         <li>
55                             <label for="categorycode_filter">Category:</label>
56                             <select id="categorycode_filter">
57                                 <option value="">Any</option>
58                                 [% FOREACH category IN categories %]
59                                     <option value="[% category.categorycode | html %]">[% category.description | html %]</option>
60                                 [% END %]
61                             </select>
62                         </li>
63                     [% CASE 'sort1' %]
64                         <li>
65                             <label for="sort1_filter">Sort 1:</label>
66                             [% PROCESS 'av-build-dropbox.inc' name="sort1_filter", category="Bsort1", empty=1, size = 20 %]
67                         </li>
68                     [% CASE 'sort2' %]
69                         <li>
70                             <label for="sort2_filter">Sort 2:</label>
71                             [% PROCESS 'av-build-dropbox.inc' name="sort2_filter", category="Bsort2", empty=1, size = 20 %]
72                         </li>
73                     [% CASE 'search_field' %]
74                         <li>
75                             [% INCLUDE patron_fields_dropdown %]
76                         </li>
77                     [% CASE 'search_type' %]
78                         <li>
79                             <label for="searchtype_filter">Search type:</label>
80                             <select name="searchtype" id="searchtype_filter">
81                               [% IF searchtype == "starts_with" %]
82                                 <option value='starts_with' selected="selected">Starts with</option>
83                                 <option value="contains">Contains</option>
84                               [% ELSE %]
85                                 <option value='starts_with'>Starts with</option>
86                                 <option value="contains" selected="selected">Contains</option>
87                               [% END %]
88                             </select>
89                         </li>
90                     [% END %]
91                 [% END %]
92             </ol>
93             <fieldset class="action">
94                 <input type="submit" class="btn btn-primary" value="Search" />
95                 <input type="button" value="Clear" id="clear_search" />
96             </fieldset>
97         </fieldset>
98     </form>
99 [% END %]
100
101 [%# Display the table with: %]
102 [%# - At the top a hint about a possible filter %]
103 [%# - Browse by last name %]
104 [%# - The table %]
105 [%# Get the following parameters: %]
106 [%# - filter: can be 'suggestions_managers', 'orders_managers', 'funds_owners', 'funds_users' or 'erm_users' to filter patrons on their permissions %]
107 [%# - table_id: the ID of the table %]
108 [%# open_on_row_click: See patron_search_js %]
109 [%# columns: See patron_search_js %]
110 [% BLOCK patron_search_table %]
111
112     [% IF filter == 'suggestions_managers' %]
113         <div class="hint">Only staff with superlibrarian or suggestions_manage permissions are returned in the search results</div>
114     [% ELSIF filter == 'orders_managers' OR filter == 'baskets_managers' %]
115         <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>
116     [% ELSIF filter == 'funds_owners' OR filter == 'funds_users' %]
117         <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>
118     [% ELSIF filter == 'erm_users' %]
119         <div class="hint">Only staff with superlibrarian or ERM permissions are returned in the search results</div>
120     [% END %]
121
122     <div class="browse">
123         Browse by last name:
124         [% SET alphabet = Koha.Preference('alphabet').split(' ') %]
125         [% UNLESS alphabet.size %]
126             [% alphabet = ['A' .. 'Z'] %]
127         [% END %]
128         [% FOREACH letter IN alphabet %]
129             <a href="#" class="filterByLetter">[% letter | html %]</a>
130         [% END %]
131     </div>
132
133
134     <h3 style="display: none;">Patrons found for: <span id="searchpattern"></span></h3>
135
136     <div id="[% table_id | html %]_search_results" style="display:none;">
137
138         <div id="info" class="dialog message" style="display: none;"></div>
139         <div id="error" class="dialog alert" style="display: none;"></div>
140
141         <input type="hidden" id="firstletter_filter" value="" />
142         [% IF open_on_row_click %]
143         <table id="[% table_id | html %]" class="selections-table">
144         [% ELSE %]
145         <table id="[% table_id | html %]">
146         [% END %]
147             <thead>
148                 <tr>
149                     [% FOR column IN columns %]
150                         [% SWITCH column %]
151                             [% CASE 'checkbox' %]<th class="noExport"></th>
152                             [% CASE 'cardnumber' %]<th>Card</th>
153                             [% CASE 'dateofbirth' %]<th>Date of birth</th>
154                             [% CASE 'name' %]<th>Name</th>
155                             [% CASE 'name-address' %]<th>Name</th>
156                             [% CASE 'address' %]<th>Address</th>
157                             [% CASE 'address-library' %]<th>Address</th>
158                             [% CASE 'branch' %]<th data-filter="libraries">Library</th>
159                             [% CASE 'category' %]<th data-filter="categories">Category</th>
160                             [% CASE 'dateexpiry' %]<th>Expires on</th>
161                             [% CASE 'borrowernotes' %]<th>Notes</th>
162                             [% CASE 'phone' %]<th>Phone</th>
163                             [% CASE 'checkouts' %]<th>Checkouts</th>
164                             [% CASE 'account_balance' %]<th>Fines</th>
165                             [% CASE 'action' %]<th class="noExport">&nbsp;</th>
166                         [% END %]
167                     [% END %]
168                 </tr>
169               </thead>
170             <tbody></tbody>
171         </table>
172     </div>
173
174 <!-- Patron preview modal -->
175 <div class="modal" id="patronPreview" tabindex="-1" role="dialog" aria-labelledby="patronPreviewLabel">
176     <div class="modal-dialog" role="document">
177         <div class="modal-content">
178             <div class="modal-header">
179                 <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
180                 <h4 class="modal-title" id="patronPreviewLabel"></h4>
181             </div>
182             <div class="modal-body">
183                 <div id="loading">
184                     <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading
185                 </div>
186             </div>
187             <div class="modal-footer">
188                 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
189             </div>
190         </div>
191     </div>
192 </div>
193
194 [% END %]
195
196 [%# Integrate all the JS code, outside of a script tag %]
197 [%# Get the following parameters: %]
198 [%# - 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) %]
199 [%# - redirect_url: The URL to use, the borrowernumber parameter will be added %]
200 [%# - redirect_if_attribute_equal: Name of the attribute to use for the redirect. Query using this attribute, before the normal search %]
201 [%# filter: Same as patron_search_table %]
202 [%# open_on_row_click: boolean, default off. Will allow to select a patron by clicking on the whole tr element %]
203 [%# 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' %]
204 [%# preview_on_name_click: Open a modal window with patron's info when the name is clicked %]
205 [%# actions: list of buttons to display in the action column. Possible values are: 'select', 'add', 'edit', 'checkout' %]
206 [%# sticky_header and sticky_to: If we need a sticky header %]
207 [%# callback: name of the JS function that will be called when a patron is selected. Only work with action=select %]
208 [%# display_search_description: boolean, default off. Display the description of the search %]
209 [% BLOCK patron_search_js %]
210
211     [% IF redirect_if_one_result && !redirect_url %]
212         <script>console.log("Wrong call of patron_search_js - missing redirect_url");</script>
213     [% END %]
214
215     <script>
216         let categories = [% To.json(categories) | $raw %].map(e => {
217             e['_id'] = e.categorycode.toLowerCase();
218             e['_str'] = e.description;
219             return e;
220         });
221         let categories_map = categories.reduce((map, e) => {
222             map[e._id] = e;
223             return map;
224         }, {});
225         let libraries  = [% To.json(libraries) | $raw %].map(e => {
226             e['_id'] = e.branchcode;
227             e['_str'] = e.branchname;
228             return e;
229         });
230         let libraries_map = libraries.reduce((map, e) => {
231             map[e._id] = e;
232             return map;
233         }, {});
234
235         [% IF Koha.Preference('ExtendedPatronAttributes') %]
236             [% SET extended_attribute_types = [ ExtendedAttributeTypes.codes( staff_searchable => 1, searched_by_default => 1 ) ] %]
237             let extended_attribute_types = [% To.json(extended_attribute_types || []) | $raw %];
238         [% END %]
239
240         $(document).ready(function() {
241             $('select#sort1_filter').select2({allowClear:true});
242             $('select#sort2_filter').select2({allowClear:true});
243         });
244     </script>
245
246     [% INCLUDE 'datatables.inc' %]
247     [% INCLUDE 'js-patron-get-age.inc' %]
248     [% INCLUDE 'js-patron-format.inc' %]
249     [% INCLUDE 'js-patron-format-address.inc' %]
250     [% IF sticky_header %]
251         [% Asset.js("lib/hc-sticky.js") | $raw %]
252     [% END %]
253
254     <script>
255         var first_draw = 0;
256         let patrons_table;
257         var Sticky;
258         var singleBranchMode = '[% singleBranchMode | html %]';
259         let logged_in_library_id = "[% Branches.GetLoggedInBranchcode | html %]";
260         [% IF do_not_defer_loading %]
261             let defer_loading = 0;
262         [% ELSE %]
263             let defer_loading = 1;
264         [% END %]
265
266         /* popstate event triggered by forward and back button. Need to refresh search */
267         window.addEventListener('popstate', (event) => {
268             getSearchByLocation( false );
269         });
270
271         [% SWITCH filter %]
272         [% CASE 'suggestions_managers' %]
273             let patron_search_url = '/api/v1/suggestions/managers';
274         [% CASE 'baskets_managers' %]
275             let patron_search_url = '/api/v1/acquisitions/baskets/managers';
276         [% CASE 'funds_owners' %]
277             let patron_search_url = '/api/v1/acquisitions/funds/owners';
278         [% CASE 'funds_users' %]
279             let patron_search_url = '/api/v1/acquisitions/funds/users';
280         [% CASE 'erm_users' %]
281             let patron_search_url = '/api/v1/erm/users';
282         [% CASE %]
283             let patron_search_url = '/api/v1/patrons';
284         [% END %]
285         $(document).ready(function(){
286
287             $("#info").hide();
288             $("#error").hide();
289
290             // Build the aLengthMenu
291             var aLengthMenu = [
292                 [% Koha.Preference('PatronsPerPage') | html %], 10, 20, 50, 100, -1
293             ];
294             jQuery.unique(aLengthMenu);
295             aLengthMenu.sort(function( a, b ){
296                 // Put "All" at the end
297                 if ( a == -1 ) {
298                     return 1;
299                 } else if ( b == -1 ) {
300                     return -1;
301                 }
302                 return parseInt(a) < parseInt(b) ? -1 : 1;}
303             );
304             var aLengthMenuLabel = [];
305             $(aLengthMenu).each(function(){
306                 if ( this == -1 ) {
307                     // Label for -1 is "All"
308                     aLengthMenuLabel.push(_("All"));
309                 } else {
310                     aLengthMenuLabel.push(this);
311                 }
312             });
313
314             let additional_filters = {
315                 surname: function(){
316                     let start_with = $("#firstletter_filter").val()
317                     if (!start_with) return "";
318                     return { "like": start_with + "%" }
319                 },
320                 "-and": function(){
321                     let filters = [];
322
323                     let search_type = $("#searchtype_filter").val();
324                     let search_fields = $("#searchfieldstype_filter").val() || "standard";
325                     let pattern = $("#search_patron_filter").val();
326
327                     filters = buildPatronSearchQuery(
328                         pattern,
329                         {
330                             search_type: search_type,
331                             search_fields: search_fields,
332                             ...(typeof extended_attribute_types != 'undefined' && {extended_attribute_types: extended_attribute_types})
333                         }
334                     );
335
336                     let f_sort1 = $("#sort1_filter").val();
337                     if ( f_sort1 ) {
338                         filters.push({
339                             "me.sort1": f_sort1
340                         });
341                     }
342                     let f_sort2 = $("#sort2_filter").val();
343                     if ( f_sort2 ) {
344                         filters.push({
345                             "me.sort2": f_sort2
346                         });
347                     }
348
349                     return filters;
350                 }
351             };
352
353             [% UNLESS default_sort_column %]
354                 [% default_sort_column = "name" %]
355             [% END %]
356             [% SET order_column_index = 0 %]
357             [% SET embed = ['extended_attributes'] %]
358             patrons_table = $("#[% table_id | html %]").kohaTable({
359                 "ajax": {
360                     "url": patron_search_url,
361                     "dataSrc": function ( json ) {
362                         [% IF redirect_if_one_result %]
363                             // redirect if there is only 1 result.
364                             if ( first_draw && json.recordsFiltered == 1 ) {
365                                 let url = '[% redirect_url | url %]'.indexOf("?") != -1
366                                     ? '[% redirect_url | url %]&borrowernumber=' + json.data[0].patron_id
367                                     : '[% redirect_url | url %]?borrowernumber=' + json.data[0].patron_id;
368                                 document.location.href = url;
369                                 return false;
370                             }
371                             first_draw = 0;
372                         [% END %]
373                         return json.data;
374                     }
375                 },
376                 [% IF open_on_row_click OR preview_on_name_click OR remember_selections %]
377                 "drawCallback": function( settings ) {
378                     var api = this.api();
379                     var data = api.data();
380                     if ( data.length == 0 ) return;
381
382                     [% IF open_on_row_click %]
383                     $.each($(this).find("tbody tr"), function(index, tr) {
384                         let url = "[% on_click_url | url %]&borrowernumber=" + data[index].patron_id;
385                         $(tr).off('click').on('click', function() {
386                             document.location.href = url;
387                         }).addClass('clickable');
388                         $(tr).find("a.patron_name").attr('href', url);
389                     });
390                     [% END %]
391                     [% IF preview_on_name_click %]
392                     $.each($(this).find("tbody tr"), function(index, tr) {
393                         $(tr).find("a.patron_name").addClass("patron_preview");
394                     });
395                     [% END %]
396                     [% IF remember_selections %]
397                         prepSelections();
398                     [% END %]
399                 },
400                 [% END %]
401                 "deferLoading": defer_loading,
402                 "columns": [
403                     [% FOR column IN columns %]
404                         [% IF default_sort_column == column %]
405                             [% order_column_index = loop.count - 1%]
406                         [% END %]
407                         [% SWITCH column %]
408                             [% CASE 'checkbox' %]
409                             {
410                                 "data": "patron_id",
411                                 "searchable": false,
412                                 "orderable": false,
413                                 "render": function( data, type, row, meta ) {
414                                     return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' id='check" + data + "' class='selection' name='borrowernumber' value='" + data + "' />";
415                                 }
416                             }
417                             [% CASE 'cardnumber' %]
418                             {
419                                 "data": "cardnumber",
420                                 "searchable": true,
421                                 "orderable": true,
422                                 "render": function( data, type, row, meta ) {
423                                     let patron_id = encodeURIComponent(row.patron_id);
424                                     [% IF !open_on_row_click AND CAN_user_circulate_circulate_remaining_permissions %]
425                                         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>";
426                                     [% ELSE %]
427                                         return escape_str(data);
428                                     [% END %]
429                                 }
430
431                             }
432                             [% CASE 'dateofbirth' %]
433                             {
434                                 "data": "date_of_birth",
435                                 "type": "date",
436                                 "searchable": true,
437                                 "orderable": true,
438                                 "render": function( data, type, row, meta ) {
439                                     return data ? "<span class=\"dateofbirth\">" + escape_str($date(data)) + "<span class=\"agehint\"> (" + _("%s years").format($get_age(data)) + ")</span></span>" : "";
440                                 }
441                             }
442                             [% CASE 'address' %]
443                             {
444                                 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
445                                 "searchable": true,
446                                 "orderable": true,
447                                  "render": function( data, type, row, meta ) {
448                                     let r = '<div class="address"><ul>';
449                                     r += $format_address(row, { no_line_break: true, include_li: true });
450                                     r += '</div></ul>';
451                                     return r;
452                                 }
453                             }
454                             [% CASE 'address-library' %]
455                             {
456                                 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
457                                 "searchable": true,
458                                 "orderable": true,
459                                 "render": function( data, type, row, meta ) {
460                                     let r = '<div class="address"><ul>';
461                                     r += $format_address(row, { no_line_break: true, include_li: true });
462                                     r += '</div></ul>';
463                                     r += " " + escape_str(libraries_map[row.library_id].branchname);
464                                     return r;
465                                 }
466                             }
467                             [% CASE 'name-address' %]
468                             {
469                                 "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",
470                                 "searchable": true,
471                                 "orderable": true,
472                                 "render": function( data, type, row, meta ) {
473                                     let patron_id = encodeURIComponent(row.patron_id);
474                                     let r = '';
475                                     [% IF ! open_on_row_click %]
476                                     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>";
477                                     [% ELSE %]
478                                     r += $patron_to_html(row, { invert_name: 1 });
479                                     [% END %]
480                                     r += '<br/>';
481                                     r += '<div class="address"><ul>';
482                                     r += $format_address(row, { no_line_break: true, include_li: true });
483
484                                     if ( row.email ) {
485                                         r += "<li>" + _("Email: ") + "<a href='mailto:" + encodeURIComponent(row.email) + "'>" + escape_str(row.email) + "</a></li>";
486                                     }
487                                     r += '</ul></div>'
488
489                                     return r;
490                                 }
491                             }
492                             [% CASE 'name' %]
493                             {
494                                 "data": "me.surname:me.firstname:me.middle_name:me.othernames",
495                                 "searchable": true,
496                                 "orderable": true,
497                                 "render": function( data, type, row, meta ) {
498                                     let patron_id = encodeURIComponent(row.patron_id);
499                                     [% IF ! open_on_row_click %]
500                                     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>";
501                                     [% ELSE %]
502                                     return $patron_to_html(row, { invert_name: 1 });
503                                     [% END %]
504                                 }
505                             }
506                             [% CASE 'branch' %]
507                             {
508                                 "data": "library_id",
509                                 "searchable": true,
510                                 "orderable": true,
511                                 "render": function( data, type, row, meta ) {
512                                     let library_name = libraries_map[data].branchname
513                                     if( !singleBranchMode && data == logged_in_library_id ) {
514                                         return "<span class=\"currentlibrary\">" + escape_str(library_name) + "</span>";
515                                     } else {
516                                         return escape_str(library_name);
517                                     }
518                                 }
519                             }
520                             [% CASE 'category' %]
521                             {
522                                 "data": "category_id",
523                                 "searchable": true,
524                                 "orderable": true,
525                                 "render": function( data, type, row, meta ) {
526                                     return escape_str(categories_map[data.toLowerCase()].description);
527                                 }
528                             }
529                             [% CASE 'dateexpiry' %]
530                             {
531                                 "data": "expiry_date",
532                                 "type": "date",
533                                 "searchable": true,
534                                 "orderable": true,
535                                 "render": function( data, type, row, meta ) {
536                                     return data ? escape_str($date(data)) : "";
537                                 }
538                             }
539                             [% CASE 'borrowernotes' %]
540                             {
541                                 "data": "staff_notes",
542                                 "searchable": true,
543                                 "orderable": true,
544                                 [%# We don't escape here, we allow html tag in staff notes %]
545                             }
546                             [% CASE 'phone' %]
547                             {
548                                 "data": "phone",
549                                 "searchable": true,
550                                 "orderable": true,
551                                 "render": function( data, type, row, meta ) {
552                                     return escape_str(data);
553                                 }
554                             }
555                             [% CASE 'checkouts' %][% embed.push('checkouts+count', 'overdues+count') %]
556                             {
557                                 "data": "",
558                                 "searchable": false,
559                                 "orderable": false,
560                                 "render": function( data, type, row, meta ) {
561                                     if ( row.overdues_count ) {
562                                         return "<span class='overdue'><strong>"+row.overdues_count + "</strong></span>" + " / " + row.checkouts_count;
563                                     } else {
564                                         return "0 / " + row.checkouts_count;
565                                     }
566                                 }
567                             }
568                             [% CASE 'account_balance' %][% embed.push('account_balance') %]
569                             {
570                                 "data": "",
571                                 "searchable": false,
572                                 "orderable": false,
573                                 "render": function( data, type, row, meta ) {
574                                     let r = "<span style='text-align: right; display: block;'><a href=\"/cgi-bin/koha/members/boraccount.pl?borrowernumber="+row.patron_id+"\">";
575                                     let balance_str = row.account_balance || 0;
576                                     balance_str = balance_str.escapeHtml().format_price();
577                                     if ( row.account_balance < 0 ) {
578                                         // FIXME Format price here
579                                         r += "<span class='credit'>" + balance_str + "</span>";
580                                     } else if ( row.account_balance > 0 ) {
581                                         r += "<span class='debit'><strong>" + balance_str  + "</strong></span>"
582                                     } else {
583                                         r += balance_str;
584                                     }
585                                     r += "</a></span>";
586                                     return r;
587                                 }
588                             }
589
590                             [% CASE 'action' %]
591                             {
592                                 "data": function( row, type, val, meta ) {
593
594                                     let patron_id = encodeURIComponent(row.patron_id);
595                                     let action_node = "";
596                                     [% FOR action IN actions %]
597                                     [% SWITCH action %]
598                                     [% CASE 'select' %]
599                                         action_node += '<a href="#" class="btn btn-default btn-xs select_user" data-borrowernumber="' + patron_id + '">' + _("Select") + '</a>';
600                                     [% CASE 'add' %]
601                                         action_node += '<a href="#" class="btn btn-default btn-xs add_user" data-borrowernumber="' + patron_id + '">' + _("Add") + '</a>';
602                                     [% CASE 'edit' %]
603                                         action_node += '<a href="/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=' + patron_id + '" class="btn btn-default btn-xs"><i class="fa-solid fa-pencil" aria-hidden="true"></i> ' + _("Edit") + '</a>';
604                                     [% CASE 'checkout' %]
605                                         [% IF CAN_user_circulate_circulate_remaining_permissions %]
606                                             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>';
607                                         [% END %]
608                                     [% END %]
609                                     [% END %]
610
611                                     let patron_str = JSON.stringify(row);
612                                     let input_node = $('<input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '"/>');
613                                     $(input_node).val(patron_str);
614                                     action_node += $(input_node).prop('outerHTML');
615
616                                     return action_node;
617                                 },
618                                 "searchable": false,
619                                 "orderable": false
620                             }
621                         [% END %]
622                         [% UNLESS loop.last %],[% END %]
623                     [% END %]
624                 ],
625                 'embed': [% To.json(embed) | $raw %],
626                 "order": [[ [% order_column_index | html %], "asc" ]],
627                 "autoWidth": false,
628                 'lengthMenu': [aLengthMenu, aLengthMenuLabel],
629                 "pagingType": 'full_numbers',
630                 "pageLength": [% Koha.Preference('PatronsPerPage') | html %],
631                 [% IF sticky_header %]
632                 "initComplete": function(settings, json) {
633                     $("#[% sticky_header | html %]").show();
634                     Sticky = $("#[% sticky_header | html %]");
635                     Sticky.hcSticky({
636                         stickTo: "#[% sticky_to | html %]",
637                         stickyClass: "floating"
638                     });
639                 },
640                 [% END %]
641             }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters);
642
643             $("#patron_search_form").on('submit', filter);
644             $("#patron_search_form").on('submit', update_search_type);
645             $(".filterByLetter").on("click",function(e){
646                 e.preventDefault();
647                 filterByFirstLetterSurname($(this).text(), true);
648             });
649             $("body").on("click",".add_user",function(e){
650                 e.preventDefault();
651                 var borrowernumber = $(this).data("borrowernumber");
652                 var borrower_data = JSON.parse($("#borrower_data"+borrowernumber).val());
653                 add_user( borrowernumber, borrower_data.firstname + " " + borrower_data.surname );
654             });
655
656             $("body").on("click",".select_user",function(e){
657                 e.preventDefault();
658                 var borrowernumber = $(this).data("borrowernumber");
659                 var borrower_data = JSON.parse($("#borrower_data"+borrowernumber).val());
660                 select_user( borrowernumber, borrower_data );
661             });
662
663             $("body").on("click",".patron_preview", function( e ){
664                 e.preventDefault();
665                 var borrowernumber = $(this).data("borrowernumber");
666                 var page = "/cgi-bin/koha/members/moremember.pl?print=brief&borrowernumber=" + borrowernumber;
667                 $("#patronPreview .modal-body").load( page + " div.container-fluid" );
668                 $('#patronPreview').modal({show:true});
669             });
670
671             $("#patronPreview").on('hidden.bs.modal', function (e) {
672                 $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading");
673             });
674
675             $("#clear_search").on("click",function(e){
676                 e.preventDefault();
677                 clearFilters();
678                 $("#searchpattern").parent().hide();
679             });
680
681             if ( !defer_loading ) {
682                 $("#patron_search_form").submit();
683             }
684
685             /* Initial page load does not trigger the popstate event, so we explicitly call this */
686             getSearchByLocation( false );
687
688         });
689
690         function getSearchByLocation( setstate ){
691             /* Check to see if the URL contains a search parameter */
692             if( location.search != ""){
693                 var params = new URLSearchParams( location.search );
694                 var firstletter = params.get("firstletter");
695                 /* Check to see if search is a first letter param */
696                 if( firstletter ){
697                     /* Trigger function to return search results by letter */
698                     filterByFirstLetterSurname( firstletter, setstate );
699                 }
700             }
701         }
702
703         function update_search_type(){
704             $("#searchtype").val($("#searchtype_filter").val());
705         }
706
707         function update_search_description(){
708             var searched = $("#searchfieldstype_filter").find("option:selected").text();
709             if ( $("#search_patron_filter").val() ) {
710                 if ( $("#searchtype_filter").val() == 'starts_with' ) {
711                     searched += _(" starting with ");
712                 } else {
713                     searched += _(" containing ");
714                 }
715                 searched += "'" + $("#search_patron_filter").val() + "'";
716             }
717             if ( $("#firstletter_filter").val() ) {
718                 searched += _(" begins with ") + "'" + $("#firstletter_filter").val() +"'";
719             }
720             if ( $("#categorycode_filter").val() ) {
721                 searched += _(" with category ") + "'" + $("#categorycode_filter").find("option:selected").text() + "'";
722             }
723             if ( $("#branchcode_filter").val() ) {
724                 searched += _(" in library ") + $("#branchcode_filter").find("option:selected").text();
725             }
726             if ( $("#sort1_filter").val() ) {
727                 searched += _(" with sort1 ")
728                 if ( $("select#sort1_filter") ) {
729                     searched += $("select#sort1_filter").find("option:selected").text();
730                 }
731                 else {
732                     searched += $("#sort1_filter").val();
733                 }
734             }
735             if ( $("#sort2_filter").val() ) {
736                 searched += _(" with sort2 ");
737                 if ( $("select#sort2_filter") ) {
738                     searched += $("select#sort2_filter").find("option:selected").text();
739                 }
740                 else {
741                     searched += $("#sort2_filter").val();
742                 }
743             }
744             $("#searchpattern").text(searched);
745             $("#searchpattern").parent().show();
746         }
747
748         function filter() {
749             [% IF redirect_if_attribute_equal %]
750                 let filter = $("#search_patron_filter").val();
751                 if ( filter ) {
752                     $.ajax({
753                         data: { cardnumber: filter, _match: 'exact' },
754                         type: 'GET',
755                         url: patron_search_url,
756                         success: function(data) {
757                             if ( data.length == 1 ) {
758                                 let url = '[% redirect_url | url %]'.indexOf("?") != -1
759                                     ? '[% redirect_url | url %]&borrowernumber=' + data[0].patron_id
760                                     : '[% redirect_url | url %]?borrowernumber=' + data[0].patron_id;
761                                 document.location.href = url;
762                                 return false;
763                             }
764                         },
765                         error: function() {
766                             alert( _("An error occurred. Check the logs for details.") );
767                         }
768                     });
769                 }
770             [% END %]
771             $("#firstletter_filter").val('');
772             $("#[% table_id | html %]_search_results").show();
773
774             let table_dt = patrons_table.DataTable();
775             [% FOR c IN columns %]
776                 [% SWITCH c %]
777                 [% CASE 'branch' %]
778                     library_id = $("#branchcode_filter").val() || "";
779                     patrons_table.find('thead tr:eq(1) th[data-filter="libraries"] select').val(library_id);
780                     table_dt.column([% loop.count - 1 %]).search(library_id ? '^'+library_id+'$' : '');
781                 [% CASE 'category' %]
782                     let category_id = $("#categorycode_filter").val() || "";
783                     patrons_table.find('thead tr:eq(1) th[data-filter="categories"] select').val(category_id);
784                     table_dt.column([% loop.count - 1 %]).search(category_id ? '^'+category_id+'$' : '');
785                 [% END %]
786             [% END %]
787             table_dt.search("");
788             first_draw = 1; // Only redirect if we are coming from here
789             table_dt.draw();
790             [% IF display_search_description %]
791                 update_search_description();
792             [% END %]
793             return false;
794         }
795
796         function clearFilters() {
797             $("#searchfieldstype_filter option:first").prop("selected", true);
798             $("#searchtype_filter option[value='[% searchtype | html %]']").prop("selected", true);
799             $("#categorycode_filter option:first").prop("selected", true);
800             $("#branchcode_filter option:first").prop("selected", true);
801             $("#sort1_filter").val('').trigger("change");
802             $("#sort2_filter").val('').trigger("change");
803             $("#firstletter_filter").val('');
804             $("#search_patron_filter").val('');
805             /* remove any search string added by firstletter search */
806             history.pushState( {}, null, window.location.href.split("?" )[0]);
807             $("#[% table_id | html %]_search_results").hide();
808             [% IF display_search_description %]
809                 update_search_description();
810             [% END %]
811         }
812
813         // User has clicked on a letter
814         function filterByFirstLetterSurname(letter, setstate ) {
815             $("#firstletter_filter").val(letter);
816
817             $("#[% table_id | html %]_search_results").show();
818
819             if ( setstate ) {
820                 history.pushState( null, null, "?firstletter=" + letter );
821             }
822
823             patrons_table.DataTable().draw();
824             [% IF display_search_description %]
825                 update_search_description();
826             [% END %]
827         }
828
829         // modify parent window owner element
830         function add_user(borrowernumber, borrowername) {
831             var p = window.opener;
832             // In one place (serials/routing.tt), the page is reload on every add
833             // We have to wait for the page to be there
834             function wait_for_opener () {
835                 if ( ! $(opener.document).find('body').size() ) {
836                     setTimeout(wait_for_opener, 500);
837                 } else {
838                     [%# Note that add_user could sent data instead of borrowername too %]
839                     $("#info").hide();
840                     $("#error").hide();
841                     if ( p.add_user(borrowernumber, borrowername) < 0 ) {
842                         $("#error").html(_("Patron '%s' is already in the list.").format(borrowername));
843                         $("#error").show();
844                     } else {
845                         $("#info").html(_("Patron '%s' added.").format(borrowername));
846                         $("#info").show();
847                     }
848                 }
849             }
850             wait_for_opener();
851         }
852         function select_user(borrowernumber, data) {
853             var p = window.opener;
854             if ( p.document.getElementById("selected_patron_id") ) {
855                 p.document.getElementById("selected_patron_id").value = borrowernumber;
856             } else {
857                 [%  IF callback %]
858                     p.[% callback | html %](borrowernumber, data);
859                 [%  ELSE %]
860                     p.select_user(borrowernumber, data);
861                 [% END %]
862             }
863             window.close();
864         }
865     </script>
866 [% END %]