From f7ddbd86bb9ff8dc613f89e2ad3790529b480a88 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Fri, 3 Feb 2023 12:00:39 +0100 Subject: [PATCH] Bug 30975: Fix framework plugins not working on cloned fields/subfields Since bug 29155 (Upgrade jquery version to 3.6.0) framework plugins do not work on a cloned field/subfield. That is because we rely on jQuery's `.data('events')` to clone event handlers to the cloned elements, and that was removed in jQuery 1.8.0 (I did not confirm but it's possible it continued to work after that thanks to jQuery Migrate) It is apparently still possible to access these event handlers by using `jQuery._data(element, "events")` but here's what jQuery 1.8.0 release notes says about it: "this is not a supported public interface; the actual data structures may change incompatibly from version to version." So we should not rely on it. Instead, what this patch does is use event delegation [1]. Events are bound to a parent container, so when elements are added dynamically inside that container, we don't need to re-attach event handlers manually This patch also comes with a bit of cleanup, and introduce "breaking changes" (they are breaking changes only if you happen to have custom framework plugins): 1) 'mouseover', 'mousemove', 'keypress' events are no longer listened to 'mouseover' and 'mousemove' are not used and would trigger too much events. 'keypress' is also not used, and is deprecated 2) Event handlers now takes a single parameter that is an Event object It just makes the code a lot less complicated. 3) Event handlers do not pollute the global scope anymore [1] https://learn.jquery.com/events/event-delegation/ Test plan: - Go to every page that has a MARC editor and verify that plugins still work. This includes addbiblio.pl, additem.pl, authorities.pl, neworderempty.pl, orderreceive.pl - Test plugins that use 'focus' event (for instance barcode.pl), 'blur' event (callnumber.pl) and 'click' event (almost all the others) - Test that plugins work on cloned fields/subfields --- C4/Items.pm | 2 +- Koha/FrameworkPlugin.pm | 84 ++-------------- Koha/UI/Form/Builder/Item.pm | 1 + authorities/authorities.pl | 1 + cataloguing/value_builder/EXAMPLE.pl | 26 +---- .../prog/en/includes/html_helpers.inc | 6 +- .../prog/en/modules/acqui/neworderempty.tt | 2 +- .../prog/en/modules/acqui/orderreceive.tt | 2 +- .../en/modules/authorities/authorities.tt | 4 +- .../prog/en/modules/cataloguing/addbiblio.tt | 8 +- .../prog/en/modules/cataloguing/additem.tt | 2 +- koha-tmpl/intranet-tmpl/prog/js/additem.js | 14 --- koha-tmpl/intranet-tmpl/prog/js/cataloging.js | 97 ++++++++----------- 13 files changed, 68 insertions(+), 181 deletions(-) diff --git a/C4/Items.pm b/C4/Items.pm index de776d5dfe..94713f0dc5 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -1520,7 +1520,7 @@ sub PrepareItemrecordDisplay { my $tab= $plugin->noclick? '-1': ''; my $class= $plugin->noclick? ' disabled': ''; my $title= $plugin->noclick? 'No popup': 'Tag editor'; - $subfield_data{marc_value} = qq[...\n].$plugin->javascript; + $subfield_data{marc_value} = qq[...\n].$plugin->javascript; } else { warn $plugin->errstr; $subfield_data{marc_value} = qq(); # supply default input form diff --git a/Koha/FrameworkPlugin.pm b/Koha/FrameworkPlugin.pm index 2331bfa6b3..56aa678f82 100644 --- a/Koha/FrameworkPlugin.pm +++ b/Koha/FrameworkPlugin.pm @@ -297,94 +297,24 @@ sub _process_javascript { $script =~ s/\]*\>\s*(\/\/\\s*)?\<\/script\>//s; - my $id = $params->{id}//''; - my $bind = ''; my $clickfound = 0; - my @events = qw|click focus blur change mouseover mouseout mousedown - mouseup mousemove keydown keypress keyup|; + my @events = qw|click focus blur change mousedown mouseup keydown keyup|; foreach my $ev ( @events ) { my $scan = $ev eq 'click' && $self->{oldschool}? 'clic': $ev; - if( $script =~ /function\s+($scan\w+)\s*\(([^\)]*)\)/is ) { - my ( $bl, $sl ) = $self->_add_binding( $1, $2, $ev, $id ); - $script .= $sl; - $bind .= $bl; + if( $script =~ /function\s+($scan\w+)\s*\(/is ) { + my $function_name = $1; + $script .= sprintf('registerFrameworkPluginHandler("%s", "%s", %s);', $self->name, $ev, $function_name); $clickfound = 1 if $ev eq 'click'; } } - if( !$clickfound ) { # make buttonDot do nothing - my ( $bl ) = $self->_add_binding( 'noclick', '', 'click', $id ); - $bind .= $bl; - } $self->{noclick} = !$clickfound; - $self->{javascript}= _merge_script( $id, $script, $bind ); -} - -sub _add_binding { -# adds some jQuery code for event binding: -# $bind contains lines for the actual event binding: .click, .focus, etc. -# $script contains function definitions (if needed) - my ( $self, $fname, $pars, $ev, $id ) = @_; - my ( $bind, $script ); - my $ctl= $ev eq 'click'? 'buttonDot_'.$id: $id; - #click event applies to buttonDot - - if( $pars =~ /^(e|ev|event)$/i ) { # new style event handler assumed - $bind= qq| \$("#$ctl").$ev(\{id: '$id'\}, $fname);\n|; - $script=''; - } elsif( $fname eq 'noclick' ) { # no click: return false, no scroll - $bind= qq| \$("#$ctl").$ev(function () { return false; });\n|; - $script=''; - } else { # add real event handler calling the function found - $bind=qq| \$("#$ctl").$ev(\{id: '$id'\}, ${fname}_handler);\n|; - $script = $self->_add_handler( $ev, $fname ); - } - return ( $bind, $script ); -} - -sub _add_handler { -# adds a handler with event parameter -# event.data.id is passed to the plugin function in parameters -# for the click event we always return false to prevent scrolling - my ( $self, $ev, $fname ) = @_; - my $first= $self->_first_item_par( $ev ); - my $prefix= $ev eq 'click'? '': 'return '; - my $suffix= $ev eq 'click'? "\n return false;": ''; - return <{item_style} && $self->{oldschool} && - $event=~/focus|blur|change/ ) { - return qq/'0',/; - } - return ''; -} - -sub _merge_script { -# Combine script and event bindings, enclosed in script tags. -# The BindEvents function is added to easily repeat event binding; -# this is used in additem.js for dynamically created item blocks. - my ( $id, $script, $bind ) = @_; - chomp ($script, $bind); - return <{javascript} = < +\$(document).ready(function () { $script -function BindEvents$id() { -$bind -} -\$(document).ready(function() { - BindEvents$id(); }); -HERE +JS } =head1 AUTHOR diff --git a/Koha/UI/Form/Builder/Item.pm b/Koha/UI/Form/Builder/Item.pm index 3a478ee05e..a1100f19a6 100644 --- a/Koha/UI/Form/Builder/Item.pm +++ b/Koha/UI/Form/Builder/Item.pm @@ -330,6 +330,7 @@ sub generate_subfield_form { class => $class, nopopup => $plugin->noclick, javascript => $plugin->javascript, + plugin => $plugin->name, }; } else { diff --git a/authorities/authorities.pl b/authorities/authorities.pl index 538f7cdb7b..a924426da6 100755 --- a/authorities/authorities.pl +++ b/authorities/authorities.pl @@ -199,6 +199,7 @@ sub create_input { maxlength => $max_length, javascript => $plugin->javascript, noclick => $plugin->noclick, + plugin => $plugin->name, }; } else { # warn and supply default field warn $plugin->errstr; diff --git a/cataloguing/value_builder/EXAMPLE.pl b/cataloguing/value_builder/EXAMPLE.pl index 691e69f8e2..31319494e1 100755 --- a/cataloguing/value_builder/EXAMPLE.pl +++ b/cataloguing/value_builder/EXAMPLE.pl @@ -65,37 +65,19 @@ my $builder= sub { |; }; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc index dd8682a2b7..76bbd36ca9 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc @@ -135,11 +135,11 @@ [% IF mv.readonly %] [% ELSE %] - + [% IF ( mv.nopopup ) %] - ... + ... [% ELSE %] - ... + ... [% END %] [% UNLESS no_plugin %][%# FIXME - from batchMod-edit, jQuery is included at the end of the template and cataloguing plugins are not working in this situation %] [%- mv.javascript | $raw -%] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/neworderempty.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/neworderempty.tt index 451d55f1f3..fb5977957c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/neworderempty.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/neworderempty.tt @@ -349,7 +349,7 @@
The autoBarcode system preference is set to [% Koha.Preference('autoBarcode') | html %] and items with blank barcodes will have barcodes generated upon save to database
[% END %] -
+
[% END %][%# | html UNLESS subscriptionid %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt index 894af69294..107a135f06 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/orderreceive.tt @@ -221,7 +221,7 @@ used

[% END %] -
+
[% END %] [% ELSIF (AcqCreateItem == 'ordering') %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt index ebca79dbdf..40ddf3fcd4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt @@ -366,7 +366,7 @@ This authority is not used in any records. [% END %] -
+
[% FOREACH BIG_LOO IN BIG_LOOP %] [% IF loop.first %] @@ -558,7 +558,7 @@ [% IF mv.noclick %] ... [% ELSE %] - ... + ... [% END %] [% mv.javascript | $raw %] [% END #/IF ( mv.type == 'text1' ) %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt index 5d664a3a99..01e93b8373 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt @@ -1004,7 +1004,7 @@ function PopupMARCFieldDoc(field) { -
+
[% FOREACH BIG_LOO IN BIG_LOOP %] @@ -1137,7 +1137,7 @@ function PopupMARCFieldDoc(field) { [% END %] [% ELSIF ( mv.type == 'text_complex' ) %] - + [% mv.javascript | $raw %] [% ELSIF ( mv.type == 'hidden' ) %] @@ -1178,9 +1178,9 @@ function PopupMARCFieldDoc(field) { [% ELSE %] [% IF mv.plugin == "upload.pl" %] - Upload + Upload [% ELSE %] - Tag editor + Tag editor [% END %] [% END %] 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 38fdd8995a..f38531a60a 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt @@ -155,7 +155,7 @@
-
+
[% IF (popup) %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/additem.js b/koha-tmpl/intranet-tmpl/prog/js/additem.js index fac6b2648c..16d93976b4 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/additem.js +++ b/koha-tmpl/intranet-tmpl/prog/js/additem.js @@ -213,24 +213,10 @@ function cloneItemBlock(index, unique_item_fields, callback) { var cloneIndex = "itemblock"+random; callback(cloneIndex); } - BindPluginEvents(data); } }); } -function BindPluginEvents(data) { -// the script tag in data for plugins contains a document ready that binds -// the events for the plugin -// when we append, this code does not get executed anymore; so we do it here - var events= data.match(/BindEventstag_\d+_subfield_._\d+/g); - if ( events == null ) return; - for(var i=0; i