This issues started as an ask for new hooks, but has been simplified to updating the widget form arrays to provide the paragraph bundle names.

hook_field_widget_WIDGET_TYPE_form_alter() can be used to alter paragraph forms using with type name paragraphs for fields using the new experimental widget or entity_reference_paragraphs for the classic inline forms.

Original suggestion:
Add new hooks

hook_form_paragraphs_subform_alter()
hook_form_paragraphs_subform_TYPE_alter()
hook_form_paragraphs_subform_WIDGET_alter()
hook_form_paragraphs_subform_WIDGET_TYPE_alter()

When trying to form alter paragraphs for an individual type of paragraph is extremely difficult having to pick through the parent forms to and to edit a single type of form has a lot of overheard.

With these 2 alter hooks you can quickly alter the subform for and not take up too much processing time.

Comments

gordon created an issue. See original summary.

gordon’s picture

Assigned: Unassigned » gordon
Status: Active » Needs review
StatusFileSize
new1.74 KB
primsi’s picture

Status: Needs review » Needs work

Thanks for the patch! Discussed this with @Berdir a bit. This could be useful.

+++ b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php
@@ -652,6 +652,8 @@ class InlineParagraphsWidget extends WidgetBase {
+        \Drupal::ModuleHandler()->alter([ 'paragraphs_component_form', 'paragraphs_component_' . $paragraphs_entity->get('type')[0]->target_id . '_form' ], $element['subform'], $form_state, $delta);

Is this the right syntax for ModuleHandler::alter? From documentation I see ::alter($type, &$data, &$context1 = NULL, &$context2 = NULL)

I think we are also missing:

  1. documentation in api.php
  2. tests (we have a paragraphs_tests.module where we can implement this hook for testing)

@Berdir also suggested to have different names for experimental and classic, so that checking for that is not needed.

seanb’s picture

StatusFileSize
new6.56 KB
new6.92 KB

+1 for this!

You can add multiple types for alters:

@param string|array $type
A string describing the type of the alterable $data. 'form', 'links', 'node_content', and so on are several examples. Alternatively can be an array, in which case hook_TYPE_alter() is invoked for each value in the array, ordered first by module, and then for each module, in the order of values in $type. For example, when Form API is using $this->alter() to execute both hook_form_alter() and hook_form_FORM_ID_alter() implementations, it passes array('form', 'form_' . $form_id) for $type.

I added small changes to the patch and added documentation. Didn't have time for tests yet (since there are a lot of hooks we want to check).

  • Changed the hook from hook_paragraphs_component_form_alter() to hook_form_paragraphs_subform_alter() since a form alter is probably what people will be looking for.
  • Added specific hooks for the classic/experimental widgets, but kept the generic ones in case you want to do something for both at the same time.

One thing I noticed is you do'nt have easy access to the paragraphs entity. Not sure if you need it, but this could be something to consider adding.

Any thoughts?

gordon’s picture

Thanks for the documentation.

I agree with you about adding the paragraph entity to the but problem being that we can't pass anymore parameters. However I do think that you might be able to get the entity from $element

seanb’s picture

We could add an example on how to get the paragraph from $element in the documentation to help people?

primsi’s picture

Yes, that would be a good idea. I think we are doing it in the widget itself multiple times, so it can be just copied from there.

gordon’s picture

Status: Needs work » Needs review
StatusFileSize
new7.38 KB
new2.59 KB

I was investigating some things with views and found that they use $form_state->set() to save the current view to the form storage for anyone to get the current view. I thought that if it is good enough for views it is good enough for paragraphs.

So now using the following in the alter().

$paragraph = $form_state->get('paragraph')

will return you the current paragraph that is being altered.

I didn't unset the paragraph at the end since there is no $form_state->del('paragraph') and the only way to do it is to get the entire storage and unset the paragraph and set it again. Not a very elegant method and in the end it is only going to hold the pointer to the last one outside the hooks and I don't think this is going to cause any memory issues.

rajab natshah’s picture

+1
Using this patch:

hook_form_paragraphs_subform_alter()
hook_form_paragraphs_subform_TYPE_alter()
hook_form_paragraphs_subform_WIDGET_alter()
hook_form_paragraphs_subform_WIDGET_TYPE_alter()
rajab natshah’s picture

Issue summary: View changes
johnchque’s picture

Status: Needs review » Needs work

Looks great, we need tests now. :)

acrosman’s picture

Issue tags: +Needs tests
sgurlt’s picture

Issue summary: View changes
StatusFileSize
new142.97 KB

Hm just to clarify, this patch will not allow me to add additional fields to the Paragraphs Form Widget itself, like for example adding another value to the "Add Mode" array.

It just allows me to change the form fields within a paragraph right?

miro_dietiker’s picture

Yeah correct. We would need this in addition. We could also pluginify the add mode... There is a lot code distributed inside Paragraphs around the add modes. I'd be happy to see this removed from the widget spaghetti and put into more specific code / plugins. But the problem is that we did not yet understand how deeply other/new add modes need to be entangled into the internal structure.

jacine’s picture

Great patch! Thank you :)

yasmeensalah’s picture

StatusFileSize
new6.85 KB

I re-rolled the last patch to the current 8.x-1.x.

