4 [% USE TablesSettings %]
8 [% PROCESS 'patronfields.inc' %]
9 [% INCLUDE 'doc-head-open.inc' %]
10 <title>Patrons[% IF ( searching ) %] › Search results[% END %] › Koha</title>
11 [% INCLUDE 'doc-head-close.inc' %]
14 <body id="pat_member" class="pat">
15 [% INCLUDE 'header.inc' %]
16 [% INCLUDE 'patron-search.inc' %]
18 <nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
21 <a href="/cgi-bin/koha/mainpage.pl">Home</a>
23 [% IF ( searching ) %]
25 <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>
28 <a href="#" aria-current="page">
34 <a href="#" aria-current="page">
42 <div class="main container-fluid">
44 <div class="col-sm-10 col-sm-push-2">
47 [% IF CAN_user_tools_manage_patron_lists %]
48 <div id="patron_list_dialog" class="dialog message">
49 Added <span class="patrons-length"></span> patrons to <a></a>.
53 [% INCLUDE 'patron-toolbar.inc' %]
54 [% INCLUDE 'noadd-warnings.inc' %]
58 [% FOREACH letter IN alphabet.split(' ') %]
59 <a href="#" class="filterByLetter">[% letter | html %]</a>
63 [% IF CAN_user_borrowers_edit_borrowers && pending_borrower_modifications %]
64 <div class="pending-info" id="patron_updates_pending">
65 <a href="/cgi-bin/koha/members/members-update.pl">Patrons requesting modifications</a>:
66 <span class="number_box"><a href="/cgi-bin/koha/members/members-update.pl">[% pending_borrower_modifications | html %]</a></span>
70 <div id="searchresults">
71 <h3>Patrons found for: <span id="searchpattern">[% IF searchmember %] for '[% searchmember | html %]'[% END %]</span></h3>
72 [% IF CAN_user_tools_manage_patron_lists || CAN_user_borrowers_edit_borrowers %]
73 <div class="searchheader fh-fixedHeader" id="searchheader">
75 <a href="#" class="btn btn-link" id="select_all"><i class="fa fa-check"></i> Select all</a>
77 <a href="#" class="btn btn-link" id="clear_all"><i class="fa fa-remove"></i> Clear all</a>
78 [% IF CAN_user_tools_manage_patron_lists %]
82 [% IF CAN_user_tools_manage_patron_lists %]
83 <div id="patronlist-dropdown" class="btn-group">
84 <button id="patronlist-menu" type="button" class="btn btn-sm btn-default dropdown-toggle patron-edits disabled" disabled="disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
85 Add to patron list <span class="caret"></span>
87 <ul class="dropdown-menu">
89 [% FOREACH pl IN patron_lists %]
90 <li><a href="#" class="patron-list-add" data-listid="[% pl.patron_list_id | html %]">[% pl.name | html %]</a></li>
93 <li role="separator" class="divider"></li>
94 <li><a href="#" class="patron-list-add" data-listid="new">New list</a></li>
99 [% IF CAN_user_borrowers_edit_borrowers %]
100 <button id="merge-patrons" class="btn btn-sm btn-default disabled" disabled="disabled" type="submit"><i class="fa fa-compress" aria-hidden="true"></i> Merge selected patrons</button>
106 <table id="memberresultst">
109 [% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
110 <th class="noExport"> </th>
114 <th>Date of birth</th>
118 <th>OD/Checkouts</th>
121 <th class="noExport"> </th>
129 </div> <!-- /.col-sm-10.col-sm-push-2 -->
131 <div class="col-sm-2 col-sm-pull-10">
133 <form method="get" id="searchform">
134 <input type="hidden" id="firstletter_filter" value="" />
135 <fieldset class="brief">
136 <h3>Search patrons</h3>
139 <label for="searchmember_filter">Search for:</label>
140 <input type="text" id="searchmember_filter" value="[% searchmember | html %]"/>
143 <label for="searchfieldstype_filter">Search field:</label>
144 <select name="searchfieldstype" id="searchfieldstype_filter">
145 [% pref_fields = Koha.Preference('DefaultPatronSearchFields').split(',') %]
146 [% default_fields = [ 'standard', 'surname', 'cardnumber', 'email', 'borrowernumber', 'userid', 'phone', 'address', 'dateofbirth', 'sort1', 'sort2' ] %]
147 [% search_options = default_fields.merge(pref_fields).unique %]
148 [% FOREACH s_o IN search_options %]
149 [% display_name = PROCESS patron_fields name=s_o %]
150 [% NEXT IF !display_name %]
151 [% IF searchfieldstype == s_o %]
152 <option selected="selected" value=[% s_o | html %]>[% display_name | $raw %]</option>
154 <option value=[% s_o | html %]>[% display_name | $raw %]</option>
160 <label for="searchtype_filter">Search type:</label>
161 <select name="searchtype" id="searchtype_filter">
162 [% IF searchtype == "start_with" %]
163 <option value='start_with' selected="selected">Starts with</option>
164 <option value="contain">Contains</option>
166 <option value='start_with'>Starts with</option>
167 <option value="contain" selected="selected">Contains</option>
172 <label for="categorycode_filter">Patron category:</label>
173 [% SET categories = Categories.all() %]
174 <select id="categorycode_filter">
175 <option value="">Any</option>
176 [% FOREACH cat IN categories %]
177 [% IF cat.categorycode == categorycode_filter %]
178 <option selected="selected" value="[% cat.categorycode | html %]">[% cat.description | html %]</option>
180 <option value="[% cat.categorycode | html %]">[% cat.description | html %]</option>
187 <label for="branchcode_filter">Library:</label>
188 [% SET branches = Branches.all( selected => branchcode_filter, only_from_group => 1 ) %]
189 <select id="branchcode_filter">
190 [% IF branches.size != 1 %]
191 <option value="">Any</option>
193 [% PROCESS options_for_libraries libraries => branches %]
197 <fieldset class="action">
198 <input type="submit" value="Search" />
199 <input type="button" value="Clear" id="clear_search" />
204 </div> <!-- /.col-sm-2.col-sm-pull-10 -->
205 </div> <!-- /.row -->
207 <!-- New Patron List Modal -->
208 <div class="modal" id="new-patron-list" tabindex="-1" role="dialog" aria-labelledby="new-patron-listLabel">
209 <div class="modal-dialog" role="document">
210 <div class="modal-content">
211 <div class="modal-header">
212 <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
213 <h4 class="modal-title" id="new-patron-listLabel">Add patrons to a new patron list</h4>
215 <form id="new-patron-list_form">
216 <div class="modal-body">
217 <div class="form-group">
218 <label for="new_patron_list" class="required">Patron list name: </label>
219 <input class="form-control required" type="text" name="new_patron_list" id="new_patron_list" required="required" />
220 <input type="hidden" name="add_to_patron_list" id="add_to_patron_list" />
221 <span class="required">Required</span>
223 </div> <!-- /.modal-body -->
224 <div class="modal-footer">
225 <button type="submit" id="add_to_patron_list_submit" class="btn btn-default approve">Submit</button>
226 <button type="button" class="btn btn-default deny" data-dismiss="modal">Cancel</button>
227 </div> <!-- /.modal-footer -->
228 </form> <!-- /#new-patron-list_form -->
229 </div> <!-- /.modal-content -->
230 </div> <!-- /.modal-dialog -->
231 </div> <!-- /#new-patron-list -->
233 [% MACRO jsinclude BLOCK %]
234 [% INCLUDE 'datatables.inc' %]
235 [% INCLUDE 'columns_settings.inc' %]
236 [% INCLUDE 'str/members-menu.inc' %]
237 [% Asset.js("lib/hc-sticky.js") | $raw %]
238 [% Asset.js("js/members-menu.js") | $raw %]
240 var singleBranchMode = '[% singleBranchMode | html %]';
243 /* popstate event triggered by forward and back button. Need to refresh search */
244 window.addEventListener('popstate', (event) => {
245 getSearchByLocation( false );
248 $(document).ready(function() {
249 $('#merge-patrons').prop('disabled', true);
250 $('#memberresultst').on('change', 'input.selection', function() {
251 if ( $('.selection:checked').length > 1 ) {
252 /* More than one checkbox has been checked */
253 $('#merge-patrons').prop('disabled', false).removeClass("disabled");
254 $("#patronlist-menu").removeClass("disabled").prop("disabled", false);
255 } else if ( $('.selection:checked').length == 1 ) {
256 /* At least one checkbox has been checked */
257 $('#merge-patrons').prop('disabled', true).addClass("disabled");
258 $("#patronlist-menu").removeClass("disabled").prop("disabled", false);
260 /* No checkbox has been checked */
261 $('#merge-patrons').prop('disabled', true).addClass("disabled");
262 $("#patronlist-menu").addClass("disabled").prop("disabled", true);
266 $('#merge-patrons').on('click', function() {
267 var merge_patrons_url = 'merge-patrons.pl?' + $('.selection:checked')
269 return "id=" + $(this).val()
271 window.location.href = merge_patrons_url;
274 $("#patronlist-dropdown").on("click", ".patron-list-add", function(e){
277 if ( $("#memberresultst input:checkbox:checked").length == 0 ) {
278 alert( _("You have not selected any patrons to add to a list!") );
279 $(".btn-group").removeClass("open"); /* Close button menu */
283 var listid = $(this).data("listid");
284 $("#add_to_patron_list").val( listid );
285 if( listid == "new" ){
286 /* #add_to_patron_list value "new" in the modal form will tell API to create a new list */
287 $("#new-patron-list").modal("show");
289 /* Ajax submit the patrons to list */
295 /* Submit selected patrons to a list via AJAX */
296 $("#new-patron-list_form").on('submit', function(e){
298 /* Upon submitting modal patron list add form... */
299 if ( $('#new_patron_list').val() ) {
300 $(".patron-list-add").each(function() {
301 /* Check each list name in the menu of patron lists */
302 /* If submitted list name matches... */
303 if ( $(this).text() == $('#new_patron_list').val() ) {
304 alert( _("You already have a list with that name!") );
309 alert( _("You must give your new patron list a name!") );
312 $("#new-patron-list").modal("hide");
316 $(".filterByLetter").on("click",function(e){
318 filterByFirstLetterSurname( $(this).text(), true );
320 $("#select_all").on("click",function(e){
322 $(".selection").prop("checked", true).change();
324 $("#clear_all").on("click",function(e){
326 $(".selection").prop("checked", false).change();
328 $("#clear_search").on("click",function(e){
332 $("#searchform").on("submit", filter);
334 [% IF searchmember %]
335 $("#searchmember_filter").val("[% searchmember | html %]");
337 [% IF searchfieldstype %]
338 $("searchfieldstype_filter").val("[% searchfieldstype | html %]");
341 $("#searchtype_filter").val("[% searchtype | html %]");
343 [% IF categorycode %]
344 $("#categorycode_filter").val("[% categorycode_filter | html %]");
347 $("#branchcode_filter").val("[% branchcode_filter | html %]");
350 [% IF view != "show_results" %]
353 $("#searchresults").show();
356 // Build the aLengthMenu
358 [% PatronsPerPage | html %], 10, 20, 50, 100, -1
360 jQuery.unique(aLengthMenu);
361 aLengthMenu.sort(function( a, b ){
362 // Put "All" at the end
365 } else if ( b == -1 ) {
368 return parseInt(a) < parseInt(b) ? -1 : 1;}
370 var aLengthMenuLabel = [];
371 $(aLengthMenu).each(function(){
373 // Label for -1 is "All"
374 aLengthMenuLabel.push(_("All"));
376 aLengthMenuLabel.push(this);
380 // Apply DataTables on the results table
381 var columns_settings = [% TablesSettings.GetColumns( 'members', 'member', 'memberresultst', 'json' ) | $raw %];
382 [% UNLESS CAN_user_borrowers_edit_borrowers OR CAN_user_tools_manage_patron_lists %]
383 [%# Remove the first column if we do not display the checkbox %]
384 columns_settings.splice(0, 1);
386 dtMemberResults = KohaTable("memberresultst", {
388 'sAjaxSource': "/cgi-bin/koha/svc/members/search",
389 'fnServerData': function(sSource, aoData, fnCallback) {
394 'name': 'searchmember',
395 'value': $("#searchmember_filter").val()
397 'name': 'firstletter',
398 'value': $("#firstletter_filter").val()
400 'name': 'searchfieldstype',
401 'value': $("#searchfieldstype_filter").val()
403 'name': 'searchtype',
404 'value': $("#searchtype_filter").val()
406 'name': 'categorycode',
407 'value': $("#categorycode_filter").val()
409 'name': 'branchcode',
410 'value': $("#branchcode_filter").val()
412 'name': 'name_sorton',
413 'value': 'borrowers.surname borrowers.firstname'
415 'name': 'cardnumber_sorton',
416 'value': 'borrowers.cardnumber',
418 'name': 'dateofbirth_sorton',
419 'value': 'borrowers.dateofbirth',
421 'name': 'dateexpiry_sorton',
422 'value': 'borrowers.dateexpiry',
424 'name': 'category_sorton',
425 'value': 'categories.description',
427 'name': 'branch_sorton',
428 'value': 'branches.branchname'
430 'name': 'borrowernotes_sorton',
431 'value': 'borrowers.borrowernotes'
433 'name': 'template_path',
434 'value': 'members/tables/members_results.tt',
441 'success': function(json){
442 // redirect if there is only 1 result.
443 if ( json.aaData.length == 1 && aoData.iDisplayStart == 0 ) {
444 var borrowernumber = json.aaData[0].borrowernumber;
445 /* Overwrite history state of firstletter search since only one result was returned; This prevents a loop upon clicking back */
446 history.replaceState( {}, null, window.location.href.split("?" )[0]);
447 [% IF circsearch == 1 %]
448 [% SET redirect = "circ/circulation" %]
450 [% SET redirect = "members/moremember" %]
452 document.location.href="/cgi-bin/koha/[% redirect | $raw %].pl?borrowernumber="+borrowernumber;
460 [% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
461 { 'mDataProp': 'dt_borrowernumber', 'bSortable': false },
463 { 'mDataProp': 'dt_cardnumber' },
464 { 'mDataProp': 'dt_name' },
465 { 'mDataProp': 'dt_dateofbirth' },
466 { 'mDataProp': 'dt_category' },
468 'mDataProp': function ( oObj ) {
469 if( !singleBranchMode && oObj.dt_branch == "[% Branches.GetLoggedInBranchname | html %]" ){
470 return "<span class=\"currentlibrary\">" + oObj.dt_branch + "</span>";
472 return oObj.dt_branch;
476 { 'mDataProp': 'dt_dateexpiry' },
477 { 'mDataProp': 'dt_od_checkouts', 'bSortable': false },
478 { 'mDataProp': 'dt_fines', 'bSortable': false },
479 { 'mDataProp': 'dt_borrowernotes' },
480 { 'mDataProp': 'dt_action', 'bSortable': false, 'sClass': 'actions' }
484 [% IF CAN_user_borrowers_edit_borrowers || CAN_user_tools_manage_patron_lists %]
485 'aaSorting': [[2, 'asc']],
487 'aaSorting': [[1, 'asc']],
489 "aLengthMenu": [aLengthMenu, aLengthMenuLabel],
490 'sPaginationType': 'full_numbers',
491 "iDisplayLength": [% PatronsPerPage | html %],
493 "initComplete": function(settings, json) {
494 Sticky = $("#searchheader");
496 stickTo: "#searchresults",
497 stickyClass: "floating"
500 }, columns_settings);
502 /* Initial page load doesn't trigger the popstate event, so we explicitly call this */
503 getSearchByLocation( false );
507 function patronListAdd(){
508 var borrowernumbers = [];
509 $("#memberresultst").find("input:checkbox:checked").each(function(){
510 borrowernumbers.push($(this).val());
513 add_to_patron_list: $("#add_to_patron_list").val(),
514 new_patron_list: $("#new_patron_list").val(),
515 borrowernumbers: borrowernumbers
520 url: '/cgi-bin/koha/svc/members/add_to_list',
521 success: function(data) {
522 $("#patron_list_dialog").show();
523 $("#patron_list_dialog > span.patrons-length").html(data.patrons_added_to_list);
524 $("#patron_list_dialog > a").attr("href", "/cgi-bin/koha/patron_lists/list.pl?patron_list_id=" + data.patron_list.patron_list_id);
525 $("#patron_list_dialog > a").html(data.patron_list.name);
527 if ( $('#add_to_patron_list').val() == 'new' ) {
528 /* Add a new entry to the menu */
529 $("#patronlist-dropdown .divider").before('<li><a class="patron-list-add" href="#" data-listid="' + data.patron_list.patron_list_id + '">' + data.patron_list.name + '</li>');
533 alert( _("An error occurred. Patron list could not be updated.") );
539 function getSearchByLocation( setstate ){
540 /* Check to see if the URL contains a search parameter */
541 if( location.search != ""){
542 var params = new URLSearchParams( location.search );
543 var firstletter = params.get("firstletter");
544 /* Check to see if search is a first letter param */
546 /* Trigger function to return search results by letter */
547 filterByFirstLetterSurname( firstletter, setstate );
552 // Update the string "Results found ..."
553 function update_searched(){
554 var searched = $("#searchfieldstype_filter").find("option:selected").text();
555 if ( $("#searchmember_filter").val() ) {
556 if ( $("#searchtype_filter").val() == 'start_with' ) {
557 searched += _(" starting with ");
559 searched += _(" containing ");
561 searched += "'" + $("#searchmember_filter").val() + "'";
563 if ( $("#firstletter_filter").val() ) {
564 searched += _(" begins with ") + "'" + $("#firstletter_filter").val() +"'";
566 if ( $("#categorycode_filter").val() ) {
567 searched += _(" with category ") + "'" + $("#categorycode_filter").find("option:selected").text() + "'";
569 if ( $("#branchcode_filter").val() ) {
570 searched += _(" in library ") + $("#branchcode_filter").find("option:selected").text();
572 $("#searchpattern").text(searched);
577 $("#firstletter_filter").val('');
580 $("#searchresults").show();
581 dtMemberResults.fnDraw();
585 $('#memberresultst tbody').on('click','td',function(e){
586 var $checkbox = $(this).find("input[type=checkbox]");
587 if (e.target.type != "checkbox") {
588 $checkbox.prop('checked', !$checkbox.prop("checked"));
593 // User has clicked on the Clear button
594 function clearFilters(redraw) {
595 $("#searchform select").val('');
596 $("#firstletter_filter").val('');
597 $("#searchmember_filter").val('');
599 /* remove any search string added by firstletter search */
600 history.pushState( {}, null, window.location.href.split("?" )[0]);
603 $("#searchresults").hide();
604 dtMemberResults.fnDraw();
608 // User has clicked on a letter
609 function filterByFirstLetterSurname( letter, setstate ) {
611 $("#firstletter_filter").val(letter);
613 history.pushState( null, null, "?firstletter=" + letter );
617 $("#searchresults").show();
618 dtMemberResults.fnDraw();
623 [% INCLUDE 'intranet-bottom.inc' %]