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 4423-4428 div .suggestion_note { Link Here
4423
    }
4423
    }
4424
}
4424
}
4425
4425
4426
#consentJSItems {
4427
    #consentJSWarning {
4428
        margin: 20px 0;
4429
        text-align: center;
4430
        font-weight: 800;
4431
        font-size: 120%;
4432
    }
4433
    .consentJSItem {
4434
        margin-bottom: 30px;
4435
        padding: 10px;
4436
        border: 1px solid #ccc;
4437
    }
4438
    .consentRow {
4439
        display: flex;
4440
        margin-bottom: 20px;
4441
        .consentItem {
4442
            margin-right: 40px;
4443
        }
4444
        .consentItem:last-child {
4445
            margin-right: 0;
4446
        }
4447
    }
4448
    .codeRow {
4449
        display: block;
4450
    }
4451
    .consentDelete {
4452
        display: block;
4453
        text-align: right;
4454
    }
4455
}
4456
4426
@media (min-width: 200px) {
4457
@media (min-width: 200px) {
4427
4458
4428
}
4459
}
(-)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 256-283 $( document ).ready( function () { Link Here
256
            }).appendTo("#prefModalForm");
295
            }).appendTo("#prefModalForm");
257
        });
296
        });
258
        $("#saveModalPrefs").data("target", this.id );
297
        $("#saveModalPrefs").data("target", this.id );
298
        $("#saveModalPrefs").data("type", "modalselect" );
259
        $("#prefModalLabel").text( pref_name );
299
        $("#prefModalLabel").text( pref_name );
260
        $("#prefModal").modal("show");
300
        $("#prefModal").modal("show");
261
    });
301
    });
262
302
263
    $(".modaljs").on("click", function () {
303
    // Initialise the content of our modal, using the function
264
        console.log('modaljs clicked');
304
    // specified in the data-initiator attribute
305
    $('.modaljs').on('click', function () {
306
        const init = $(this).data('initiator');
307
        if (init) {
308
            window[init](this);
309
            $("#prefModal").modal("show");
310
        }
265
    });
311
    });
