This is part of #2877927: Show add widget between paragraphs and it's also one part of bigger scope #2236905: [META] Nicer UI / Icons for "Paragraph type" + "Add another Paragraph".

Problem/Motivation

After defining next steps in order to support "Add in between" functionality, we came to the conclusion that paragraphs will support adding a new paragraph to the desired position. In the first step, it will not provide UI for that and UI can be provided by sub-modules or other integrations. UI should be purely done in JavaScript and underlying paragraph functionalities should provide easy integration.

Proposed resolution

Add a new hidden field for Modal dialog mode that will pass information about the position where new paragraph should be added.

Remaining tasks

  1. add new hidden field for "Modal" mode
  2. add extensive testing for that
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mtodor created an issue. See original summary.

miro_dietiker’s picture

Looking very much forward to it. :-)

mtodor’s picture

Status: Active » Needs review
FileSize
18.94 KB

Here is a patch with the proposed solution.

Following changes are made:

  1. new hidden field is introduced with custom CSS class for easier JS integration
  2. refactored duplicate functionality, because it has overlapping functionality with adding to custom delta position (so that part is extracted to dedicated function)
  3. added some testing for new feature

Status: Needs review » Needs work

The last submitted patch, 3: 2944372_3.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mtodor’s picture

Status: Needs work » Needs review
FileSize
1.24 KB
19.03 KB

Fixed incorrect fetching of submitted delta value.

Berdir’s picture

