Bug 9811: Patron search improvement
authorJonathan Druart <jonathan.druart@biblibre.com>
Tue, 19 Mar 2013 10:12:26 +0000 (11:12 +0100)
committerTomas Cohen Arazi <tomascohen@gmail.com>
Tue, 1 Jul 2014 12:57:09 +0000 (09:57 -0300)
This patch add DataTables using server-side processing for the patrons
search.

It adds:
- 1 module C4/Utils/DataTables/Members.pm
- 2 services svc/members/search and svc/members/add_to_list
- 1 template members/tables/members_results.tt
- 1 new practice which is to add template for DataTables in a
  subdirectory named 'tables'.

Impacted scripts: members/members-home.pl and members/members.pl

To go further: We can imagine that all patrons searches use the same
service with no big changes: 1 little template creates a JSON file and
to implement DataTables on the template page, that's all.

Amended patch: Since bug 10565 has been pushed, these patches don't
apply cleanly. I had to rewrite a part of the patron list feature.
I removed the choice to add all resultant patrons from a search. I think
this choice is useless with this patch: we are able to display the
number of patrons we want and to select all of them.

Test plan:
- Check that there is no regression on searching patrons.
- Try filters on the left of the screen.
- Try to sort each column.
- Try the "Browse by last name" links.
- Check that the "Clear" button clears yours filters.
- Try with IndependantBranches ON and OFF.
- Verify this feature does not break the patron list feature (cf bug
  10565).

Signed-off-by: Cedric Vita <cedric.vita@dracenie.com>
Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Passes all tests and QA script, couldn't find any regressions
or problems. Some notes left on the bug.

Bug 9811: Add unit tests for C4::Utils::DT::Members

Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Bug 9811: QA followup

- removes 2 tabs
- removes mysqlisms
- add sort on borrowernotes
- fix wrong capitalization
- cat => Category

Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Thx for fixing these!

Bug 9811 - multilines notes brakes JSON

In new patron search feature, the search results are fetched using Ajax and returned in JSON format.
The JSON is created by TT using koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/members_results.tt.
One of the fields is the borrower notes. When this notes contains several lines, the JSON is broken.

This patch uses TT fileters to consert in notes linefeeds into HTML line break (html_line_break) and then remove linefeeds (collapse).

Test plan :
- perform a member search that does not return a borrower with a circ note
- edit one of the borrowers returned by this search
- enter serveral lines of text in "Circulation note" and save
- reperform the member search
=> circ note is well displayed on several lines

Bug 9811: use count(primary_key) instead of count(*)

Bug 9811: A limit clause should be always added.

By default, we want to retrieve 20 first results.

Bug 9811: Load the page without any data.

Displaying the first 20 patrons is not useful. With this patch, the
table is hidden and no record is retrieved by default.
On the same way, the existing side effect on redirect disappears.

Signed-off-by: Olli-Antti Kivilahti <olli-antti.kivilahti@jns.fi>
-------------
-TEST REPORT-
-------------
For the filter: Tested all the search fields, branches, search type.
Found a bug with "date of birth", followup provided.
Tested display limits and verified that AJAX-queries are
  efficient (using LIMIT clause) to not stress DB needlessly.
Tested adding Patrons to a list.
A good feature, which seems to work quite well.

Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Adding my test plan to the last patch of this bug.

Signed-off-by: Tomas Cohen Arazi <tomascohen@gmail.com>
14 files changed:
C4/Members.pm
C4/Utils/DataTables/Members.pm [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/includes/home-search.inc
koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
koha-tmpl/intranet-tmpl/prog/en/includes/patron-title.inc
koha-tmpl/intranet-tmpl/prog/en/js/datatables.js
koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt
koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/members_results.tt [new file with mode: 0644]
members/member.pl
members/members-home.pl
members/moremember.pl
svc/members/add_to_list [new file with mode: 0755]
svc/members/search [new file with mode: 0755]
t/DataTables/Members.t [new file with mode: 0644]

index 33106cf..13244e0 100644 (file)
@@ -274,7 +274,7 @@ sub Search {
                 else {
                     $filter = { '' => $filter, branchcode => $branch };
                 }
-            }      
+            }
         }
     }
 