mohammed j. razem’s picture

Status: Needs work » Needs review

The last submitted patch, 4: 2868155-4.patch, failed testing. View results

swilmes’s picture

Am I doing something wrong? I've added the patch and checked that its applied, but when I call mymodule_form_paragraphs_subform_alter(array &$subform, FormStateInterface &$form_state, $delta) {} and turn on my debugger, it never triggers when visiting the node add page that contains a paragraph.

letrotteur’s picture

After applying the patch, I can use hook_form_paragraphs_subform_alter() to restrict access to a field like this

$subform['field_name']['#access'] = FALSE;

but can't seem to get the visibility through ['#states'] using something like this

$subform['field_name']['#states'] = [
    'visible' => [
      ':input[name="field_another_field"]' => ['value' => 'some_value']
    ]
  ];

Am I doing something wrong?

strykaizer’s picture

Works fine here. Thx!

@letrotteur If you want states for other fields from the same paragraph entity, use following structure, works here.

$subform['field_name']['#states'] = ['visible' => [[':input[name="field_paragraphs[' . $delta . '][subform][field_another_field]"]' => [["value" => "some_value"]],]],];

Or if you dont want to hardcode the paragraphs field you can build the parent trail like this

$parents = $subform['#parents'];
$parents_input_name = array_shift($parents);
$parents_input_name .= '[' . implode('][', $parents) . ']';

$subform['field_name']['#states'] = ['visible' => [[':input[name="' . $parents_input_name . '[field_another_field]"]' => [["value" => "some_value"]],]],];

miro_dietiker’s picture

Great help, plz help to document how to handle states either in documentation pages, some txt or example code in example module or paragraphs demo.

With various nesting levels like containers, it's important to create the states key dynamically.

How about creating an issue to offer a helper to simplify handling states if not yet existing?

andysipple’s picture

Applied patch 2868155-16.patch.
I have a dropdown where I want to conditionally hide and show a image field or a text field based on what was selected from the dropdown.
The following code accomplishes that beautifully (see attached images for example):

function custom_module_form_paragraphs_subform_alter(array &$subform, FormStateInterface &$form_state, $delta) {
    $paragraph = $form_state->get('paragraph');
    $paragraph_type = $paragraph->getType();
    if($paragraph_type == 'media_with_text') {
        $subform['field_media_image']['#states'] = array(
            'visible' => array(
                'select[name="field_content_segments[' . $delta . '][subform][field_media_type_upload]"]' => array('value' => 'image')
            )
        );
        $subform['field_media_youtube_video']['#states'] = array(
            'visible' => array(
                'select[name="field_content_segments[' . $delta . '][subform][field_media_type_upload]"]' => array('value' => 'youtube')
            )
        );
    }
}

I am wondering is there a way to also make these the fields required depending on what was selected from the drop-down? Say I select "image" from the drop-down I want the image field to be required.

kosher’s picture

I would love to see this for Drupal 7.

iampuma’s picture

Just the hooks what I was looking for, thanks!

anybody’s picture

This looks quite good. Do we have enought tests for RTBC yet?

miro_dietiker’s picture

Status: Needs review » Needs work

@Anybody there is zero test coverage at all here. The test module should implement these hooks and a test needs to represent a scenario that makes sense - such as adding an action.

andysipple’s picture

I was able to solve my required problem #23. If anyone is interested I would be happy to share the code.

pavlosdan’s picture

Just tried these hooks to add validation to the subform using:

$subform['#element_validate'][] = 'my_validation';

In the alter hook the $form_state->get('paragraph')->getParagraphType()->id(); returns the expected paragraph type.

In the validation function though it returns the type of whatever the last paragraph is in the widget.

I suspect this would have something to do with what is mentioned in #8:

"I didn't unset the paragraph at the end since there is no $form_state->del('paragraph') and the only way to do it is to get the entire storage and unset the paragraph and set it again. Not a very elegant method and in the end it is only going to hold the pointer to the last one outside the hooks and I don't think this is going to cause any memory issues."

Would this hooks be the wrong hooks to use to add a validation to a specific paragraph type form?

berdir’s picture

Yes, the correct approach to add validation to a specific type is to use a validation constraint, implement hook_entity_type_alter() and add it there. See https://www.drupal.org/node/2438011, a quick google search also pointed me to https://www.sitepoint.com/drupal-8-entity-validation-and-typed-data-demo..., https://www.drupalwatchdog.com/volume-5/issue-2/introducing-drupal-8s-en..., did not review them but they look pretty good.

I see no mention of hook_field_widget_form_alter()/hook_field_widget_WIDGET_TYPE_form_alter() in this issue, which confuses me a bit.

I don't see much added benefit of this over those generic hooks. They have $context which contains the $delta, $items and $widget. It's *slightly* more complicated to check which widget type it is ($widget instanceof ParagraphsWidget) and the one thing that's pretty tricky right now is the paragraph type. But I think we can make that available simply by adding something like $elements['#paragraph_type'] in the widgets and others things if we deem them necessary (or we can add methods to the widget to get that information.

That also has the advantage that it's one level higher and doesn't just contain the subform but also behaviors, actions and anything else you might want to mess with.

If I'm missing something then please explain that, otherwise my suggestion would be to focus on making the necessary information available to that existing hook.