+++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
@@ -1529,7 +1581,26 @@ class ParagraphsWidget extends WidgetBase {
+      if (is_numeric($add_more_delta)) {
+        $field_path = array_merge($submit['element']['#field_parents'], [$submit['element']['#field_name']]);
+
+        // Create place for new paragraph on specific position.
+        static::createCustomPosition(
+          $submit['widget_state'],
+          $form_state,
+          $field_path,
+          intval($add_more_delta)
+        );
+      }
+      else {
+        $submit['widget_state']['items_count']++;
+      }

Would it be easier to just set add_more_delta to $max_delta (or maybe support an explicit NULL argument) to have it added at the end?

I'm also not quite sure yet about the name. createNewPosition() sounds very generic. I'm not quite sure how to call it, maybe something more specific to what we are doing, something involving the deltas...

mtodor’s picture

@Berdir I'm not sure what you meant with

Would it be easier to just set add_more_delta to $max_delta (or maybe support an explicit NULL argument) to have it added at the end?

But I agree, that part with if/else wasn't nice, so I moved that logic into the function, since "else" part is already done in the function too and it's also nice to have all delta constrains in one place.

Here are changes for this interdiff:

  1. moved logic for setting delta position into function, so that calling of function is straightforward
  2. renamed function, that it better explains actual inner functionality (it's a bit closer, I'm still open for suggestions)
  3. refactored function, that parts of it are better organized in blocks and more readable: filter at start -> then get data -> adjust data -> set at end.
  4. adjusted tests a bit, so that some additional edge cases are covered
Berdir’s picture

  1. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -1581,26 +1590,15 @@ class ParagraphsWidget extends WidgetBase {
    +      static::defineDeltaPosition(
    +        $submit['widget_state'],
    +        $form_state,
    +        array_merge($submit['element']['#field_parents'], [$submit['element']['#field_name']]),
    +        NestedArray::getValue(
    +          $submit['element'],
    +          ['add_more', 'add_modal_form_area', 'add_more_delta', '#value']
    +        )
           );
    

    For readability, I personally prefer to keep function calls on a single line and instead move some of those long arguments to a variable on a separate line like $field_path and $add_more_delta (as before)

  2. +++ b/tests/src/FunctionalJavascript/ParagraphsExperimentalAddWidgetTest.php
    @@ -225,7 +225,8 @@ class ParagraphsExperimentalAddWidgetTest extends JavascriptTestBase {
     
    -    // Add a nested paragraph with the add widget.
    +    // Add a nested paragraph with the add widget - use negative delta.
    +    $this->getSession()->executeScript("jQuery('input.paragraph-type-add-modal-delta').last().val(-100)");
         $page->find('xpath', '//*[@name="button_add_modal"]')->click();
    

    this is to test that a negative delta is ignored and used as 0?

  3. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -1522,6 +1535,54 @@ class ParagraphsWidget extends WidgetBase {
    +  /**
    +   * Create custom position for new Paragraph.
    +   *
    +   * @param array $widget_state
    +   *   Widget state as reference, so that it can be updated.
    +   * @param \Drupal\Core\Form\FormStateInterface $form_state
    +   *   Form state.
    +   * @param array $field_path
    +   *   Path to paragraph field.
    +   * @param int|mixed $delta
    +   *   Delta position in list of paragraphs, where new paragraph will be added.
    +   */
    +  protected static function defineDeltaPosition(array &$widget_state, FormStateInterface $form_state, array $field_path, $delta) {
    

    what about prepareDeltaPosition()?

    Or: updateWidgetStateDeltas()

    And the description could be something like

    Prepares the widget state to add a new paragraph at a specific position.

    and $delta could be $new_delta or $add_$delta?

    I also think it is int|null, not mixed?

  4. +++ b/tests/src/FunctionalJavascript/ParagraphsExperimentalAddWidgetTest.php
    @@ -173,4 +165,219 @@ class ParagraphsExperimentalAddWidgetTest extends JavascriptTestBase {
    +
    +    // Add a Paragraph types.
    +    $this->addParagraphsType('test_1');
    +    $this->addParagraphsType('test_2');
    +    $this->addParagraphsType('test_3');
    +
    +    // Add a text field to the text_paragraph type.
    +    static::fieldUIAddNewField('admin/structure/paragraphs_type/test_1', 'text_1', 'Text', 'text_long', [], []);
    +    static::fieldUIAddNewField('admin/structure/paragraphs_type/test_2', 'text_2', 'Text', 'text_long', [], []);
    +    static::fieldUIAddNewField('admin/structure/paragraphs_type/test_3', 'text_3', 'Text', 'text_long', [], []);
    +
    

    do we really need 3 different text-ish paragraph types for this test? Defining them all with a different text adds quite bit of overhead.

    A nested does make sense to test nested scenarios.

  5. +++ b/tests/src/FunctionalJavascript/ParagraphsExperimentalAddWidgetTest.php
    @@ -173,4 +165,219 @@ class ParagraphsExperimentalAddWidgetTest extends JavascriptTestBase {
    +    // There should be 3 paragraphs and last one should be "test_2" type.
    +    $base_paragraphs = $page->findAll('xpath', '//*[contains(@class, "paragraph-type-label") and not(ancestor::div[contains(@class, "paragraphs-nested")])]');
    +    $this->assertEquals(3, count($base_paragraphs), 'There should be 3 paragraphs.');
    +    $this->assertEquals('test_2', $base_paragraphs[2]->getText(), 'Last paragraph should be type "test_2".');
    

    ah, so you use that to make it easier to test the expected order. Fair enough, ignore that part then.

mtodor’s picture

@Berdir tnx for review. I have adjusted everything from #8.

  1. done
  2. it's a bit more then just testing that $new_delta=0 will be used. Important part is that when we have empty paragraph list, that full path inside prepareDeltaPosition works. I have added comment.
  3. we have few sub comments here:
    1. I have used prepareDeltaPosition because we are adjusting there everything required for that task, not just widget state.
    2. comment adjusted
    3. using $new_delta
    4. I didn't change int|mixed because it can be also string (there is also test for that, search "some_text"). Point there is, with int|mixed we are telling that - int value should be used, but everything is accepted and it will work
  4. with test1,2,3 - it's easier to combine that one type is at start or list, other at end, etc. - and also ensure that order is correct
  5. exactly that
miro_dietiker’s picture

Crosspost...
Awesome, this reads so straight and shares so much code with duplicate. :-)

Agree, simple is beautiful, but the common approach would be to add unique text to each Paragraph. You could then easily check for expected text in items or on output. Is the current approach somehow easier?

Checked code in detail and the delta handling feels perfectly right. Didn't test though.

	+    foreach ($widget_state['original_deltas'] as $current_delta => $original_delta) {
+      $new_delta = $current_delta >= $delta ? $current_delta + 1 : $current_delta;
+
+++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
@@ -1522,6 +1535,54 @@ class ParagraphsWidget extends WidgetBase {
+    $original_deltas_size = count($widget_state['original_deltas']);
...
+    $new_original_deltas[$delta] = $original_deltas_size;

I think i would move this down to where the variable is used, at least after the loop.

@mtodor Will you look into consuming this new API directly with Thunderspecific JS or also try to help us bring the button to the actions first?

mtodor’s picture

@miro_dietiker - tnx for review. I have moved the line to the bottom part, nice catch.

Regarding testing: Yes - it's easier and simpler. Tests are executed faster and they also test more explicitly new functionality.
Regarding using new API: We will probably first migrate functionality we already have, more or less what is provided in #2877927: Show add widget between paragraphs.

  • miro_dietiker committed 0bad600 on 8.x-1.x authored by mtodor
    Issue #2944372 by mtodor, miro_dietiker, Berdir: Introduce hidden field...
miro_dietiker’s picture

Status: Needs review » Fixed

Awesome this is ready to unlock the next level. :-)

Thank you for the great work. Committed.

mtodor’s picture

Here is the first use of this API. https://www.drupal.org/project/paragraphs_features

Status: Fixed » Closed (fixed)

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