diff --git a/C4/Utils/DataTables/Members.pm b/C4/Utils/DataTables/Members.pm
new file mode 100644 (file)
index 0000000..f667cdb
--- /dev/null
@@ -0,0 +1,213 @@
+package C4::Utils::DataTables::Members;
+
+use C4::Branch qw/onlymine/;
+use C4::Context;
+use C4::Members qw/GetMemberIssuesAndFines/;
+use C4::Utils::DataTables;
+use Modern::Perl;
+
+sub search {
+    my ( $params ) = @_;
+    my $searchmember = $params->{searchmember};
+    my $firstletter = $params->{firstletter};
+    my $categorycode = $params->{categorycode};
+    my $branchcode = $params->{branchcode};
+    my $searchtype = $params->{searchtype};
+    my $searchfieldstype = $params->{searchfieldstype};
+    my $dt_params = $params->{dt_params};
+
+    my ($iTotalRecords, $iTotalDisplayRecords);
+
+    # If branches are independant and user is not superlibrarian
+    # The search has to be only on the user branch
+    if ( C4::Branch::onlymine ) {
+        my $userenv = C4::Context->userenv;
+        $branchcode = $userenv->{'branch'};
+
+    }
+
+    my $dbh = C4::Context->dbh;
+    my $select = "SELECT
+        borrowers.borrowernumber, borrowers.surname, borrowers.firstname, borrowers.address,
+        borrowers.address2, borrowers.city, borrowers.zipcode, borrowers.country,
+        CAST(borrowers.cardnumber AS UNSIGNED) AS cardnumber, borrowers.dateexpiry,
+        borrowers.borrowernotes, borrowers.branchcode,
+        categories.description AS category_description, categories.category_type,
+        branches.branchname";
+    my $from = "FROM borrowers
+        LEFT JOIN branches ON borrowers.branchcode = branches.branchcode
+        LEFT JOIN categories ON borrowers.categorycode = categories.categorycode";
+    my @where_args;
+    my @where_strs;
+    if(defined $firstletter and $firstletter ne '') {
+        push @where_strs, "borrowers.surname LIKE ?";
+        push @where_args, "$firstletter%";
+    }
+    if(defined $categorycode and $categorycode ne '') {
+        push @where_strs, "borrowers.categorycode = ?";
+        push @where_args, $categorycode;
+    }
+    if(defined $branchcode and $branchcode ne '') {
+        push @where_strs, "borrowers.branchcode = ?";
+        push @where_args, $branchcode;
+    }
+
+    # split on coma
+    $searchmember =~ s/,/ /g if $searchmember;
+    my @where_strs_or;
+    my $searchfields = {
+        standard => 'surname,firstname,othernames,cardnumber',
+        email => 'email,emailpro,B_email',
+        borrowernumber => 'borrowernumber',
+        phone => 'phone,phonepro,B_phone,altcontactphone,mobile',
+        address => 'streettype,address,address2,city,state,zipcode,country',
+        dateofbirth => 'dateofbirth',
+        sort1 => 'sort1',
+        sort2 => 'sort2',
+    };
+    for my $searchfield ( split /,/, $searchfields->{$searchfieldstype} ) {
+        foreach my $term ( split / /, $searchmember) {
+            next unless $term;
+            $searchmember =~ s/\*/%/g; # * is replaced with % for sql
+            $term .= '%' # end with anything
+                if $term !~ /%$/;
+            $term = "%$term" # begin with anythin unless start_with
+                if $term !~ /^%/
+                    and $searchtype eq "contain";
+            push @where_strs_or, "borrowers." . $dbh->quote_identifier($searchfield) . " LIKE ?";
+            push @where_args, $term;
+        }
+    }
+    push @where_strs, '('. join (' OR ', @where_strs_or) . ')'
+        if @where_strs_or;
+
+    my $where;
+    $where = " WHERE " . join (" AND ", @where_strs) if @where_strs;
+    my $orderby = dt_build_orderby($dt_params);
+
+    my $limit;
+    # If iDisplayLength == -1, we want to display all patrons
+    if ( $dt_params->{iDisplayLength} > -1 ) {
+        # In order to avoid sql injection
+        $dt_params->{iDisplayStart} =~ s/\D//g;
+        $dt_params->{iDisplayLength} =~ s/\D//g;
+        $dt_params->{iDisplayStart} //= 0;
+        $dt_params->{iDisplayLength} //= 20;
+        $limit = "LIMIT $dt_params->{iDisplayStart},$dt_params->{iDisplayLength}";
+    }
+
+    my $query = join(
+        " ",
+        ($select ? $select : ""),
+        ($from ? $from : ""),
+        ($where ? $where : ""),
+        ($orderby ? $orderby : ""),
+        ($limit ? $limit : "")
+    );
+    my $sth = $dbh->prepare($query);
+    $sth->execute(@where_args);
+    my $patrons = $sth->fetchall_arrayref({});
+
+    # Get the iTotalDisplayRecords DataTable variable
+    $query = "SELECT COUNT(borrowers.borrowernumber) " . $from . ($where ? $where : "");
+    $sth = $dbh->prepare($query);
+    $sth->execute(@where_args);
+    ($iTotalDisplayRecords) = $sth->fetchrow_array;
+
+    # Get the iTotalRecords DataTable variable
+    $query = "SELECT COUNT(borrowers.borrowernumber) FROM borrowers";
+    $sth = $dbh->prepare($query);
+    $sth->execute;
+    ($iTotalRecords) = $sth->fetchrow_array;
+
+    # Get some information on patrons
+    foreach my $patron (@$patrons) {
+        ($patron->{overdues}, $patron->{issues}, $patron->{fines}) =
+            GetMemberIssuesAndFines($patron->{borrowernumber});
+        if($patron->{dateexpiry} and $patron->{dateexpiry} ne '0000-00-00') {
+            $patron->{dateexpiry} = C4::Dates->new($patron->{dateexpiry}, "iso")->output();
+        } else {
+            $patron->{dateexpiry} = '';
+        }
+        $patron->{fines} = sprintf("%.2f", $patron->{fines} || 0);
+    }
+
+    return {
+        iTotalRecords => $iTotalRecords,
+        iTotalDisplayRecords => $iTotalDisplayRecords,
+        patrons => $patrons
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+C4::Utils::DataTables::Members - module for using DataTables with patrons
+
+=head1 SYNOPSIS
+
+This module provides (one for the moment) routines used by the patrons search
+
+=head2 FUNCTIONS
+
+=head3 search
+
+    my $dt_infos = C4::Utils::DataTables::Members->search($params);
+
+$params is a hashref with some keys:
+
+=over 4
+
+=item searchmember
+
+  String to search in the borrowers sql table
+
+=item firstletter
+
+  Introduced to contain 1 letter but can contain more.
+  The search will done on the borrowers.surname field
+
+=item categorycode
+
+  Search patrons with this categorycode
+
+=item branchcode
+
+  Search patrons with this branchcode
+
+=item searchtype
+
+  Can be 'contain' or 'start_with'. Used for the searchmember parameter.
+
+=item searchfieldstype
+
+  Can be 'standard', 'email', 'borrowernumber', 'phone', 'address' or 'dateofbirth', 'sort1', 'sort2'
+
+=item dt_params
+
+  Is the reference of C4::Utils::DataTables::dt_get_params($input);
+
+=cut
+
+=back
+
+=head1 LICENSE
+
+Copyright 2013 BibLibre
+
+This file is part of Koha.
+
+Koha is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with Koha; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
index 46d8b45..f61ce85 100644 (file)
@@ -15,7 +15,8 @@
 <div id="patron_search" class="residentsearch">
     <p class="tip">Enter patron card number or partial name:</p>
     <form action="/cgi-bin/koha/members/member.pl" method="post">
-        <input name="member" id="searchmember" size="40" type="text"/>
+        <input name="searchmember" id="searchmember" size="40" type="text"/>
+        <input type="hidden" name="quicksearch" value="1" />
         <input value="Submit" class="submit" type="submit" />
     </form>
 </div>[% END %]
index bccd39c..a1f400c 100644 (file)
        <div id="patron_search" class="residentsearch">
        <p class="tip">Enter patron card number or partial name:</p>
        <form action="/cgi-bin/koha/members/member.pl" method="post">
-    <input id="searchmember" data-toggle="tooltip" size="25" class="focus" name="member" type="text" value="[% member %]"/>
+    <input id="searchmember" data-toggle="tooltip" size="25" class="focus" name="searchmember" type="text" value="[% searchmember %]"/>
+    <input type="hidden" name="quicksearch" value="1" />
        <span class="filteraction" id="filteraction_off"> <a href="#" onclick="$('#filters').toggle();$('.filteraction').toggle();">[-]</a></span>
        <span class="filteraction" id="filteraction_on"> <a href="#" onclick="$('#filters').toggle();$('.filteraction').toggle();">[+]</a></span>
 
-    <input value="Search" class="submit" type="submit" />
+      <label for="searchfieldstype">Search fields:</label>
+      <select name="searchfieldstype" id="searchfieldstype">
+        [% IF searchfieldstype == "standard" %]
+          <option selected="selected" value='standard'>Standard</option>
+        [% ELSE %]
+          <option value='standard'>Standard</option>
+        [% END %]
+        [% IF searchfieldstype == "email" %]
+          <option selected="selected" value='email'>Email</option>
+        [% ELSE %]
+          <option value='email'>Email</option>
+        [% END %]
+        [% IF searchfieldstype == "borrowernumber" %]
+          <option selected="selected" value='borrowernumber'>Borrower number</option>
+        [% ELSE %]
+          <option value='borrowernumber'>Borrower number</option>
+        [% END %]
+        [% IF searchfieldstype == "phone" %]
+          <option selected="selected" value='phone'>Phone number</option>
+        [% ELSE %]
+          <option value='phone'>Phone number</option>
+        [% END %]
+        [% IF searchfieldstype == "address" %]
+          <option selected="selected" value='address'>Street Address</option>
+        [% ELSE %]
+          <option value='address'>Street Address</option>
+        [% END %]
+        [% IF searchfieldstype == "dateofbirth" %]
+          <option selected="selected" value='dateofbirth'>Date of birth</option>
+        [% ELSE %]
+          <option value='dateofbirth'>Date of birth</option>
+        [% END %]
+        [% IF searchfieldstype == "sort1" %]
+          <option selected="selected" value='sort1'>Sort field 1</option>
+        [% ELSE %]
+          <option value='sort1'>Sort field 1</option>
+        [% END %]
+        [% IF searchfieldstype == "sort2" %]
+          <option selected="selected" value='sort2'>Sort field 2</option>
+        [% ELSE %]
+          <option value='sort2'>Sort field 2</option>
+        [% END %]
+      </select>
+
+      <script type="text/javascript">
+          [% SET dateformat = Koha.Preference('dateformat') %]
+          $("#searchfields").change(function() {
+              if ( $(this).val() == 'dateofbirth' ) {
+                  [% IF dateformat == 'us' %]
+                      var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'MM/DD/YYYY'");
+                  [% ELSIF dateformat == 'iso' %]
+                      var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'YYYY-MM-DD'");
+                  [% ELSIF dateformat == 'metric' %]
+                      var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'DD/MM/YYYY'");
+                  [% END %]
+                  $('#searchmember').attr("title",MSG_DATE_FORMAT).tooltip('show');
+              } else {
+                  $('#searchmember').tooltip('destroy');
+              }
+          });
+
+      </script>
 
-  <div id="filters">
-      <p><label for="searchfields">Search fields:</label>
-            <select name="searchfields" id="searchfields">
-                <option selected="selected" value=''>Standard</option>
-                <option value='email,emailpro,B_email,'>Email</option>
-                <option value='borrowernumber'>Borrower number</option>
-                <option value='phone,phonepro,B_phone,altcontactphone,mobile'>Phone number</option>
-                <option value='streettype,address,address2,city,state,zipcode,country'>Street Address</option>
-                <option value='dateofbirth'>Date of birth</option>
-                <option value='sort1'>Sort field 1</option>
-                <option value='sort2'>Sort field 2</option>
-            </select>
-            <script type="text/javascript">
-                [% SET dateformat = Koha.Preference('dateformat') %]
-                $("#searchfields").change(function() {
-                    if ( $(this).val() == 'dateofbirth' ) {
-                        [% IF dateformat == 'us' %]
-                            var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'MM/DD/YYYY'");
-                        [% ELSIF dateformat == 'iso' %]
-                            var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'YYYY-MM-DD'");
-                        [% ELSIF dateformat == 'metric' %]
-                            var MSG_DATE_FORMAT = _("Dates of birth should be entered in the format 'DD/MM/YYYY'");
-                        [% END %]
-                        $('#searchmember').attr("title",MSG_DATE_FORMAT).tooltip('show');
-                    } else {
-                        $('#searchmember').tooltip('destroy');
-                    }
-                });
-            </script>
-        </p>
-        <p><label for="searchtype">Search type:</label>
-                <select name="searchtype" id="searchtype">
-                    <option selected="selected" value=''>Starts with</option>
-                    <option value='contain'>Contains</option>
-                </select></p>
+      <label for="searchtype">Search type:</label>
+      <select name="searchtype" id="searchtype">
+          <option selected="selected" value='start_with'>Starts with</option>
+          <option value='contain'>Contains</option>
+      </select>
 
-      <p><label for="searchorderby">Order by:</label>
-            <select name="orderby" id="searchorderby">
-            <option value="">Surname, Firstname</option>
-            <option value="cardnumber,0">Cardnumber</option>
-            </select></p>
-        [% IF ( branchloop ) %] <p><label for="branchcode">Library: </label><select name="branchcode" id="branchcode">
-                <option value="">Any</option>[% FOREACH branchloo IN branchloop %]
-                [% IF ( branchloo.selected ) %]
-                <option value="[% branchloo.value %]" selected="selected">[% branchloo.branchname %]</option>[% ELSE %]
-                <option value="[% branchloo.value %]">[% branchloo.branchname %]</option>[% END %]
-              [% END %]</select></p>
-      [% END %]
-      [% IF ( categories ) %]
-        <p><label for="categorycode">Category: </label><select name="categorycode" id="categorycode">
-                <option value="">Any</option>[% FOREACH categorie IN categories %]
-                [% IF ( categorie.selected ) %]
-                <option value="[% categorie.categorycode %]" selected="selected">[% categorie.description |html_entity %]</option>[% ELSE %]
-                <option value="[% categorie.categorycode %]">[% categorie.description |html_entity %]</option>[% END %]
-                [% END %]</select></p>
-      [% END %]
-  </div>
+    <input value="Search" class="submit" type="submit" />
+    [% IF ( branchloop ) %]
+    <p id="filters"> <label for="branchcode">Library: </label>
+    <select name="branchcode" id="branchcode">
+        [% IF branchloop.size != 1 %]
+          <option value="">Any</option>
+        [% END %]
+        [% FOREACH branchloo IN branchloop %]
+        [% IF ( branchloo.selected ) %]
+        <option value="[% branchloo.value %]" selected="selected">[% branchloo.branchname %]</option>[% ELSE %]
+        <option value="[% branchloo.value %]">[% branchloo.branchname %]</option>[% END %]
+      [% END %]</select>
+                 <label for="categorycode">Category: </label><select name="categorycode" id="categorycode">
+        <option value="">Any</option>[% FOREACH categorie IN categories %]
+        [% IF ( categorie.selected ) %]
+        <option value="[% categorie.categorycode %]" selected="selected">[% categorie.description |html_entity %]</option>[% ELSE %]
+        <option value="[% categorie.categorycode %]">[% categorie.description |html_entity %]</option>[% END %]
+      [% END %]</select>
+    </p>
+    [% END %]
 </form>
        </div>
 
index ac2f388..3a3b90c 100644 (file)
@@ -1,23 +1,23 @@
-[% IF ( borrower.borrowernumber ) %]
-    [% IF borrower.category_type == 'I' %]
-        [% borrower.surname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %]
-    [% ELSE %]
-        [% IF invert_name %]
-            [% borrower.surname %], [% borrower.firstname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %]
-        [% ELSE %]
-            [% borrower.firstname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %] [% borrower.surname %]
-        [% END %]
-    [% END %]
+[%- IF ( borrower.borrowernumber ) %]
+    [%- IF borrower.category_type == 'I' %]
+        [%- borrower.surname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %]
+    [%- ELSE %]
+        [%- IF invert_name %]
+            [%- borrower.surname %], [% borrower.firstname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %]
+        [%- ELSE %]
+            [%- borrower.firstname %] [% IF borrower.othernames %] ([% borrower.othernames %]) [% END %] [% borrower.surname %]
+        [%- END -%]
+    [%- END -%]
     ([% borrower.cardnumber %])
-[% ELSIF ( borrowernumber ) %]
-    [% IF category_type == 'I' %]
-        [% surname %] [% IF othernames %] ([% othernames %]) [% END %]
-    [% ELSE %]
-        [% IF invert_name %]
-            [% surname %], [% firstname %] [% IF othernames %] ([% othernames %]) [% END %]
-        [% ELSE %]
-            [% firstname %] [% IF othernames %] ([% othernames %]) [% END %] [% surname %]
-        [% END %]
-    [% END %]
-    ([% cardnumber %])
-[% END %]
\ No newline at end of file
+[%- ELSIF ( borrowernumber ) %]
+    [%- IF category_type == 'I' %]
+        [%- surname %] [% IF othernames %] ([% othernames %]) [% END %]
+    [%- ELSE %]
+        [%- IF invert_name %]
+            [%- surname %], [% firstname %] [% IF othernames %] ([% othernames %]) [% END %]
+        [%- ELSE %]
+            [%- firstname %] [% IF othernames %] ([% othernames %]) [% END %] [% surname %]
+        [%- END %]
+    [%- END -%]
+    ([% cardnumber -%])
+[%- END -%]
index c3b882c..ec07acb 100644 (file)
@@ -23,7 +23,7 @@ var dataTablesDefaults = {
         "sSearch"           : window.MSG_DT_SEARCH || "Search:",
         "sZeroRecords"      : window.MSG_DT_ZERO_RECORDS || "No matching records found"
     },
-    "sDom": '<"top pager"ilpf>t<"bottom pager"ip>',
+    "sDom": '<"top pager"ilpf>tr<"bottom pager"ip>',
     "aLengthMenu": [[10, 20, 50, 100, -1], [10, 20, 50, 100, window.MSG_DT_ALL || "All"]],
     "iDisplayLength": 20
 };
index b906e2b..a017726 100644 (file)
@@ -1,7 +1,8 @@
 [% INCLUDE 'doc-head-open.inc' %]
 <title>Koha &rsaquo; Patrons [% IF ( searching ) %]&rsaquo; Search results[% END %]</title>
 [% INCLUDE 'doc-head-close.inc' %]
-
+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'datatables.inc' %]
 <script type="text/javascript">
 //<![CDATA[
 $(document).ready(function() {
@@ -21,7 +22,6 @@ $(document).ready(function() {
             $('#new_patron_list').hide();
             $('#add_to_patron_list_submit').attr('disabled', 'disabled');
         }
-
     });
 
     $('#new_patron_list').on('input', function() {
@@ -31,43 +31,249 @@ $(document).ready(function() {
             $('#add_to_patron_list_submit').attr('disabled', 'disabled');
         }
     });
+
+    $("#patron_list_dialog").hide();
+    $("#add_to_patron_list_submit").on('click', function(e){
+        if ( $('#add_to_patron_list').val() == 'new' ) {
+            if ( $('#new_patron_list').val() ) {
+                $("#add_to_patron_list option").each(function() {
+                    if ( $(this).text() == $('#new_patron_list').val() ) {
+                        alert( _("You already have a list with that name!") );
+                        return false;
+                    }
+                });
+            } else {
+                alert( _("You must give your new patron list a name!") );
+                return false;
+            }
+        }
+
+        if ( $("#memberresultst input:checkbox:checked").length == 0 ) {
+            alert( _("You have not selected any patrons to add to a list!") );
+            return false;
+        }
+
+        var borrowernumbers = [];
+        $("#memberresultst").find("input:checkbox:checked").each(function(){
+            borrowernumbers.push($(this).val());
+        });
+        var data = {
+            add_to_patron_list: encodeURIComponent($("#add_to_patron_list").val()),
+            new_patron_list: encodeURIComponent($("#new_patron_list").val()),
+            borrowernumbers: borrowernumbers
+        };
+        $.ajax({
+            data: data,
+            type: 'POST',
+            url: '/cgi-bin/koha/svc/members/add_to_list',
+            success: function(data) {
+                $("#patron_list_dialog").show();
+                $("#patron_list_dialog > span.patrons-length").html(data.patrons_added_to_list);
+                $("#patron_list_dialog > a").attr("href", "/cgi-bin/koha/patron_lists/list.pl?patron_list_id=" + data.patron_list.patron_list_id);
+                $("#patron_list_dialog > a").html(data.patron_list.name);
+            },
+            error: function() {
+                alert("an error occurred");
+            }
+        });
+        return true;
+    });
 });
 