What's missing and where I could see a custom hook being useful is to have not just a single item form but the whole widget, including the add form elements as we for example have use cases to limit allowed nesting and I built that using some pretty tricky recursive function.

miro_dietiker’s picture

Connecting an issue about a use case where we proposed to test the hooks first.

sgurlt’s picture

Assigned: gordon » sgurlt

Hey,

we are going to work on the required tests starting from tomorrow. As soon as we got anything showable we will let you know.

berdir’s picture

the limitation of not being able to alter the whole widget was also noticed in core: #2940201: hook_field_widget_form_alter() can no longer affect the whole widget for multi-value fields

lyalyuk’s picture

Here is test for patch in #16. Please review.

johnchque’s picture

Status: Needs work » Needs review
johnchque’s picture

Let's trigger test bot.

Status: Needs review » Needs work
lyalyuk’s picture

Merge conflict is solved. Please check

jonathanshaw’s picture

Status: Needs work » Needs review

Setting to NR to trigger bot.

Status: Needs review » Needs work
lyalyuk’s picture

joined patch with tests and patch from comment#16

berdir’s picture

Thanks, but this seems to ignore #30, which points out that there are already existing hooks in core. I haven't seen a response to that yet that would explain why we would need to custom hooks, which are basically identical as far as I see.

lyalyuk’s picture

@Berdir
No I didn't skip #30. The main difference for subform hooks is performance. We have landing page nodes with 30+ paragraphs in field. And I feel our case is not the only.

hook_field_widget_form_alter() doesn't match because it is called too often.

hook_field_widget_WIDGET_TYPE_form_alter() is a little bit better in performance aspect but it still called ($delta + 1) times when you click on "edit" for separate paragraph item. Also it is called even when paragraphs subform is closed and when user collapse subform.
If we were use this hook we have to check if the widget is open.

So subform hook allows us to avoid unnecessary checks and function calls. I think these hooks are have sense for big quantity of paragraphs.

sgurlt’s picture

I also just had a look on that and I have to agree with Iyalyuk.
hook_field_widget_form_alter() just called way to often when you look on entities edit pages with multiple paragraphs and it could have a bad influence on the performance when we are talking about sites with 40+ nested paragraphs.

@Berdir: I agree with you that it would be possible to just solve it using hook_field_widget_form_alter() or hook_field_widget_WIDGET_TYPE_form_alter(), but from performance point of view I really would like to go with the additional hooks. What do you think?

jonathanshaw’s picture

Aren't there things you can't do from within a widget hook, like showing/hiding a field conditionally on another one?

berdir’s picture

Maybe there was a misunderstanding with the widget hook? Each paragraph form is also a widget and it's actually one level *up* from the subform alter. Meaning, you can implement hook_field_widget_paragraphs_form_alter(&$element, FormStateInterface $form_state, $context) and then the paragraph subform is in $element['subform'].

So I don't see where the performance argument is coming from, this issue actually adds 4 hook invocations per paragraph.

The main limitation with that is that you can't alter the add paragraph area, as that is outside of the item element, but as I said, neither do the hooks here allow that.

And for that there actually is #2940201: hook_field_widget_form_alter() can no longer affect the whole widget for multi-value fields now. If someone would be pushing that forward then I think it might still have a chance of landing in 8.5, which is not that far away anymore.

lyalyuk’s picture

@Berdit Hi! I did not have a chance check performance with profiler. My comment about performance is based on the quantity of xdebug breaks. I know that it is not the totally correct way to measure performance. Thank you very much for you detailed answer about new behavior of hook_field_widget_form_alter() in 8.5. But I have to let you know the reason why did we start the development with this hooks.

We have two modules what we really enjoy: paragraphs_browser and paragraphs_previewer. I like them both but they both extends InlineParagraphsWidget (paragraph classic) and I don't have a way to use them both. I had an idea to make Paragraph Experimental widget pluginable. From the one side it will allow us to use multiple modules for widget, but from another side it requires a lot of changes and potentially it can make future development more complex. But from the my point of view plugin system is more flexible hook system.

Can you tell me how do you see the evolution of paragraph widget (Experimental) in future?

jacine’s picture

Just wanted to say that I found this issue after struggling to implement a simple States API setting on a Paragraphs field, to toggle the visibility of one field, based on another. IIRC, I think was having a problem with the location of the widget being in a different place in node_form vs. node_edit_form. I think I tried hook_field_widget_paragraphs_form_alter() at the time without success. I'm not sure if these hooks are needed or not, but they definitely made what I was trying to accomplish a lot easier, with cleaner code. Thanks for linking that issue @Berdir, I'll check it out.

miro_dietiker’s picture

I‘d vote to add an example to Paragraphs tests or maybe demo that demonstrate widget alteration and how to use states correctly.

For easier states handling we once discussed to add generator methods to the widget so there is less guessing. Lost track of this proposal, can‘t find a dedicsted issue. Let‘s figure out if this is still needed with the example?

EDIT Can be a separate issue as well..

berdir’s picture

#2940201: hook_field_widget_form_alter() can no longer affect the whole widget for multi-value fields was just committed, so that's in 8.5 and available soon.

I still really don't understand the argument for these additional hooks.

