Problem/Motivation

Having too many Paragraphs open causes slow loading time and loss of overview.
We therefore have improved the summary so the user is aware of each Paragraph content and added the "Collapse all" button.

1) If a user creates new content, adding 10 paragraphs, all of them remain open, causing to slow down the UI with every paragraph added. (The main component for slow down is client side loading of many WYSIWYG editors.)
2) If a user edits multiple elements, the previous remain open. The Behavior tab is not yet sticky and it's a messy scroll interaction (scroll up, change tab, scroll down and search to find the right para).

Proposed resolution

Autocollapse will help in all situations.

Remaining tasks

If Autocollapse is enabled, the button "Collapse all" seems to be superfluous and should be hidden.
If a user wants to copy content from nest A to nest B, autocollapse might be annoying. However, if full hierarchy drag & drop works, this is no more needed in most of the cases.
So we need to decide if we still want to keep the "Expand all" button...

Also we might treat containers different from regular items.
We could offer an option so that autocollapse does not affect containers ever, but only leaves.
Should we collapse an open leave inside nest A when a leave inside nest B is opened?

This might change strategy for
#2846557: Change "Collapse" button in closed editing mode to "Cancel"
#2748361: Preview / Closed mode and several level of nesting...
#2829677: Implement a new render element for a collapsible button
Check and update them.

User interface changes

The experimental widget settings will change:
- Default mode is just "open" or "closed"
- New Closed mode dropdown "summary" or "preview"
- Autocollapse setting: None, All, Leaves only

The first two settings changes could also be implemented in a separate issue..

API changes

Data model changes

Comments

miro_dietiker created an issue. See original summary.

VladimirMarko’s picture

Assigned: Unassigned » VladimirMarko
miro_dietiker’s picture

Priority: Normal » Major
berdir’s picture

Some thoughts:

Despite what I said, adding it as a separate setting (with a description saying that it only makes sense in combination with edit mode closed/preview) should actually be much easier to implement, as you don't need to touch the dozen existing closed checks.

Might be enough to just implement this in the submit callbacks, similar to colapse all/edit all

VladimirMarko’s picture

Status: Active » Needs review
StatusFileSize
new10.94 KB

I left out the "Leaves only" option, as traversing the the form looking for leaves is too complicated right now.

This works, but it still needs tests.

miro_dietiker’s picture

Status: Needs review » Needs work

Your autocollapse now also resets paragraphs that are marked as deleted.
Reproduce: Click on deleted of one Paragraph (disappears), then edit on some other. Suddenly, the deleted reappears.

miro_dietiker’s picture

Quickly tested, seems to work fine.

Let's care about special container treatment in a follow-up. It can start as simple as not collapsing containers (Paragraphs with children).

Once this is in, awareness & UX is still significantly limited by these issues:
#2895565: Add warning message on collapsed container if changed child
#2893470: Collapsible items with WYSIWYG field always show as changed

VladimirMarko’s picture

StatusFileSize
new744 bytes
new11 KB

Fixed the undeleting bug. This is still missing the tests.

miro_dietiker’s picture

Status: Needs work » Needs review
miro_dietiker’s picture

Follow-up: This should interact with "Expand all" / "Collapse all" button.
Specifically, if i click "Expand all", the autocollapsing should be temporarily disabled until i click "Collapse all".

VladimirMarko’s picture

StatusFileSize
new5.07 KB
new13.1 KB

Follow-up: This should interact with "Expand all" / "Collapse all" button.

That is already implemented here.

I made the closed_mode and autocollapse properties belong to the state of the whole widget, instead of its individual items.

Duplicating paragraphs or adding new ones now closes all the others.

Fixed the widget configuration schema.

Tests are still missing.

VladimirMarko’s picture

StatusFileSize
new742 bytes
new13.14 KB

Now, there should no longer be attempts to access potentially non-existing array keys in the widget.

miro_dietiker’s picture

One new problem here is scroll persistency.

Since we change other items, the view port is likely off.
A follow-up should determine the location of the button pressed and scroll to that after ajax completion.

VladimirMarko’s picture

StatusFileSize
new24.7 KB
new26.13 KB
new38.84 KB

Created a helper function setParagraphsWidgetSettings it is both in ParagraphsTestBase and ParagraphsTestBaseTrait. In contrast to ParagraphsTestBase::setParagraphsWidgetMode, it can handle the new settings for experimental widget.

