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