From e5e3d922b7135672af994befb50872fe29bc3463 Mon Sep 17 00:00:00 2001 From: Sam Lau Date: Mon, 19 Aug 2024 20:44:22 +0000 Subject: [PATCH] Bug 37637: Use the API to load the items table This patch removes the previous use of the database to load the additem table and instead uses the API. To test: 1) Test the current datatable behavior: visit an item's detail page and select 'Edit' then 'Edit items' 2) Apply the patches, restart_all, reset_all, clear caches, etc. 3) Again, test the table behaviors. Try adding items, using the actions, filtering and sorting the items, etc. --- cataloguing/additem.pl | 3 + .../prog/en/modules/cataloguing/additem.tt | 41 ++ .../prog/js/cataloging_additem.js | 493 +++++++++++++++++- 3 files changed, 511 insertions(+), 26 deletions(-) diff --git a/cataloguing/additem.pl b/cataloguing/additem.pl index 3d361ec1e63..1982fd15844 100755 --- a/cataloguing/additem.pl +++ b/cataloguing/additem.pl @@ -679,6 +679,9 @@ if ($op) { # now, build existiing item list +my @libraries = $patron->libraries_where_can_edit_items; +$template->param(can_edit_items_from => \@libraries); + my @items; for my $item ( $biblio->items->as_list, $biblio->host_items->as_list ) { my $i = $item->columns_to_str; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt index cf7259975c6..90db8345090 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt @@ -5,6 +5,11 @@ [% USE KohaDates %] [% USE Price %] [% USE TablesSettings %] +[% USE ItemTypes %] +[% USE To %] +[% USE tojson = To %] +[% USE AuthorisedValues %] +[% USE ClassSources %] [% PROCESS 'i18n.inc' %] [% INCLUDE 'doc-head-open.inc' %] [% FILTER collapse %] @@ -34,7 +39,39 @@ [% Asset.js("js/browser.js") | $raw %] [% INCLUDE 'calendar.inc' %] [% INCLUDE 'str/cataloging_additem.inc' %] +[% INCLUDE 'csrf-token.inc' %] [% Asset.js("js/cataloging_additem.js") | $raw %] + <script> + const can_edit_items_from = [% To.json(can_edit_items_from || []) | $raw %]; + + // filter dropdown variables + const all_libraries = [% To.json(Branches.all) | $raw %]; + const all_item_types = [% To.json(ItemTypes.Get) | $raw %]; + const all_damaged_statuses = [% To.json(AuthorisedValues.Get("DAMAGED")) | $raw %]; + const all_lost_statuses = [% To.json(AuthorisedValues.Get("LOST")) | $raw %]; + const all_stack = [% To.json(AuthorisedValues.Get("STACK")) | $raw %]; + const all_collection_codes = [% To.json(AuthorisedValues.Get("CCODE")) | $raw %]; + const all_shelivng_locations = [% To.json(AuthorisedValues.Get("LOC")) | $raw %]; + const all_not_for_loan_statuses = [% To.json(AuthorisedValues.Get("NOT_LOAN")) | $raw %]; + const all_restricted_statuses = [% To.json(AuthorisedValues.Get("RESTRICTED")) | $raw %]; + const all_withdrawn = [% To.json(AuthorisedValues.Get("WITHDRAWN")) | $raw %]; + const all_class_sources = [ + [% FOREACH class_source IN ClassSources.all %] + { + cn_source: "[% class_source.cn_source | html %]", + description: "[% class_source.description | html %]" + }, + [% END %] + ]; + + var opac_base_url; + [% IF ( Koha.Preference('OPACBaseURL') ) %] + var opac_base_url = "[% Koha.Preference('OPACBaseURL') | url %]"; + [% END %] + + var is_independent_branches = [% Koha.Preference('IndependentBranches') | html %] && ![% logged_in_user.is_superlibrarian | html %]; + var logged_in_branchname = "[% Branches.GetLoggedInBranchname() | html %]"; + </script> [% Asset.js("js/form-submit.js") | $raw %] <script> var has_item_groups = "[% item_groups.size | html %]"; @@ -91,6 +128,10 @@ [% IF items %] [% SET date_fields = [ 'dateaccessioned', 'onloan', 'datelastseen', 'datelastborrowed', 'replacementpricedate' ] %] <div class="page-section"> + <span class="show_hide_filters"> + <a href="#" class="show_filters"><i class="fa fa-filter"></i> Show filters</a> + <a href="#" class="hide_filters" style="display: none;"><i class="fa fa-filter"></i> Hide filters</a> + </span> <table id="itemst"> <thead> <tr> diff --git a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js index c8a2189b834..7c7bde3f15c 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js +++ b/koha-tmpl/intranet-tmpl/prog/js/cataloging_additem.js @@ -4,6 +4,97 @@ var browser = KOHA.browser(searchid, parseInt(biblionumber, 10)); browser.show(); $(document).ready(function(){ + table_settings['columns'].unshift( { cannot_be_toggled: "1" } ); + build_itemst(false, {}); + + const filterContainer = document.querySelector('.show_hide_filters'); + const showFiltersLink = filterContainer.querySelector('.show_filters'); + const hideFiltersLink = filterContainer.querySelector('.hide_filters'); + + $(".show_filters").on("click",function(e){ + e.preventDefault(); + showFiltersLink.style.display = 'none'; + hideFiltersLink.style.display = 'inline'; + $("#itemst thead tr:eq(1)").remove(); + build_itemst(true, { destroy: true }); + }); + + $(".hide_filters").on("click",function(e){ + e.preventDefault(); + showFiltersLink.style.display = 'inline'; + hideFiltersLink.style.display = 'none'; + $("#itemst thead tr:eq(1)").remove(); + build_itemst(false, { destroy: true }); + }); + + // Build the filter dropdowns + const libraries_filters = all_libraries.map(e => { + e["_id"] = e["branchcode"]; + e["_str"] = e["branchname"]; + return e; + }); + const libraries_names = new Map(all_libraries.map( l => [l.branchcode, l.branchname] )); + + const item_types_filters = all_item_types.map(e => { + e["_id"] = e["itemtype"]; + e["_str"] = e["translated_description"]; + return e; + }); + + const damaged_filters = all_damaged_statuses.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const lost_filters = all_lost_statuses.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const stack_filters = all_stack.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const ccode_filters = all_collection_codes.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const shelving_location_filters = all_shelivng_locations.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const not_for_loan_filters = all_not_for_loan_statuses.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const withdrawn_filters = all_withdrawn.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + + const restricted_filters = all_restricted_statuses.map(e => { + e["_id"] = e["authorised_value"]; + e["_str"] = e["lib"]; + return e; + }); + + const class_sources_filters = all_class_sources.map(e => { + e["_id"] = e["cn_source"]; + e["_str"] = e["description"]; + return e; + }); // Remove the onclick event defined in browser.js, // otherwise the deletion confirmation will not work correctly @@ -14,23 +105,7 @@ $(document).ready(function(){ } $("fieldset.rows input, fieldset.rows select").addClass("noEnterSubmit"); - /* Inline edit/delete links */ var biblionumber = $("input[name='biblionumber']").val(); - $("tr.editable").each(function(){ - $(this).find("td:not(:first)").on('click', function(){ - var rowid = $(this).parent().attr("id"); - var num_rowid = rowid.replace("row",""); - $(".linktools").remove(); - var edit_link = $('<a href="/cgi-bin/koha/cataloguing/additem.pl?op=edititem&frameworkcode=' + frameworkcode + '&biblionumber=' + biblionumber + '&itemnumber=' + num_rowid + '&searchid=' + searchid + '#edititem"></a>'); - $(edit_link).text( LABEL_EDIT_ITEM ); - var delete_link = $('<a class="delete" data-item="'+num_rowid+'" href="#"></a>'); - $(delete_link).text( LABEL_DELETE_ITEM ); - var tools_node = $('<span class="linktools"></span>'); - $(tools_node).append(edit_link); - $(tools_node).append(delete_link); - $(this).append(tools_node); - }); - }); $("#addnewitem").click(function(){ if ( confirm( MSG_CONFIRM_ADD_ITEM ) ){ @@ -38,16 +113,6 @@ $(document).ready(function(){ } }); - // Skip the first column - table_settings['columns'].unshift( { cannot_be_toggled: "1" } ); - - var itemst = KohaTable("itemst", { - "paging": false, - "info": false, - "autoWidth": false, - "bKohaColumnsUseNames": true - }, table_settings); - var multiCopyControl = $("#add_multiple_copies_span"); var addMultipleBlock = $("#addmultiple"); var addSingleBlock = $("#addsingle"); @@ -203,3 +268,379 @@ function Dopop(link,i) { var defaultvalue=document.forms[0].field_value[i].value; var newin=window.open(link+"&result=" + defaultvalue,"valuebuilder",'width=500,height=400,toolbar=false,scrollbars=yes'); } + +function build_itemst(show_filters, dt_options) { + + if ( dt_options && dt_options.hasOwnProperty('destroy') ) { + let table_id = "#itemst"; + if( $.fn.dataTable.isDataTable(table_id) ) { + $(table_id).DataTable().destroy(); + } + dt_options['destroy'] = null; + } + + // assign positions for the filter dropdowns + let default_filters = {}; + var offset = 4; + let filters_options = { + [offset] : () => all_libraries, + [offset+6] : () => all_stack, + [offset+7] : () => all_not_for_loan_statuses, + [offset+8] : () => all_damaged_statuses, + [offset+9] : () => all_lost_statuses, + [offset+10] : () => all_withdrawn, + [offset+13] : () => all_restricted_statuses, + [offset+16] : () => all_libraries, + [offset+18] : () => all_shelivng_locations, + [offset+20] : () => all_class_sources, + [offset+21] : () => all_collection_codes, + [offset+24] : () => all_item_types, + }; + + var biblionumber = $("input[name='biblionumber']").val(); + var items_table_url = '/api/v1/biblios/' + biblionumber + '/items?'; + var items_table = $("#itemst").kohaTable({ + ajax: { + url: items_table_url + }, + order: [], + embed: ["+strings"], + autoWidth: false, + columns: [ + { + data: function(row, type, val, meta) { + let nodes = ''; + + let can_be_edited = !(is_independent_branches && row.home_library_id != logged_in_branchname); + if (can_be_edited) { + nodes += '<td>' + nodes += '<div class="btn-group dropup">'; + nodes += '<a class="btn btn-default btn-xs dropdown-toggle" id="itemactions' + row.item_id + '" role="button" data-toggle="dropdown" href="#">'; + nodes += 'Actions <b class="caret"></b>'; + nodes += '</a>'; + nodes += '<ul class="dropdown-menu" role="menu" aria-labelledby="itemactions' + row.item_id + '">'; + if (row.biblio_id != biblionumber) { // Host item + nodes += '<li><a href="additem.pl?op=edititem&biblionumber=' + row.biblio_id + '&itemnumber=' + row.item_id + '#edititem">Edit in host</a>   <a class="delete" href="/cgi-bin/koha/cataloguing/additem.pl?op=delinkitem&biblionumber=' + row.biblio_id + '&hostitemnumber=' + row.item_id + '&searchid=' + row.searchid + '">Delink</a></li>'; + } else { + if (can_edit_items_from.includes(row.home_library_id) || !can_edit_items_from.length) { + nodes += '<li><a href="additem.pl?op=edititem&biblionumber=' + row.biblio_id + '&itemnumber=' + row.item_id + '&searchid=' + row.searchid + '#edititem">Edit</a></li>'; + } + nodes += '<li><a href="additem.pl?op=dupeitem&biblionumber=' + row.biblio_id + '&itemnumber=' + row.item_id + '&searchid=' + row.searchid + '#additema">Duplicate</a></li>'; + nodes += '<li class="print_label">'; + nodes += '<a class="submit-form-link" target="_blank" href="#" data-op="cud-add" data-number_list="' + row.item_id + '" data-number_type="itemnumber" data-method="post" data-action="/cgi-bin/koha/labels/label-edit-batch.pl" data-new_tab="true">Print label</a>'; + nodes += '</li>'; + if (can_edit_items_from.includes(row.home_library_id) || !can_edit_items_from.length) { + let csrfToken = $('meta[name="csrf-token"]').attr('content'); + nodes += '<li>'; + nodes += '<form id="' + row.item_id + '-delete-item-form" action="/cgi-bin/koha/cataloguing/additem.pl" method="post">'; + nodes += '<input type="hidden" name="csrf_token" value="' + csrfToken + '" />'; + nodes += '<input type="hidden" name="op" value="cud-delitem" />'; + nodes += '<input type="hidden" name="biblionumber" value="' + row.biblio_id + '" />'; + nodes += '<input type="hidden" name="itemnumber" value="' + row.item_id + '" />'; + nodes += '<input type="hidden" name="searchid" value="' + row.searchid + '" />'; + nodes += '</form>'; + nodes += '<a class="delete" data-item="' + row.item_id + '" href="#">Delete</a>'; + nodes += '</li>'; + } + } + if ( opac_base_url ) { + var href = opac_base_url + "/cgi-bin/koha/opac-detail.pl?biblionumber=" + row.biblio_id; + nodes+= '<li class="view-in-opac"><a target="_blank" href="' + href + '">OPAC view</a></li>' + } + nodes += '</ul>'; + nodes += '</div>'; + nodes += '</td>'; + } else { + nodes += '<td> </td>' + } + return nodes; + }, + searchable: false, + orderable: false, + }, + { + data: "external_id", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + if ( row.external_id != null ) { + return '<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=%s&itemnumber=%s#item%s">%s</a>'.format(row.biblio_id, row.item_id, row.item_id, row.external_id); + } + return ''; + } + }, + { + data: "acquisition_date", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.acquisition_date); + } + }, + { + data: "acquisition_source", + searchable: true, + orderable: true, + }, + { + data: "home_library_id", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.home_library_id ? escape_str(row._strings.home_library_id.str) : ""; + } + }, + { + data: "purchase_price", + searchable: true, + orderable: true, + }, + { + data: "replacement_price", + searchable: true, + orderable: true, + }, + { + data: "replacement_price_date", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.replacement_price_date); + } + }, + { + data: "last_checkout_date", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.last_checkout_date); + } + }, + { + data: "last_seen_date", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.last_seen_date); + } + }, + { + data: "stack", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.stack ? escape_str(row._strings.stack.str) : ""; + } + }, + { + data: "not_for_loan_status", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.not_for_loan_status ? escape_str(row._strings.not_for_loan_status.str) : ""; + } + }, + { + data: "damaged_status", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.damaged_status ? escape_str(row._strings.damaged_status.str) : ""; + } + }, + { + data: "lost_status", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.lost_status ? escape_str(row._strings.lost_status.str) : ""; + } + }, + { + data: "withdrawn", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.withdrawn ? escape_str(row._strings.withdrawn.str) : ""; + } + }, + { + data: "callnumber", + searchable: true, + orderable: true, + }, + { + data: "coded_location_qualifier", + searchable: true, + orderable: true, + }, + { + data: "restricted_status", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.restricted_status ? escape_str(row._strings.restricted_status.str) : ""; + } + }, + { + data: "public_notes", + searchable: true, + orderable: true, + }, + { + data: "internal_notes", + searchable: true, + orderable: true, + }, + { + data: "holding_library_id", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.holding_library_id ? escape_str(row._strings.holding_library_id.str) : ""; + } + }, + { + data: "timestamp", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.timestamp); + } + }, + { + data: "location", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.location ? escape_str(row._strings.location.str) : ""; + } + + }, + { + data: "checked_out_date", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return $date(row.checked_out_date); + } + }, + { + data: "call_number_source", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.call_number_source ? escape_str(row._strings.call_number_source.str) : ""; + } + }, + { + data: "collection_code", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.collection_code ? escape_str(row._strings.collection_code.str) : ""; + } + }, + { + data: "materials_notes", + searchable: true, + orderable: true, + }, + { + data: "uri", + searchable: true, + orderable: true, + }, + { + data: "item_type_id", + searchable: true, + orderable: true, + render: function (data, type, row, meta) { + return row._strings.item_type_id ? escape_str(row._strings.item_type_id.str) : ""; + } + }, + { + data: "serial_issue_number", + searchable: true, + orderable: true, + }, + { + data: "copy_number", + searchable: true, + orderable: true, + }, + { + data: "inventory_number", + searchable: true, + orderable: true, + }, + ], + createdRow: function(row, data, dataIndex) { + $(row).attr('id', 'row' + data.item_id); + + let can_be_edited = !(is_independent_branches && data.home_library_id != logged_in_branchname); + + if (can_be_edited) { + $(row).addClass('editable'); + } + }, + initComplete: function( settings, json ){ + let table = settings.oInstance.api(); + table.columns().every(function(i){ + let is_empty = true; + let nodes = this.nodes(); + nodes.each((td, ii) => { + if ( $(td).html() !== '' ) { + is_empty = false; + return; + } + }); + if ( is_empty ) { + table.columns(i).visible(false); + } + }); + + }, + drawCallback: function(settings){ + + if (!show_filters && settings._iRecordsDisplay == settings._iRecordsTotal && settings._iDisplayLength >= settings._iRecordsDisplay){ + let container = $(this).parent(); + container.find(".dt-info").remove(); + container.find(".table_entries").remove(); + container.find(".dataTables_filter").remove(); + container.find(".dt_button_clear_filter").remove(); + container.find(".bottom.pager").remove(); + } + + setupRowClickHandlers(); + + }, + ...dt_options, + + }, table_settings, show_filters, default_filters, filters_options,); + + return items_table; + +} + +/* Inline edit/delete links */ +function setupRowClickHandlers() { + $("tr.editable").each(function(){ + $(this).find("td:not(:first)").off('click').on('click', function(){ + $("tr.editable").removeClass('active'); + $(this).parent().addClass('active'); + + var rowid = $(this).parent().attr("id"); + var num_rowid = rowid.replace("row",""); + $(".linktools").remove(); + var edit_link = $('<a href="/cgi-bin/koha/cataloguing/additem.pl?op=edititem&frameworkcode=' + frameworkcode + '&biblionumber=' + biblionumber + '&itemnumber=' + num_rowid + '&searchid=' + searchid + '#edititem"></a>'); + $(edit_link).text( LABEL_EDIT_ITEM ); + var delete_link = $('<a class="delete" data-item="'+num_rowid+'" href="#"></a>'); + $(delete_link).text( LABEL_DELETE_ITEM ); + var tools_node = $('<span class="linktools"></span>'); + $(tools_node).append(edit_link); + $(tools_node).append(delete_link); + $(this).append(tools_node); + }); + }); +} -- 2.39.2