The new hooks here are added in \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::formElement(), towards the end, which is called through \Drupal\Core\Field\WidgetBase::formSingleElement(), which then invokes the hooks that I mentioned.

That means it is called slightly more often, specifically also for paragraph items that are not in the edit mode, but I very much doubt that there is a measurable performance difference because a hook that simply checks for a non-empty $element['subform'] should be very fast. And adding completely new hooks might actually be slower because Drupal needs to check each installed module to see who implements the hook and cache that.

@Jacine: Can you share your hook implementation and what the problem was with implementing the existing hook? As I mentioned, I believe one problem with the current hook is that it is challenging to to get at certain information like the paragraph type, but that's something that we can fix with a single line $element['#paragraph_type'] = ...;.

Another thing is that if we know about specific problems/challenges is that we can document examples and how to get certain information in that hook.

jacine’s picture

Thanks @Berdir! Yes, now that I think about it, I believe the lack of access to the Paragraph bundle is probably the reason I abandoned the hook_field_widget_WIDGET_TYPE_form_alter()approach. I need it, and couldn’t (and still can’t) seem to find it within that hook, and my attempts in hook_form_alter() were futile, so I was very happy to find this patch. LOL. This is the code I have in place:

function module_form_paragraphs_subform_hero_alter(array &$subform, FormStateInterface $form_state, $delta) {
  if (!empty($subform['field_heading'])) {
    $subform['field_heading']['#states'] = [
      'visible' => [
        ':input[name*="[field_hero_page_title]"]' => [
          ['checked' => FALSE],
        ]
      ]
    ];
  }
}

I went back to the code and tried it again with hook_field_widget_WIDGET_TYPE_form_alter(), and it does work, so it's probably not related to the core bug in my case. I still need to be able to limit to a specific bundle, but as you mentioned, adding a key for that would solve my problem for this use case.

function module_field_widget_paragraphs_form_alter(&$element, FormStateInterface $form_state, $context) {
  // Still need a way to limit this to bundle type.
  if (!empty($element['subform']['field_heading'])) {
    $element['subform']['field_heading']['#states'] = [
      'visible' => [
        ':input[name*="[field_hero_page_title]"]' => [
          ['checked' => FALSE],
        ]
      ]
    ];
  }
}
AndersNielsen’s picture

hook_field_widget_WIDGET_TYPE_form_alter() seemed to do the trick for me (on 8.4.4) thanks @Berdir for that tip
+1 for adding $element['#paragraph_type'] that could be very useful

function mymodule_field_widget_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
  if(!empty($element['subform']['field_toggle_display'])) {
    $delta = $context['delta'];
    $element['subform']['field_body']['#states'] = [
      'visible' => [
        ':input[name="field_paragraphs[' . $delta . '][subform][field_toggle_display]"]' => ['value' => 'body']
      ],
    ];
    $element['subform']['field_link']['#states'] = [
      'visible' => [
        ':input[name="field_paragraphs[' . $delta . '][subform][field_toggle_display]"]' => ['value' => 'link']
      ]
    ];
  }
}
marcvangend’s picture

Re #51, #52: Not really related to the main issue, but I wanted to leave a note here for others looking to use #states in paragraphs forms.

If you're implementing hook_field_widget_WIDGET_TYPE_form_alter() for the experimental Paragraphs widget (machine name "paragrahps", not "entity_reference_paragraphs"), you may find that your states do not work. The widget adds paragraphs.admin.js to the page, which contains the line $parContent.show(); in the setUpTabs function. As a result, the form elements that had been hidden by the Form API #states, are immediately revealed again. I will update this comment with a solution when I find one.

berdir’s picture

Oh, that is a fun problem indeed. I suggest you open a separate issue, about that, I guess we should explicitly prevent showing currently hidden elements, but it does need to work when switching the tabs hand having a conditionally visible element.

marcvangend’s picture

Thanks Berdir, I was already working on a separate issue :-)
#2946856: Perspectives tabs break Form API #states

AndersNielsen’s picture

Re #53
For me the states is just an example. The point is that if you can already alter the form with the hook_field_widget_WIDGET_TYPE_form_alter() there's not much need for an extra hook for this purpose at least, unless it adds something extra to the table.

(Nice catch with the JS btw)

acrosman’s picture

Doesn't look like extra hooks are needed, but as @Jacine points out the lack of easy access to the paragraph bundle from within a hook_field_widget_paragraphs_form_alter() means having to work it out from form structure – which one can do but is annoying. While the extra hooks offered a work around to it (and it's not strictly required in any use case I can think of), it seems like adding a reference to the bundle on the passed in form widget would probably address most of the frustration that causes people to be looking for these hooks.

berdir’s picture

Fully agreed, see #50. Patches that add that are more than welcome, should basically be a one-line change.

acrosman’s picture

Assigned: sgurlt » Unassigned
Status: Needs work » Needs review
StatusFileSize
new1.04 KB

For consistency with the existing entity type reference I added to the subform section for both of the current widget types.

acrosman’s picture

Title: Add new hooks to allow easier editing of paragraph forms » Add paragraph bundle to widget forms to allow easier editing of paragraph forms
Issue summary: View changes

Updated issue title and description to reflect the current discussion.

