From 498a37f229b1d42f2d0d698c712e0ae9a912438c Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Fri, 24 Feb 2023 13:14:19 +0100 Subject: [PATCH] Bug 33066: Introduce a KohaTable Vue component MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The way we deal with DataTables in Vue component is not nice, especially when we need to add buttons/link in the cell and interact with the rest of the Vue app from there. When I started to work on Vue last year there was no good solution from DataTables, now there is a Vue component. It is not perfect, you still cannot add Vue component in the DT component, but it brings something to follow. Agustin implemented something on theke/import_source_vue, but he went too far, and it will need to rewrite the whole ERM module. Additionally he didn't provide a solution that has the same features as what we have now. The goal of this patch is to not duplicate the code in datatables.js, we don't want to maintain two version of this code (one is enough already!) We split the huge function in datatables.js in small ones to make them reusable from the Vue component. This is quite ugly, and it needs to lot more addition, but it's a first start! Help, ideas, and feedback welcome (and needed!) Bug 33066: Fix agreement name in delete confirmation dialog Signed-off-by: Pedro Amorim Signed-off-by: Agustín Moyano Signed-off-by: Tomas Cohen Arazi (cherry picked from commit 765fd1ced3b9efc4ff6fb71e2fee1a7a227d1cae) Signed-off-by: Martin Renvoize --- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 738 +++++++++--------- .../js/vue/components/ERM/AgreementsList.vue | 205 ++++- .../prog/js/vue/components/KohaTable.vue | 142 ++++ package.json | 2 + yarn.lock | 28 + 5 files changed, 722 insertions(+), 393 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/KohaTable.vue diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index f6e0834f22..81a900e084 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -504,6 +504,381 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { console.log(message); }; +function _dt_default_ajax (params){ + let default_filters = params.default_filters; + let options = params.options; + + if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains'; + options.criteria = options.criteria.toLowerCase(); + + return { + 'type': 'GET', + 'cache': true, + 'dataSrc': 'data', + 'beforeSend': function(xhr, settings) { + this._xhr = xhr; + if(options.embed) { + xhr.setRequestHeader('x-koha-embed', Array.isArray(options.embed)?options.embed.join(','):options.embed); + } + }, + 'dataFilter': function(data, type) { + var json = {data: JSON.parse(data)}; + if (total = this._xhr.getResponseHeader('x-total-count')) { + json.recordsTotal = total; + json.recordsFiltered = total; + } + if (total = this._xhr.getResponseHeader('x-base-total-count')) { + json.recordsTotal = total; + } + if (draw = this._xhr.getResponseHeader('x-koha-request-id')) { + json.draw = draw; + } + + return JSON.stringify(json); + }, + 'data': function( data, settings ) { + var length = data.length; + var start = data.start; + + var dataSet = { + _page: Math.floor(start/length) + 1, + _per_page: length + }; + + function build_query(col, value){ + + var parts = []; + var attributes = col.data.split(':'); + for (var i=0;i order_dir + (!x.includes('.')?'me.'+x:x))); + }); + dataSet._order_by = orderArray.filter((v, i, a) => a.indexOf(v) === i).join(','); + } + + return dataSet; + } + } +} + +function _dt_buttons(params){ + let included_ids = params.included_ids || []; + let settings = params.settings || {}; + let table_settings = params.table_settings || {}; + + var exportColumns = ":visible:not(.noExport)"; + if( settings.hasOwnProperty("exportColumns") ){ + // A custom buttons configuration has been passed from the page + exportColumns = settings["exportColumns"]; + } + + var export_format = { + body: function ( data, row, column, node ) { + var newnode = $(node); + + if ( newnode.find(".noExport").length > 0 ) { + newnode = newnode.clone(); + newnode.find(".noExport").remove(); + } + + return newnode.text().replace( /\n/g, ' ' ).trim(); + } + } + + var export_buttons = [ + { + extend: 'excelHtml5', + text: __("Excel"), + exportOptions: { + columns: exportColumns, + format: export_format + }, + }, + { + extend: 'csvHtml5', + text: __("CSV"), + exportOptions: { + columns: exportColumns, + format: export_format + }, + }, + { + extend: 'copyHtml5', + text: __("Copy"), + exportOptions: { + columns: exportColumns, + format: export_format + }, + }, + { + extend: 'print', + text: __("Print"), + exportOptions: { + columns: exportColumns, + format: export_format + }, + } + ]; + + let buttons = []; + buttons.push( + { + fade: 100, + className: "dt_button_clear_filter", + titleAttr: __("Clear filter"), + enabled: false, + text: ' ' + __("Clear filter") + '', + action: function ( e, dt, node, config ) { + dt.search( "" ).draw("page"); + node.addClass("disabled"); + } + } + ); + + if( included_ids.length > 0 ){ + buttons.push( + { + extend: 'colvis', + fade: 100, + columns: included_ids, + className: "columns_controls", + titleAttr: __("Columns settings"), + text: ' ' + __("Columns") + '', + exportOptions: { + columns: exportColumns + } + } + ); + } + + buttons.push( + { + extend: 'collection', + autoClose: true, + fade: 100, + className: "export_controls", + titleAttr: __("Export or print"), + text: ' ' + __("Export") + '', + buttons: export_buttons + } + ); + + if ( table_settings && CAN_user_parameters_manage_column_config ) { + buttons.push( + { + className: "dt_button_configure_table", + fade: 100, + titleAttr: __("Configure table"), + text: ' ' + __("Configure") + '', + action: function() { + window.location = '/cgi-bin/koha/admin/columns_settings.pl?module=' + table_settings['module'] + '&page=' + table_settings['page'] + '&table=' + table_settings['table']; + }, + } + ); + } + + return buttons; +} + +function _dt_visibility(table_settings, settings){ + var counter = 0; + let hidden_ids = []; + let included_ids = []; + if ( table_settings ) { + var columns_settings = table_settings['columns']; + $(columns_settings).each( function() { + var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', this ).index( 'th' ); + var used_id = settings.bKohaColumnsUseNames ? named_id : counter; + if ( used_id == -1 ) return; + + if ( this['is_hidden'] == "1" ) { + hidden_ids.push( used_id ); + } + if ( this['cannot_be_toggled'] == "0" ) { + included_ids.push( used_id ); + } + counter++; + }); + } + return [hidden_ids, included_ids]; +} + +function _dt_on_visibility(add_filters, table_node, table_dt){ + if ( add_filters ) { + let visible_columns = table_dt.columns().visible(); + $(table_node).find('thead tr:eq(1) th').each( function (i) { + let th_id = $(this).data('th-id'); + if ( visible_columns[th_id] == false ) { + $(this).hide(); + } else { + $(this).show(); + } + }); + } + + if( typeof columnsInit == 'function' ){ + // This function can be created separately and used to trigger + // an event after the DataTable has loaded AND column visibility + // has been updated according to the table's configuration + columnsInit(); + } +} + +function _dt_add_filters(table_node, table_dt) { + $(table_node).find('thead tr').clone().appendTo( $(table_node).find('thead') ); + + $(table_node).find('thead tr:eq(1) th').each( function (i) { + var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable; + $(this).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc"); + $(this).data('th-id', i); + if ( is_searchable ) { + let input_type = 'input'; + if ( $(this).data('filter') ) { + input_type = 'select' + let filter_type = $(this).data('filter'); + var existing_search = table_dt.column(i).search(); + let select = $(''.format(existing_search) ); + } else { + var search_title = _("%s search").format(title); + $(this).html( ''.format(search_title) ); + } + } + + $( input_type, this ).on( 'keyup change', function () { + if ( table_dt.column(i).search() !== this.value ) { + if ( input_type == "input" ) { + table_dt + .column(i) + .search( this.value ) + .draw(); + } else { + table_dt + .column(i) + .search( this.value.length ? '^'+this.value+'$' : '', true, false ) + .draw(); + } + } + } ); + } else { + $(this).html(''); + } + } ); +} + + (function($) { /** @@ -522,9 +897,6 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { var settings = null; if(options) { - if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains'; - options.criteria = options.criteria.toLowerCase(); - // Don't redefine the default initComplete if ( options.initComplete ) { let our_initComplete = options.initComplete; @@ -544,286 +916,14 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { 'language': { 'emptyTable': (options.emptyTable) ? options.emptyTable : __("No data available in table") }, - 'ajax': { - 'type': 'GET', - 'cache': true, - 'dataSrc': 'data', - 'beforeSend': function(xhr, settings) { - this._xhr = xhr; - if(options.embed) { - xhr.setRequestHeader('x-koha-embed', Array.isArray(options.embed)?options.embed.join(','):options.embed); - } - }, - 'dataFilter': function(data, type) { - var json = {data: JSON.parse(data)}; - if (total = this._xhr.getResponseHeader('x-total-count')) { - json.recordsTotal = total; - json.recordsFiltered = total; - } - if (total = this._xhr.getResponseHeader('x-base-total-count')) { - json.recordsTotal = total; - } - if (draw = this._xhr.getResponseHeader('x-koha-request-id')) { - json.draw = draw; - } - - return JSON.stringify(json); - }, - 'data': function( data, settings ) { - var length = data.length; - var start = data.start; - - var dataSet = { - _page: Math.floor(start/length) + 1, - _per_page: length - }; - - function build_query(col, value){ - - var parts = []; - var attributes = col.data.split(':'); - for (var i=0;i order_dir + (!x.includes('.')?'me.'+x:x))); - }); - dataSet._order_by = orderArray.filter((v, i, a) => a.indexOf(v) === i).join(','); - } - - return dataSet; - } - } + 'ajax': _dt_default_ajax({default_filters, options}), }, options); } - var counter = 0; - var hidden_ids = []; - var included_ids = []; - - - if ( table_settings ) { - var columns_settings = table_settings['columns']; - $(columns_settings).each( function() { - var named_id = $( 'thead th[data-colname="' + this.columnname + '"]', this ).index( 'th' ); - var used_id = settings.bKohaColumnsUseNames ? named_id : counter; - if ( used_id == -1 ) return; - - if ( this['is_hidden'] == "1" ) { - hidden_ids.push( used_id ); - } - if ( this['cannot_be_toggled'] == "0" ) { - included_ids.push( used_id ); - } - counter++; - }); - } - - var exportColumns = ":visible:not(.noExport)"; - if( settings.hasOwnProperty("exportColumns") ){ - // A custom buttons configuration has been passed from the page - exportColumns = settings["exportColumns"]; - } - - var export_format = { - body: function ( data, row, column, node ) { - var newnode = $(node); - - if ( newnode.find(".noExport").length > 0 ) { - newnode = newnode.clone(); - newnode.find(".noExport").remove(); - } - - return newnode.text().replace( /\n/g, ' ' ).trim(); - } - } - - var export_buttons = [ - { - extend: 'excelHtml5', - text: __("Excel"), - exportOptions: { - columns: exportColumns, - format: export_format - }, - }, - { - extend: 'csvHtml5', - text: __("CSV"), - exportOptions: { - columns: exportColumns, - format: export_format - }, - }, - { - extend: 'copyHtml5', - text: __("Copy"), - exportOptions: { - columns: exportColumns, - format: export_format - }, - }, - { - extend: 'print', - text: __("Print"), - exportOptions: { - columns: exportColumns, - format: export_format - }, - } - ]; - - settings[ "buttons" ] = [ - { - fade: 100, - className: "dt_button_clear_filter", - titleAttr: __("Clear filter"), - enabled: false, - text: ' ' + __("Clear filter") + '', - action: function ( e, dt, node, config ) { - dt.search( "" ).draw("page"); - node.addClass("disabled"); - } - } - ]; - - if( included_ids.length > 0 ){ - settings[ "buttons" ].push( - { - extend: 'colvis', - fade: 100, - columns: included_ids, - className: "columns_controls", - titleAttr: __("Columns settings"), - text: ' ' + __("Columns") + '', - exportOptions: { - columns: exportColumns - } - } - ); - } - - settings[ "buttons" ].push( - { - extend: 'collection', - autoClose: true, - fade: 100, - className: "export_controls", - titleAttr: __("Export or print"), - text: ' ' + __("Export") + '', - buttons: export_buttons - } - ); + let hidden_ids, included_ids; + [hidden_ids, included_ids] = _dt_visibility(table_settings, settings) - if ( table_settings && CAN_user_parameters_manage_column_config ) { - settings[ "buttons" ].push( - { - className: "dt_button_configure_table", - fade: 100, - titleAttr: __("Configure table"), - text: ' ' + __("Configure") + '', - action: function() { - window.location = '/cgi-bin/koha/admin/columns_settings.pl?module=' + table_settings['module'] + '&page=' + table_settings['page'] + '&table=' + table_settings['table']; - }, - } - ); - } + settings["buttons"] = _dt_buttons({included_ids, settings, table_settings}); $(".dt_button_clear_filter, .columns_controls, .export_controls, .dt_button_configure_table").tooltip(); @@ -842,85 +942,13 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { var table = $(this).dataTable(settings); - + var table_dt = table.DataTable(); if ( add_filters ) { - var table_dt = table.DataTable(); - - $(this).find('thead tr').clone().appendTo( $(this).find('thead') ); - - $(this).find('thead tr:eq(1) th').each( function (i) { - var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable; - $(this).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc"); - $(this).data('th-id', i); - if ( is_searchable ) { - let input_type = 'input'; - if ( $(this).data('filter') ) { - input_type = 'select' - let filter_type = $(this).data('filter'); - var existing_search = table_dt.column(i).search(); - let select = $(''.format(existing_search) ); - } else { - var search_title = __("%s search").format(title); - $(this).html( ''.format(search_title) ); - } - } - - $( input_type, this ).on( 'keyup change', function () { - if ( table_dt.column(i).search() !== this.value ) { - if ( input_type == "input" ) { - table_dt - .column(i) - .search( this.value ) - .draw(); - } else { - table_dt - .column(i) - .search( this.value.length ? '^'+this.value+'$' : '', true, false ) - .draw(); - } - } - } ); - } else { - $(this).html(''); - } - } ); + _dt_add_filters(this, table_dt); } - table.DataTable().on("column-visibility.dt", function(){ - if ( add_filters ) { - let visible_columns = table_dt.columns().visible(); - $(table).find('thead tr:eq(1) th').each( function (i) { - let th_id = $(this).data('th-id'); - if ( visible_columns[th_id] == false ) { - $(this).hide(); - } else { - $(this).show(); - } - }); - } - - if( typeof columnsInit == 'function' ){ - // This function can be created separately and used to trigger - // an event after the DataTable has loaded AND column visibility - // has been updated according to the table's configuration - columnsInit(); - } - }).columns( hidden_ids ).visible( false ); + table.DataTable().on("column-visibility.dt", function(){_dt_on_visibility(add_filters, table, table_dt);}) + .columns( hidden_ids ).visible( false ); return table; }; diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue index d6716edab8..89cd3f95cb 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue @@ -33,7 +33,13 @@ />
-
+
{{ $__("There are no agreements defined") }} @@ -44,10 +50,11 @@ diff --git a/package.json b/package.json index 046755dbb8..1a4d20e98e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "bootstrap": "^4.5.2", "css-loader": "^6.6.0", "cypress": "^9.5.2", + "datatables.net-buttons": "^2.3.4", + "datatables.net-vue3": "^2.0.0", "gulp": "^4.0.2", "gulp-autoprefixer": "^4.0.0", "gulp-concat-po": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 5c2947a442..cefae98024 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3518,6 +3518,29 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +datatables.net-buttons@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/datatables.net-buttons/-/datatables.net-buttons-2.3.4.tgz#85b88baed81d380cb04c06608d549c8868326ece" + integrity sha512-1fe/aiKBdKbwJ5j0OobP2dzhbg/alGOphnTfLFGaqlP5yVxDCfcZ9EsuglYeHRJ/KnU7DZ8BgsPFiTE0tOFx8Q== + dependencies: + datatables.net ">=1.12.1" + jquery ">=1.7" + +datatables.net-vue3@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/datatables.net-vue3/-/datatables.net-vue3-2.0.0.tgz#01b20c68201f49f57920dea62a5f742b7119880b" + integrity sha512-auwLfwqebGZ0gFnU8C/HWQYpkVtU64x8T+gYs5i7/Jqyo3YNTDU2M/lWwp7rJ+VSlolkDICrKpfmmo/Rz6ZBFw== + dependencies: + datatables.net "^1.13.1" + jquery "^3.6.0" + +datatables.net@>=1.12.1, datatables.net@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.2.tgz#48f7035b1696a29cb70909db1f2e0ebd5f946f3e" + integrity sha512-u5nOU+C9SBp1SyPmd6G+niozZtrBwo1E8xzdOk3JJaAkFYgX/KxF3Gd79R8YLbUfmIs2OLnLe5gaz/qs5U8UDA== + dependencies: + jquery ">=1.7" + dayjs@^1.10.4: version "1.11.5" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" @@ -5935,6 +5958,11 @@ joi@^17.4.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +jquery@>=1.7, jquery@^3.6.0: + version "3.6.3" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6" + integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg== + js-message@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47" -- 2.39.5