Adjusted the tests in ParagraphsExperimentalAdministrationTest, ParagraphsExperimentalInlineEntityFormTest and ParagraphsExperimentalWidgetButtonsTest to work with the new settings.

Added tests for the autocollapse functionality using the new BrowserTestBase base class.

Adjusted ParagraphsWidget::__construct to accommodate legacy settings.

The last submitted patch, 14: autocollapse_all-2896115-14-test-only.patch, failed testing. View results

berdir’s picture

Status: Needs review » Needs work
  1. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -74,6 +74,35 @@ class ParagraphsWidget extends WidgetBase {
       /**
    +   * @var array
    +   */
    +  protected $allSettingOptions;
    +
    

    missing docs.

  2. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -163,6 +203,50 @@ class ParagraphsWidget extends WidgetBase {
    +   * Get select options for a plugin setting.
    +   *
    +   * This is done to allow settingsSummary to access option labels.
    +   * Not all plugin setting are available.
    

    GetS, but it's not really getting, Returns might be better?

    referencing another method needs to use class::method() should also use as much as possible of the 80 characters.

  3. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -163,6 +203,50 @@ class ParagraphsWidget extends WidgetBase {
    +   * @param string $setting_name
    +   *   The name of the widget setting.
    +   * @return array|null
    +   *   An array of setting option usable as a value for a "#options" key.
    

    @param should list the support setting names.

    missing empty line above @return.

  4. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -639,22 +702,28 @@ class ParagraphsWidget extends WidgetBase {
    +        if ($closed_mode_setting === 'preview') {
    +          // The closed paragraph is displayed as a rendered preview.
    +          $view_builder = $entity_type_manager->getViewBuilder('paragraph');
    +
    +          $element['subform'] = [];
    +          $element['behavior_plugins'] = [];
    +          $element['preview'] = $view_builder->view($paragraphs_entity, 'preview', $paragraphs_entity->language()->getId());
    +          $element['preview']['#access'] = $paragraphs_entity->access('view');
    

    part of those lines can be moved outside of the if now to avoid duplication.

    view access check here is interesting, wondering if that should be checked for summary as well, but being able to edit and not view seems like a pretty weird use case anyway?

  5. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -1226,6 +1297,15 @@ class ParagraphsWidget extends WidgetBase {
    +    // Close all other paragraphs, if autocollapse is on.
    +    if ($widget_state['real_item_count'] > 0 && $widget_state['autocollapse'] !== 'none') {
    +      foreach ($widget_state['paragraphs'] as $delta => $value) {
    +        if ($widget_state['paragraphs'][$delta]['mode'] === 'edit') {
    +          $widget_state['paragraphs'][$delta]['mode'] = 'closed';
    +        }
    +      }
    +    }
    +
    

    this snippet exists in 3, slightly different variations, can we define a helper method for it?

  6. +++ b/src/Tests/Classic/ParagraphsTestBase.php
    @@ -203,4 +205,51 @@ abstract class ParagraphsTestBase extends WebTestBase {
    +   * @param string $content_entity_type
    +   *   (optional) Machine name of the content entity type (e.g., "node" for a
    +   *   content type, "paragraph" for a paragraphs type, etc.) that the bundle
    +   *   belongs to. Defaults to a content type.
    

    I think it's more common to refer to this just as $entity_type_id. And not sure that we need that extensive documentation on it, anoyne who uses this should know what an entity type is. Also the defaults is a bit confusing, Defaults to node is clearer than an indirect/implicit reference to content _type (which should be node_type in code).

    same for the one on the rait.

    Also, do we really need this on the base class, can't we use the trait if we have a method that needs it?

  7. +++ b/src/Tests/Experimental/ParagraphsExperimentalAdministrationTest.php
    @@ -286,8 +290,8 @@ class ParagraphsExperimentalAdministrationTest extends ParagraphsExperimentalTes
         $this->drupalPostAjaxForm(NULL, array(), "field_paragraphs_settings_edit");
    -    // Assert the 'Preview' option is selected.
    -    $this->assertOptionSelected('edit-fields-field-paragraphs-settings-edit-form-settings-edit-mode', 'preview', 'Updated value correctly.');
    +    // Assert the 'Closed' option is selected.
    +    $this->assertOptionSelected('edit-fields-field-paragraphs-settings-edit-form-settings-edit-mode', 'closed', 'Updated value correctly.');
         // Restore the value to Open for next test.
    

    should also assert that the preview closed mode is selected in that case.

  8. +++ b/src/Tests/Experimental/ParagraphsExperimentalAdministrationTest.php
    @@ -491,14 +498,20 @@ class ParagraphsExperimentalAdministrationTest extends ParagraphsExperimentalTes
         $node = $this->drupalGetNodeByTitle('choke test');
         // Attempt to edit the Paragraph.
    -    $this->drupalPostAjaxForm('node/' . $node->id() . '/edit', [], 'field_paragraphs_0_edit');
    +    $this->drupalGet('node/' . $node->id() . '/edit');
    +    // Since we have another validation error, the paragraph is by default in
    +    // the edit mode again.
    +    $this->assertFieldByName('field_paragraphs[0][subform][field_entity_reference][0][target_id]');
    +    $this->assertFieldByName('field_paragraphs[0][subform][field_entity_reference][1][target_id]');
         // Try to save with and invalid reference.
         $edit = ['field_paragraphs[0][subform][field_entity_reference][0][target_id]' => 'foo'];
    

    that looks like an unrelated behavior change?

  9. +++ b/tests/src/Functional/ParagraphsExperimentalWidgetButtonsTest.php
    @@ -0,0 +1,253 @@
    +      'administer nodes',
    

    do we really need administer nodes for this this? The trickery with ParagraphsCoreVersionUiTestTrait is only needed in case the user has this permission, otherwise it is always "Save". The permission is only needed to control status, revision, author, .. of the node.

  10. +++ b/tests/src/Functional/ParagraphsExperimentalWidgetButtonsTest.php
    @@ -0,0 +1,253 @@
    +  // @todo Move the rest of \Drupal\paragraphs\Tests\Experimental\ParagraphsExperimentalWidgetButtonsTest
    +  //   here.
    +
    

    strange position for a todo, should instead be on the class doc.

    What exactly is the other on testing that this doesn't cover yet? @todo could maybe be a bit more specific. And we should create a follow-up issue and reference it.

  11. +++ b/tests/src/Functional/ParagraphsExperimentalWidgetButtonsTest.php
    @@ -0,0 +1,253 @@
    +    $option = $this->assertSession()->optionExists('fields[field_paragraphs][type]', 'paragraphs');;
    

    ;;

  12. +++ b/tests/src/Functional/ParagraphsExperimentalWidgetButtonsTest.php
    @@ -0,0 +1,253 @@
    +    if ($mode === 'edit') {
    +      $this->assertSession()->buttonNotExists($button_prefix . '_edit');
    +      $this->assertSession()->buttonExists($button_prefix . '_collapse');
    +    }
    +    else if ($mode === 'closed') {
    +      $this->assertSession()->buttonExists($button_prefix . '_edit');
    +      $this->assertSession()->buttonNotExists($button_prefix . '_collapse');
    +    }
    

    seems like a cood case for a switch statement, would be easier to read.

VladimirMarko’s picture

Status: Needs work » Needs review
StatusFileSize
new23.81 KB
new17.26 KB
new37.99 KB

@Berdir:

1.
That variable was unused. I removed it.

4.
Added a view access check to the summary as well.

8.
Kept the behavior change, as discussed.

10.
Deleted the @todo annotation for now.

I resolved the other points.

Status: Needs review » Needs work

The last submitted patch, 17: autocollapse_all-2896115-17.patch, failed testing. View results

VladimirMarko’s picture

Status: Needs work » Needs review
StatusFileSize
new23.84 KB
new3.79 KB
new37.81 KB

Fixed the tests and simplified ParagraphsWidget::autocollapse.

The last submitted patch, 19: autocollapse_all-2896115-19-test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 19: autocollapse_all-2896115-19.patch, failed testing. View results

VladimirMarko’s picture

Status: Needs work » Needs review
StatusFileSize
new37.8 KB

Re-roll.

miro_dietiker’s picture

Status: Needs review » Fixed

So yeah, committed this... Lots of changes, great work! :-)

Created follow-ups:
#2901994: Add edit mode option to expand only paragraph types with paragraph field
#2901995: Scroll to the right position after edit / collapse

All other items seem to be processed already.

Additionally identified this consistency issue about Paragragraph UI text:
#2901996: Capitalise Paragraph(s) in the UI text

Status: Fixed » Closed (fixed)

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