berdir’s picture

Status: Needs review » Needs work

It should be #paragraph_type. bundle is the generic concept of entity sub-types, but our sub-type is called paragraph type, not paragraph bundle.

I don't really understand why it should be in subform. Everything depends on the type, also the actions and information in the top area, so I would prefer to have it there.

acrosman’s picture

I'd included it there is #entity_type is in the subform so it felt consistent, but I don't have stronger logic than that.

Updated patch attached.

miro_dietiker’s picture

Status: Needs review » Needs work

@acrosman After all the discussion and different approaches, the key question is WHY we need these properties added. And that's why we previously asked for tests that describe the scenario in the test module. Please readd and adjust the tests accordingly.

berdir’s picture

Didn't check the existing test coverage too closely, it should do something like this:

* Add an alter hook to our existing test module.
* Check for a specfic paragraph type, if it matches, add something to the widget, e.g. change the displayed label or something like that. That's a more realistic scenario than just a drupal_set_message() and it also serves as documentation a bit.
* Write a test that creates such a paragraph type and creates a paragraph with it, ensuring that the customization shows up.

alan d.’s picture

Simple q. Why not just pass in the entire paragraph entity? Much more useful than just the bundle info as you can react to field values, etc.

+        '#paragraphs_entity' => $paragraphs_entity,
function hook_field_widget_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
  /* @var \Drupal\paragraphs\Entity\Paragraph $p */
  $p = $element['#paragraphs_entity'];
  $bundle = $p->bundle();
....
}
alan d.’s picture

StatusFileSize
new27.83 KB

And in response to Miro, just makes life easier to alter accordingly. Many ways to do this, but this seems the cleanest & independent of the theming system.

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
function MODULE_field_widget_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
  if ($element['#paragraphs_entity']->bundle() == 'school_education') {
    $element['#attached']['library'][] = 'MODULE/inline-paragraph-items';
    $element['#attributes']['class'][] = 'MODULE-inline-paragraph-items';
    $element['top']['paragraph_type_title']['#access'] = FALSE;
    foreach (Element::children($element['subform']) as $child) {
      // Move titles underneath.
      if (isset($element['subform'][$child]['widget'][0])) {
        foreach (Element::children($element['subform'][$child]['widget'][0]) as $value_key) {
          $element['subform'][$child]['widget'][0][$value_key]['#description'] = $element['subform'][$child]['widget'][0][$value_key]['#title'];
          $element['subform'][$child]['widget'][0][$value_key]['#title_display'] = 'none';
        }
      }
    }
  }
}

jopdebeeck’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests +Added test needs review
StatusFileSize
new3.63 KB

Re #64
I've added a test for patch #62, it puts an alter hook in paragraphs_test.module that changes the title of a text field added to a certain paragraph_type.

I've put the test under the Experimental folder, I wasn't quite sure where to put it otherwise.

alan d.’s picture

Repeating #62, passing objects is the norm in D8 core, any valid reason not to? Generally in core, the only places passing around string references like this tend to be there to help themers. :)

berdir’s picture

Because most of the time, the only thing that is needed is the bundle/paragraph type ID, not the object. If needed, it can be loaded easily with ParagraphsType::load() to e.g. load a third party setting.

This is a form structure, which means it gets serialized into the form cache, so we shouldn't be adding additional objects unless there is a good reason to do so.

alan d.’s picture

I wonder if having 2 references to the same object does double the caching. Anyways, if no object, maybe including the id/rid to complete the full context to the actual entity being rendered. :)

Of the 3 recent usages of hook_field_widget_WIDGET_TYPE_form_alter(), 2 reacted to the properties on the P entity proper, the type and field values.

This seems appears to be the easiest way to get the P entity proper, albeit it feels highly fragile & was the reason I came across this thread:

  if (!empty($context['items']) && !$context['items']->isEmpty()) {
    $p = Paragraph::load($context['items']->get(0)->target_id);
  }
