View | Details | Raw Unified | Return to bug 27378
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+31 lines)
Lines 4349-4354 div .suggestion_note { Link Here
4349
    }
4349
    }
4350
}
4350
}
4351
4351
4352
#consentJSItems {
4353
    #consentJSWarning {
4354
        margin: 20px 0;
4355
        text-align: center;
4356
        font-weight: 800;
4357
        font-size: 120%;
4358
    }
4359
    .consentJSItem {
4360
        margin-bottom: 30px;
4361
        padding: 10px;
4362
        border: 1px solid #ccc;
4363
    }
4364
    .consentRow {
4365
        display: flex;
4366
        margin-bottom: 20px;
4367
        .consentItem {
4368
            margin-right: 40px;
4369
        }
4370
        .consentItem:last-child {
4371
            margin-right: 0;
4372
        }
4373
    }
4374
    .codeRow {
4375
        display: block;
4376
    }
4377
    .consentDelete {
4378
        display: block;
4379
        text-align: right;
4380
    }
4381
}
4382
4352
@media (min-width: 200px) {
4383
@media (min-width: 200px) {
4353
4384
4354
}
4385
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/pages/preferences.js (-47 / +257 lines)
Lines 64-69 window.onbeforeunload = function () { Link Here
64
    }
64
    }
65
};
65
};
66
66
67
// Add event handlers to any elements with .expand-textarea classes
68
// this allows the CodeMirror widget to be displayed
69
function addExpandHandler() {
70
    // Don't duplicate click event handlers
71
    const ev = $._data($('.expand-textarea'), 'events');
72
    if (ev && ev.click) {
73
        return;
74
    }
75
    $(".expand-textarea").on("click", function (e) {
76
        e.preventDefault();
77
        $(this).hide();
78
        var target = $(this).data("target");
79
        var syntax = $(this).data("syntax");
80
        $("#collapse_" + target).show();
81
        if (syntax) {
82
            var editor = CodeMirror.fromTextArea(document.getElementById("pref_" + target), {
83
                lineNumbers: true,
84
                mode: syntax,
85
                lineWrapping: true,
86
                viewportMargin: Infinity,
87
                gutters: ["CodeMirror-lint-markers"],
88
                lint: true
89
            });
90
            editor.on("change", function () {
91
                mark_modified.call($("#pref_" + target)[0]);
92
            });
93
            editor.on("blur", function () {
94
                editor.save();
95
            });
96
        } else {
97
            $("#pref_" + target).show();
98
        }
99
    });
100
}
101
102
// Add event handlers to any elements with .collapse-textarea classes
103
// this allows the hiding of the CodeMirror widget
104
function addCollapseHandler() {
105
    // Don't duplicate click event handlers
106
    const ev = $._data($('.collapse-textarea'), 'events');
107
    if (ev && ev.click) {
108
        return;
109
    }
110
    $(".collapse-textarea").on("click", function (e) {
111
        e.preventDefault();
112
        $(this).hide();
113
        var target = $(this).data("target");
114
        var syntax = $(this).data("syntax");
115
        $("#expand_" + target).show();
116
        if (syntax) {
117
            var editor = $("#pref_" + target).next(".CodeMirror")[0].CodeMirror;
118
            editor.toTextArea();
119
        }
120
        $("#pref_" + target).hide();
121
    });
122
}
123
124
// Add a handler for any consent delete links
125
function addConsentDeleteHandler() {
126
    // Don't duplicate click event handlers
127
    const ev = $._data($('.consentDelete'), 'events');
128
    if (ev && ev.click) {
129
        return;
130
    }
131
    $('.consentDelete').on('click', function (e) {
132
        e.preventDefault();
133
        const target = $(this).data('target');
134
        const proceed = confirm(__('Are you sure you want to delete this consent item?'));
135
        if (proceed) {
136
            $('#' + target).remove();
137
        }
138
    });
139
}
140
67
$( document ).ready( function () {
141
$( document ).ready( function () {
68
142
69
    $("table.preferences").dataTable($.extend(true, {}, dataTablesDefaults, {
143
    $("table.preferences").dataTable($.extend(true, {}, dataTablesDefaults, {
Lines 107-150 $( document ).ready( function () { Link Here
107
        return false;
181
        return false;
108
    });
182
    });
109
183
110
    $( ".expand-textarea" ).on("click", function(e){
184
    addExpandHandler();
111
        e.preventDefault();
112
        $(this).hide();
113
        var target = $(this).data("target");
114
        var syntax = $(this).data("syntax");
115
        $("#collapse_" + target ).show();
116
        if( syntax ){
117
            var editor = CodeMirror.fromTextArea( document.getElementById( "pref_" + target ), {
118
                lineNumbers: true,
119
                mode: syntax,
120
                lineWrapping: true,
121
                viewportMargin: Infinity,
122
                gutters: ["CodeMirror-lint-markers"],
123
                lint: true
124
            });
125
            editor.on("change", function(){
126
                mark_modified.call( $("#pref_" + target )[0]);
127
            });
128
            editor.on("blur", function(){
129
                editor.save();
130
            });
131
        } else {
132
            $("#pref_" + target ).show();
133
        }
134
    });
135
185
136
    $( ".collapse-textarea" ).on("click", function(e){
186
    addCollapseHandler();
137
        e.preventDefault();
138
        $(this).hide();
139
        var target = $(this).data("target");
140
        var syntax = $(this).data("syntax");
141
        $("#expand_" + target ).show();
142
        if( syntax ){
143
            var editor = $("#pref_" + target ).next(".CodeMirror")[0].CodeMirror;
144
            editor.toTextArea();
145
        }
146
        $("#pref_" + target ).hide();
147
    });
148
187
149
    $("h3").attr("class", "expanded").attr("title", __("Click to collapse this section"));
188
    $("h3").attr("class", "expanded").attr("title", __("Click to collapse this section"));
150
    var collapsible = $(".collapsed,.expanded");
189
    var collapsible = $(".collapsed,.expanded");
Lines 246-273 $( document ).ready( function () { Link Here
246
            }).appendTo("#prefModalForm");
285
            }).appendTo("#prefModalForm");
247
        });
286
        });
248
        $("#saveModalPrefs").data("target", this.id );
287
        $("#saveModalPrefs").data("target", this.id );
288
        $("#saveModalPrefs").data("type", "modalselect" );
249
        $("#prefModalLabel").text( pref_name );
289
        $("#prefModalLabel").text( pref_name );
250
        $("#prefModal").modal("show");
290
        $("#prefModal").modal("show");
251
    });
