Problem/Motivation
Paragraphs targets the wrong element ID when inserting AJAX response content, if another module has run drupal_html_id() in hook_init().
Paragraphs tries to clear $_POST['ajax_html_ids'] in its page callbacks, but it's too late. Since the selector used in the returned AJAX command matches nothing jQuery silently fails to replace/insert content when clicking "Edit", "Collapse", "Remove" or "Restore" in a Paragraphs item.
Background
We have a content type which has a recursive Paragraphs field. That is, the Paragraph bundle allowed by the field also has a Paragraphs field with the same bundle enabled.
You can make the rabbit hole as deep as you'd like...
See the test sites below for an example of the above configuration.
I think it's also possible to reproduce the issue without nested paragraphs if you have set them to be closed by default, save a node with some content, and then try to go back and edit it.
The problem can be triggered by tiny custom module consisting only of:
function tst_para_init() {
drupal_html_id('foo');
}
Proposed resolution
It's much easier to ensure that Paragraphs actually tries to replace elements using the ID currently on the client by ensuring it's passed along as the '#ajax' => array('wrapper' => 'TARGET_ID') value when the form is first generated. This means we don't need to care about which ID the re-rendered element gets, and we don't need to send a specific selector with each AJAX command, instead falling back to the specified wrapper id already on the client. With each AJAX replacement of the element, it gets a new ID. Drupal's AJAX behavior then picks up that ID when it re-attaches and uses it on the next request. This is the default behavior already in Core, so we can remove the AJAX-related menu callbacks completely and fall back to a simple callback which just fetches the element we want and passes it back fully rendered.
This is actually what Core's Field module does, and what Paragraphs also already does when adding a new paragraph widget/delta row.
Paragraphs is a bit unusual since it requires buttons inside the widget to replace the entire widget, ("Confirm Deletion" button being the extreme case since it deletes the entire widget/delta row.)
This makes it tricky when deciding which element to replace, but the only safe bet is to always replace the entire widget (all its potential delta values) on each AJAX response.
This means passing down $widget_id as generated by the multi-value part of the widget code to each individual delta.
The "Edit", "Collapse", "Remove" and "Add another Paragraph" buttons could work by replacing an element inside each delta row. But as soon as you click "Confirm Deletion" button it may try to replace a wrapper element with the wrong ID because the button was re-built on the server, but the re-built wrapping element which actually has that ID wasn't. The wrapping element on the client would still have the same ID id had when it was first built. Hence, always replace the entire widget wrapper, which also avoids adding yet another wrapper div inside the table, inside another div, inside another table, inside....
Remaining tasks
None
User interface changes
None
API changes
Removal of the paragraphs/*/ajax menu callbacks. They're not needed anymore.
Data model changes
None
Test sites:
Unpatched 7.x-1.x-dev
7.x-1.x-dev patched with #1
(Sites above will be removed without notice when deemed no longer needed.)
| Comment | File | Size | Author |
|---|---|---|---|
| #8 | paragraphs_ajax-2680101-8.patch | 13.91 KB | jstoller |
| #2 | paragraphs-ajax.2680101.1.patch | 5.8 KB | twod |
Comments
Comment #2
twodComment #3
recrit commented@TwoD, thanks! I ran into this bug and the patch fixes it.
Some questions / feedback:
Comment #4
drupa11y commentedFor me the patch didn´t fix it. Please see https://www.drupal.org/node/2780561
Therefore I tried https://www.drupal.org/node/2481627 , which didn´t help also.
Comment #5
emmanvazz commentedThis patch was helpful and fixed our issue. Thanks!
Comment #6
ducktape commentedWorks like a charm, thanks!
Comment #7
gomez_in_the_south commentedThis bug bit us as well. It was unusual in that the remove button would work correctly for some languages, but with others it would not only fail to delete the paragraph item, but would invalidate the form edit that was in progress.
Thanks for the patch, it is working well.
Comment #8
jstollerRe-rolled patch to apply to latest dev and removed the paragraphs.ajax.inc file. As @recrit points out in #3, it doesn't appear to be needed anymore.
@recrit, as for your second question, changing the array_slice length from -4 to -3 means that instead of grabbing $form['field_some_name'], we're grabbing $form['field_some_name']['und']. I'm not sure there's any case where a single paragraphs item element would have multiple languages, but it seems like a reasonable change and it seems to be working.
I'd love to have someone else double check my work, but if I don't hear any complaints I'll probably commit it to dev soon.
Comment #10
jstollerPatch committed to dev.