marcoscano’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Added test needs review
  1. +++ b/src/Tests/Experimental/ParagraphsExperimentalAlterByTypeTest.php
    @@ -0,0 +1,40 @@
    +    // Test the hook_field_widget_WIDGET_TYPE_form_alter hook. Which should alter the title of the text_field of our paragraph.
    

    Line spans over 80chars.
    Maybe the wording could also be clearer? For example something such as
    "Check that the alteration performed by paragraphs_test_field_widget_entity_reference_paragraphs_form_alter() works."

  2. +++ b/src/Tests/Experimental/ParagraphsExperimentalAlterByTypeTest.php
    @@ -0,0 +1,40 @@
    \ No newline at end of file
    

    Missing empty newline.

  3. +++ b/tests/modules/paragraphs_test/paragraphs_test.module
    @@ -41,3 +41,12 @@ function paragraphs_test_paragraph_view(array &$build, ParagraphInterface $entit
    \ No newline at end of file
    

    Missing empty newline.

I've tested it manually and it works as expected. Noted here some minor CS adjustments, that could be fixed on commit. I believe this is good to go.

miro_dietiker’s picture

Status: Reviewed & tested by the community » Fixed

Committed this with some comment alterations.

Is there anything missing for the use case to cover?
Please either create small follow-ups or a meta task to discuss the goal as a whole.
Most importantly, we need an exact use cases to extend test coverage as a reference.

I also created an issue to discuss combined extensibility by example: #2972223: Allow combined extension by other contrib

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

themodularlab’s picture

FYI, Unless I just missed a comment mentioning it, it does not look like any of the new hooks (hook_form_paragraphs_subform_alter(), hook_form_paragraphs_subform_TYPE_alter(), hook_form_paragraphs_subform_WIDGET_alter()
hook_form_paragraphs_subform_WIDGET_TYPE_alter()) made it into version 1.3 despite being mentioned as one of the added features of 1.3.

berdir’s picture

This issue is in 8.x-1.3 but unfortunately the issue summary was never updated. The only thing that was done here is ensure the paragraph type is available for the existing widget alter hooks which can do exactly the same and more as the suggested hooks. See the test implementation in the latest patch/commit.

themodularlab’s picture

Ah okay. That makes sense. Thank you for clarifying.

lily.yan’s picture

I have a dropdown where I want to conditional hide and show event with an end date/time and event without an end date/time based on what was selected from the dropdown.
After applied patch 2868155-16.patch and use the following code to accomplish that successfully.

function custom_module_form_paragraphs_subform_alter(array &$subform, FormStateInterface &$form_state, $delta) {

  $paragraph = $form_state->get('paragraph');
  $paragraph_type = $paragraph->getType();

  if($paragraph_type == 'events') {

    $subform['field_event_dates']['#states'] = array(
      'visible' => array(
        'select[name="[field_events][' . $delta . '][subform][field_event_type]"]' => array('value' => 'event with an end date/time')
      )
    );
    $subform['field_event_date_time']['#states'] = array(
      'visible' => array(
        'select[name="[field_events][' . $delta . '][subform][field_event_type]"]' => array('value' => 'event without an end date/time')
      )
    );
  }
}
matthieuscarset’s picture

I am not sure to understand.

Are these hooks available in 1.3?

In #76 it says to look into the test module but - looking into it - I can not find any example of implementation. There is only "widget-related" hook.

Are we still supposed to apply 2868155-16.patch in order to use hook_form_paragraphs_subform_alter()?
Or what am I missing?

Thanks!

spurlos’s picture

Custom hooks were discarded in favor of core hook_field_widget_WIDGET_TYPE_form_alter(). Check paragraphs_test.module line 49 for implementation reference.

berdir’s picture

See #76. There is no need for those additional hooks as there is nothing that can be done with those that isn't possible with the already existing default alter hooks that exist:

+/**
+ * Implements hook_field_widget_WIDGET_TYPE_form_alter().
+ */
+function paragraphs_test_field_widget_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
+  if ($element['#paragraph_type'] == 'altered_paragraph') {
+    $element['subform']['field_text']['widget'][0]['#title'] = 'Altered title';
+  }
+}
rajab natshah’s picture

kevgrob’s picture

Originally I had used the added hook_form_paragraphs_subform_alter() successfully, however, I'm in the process of modifying my code to use hook_field_widget_WIDGET_TYPE_form_alter() instead. The previous hook, allowed me to retrieve the current paragraph using $form_state->get('paragraph'), which now returns NULL for everything; In my case, I do need to be able to check the current paragraph entity for a certain field and its value and based on that, modify the paragraph type title found in $element. Does anyone have any suggestions on accomplishing this with the new hook?

I would rather not take the route of adding a new key to the $element array for $paragraphs_entity. I've tried looking through the $element array, particularly $element['subform']['#process'], but is there a way to retrieve an entity or its values through an EntityFormDisplay, or is there another way someone knows to retrieve the field values, short of having to traverse the entire form?

Thanks in advance.

alan d.’s picture

I went with a custom hack to implement this requirement as per #65, along with cweagans/composer-patches ;)

https://cgit.drupalcode.org/paragraphs/commit/?id=b04d2ac

src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php
src/Plugin/Field/FieldWidget/ParagraphsWidget.php

Where you have '#paragraph_type' inserted, also add

'#paragraphs_entity' => $paragraphs_entity,

Note. No one else seems to have any interest in this and one of the maintainers was against the idea. There is a large caching overhead apparently.

Post back if you find a better way though, always nice not to have to hack contrib. ;)

joshua.boltz’s picture

Probably not directly related to this, but asking because I may be able to leverage some of this with my use case.

Use case:
- select field with options that map over to paragraph bundle machine names
- field_components entity reference revisions field with target bundles of multiple paragraph types
- when the select list value is selected/changed, ONLY the paragraph bundle that maps to the selected select value should have its button/option displayed in the widget. Ideally this would be triggered by an #ajax call on the select list to update the paragraphs widget subform items.
- because the ERR field could have multiple target bundles, we want to limit which one is shown based on the select list.

johnpitcairn’s picture

@joshua.boltz: I suspect a lot of sites have exactly that use-case. We have a "page type" select field (basically because the client is incapable of choosing the correct content type from the outset, so we need to be able to switch), and I'd love to be able to dynamically limit the paragraph bundles available in an ERR filed based on that selection (and optionally remove/hide existing invalid paragraph bundes after selection change). Sounds like a lot of work ;-)

joshua.boltz’s picture