291
    });
252
292
253
    $(".modaljs").on("click", function () {
293
    // Initialise the content of our modal, using the function
254
        console.log('modaljs clicked');
294
    // specified in the data-initiator attribute
295
    $('.modaljs').on('click', function () {
296
        const init = $(this).data('initiator');
297
        if (init) {
298
            window[init](this);
299
            $("#prefModal").modal("show");
300
        }
255
    });
301
    });
256
302
257
    $("#saveModalPrefs").on("click", function(){
303
    // Initialise the content of our modal, if we are dealing
258
        var formfieldid = $("#" + $(this).data("target") );
304
    // with a modalselect modal
305
    function prepareModalSelect(formfieldid) {
259
        var prefs = [];
306
        var prefs = [];
260
        $("#prefModal input[type='checkbox']").each(function(){
307
        $("#prefModal input[type='checkbox']").each(function(){
261
            if( $(this).prop("checked") ){
308
            if( $(this).prop("checked") ){
262
                prefs.push( this.value );
309
                prefs.push( this.value );
263
            }
310
            }
264
        });
311
        });
312
        return prefs.join("|");
313
    }
314
315
    // Return a checkbox with an appropriate checked state
316
    function checkBox(id, className, state) {
317
        return state ?
318
            '<input id="' + id + '" type="checkbox" class="' + className + '" checked>' :
319
            '<input id="' + id + '" type="checkbox" class="' + className + '">';
320
    }
321
322
    // Create a consentJS item, correctly populated
323
    function createConsentJSItem(item, idx) {
324
        const id = 'ConsentJS_' + idx;
325
        const code = item.code && item.code.length > 0 ? atob(item.code) : '';
326
        const itemId = item.id && item.id.length > 0 ? item.id : '';
327
        return '<div id="' + id + '" class="consentJSItem" data-id="' + itemId + '"><div class="consentRow"><div class="consentItem"><label class="required" for="name_' + id + '">' + __('Name') + ':</label><input id="name_' + id + '" class="metaName" type="text" value="' + item.name + '"><span class="required">' + __('Required')  + '</span></div><div class="consentItem"><label class="required" for="description_' + id + '">' + __('Description') + ':</label><input id="description_' + id + '" class="metaDescription" type="text" value="' + item.description + '"><span class="required">' + __('Required') + '</span></div><div class="consentItem"><label for="opacConsent_' + id + '">' + __('Requires consent in OPAC') + ':</label>' + checkBox('opacConsent_' + id, 'opacConsent', item.opacConsent) + '</div><div class="consentItem"><label for="staffConsent_' + id + '">' + __('Requires consent in staff interface') + ':</label>' + checkBox('staffConsent_' + id, 'staffConsent', item.staffConsent) + '</div></div><div class="consentRow codeRow"><textarea style="display:none;" id="pref_' + id + '" class="preference preference-code codemirror" rows="10" cols="40">' + code + '</textarea><div><a id="expand_' + id + '" class="expand-textarea" data-target="' + id + '" data-syntax="javascript" href="#">' + __('Click to expand') + '</a><a id="collapse_' + id + '" class="collapse-textarea" data-target="' + id + '" data-syntax="javascript" href="#" style="display:none">' + __('Click to collapse') + '</a></div></div><a class="consentDelete" data-target="' + id + '" href="#">Delete</a></div>';
328
    }
329
330
    // Return the markup for all consentJS items concatenated
331
    function populateConsentMarkup(items) {
332
        return items.reduce(function (acc, current, idx) {
333
            return acc + createConsentJSItem(current, idx);
334
        }, '');
335
    }
336
337
    // Return the markup for a validation warning
338
    function populateValidationWarning() {
339
        return '<div id="consentJSWarning" class="error" style="display:none;">' + __('You must complete all fields') + '</div>';
340
    }
341
342
    // Return a new, empty consent item
343
    function emptyConsentItem() {
344
        return {
345
            name: '',
346
            description: '',
347
            code: '',
348
            consentInOpac: false,
349
            consentInStaff: false
350
        };
351
    }
352
353
    // Add the handler for a new empty consent item
354
    function addNewHandler() {
355
        $("#consentJSAddNew").on("click", function (e) {
356
            e.preventDefault();
357
            const currentLen = $('.consentJSItem').length;
358
            const newItem = emptyConsentItem();
359
            const markup = createConsentJSItem(newItem, currentLen);
360
            $('#prefModal .modal-body #consentJSItems').append($(markup));
361
            addExpandHandler();
362
            addCollapseHandler();
363
            addConsentDeleteHandler();
364
        });
365
    }
366
367
    // Populate the consentJS modal, we also initialise any
368
    // event handlers that are required. This function is added
369
    // to the window object so we can call it if we are passed it's name
370
    // as a data-initiator attribute
371
    // (e.g.)
372
    // const f = 'populateConsentJS';
373
    // window[f]();
374
    window.populateConsentJS = function(el) {
375
        let items = [];
376
        let decoded = '';
377
        if (el.value && el.value.length > 0) {
378
            try {
379
                decoded = atob(el.value);
380
            } catch (err) {
381
                throw (__(
382
                    'Unable to Base64 decode value stored in ConsentJS syspref: ' +
383
                    err.message
384
                ));
385
            }
386
            try {
387
                items = JSON.parse(decoded);
388
            } catch (err) {
389
                throw (__(
390
                    'Unable to JSON parse decoded value stored in ConsentJS syspref: ' +
391
                    err.message
392
                ));
393
            }
394
        }
395
        const markup = populateConsentMarkup(items);
396
        const validationWarning = populateValidationWarning();
397
        const pref_name = el.id.replace(/pref_/, '');
398
        $('#saveModalPrefs').data('target', el.id);
399
        $('#prefModalLabel').text( pref_name );
400
        $('#prefModal .modal-body').html($('<div id="consentJSItems">' + validationWarning + markup + '</div><div><a href="#" id="consentJSAddNew">' + __('Add new code') + '</a></div>'));
401
        addExpandHandler();
402
        addCollapseHandler();
403
        addNewHandler();
404
        addConsentDeleteHandler();
405
    }
406
407
    // Prepare the data in the UI for sending back as a syspref.
408
    // We validate that everything is what we expect. This function is added
409
    // to the window object so we can call it if we are passed it's name
410
    // as a data-initiator attribute
411
    // e.g.
412
    // const f = 'prepareConsentJS';
413
    // window[f]();
414
    window.prepareConsentJS = function () {
415
        const items = $('.consentJSItem');
416
        const invalid = [];
417
        const valid = [];
418
        items.each(function () {
419
            const id = $(this).data('id').length > 0 ?
420
                $(this).data('id') :
421
                '_' + Math.random().toString(36).substr(2, 9);
422
            const name = $(this).find('.metaName').val();
423
            const desc = $(this).find('.metaDescription').val();
424
            const opacConsent = $(this).find('.opacConsent').is(':checked')
425
            const staffConsent = $(this).find('.staffConsent').is(':checked');
426
            const code = $(this).find('.preference-code').val();
427
            // If the name, description and code are empty, then they've
428
            // added a new entry, but not filled it in, we can skip it
429
            if (name.length === 0 && desc.length === 0 && code.length === 0) {
430
                return;
431
            }
432
            // They've filled in at least some info
433
            if (
434
                (name.length === 0) ||
435
                (desc.length === 0) ||
436
                (code.length === 0)
437
            ) {
438
                invalid.push(this);
439
            } else {
440
                const obj = {
441
                    id: id,
442
                    name: name,
443
                    description: desc,
444
                    opacConsent: opacConsent,
445
                    staffConsent: staffConsent,
446
                    code: btoa(code)
447
                }
448
                valid.push(obj);
449
            }
450
        });
451
        // We failed validation
452
        if (invalid.length > 0) {
453
            $('#consentJSWarning').show();
454
            return false;
455
        }
456
        $('#consentJSWarning').hide();
457
        if (valid.length === 0) {
458
            return '';
459
        }
460
        const json = JSON.stringify(valid);
461
        const base64 = btoa(json);
462
        return base64;
463
    }
265
464
266
        formfieldid.val( prefs.join("|") )
465
    $("#saveModalPrefs").on("click", function(){
267
            .addClass("modified");
466
        var formfieldid = $("#" + $(this).data("target"));
268
        mark_modified.call( formfieldid );
467
        let finalString = "";
269
        KOHA.Preferences.Save( formfieldid.closest("form") );
468
        if ($(this).data("type") == "modalselect") {
270
        $("#prefModal").modal("hide");
469
            finalString = prepareModalSelect(formfieldid);
470
        } else {
471
            const processor = $(".modaljs").data("processor");
472
            finalString = window[processor]();
473
        }
474
        // A processor can return false if any of the submitted
475
        // data has failed validation
476
        if (finalString !== false) {
477
            formfieldid.val(finalString).addClass("modified");
478
            mark_modified.call( formfieldid );
479
            KOHA.Preferences.Save( formfieldid.closest("form") );
480
            $("#prefModal").modal("hide");
481
        }
271
    });
482
    });
272
483
273
    $("#prefModal").on("hide.bs.modal", function(){
484
    $("#prefModal").on("hide.bs.modal", function(){
274
- 

Return to bug 27378