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

Return to bug 27378