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