266
312
267
    $("#saveModalPrefs").on("click", function(){
313
    // Initialise the content of our modal, if we are dealing
268
        var formfieldid = $("#" + $(this).data("target") );
314
    // with a modalselect modal
315
    function prepareModalSelect(formfieldid) {
269
        var prefs = [];
316
        var prefs = [];
270
        $("#prefModal input[type='checkbox']").each(function(){
317
        $("#prefModal input[type='checkbox']").each(function(){
271
            if( $(this).prop("checked") ){
318
            if( $(this).prop("checked") ){
272
                prefs.push( this.value );
319
                prefs.push( this.value );
273
            }
320
            }
274
        });
321
        });
322
        return prefs.join("|");
323
    }
324
325
    // Return a checkbox with an appropriate checked state
326
    function checkBox(id, className, state) {
327
        return state ?
328
            '<input id="' + id + '" type="checkbox" class="' + className + '" checked>' :
329
            '<input id="' + id + '" type="checkbox" class="' + className + '">';
330
    }
331
332
    // Create a consentJS item, correctly populated
333
    function createConsentJSItem(item, idx) {
334
        const id = 'ConsentJS_' + idx;
335
        const code = item.code && item.code.length > 0 ? atob(item.code) : '';
336
        const itemId = item.id && item.id.length > 0 ? item.id : '';
337
        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>';
338
    }
339
340
    // Return the markup for all consentJS items concatenated
341
    function populateConsentMarkup(items) {
342
        return items.reduce(function (acc, current, idx) {
343
            return acc + createConsentJSItem(current, idx);
344
        }, '');
345
    }
346
347
    // Return the markup for a validation warning
348
    function populateValidationWarning() {
349
        return '<div id="consentJSWarning" class="error" style="display:none;">' + __('You must complete all fields') + '</div>';
350
    }
351
352
    // Return a new, empty consent item
353
    function emptyConsentItem() {
354
        return {
355
            name: '',
356
            description: '',
357
            code: '',
358
            consentInOpac: false,
359
            consentInStaff: false
360
        };
361
    }
362
363
    // Add the handler for a new empty consent item
364
    function addNewHandler() {
365
        $("#consentJSAddNew").on("click", function (e) {
366
            e.preventDefault();
367
            const currentLen = $('.consentJSItem').length;
368
            const newItem = emptyConsentItem();
369
            const markup = createConsentJSItem(newItem, currentLen);
370
            $('#prefModal .modal-body #consentJSItems').append($(markup));
371
            addExpandHandler();
372
            addCollapseHandler();
373
            addConsentDeleteHandler();
374
        });
375
    }
376
377
    // Populate the consentJS modal, we also initialise any
378
    // event handlers that are required. This function is added
379
    // to the window object so we can call it if we are passed it's name
380
    // as a data-initiator attribute
381
    // (e.g.)
382
    // const f = 'populateConsentJS';
383
    // window[f]();
384
    window.populateConsentJS = function(el) {
385
        let items = [];
386
        let decoded = '';
387
        if (el.value && el.value.length > 0) {
388
            try {
389
                decoded = atob(el.value);
390
            } catch (err) {
391
                throw (__(
392
                    'Unable to Base64 decode value stored in ConsentJS syspref: ' +
393
                    err.message
394
                ));
395
            }
396
            try {
397
                items = JSON.parse(decoded);
398
            } catch (err) {
399
                throw (__(
400
                    'Unable to JSON parse decoded value stored in ConsentJS syspref: ' +
401
                    err.message
402
                ));
403
            }
404
        }
405
        const markup = populateConsentMarkup(items);
406
        const validationWarning = populateValidationWarning();
407
        const pref_name = el.id.replace(/pref_/, '');
408
        $('#saveModalPrefs').data('target', el.id);
409
        $('#prefModalLabel').text( pref_name );
410
        $('#prefModal .modal-body').html($('<div id="consentJSItems">' + validationWarning + markup + '</div><div><a href="#" id="consentJSAddNew">' + __('Add new code') + '</a></div>'));
411
        addExpandHandler();
412
        addCollapseHandler();
413
        addNewHandler();
414
        addConsentDeleteHandler();
415
    }
416
417
    // Prepare the data in the UI for sending back as a syspref.
418
    // We validate that everything is what we expect. This function is added
419
    // to the window object so we can call it if we are passed it's name
420
    // as a data-initiator attribute
421
    // e.g.
422
    // const f = 'prepareConsentJS';
423
    // window[f]();
424
    window.prepareConsentJS = function () {
425
        const items = $('.consentJSItem');
426
        const invalid = [];
427
        const valid = [];
428
        items.each(function () {
429
            const id = $(this).data('id').length > 0 ?
430
                $(this).data('id') :
431
                '_' + Math.random().toString(36).substr(2, 9);
432
            const name = $(this).find('.metaName').val();
433
            const desc = $(this).find('.metaDescription').val();
434
            const opacConsent = $(this).find('.opacConsent').is(':checked')
435
            const staffConsent = $(this).find('.staffConsent').is(':checked');
436
            const code = $(this).find('.preference-code').val();
437
            // If the name, description and code are empty, then they've
438
            // added a new entry, but not filled it in, we can skip it
439
            if (name.length === 0 && desc.length === 0 && code.length === 0) {
440
                return;
441
            }
442
            // They've filled in at least some info
443
            if (
444
                (name.length === 0) ||
445
                (desc.length === 0) ||
446
                (code.length === 0)
447
            ) {
448
                invalid.push(this);
449
            } else {
450
                const obj = {
451
                    id: id,
452
                    name: name,
453
                    description: desc,
454
                    opacConsent: opacConsent,
455
                    staffConsent: staffConsent,
456
                    code: btoa(code)
457
                }
458
                valid.push(obj);
459
            }
460
        });
461
        // We failed validation
462
        if (invalid.length > 0) {
463
            $('#consentJSWarning').show();
464
            return false;
465
        }
466
        $('#consentJSWarning').hide();
467
        if (valid.length === 0) {
468
            return '';
469
        }
470
        const json = JSON.stringify(valid);
471
        const base64 = btoa(json);
472
        return base64;
473
    }
275
474
276
        formfieldid.val( prefs.join("|") )
475
    $("#saveModalPrefs").on("click", function(){
277
            .addClass("modified");
476
        var formfieldid = $("#" + $(this).data("target"));
278
        mark_modified.call( formfieldid );
477
        let finalString = "";
279
        KOHA.Preferences.Save( formfieldid.closest("form") );
478
        if ($(this).data("type") == "modalselect") {
280
        $("#prefModal").modal("hide");
479
            finalString = prepareModalSelect(formfieldid);
480
        } else {
481
            const processor = $(".modaljs").data("processor");
482
            finalString = window[processor]();
483
        }
484
        // A processor can return false if any of the submitted
485
        // data has failed validation
486
        if (finalString !== false) {
487
            formfieldid.val(finalString).addClass("modified");
488
            mark_modified.call( formfieldid );
489
            KOHA.Preferences.Save( formfieldid.closest("form") );
490
            $("#prefModal").modal("hide");
491
        }
281
    });
492
    });
282
493
283
    $("#prefModal").on("hide.bs.modal", function(){
494
    $("#prefModal").on("hide.bs.modal", function(){
284
- 

Return to bug 27378