I’ve gotten it pretty close to doing what I need with an #ajax callback on the select list in an overridden ->form() method in my subclass. I’m also overriding the ->buildButtonsAddMode() method to change the $options based on that select list value, that gets updated from form state.

The issue I think I’m hitting is around form state or widget state, because even though xdebug shows the $options containing what I need, which is a single paragraph machine name, the paragraph button for it never gets added/changed/swapped.

jwilson3’s picture

Similar to #82, I've refactored the subform hooks to use hook_field_widget_WIDGET_TYPE_form_alter as was finally decided by this issue in a contrib module I maintain. Since I had already implemented more than one subform handler for different paragraph types, I decided to just rename the subform alter functions to something straightforward, and use a switch statement to call those functions. Maybe someone could find it useful as a guide. https://cgit.drupalcode.org/iu_paragraphs/commit/?id=278bbd8

leisurman’s picture

I was able to add Conditional Fields in Paragraphs using this article https://agaric.coop/blog/conditional-fields-paragraphs-using-javascript-...

But it controls and hides only 1 field. I need to control multiple fields from 1 select text list. I added multiple fields to a variable and created a for loop. The select list has 3 options

3row|3row
2row|2row
1row|1row

The custom module is below. When it runs. The last for loop is the only one that works. The first 2 select list options do not work. Do I need to add a field value check before each for loop? My module name is conditional_fields.

          <?php

          use Drupal\node\NodeInterface;
          use Drupal\node\NodeForm;
          use Drupal\Core\Form\FormStateInterface;
          use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
          use Drupal\paragraphs\ParagraphInterface;
          use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;
          use Drupal\paragraphs\Entity\Paragraph;

          /*
          * Implements hook_field_widget_form_alter().
          */
          function conditional_fields_field_widget_paragraphs_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {

          $field_definition = $context['items']->getFieldDefinition();
          $paragraph_entity_reference_field_name = $field_definition->getName();

          // Determine the entity reference field you are targeting
          if ($paragraph_entity_reference_field_name == 'field_topic_paragraphs') {
            /** @see \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget::formElement() */
            $widget_state = \Drupal\Core\Field\WidgetBase::getWidgetState($element['#field_parents'], $paragraph_entity_reference_field_name, $form_state);

            /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
            $paragraph_instance = $widget_state['paragraphs'][$element['#delta']]['entity'];
            $paragraph_type = $paragraph_instance->bundle();

            // Determine which paragraph type is being embedded
            if ($paragraph_type == 'teaser_content') {
              $dependee_field_name = 'field_paragraph_number_of_rows';
              $selector = sprintf('select[name="%s[%d][subform][%s]"]', $paragraph_entity_reference_field_name, $element['#delta'], $dependee_field_name);

                // Controlled fields for 1 rows
                $fields_to_control1 = ['field_description_col_5','field_link_col_5','field_description_col_6','field_link_col_6','field_description_col_7','field_link_col_7','field_description_col_8','field_link_col_8','field_description_col_9','field_link_col_9','field_description_col_10','field_link_col_10','field_description_col_11','field_link_col_11','field_description_col_12','field_link_col_12','field_description_col_13','field_link_col_13','field_description_col_14','field_link_col_14','field_description_col_15','field_link_col_15','field_description_col_16','field_link_col_16'];

                // Controlled fields for 2 rows
                $fields_to_control2 = ['field_description_col_9','field_link_col_9','field_description_col_10','field_link_col_10','field_description_col_11','field_link_col_11','field_description_col_12','field_link_col_12','field_description_col_13','field_link_col_13','field_description_col_14','field_link_col_14','field_description_col_15','field_link_col_15','field_description_col_16','field_link_col_16'];

                // Controlled fields for 3 rows
                $fields_to_control3 = ['field_description_col_13','field_link_col_13','field_description_col_14','field_link_col_14','field_description_col_15','field_link_col_15','field_description_col_16','field_link_col_16'];


                    // Set the state of the controlled fields for 3 rows
                    foreach($fields_to_control3 as $field){
                      $element['subform'][$field]['#states'] = [
                               'invisible' => [
                                 $selector => ['value' => '3row'],
                             ],
                          ];
                    }

                    // Set the state of the controlled fields for 2 rows
                    foreach($fields_to_control2 as $field){
                      $element['subform'][$field]['#states'] = [
                               'invisible' => [
                                 $selector => ['value' => '2row'],
                             ],
                          ];
                    }

                    // Set the state of the controlled fields for 1 rows
                    foreach($fields_to_control1 as $field){
                      $element['subform'][$field]['#states'] = [
                               'invisible' => [
                                 $selector => ['value' => '1row'],
                             ],
                          ];
                    }

                    // For the row option select list, remove the option -none- and replace it with - 4 Rows -
                    unset($element['subform']['field_paragraph_number_of_rows']['widget']['#options']['_none']);
                    $element['subform']['field_paragraph_number_of_rows']['widget']['#empty_option']  = t('- 4 Rows -');
              }
            }
          }
leisurman’s picture

I found help here. https://drupal.stackexchange.com/questions/7980/multiple-values-to-trigg....
I was able to add an array like this:

