From 0afeb3a9ee4786d503a280d695e20e771aa68561 Mon Sep 17 00:00:00 2001
From: Owen Leonard <oleonard@myacpl.org>
Date: Fri, 3 May 2019 13:56:02 +0000
Subject: [PATCH] Bug 22844: Simplify the process of selecting database columns
 for system preferences

This patch introduces a new way for users to select database columns for
system preferences like BorrowerMandatoryField, which currently
require hand-typing of database names.

This new system uses a JSON file containing label:column pairs for
database columns which are relevant to preferences which reference
borrower table columns. My intention was to have user-friendly values as
the labels, but embedding English strings in JSON would make them
untranslatable.

The following preferences are affected:

 - BorrowerMandatoryField
 - BorrowerUnwantedField
 - PatronSelfModificationBorrowerUnwantedField
 - PatronSelfRegistrationBorrowerMandatoryField
 - PatronSelfRegistrationBorrowerUnwantedField

To test, apply the patch and regenerate the staff client CSS.
restart_all to make sure the updated .pref file is used.

 - Go to Administration -> System preferences, and search for
   "PatronSelf"
 - The input fields for PatronSelfModificationBorrowerUnwantedField,
   PatronSelfRegistrationBorrowerMandatoryField, and
   PatronSelfRegistrationBorrowerUnwantedField should appear as "locked"
   (read-only) inputs.
 - Clicking the input field should trigger a modal window with
   checkboxes for each available column from the borrowers table.
 - Test that the "select all" and "clear all" links work correctly.
 - Test that the "cancel" link closes the modal without saving your
   selections.
 - Test that the "Save" button closes the modal, copies your selections
   to the form field, and triggers the preference-saving function (this
   eliminates the need to click a save button again after closing the
   modal).
   - Test this process by making modifications to all three different
     preferences, confirming that the right data is preselected each
     time the modal is shown and the right data is saved to the right
     field each time.
---
 admin/preferences.pl                               |  6 +++
 koha-tmpl/intranet-tmpl/prog/css/preferences.css   | 26 ++++++++-
 .../intranet-tmpl/prog/css/src/staff-global.scss   |  5 ++
 .../prog/en/modules/admin/preferences.tt           | 29 +++++++++-
 .../en/modules/admin/preferences/borrowers.json    | 53 +++++++++++++++++++
 .../prog/en/modules/admin/preferences/opac.pref    | 12 ++---
 .../prog/en/modules/admin/preferences/patrons.pref |  7 +--
 .../intranet-tmpl/prog/js/pages/preferences.js     | 61 +++++++++++++++++++++-
 8 files changed, 187 insertions(+), 12 deletions(-)
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/borrowers.json

