@@ -, +, @@ --- .../prog/css/src/staff-global.scss | 31 ++ .../prog/js/pages/preferences.js | 303 +++++++++++++++--- 2 files changed, 288 insertions(+), 46 deletions(-) --- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss +++ a/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) { } --- a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js +++ a/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) + '
' + __('Click to expand') + '
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(){ --