foreach($fields_to_control3 as $field){
              $element['subform'][$field]['#states'] = [
                       'invisible' => [
                         $selector => array(
                           array( 'value' => '3row'),
                            array('value' => '2row'),
                         ),
                     ],
                  ];
            }
jacine’s picture

Just wanted to provide a quick note for anyone coming across this, that is still having issues. hook_field_widget_WIDGET_TYPE_form_alter() is definitely working now (thank you!!!), but you may run into "weight" issues.

In my case, I have a very simple custom module, that implements this hook, but the hook was not firing. I believe the reason for this has to do with module installation order. For example, in core.extension.yml, you will notice that Paragraphs module currently installs with a weight of 11.

module:
  paragraphs: 11
  my_custom_module: 20

Increasing the weight of my custom module to one that is higher than Paragraphs fixed the issue I was having, and ensured my hook implementation ran properly.

🙂

thirstysix’s picture

Alter the Paragraph form fields.

#patch 2868155-16.patch is working fine (new hooks) with 8.x-1.12
In Drupal 9

use Drupal\Core\Form\FormStateInterface;

function hook_form_paragraphs_subform_alter(array &$subform, FormStateInterface $form_state, $delta) {

  $paragraph = $form_state->get('paragraph');
  $paragraph_type = $paragraph->getType();

  if($paragraph_type == 'content') {
    $subform['FIELD_NAME']['widget'][0]['#prefix'] = '<div class="visually-hidden">';
    $subform['FIELD_NAME']['widget'][0]['#suffix'] = '</div>';
    $subform['FIELD_NAME']['widget'][0]['value']['#default_value'] = 'footer';
  }

}

Version 8.x-1.x-dev isn't working for me.

vistree’s picture

Hi, I use hook_field_widget_WIDGET_TYPE_form_alter() to change the values of a paragraphs select list field.
This works fine when I create a new node page - but when editing an existing node, it does not apply. It seems, that the hook is called to late. The node page seems already to be loaded before the hook is called. When I add a new paragraph to the node, then the new AND the existing paragraphs are updated and get the custom select list applied.
Any ideas how to solve this? I already tried what @Jacine suggested in https://www.drupal.org/project/paragraphs/issues/2868155#comment-13385810
I changed the weight of my custom module to be bigger then the weight of the paragraphs module, ran drush cim - but nothing changes here.

arnaud-brugnon’s picture

Great news

https://www.drupal.org/node/3180429

hook_field_widget_WIDGET_TYPE_form_alter no longer exist.
Use hook_field_widget_single_element_form_alter instead

alberto56’s picture

Here is my experiencing updating paragraphs 1.17 (with the patch at #16) to 1.19 (at the time of this writing the latest version of Paragraphs) on Drupal 10.5.1. This is purely my own experience and I am probably missing something, but I'd like to explain why I have decided to keep the patch at #16 of this issue, even though the issue is purportedly fixed as of May 18, 2018, as per #73.

When I inherited the site with paragraphs 1.17 with the patch at #16, one of the custom modules was implementing a hook like this:

/**
 * Implements hook_form_paragraphs_subform_TYPE_alter().
 */
function MY_MODULE_form_paragraphs_subform_PARAGRAPH_TYPE_alter(array &$subform, FormStateInterface $form_state, $delta) {
  $subform['description']['#markup'] = 'Some markup';
}

This was causing "some markup" to appear when I visited a widget controlling PARAGRAPH TYPE.

Because this issue #2868155 is marked as fixed, I updated paragraphs to 1.19 and did not patch it. I examined the code at https://git.drupalcode.org/project/paragraphs/-/blob/8.x-1.x/tests/modul... and found that the test implements

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
function paragraphs_test_field_widget_single_element_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
  if ($element['#paragraph_type'] == 'altered_paragraph') {
    $element['subform']['field_text']['widget'][0]['#title'] = 'Altered title';
  }
}

However, as mentioned by @arnaud-brugnon in #94, according to https://www.drupal.org/node/3180429, hook_field_widget_WIDGET_TYPE_form_alter() has been "marked as deprecated and will be removed in Drupal 10.".

I am assuming, therefore, that I cannot hook_field_widget_WIDGET_TYPE_form_alter().

https://www.drupal.org/node/3180429 states that:

The new field widget hooks hook_field_widget_single_element_form_alter (hook_field_widget_single_element_WIDGET_TYPE_form_alter) should be used instead of hook_field_widget_form_alter (hook_field_widget_WIDGET_TYPE_form_alter).

However when I try to replace my existing hook, MY_MODULE_form_paragraphs_subform_PARAGRAPH_TYPE_alter() for Paragraphs 1.17 patched with #16, with MY_MODULE_field_widget_single_element_form_alter() for Paragraphs 1.19 unpatched as described in https://api.drupal.org/api/drupal/core%21modules%21field%21field.api.php..., I am unable to determine the paragraph type (I'm sure there is a way, I just can't figure it out):

I can determine that the widget type is entity_reference because $field_definition->getType() == 'entity_reference', however it's not clear to me how to determine the paragraph type in order to alter the form for that paragraph.

In my tests, applying the patch at #16 to Paragraphs 1.19 makes it so I can continue to use hook_form_paragraphs_subform_TYPE_alter() as I could with Paragraphs 1.17 patched with #16.