diff --git a/admin/preferences.pl b/admin/preferences.pl
index 92c5bbe..2862e69 100755
--- a/admin/preferences.pl
+++ b/admin/preferences.pl
@@ -64,6 +64,12 @@ sub _get_chunk {
     if( $options{'syntax'} ){
         $chunk->{'syntax'} = $options{'syntax'};
     }
+
+    if( $options{'type'} && $options{'type'} eq 'modalselect' ){
+        $chunk->{'source'} = $options{'source'};
+        $chunk->{'type'} = 'modalselect';
+    }
+
     if ( $options{'class'} && $options{'class'} eq 'password' ) {
         $chunk->{'input_type'} = 'password';
     } elsif ( $options{'class'} && $options{'class'} eq 'date' ) {
diff --git a/koha-tmpl/intranet-tmpl/prog/css/preferences.css b/koha-tmpl/intranet-tmpl/prog/css/preferences.css
index 7ecce1b..0b27e49 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/preferences.css
+++ b/koha-tmpl/intranet-tmpl/prog/css/preferences.css
@@ -1,4 +1,8 @@
-.preference-url, .preference-multi, .preference-long, .preference-file {
+.preference-url,
+.preference-multi,
+.preference-long,
+.preference-file,
+.preference-modalselect {
     width: 20em;
 }
 
@@ -10,6 +14,14 @@
     width: 5em;
 }
 
+input[type="text"].modalselect {
+    cursor: pointer;
+}
+
+input[type="text"].modalselect:hover {
+    background-color: #FFC;
+}
+
 textarea.preference {
     width: 35em;
 	height: 20em;
@@ -134,4 +146,16 @@ span.overridden {
     border: 1px solid #EEE;
     margin: 1em 1em 1em 0;
     resize:  vertical;
+}
+
+#prefModal label {
+    display: block;
+    font-weight: bold;
+    line-height: 1.5em;
+}
+
+.dbcolumn {
+    font-weight: normal;
+    font-family: monospace;
+    color: #666;
 }
\ No newline at end of file
diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
index c0f13ae..fefd7dd 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -4285,6 +4285,11 @@ span {
     margin:  .5em 0;
 }
 
+.columns-2 {
+    columns: 2 auto;
+    column-gap: 2.5em;
+}
+
 .columns-3 {
     columns: 3 auto;
     column-gap: 2.5em;
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences.tt
index 764f164..c9ef8e7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences.tt
@@ -92,6 +92,8 @@
                         </option>
                         [% END %]
                     </select>
+                    [% ELSIF ( CHUNK.type_modalselect ) %]
+                        <input type="text" name="pref_[% CHUNK.name | html %]" id="pref_[% CHUNK.name | html %]" class="modalselect preference preference-[% CHUNK.type | html %]" data-source="[% CHUNK.source | html %]" readonly="readonly" value="[% CHUNK.value | html %]"/>
                     [% ELSIF ( CHUNK.type_multiple ) %]
                     <select name="pref_[% CHUNK.name | html %]" id="pref_[% CHUNK.name | html %]" class="preference preference-[% CHUNK.class or "choice" | html %]" multiple="multiple">
                         [% FOREACH CHOICE IN CHUNK.CHOICES %][% IF ( CHOICE.selected ) %]<option value="[% CHOICE.value | html %]" selected="selected">[% ELSE %]<option value="[% CHOICE.value | html %]">[% END %][% CHOICE.text | html %]</option>[% END %]
@@ -163,6 +165,31 @@
         </div> <!-- /.col-sm-2.col-sm-pull-10 -->
      </div> <!-- /.row -->
 
+<!-- Modal -->
+<div class="modal" id="prefModal" tabindex="-1" role="dialog" aria-labelledby="prefModalLabel">
+    <div class="modal-dialog modal-wide" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title" id="prefModalLabel">Modal title</h4>
+            </div>
+            <div class="modal-body">
+                <p>
+                    <a href="#" id="select_all"><i class="fa fa-check"></i> Select all</a>
+                    |
+                    <a href="#" id="clear_all"><i class="fa fa-remove"></i> Clear all</a>
+                </p>
+                <form action="#" id="prefModalForm">
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button id="saveModalPrefs" data-target="" type="button" class="btn btn-default">Save</button>
+                <button type="button" class="btn btn-link cancel" data-dismiss="modal">Cancel</button>
+            </div>
+        </div>
+    </div>
+</div>
+
 [% MACRO jsinclude BLOCK %]
     [% INCLUDE 'datatables.inc' %]
     [% Asset.js("lib/hc-sticky.js") | $raw %]
@@ -174,6 +201,7 @@
     [% Asset.js( "lib/codemirror/yaml.min.js" ) | $raw %]
     <script>
         var Sticky;
+        var themelang = "[% themelang | html %]";
         $(document).ready(function(){
             [% UNLESS ( searchfield ) %]
                 Sticky = $("#toolbar");
@@ -193,7 +221,6 @@
                 e.preventDefault();
                 window.location.reload(true);
             });
-
         });
         // This is here because of its dependence on template variables, everything else should go in js/pages/preferences.js - jpw
         var to_highlight = "[% searchfield |replace("'", "\'") |replace('"', '\"') |replace('\n', '\\n') |replace('\r', '\\r') | html %]";
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/borrowers.json b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/borrowers.json
new file mode 100644
index 0000000..fc1482f
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/borrowers.json
@@ -0,0 +1,53 @@
+{
+    "cardnumber": "cardnumber",
+    "surname": "surname",
+    "firstname": "firstname",
+    "title": "title",
+    "othernames": "othernames",
+    "initials": "initials",
+    "streetnumber": "streetnumber",
+    "streettype": "streettype",
+    "address": "address",
+    "address2": "address2",
+    "city": "city",
+    "state": "state",
+    "zipcode": "zipcode",
+    "country": "country",
+    "email": "email",
+    "phone": "phone",
+    "mobile": "mobile",
+    "fax": "fax",
+    "emailpro": "emailpro",
+    "phonepro": "phonepro",
+    "B_streetnumber": "B_streetnumber",
+    "B_streettype": "B_streettype",
+    "B_address": "B_address",
+    "B_address2": "B_address2",
+    "B_city": "B_city",
+    "B_state": "B_state",
+    "B_zipcode": "B_zipcode",
+    "B_country": "B_country",
+    "B_email": "B_email",
+    "B_phone": "B_phone",
+    "dateofbirth": "dateofbirth",
+    "branchcode": "branchcode",
+    "categorycode": "categorycode",
+    "contactname": "contactname",
+    "contactfirstname": "contactfirstname",
+    "borrowernotes": "borrowernotes",
+    "sex": "sex",
+    "password": "password",
+    "userid": "userid",
+    "opacnote": "opacnote",
+    "contactnote": "contactnote",
+    "altcontactfirstname": "altcontactfirstname",
+    "altcontactsurname": "altcontactsurname",
+    "altcontactaddress1": "altcontactaddress1",
+    "altcontactaddress2": "altcontactaddress2",
+    "altcontactaddress3": "altcontactaddress3",
+    "altcontactstate": "altcontactstate",
+    "altcontactzipcode": "altcontactzipcode",
+    "altcontactcountry": "altcontactcountry",
+    "altcontactphone": "altcontactphone",
+    "smsalertnumber": "smsalertnumber"
+}
\ No newline at end of file
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
index 8300553..6ae7027 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
@@ -793,18 +793,18 @@ OPAC:
         -
             - "The following <a href='http://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='blank'>database columns</a> must be filled in on the patron entry screen:"
             - pref: PatronSelfRegistrationBorrowerMandatoryField
-              class: multi
-            - (separate columns with |)
+              type: modalselect
+              source: borrowers
         -
             - "The following <a href='http://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='blank'>database columns</a> will not appear on the patron self-registration screen:"
             - pref: PatronSelfRegistrationBorrowerUnwantedField
-              class: multi
-            - (separate columns with |)
+              type: modalselect
+              source: borrowers
         -
             - "The following <a href='http://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='blank'>database columns</a> will not appear on the patron self-modification screen:"
             - pref: PatronSelfModificationBorrowerUnwantedField
-              class: multi
-            - (separate columns with |)
+              type: modalselect
+              source: borrowers
         -
             - "Display the following additional instructions for patrons who self register via the OPAC ( HTML is allowed ):"
             - pref: PatronSelfRegistrationAdditionalInstructions
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 9007da9..2920c2c 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
@@ -39,13 +39,14 @@ Patrons:
      -
          - "The following <a href='http://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='blank'>database columns</a> must be filled in on the patron entry screen:"
          - pref: BorrowerMandatoryField
-           class: multi
-         - (separate columns with |)
+           type: modalselect
+           source: borrowers
          - "<strong>NOTE:</strong> If autoMemberNum is enabled, the system preference BorrowerMandatoryField must not contain the field cardnumber."
      -
          - "The following <a href='http://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='blank'>database columns</a> will not appear on the patron entry screen:"
          - pref: BorrowerUnwantedField
-           class: multi
+           type: modalselect
+           source: borrowers
          - (separate columns with |)
      -
          - "Guarantors can be the following of those they guarantee:"
diff --git a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js b/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js
index d5db030..9d9599a 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js
@@ -1,4 +1,4 @@
-/* global KOHA MSG_MADE_CHANGES CodeMirror MSG_CLICK_TO_EXPAND MSG_CLICK_TO_COLLAPSE to_highlight search_jumped humanMsg MSG_NOTHING_TO_SAVE MSG_MODIFIED MSG_SAVING MSG_SAVED_PREFERENCE dataTablesDefaults */
+/* global KOHA MSG_MADE_CHANGES CodeMirror MSG_CLICK_TO_EXPAND MSG_CLICK_TO_COLLAPSE to_highlight search_jumped humanMsg MSG_NOTHING_TO_SAVE MSG_MODIFIED MSG_SAVING MSG_SAVED_PREFERENCE dataTablesDefaults themelang */
 // We can assume 'KOHA' exists, as we depend on KOHA.AJAX
 
 KOHA.Preferences = {
@@ -205,4 +205,63 @@ $( document ).ready( function () {
             email: true
         });
     });
