processwire-issues icon indicating copy to clipboard operation
processwire-issues copied to clipboard

InputfieldRepeater reloaded event triggered multiple times on wrong elements

Open Toutouwai opened this issue 1 year ago • 2 comments

There is a JavaScript reloaded event that is triggered when an inputfield is AJAX-loaded.

    • reloaded: Triggered on an .Inputfield element after it has reloaded via ajax.

In the example below I have a template containing one text field and one repeater field. Both fields have their visibility set to "Closed + Load only when opened (AJAX)". The same behaviour would apply in other situations where fields are AJAX-loaded, e.g. insider repeater items.

2024-10-02_162016

I have this hook to log the reloaded events to the browser console:

$wire->addHookAfter('ProcessPageEdit::buildFormContent', function(HookEvent $event) {
	$event->return->appendMarkup .= <<<EOT
<script>
	$(document).on('reloaded', '.InputfieldText', function(event) {
		console.log('reloaded text field');
	});
	$(document).on('reloaded', '.InputfieldRepeater', function(event) {
		console.log('reloaded repeater field');
	});
</script>
EOT;
});

In this first case I have 3 items inside the repeater field, with each item consisting of only a title field.

After opening (i.e. AJAX-loading) the text inputfield and then the repeater inputfield the console shows this: 2024-10-02_161556

The text inputfield shows the expected result - that it has been "reloaded" once. But the repeater inputfield shows that it has been "reloaded" 16 times. This isn't correct because there is only a single InputfieldRepeater and it has been AJAX-loaded once.

Next I've changed the hook JS to log the target of the reloaded event. I've also removed two of the repeater items so there is now only a single item in the repeater (to reduce the number of lines logged).

$wire->addHookAfter('ProcessPageEdit::buildFormContent', function(HookEvent $event) {
	$event->return->appendMarkup .= <<<EOT
<script>
	$(document).on('reloaded', '.InputfieldText', function(event) {
		console.log(event.target);
	});
	$(document).on('reloaded', '.InputfieldRepeater', function(event) {
		console.log(event.target);
	});
</script>
EOT;
});

2024-10-02_162358

The console shows the expected result for the text inputfield - the target is the .InputfieldText wrapping element.

As before, there are multiple reloaded events for the single repeater field, and none of targets are the .InputfieldRepeater wrapping element.

The correct result for the repeater should be a single reloaded event triggered on the .InputfieldRepeater element. Triggering the reloaded event multiple times for a single AJAX-loading event causes problems for JS code that listens for the reloaded event and should only fire the number of times the inputfield is actually reloaded (once).

Setup/Environment

  • ProcessWire version: 3.0.241

Toutouwai avatar Oct 02 '24 03:10 Toutouwai

When a repeater item is opened, every Inputfield within it gets a reloaded event, which are also seen by the InputfieldRepeaterItem, InputfieldRepeater, and so on up the tree. Is this what you are seeing?

ryancramerdesign avatar Oct 04 '24 19:10 ryancramerdesign

@ryancramerdesign, I'm not opening any repeater items in this case, just the repeater field itself.

I've traced the issue back to this section of inputfields.js

var $inputfields = $li.find('.Inputfield');
if($inputfields.length) {
	$inputfields.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
	InputfieldStates($li);	
	InputfieldRequirements($li);
	InputfieldHeaderActions($li);
	InputfieldColumnWidths();
} else {
	$li.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
	InputfieldColumnWidths();
}

I think this should be changed to:

var $inputfields = $li.find('.Inputfield');
if($inputfields.length) {
	$inputfields.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
	InputfieldStates($li);	
	InputfieldRequirements($li);
	InputfieldHeaderActions($li);
	InputfieldColumnWidths();
}
$li.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
InputfieldColumnWidths();

Because regardless of whether the li.Inputfield that is being reloaded has other .Inputfields inside it, the li.Inputfield itself has been reloaded (once) and so it ought to be possible to detect a single reloaded event on that target li.Inputfield. If other reloaded events need to be triggered for child Inputfields then those will bubble up and that's fine because they can be distinguished by checking the attributes of the event target.

The original code above was perhaps more focused on InputfieldFieldsetOpen rather than InputfieldRepeater, as there could be all kinds of inputfields inside the fieldset that need JS initialisation (whereas for InputfieldRepeater, reloaded is being triggered for the inputfields relating to unpublish/delete/sort that I don't think need any initialisation). But I'd make the same argument for that case too - somebody may have some custom JS that needs to initialise when the fieldset is opened and it ought to be possible to do that by listening for reloaded specifically on .InputfieldFieldsetOpen.

Toutouwai avatar Oct 06 '24 03:10 Toutouwai