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