+
+
+    $(".modalselect").on("click", function(){
+        var datasource = $(this).data("source");
+        var pref_name = this.id.replace(/pref_/, '');
+        var pref_value = this.value;
+        var prefs = pref_value.split("|");
+
+        $.getJSON( themelang + "/modules/admin/preferences/" + datasource + ".json", function( data ){
+            var items = [];
+            var checked = "";
+            $.each( data, function( key, val ){
+                if( prefs.indexOf( val ) >= 0 ){
+                    checked = ' checked="checked" ';
+                } else {
+                    checked = "";
+                }
+                items.push('<label><input class="dbcolumn_selection" type="checkbox" id="' + key + '"' + checked + ' name="pref" value="' + val + '" /> ' + key + '</label>');
+            });
+            $("<div/>", {
+                "class": "columns-2",
+                html: items.join("")
+            }).appendTo("#prefModalForm");
+        });
+        $("#saveModalPrefs").data("target", this.id );
+        $("#prefModalLabel").text( pref_name );
+        $("#prefModal").modal("show");
+    });
+
+    $("#saveModalPrefs").on("click", function(){
+        var formfieldid = $("#" + $(this).data("target") );
+        var prefs = [];
+        $("#prefModal input[type='checkbox']").each(function(){
+            if( $(this).prop("checked") ){
+                prefs.push( this.value );
+            }
+        });
+
+        formfieldid.val( prefs.join("|") )
+            .addClass("modified");
+        mark_modified.call( formfieldid );
+        KOHA.Preferences.Save( formfieldid.closest("form") );
+        $("#prefModal").modal("hide");
+    });
+
+    $("#prefModal").on("hide.bs.modal", function(){
+        $("#prefModalLabel,#prefModalForm").html("");
+        $("#saveModalPrefs").data("target", "" );
+    });
+
+    $("#select_all").on("click",function(e){
+        e.preventDefault();
+        $(".dbcolumn_selection").prop("checked", true);
+    });
+    $("#clear_all").on("click",function(e){
+        e.preventDefault();
+        $(".dbcolumn_selection").prop("checked", false);
+    });
+
 } );
-- 
2.1.4