From ae659d6001a7a5e0caf31d52abc662cdd6b852ab Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Thu, 10 Jul 2014 10:26:57 -0400
Subject: [PATCH] Bug 12550 - Add ability to delay loading of patron's checkouts in circulation and patron details

Now that a patron's checkouts are loaded asynchronously, we can further
improve Koha's performance by not loading the checkouts table when it is
not needed. For example, if a librarian is checking out 5 items to a
patron, we really don't need to load the table during the scanning of
items 1 to 4, just for item 5. Another example would be browsing to the
patron details table in order to change a patron's password.

I propose we add two separate system preferences to add a timer for
loading the patron's checkouts table, one for circulation and one for
patron details. That way a library can choose to have enough delay so
that most librarians can scan items repeatedly without loading the
checkouts table, and can have the patron details table load more quickly
if they wish. In addition, setting one or both timeouts to 0 will cause
the system to act as it currently does.

Test Plan:
1) Apply this patch
2) Run updatedatabase.pl
3) Test circulation and patron details,
   no changes should be noted
4) Set CheckoutsTableSecondsToLoadCirculation and CheckoutsTableSecondsToLoadPatronDetails
   to some number of seconds.
5) Test again, note tables load after the set number of seconds
6) Test the "Load now" button, note it triggers the table to load immediately
---
 installer/data/mysql/sysprefs.sql                  |    2 +
 installer/data/mysql/updatedatabase.pl             |   12 +
 koha-tmpl/intranet-tmpl/prog/en/js/checkouts.js    |  417 +++++++++++---------
 .../prog/en/modules/admin/preferences/patrons.pref |    8 +
 .../prog/en/modules/circ/circulation.tt            |   13 +-
 .../prog/en/modules/members/moremember.tt          |   67 ++--
 6 files changed, 300 insertions(+), 219 deletions(-)

diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql
index ed427ea..6ece2b7 100644
--- a/installer/data/mysql/sysprefs.sql
+++ b/installer/data/mysql/sysprefs.sql
@@ -82,6 +82,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
 ('CatalogModuleRelink','0',NULL,'If OFF the linker will never replace the authids that are set in the cataloging module.','YesNo'),
 ('CataloguingLog','1',NULL,'If ON, log edit/create/delete actions on bibliographic data. WARNING: this feature is very resource consuming.','YesNo'),
 ('checkdigit','none','none|katipo','If ON, enable checks on patron cardnumber: none or \"Katipo\" style checks','Choice'),
+('CheckoutsTableSecondsToLoadCirculation', '0', NULL, 'Number of seconds to delay the loading of the checkouts table for circulation.', 'Integer'), 
+('CheckoutsTableSecondsToLoadPatronDetails', '0', NULL, 'Number of seconds to delay the loading of the checkouts table for patron details.', 'Integer'),
 ('CircAutocompl','1',NULL,'If ON, autocompletion is enabled for the Circulation input','YesNo'),
 ('CircAutoPrintQuickSlip','qslip',NULL,'Choose what should happen when an empty barcode field is submitted in circulation: Display a print quick slip window, Display a print slip window or Clear the screen.','Choice'),
 ('CircControl','ItemHomeLibrary','PickupLibrary|PatronLibrary|ItemHomeLibrary','Specify the agency that controls the circulation and fines policy','Choice'),
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index 1030454..83248ef 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -8570,6 +8570,18 @@ if ( CheckVersion($DBversion) ) {
     SetVersion($DBversion);
 }
 
+$DBversion = "3.17.00.XXX";
+if ( CheckVersion($DBversion) ) {
+    $dbh->do(q{
+        INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES 
+            ('CheckoutsTableSecondsToLoadCirculation', '0', NULL, 'Number of seconds to delay the loading of the checkouts table for circulation.', 'Integer'), 
+            ('CheckoutsTableSecondsToLoadPatronDetails', '0', NULL, 'Number of seconds to delay the loading of the checkouts table for patron details.', 'Integer');
+    });
+
+    print "Upgrade to $DBversion done (Bug 12550 - Add ability to delay loading of patron's checkouts in circulation and patron details)\n";
+    SetVersion($DBversion);
+}
+
 =head1 FUNCTIONS
 
 =head2 TableExists($table)
diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/checkouts.js b/koha-tmpl/intranet-tmpl/prog/en/js/checkouts.js
index 6efe9f9..cd12fae 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/js/checkouts.js
+++ b/koha-tmpl/intranet-tmpl/prog/en/js/checkouts.js
@@ -126,219 +126,254 @@ $(document).ready(function() {
 
     var ymd = $.datepicker.formatDate('yy-mm-dd', new Date());
 
-    var issuesTable;
-    var drawn = 0;
-    issuesTable = $("#issues-table").dataTable({
-        "oLanguage": {
-            "sEmptyTable" : MSG_DT_LOADING_RECORDS,
-        },
-        "bAutoWidth": false,
-        "sDom": "<'row-fluid'<'span6'><'span6'>r>t<'row-fluid'>t",
-        "aoColumns": [
-            {
-                "mDataProp": function( oObj ) {
-                    if ( oObj.issued_today ) {
-                        return "<strong>" + TODAYS_CHECKOUTS + "</strong>";
-                    } else {
-                        return "<strong>" + PREVIOUS_CHECKOUTS + "</strong>";
-                    }
-                }
-            },
-            {
-                "mDataProp": "date_due",
-                "bVisible": false,
+    // Don't bother loading table if there are no checkouts to show
+    if ( issues_count ) {
+        var issuesTable;
+
+        if ( checkouts_table_seconds_to_load ) {
+            $('#issues-table').hide();
+            $('#issues-table-actions').hide();
+            $('#issues-table-load-now-button').click(function(){
+                LoadIssuesTableNow();
+                return false;
+            });
+            var loadIssuesTableInterval = setInterval(function(){ CountdownIssuesTableLoading(); }, 1000);
+        } else {
+            $('#issues-table-loading-message').hide();
+            LoadIssuesTable();
+        }
+    }
+    
+    function CountdownIssuesTableLoading(){
+        if ( checkouts_table_seconds_to_load ) {
+            $('#issues-table-loading-count').text( checkouts_table_seconds_to_load );
+            checkouts_table_seconds_to_load--;
+        } else {
+            LoadIssuesTableNow();
+        }
+    }
+
+    function LoadIssuesTableNow() {
+        clearInterval(loadIssuesTableInterval);
+        $('#issues-table-loading-message').hide();
+        $('#issues-table').show();
+        $('#issues-table-actions').show();
+        LoadIssuesTable();
+    }
+
+    function LoadIssuesTable() {
+        issuesTable = $("#issues-table").dataTable({
+            "oLanguage": {
+                "sEmptyTable" : MSG_DT_LOADING_RECORDS,
             },
-            {
-                "iDataSort": 1, // Sort on hidden unformatted date due column
-                "mDataProp": function( oObj ) {
-                    var today = new Date();
-                    var due = new Date( oObj.date_due );
-                    if ( today > due ) {
-                        return "<span class='overdue'>" + oObj.date_due_formatted + "</span>";
-                    } else {
-                        return oObj.date_due_formatted;
+            "bAutoWidth": false,
+            "sDom": "<'row-fluid'<'span6'><'span6'>r>t<'row-fluid'>t",
+            "aoColumns": [
+                {
+                    "mDataProp": function( oObj ) {
+                        if ( oObj.issued_today ) {
+                            return "<strong>" + TODAYS_CHECKOUTS + "</strong>";
+                        } else {
+                            return "<strong>" + PREVIOUS_CHECKOUTS + "</strong>";
+                        }
                     }
-                }
-            },
-            {
-                "mDataProp": function ( oObj ) {
-                    title = "<span class='strong'><a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber="
-                          + oObj.biblionumber
-                          + "'>"
-                          + oObj.title;
-
-                    $.each(oObj.subtitle, function( index, value ) {
-                              title += " " + value.subfield;
-                    });
+                },
+                {
+                    "mDataProp": "date_due",
+                    "bVisible": false,
+                },
+                {
+                    "iDataSort": 1, // Sort on hidden unformatted date due column
+                    "mDataProp": function( oObj ) {
+                        var today = new Date();
+                        var due = new Date( oObj.date_due );
+                        if ( today > due ) {
+                            return "<span class='overdue'>" + oObj.date_due_formatted + "</span>";
+                        } else {
+                            return oObj.date_due_formatted;
+                        }
+                    }
+                },
+                {
+                    "mDataProp": function ( oObj ) {
+                        title = "<span class='strong'><a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber="
+                              + oObj.biblionumber
+                              + "'>"
+                              + oObj.title;
 
-                    title += "</a></span>";
+                        $.each(oObj.subtitle, function( index, value ) {
+                                  title += " " + value.subfield;
+                        });
 
-                    if ( oObj.author ) {
-                        title += " " + BY.replace( "_AUTHOR_",  " " + oObj.author );
-                    }
+                        title += "</a></span>";
 
-                    if ( oObj.itemnotes ) {
-                        var span_class = "";
-                        if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
-                            span_class = "circ-hlt";
+                        if ( oObj.author ) {
+                            title += " " + BY.replace( "_AUTHOR_",  " " + oObj.author );
                         }
-                        title += " - <span class='" + span_class + "'>" + oObj.itemnotes + "</span>"
-                    }
 
-                    title += " "
-                          + "<a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
-                          + oObj.biblionumber
-                          + "&itemnumber="
-                          + oObj.itemnumber
-                          + "#"
-                          + oObj.itemnumber
-                          + "'>"
-                          + oObj.barcode
-                          + "</a>";
-
-                    return title;
-                }
-            },
-            { "mDataProp": "itemtype" },
-            { "mDataProp": "issuedate_formatted" },
-            { "mDataProp": "branchname" },
-            { "mDataProp": "itemcallnumber" },
-            {
-                "mDataProp": function ( oObj ) {
-                    if ( ! oObj.charge ) oObj.charge = 0;
-                    return parseFloat(oObj.charge).toFixed(2);
-                }
-            },
-            {
-                "mDataProp": function ( oObj ) {
-                    if ( ! oObj.price ) oObj.price = 0;
-                    return parseFloat(oObj.price).toFixed(2);
-                }
-            },
-            {
-                "bSortable": false,
-                "mDataProp": function ( oObj ) {
-                    var content = "";
-                    var span_style = "";
-                    var span_class = "";
-
-                    content += "<span>";
-                    content += "<span style='padding: 0 1em;'>" + oObj.renewals_count + "</span>";
-
-                    if ( oObj.can_renew ) {
-                        // Do nothing
-                    } else if ( oObj.can_renew_error == "on_reserve" ) {
-                        content += "<span class='renewals-disabled'>"
-                                + "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>"
-                                + "</span>";
-
-                        span_style = "display: none";
-                        span_class = "renewals-allowed";
-                    } else if ( oObj.can_renew_error == "too_many" ) {
-                        content += "<span class='renewals-disabled'>"
-                                + NOT_RENEWABLE
-                                + "</span>";
-
-                        span_style = "display: none";
-                        span_class = "renewals-allowed";
-                    } else if ( oObj.can_renew_error == "too_soon" ) {
-                        content += "<span class='renewals-disabled'>"
-                                + NOT_RENEWABLE_TOO_SOON.format( oObj.can_renew_date )
-                                + "</span>";
-
-                        span_style = "display: none";
-                        span_class = "renewals-allowed";
-                    } else {
-                        content += "<span class='renewals-disabled'>"
-                                + oObj.can_renew_error
-                                + "</span>";
+                        if ( oObj.itemnotes ) {
+                            var span_class = "";
+                            if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
+                                span_class = "circ-hlt";
+                            }
+                            title += " - <span class='" + span_class + "'>" + oObj.itemnotes + "</span>"
+                        }
 
-                        span_style = "display: none";
-                        span_class = "renewals-allowed";
+                        title += " "
+                              + "<a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
+                              + oObj.biblionumber
+                              + "&itemnumber="
+                              + oObj.itemnumber
+                              + "#"
+                              + oObj.itemnumber
+                              + "'>"
+                              + oObj.barcode
+                              + "</a>";
+
+                        return title;
+                    }
+                },
+                { "mDataProp": "itemtype" },
+                { "mDataProp": "issuedate_formatted" },
+                { "mDataProp": "branchname" },
+                { "mDataProp": "itemcallnumber" },
+                {
+                    "mDataProp": function ( oObj ) {
+                        if ( ! oObj.charge ) oObj.charge = 0;
+                        return parseFloat(oObj.charge).toFixed(2);
+                    }
+                },
+                {
+                    "mDataProp": function ( oObj ) {
+                        if ( ! oObj.price ) oObj.price = 0;
+                        return parseFloat(oObj.price).toFixed(2);
                     }
+                },
+                {
+                    "bSortable": false,
+                    "mDataProp": function ( oObj ) {
+                        var content = "";
+                        var span_style = "";
+                        var span_class = "";
 
-                    content += "<span class='" + span_class + "' style='" + span_style + "'>"
-                            +  "<input type='checkbox' class='renew' id='renew_" + oObj.itemnumber + "' name='renew' value='" + oObj.itemnumber +"'/>"
-                            +  "</span>";
+                        content += "<span>";
+                        content += "<span style='padding: 0 1em;'>" + oObj.renewals_count + "</span>";
+
+                        if ( oObj.can_renew ) {
+                            // Do nothing
+                        } else if ( oObj.can_renew_error == "on_reserve" ) {
+                            content += "<span class='renewals-disabled'>"
+                                    + "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>"
+                                    + "</span>";
+
+                            span_style = "display: none";
+                            span_class = "renewals-allowed";
+                        } else if ( oObj.can_renew_error == "too_many" ) {
+                            content += "<span class='renewals-disabled'>"
+                                    + NOT_RENEWABLE
+                                    + "</span>";
+
+                            span_style = "display: none";
+                            span_class = "renewals-allowed";
+                        } else if ( oObj.can_renew_error == "too_soon" ) {
+                            content += "<span class='renewals-disabled'>"
+                                    + NOT_RENEWABLE_TOO_SOON.format( oObj.can_renew_date )
+                                    + "</span>";
+
+                            span_style = "display: none";
+                            span_class = "renewals-allowed";
+                        } else {
+                            content += "<span class='renewals-disabled'>"
+                                    + oObj.can_renew_error
+                                    + "</span>";
+
+                            span_style = "display: none";
+                            span_class = "renewals-allowed";
+                        }
 
-                    if ( oObj.renewals_remaining ) {
-                        content += "<span class='renewals'>("
-                                + RENEWALS_REMAINING.format( oObj.renewals_remaining, oObj.renewals_allowed )
-                                + ")</span>";
-                    }
+                        content += "<span class='" + span_class + "' style='" + span_style + "'>"
+                                +  "<input type='checkbox' class='renew' id='renew_" + oObj.itemnumber + "' name='renew' value='" + oObj.itemnumber +"'/>"
+                                +  "</span>";
 
-                    content += "</span>";
+                        if ( oObj.renewals_remaining ) {
+                            content += "<span class='renewals'>("
+                                    + RENEWALS_REMAINING.format( oObj.renewals_remaining, oObj.renewals_allowed )
+                                    + ")</span>";
+                        }
+
+                        content += "</span>";
 
 
-                    return content;
+                        return content;
+                    }
+                },
+                {
+                    "bSortable": false,
+                    "mDataProp": function ( oObj ) {
+                        if ( oObj.can_renew_error == "on_reserve" ) {
+                            return "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>";
+                        } else {
+                            return "<input type='checkbox' class='checkin' id='checkin_" + oObj.itemnumber + "' name='checkin' value='" + oObj.itemnumber +"'></input>";
+                        }
+                    }
+                },
+                {
+                    "bVisible": exports_enabled ? true : false,
+                    "bSortable": false,
+                    "mDataProp": function ( oObj ) {
+                        return "<input type='checkbox' class='export' id='export_" + oObj.biblionumber + "' name='biblionumbers' value='" + oObj.biblionumber + "' />";
+                    }
+                }
+            ],
+            "fnFooterCallback": function ( nRow, aaData, iStart, iEnd, aiDisplay ) {
+                var total_charge = 0;
+                var total_price = 0;
+                for ( var i=0; i < aaData.length; i++ ) {
+                    total_charge += aaData[i]['charge'] * 1;
+                    total_price  += aaData[i]['price'] * 1;
                 }
+                var nCells = nRow.getElementsByTagName('td');
+                nCells[1].innerHTML = total_charge.toFixed(2);
+                nCells[2].innerHTML = total_price.toFixed(2);
             },
-            {
-                "bSortable": false,
-                "mDataProp": function ( oObj ) {
-                    if ( oObj.can_renew_error == "on_reserve" ) {
-                        return "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>";
-                    } else {
-                        return "<input type='checkbox' class='checkin' id='checkin_" + oObj.itemnumber + "' name='checkin' value='" + oObj.itemnumber +"'></input>";
+            "bPaginate": false,
+            "bProcessing": true,
+            "bServerSide": false,
+            "sAjaxSource": '/cgi-bin/koha/svc/checkouts',
+            "fnServerData": function ( sSource, aoData, fnCallback ) {
+                aoData.push( { "name": "borrowernumber", "value": borrowernumber } );
+
+                $.getJSON( sSource, aoData, function (json) {
+                    fnCallback(json)
+                } );
+            },
+            "fnInitComplete": function(oSettings) {
+                // Disable rowGrouping plugin after first use
+                // so any sorting on the table doesn't use it
+                var oSettings = issuesTable.fnSettings();
+
+                for (f = 0; f < oSettings.aoDrawCallback.length; f++) {
+                    if (oSettings.aoDrawCallback[f].sName == 'fnRowGrouping') {
+                        oSettings.aoDrawCallback.splice(f, 1);
+                        break;
                     }
                 }
+
+                oSettings.aaSortingFixed = null;
             },
+        }).rowGrouping(
             {
-                "bVisible": exports_enabled ? true : false,
-                "bSortable": false,
-                "mDataProp": function ( oObj ) {
-                    return "<input type='checkbox' class='export' id='export_" + oObj.biblionumber + "' name='biblionumbers' value='" + oObj.biblionumber + "' />";
-                }
-            }
-        ],
-        "fnFooterCallback": function ( nRow, aaData, iStart, iEnd, aiDisplay ) {
-            var total_charge = 0;
-            var total_price = 0;
-            for ( var i=0; i < aaData.length; i++ ) {
-                total_charge += aaData[i]['charge'] * 1;
-                total_price  += aaData[i]['price'] * 1;
-            }
-            var nCells = nRow.getElementsByTagName('td');
-            nCells[1].innerHTML = total_charge.toFixed(2);
-            nCells[2].innerHTML = total_price.toFixed(2);
-        },
-        "bPaginate": false,
-        "bProcessing": true,
-        "bServerSide": false,
-        "sAjaxSource": '/cgi-bin/koha/svc/checkouts',
-        "fnServerData": function ( sSource, aoData, fnCallback ) {
-            aoData.push( { "name": "borrowernumber", "value": borrowernumber } );
-
-            $.getJSON( sSource, aoData, function (json) {
-                fnCallback(json)
-            } );
-        },
-        "fnInitComplete": function(oSettings) {
-            // Disable rowGrouping plugin after first use
-            // so any sorting on the table doesn't use it
-            var oSettings = issuesTable.fnSettings();
-
-            for (f = 0; f < oSettings.aoDrawCallback.length; f++) {
-                if (oSettings.aoDrawCallback[f].sName == 'fnRowGrouping') {
-                    oSettings.aoDrawCallback.splice(f, 1);
-                    break;
-                }
+                iGroupingOrderByColumnIndex: 0,
+                sGroupingColumnSortDirection: "desc"
             }
+        );
 
-            oSettings.aaSortingFixed = null;
-        },
-    }).rowGrouping(
-        {
-            iGroupingOrderByColumnIndex: 0,
-            sGroupingColumnSortDirection: "desc"
+        if ( $("#issues-table").length ) {
+            $("#issues-table_processing").position({
+                of: $( "#issues-table" ),
+                collision: "none"
+            });
         }
-    );
-
-    if ( $("#issues-table").length ) {
-        $("#issues-table_processing").position({
-            of: $( "#issues-table" ),
-            collision: "none"
-        });
     }
 
     // Don't load relatives' issues table unless it is clicked on
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
index 2469431..d6a4204 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
@@ -138,3 +138,11 @@ Patrons:
          - pref: CardnumberLength
          - "characters long. The length can be a single number to specify an exact length, a range separated by a comma (i.e., 'Min,Max'), or a maximum with no minimum (i.e., ',Max')."
          - "If 'cardnumber' is included in the BorrowerMandatoryField list, the minimum length, if not specified here, defaults to one."
+     -
+         - Wait
+         - pref: CheckoutsTableSecondsToLoadCirculation
+           class: integer
+         - seconds before loading the patron's checkouts table when checking out items to a patron and
+         - pref: CheckoutsTableSecondsToLoadPatronDetails
+           class: integer
+         - seconds when viewing a patron's details.
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
index d573ba4..a56f0cf 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
@@ -1,3 +1,4 @@
+[% USE Koha %]
 [% USE Branches %]
 [% USE KohaDates %]
 [% IF ( export_remove_fields OR export_with_csv_profile ) %]
@@ -33,6 +34,9 @@ var borrowernumber = "[% borrowernumber %]";
 var branchcode = "[% branch %]";
 var exports_enabled = "[% exports_enabled %]";
 var AllowRenewalLimitOverride = [% CAN_user_circulate_override_renewals && AllowRenewalLimitOverride %];
+var issues_count = [% issuecount %];
+[%- SET CheckoutsTableSecondsToLoad = Koha.Preference('CheckoutsTableSecondsToLoadCirculation') -%]
+var checkouts_table_seconds_to_load = [% CheckoutsTableSecondsToLoad ? CheckoutsTableSecondsToLoad - 1 : 0 %];
 var relatives_borrowernumbers = new Array();
 [% FOREACH b IN relatives_borrowernumbers %]
     relatives_borrowernumbers.push("[% b %]");
@@ -680,6 +684,13 @@ No patron matched <span class="ex">[% message %]</span>
 <!-- SUMMARY : TODAY & PREVIOUS ISSUES -->
 <div id="checkouts">
     [% IF ( issuecount ) %]
+        <div id="issues-table-loading-message">
+            <p>
+                <a id="issues-table-load-now-button" href="#" class="btn"><i class="icon-download"></i> Load now</a>
+                Loading in <span id="issues-table-loading-count">[% Koha.Preference('CheckoutsTableSecondsToLoadCirculation') %]</span> seconds...
+            </p>
+        </div>
+
         <table id="issues-table">
             <thead>
                 <tr>
@@ -701,7 +712,7 @@ No patron matched <span class="ex">[% message %]</span>
             [% INCLUDE 'checkouts-table-footer.inc' %]
         </table>
 
-        <fieldset class="action">
+        <fieldset id="issues-table-actions" class="action">
             [% IF ( CAN_user_circulate_override_renewals ) %]
                 [% IF ( AllowRenewalLimitOverride ) %]
                     <label for="override_limit">Override renewal limit:</label>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
index d6b95f8..465a719 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt
@@ -1,3 +1,4 @@
+[% USE Koha %]
 [% USE KohaDates %]
 [% INCLUDE 'doc-head-open.inc' %]
 <title>Koha &rsaquo; Patrons &rsaquo;
@@ -27,6 +28,9 @@ var borrowernumber = "[% borrowernumber %]";
 var branchcode = "[% branch %]";
 var exports_enabled = "[% exports_enabled %]";
 var AllowRenewalLimitOverride = [% CAN_user_circulate_override_renewals && AllowRenewalLimitOverride %];
+var issues_count = [% issuecount %];
+[%- SET CheckoutsTableSecondsToLoad = Koha.Preference('CheckoutsTableSecondsToLoadPatronDetails') -%]
+var checkouts_table_seconds_to_load = [% CheckoutsTableSecondsToLoad ? CheckoutsTableSecondsToLoad - 1 : 0 %];
 var relatives_borrowernumbers = new Array();
 [% FOREACH b IN relatives_borrowernumbers %]
     relatives_borrowernumbers.push("[% b %]");
@@ -408,6 +412,13 @@ function validate1(date) {
 
     <div id="checkouts">
         [% IF ( issuecount ) %]
+            <div id="issues-table-loading-message">
+                <p>
+                    <a id="issues-table-load-now-button" href="#" class="btn"><i class="icon-download"></i> Load now</a>
+                    Loading in <span id="issues-table-loading-count">[% Koha.Preference('CheckoutsTableSecondsToLoadPatronDetails') %]</span> seconds...
+                </p>
+            </div>
+
             <form name="issues" action="/cgi-bin/koha/tools/export.pl" method="post" class="checkboxed">
                 <table id="issues-table" style="width: 100% !Important;">
                     <thead>
@@ -431,36 +442,38 @@ function validate1(date) {
                 </table>
 
                 [% IF ( issuecount ) %]
-                    <fieldset class="action">
-                        [% IF ( CAN_user_circulate_override_renewals ) %]
-                            [% IF ( AllowRenewalLimitOverride ) %]
-                                <label for="override_limit">Override renewal limit:</label>
-                                <input type="checkbox" name="override_limit" id="override_limit" value="1" />
-                            [% END %]
-                        [% END %]
-                        <button class="btn" id="RenewCheckinChecked"><i class="icon-check"></i> Renew or return checked items</button>
-                        <button class="btn" id="RenewAll"><i class="icon-book"></i> Renew all</button>
-                    </fieldset>
-
-                    [% IF ( exports_enabled ) %]
-                        <fieldset>
-                            <label for="export_formats"><b>Export checkouts using format:</b></label>
-                            <select name="export_formats" id="export_formats">
-                                <option value="iso2709_995">ISO2709 with items</option>
-                                <option value="iso2709">ISO2709 without items</option>
-                                [% IF ( export_with_csv_profile ) %]
-                                    <option value="csv">CSV</option>
+                    <div id="issues-table-actions">
+                        <fieldset class="action">
+                            [% IF ( CAN_user_circulate_override_renewals ) %]
+                                [% IF ( AllowRenewalLimitOverride ) %]
+                                    <label for="override_limit">Override renewal limit:</label>
+                                    <input type="checkbox" name="override_limit" id="override_limit" value="1" />
                                 [% END %]
-                            </select>
-
-                           <label for="export_remove_fields">Don't export fields:</label> <input type="text" id="export_remove_fields" name="export_remove_fields" value="[% export_remove_fields %]" title="Use for iso2709 exports" />
-                            <input type="hidden" name="op" value="export" />
-                            <input type="hidden" id="export_format" name="format" value="iso2709" />
-                            <input type="hidden" id="dont_export_item" name="dont_export_item" value="0" />
-                            <input type="hidden" id="record_type" name="record_type" value="bibs" />
-                            <button class="btn btn-small" id="export_submit"><i class="icon-download-alt"></i> Export</button>
+                            [% END %]
+                            <button class="btn" id="RenewCheckinChecked"><i class="icon-check"></i> Renew or return checked items</button>
+                            <button class="btn" id="RenewAll"><i class="icon-book"></i> Renew all</button>
                         </fieldset>
-                    [% END %]
+
+                        [% IF ( exports_enabled ) %]
+                            <fieldset>
+                                <label for="export_formats"><b>Export checkouts using format:</b></label>
+                                <select name="export_formats" id="export_formats">
+                                    <option value="iso2709_995">ISO2709 with items</option>
+                                    <option value="iso2709">ISO2709 without items</option>
+                                    [% IF ( export_with_csv_profile ) %]
+                                        <option value="csv">CSV</option>
+                                    [% END %]
+                                </select>
+
+                               <label for="export_remove_fields">Don't export fields:</label> <input type="text" id="export_remove_fields" name="export_remove_fields" value="[% export_remove_fields %]" title="Use for iso2709 exports" />
+                                <input type="hidden" name="op" value="export" />
+                                <input type="hidden" id="export_format" name="format" value="iso2709" />
+                                <input type="hidden" id="dont_export_item" name="dont_export_item" value="0" />
+                                <input type="hidden" id="record_type" name="record_type" value="bibs" />
+                                <button class="btn btn-small" id="export_submit"><i class="icon-download-alt"></i> Export</button>
+                            </fieldset>
+                        [% END %]
+                    </div>
                 [% END %]
             </form>
         [% ELSE %]
-- 
1.7.2.5