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

(-)a/koha-tmpl/intranet-tmpl/prog/js/datatables.js (-355 / +383 lines)
Lines 504-509 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
504
    console.log(message);
504
    console.log(message);
505
};
505
};
506
506
507
function _dt_default_ajax (params){
508
    let default_filters = params.default_filters;
509
    let options = params.options;
510
511
    if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains';
512
    options.criteria = options.criteria.toLowerCase();
513
514
    return {
515
        'type': 'GET',
516
        'cache': true,
517
        'dataSrc': 'data',
518
        'beforeSend': function(xhr, settings) {
519
            this._xhr = xhr;
520
            if(options.embed) {
521
                xhr.setRequestHeader('x-koha-embed', Array.isArray(options.embed)?options.embed.join(','):options.embed);
522
            }
523
        },
524
        'dataFilter': function(data, type) {
525
            var json = {data: JSON.parse(data)};
526
            if (total = this._xhr.getResponseHeader('x-total-count')) {
527
                json.recordsTotal = total;
528
                json.recordsFiltered = total;
529
            }
530
            if (total = this._xhr.getResponseHeader('x-base-total-count')) {
531
                json.recordsTotal = total;
532
            }
533
            if (draw = this._xhr.getResponseHeader('x-koha-request-id')) {
534
                json.draw = draw;
535
            }
536
537
            return JSON.stringify(json);
538
        },
539
        'data': function( data, settings ) {
540
            var length = data.length;
541
            var start  = data.start;
542
543
            var dataSet = {
544
                _page: Math.floor(start/length) + 1,
545
                _per_page: length
546
            };
547
548
            function build_query(col, value){
549
550
                var parts = [];
551
                var attributes = col.data.split(':');
552
                for (var i=0;i<attributes.length;i++){
553
                    var part = {};
554
                    var attr = attributes[i];
555
                    let criteria = options.criteria;
556
                    if ( value.match(/^\^(.*)\$$/) ) {
557
                        value = value.replace(/^\^/, '').replace(/\$$/, '');
558
                        criteria = "exact";
559
                    } else {
560
                       // escape SQL LIKE special characters % and _
561
                       value = value.replace(/(\%|\\)/g, "\\$1");
562
                    }
563
                    part[!attr.includes('.')?'me.'+attr:attr] = criteria === 'exact'
564
                        ? value
565
                        : {like: (['contains', 'ends_with'].indexOf(criteria) !== -1?'%':'') + value + (['contains', 'starts_with'].indexOf(criteria) !== -1?'%':'')};
566
                    parts.push(part);
567
                }
568
                return parts;
569
            }
570
571
            var filter = data.search.value;
572
            // Build query for each column filter
573
            var and_query_parameters = settings.aoColumns
574
            .filter(function(col) {
575
                return col.bSearchable && typeof col.data == 'string' && data.columns[col.idx].search.value != ''
576
            })
577
            .map(function(col) {
578
                var value = data.columns[col.idx].search.value;
579
                return build_query(col, value)
580
            })
581
            .map(function r(e){
582
                return ($.isArray(e) ? $.map(e, r) : e);
583
            });
584
585
            // Build query for the global search filter
586
            var or_query_parameters = settings.aoColumns
587
            .filter(function(col) {
588
                return col.bSearchable && filter != ''
589
            })
590
            .map(function(col) {
591
                var value = filter;
592
                return build_query(col, value)
593
            })
594
            .map(function r(e){
595
                return ($.isArray(e) ? $.map(e, r) : e);
596
            });
597
598
            if ( default_filters ) {
599
                let additional_filters = {};
600
                for ( f in default_filters ) {
601
                    let k; let v;
602
                    if ( typeof(default_filters[f]) === 'function' ) {
603
                        let val = default_filters[f]();
604
                        if ( val != undefined && val != "" ) {
605
                            k = f; v = val;
606
                        }
607
                    } else {
608
                        k = f; v = default_filters[f];
609
                    }
610
611
                    // Pass to -or if you want a separate OR clause
612
                    // It's not the usual DBIC notation!
613
                    if ( f == '-or' ) {
614
                        if (v) or_query_parameters.push(v)
615
                    } else if ( f == '-and' ) {
616
                        if (v) and_query_parameters.push(v)
617
                    } else if ( v ) {
618
                        additional_filters[k] = v;
619
                    }
620
                }
621
                if ( Object.keys(additional_filters).length ) {
622
                    and_query_parameters.push(additional_filters);
623
                }
624
            }
625
            query_parameters = and_query_parameters;
626
            if ( or_query_parameters.length) {
627
                query_parameters.push(or_query_parameters);
628
            }
629
630
            if(query_parameters.length) {
631
                query_parameters = JSON.stringify(query_parameters.length === 1?query_parameters[0]:{"-and": query_parameters});
632
                dataSet.q = query_parameters;
633
                delete options.query_parameters;
634
            } else {
635
                delete options.query_parameters;
636
            }
637
638
            dataSet._match = options.criteria;
639
640
            if ( data["draw"] !== undefined ) {
641
                settings.ajax.headers = { 'x-koha-request-id': data.draw }
642
            }
643
644
            if(options.columns) {
645
                var order = data.order;
646
                var orderArray = new Array();
647
                order.forEach(function (e,i) {
648
                    var order_col      = e.column;
649
                    var order_by       = options.columns[order_col].data;
650
                    order_by           = order_by.split(':');
651
                    var order_dir      = e.dir == 'asc' ? '+' : '-';
652
                    Array.prototype.push.apply(orderArray,order_by.map(x => order_dir + (!x.includes('.')?'me.'+x:x)));
653
                });
654
                dataSet._order_by = orderArray.filter((v, i, a) => a.indexOf(v) === i).join(',');
655
            }
656
657
            return dataSet;
658
        }
659
    }
660
}
661
662
function _dt_buttons(params){
663
    let included_ids = params.included_ids || [];
664
    let settings = params.settings || {};
665
    let table_settings = params.table_settings || {};
666
667
    var exportColumns = ":visible:not(.noExport)";
668
    if( settings.hasOwnProperty("exportColumns") ){
669
        // A custom buttons configuration has been passed from the page
670
        exportColumns = settings["exportColumns"];
671
    }
672
673
    var export_format = {
674
        body: function ( data, row, column, node ) {
675
            var newnode = $(node);
676
677
            if ( newnode.find(".noExport").length > 0 ) {
678
                newnode = newnode.clone();
679
                newnode.find(".noExport").remove();
680
            }
681
682
            return newnode.text().replace( /\n/g, ' ' ).trim();
683
        }
684
    }
685
686
    var export_buttons = [
687
        {
688
            extend: 'excelHtml5',
689
            text: __("Excel"),
690
            exportOptions: {
691
                columns: exportColumns,
692
                format:  export_format
693
            },
694
        },
695
        {
696
            extend: 'csvHtml5',
697
            text: __("CSV"),
698
            exportOptions: {
699
                columns: exportColumns,
700
                format:  export_format
701
            },
702
        },
703
        {
704
            extend: 'copyHtml5',
705
            text: __("Copy"),
706
            exportOptions: {
707
                columns: exportColumns,
708
                format:  export_format
709
            },
710
        },
711
        {
712
            extend: 'print',
713
            text: __("Print"),
714
            exportOptions: {
715
                columns: exportColumns,
716
                format:  export_format
717
            },
718
        }
719
    ];
720
721
    let buttons = [];
722
    buttons.push(
723
        {
724
            fade: 100,
725
            className: "dt_button_clear_filter",
726
            titleAttr: __("Clear filter"),
727
            enabled: false,
728
            text: '<i class="fa fa-lg fa-remove"></i> <span class="dt-button-text">' + __("Clear filter") + '</span>',
729
            action: function ( e, dt, node, config ) {
730
                dt.search( "" ).draw("page");
731
                node.addClass("disabled");
732
            }
733
        }
734
    );
735
736
    if( included_ids.length > 0 ){
737
        buttons.push(
738
            {
739
                extend: 'colvis',
740
                fade: 100,
741
                columns: included_ids,
742
                className: "columns_controls",
743
                titleAttr: __("Columns settings"),
744
                text: '<i class="fa fa-lg fa-gear"></i> <span class="dt-button-text">' + __("Columns") + '</span>',
745
                exportOptions: {
746
                    columns: exportColumns
747
                }
748
            }
749
        );
750
    }
751
752
    buttons.push(
753
        {
754
            extend: 'collection',
755
            autoClose: true,
756
            fade: 100,
757
            className: "export_controls",
758
            titleAttr: __("Export or print"),
759
            text: '<i class="fa fa-lg fa-download"></i> <span class="dt-button-text">' + __("Export") + '</span>',
760
            buttons: export_buttons
761
        }
762
    );
763
764
    if ( table_settings && CAN_user_parameters_manage_column_config ) {
765
        buttons.push(
766
            {
767
                className: "dt_button_configure_table",
768
                fade: 100,
769
                titleAttr: __("Configure table"),
770
                text: '<i class="fa fa-lg fa-wrench"></i> <span class="dt-button-text">' + __("Configure") + '</span>',
771
                action: function() {
772
                    window.location = '/cgi-bin/koha/admin/columns_settings.pl?module=' + table_settings['module'] + '&page=' + table_settings['page'] + '&table=' + table_settings['table'];
773
                },
774
            }
775
        );
776
    }
777
778
    return buttons;
779
}
780
781
function _dt_visibility(table_settings, settings){
782
    var counter = 0;
783
    let hidden_ids = [];
784
    let included_ids = [];
785
    if ( table_settings ) {
786
        var columns_settings = table_settings['columns'];
787
        $(columns_settings).each( function() {
788
            var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', this ).index( 'th' );
789
            var used_id = settings.bKohaColumnsUseNames ? named_id : counter;
790
            if ( used_id == -1 ) return;
791
792
            if ( this['is_hidden'] == "1" ) {
793
                hidden_ids.push( used_id );
794
            }
795
            if ( this['cannot_be_toggled'] == "0" ) {
796
                included_ids.push( used_id );
797
            }
798
            counter++;
799
        });
800
    }
801
    return [hidden_ids, included_ids];
802
}
803
804
function _dt_on_visibility(add_filters, table_node, table_dt){
805
    if ( add_filters ) {
806
        let visible_columns = table_dt.columns().visible();
807
        $(table_node).find('thead tr:eq(1) th').each( function (i) {
808
            let th_id = $(this).data('th-id');
809
            if ( visible_columns[th_id] == false ) {
810
                $(this).hide();
811
            } else {
812
                $(this).show();
813
            }
814
        });
815
    }
816
817
    if( typeof columnsInit == 'function' ){
818
        // This function can be created separately and used to trigger
819
        // an event after the DataTable has loaded AND column visibility
820
        // has been updated according to the table's configuration
821
        columnsInit();
822
    }
823
}
824
825
function _dt_add_filters(table_node, table_dt) {
826
    $(table_node).find('thead tr').clone().appendTo( $(table_node).find('thead') );
827
828
    $(table_node).find('thead tr:eq(1) th').each( function (i) {
829
        var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable;
830
        $(this).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc");
831
        $(this).data('th-id', i);
832
        if ( is_searchable ) {
833
            let input_type = 'input';
834
            if ( $(this).data('filter') ) {
835
                input_type = 'select'
836
                let filter_type = $(this).data('filter');
837
                var existing_search = table_dt.column(i).search();
838
                let select = $('<select><option value=""></option></select');
839
840
                // FIXME eval here is bad and dangerous, how do we workaround that?
841
                $(eval(filter_type)).each(function(){
842
                    let o = $('<option value="%s">%s</option>'.format(this._id, this._str));
843
                    if ( existing_search === this._id ) {
844
                        o.prop("selected", "selected");
845
                    }
846
                    o.appendTo(select);
847
                });
848
                $(this).html( select );
849
            } else {
850
                var title = $(this).text();
851
                var existing_search = table_dt.column(i).search();
852
                if ( existing_search ) {
853
                    $(this).html( '<input type="text" value="%s" style="width: 100%" />'.format(existing_search) );
854
                } else {
855
                    var search_title = _("%s search").format(title);
856
                    $(this).html( '<input type="text" placeholder="%s" style="width: 100%" />'.format(search_title) );
857
                }
858
            }
859
860
            $( input_type, this ).on( 'keyup change', function () {
861
                if ( table_dt.column(i).search() !== this.value ) {
862
                    if ( input_type == "input" ) {
863
                        table_dt
864
                            .column(i)
865
                            .search( this.value )
866
                            .draw();
867
                    } else {
868
                        table_dt
869
                            .column(i)
870
                            .search( this.value.length ? '^'+this.value+'$' : '', true, false )
871
                            .draw();
872
                    }
873
                }
874
            } );
875
        } else {
876
            $(this).html('');
877
        }
878
    } );
879
}
880
881
507
(function($) {
882
(function($) {
508
883
509
    /**
884
    /**
Lines 522-530 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
522
        var settings = null;
897
        var settings = null;
523
898
524
        if(options) {
899
        if(options) {
525
            if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains';
526
            options.criteria = options.criteria.toLowerCase();
527
528
            // Don't redefine the default initComplete
900
            // Don't redefine the default initComplete
529
            if ( options.initComplete ) {
901
            if ( options.initComplete ) {
530
                let our_initComplete = options.initComplete;
902
                let our_initComplete = options.initComplete;
Lines 544-829 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
544
                        'language': {
916
                        'language': {
545
                            'emptyTable': (options.emptyTable) ? options.emptyTable : __("No data available in table")
917
                            'emptyTable': (options.emptyTable) ? options.emptyTable : __("No data available in table")
546
                        },
918
                        },
547
                        'ajax': {
919
                        'ajax': _dt_default_ajax({default_filters, options}),
548
                            'type': 'GET',
549
                            'cache': true,
550
                            'dataSrc': 'data',
551
                            'beforeSend': function(xhr, settings) {
552
                                this._xhr = xhr;
553
                                if(options.embed) {
554
                                    xhr.setRequestHeader('x-koha-embed', Array.isArray(options.embed)?options.embed.join(','):options.embed);
555
                                }
556
                            },
557
                            'dataFilter': function(data, type) {
558
                                var json = {data: JSON.parse(data)};
559
                                if (total = this._xhr.getResponseHeader('x-total-count')) {
560
                                    json.recordsTotal = total;
561
                                    json.recordsFiltered = total;
562
                                }
563
                                if (total = this._xhr.getResponseHeader('x-base-total-count')) {
564
                                    json.recordsTotal = total;
565
                                }
566
                                if (draw = this._xhr.getResponseHeader('x-koha-request-id')) {
567
                                    json.draw = draw;
568
                                }
569
570
                                return JSON.stringify(json);
571
                            },
572
                            'data': function( data, settings ) {
573
                                var length = data.length;
574
                                var start  = data.start;
575
576
                                var dataSet = {
577
                                    _page: Math.floor(start/length) + 1,
578
                                    _per_page: length
579
                                };
580
581
                                function build_query(col, value){
582
583
                                    var parts = [];
584
                                    var attributes = col.data.split(':');
585
                                    for (var i=0;i<attributes.length;i++){
586
                                        var part = {};
587
                                        var attr = attributes[i];
588
                                        let criteria = options.criteria;
589
                                        if ( value.match(/^\^(.*)\$$/) ) {
590
                                            value = value.replace(/^\^/, '').replace(/\$$/, '');
591
                                            criteria = "exact";
592
                                        } else {
593
                                           // escape SQL LIKE special characters % and _
594
                                           value = value.replace(/(\%|\\)/g, "\\$1");
595
                                        }
596
                                        part[!attr.includes('.')?'me.'+attr:attr] = criteria === 'exact'
597
                                            ? value
598
                                            : {like: (['contains', 'ends_with'].indexOf(criteria) !== -1?'%':'') + value + (['contains', 'starts_with'].indexOf(criteria) !== -1?'%':'')};
599
                                        parts.push(part);
600
                                    }
601
                                    return parts;
602
                                }
603
604
                                var filter = data.search.value;
605
                                // Build query for each column filter
606
                                var and_query_parameters = settings.aoColumns
607
                                .filter(function(col) {
608
                                    return col.bSearchable && typeof col.data == 'string' && data.columns[col.idx].search.value != ''
609
                                })
610
                                .map(function(col) {
611
                                    var value = data.columns[col.idx].search.value;
612
                                    return build_query(col, value)
613
                                })
614
                                .map(function r(e){
615
                                    return ($.isArray(e) ? $.map(e, r) : e);
616
                                });
617
618
                                // Build query for the global search filter
619
                                var or_query_parameters = settings.aoColumns
620
                                .filter(function(col) {
621
                                    return col.bSearchable && filter != ''
622
                                })
623
                                .map(function(col) {
624
                                    var value = filter;
625
                                    return build_query(col, value)
626
                                })
627
                                .map(function r(e){
628
                                    return ($.isArray(e) ? $.map(e, r) : e);
629
                                });
630
631
                                if ( default_filters ) {
632
                                    let additional_filters = {};
633
                                    for ( f in default_filters ) {
634
                                        let k; let v;
635
                                        if ( typeof(default_filters[f]) === 'function' ) {
636
                                            let val = default_filters[f]();
637
                                            if ( val != undefined && val != "" ) {
638
                                                k = f; v = val;
639
                                            }
640
                                        } else {
641
                                            k = f; v = default_filters[f];
642
                                        }
643
644
                                        // Pass to -or if you want a separate OR clause
645
                                        // It's not the usual DBIC notation!
646
                                        if ( f == '-or' ) {
647
                                            if (v) or_query_parameters.push(v)
648
                                        } else if ( f == '-and' ) {
649
                                            if (v) and_query_parameters.push(v)
650
                                        } else if ( v ) {
651
                                            additional_filters[k] = v;
652
                                        }
653
                                    }
654
                                    if ( Object.keys(additional_filters).length ) {
655
                                        and_query_parameters.push(additional_filters);
656
                                    }
657
                                }
658
                                query_parameters = and_query_parameters;
659
                                if ( or_query_parameters.length) {
660
                                    query_parameters.push(or_query_parameters);
661
                                }
662
663
                                if(query_parameters.length) {
664
                                    query_parameters = JSON.stringify(query_parameters.length === 1?query_parameters[0]:{"-and": query_parameters});
665
                                    dataSet.q = query_parameters;
666
                                    delete options.query_parameters;
667
                                } else {
668
                                    delete options.query_parameters;
669
                                }
670
671
                                dataSet._match = options.criteria;
672
673
                                if ( data["draw"] !== undefined ) {
674
                                    settings.ajax.headers = { 'x-koha-request-id': data.draw }
675
                                }
676
677
                                if(options.columns) {
678
                                    var order = data.order;
679
                                    var orderArray = new Array();
680
                                    order.forEach(function (e,i) {
681
                                        var order_col      = e.column;
682
                                        var order_by       = options.columns[order_col].data;
683
                                        order_by           = order_by.split(':');
684
                                        var order_dir      = e.dir == 'asc' ? '+' : '-';
685
                                        Array.prototype.push.apply(orderArray,order_by.map(x => order_dir + (!x.includes('.')?'me.'+x:x)));
686
                                    });
687
                                    dataSet._order_by = orderArray.filter((v, i, a) => a.indexOf(v) === i).join(',');
688
                                }
689
690
                                return dataSet;
691
                            }
692
                        }
693
                    }, options);
920
                    }, options);
694
        }
921
        }
695
922
696
        var counter = 0;
923
        let hidden_ids, included_ids;
697
        var hidden_ids = [];
924
        [hidden_ids, included_ids] = _dt_visibility(table_settings, settings)
698
        var included_ids = [];
699
700
701
        if ( table_settings ) {
702
            var columns_settings = table_settings['columns'];
703
            $(columns_settings).each( function() {
704
                var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', this ).index( 'th' );
705
                var used_id = settings.bKohaColumnsUseNames ? named_id : counter;
706
                if ( used_id == -1 ) return;
707
708
                if ( this['is_hidden'] == "1" ) {
709
                    hidden_ids.push( used_id );
710
                }
711
                if ( this['cannot_be_toggled'] == "0" ) {
712
                    included_ids.push( used_id );
713
                }
714
                counter++;
715
            });
716
        }
717
718
        var exportColumns = ":visible:not(.noExport)";
719
        if( settings.hasOwnProperty("exportColumns") ){
720
            // A custom buttons configuration has been passed from the page
721
            exportColumns = settings["exportColumns"];
722
        }
723
724
        var export_format = {
725
            body: function ( data, row, column, node ) {
726
                var newnode = $(node);
727
728
                if ( newnode.find(".noExport").length > 0 ) {
729
                    newnode = newnode.clone();
730
                    newnode.find(".noExport").remove();
731
                }
732
733
                return newnode.text().replace( /\n/g, ' ' ).trim();
734
            }
735
        }
736
737
        var export_buttons = [
738
            {
739
                extend: 'excelHtml5',
740
                text: __("Excel"),
741
                exportOptions: {
742
                    columns: exportColumns,
743
                    format:  export_format
744
                },
745
            },
746
            {
747
                extend: 'csvHtml5',
748
                text: __("CSV"),
749
                exportOptions: {
750
                    columns: exportColumns,
751
                    format:  export_format
752
                },
753
            },
754
            {
755
                extend: 'copyHtml5',
756
                text: __("Copy"),
757
                exportOptions: {
758
                    columns: exportColumns,
759
                    format:  export_format
760
                },
761
            },
762
            {
763
                extend: 'print',
764
                text: __("Print"),
765
                exportOptions: {
766
                    columns: exportColumns,
767
                    format:  export_format
768
                },
769
            }
770
        ];
771
772
        settings[ "buttons" ] = [
773
            {
774
                fade: 100,
775
                className: "dt_button_clear_filter",
776
                titleAttr: __("Clear filter"),
777
                enabled: false,
778
                text: '<i class="fa fa-lg fa-remove"></i> <span class="dt-button-text">' + __("Clear filter") + '</span>',
779
                action: function ( e, dt, node, config ) {
780
                    dt.search( "" ).draw("page");
781
                    node.addClass("disabled");
782
                }
783
            }
784
        ];
785
786
        if( included_ids.length > 0 ){
787
            settings[ "buttons" ].push(
788
                {
789
                    extend: 'colvis',
790
                    fade: 100,
791
                    columns: included_ids,
792
                    className: "columns_controls",
793
                    titleAttr: __("Columns settings"),
794
                    text: '<i class="fa fa-lg fa-gear"></i> <span class="dt-button-text">' + __("Columns") + '</span>',
795
                    exportOptions: {
796
                        columns: exportColumns
797
                    }
798
                }
799
            );
800
        }
801
802
        settings[ "buttons" ].push(
803
            {
804
                extend: 'collection',
805
                autoClose: true,
806
                fade: 100,
807
                className: "export_controls",
808
                titleAttr: __("Export or print"),
809
                text: '<i class="fa fa-lg fa-download"></i> <span class="dt-button-text">' + __("Export") + '</span>',
810
                buttons: export_buttons
811
            }
812
        );
813
925
814
        if ( table_settings && CAN_user_parameters_manage_column_config ) {
926
        settings["buttons"] = _dt_buttons({included_ids, settings, table_settings});
815
            settings[ "buttons" ].push(
816
                {
817
                    className: "dt_button_configure_table",
818
                    fade: 100,
819
                    titleAttr: __("Configure table"),
820
                    text: '<i class="fa fa-lg fa-wrench"></i> <span class="dt-button-text">' + __("Configure") + '</span>',
821
                    action: function() {
822
                        window.location = '/cgi-bin/koha/admin/columns_settings.pl?module=' + table_settings['module'] + '&page=' + table_settings['page'] + '&table=' + table_settings['table'];
823
                    },
824
                }
825
            );
826
        }
827
927
828
        $(".dt_button_clear_filter, .columns_controls, .export_controls, .dt_button_configure_table").tooltip();
928
        $(".dt_button_clear_filter, .columns_controls, .export_controls, .dt_button_configure_table").tooltip();
829
929
Lines 842-926 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
842
942
843
        var table = $(this).dataTable(settings);
943
        var table = $(this).dataTable(settings);
844
944
845
945
        var table_dt = table.DataTable();
846
        if ( add_filters ) {
946
        if ( add_filters ) {
847
            var table_dt = table.DataTable();
947
            _dt_add_filters(this, table_dt);
848
849
            $(this).find('thead tr').clone().appendTo( $(this).find('thead') );
850
851
            $(this).find('thead tr:eq(1) th').each( function (i) {
852
                var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable;
853
                $(this).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc");
854
                $(this).data('th-id', i);
855
                if ( is_searchable ) {
856
                    let input_type = 'input';
857
                    if ( $(this).data('filter') ) {
858
                        input_type = 'select'
859
                        let filter_type = $(this).data('filter');
860
                        var existing_search = table_dt.column(i).search();
861
                        let select = $('<select><option value=""></option></select');
862
863
                        // FIXME eval here is bad and dangerous, how do we workaround that?
864
                        $(eval(filter_type)).each(function(){
865
                            let o = $('<option value="%s">%s</option>'.format(this._id, this._str));
866
                            if ( existing_search === this._id ) {
867
                                o.prop("selected", "selected");
868
                            }
869
                            o.appendTo(select);
870
                        });
871
                        $(this).html( select );
872
                    } else {
873
                        var title = $(this).text();
874
                        var existing_search = table_dt.column(i).search();
875
                        if ( existing_search ) {
876
                            $(this).html( '<input type="text" value="%s" style="width: 100%" />'.format(existing_search) );
877
                        } else {
878
                            var search_title = _("%s search").format(title);
879
                            $(this).html( '<input type="text" placeholder="%s" style="width: 100%" />'.format(search_title) );
880
                        }
881
                    }
882
883
                    $( input_type, this ).on( 'keyup change', function () {
884
                        if ( table_dt.column(i).search() !== this.value ) {
885
                            if ( input_type == "input" ) {
886
                                table_dt
887
                                    .column(i)
888
                                    .search( this.value )
889
                                    .draw();
890
                            } else {
891
                                table_dt
892
                                    .column(i)
893
                                    .search( this.value.length ? '^'+this.value+'$' : '', true, false )
894
                                    .draw();
895
                            }
896
                        }
897
                    } );
898
                } else {
899
                    $(this).html('');
900
                }
901
            } );
902
        }
948
        }
903
949
904
        table.DataTable().on("column-visibility.dt", function(){
950
        table.DataTable().on("column-visibility.dt", function(){_dt_on_visibility(add_filters, table, table_dt);})
905
            if ( add_filters ) {
951
            .columns( hidden_ids ).visible( false );
906
                let visible_columns = table_dt.columns().visible();
907
                $(table).find('thead tr:eq(1) th').each( function (i) {
908
                    let th_id = $(this).data('th-id');
909
                    if ( visible_columns[th_id] == false ) {
910
                        $(this).hide();
911
                    } else {
912
                        $(this).show();
913
                    }
914
                });
915
            }
916
917
            if( typeof columnsInit == 'function' ){
918
                // This function can be created separately and used to trigger
919
                // an event after the DataTable has loaded AND column visibility
920
                // has been updated according to the table's configuration
921
                columnsInit();
922
            }
923
        }).columns( hidden_ids ).visible( false );
924
952
925
        return table;
953
        return table;
926
    };
954
    };
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue (-35 / +164 lines)
Lines 25-31 Link Here
25
            />
25
            />
26
        </fieldset>
26
        </fieldset>
27
        <div v-if="agreement_count > 0" class="page-section">
27
        <div v-if="agreement_count > 0" class="page-section">
28
            <table :id="table_id"></table>
28
            <KohaTable
29
                ref="table"
30
                v-bind="tableOptions"
31
                @show="doShow"
32
                @edit="doEdit"
33
                @delete="doDelete"
34
            ></KohaTable>
29
        </div>
35
        </div>
30
        <div v-else-if="initialized" class="dialog message">
36
        <div v-else-if="initialized" class="dialog message">
31
            {{ $__("There are no agreements defined") }}
37
            {{ $__("There are no agreements defined") }}
Lines 36-45 Link Here
36
<script>
42
<script>
37
import flatPickr from "vue-flatpickr-component"
43
import flatPickr from "vue-flatpickr-component"
38
import Toolbar from "./AgreementsToolbar.vue"
44
import Toolbar from "./AgreementsToolbar.vue"
39
import { inject, createVNode, render } from "vue"
45
import { inject, createVNode, render, ref } from "vue"
40
import { APIClient } from "../../fetch/api-client.js"
46
import { APIClient } from "../../fetch/api-client.js"
41
import { storeToRefs } from "pinia"
47
import { storeToRefs } from "pinia"
42
import { useDataTable, build_url } from "../../composables/datatables"
48
import { build_url } from "../../composables/datatables"
49
import KohaTable from "../KohaTable.vue"
43
50
44
export default {
51
export default {
45
    setup() {
52
    setup() {
Lines 51-66 export default { Link Here
51
58
52
        const { setConfirmation, setMessage } = inject("mainStore")
59
        const { setConfirmation, setMessage } = inject("mainStore")
53
60
54
        const table_id = "agreement_list"
61
        const table = ref()
55
        useDataTable(table_id)
56
57
        return {
62
        return {
58
            vendors,
63
            vendors,
59
            get_lib_from_av,
64
            get_lib_from_av,
60
            map_av_dt_filter,
65
            map_av_dt_filter,
61
            table_id,
66
            table,
62
            setConfirmation,
67
            setConfirmation,
63
            setMessage,
68
            setMessage,
69
            escape_str,
70
            agreement_table_settings,
64
        }
71
        }
65
    },
72
    },
66
    data: function () {
73
    data: function () {
Lines 75-80 export default { Link Here
75
            },
82
            },
76
            before_route_entered: false,
83
            before_route_entered: false,
77
            building_table: false,
84
            building_table: false,
85
            tableOptions: {
86
                columns: this.getTableColumns(),
87
                url: () => this.table_url(),
88
                table_settings: this.agreement_table_settings,
89
                add_filters: true,
90
                actions: {
91
                    0: ["show"],
92
                    "-1": ["edit", "delete"],
93
                },
94
            },
78
        }
95
        }
79
    },
96
    },
80
    beforeRouteEnter(to, from, next) {
97
    beforeRouteEnter(to, from, next) {
Lines 82-100 export default { Link Here
82
            vm.before_route_entered = true // FIXME This is ugly, but we need to distinguish when it's used as main component or child component (from EHoldingsEBSCOPAckagesShow for instance)
99
            vm.before_route_entered = true // FIXME This is ugly, but we need to distinguish when it's used as main component or child component (from EHoldingsEBSCOPAckagesShow for instance)
83
            if (!vm.building_table) {
100
            if (!vm.building_table) {
84
                vm.building_table = true
101
                vm.building_table = true
85
                vm.getAgreementCount().then(() => vm.build_datatable())
102
                vm.getAgreementCount()
103
                vm.initialized = true
86
            }
104
            }
87
        })
105
        })
88
    },
106
    },
89
    computed: {
90
        datatable_url() {
91
            let url = "/api/v1/erm/agreements"
92
            if (this.filters.by_expired)
93
                url +=
94
                    "?max_expiration_date=" + this.filters.max_expiration_date
95
            return url
96
        },
97
    },
98
    methods: {
107
    methods: {
99
        async getAgreementCount() {
108
        async getAgreementCount() {
100
            const client = APIClient.erm
109
            const client = APIClient.erm
Lines 106-144 export default { Link Here
106
                error => {}
115
                error => {}
107
            )
116
            )
108
        },
117
        },
109
        show_agreement: function (agreement_id) {
118
        doShow: function (agreement, dt, event) {
110
            this.$router.push("/cgi-bin/koha/erm/agreements/" + agreement_id)
119
            event.preventDefault()
120
            this.$router.push(
121
                "/cgi-bin/koha/erm/agreements/" + agreement.agreement_id
122
            )
111
        },
123
        },
112
        edit_agreement: function (agreement_id) {
124
        doEdit: function (agreement, dt, event) {
113
            this.$router.push(
125
            this.$router.push(
114
                "/cgi-bin/koha/erm/agreements/edit/" + agreement_id
126
                "/cgi-bin/koha/erm/agreements/edit/" + agreement.agreement_id
115
            )
127
            )
116
        },
128
        },
117
        delete_agreement: function (agreement_id) {
129
        doDelete: function (agreement, dt, event) {
118
            this.setConfirmation(
130
            this.setConfirmation(
119
                this.$__("Are you sure you want to remove this agreement?"),
131
                this.$__("Are you sure you want to delete this agreement?"),
120
                () => {
132
                () => {
121
                    const client = APIClient.erm
133
                    const client = APIClient.erm
122
                    client.agreements.delete(agreement_id).then(
134
                    client.agreements.delete(agreement.agreement_id).then(
123
                        success => {
135
                        success => {
124
                            this.setMessage(this.$__("Agreement deleted"))
136
                            this.setMessage(this.$__("Agreement deleted"))
125
                            this.refresh_table()
137
                            dt.draw()
126
                        },
138
                        },
127
                        error => {}
139
                        error => {}
128
                    )
140
                    )
129
                }
141
                }
130
            )
142
            )
131
        },
143
        },
144
        table_url: function () {
145
            let url = "/api/v1/erm/agreements"
146
            if (this.filters.by_expired)
147
                url +=
148
                    "?max_expiration_date=" + this.filters.max_expiration_date
149
            return url
150
        },
132
        select_agreement: function (agreement_id) {
151
        select_agreement: function (agreement_id) {
133
            this.$emit("select-agreement", agreement_id)
152
            this.$emit("select-agreement", agreement_id)
134
            this.$emit("close")
153
            this.$emit("close")
135
        },
154
        },
136
        refresh_table: function () {
137
            $("#" + this.table_id)
138
                .DataTable()
139
                .ajax.url(this.datatable_url)
140
                .draw()
141
        },
142
        filter_table: async function () {
155
        filter_table: async function () {
143
            if (this.before_route_entered) {
156
            if (this.before_route_entered) {
144
                let new_route = build_url(
157
                let new_route = build_url(
Lines 153-161 export default { Link Here
153
                        .toISOString()
166
                        .toISOString()
154
                        .substring(0, 10)
167
                        .substring(0, 10)
155
            }
168
            }
156
            this.refresh_table()
169
            this.$refs.table.redraw(this.table_url())
157
        },
170
        },
158
        table_url: function () {},
159
        build_datatable: function () {
171
        build_datatable: function () {
160
            let show_agreement = this.show_agreement
172
            let show_agreement = this.show_agreement
161
            let edit_agreement = this.edit_agreement
173
            let edit_agreement = this.edit_agreement
Lines 442-455 export default { Link Here
442
                1
454
                1
443
            )
455
            )
444
        },
456
        },
457
        getTableColumns: function () {
458
            let get_lib_from_av = this.get_lib_from_av
459
            let escape_str = this.escape_str
460
            window["vendors"] = this.vendors.map(e => {
461
                e["_id"] = e["id"]
462
                e["_str"] = e["name"]
463
                return e
464
            })
465
            let vendors_map = this.vendors.reduce((map, e) => {
466
                map[e.id] = e
467
                return map
468
            }, {})
469
            let avs = [
470
                "av_agreement_statuses",
471
                "av_agreement_closure_reasons",
472
                "av_agreement_renewal_priorities",
473
            ]
474
            let c = this
475
            avs.forEach(function (av_cat) {
476
                window[av_cat] = c.map_av_dt_filter(av_cat)
477
            })
478
479
            window["av_agreement_is_perpetual"] = [
480
                { _id: 0, _str: _("No") },
481
                { _id: 1, _str: _("Yes") },
482
            ]
483
            return [
484
                {
485
                    title: __("Name"),
486
                    data: "me.agreement_id:me.name",
487
                    searchable: true,
488
                    orderable: true,
489
                    render: function (data, type, row, meta) {
490
                        // Rendering done in drawCallback
491
                        return (
492
                            '<a href="/cgi-bin/koha/erm/agreements/' +
493
                            row.agreement_id +
494
                            '" class="show">show</a>'
495
                        )
496
                    },
497
                },
498
                {
499
                    title: __("Vendor"),
500
                    data: "vendor_id",
501
                    searchable: true,
502
                    orderable: true,
503
                    render: function (data, type, row, meta) {
504
                        return row.vendor_id != undefined
505
                            ? escape_str(vendors_map[row.vendor_id].name)
506
                            : ""
507
                    },
508
                },
509
                {
510
                    title: __("Description"),
511
                    data: "description",
512
                    searchable: true,
513
                    orderable: true,
514
                },
515
                {
516
                    title: __("Status"),
517
                    data: "status",
518
                    searchable: true,
519
                    orderable: true,
520
                    render: function (data, type, row, meta) {
521
                        return escape_str(
522
                            get_lib_from_av("av_agreement_statuses", row.status)
523
                        )
524
                    },
525
                },
526
                {
527
                    title: __("Closure reason"),
528
                    data: "closure_reason",
529
                    searchable: true,
530
                    orderable: true,
531
                    render: function (data, type, row, meta) {
532
                        return escape_str(
533
                            get_lib_from_av(
534
                                "av_agreement_closure_reasons",
535
                                row.closure_reason
536
                            )
537
                        )
538
                    },
539
                },
540
                {
541
                    title: __("Is perpetual"),
542
                    data: "is_perpetual",
543
                    searchable: true,
544
                    orderable: true,
545
                    render: function (data, type, row, meta) {
546
                        return escape_str(row.is_perpetual ? _("Yes") : _("No"))
547
                    },
548
                },
549
                {
550
                    title: __("Renewal priority"),
551
                    data: "renewal_priority",
552
                    searchable: true,
553
                    orderable: true,
554
                    render: function (data, type, row, meta) {
555
                        return escape_str(
556
                            get_lib_from_av(
557
                                "av_agreement_renewal_priorities",
558
                                row.renewal_priority
559
                            )
560
                        )
561
                    },
562
                },
563
                {
564
                    title: __("Actions"),
565
                    data: function (row, type, val, meta) {
566
                        return '<div class="actions"></div>'
567
                    },
568
                    className: "actions noExport",
569
                    searchable: false,
570
                    orderable: false,
571
                },
572
            ]
573
        },
445
    },
574
    },
446
    mounted() {
575
    mounted() {
447
        if (!this.building_table) {
576
        if (!this.building_table) {
448
            this.building_table = true
577
            this.building_table = true
449
            this.getAgreementCount().then(() => this.build_datatable())
578
            this.getAgreementCount()
450
        }
579
        }
451
    },
580
    },
452
    components: { flatPickr, Toolbar },
581
    components: { flatPickr, Toolbar, KohaTable },
453
    name: "AgreementsList",
582
    name: "AgreementsList",
454
    emits: ["select-agreement", "close"],
583
    emits: ["select-agreement", "close"],
455
}
584
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/KohaTable.vue (+142 lines)
Line 0 Link Here
1
<template>
2
    <DataTable
3
        :columns="tableColumns"
4
        :options="{ ...dataTablesDefaults, ...allOptions }"
5
        :data="data"
6
        ref="table"
7
    >
8
        <slot></slot>
9
    </DataTable>
10
</template>
11
12
<script>
13
import DataTable from "datatables.net-vue3"
14
import DataTablesLib from "datatables.net"
15
import "datatables.net-buttons"
16
import "datatables.net-buttons/js/buttons.html5"
17
import "datatables.net-buttons/js/buttons.print"
18
DataTable.use(DataTablesLib)
19
20
export default {
21
    name: "KohaTable",
22
    data() {
23
        return {
24
            data: [],
25
            tableColumns: this.columns,
26
            allOptions: {
27
                deferRender: true,
28
                paging: true,
29
                serverSide: true,
30
                searching: true,
31
                pagingType: "full_numbers",
32
                processing: true,
33
                ajax: {
34
                    url: typeof this.url === "function" ? this.url() : this.url,
35
                    ..._dt_default_ajax({ options: this.options }),
36
                },
37
                buttons: _dt_buttons({ table_settings: this.table_settings }),
38
                default_search: this.$route.query.q,
39
            },
40
        }
41
    },
42
    setup() {
43
        return { dataTablesDefaults }
44
    },
45
    methods: {
46
        redraw: function (url) {
47
            this.$refs.table.dt().ajax.url(url).draw()
48
        },
49
    },
50
    beforeMount() {
51
        if (this.actions.hasOwnProperty("-1")) {
52
            let actions = this.actions["-1"]
53
            this.tableColumns = [
54
                ...this.tableColumns,
55
                {
56
                    name: "actions",
57
                    title: this.$__("Actions"),
58
                    searchable: false,
59
                    render: (data, type, row) => {
60
                        let content = []
61
                        this.actions["-1"].forEach(a => {
62
                            if (a == "edit") {
63
                                content.push(
64
                                    '<a class="edit btn btn-default btn-xs" role="button"><i class="fa fa-pencil"></i>' +
65
                                        this.$__("Edit") +
66
                                        "</a>"
67
                                )
68
                            } else if (a == "delete") {
69
                                content.push(
70
                                    '<a class="delete btn btn-default btn-xs" role="button"><i class="fa fa-trash"></i>' +
71
                                        this.$__("Delete") +
72
                                        "</a>"
73
                                )
74
                            }
75
                        })
76
                        return content.join(" ")
77
                    },
78
                },
79
            ]
80
        }
81
    },
82
    mounted() {
83
        if (Object.keys(this.actions).length) {
84
            const dt = this.$refs.table.dt()
85
            const self = this
86
            dt.on("draw", () => {
87
                const dataSet = dt.rows().data()
88
                Object.entries(this.actions).forEach(([col_id, actions]) => {
89
                    dt.column(col_id)
90
                        .nodes()
91
                        .to$()
92
                        .each(function (idx) {
93
                            const data = dataSet[idx]
94
                            actions.forEach(action => {
95
                                $("." + action, this).on("click", e => {
96
                                    self.$emit(action, data, dt, e)
97
                                })
98
                            })
99
                        })
100
                })
101
            })
102
        }
103
    },
104
    beforeUnmount() {
105
        const dt = this.$refs.table.dt()
106
        dt.destroy()
107
    },
108
    components: {
109
        DataTable,
110
    },
111
    props: {
112
        url: {
113
            type: [String, Function],
114
            default: "",
115
        },
116
        columns: {
117
            type: Array,
118
            default: [],
119
        },
120
        actions: {
121
            type: Object,
122
            default: {},
123
        },
124
        options: {
125
            type: Object,
126
            default: {},
127
        },
128
        default_filters: {
129
            type: Object,
130
            required: false,
131
        },
132
        table_settings: {
133
            type: Object,
134
            required: false,
135
        },
136
        default_search: {
137
            type: String,
138
            required: false,
139
        },
140
    },
141
}
142
</script>
(-)a/package.json (+2 lines)
Lines 18-23 Link Here
18
    "bootstrap": "^4.5.2",
18
    "bootstrap": "^4.5.2",
19
    "css-loader": "^6.6.0",
19
    "css-loader": "^6.6.0",
20
    "cypress": "^9.5.2",
20
    "cypress": "^9.5.2",
21
    "datatables.net-buttons": "^2.3.4",
22
    "datatables.net-vue3": "^2.0.0",
21
    "gulp": "^4.0.2",
23
    "gulp": "^4.0.2",
22
    "gulp-autoprefixer": "^4.0.0",
24
    "gulp-autoprefixer": "^4.0.0",
23
    "gulp-concat-po": "^1.0.0",
25
    "gulp-concat-po": "^1.0.0",
(-)a/yarn.lock (-1 / +28 lines)
Lines 3518-3523 dashdash@^1.12.0: Link Here
3518
  dependencies:
3518
  dependencies:
3519
    assert-plus "^1.0.0"
3519
    assert-plus "^1.0.0"
3520
3520
3521
datatables.net-buttons@^2.3.4:
3522
  version "2.3.4"
3523
  resolved "https://registry.yarnpkg.com/datatables.net-buttons/-/datatables.net-buttons-2.3.4.tgz#85b88baed81d380cb04c06608d549c8868326ece"
3524
  integrity sha512-1fe/aiKBdKbwJ5j0OobP2dzhbg/alGOphnTfLFGaqlP5yVxDCfcZ9EsuglYeHRJ/KnU7DZ8BgsPFiTE0tOFx8Q==
3525
  dependencies:
3526
    datatables.net ">=1.12.1"
3527
    jquery ">=1.7"
3528
3529
datatables.net-vue3@^2.0.0:
3530
  version "2.0.0"
3531
  resolved "https://registry.yarnpkg.com/datatables.net-vue3/-/datatables.net-vue3-2.0.0.tgz#01b20c68201f49f57920dea62a5f742b7119880b"
3532
  integrity sha512-auwLfwqebGZ0gFnU8C/HWQYpkVtU64x8T+gYs5i7/Jqyo3YNTDU2M/lWwp7rJ+VSlolkDICrKpfmmo/Rz6ZBFw==
3533
  dependencies:
3534
    datatables.net "^1.13.1"
3535
    jquery "^3.6.0"
3536
3537
datatables.net@>=1.12.1, datatables.net@^1.13.1:
3538
  version "1.13.2"
3539
  resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.2.tgz#48f7035b1696a29cb70909db1f2e0ebd5f946f3e"
3540
  integrity sha512-u5nOU+C9SBp1SyPmd6G+niozZtrBwo1E8xzdOk3JJaAkFYgX/KxF3Gd79R8YLbUfmIs2OLnLe5gaz/qs5U8UDA==
3541
  dependencies:
3542
    jquery ">=1.7"
3543
3521
dayjs@^1.10.4:
3544
dayjs@^1.10.4:
3522
  version "1.11.5"
3545
  version "1.11.5"
3523
  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
3546
  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
Lines 5935-5940 joi@^17.4.0: Link Here
5935
    "@sideway/formula" "^3.0.0"
5958
    "@sideway/formula" "^3.0.0"
5936
    "@sideway/pinpoint" "^2.0.0"
5959
    "@sideway/pinpoint" "^2.0.0"
5937
5960
5961
jquery@>=1.7, jquery@^3.6.0:
5962
  version "3.6.3"
5963
  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
5964
  integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==
5965
5938
js-message@1.0.7:
5966
js-message@1.0.7:
5939
  version "1.0.7"
5967
  version "1.0.7"
5940
  resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
5968
  resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
5941
- 

Return to bug 33066