-function CheckForm() {
-    if ( $('#add_to_patron_list').val() == 'new' ) {
-        if ( $('#new_patron_list').val() ) {
-            var exists = false;
-            $("#add_to_patron_list option").each(function() {
-                if ( $(this).text() == $('#new_patron_list').val() ) {
-                    exists = true;
-                    return false;
+var dtMemberResults;
+var search = 1;
+$(document).ready(function() {
+    [% IF searchmember %]
+        $("#searchmember_filter").val("[% searchmember %]");
+    [% END %]
+    [% IF searchfieldstype %]
+        $("searchfieldstype_filter").val("[% searchfieldstype %]");
+    [% END %]
+    [% IF searchtype %]
+        $("#searchtype_filter").val("[% searchtype %]");
+    [% END %]
+    [% IF categorycode %]
+        $("#categorycode_filter").val("[% categorycode %]");
+    [% END %]
+    [% IF branchcode %]
+        $("#branchcode_filter").val("[% branchcode %]");
+    [% END %]
+
+    [% IF view != "show_results" %]
+        $("#searchresults").hide();
+        search = 0;
+    [% END %]
+
+    // Build the aLengthMenu
+    var aLengthMenu = [
+        [%PatronsPerPage %], 10, 20, 50, 100, -1
+    ];
+    jQuery.unique(aLengthMenu);
+    aLengthMenu.sort(function( a, b ){
+        // Put "All" at the end
+        if ( a == -1 ) {
+            return 1;
+        } else if ( b == -1 ) {
+            return -1;
+        }
+        return parseInt(a) < parseInt(b) ? -1 : 1;}
+    );
+    var aLengthMenuLabel = [];
+    $(aLengthMenu).each(function(){
+        if ( this == -1 ) {
+            // Label for -1 is "All"
+            aLengthMenuLabel.push("All");
+        } else {
+            aLengthMenuLabel.push(this);
+        }
+    });
+
+    // Apply DataTables on the results table
+    dtMemberResults = $("#memberresultst").dataTable($.extend(true, {}, dataTablesDefaults, {
+        'bServerSide': true,
+        'sAjaxSource': "/cgi-bin/koha/svc/members/search",
+        'fnServerData': function(sSource, aoData, fnCallback) {
+            if ( ! search ) {
+                return;
+            }
+            aoData.push({
+                'name': 'searchmember',
+                'value': $("#searchmember_filter").val()
+            },{
+                'name': 'firstletter',
+                'value': $("#firstletter_filter").val()
+            },{
+                'name': 'searchfieldstype',
+                'value': $("#searchfieldstype_filter").val()
+            },{
+                'name': 'searchtype',
+                'value': $("#searchtype_filter").val()
+            },{
+                'name': 'categorycode',
+                'value': $("#categorycode_filter").val()
+            },{
+                'name': 'branchcode',
+                'value': $("#branchcode_filter").val()
+            },{
+                'name': 'name_sorton',
+                'value': 'borrowers.surname borrowers.firstname'
+            },{
+                'name': 'category_sorton',
+                'value': 'categories.description',
+            },{
+                'name': 'branch_sorton',
+                'value': 'branches.branchname'
+            },{
+                'name': 'template_path',
+                'value': 'members/tables/members_results.tt',
+            });
+            $.ajax({
+                'dataType': 'json',
+                'type': 'POST',
+                'url': sSource,
+                'data': aoData,
+                'success': function(json){
+                    // redirect if there is only 1 result.
+                    if ( json.aaData.length == 1 ) {
+                        var borrowernumber = json.aaData[0].borrowernumber;
+                        document.location.href="/cgi-bin/koha/members/moremember.pl?borrowernumber="+borrowernumber;
+                        return false;
+                    }
+                    fnCallback(json);
                 }
             });
+        },
+        'aoColumns':[
+            [% IF CAN_user_tools_manage_patron_lists %]
+              { 'mDataProp': 'dt_borrowernumber' },
+            [% END %]
+            { 'mDataProp': 'dt_cardnumber' },
+            { 'mDataProp': 'dt_name' },
+            { 'mDataProp': 'dt_category' },
+            { 'mDataProp': 'dt_branch' },
+            { 'mDataProp': 'dt_dateexpiry' },
+            { 'mDataProp': 'dt_od_checkouts', 'bSortable': false },
+            { 'mDataProp': 'dt_fines', 'bSortable': false },
+            { 'mDataProp': 'dt_borrowernotes' },
+            { 'mDataProp': 'dt_action', 'bSortable': false }
+        ],
+        'fnRowCallback': function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
+            /* Center text for 6th column */
+            $("td:eq(5)", nRow).css("text-align", "center");
 
-            if ( exists ) {
-                alert( _("You already have a list with that name!") );
-                return false;
-            }
+            return nRow;
+        },
+        'bFilter': false,
+        'bAutoWidth': false,
+        [% IF orderby_cardnumber_0 %]
+            'aaSorting': [[0, 'asc']],
+        [% ELSE %]
+            'aaSorting': [[1, 'asc']],
+        [% END %]
+        "aLengthMenu": [aLengthMenu, aLengthMenuLabel],
+        'sPaginationType': 'full_numbers',
+        "iDisplayLength": [% PatronsPerPage %],
+    }));
+    update_searched();
+});
+
+// Update the string "Results found ..."
+function update_searched(){
+    var searched = "";
+    searched += "on " + $("#searchfieldstype_filter").find("option:selected").text().toLowerCase() + " fields";
+    if ( $("#searchmember_filter").val() ) {
+        if ( $("#searchtype_filter").val() == 'start_with' ) {
+            searched += _(" starting with ");
         } else {
-            alert( _("You must give your new patron list a name!") );
-            return false;
+            searched += _(" containing ");
         }
+        searched += $("#searchmember_filter").val();
+    }
+    if ( $("#firstletter_filter").val() ) {
+        searched += _(" begin with ") + $("#firstletter_filter").val();
+    }
+    if ( $("#categorycode_filter").val() ) {
+        searched += _(" with category ") + $("#categorycode_filter").find("option:selected").text();
+    }
+    if ( $("#branchcode_filter").val() ) {
+        searched += _(" in library ") + $("#branchcode_filter").find("option:selected").text();
     }
+    $("#searchpattern").text("for patron " + searched);
+}
+
+// Redraw the table
+function filter() {
+    $("#firstletter_filter").val('');
+    update_searched();
+    search = 1;
+    $("#searchresults").show();
+    dtMemberResults.fnDraw();
+    return false;
+}
 
-    if ( $('#add_to_patron_list_which').val() == 'all' ) {
-        return confirm( _("Are you sure you want to add the entire set of patron results to this list ( including results on other pages )?") );
-    } else {
-         if ( $("#add-patrons-to-list-form input:checkbox:checked").length == 0 ) {
-             alert( _("You have not selected any patrons to add to a list!") );
-             return false;
-         }
+// User has clicked on the Clear button
+function clearFilters(redraw) {
+    $("#searchform select").val('');
+    $("#firstletter_filter").val('');
+    $("#searchmember_filter").val('');
+    if(redraw) {
+        search = 1;
+        $("#searchresults").show();
+        dtMemberResults.fnDraw();
     }
+}
 
-    return true;
+// User has clicked on a letter
+function filterByFirstLetterSurname(letter) {
+    clearFilters(false);
+    $("#firstletter_filter").val(letter);
+    update_searched();
+    search = 1;
+    $("#searchresults").show();
+    dtMemberResults.fnDraw();
 }
 //]]>
 </script>
-
 </head>
 <body id="pat_member" class="pat">
 [% INCLUDE 'header.inc' %]
@@ -75,163 +281,204 @@ function CheckForm() {
 
 <div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; [% IF ( searching ) %]<a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>  &rsaquo; Search results[% ELSE %]Patrons[% END %]</div>
 
-<div id="doc2" class="yui-t7">
+<div id="doc3" class="yui-t2">
+  <div id="bd">
+    <div id="yui-main">
+      <div class="yui-b">
+        <div class="yui-g">
+          [% IF CAN_user_tools_manage_patron_lists %]
+            <div id="patron_list_dialog" class="dialog alert">
+              Added <span class="patrons-length"></span> patrons to <a></a>.
+            </div>
+          [% END %]
 
-   <div id="bd">
-               <div id="yui-main">
-                   <div class="yui-b">
-                               <div class="yui-g">
+          [% INCLUDE 'patron-toolbar.inc' %]
+          [% IF ( no_add ) %]
+            <div class="dialog alert">
+              <h3>Cannot add patron</h3>
+              [% IF ( no_branches ) %]
+                <p>There are <strong>no libraries defined</strong>. [% IF ( CAN_user_parameters ) %]Please <a href="/cgi-bin/koha/admin/branches.pl">add a library</a>.[% ELSE %]An administrator must define at least one library.[% END %]</p>
+              [% END %]
+              [% IF ( no_categories ) %]
+                <p>There are <strong>no patron categories defined</strong>. [% IF ( CAN_user_parameters ) %]Please <a href="/cgi-bin/koha/admin/categorie.pl">add a patron category</a>.[% ELSE %]An administrator must define at least one patron category.[% END %]</p>
+              [% END %]
+            </div>
+          [% END %]
+          <div class="browse">
+            Browse by last name:
+            [% FOREACH letter IN alphabet.split(' ') %]
+              <a style="cursor:pointer" onclick="filterByFirstLetterSurname('[% letter %]');">[% letter %]</a>
+            [% END %]
+          </div>
 
-                [% IF patron_list %]
-                    <div class="dialog alert">
-                        Added [% patrons_added_to_list.size %] patrons to <a href="/cgi-bin/koha/patron_lists/list.pl?patron_list_id=[% patron_list.patron_list_id %]">[% patron_list.name %]</a>.
-                    </div>
-                [% END %]
+          [% IF ( CAN_user_borrowers && pending_borrower_modifications ) %]
+            <div class="pending-info" id="patron_updates_pending">
+              <a href="/cgi-bin/koha/members/members-update.pl">Patrons requesting modifications</a>:
+              <span class="holdcount"><a href="/cgi-bin/koha/members/members-update.pl">[% pending_borrower_modifications %]</a></span>
+            </div>
+          [% END %]
+
+          <div id="searchresults">
+            <div id="searchheader">
+              <h3>Results found <span id="searchpattern">[% IF searchmember %] for '[% searchmember %]'[% END %]</span></h3>
+            </div>
+            [% IF CAN_user_tools_manage_patron_lists %]
+              <div id="searchheader">
+                  <div>
+                      <a href="javascript:void(0)" onclick="$('.selection').prop('checked', true)">Select all</a>
+                      |
+                      <a href="javascript:void(0)" onclick="$('.selection').prop('checked', false)">Clear all</a>
+                      |
+                      <span>
+                          Add selected patrons
+                          <label for="add_to_patron_list">to:</label>
+                          <select id="add_to_patron_list" name="add_to_patron_list">
+                              <option value=""></option>
+                              [% IF patron_lists %]
+                                  <optgroup label="Patron lists:">
+                                      [% FOREACH pl IN patron_lists %]
+                                          <option value="[% pl.patron_list_id %]">[% pl.name %]</option>
+                                      [% END %]
+                                  </optgroup>
+                              [% END %]
+
+                              <option value="new">[ New list ]</option>
+                          </select>
+
+                          <input type="text" id="new_patron_list" name="new_patron_list" id="new_patron_list" />
 
-                               [% INCLUDE 'patron-toolbar.inc' %]
-
-       [% IF ( no_add ) %]<div class="dialog alert"><h3>Cannot add patron</h3>
-               [% IF ( no_branches ) %]<p>There are <strong>no libraries defined</strong>. [% IF ( CAN_user_parameters ) %]Please <a href="/cgi-bin/koha/admin/branches.pl">add a library</a>.[% ELSE %]An administrator must define at least one library.[% END %]</p>[% END %]
-               [% IF ( no_categories ) %]<p>There are <strong>no patron categories defined</strong>. [% IF ( CAN_user_parameters ) %]Please <a href="/cgi-bin/koha/admin/categorie.pl">add a patron category</a>.[% ELSE %]An administrator must define at least one patron category.[% END %]</p>[% END %]</div>
-       [% END %]
-
-                                               <div class="browse">
-                                                       Browse by last name:
-                            [% FOREACH letter IN alphabet.split(' ') %]
-                                <a href="/cgi-bin/koha/members/member.pl?quicksearch=1&amp;surname=[% letter %]">[% letter %]</a>
-                                                       [% END %]
-                                               </div>
-
-                    [% IF ( CAN_user_borrowers && pending_borrower_modifications ) %]
-                        <div class="pending-info" id="patron_updates_pending">
-                            <a href="/cgi-bin/koha/members/members-update.pl">Patrons requesting modifications</a>:
-                            <span class="holdcount"><a href="/cgi-bin/koha/members/members-update.pl">[% pending_borrower_modifications %]</a></span>
-                        </div>
-                    [% END %]
-
-                    [% IF ( resultsloop ) %]
-                    [% IF (CAN_user_tools_manage_patron_lists) %]
-                    <form id="add-patrons-to-list-form" method="post" action="member.pl" onsubmit="return CheckForm()">
-                    [% END %]
-                        <div id="searchheader">
-                            <h3>Results [% from %] to [% to %] of [% numresults %] found for [% IF ( member ) %]'<span class="ex">[% member %]</span>'[% END %][% IF ( surname ) %]'<span class="ex">[% surname %]</span>'[% END %]</h3>
-
-                            [% IF (CAN_user_tools_manage_patron_lists) %]
-                            <div>
-                                <a href="javascript:void(0)" onclick="$('.selection').prop('checked', true)">Select all</a>
-                                |
-                                <a href="javascript:void(0)" onclick="$('.selection').prop('checked', false)">Clear all</a>
-                                |
-                                <span>
-                                    <label for="add_to_patron_list_which">Add:</label>
-                                    <select id="add_to_patron_list_which" name="add_to_patron_list_which">
-                                        <option value="selected">Selected patrons</option>
-                                        <option value="all">All resultant patrons</option>
-                                    </select>
-
-                                    <label for="add_to_patron_list">to:</label>
-                                    <select id="add_to_patron_list" name="add_to_patron_list">
-                                        <option value=""></option>
-                                        [% IF patron_lists %]
-                                            <optgroup label="Patron lists:">
-                                                [% FOREACH pl IN patron_lists %]
-                                                    <option value="[% pl.patron_list_id %]">[% pl.name %]</option>
-                                                [% END %]
-                                            </optgroup>
-                                        [% END %]
-
-                                        <option value="new">[ New list ]</option>
-                                    </select>
-
-                                    <input type="text" id="new_patron_list" name="new_patron_list" id="new_patron_list" />
-
-                                    [% FOREACH key IN search_parameters.keys %]
-                                        <input type="hidden" name="[% key %]" value="[% search_parameters.$key %]" />
-                                    [% END %]
-
-                                    <input id="add_to_patron_list_submit" type="submit" class="submit" value="Save">
-                                </span>
-                            </div>
-                            [% END %]
-                        </div>
-                                               <div class="searchresults">
-
-                                                       <table id="memberresultst">
-                                                       <thead>
-                                                       <tr>
-                            [% IF (CAN_user_tools_manage_patron_lists) %]
-                            <th>&nbsp</th>
-                            [% END %]
-                                                       <th>Card</th>
-                                                       <th>Name</th>
-                                                       <th>Cat</th>
-                                                       <th>Library</th>
-                                                       <th>Expires on</th>
-                                                       <th>OD/Checkouts</th>
-                                                       <th>Fines</th>
-                                                       <th>Circ note</th>
-                                                       <th>&nbsp;</th>
-                                                       </tr>
-                                                       </thead>
-                                                       <tbody>
-                                                       [% FOREACH resultsloo IN resultsloop %]
-                                                       [% IF ( resultsloo.overdue ) %]
-                                                       <tr class="problem">
-                                                       [% ELSE %]
-                                                       [% UNLESS ( loop.odd ) %]
-                                                       <tr class="highlight">
-                                                       [% ELSE %]
-                                                       <tr>
-                                                       [% END %]
-                                                       [% END %]
-                            [% IF (CAN_user_tools_manage_patron_lists) %]
-                            <td><input type="checkbox" class="selection" name="borrowernumber" value="[% resultsloo.borrowernumber %]" /></td>
-                            [% END %]
-                                                       <td>[% resultsloo.cardnumber %]</td>
-                            <td style="white-space: nowrap;">
-                            <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% resultsloo.borrowernumber %]">
-                            [% INCLUDE 'patron-title.inc' borrowernumber = resultsloo.borrowernumber category_type = resultsloo.category_type firstname = resultsloo.firstname surname = resultsloo.surname othernames = resultsloo.othernames cardnumber = resultsloo.cardnumber invert_name = 1%]
-                            </a> <br />
-                            [% IF ( resultsloo.streetnumber ) %][% resultsloo.streetnumber %] [% END %][% resultsloo.address %][% IF ( resultsloo.address2 ) %]<br />[% resultsloo.address2 %][% END %][% IF ( resultsloo.city ) %]<br />[% resultsloo.city %][% IF ( resultsloo.state ) %],[% END %][% END %][% IF ( resultsloo.state ) %] [% resultsloo.state %][% END %] [% IF ( resultsloo.zipcode ) %]  [% resultsloo.zipcode %][% END %][% IF ( resultsloo.country ) %], [% resultsloo.country %][% END %]
-                            [% IF (resultsloo.email ) %]<br/>Email: <a href="mailto:[% resultsloo.email %]">[% resultsloo.email %]</a>[% END %]
-                            </td>
-                                                       <td>[% resultsloo.category_description %] ([% resultsloo.category_type %])</td>
-                                                       <td>[% resultsloo.branchname %]</td>
-                                                       <td>[% resultsloo.dateexpiry %]</td>
-                                                       <td>[% IF ( resultsloo.overdues ) %]<span class="overdue"><strong>[% resultsloo.overdues %]</strong></span>[% ELSE %][% resultsloo.overdues %][% END %]/[% resultsloo.issues %]</td>
-                                                       <td>[% IF ( resultsloo.fines < 0 ) %]<span class="credit">[% resultsloo.fines %]</span> [% ELSIF resultsloo.fines > 0 %] <span class="debit"><strong>[% resultsloo.fines %]</strong></span> [% ELSE %] [% resultsloo.fines %] [% END %]</td>
-                                                       <td>[% resultsloo.borrowernotes %]</td>
-                                                       <td>[% IF ( resultsloo.category_type ) %]
-                                                                       <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% resultsloo.borrowernumber %]&amp;category_type=[% resultsloo.category_type %]">Edit</a>
-                                               [% ELSE %] <!-- try with categorycode if no category_type -->
-                                                       [% IF ( resultsloo.categorycode ) %]
-                                                                       <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% resultsloo.borrowernumber %]&amp;categorycode=[% resultsloo.categorycode %]">Edit</a>
-                                                       [% ELSE %] <!-- if no categorycode, set category_type to A by default -->
-                                                                       <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% resultsloo.borrowernumber %]&amp;category_type=A">Edit</a>
-                                                       [% END %]
-                                               [% END %]</td>
-                                                       </tr>
-                                                       [% END %]
-                                                       </tbody>
-                                                       </table>
-                                                       <div class="pages">[% IF ( multipage ) %][% paginationbar %][% END %]</div>
-                                               </div>
-                    [% IF (CAN_user_tools_manage_patron_lists) %]
-                    </form>
-                    [% END %]
-                    [% ELSE %]
-                        [% IF ( searching ) %]
-                            <div class="dialog alert">No results found</div>
-                        [% END %]
-                    [% END %]
-
-                                       </div>
-                               </div>
-
-                               <div class="yui-g">
-                               [% INCLUDE 'members-menu.inc' %]
-                       </div>
+                          <input id="add_to_patron_list_submit" type="submit" class="submit" value="Save">
+                      </span>
+                  </div>
+              </div>
+            [% END %]
 
+            <table id="memberresultst">
+              <thead>
+                <tr>
+                  <th>&nbsp;</th>
+                  <th>Card</th>
+                  <th>Name</th>
+                  <th>Category</th>
+                  <th>Library</th>
+                  <th>Expires on</th>
+                  <th>OD/Checkouts</th>
+                  <th>Fines</th>
+                  <th>Circ note</th>
+                  <th>&nbsp;</th>
+                </tr>
+              </thead>
+              <tbody></tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="yui-b">
+      <form onsubmit="return filter();" id="searchform">
+        <input type="hidden" id="firstletter_filter" value="" />
+        <fieldset class="brief">
+          <h3>Filters</h3>
+          <ol>
+            <li>
+              <label for="searchmember_filter">Search:</label>
+              <input type="text" id="searchmember_filter" value="[% searchmember %]"/>
+            </li>
+            <li>
+              <label for="searchfieldstype_filter">Search fields:</label>
+              <select name="searchfieldstype" id="searchfieldstype_filter">
+                [% IF searchfieldstype == "standard" %]
+                  <option selected="selected" value='standard'>Standard</option>
+                [% ELSE %]
+                  <option value='standard'>Standard</option>
+                [% END %]
+                [% IF searchfieldstype == "email" %]
+                  <option selected="selected" value='email'>Email</option>
+                [% ELSE %]
+                  <option value='email'>Email</option>
+                [% END %]
+                [% IF searchfieldstype == "borrowernumber" %]
+                  <option selected="selected" value='borrowernumber'>Borrower number</option>
+                [% ELSE %]
+                  <option value='borrowernumber'>Borrower number</option>
+                [% END %]
+                [% IF searchfieldstype == "phone" %]
+                  <option selected="selected" value='phone'>Phone number</option>
+                [% ELSE %]
+                  <option value='phone'>Phone number</option>
+                [% END %]
+                [% IF searchfieldstype == "address" %]
+                  <option selected="selected" value='address'>Street address</option>
+                [% ELSE %]
+                  <option value='address'>Street address</option>
+                [% END %]
+                [% IF searchfieldstype == "dateofbirth" %]
+                  <option selected="selected" value='dateofbirth'>Date of birth</option>
+                [% ELSE %]
+                  <option value='dateofbirth'>Date of birth</option>
+                [% END %]
+                [% IF searchfieldstype == "sort1" %]
+                  <option selected="selected" value='sort1'>Sort field 1</option>
+                [% ELSE %]
+                  <option value='sort1'>Sort field 1</option>
+                [% END %]
+                [% IF searchfieldstype == "sort2" %]
+                  <option selected="selected" value='sort2'>Sort field 2</option>
+                [% ELSE %]
+                  <option value='sort2'>Sort field 2</option>
+                [% END %]
+              </select>
+            </li>
+            <li>
+              <label for="searchtype_filter">Search type:</label>
+              <select name="searchtype" id="searchtype_filter">
+                <option value='start_with'>Starts with</option>
+                [% IF searchtype == "contain" %]
+                  <option value="contain" selected="selected">Contains</option>
+                [% ELSE %]
+                  <option value="contain" selected="selected">Contains</option>
+                [% END %]
+              </select>
+            </li>
+            <li>
+              <label for="categorycode_filter">Category:</label>
+              <select id="categorycode_filter">
+                <option value="">Any</option>
+                [% FOREACH cat IN categories %]
+                  [% IF cat.selected %]
+                    <option selected="selected" value="[% cat.categorycode %]">[% cat.description %]</option>
+                  [% ELSE %]
+                    <option value="[% cat.categorycode %]">[% cat.description %]</option>
+                  [% END %]
+                [% END %]
+              </select>
+            </li>
+            <li>
+              <label for="branchcode_filter">Branch:</label>
+              <select id="branchcode_filter">
+                [% IF branchloop.size != 1 %]
+                  <option value="">Any</option>
+                [% END %]
+                [% FOREACH b IN branchloop %]
+                  [% IF b.selected %]
+                    <option selected="selected" value="[% b.branchcode %]">[% b.branchname %]</option>
+                  [% ELSE %]
+                    <option value="[% b.branchcode %]">[% b.branchname %]</option>
+                  [% END %]
+                [% END %]
+              </select>
+            </li>
+          </ol>
+          <fieldset class="action">
+            <input type="submit" value="Search" />
+            <input type="button" value="Clear" onclick="clearFilters(true);" />
+          </fieldset>
+        </fieldset>
+      </form>
     </div>
+  </div>
+  <div class="yui-g">
+    [% INCLUDE 'members-menu.inc' %]
+  </div>
 </div>
 [% INCLUDE 'intranet-bottom.inc' %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/members_results.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/members_results.tt
new file mode 100644 (file)
index 0000000..1a975b9
--- /dev/null
@@ -0,0 +1,35 @@
+{
+    "sEcho": [% sEcho %],
+    "iTotalRecords": [% iTotalRecords %],
+    "iTotalDisplayRecords": [% iTotalDisplayRecords %],
+    "aaData": [
+        [% FOREACH data IN aaData %]
+            {
+                [% IF CAN_user_tools_manage_patron_lists %]
+                "dt_borrowernumber":
+                    "<input type='checkbox' class='selection' name='borrowernumber' value='[% data.borrowernumber %]' />",
+                [% END %]
+                "dt_cardnumber":
+                    "[% data.cardnumber %]",
+                "dt_name":
+                    "<span style='white-space:nowrap'><a href='/cgi-bin/koha/members/moremember.pl?borrowernumber=[% data.borrowernumber %]'>[% INCLUDE 'patron-title.inc' borrowernumber = data.borrowernumber category_type = data.category_type firstname = data.firstname surname = data.surname othernames = data.othernames cardnumber = data.cardnumber invert_name = 1%]</a><br />[% IF ( data.streetnumber ) %][% data.streetnumber %] [% END %][% data.address %][% IF ( data.address2 ) %]<br />[% data.address2 %][% END %][% IF ( data.city ) %]<br />[% data.city %][% IF ( data.state ) %],[% END %][% END %][% IF ( data.state ) %] [% data.state %][% END %] [% IF ( data.zipcode ) %]  [% data.zipcode %][% END %][% IF ( data.country ) %], [% data.country %][% END %]</span>",
+                "dt_category":
+                    "[% data.category_description |html %]([% data.category_type |html %])",
+                "dt_branch":
+                    "[% data.branchname |html %]",
+                "dt_dateexpiry":
+                    "[% data.dateexpiry %]",
+                "dt_od_checkouts":
+                    "[% IF data.overdues %]<span class='overdue'><strong>[% data.overdues %]</strong></span>[% ELSE %][% data.overdues %][% END %] / [% data.issues %]",
+                "dt_fines":
+                    "[% IF data.fines < 0 %]<span class='credit'>[% data.fines |html %]</span> [% ELSIF data.fines > 0 %] <span class='debit'><strong>[% data.fines |html %]</strong></span> [% ELSE %] [% data.fines |html%] [% END %]</td>",
+                "dt_borrowernotes":
+                    "[% data.borrowernotes |html |html_line_break |collapse %]",
+                "dt_action":
+                    "[% IF data.category_type %]<a href='/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% data.borrowernumber %]&amp;category_type=[% data.category_type %]'>Edit</a>[% ELSE %][% IF data.categorycode %]<a href='/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% data.borrowernumber %]&amp;categorycode=[% data.categorycode %]'>Edit</a>[% ELSE %]<a href='/cgi-bin/koha/members/memberentry.pl?op=modify&amp;destination=circ&amp;borrowernumber=[% data.borrowernumber %]&amp;category_type=A'>Edit</a>[% END %][% END %]",
+                "borrowernumber":
+                    "[% data.borrowernumber %]"
+            }[% UNLESS loop.last %],[% END %]
+        [% END %]
+    ]
+}
index dfc7363..9cefc79 100755 (executable)
@@ -6,7 +6,7 @@
 
 
 # Copyright 2000-2002 Katipo Communications
-# Copyright 2010 BibLibre
+# Copyright 2013 BibLibre
 #
 # This file is part of Koha.
 #
@@ -27,19 +27,16 @@ use Modern::Perl;
 use C4::Auth;
 use C4::Output;
 use CGI;
-use C4::Members;
 use C4::Branch;
 use C4::Category;
+use C4::Members qw( GetMember );
 use Koha::DateUtils;
 use Koha::List::Patron;
 
 my $input = new CGI;
-my $quicksearch = $input->param('quicksearch') || '';
-my $startfrom = $input->param('startfrom') || 1;
-my $resultsperpage = $input->param('resultsperpage') || C4::Context->preference("PatronsPerPage") || 20;
 
 my ($template, $loggedinuser, $cookie)
-    = get_template_and_user({template_name => "members/member.tmpl",
+    = get_template_and_user({template_name => "members/member.tt",
                  query => $input,
                  type => "intranet",
                  authnotrequired => 0,
@@ -48,188 +45,88 @@ my ($template, $loggedinuser, $cookie)
 
 my $theme = $input->param('theme') || "default";
 
-my $add_to_patron_list       = $input->param('add_to_patron_list');
-my $add_to_patron_list_which = $input->param('add_to_patron_list_which');
-my $new_patron_list          = $input->param('new_patron_list');
-my @borrowernumbers          = $input->param('borrowernumber');
-$input->delete(
-    'add_to_patron_list', 'add_to_patron_list_which',
-    'new_patron_list',    'borrowernumber',
-);
-
 my $patron = $input->Vars;
 foreach (keys %$patron){
-       delete $$patron{$_} unless($$patron{$_});
-}
-my @categories=C4::Category->all;
-
-my $branches = GetBranches;
-my @branchloop;
-
-foreach (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %$branches) {
-  my $selected;
-  $selected = 1 if $patron->{branchcode} && $branches->{$_}->{branchcode} eq $patron->{branchcode};
-  my %row = ( value => $_,
-        selected => $selected,
-        branchname => $branches->{$_}->{branchname},
-      );
-  push @branchloop, \%row;
-}
-
-my %categories_dislay;
-
-foreach my $category (@categories){
-       my $hash={
-                       category_description=>$$category{description},
-                       category_type=>$$category{category_type}
-                        };
-       $categories_dislay{$$category{categorycode}} = $hash;
+    delete $patron->{$_} unless($patron->{$_});
 }
-my $AddPatronLists = C4::Context->preference("AddPatronLists") || '';
-$template->param( 
-        "AddPatronLists_$AddPatronLists" => "1",
-            );
-if ($AddPatronLists=~/code/){
-    $categories[0]->{'first'}=1;
-}  
-
-my $member=$input->param('member') || '';
-my $orderbyparams=$input->param('orderby') || '';
-my @orderby;
-if ($orderbyparams){
-       my @orderbyelt=split(/,/,$orderbyparams);
-       push @orderby, {$orderbyelt[0]=>$orderbyelt[1]||0};
-}
-else {
-       @orderby = ({surname=>0},{firstname=>0});
-}
-
-$member =~ s/,//g;   #remove any commas from search string
-$member =~ s/\*/%/g;
 
-my $from = ( $startfrom - 1 ) * $resultsperpage;
-my $to   = $from + $resultsperpage;
+my $searchmember = $input->param('searchmember');
+my $quicksearch = $input->param('quicksearch') // 0;
 
-my ($count,$results);
-if ($member || keys %$patron) {
-    my $searchfields = $input->param('searchfields') || '';
-    my @searchfields = $searchfields ? split( ',', $searchfields ) : ( "firstname", "surname", "othernames", "cardnumber", "userid", "email" );
-
-    if ( $searchfields eq "dateofbirth" ) {
-        $member = output_pref({dt => dt_from_string($member), dateformat => 'iso', dateonly => 1});
+if ( $quicksearch ) {
+    my $branchcode;
+    if ( C4::Branch::onlymine ) {
+        my $userenv = C4::Context->userenv;
+        $branchcode = $userenv->{'branch'};
+    }
+    my $member = GetMember(
+        cardnumber => $searchmember,
+        ( $branchcode ? ( branchcode => $branchcode ) : () ),
+    );
+    if( $member ){
+        print $input->redirect("/cgi-bin/koha/members/moremember.pl?borrowernumber=" . $member->{borrowernumber});
+        exit;
     }
+}
 
-    my $searchtype = $input->param('searchtype');
-    my $search_scope =
-        $quicksearch ? "field_start_with"
-      : $searchtype  ? $searchtype
-      :                "start_with";
+my $searchfieldstype = $input->param('searchfieldstype') || 'standard';
 
-    ($results) = Search( $member || $patron, \@orderby, undef, undef, \@searchfields, $search_scope );
+if ( $searchfieldstype eq "dateofbirth" ) {
+    $searchmember = output_pref({dt => dt_from_string($searchmember), dateformat => 'iso', dateonly => 1});
 }
 
-if ($add_to_patron_list) {
-    my $patron_list;
-
-    if ( $add_to_patron_list eq 'new' ) {
-        $patron_list = AddPatronList( { name => $new_patron_list } );
-    }
-    else {
-        $patron_list =
-          [ GetPatronLists( { patron_list_id => $add_to_patron_list } ) ]->[0];
+my $branches = C4::Branch::GetBranches;
+my @branches_loop;
+if ( C4::Branch::onlymine ) {
+    my $userenv = C4::Context->userenv;
+    my $branch = C4::Branch::GetBranchDetail( $userenv->{'branch'} );
+    push @branches_loop, {
+        value => $branch->{branchcode},
+        branchcode => $branch->{branchcode},
+        branchname => $branch->{branchname},
+        selected => 1
     }
-
-    if ( $add_to_patron_list_which eq 'all' ) {
-        @borrowernumbers = map { $_->{borrowernumber} } @$results;
+} else {
+    foreach ( sort { lc($branches->{$a}->{branchname}) cmp lc($branches->{$b}->{branchname}) } keys %$branches ) {
+        my $selected = 0;
+        $selected = 1 if($patron->{branchcode} and $patron->{branchcode} eq $_);
+        push @branches_loop, {
+            value => $_,
+            branchcode => $_,
+            branchname => $branches->{$_}->{branchname},
+            selected => $selected
+        };
     }
-
-    my @patrons_added_to_list = AddPatronsToList( { list => $patron_list, borrowernumbers => \@borrowernumbers } );
-
-    $template->param(
-        patron_list           => $patron_list,
-        patrons_added_to_list => \@patrons_added_to_list,
-      )
 }
 
-if ($results) {
-       for my $field ('categorycode','branchcode'){
-               next unless ($patron->{$field});
-               @$results = grep { $_->{$field} eq $patron->{$field} } @$results; 
-       }
-    $count = scalar(@$results);
-} else {
-    $count = 0;
+my @categories = C4::Category->all;
+if ( $patron->{categorycode} ) {
+    foreach my $category ( grep { $_->{categorycode} eq $patron->{categorycode} } @categories ) {
+        $category->{selected} = 1;
+    }
 }
 
-if($count == 1){
-    print $input->redirect("/cgi-bin/koha/members/moremember.pl?borrowernumber=" . @$results[0]->{borrowernumber});
-    exit;
-}
+$template->param( 'alphabet' => C4::Context->preference('alphabet') || join ' ', 'A' .. 'Z' );
 
-my @resultsdata;
-$to=($count>$to?$to:$count);
-my $index=$from;
-foreach my $borrower(@$results[$from..$to-1]){
-  #find out stats
-  my ($od,$issue,$fines)=GetMemberIssuesAndFines($$borrower{'borrowernumber'});
-  $fines ||= 0;
-  $$borrower{'dateexpiry'}= C4::Dates->new($$borrower{'dateexpiry'},'iso')->output('syspref');
-
-  my %row = (
-    count => $index++,
-    %$borrower,
-    (defined $categories_dislay{ $borrower->{categorycode} }?   %{ $categories_dislay{ $borrower->{categorycode} } }:()),
-    overdues => $od,
-    issues => $issue,
-    odissue => "$od/$issue",
-    fines =>  sprintf("%.2f",$fines),
-    branchname => $branches->{$borrower->{branchcode}}->{branchname},
-    );
-  push(@resultsdata, \%row);
+my $orderby = $input->param('orderby') // '';
+if(defined $orderby and $orderby ne '') {
+    $orderby =~ s/[, ]/_/g;
 }
 
-if ($$patron{categorycode}){
-       foreach my $category (grep{$_->{categorycode} eq $$patron{categorycode}}@categories){
-               $$category{selected}=1;
-       }
-}
-my %parameters=
-        (  %$patron
-               , 'orderby'                     => $orderbyparams 
-               , 'resultsperpage'      => $resultsperpage 
-        , 'type'=> 'intranet'); 
-my $base_url =
-    'member.pl?&amp;'
-  . join(
-    '&amp;',
-    map { "$_=$parameters{$_}" } (keys %parameters)
-  );
-
-my @letters = map { {letter => $_} } ( 'A' .. 'Z');
+my $view = $input->request_method() eq "GET" ? "show_form" : "show_results";
 
 $template->param(
-    %$patron,
-    letters       => \@letters,
-    paginationbar => pagination_bar(
-        $base_url,
-        int( $count / $resultsperpage ) + ( $count % $resultsperpage ? 1 : 0 ),
-        $startfrom,
-        'startfrom'
-    ),
-    startfrom    => $startfrom,
-    from         => ( $startfrom - 1 ) * $resultsperpage + 1,
-    to           => $to,
-    multipage    => ( $count != $to || $startfrom != 1 ),
-    advsearch    => ( $$patron{categorycode} || $$patron{branchcode} ),
-    branchloop   => \@branchloop,
-    categories   => \@categories,
-    searching    => "1",
-    numresults   => $count,
-    resultsloop  => \@resultsdata,
-    results_per_page => $resultsperpage,
-    member => $member,
-    search_parameters => \%parameters,
     patron_lists => [ GetPatronLists() ],
+    searchmember        => $searchmember,
+    branchloop          => \@branches_loop,
+    categories          => \@categories,
+    branchcode          => $patron->{branchcode},
+    categorycode        => $patron->{categorycode},
+    searchtype          => $input->param('searchtype') || 'start_with',
+    searchfieldstype    => $searchfieldstype,
+    "orderby_$orderby"  => 1,
+    PatronsPerPage      => C4::Context->preference("PatronsPerPage") || 20,
+    view                => $view,
 );
 
 output_html_with_http_headers $input, $cookie, $template->output;
index 9d1e3e5..0024272 100755 (executable)
@@ -44,12 +44,23 @@ my ($template, $loggedinuser, $cookie)
 
 my $branches = GetBranches;
 my @branchloop;
-foreach (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %{$branches}) {
+if ( C4::Branch::onlymine ) {
+    my $userenv = C4::Context->userenv;
+    my $branch = C4::Branch::GetBranchDetail( $userenv->{'branch'} );
     push @branchloop, {
-        value      => $_,
-        selected   => ($branches->{$_}->{branchcode} eq $branch),
-        branchname => $branches->{$_}->{branchname},
-    };
+        value => $branch->{branchcode},
+        branchcode => $branch->{branchcode},
+        branchname => $branch->{branchname},
+        selected => 1
+    }
+} else {
+    foreach (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %{$branches}) {
+        push @branchloop, {
+            value      => $_,
+            selected   => ($branches->{$_}->{branchcode} eq $branch),
+            branchname => $branches->{$_}->{branchname},
+        };
+    }
 }
 
 my @categories;
@@ -85,6 +96,10 @@ $template->param(
         no_add => $no_add,
         pending_borrower_modifications => $pending_borrower_modifications,
             );
-$template->param( 'alphabet' => C4::Context->preference('alphabet') || join ' ', 'A' .. 'Z' );
+
+$template->param(
+    alphabet => C4::Context->preference('alphabet') || join (' ', 'A' .. 'Z'),
+    PatronsPerPage => C4::Context->preference("PatronsPerPage") || 20,
+);
 
 output_html_with_http_headers $query, $cookie, $template->output;
index 4f77854..92d79e5 100755 (executable)
@@ -81,21 +81,21 @@ my $template_name;
 my $quickslip = 0;
 
 my $flagsrequired;
-if ($print eq "page") {
+if (defined $print and $print eq "page") {
     $template_name = "members/moremember-print.tmpl";
     # circ staff who process checkouts but can't edit
     # patrons still need to be able to access print view
     $flagsrequired = { circulate => "circulate_remaining_permissions" };
-} elsif ($print eq "slip") {
+} elsif (defined $print and $print eq "slip") {
     $template_name = "members/moremember-receipt.tmpl";
     # circ staff who process checkouts but can't edit
     # patrons still need to be able to print receipts
     $flagsrequired =  { circulate => "circulate_remaining_permissions" };
-} elsif ($print eq "qslip") {
+} elsif (defined $print and $print eq "qslip") {
     $template_name = "members/moremember-receipt.tmpl";
     $quickslip = 1;
     $flagsrequired =  { circulate => "circulate_remaining_permissions" };
-} elsif ($print eq "brief") {
+} elsif (defined $print and $print eq "brief") {
     $template_name = "members/moremember-brief.tmpl";
     $flagsrequired = { borrowers => 1 };
 } else {
@@ -156,7 +156,7 @@ if ( IsDebarred($borrowernumber) ) {
 }
 
 $data->{'ethnicity'} = fixEthnicity( $data->{'ethnicity'} );
-$data->{ "sex_".$data->{'sex'}."_p" } = 1;
+$data->{ "sex_".$data->{'sex'}."_p" } = 1 if defined $data->{sex};
 
 my $catcode;
 if ( $category_type eq 'C') {
@@ -296,13 +296,13 @@ if ($borrowernumber) {
         $getreserv{barcodereserv}  = $getiteminfo->{'barcode'};
         $getreserv{itemtype}  = $itemtypeinfo->{'description'};
 
-        #              check if we have a waitin status for reservations
-        if ( $num_res->{'found'} eq 'W' ) {
+        # check if we have a waitin status for reservations
+        if ( defined $num_res->{found} and $num_res->{'found'} eq 'W' ) {
             $getreserv{color}   = 'reserved';
             $getreserv{waiting} = 1;
         }
 
-        #              check transfers with the itemnumber foud in th reservation loop
+        # check transfers with the itemnumber foud in th reservation loop
         if ($transfertwhen) {
             $getreserv{color}      = 'transfered';
             $getreserv{transfered} = 1;
@@ -449,6 +449,7 @@ $template->param(
     SuspendHoldsIntranet => C4::Context->preference('SuspendHoldsIntranet'),
     RoutingSerials => C4::Context->preference('RoutingSerials'),
     debarments => GetDebarments({ borrowernumber => $borrowernumber }),
+    PatronsPerPage => C4::Context->preference("PatronsPerPage") || 20,
 );
 $template->param( $error => 1 ) if $error;
 
diff --git a/svc/members/add_to_list b/svc/members/add_to_list
new file mode 100755 (executable)
index 0000000..970ae18
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+# Copyright 2013 BibLibre
+#
+# This file is part of Koha
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+use CGI;
+
+use C4::Auth qw( check_cookie_auth );
+use JSON qw( to_json );
+use Koha::List::Patron;
+
+my $input = new CGI;
+
+my ( $auth_status, $sessionID ) = check_cookie_auth(
+    $input->cookie('CGISESSID'),
+    { tools => 'manage_patron_lists' },
+);
+
+exit 0 if $auth_status ne "ok";
+my $add_to_patron_list       = $input->param('add_to_patron_list');
+my $new_patron_list          = $input->param('new_patron_list');
+my @borrowernumbers          = $input->param('borrowernumbers[]');
+
+my $response;
+if ($add_to_patron_list) {
+    my $patron_list = [];
+
+    if ( $add_to_patron_list eq 'new' ) {
+        $patron_list = AddPatronList( { name => $new_patron_list } );
+    }
+    else {
+        $patron_list =
+          [ GetPatronLists( { patron_list_id => $add_to_patron_list } ) ]->[0];
+    }
+
+    my @patrons_added_to_list = AddPatronsToList( { list => $patron_list, borrowernumbers => \@borrowernumbers } );
+
+    $response->{patron_list} = { patron_list_id => $patron_list->patron_list_id, name => $patron_list->name };
+    $response->{patrons_added_to_list} = scalar( @patrons_added_to_list );
+}
+
+binmode STDOUT, ":encoding(UTF-8)";
+print $input->header(
+    -type => 'application/json',
+    -charset => 'UTF-8'
+);
+
+print to_json( $response );
diff --git a/svc/members/search b/svc/members/search
new file mode 100755 (executable)
index 0000000..56b9e86
--- /dev/null
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+# Copyright 2013 BibLibre
+#
+# This file is part of Koha
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+use CGI;
+
+use C4::Auth qw( get_template_and_user );
+use C4::Output qw( output_with_http_headers );
+use C4::Utils::DataTables qw( dt_get_params );
+use C4::Utils::DataTables::Members qw( search );
+
+my $input = new CGI;
+
+exit unless $input->param('template_path');
+
+my ($template, $user, $cookie) = get_template_and_user({
+    template_name   => $input->param('template_path'),
+    query           => $input,
+    type            => "intranet",
+    authnotrequired => 0,
+    flagsrequired   => { borrowers => 1 }
+});
+
+my $searchmember = $input->param('searchmember');
+my $firstletter  = $input->param('firstletter');
+my $categorycode = $input->param('categorycode');
+my $branchcode = $input->param('branchcode');
+my $searchtype = $input->param('searchtype');
+my $searchfieldstype = $input->param('searchfieldstype');
+
+# variable information for DataTables (id)
+my $sEcho = $input->param('sEcho');
+
+my %dt_params = dt_get_params($input);
+foreach (grep {$_ =~ /^mDataProp/} keys %dt_params) {
+    $dt_params{$_} =~ s/^dt_//;
+}
+
+# Perform the patrons search
+my $results = C4::Utils::DataTables::Members::search(
+    {
+        searchmember => $searchmember,
+        firstletter => $firstletter,
+        categorycode => $categorycode,
+        branchcode => $branchcode,
+        searchtype => $searchtype,
+        searchfieldstype => $searchfieldstype,
+        dt_params => \%dt_params,
+
+    }
+);
+
+$template->param(
+    sEcho => $sEcho,
+    iTotalRecords => $results->{iTotalRecords},
+    iTotalDisplayRecords => $results->{iTotalDisplayRecords},
+    aaData => $results->{patrons}
+);
+
+output_with_http_headers $input, $cookie, $template->output, 'json';
+
+__END__
+
+=head1 NAME
+
+search - a search script for finding patrons
+
+=head1 SYNOPSIS
+
+This script provides a service for template for patron search using DataTables
+
+=head2 Performing a search
+
+Call this script from a DataTables table my $searchmember = $input->param('searchmember');
+All following params are optional:
+    searchmember => the search terms
+    firstletter => search patrons with surname begins with this pattern (currently only used for 1 letter)
+    categorycode and branchcode => search patrons belong to a given categorycode or a branchcode
+    searchtype: can be 'contain' or 'start_with'
+    searchfieldstype: Can be 'standard', 'email', 'borrowernumber', 'phone' or 'address'
+
+=cut
+
+=back
+
+=head1 LICENSE
+
+Copyright 2013 BibLibre
+
+This file is part of Koha.
+
+Koha is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with Koha; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/t/DataTables/Members.t b/t/DataTables/Members.t
new file mode 100644 (file)
index 0000000..942f93f
--- /dev/null
@@ -0,0 +1,14 @@
+use Modern::Perl;
+use Test::More tests => 4;
+
+use_ok( "C4::Utils::DataTables::Members" );
+
+my $patrons = C4::Utils::DataTables::Members::search({
+    searchmember => "Doe",
+    searchfieldstype => 'standard',
+    searchtype => 'contains'
+});
+
+isnt( $patrons->{iTotalDisplayRecords}, undef, "The iTotalDisplayRecords key is defined");
+isnt( $patrons->{iTotalRecords}, undef, "The iTotalRecords key is defined");
+is( ref $patrons->{patrons}, 'ARRAY', "The patrons key is an arrayref");