From d3185b6d53618abf265fc66af24fc9fe68bcec2c Mon Sep 17 00:00:00 2001 From: Andrew Isherwood Date: Thu, 14 Jan 2021 15:20:09 +0000 Subject: [PATCH] Bug 27378: Add JS to power modaljs This commit adds the Javascript that provides the functionality within the consentJS modaljs modal. It provides the ability to display the current state of any consentJS values and to amend them, as well as add new ones. There is also some refactoring of the existing modalselect functions to allow re-use of existing code between modalselect & modaljs. This refactoring has been minimal and consists of abstracting functionality into functions to allow more wide use. Styles for the consentJS UI were also added. Signed-ff-by: Barry Cannon --- .../prog/css/src/staff-global.scss | 31 ++ .../prog/js/pages/preferences.js | 303 +++++++++++++++--- 2 files changed, 288 insertions(+), 46 deletions(-) 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 49a5ce8767..0d32e2ba20 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss @@ -4667,6 +4667,37 @@ div .suggestion_note { } } +#consentJSItems { + #consentJSWarning { + margin: 20px 0; + text-align: center; + font-weight: 800; + font-size: 120%; + } + .consentJSItem { + margin-bottom: 30px; + padding: 10px; + border: 1px solid #ccc; + } + .consentRow { + display: flex; + margin-bottom: 20px; + .consentItem { + margin-right: 40px; + } + .consentItem:last-child { + margin-right: 0; + } + } + .codeRow { + display: block; + } + .consentDelete { + display: block; + text-align: right; + } +} + @media (min-width: 200px) { } diff --git a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js b/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js index 20d0f4fe3c..89e32d1769 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js +++ b/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js @@ -64,6 +64,80 @@ window.onbeforeunload = function () { } }; +// Add event handlers to any elements with .expand-textarea classes +// this allows the CodeMirror widget to be displayed +function addExpandHandler() { + // Don't duplicate click event handlers + const ev = $._data($('.expand-textarea'), 'events'); + if (ev && ev.click) { + return; + } + $(".expand-textarea").on("click", function (e) { + e.preventDefault(); + $(this).hide(); + var target = $(this).data("target"); + var syntax = $(this).data("syntax"); + $("#collapse_" + target).show(); + if (syntax) { + var editor = CodeMirror.fromTextArea(document.getElementById("pref_" + target), { + lineNumbers: true, + mode: syntax, + lineWrapping: true, + viewportMargin: Infinity, + gutters: ["CodeMirror-lint-markers"], + lint: true + }); + editor.on("change", function () { + mark_modified.call($("#pref_" + target)[0]); + }); + editor.on("blur", function () { + editor.save(); + }); + } else { + $("#pref_" + target).show(); + } + }); +} + +// Add event handlers to any elements with .collapse-textarea classes +// this allows the hiding of the CodeMirror widget +function addCollapseHandler() { + // Don't duplicate click event handlers + const ev = $._data($('.collapse-textarea'), 'events'); + if (ev && ev.click) { + return; + } + $(".collapse-textarea").on("click", function (e) { + e.preventDefault(); + $(this).hide(); + var target = $(this).data("target"); + var syntax = $(this).data("syntax"); + $("#expand_" + target).show(); + if (syntax) { + var editor = $("#pref_" + target).next(".CodeMirror")[0].CodeMirror; + editor.toTextArea(); + } + $("#pref_" + target).hide(); + }); +} + +// Add a handler for any consent delete links +function addConsentDeleteHandler() { + // Don't duplicate click event handlers + const ev = $._data($('.consentDelete'), 'events'); + if (ev && ev.click) { + return; + } + $('.consentDelete').on('click', function (e) { + e.preventDefault(); + const target = $(this).data('target'); + const proceed = confirm(__('Are you sure you want to delete this consent item?')); + if (proceed) { + $('#' + target).remove(); + } + }); +} + $( document ).ready( function () { $("table.preferences").dataTable($.extend(true, {}, dataTablesDefaults, { @@ -107,44 +181,9 @@ $( document ).ready( function () { return false; }); - $( ".expand-textarea" ).on("click", function(e){ - e.preventDefault(); - $(this).hide(); - var target = $(this).data("target"); - var syntax = $(this).data("syntax"); - $("#collapse_" + target ).show(); - if( syntax ){ - var editor = CodeMirror.fromTextArea( document.getElementById( "pref_" + target ), { - lineNumbers: true, - mode: syntax, - lineWrapping: true, - viewportMargin: Infinity, - gutters: ["CodeMirror-lint-markers"], - lint: true - }); - editor.on("change", function(){ - mark_modified.call( $("#pref_" + target )[0]); - }); - editor.on("blur", function(){ - editor.save(); - }); - } else { - $("#pref_" + target ).show(); - } - }); + addExpandHandler(); - $( ".collapse-textarea" ).on("click", function(e){ - e.preventDefault(); - $(this).hide(); - var target = $(this).data("target"); - var syntax = $(this).data("syntax"); - $("#expand_" + target ).show(); - if( syntax ){ - var editor = $("#pref_" + target ).next(".CodeMirror")[0].CodeMirror; - editor.toTextArea(); - } - $("#pref_" + target ).hide(); - }); + addCollapseHandler(); $("h3").attr("class", "expanded").attr("title", __("Click to collapse this section")); var collapsible = $(".collapsed,.expanded"); @@ -262,28 +301,200 @@ $( document ).ready( function () { }).appendTo("#prefModalForm"); $("#saveModalPrefs").data("target", this.id ); + $("#saveModalPrefs").data("type", "modalselect" ); $("#prefModalLabel").text( pref_name ); $("#prefModal").modal("show"); }); - $(".modaljs").on("click", function () { - console.log('modaljs clicked'); + // Initialise the content of our modal, using the function + // specified in the data-initiator attribute + $('.modaljs').on('click', function () { + const init = $(this).data('initiator'); + if (init) { + window[init](this); + $("#prefModal").modal("show"); + } }); - $("#saveModalPrefs").on("click", function(){ - var formfieldid = $("#" + $(this).data("target") ); + // Initialise the content of our modal, if we are dealing + // with a modalselect modal + function prepareModalSelect(formfieldid) { var prefs = []; $("#prefModal input[type='checkbox']").each(function(){ if( $(this).prop("checked") ){ prefs.push( this.value ); } }); + return prefs.join("|"); + } + + // Return a checkbox with an appropriate checked state + function checkBox(id, className, state) { + return state ? + '' : + ''; + } + + // Create a consentJS item, correctly populated + function createConsentJSItem(item, idx) { + const id = 'ConsentJS_' + idx; + const code = item.code && item.code.length > 0 ? atob(item.code) : ''; + const itemId = item.id && item.id.length > 0 ? item.id : ''; + return '
' + __('Required') + '
' + __('Required') + '
' + checkBox('opacConsent_' + id, 'opacConsent', item.opacConsent) + '
' + checkBox('staffConsent_' + id, 'staffConsent', item.staffConsent) + '
Delete
'; + } + + // Return the markup for all consentJS items concatenated + function populateConsentMarkup(items) { + return items.reduce(function (acc, current, idx) { + return acc + createConsentJSItem(current, idx); + }, ''); + } + + // Return the markup for a validation warning + function populateValidationWarning() { + return ''; + } - formfieldid.val( prefs.join("|") ) - .addClass("modified"); - mark_modified.call( formfieldid ); - KOHA.Preferences.Save( formfieldid.closest("form") ); - $("#prefModal").modal("hide"); + // Return a new, empty consent item + function emptyConsentItem() { + return { + name: '', + description: '', + code: '', + consentInOpac: false, + consentInStaff: false + }; + } + + // Add the handler for a new empty consent item + function addNewHandler() { + $("#consentJSAddNew").on("click", function (e) { + e.preventDefault(); + const currentLen = $('.consentJSItem').length; + const newItem = emptyConsentItem(); + const markup = createConsentJSItem(newItem, currentLen); + $('#prefModal .modal-body #consentJSItems').append($(markup)); + addExpandHandler(); + addCollapseHandler(); + addConsentDeleteHandler(); + }); + } + + // Populate the consentJS modal, we also initialise any + // event handlers that are required. This function is added + // to the window object so we can call it if we are passed it's name + // as a data-initiator attribute + // (e.g.) + // const f = 'populateConsentJS'; + // window[f](); + window.populateConsentJS = function(el) { + let items = []; + let decoded = ''; + if (el.value && el.value.length > 0) { + try { + decoded = atob(el.value); + } catch (err) { + throw (__( + 'Unable to Base64 decode value stored in ConsentJS syspref: ' + + err.message + )); + } + try { + items = JSON.parse(decoded); + } catch (err) { + throw (__( + 'Unable to JSON parse decoded value stored in ConsentJS syspref: ' + + err.message + )); + } + } + const markup = populateConsentMarkup(items); + const validationWarning = populateValidationWarning(); + const pref_name = el.id.replace(/pref_/, ''); + $('#saveModalPrefs').data('target', el.id); + $('#prefModalLabel').text( pref_name ); + $('#prefModal .modal-body').html($('
' + validationWarning + markup + '
' + __('Add new code') + '
')); + addExpandHandler(); + addCollapseHandler(); + addNewHandler(); + addConsentDeleteHandler(); + } + + // Prepare the data in the UI for sending back as a syspref. + // We validate that everything is what we expect. This function is added + // to the window object so we can call it if we are passed it's name + // as a data-initiator attribute + // e.g. + // const f = 'prepareConsentJS'; + // window[f](); + window.prepareConsentJS = function () { + const items = $('.consentJSItem'); + const invalid = []; + const valid = []; + items.each(function () { + const id = $(this).data('id').length > 0 ? + $(this).data('id') : + '_' + Math.random().toString(36).substr(2, 9); + const name = $(this).find('.metaName').val(); + const desc = $(this).find('.metaDescription').val(); + const opacConsent = $(this).find('.opacConsent').is(':checked') + const staffConsent = $(this).find('.staffConsent').is(':checked'); + const code = $(this).find('.preference-code').val(); + // If the name, description and code are empty, then they've + // added a new entry, but not filled it in, we can skip it + if (name.length === 0 && desc.length === 0 && code.length === 0) { + return; + } + // They've filled in at least some info + if ( + (name.length === 0) || + (desc.length === 0) || + (code.length === 0) + ) { + invalid.push(this); + } else { + const obj = { + id: id, + name: name, + description: desc, + opacConsent: opacConsent, + staffConsent: staffConsent, + code: btoa(code) + } + valid.push(obj); + } + }); + // We failed validation + if (invalid.length > 0) { + $('#consentJSWarning').show(); + return false; + } + $('#consentJSWarning').hide(); + if (valid.length === 0) { + return ''; + } + const json = JSON.stringify(valid); + const base64 = btoa(json); + return base64; + } + + $("#saveModalPrefs").on("click", function(){ + var formfieldid = $("#" + $(this).data("target")); + let finalString = ""; + if ($(this).data("type") == "modalselect") { + finalString = prepareModalSelect(formfieldid); + } else { + const processor = $(".modaljs").data("processor"); + finalString = window[processor](); + } + // A processor can return false if any of the submitted + // data has failed validation + if (finalString !== false) { + formfieldid.val(finalString).addClass("modified"); + mark_modified.call( formfieldid ); + KOHA.Preferences.Save( formfieldid.closest("form") ); + $("#prefModal").modal("hide"); + } }); $("#prefModal").on("hide.bs.modal", function(){ -- 2.20.1