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

Return to bug 27378