Overview

Quoting \Drupal\experience_builder\SdcPropToFieldTypePropMatcher::iterateJsonSchema():

…
      throw new \LogicException('Support for "array" props is not yet implemented.');
…

Proposed resolution

Support it:

  1. at the prop expression level
  2. must be able to compute a StaticPropSource
  3. must be able to match against field instances in a DynamicPropSource, to avoid accumulating a gap/technical debt for #3455629: [PP-1] [META] 7. Content Templates — aka "default layouts" — affects the tree+props data model
  4. must allow existing all-props SDC to continue to work, and its test coverage to still pass
  5. must come with solid test coverage, proving type: array works with both scalars (sparkline SDC) and type: objects (image-gallery SDC)

Out-of-scope: fully working widgets for StaticPropSources — see #28 for how it for example only partially works for MediaLibraryWidget for the new image-gallery test SDC. #3517868: Add e2e tests for multi-value textfield widget in page data form might help. But #3515563: [META] Expand support to *all* SDC prop shapes, and *all* core field widgets is the one whose scope it is to get all these working.

User interface changes

sparkline test SDC

powered by:

props:
  type: object
  required:
    - data
  properties:
    data:
      type: array
      title: Sparkline data
      maxItems: 100
      items:
        type: integer
        minimum: -100
        maximum: 100
      examples:
        - [0, 10, 20, 30, -40, -50, 5, 7, 9]
image-gallery test SDC

powered by

props:
  type: object
  required:
    - images
  properties:
    caption:
      type: string
      title: Caption
      description: The image gallery caption
    images:
      title: 'Images for gallery'
      type: array
      items:
        $ref: json-schema-definitions://experience_builder.module/image
        # @todo `type: object` should not be necessary, it's because \Drupal\sdc\Component\ComponentValidator::getClassProps() does not yet support $ref
        type: object
      examples:
        - - src: gracie.jpg
            alt: 'A good dog'
            width: 601
            height: 402
          - src: gracie.jpg
            alt: 'Still a good dog'
            width: 601
            height: 402
          - src: gracie.jpg
            alt: 'The BEST dog!'
            width: 601
            height: 402

… with a partially working widget too, see video in #28.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

kristen pol’s picture

To be clear, this is so you can have a multivalue prop? Like multiple links or tags or buttons?

wim leers’s picture

kristen pol’s picture

Gotcha 👍 thanks for the clarification

wim leers’s picture

Title: [later phase] Support matching `{type: array, …}` prop shapes » [needs design] [later phase] Support matching `{type: array, …}` prop shapes
Issue tags: +Needs design
wim leers’s picture

Title: [needs design] [later phase] Support matching `{type: array, …}` prop shapes » Support *generating* (not matching) field storage definitions for `{type: array, …}` prop shapes
Version: » 0.x-dev
Component: Page builder » Shape matching
Assigned: Unassigned » wim leers
Status: Postponed » Active
Related issues: +#3461499: Support complex SDC prop shapes: introduce (Storable)PropShape to compute field type storage settings, +#3440578: [PP-2] JSON-based data storage proposal for component-based page building

While this does need design, I think we can lay the shape matching foundations. A conversation with @catch about #3440578: [PP-2] JSON-based data storage proposal for component-based page building yesterday made me realize that:

  1. this is more within reach since #3461499: Support complex SDC prop shapes: introduce (Storable)PropShape to compute field type storage settings than I thought
  2. having this working for at least type: array, items: integer and type: array, items: string, maxItems: 2 would actually help the discussion over at #3440578

wim leers changed the visibility of the branch 3467870-support-generating-not to hidden.

wim leers credited catch.

wim leers’s picture

Status: Active » Needs work

I've made progress today on updating FieldTypePropExpression, StaticPropSource and Evaluator to support type: array.

How?

By changing StaticPropSource to not be required to contain a FieldItemInterface (i.e. a single field delta), but instead allowing a FieldItemListInterface too (i.e. multiple field deltas).

It's too unorganized right now to push; expect an update tomorrow 😊

Thanks @catch for nudging me in this direction 😄

wim leers’s picture

Just pushed initial test coverage in PropSourceTest::testStaticPropSource(), to test it with multiple cardinality:

    // A simple (expression targeting a simple prop) array example (with
    // cardinality specified, rather than the default of `cardinality=1`).
    $simple_array_example = StaticPropSource::parse([
      'sourceType' => 'static:field_item:integer',
      'sourceTypeSettings' => [
        'storage' => [
          'cardinality' => 5,
        ],
      ],
      'value' => [
        20,
        06,
        1,
        88,
        92,
      ],
      'expression' => 'ℹ︎integer␟value',
    ]);

It should now fail like this:

1) Drupal\Tests\experience_builder\Kernel\PropSourceTest::testStaticPropSource
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'{"sourceType":"static:field_item:integer","value":[20,6,1,88,92],"expression":"ℹ︎integer␟value","sourceTypeSettings":{"storage":{"cardinality":5}}}'
+'{"sourceType":"static:field_item:integer","value":null,"expression":"ℹ︎integer␟value","sourceTypeSettings":{"storage":{"cardinality":5}}}'

… because StaticPropSource currently ignores cardinality — it always just constructs a single FieldItemInterface object rather than a list when cardinality >=1.

wim leers’s picture

Yay, from:

1) Drupal\Tests\experience_builder\Kernel\HookStoragePropAlterTest::testPropShapesYieldWorkingStaticPropSources
Sample value 88 generated by field type integer for type=array&items[type]=integer is invalid!
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
-Array &0 ()
+Array &0 (
+    0 => Array &1 (
+        'property' => 'ds032jpb'
+        'pointer' => '/ds032jpb'
+        'message' => 'Integer value found, but an array is required'
+        'constraint' => 'type'
+        'context' => 1
+    )
+)

(IOW: a single integer is passed, when an array of integers is needed)

to … that test passing 🤓

More coming.

wim leers’s picture

I was chasing a heisenbug for a few hours today. I then eventually found the root cause.

It happens only for a list_string field type with one of its allowed values the empty string:

        [
          'allowed_values' => [
            ['value' => '', 'label' => ''],
            ['value' => '_blank', 'label' => '_blank'],
          ],
        ]

… because somewhere '' gets cast to NULL 😅

XB's tests generate a random value to verify the static prop source works end-to-end, and due to the changes in this MR (introducing the use of FieldItemList and hence calling FieldItemList::isEmpty(), which we didn't call before), this:

now resulted in

which is obviously wrong, but count() works fine:

… this is valid configuration for the list_string field type: its config schema type field.storage_settings.list_string does not forbid this.

But the UI does forbid it:

which causes:

So probably a fixed ListStringItem::isEmpty() that is more strict than the inherited \Drupal\options\Plugin\Field\FieldType\ListItemBase::isEmpty()'s implementation:

  public function isEmpty() {
    return empty($this->value) && (string) $this->value !== '0';
  }

… would not even be accepted by Drupal core maintainers! 😬

Unfortunately, I lost hours today because of this. If there was config schema validation in place, this would've been sorted out in minutes. 😭

So: not filing a Drupal core issue, but I am creating one for Experience Builder to disallow '' as an enum value, so that'd be a reason to refuse to support such an SDC ← I can't justify that, because it's a valid use case for SDC, and we can make it work.

wim leers’s picture

To make #13 discoverable.

wim leers’s picture

Title: Support *generating* (not matching) field storage definitions for `{type: array, …}` prop shapes » Support `{type: array, …}` prop shapes

I actually think that matching field instances should be possible too, so broadening the issue title/scope 🤓

wim leers’s picture

StatusFileSize
new146.38 KB

type: array breakthrough — it’s starting to work end-to-end! 😄

Soon: 🖼️🖼️🖼️🖼️ aka image galleries!

(Note that this currently requires removing the logic in \Drupal\experience_builder\Controller\SdcController::getComponentsList() to determine the dynamic_prop_source_candidates for each SDC.)

wim leers’s picture

99% of remaining test failures are due to not yet supporting finding DynamicPropSources (aka existing entity field instances) for the type: array prop shape.

That's why I widened the scope in #15 — because I first thought it'd be an order of magnitude harder than supporting StaticPropSources (which is the only thing the XB UI currently supports). But it'd be a shame to let that part "fall behind", so trying to tackle this while my head is in this space 😊

wim leers’s picture

1% of remaining test failures are in the Cypress E2E tests, which makes sense: the Redux integration has only been built with single-cardinality props in mind, that's changing here.

IOW: having this in also empowers #3463842: [META] Redux sync on ALL prop types, not just ones with a single [value] property to design a more complete solution.

wim leers’s picture

tedbow’s picture

Assigned: wim leers » tedbow

going to review

tedbow’s picture

Assigned: tedbow » wim leers

@Wim Leers the direction looks good to me!

wim leers’s picture

wim leers’s picture

Issue tags: +stable blocker
wim leers’s picture

Priority: Major » Critical
geek-merlin’s picture

@wim: Can you clarify: Is "array" a dict, a list, or any of it? PHP lenience...

wim leers’s picture

~90% done now. To be continued tomorrow.

wim leers’s picture

StatusFileSize
new2.78 KB
new92.08 KB

Good news — previews are now working! For example, for the Sparkline test SDC:

powered by:

props:
  type: object
  required:
    - data
  properties:
    data:
      type: array
      title: Sparkline data
      maxItems: 100
      items:
        type: integer
        minimum: -100
        maximum: 100
      examples:
        - [0, 10, 20, 30, -40, -50, 5, 7, 9]

And, the one everyone has been waiting for: image galleries … found and fixed a pre-existing bug thanks to this work: #3522673: Previews of SDCs with default image props broken. With it, I get:

powered by

props:
  type: object
  required:
    - images
  properties:
    caption:
      type: string
      title: Caption
      description: The image gallery caption
    images:
      title: 'Images for gallery'
      type: array
      items:
        $ref: json-schema-definitions://experience_builder.module/image
        # @todo `type: object` should not be necessary, it's because \Drupal\sdc\Component\ComponentValidator::getClassProps() does not yet support $ref
        type: object
      examples:
        - - src: gracie.jpg
            alt: 'A good dog'
            width: 601
            height: 402
          - src: gracie.jpg
            alt: 'Still a good dog'
            width: 601
            height: 402
          - src: gracie.jpg
            alt: 'The BEST dog!'
            width: 601
            height: 402
wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new42.88 MB

Soon: 🖼️🖼️🖼️🖼️ aka image galleries!

— me in #16, ~7 months ago

Here we go! 🚀

(It doesn't work completely yet — only the first selected image media item is rendered due to the mediaSelection client-side transform, and removing the last selected media item also doesn't work yet. But those are out-of-scope to tackle here.)

wim leers’s picture

Assigned: wim leers » Unassigned
Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs design
Parent issue: » #3520449: [META] Production-ready data storage

Removing the Needs design tag, because extracted the relevant part into a new issue: #3522718: [later phase] [needs design] UX for associating mismatched cardinality field instance (too little or too much) with a higher cardinality SDC prop (e.g. `type: array, maxItems: 5`) or lower cardinality (e.g. `type: string`).

This MR removes 3 @todos from the codebase, all new @todos are for existing issues.

Self-RTBC'ing because @tedbow already approved the direction in #21. I've applied thorough self-review. And I desperately need this to make progress on #3520449: [META] Production-ready data storage! 😅 Even if imperfect (which it surely will prove to be), this represents a big step forward, one we've been postponing for far too long.

  • wim leers committed 754f07df on 0.x
    Issue #3467870 by wim leers, tedbow, catch: Support `{type: array, …}`...

Status: Fixed » Closed (fixed)

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

larowlan’s picture

FYI this no longer works anymore.
There was no e2e tests added here and in manual testing:

* sparkline never shows the edit form, it just spins
* image gallery will render with the 3 example values, but as soon as you edit it, it will only ever show the first item selected (even if there are multiple images selected)

Testing with a simple multi-value string shows the same result, only ever the first value is saved.

wim leers’s picture

  1. FYI this no longer works anymore.
    There was no e2e tests added here and in manual testing:

    e2e tests were intentionally out of scope, this was purely about shape matching infra. The field widget/editing part of this never worked, see #16.

  2. Testing with a simple multi-value string shows the same result, only ever the first value is saved.

    Correct, that's why I wrote this in #28:

    (It doesn't work completely yet — only the first selected image media item is rendered due to the mediaSelection client-side transform, and removing the last selected media item also doesn't work yet. But those are out-of-scope to tackle here.)

That's why I posted a comment on #3463842 — see #3463842-12: [META] Redux sync on ALL prop types, not just ones with a single [value] property. But thanks for creating #3546869: Editing support for `type: array`: support multiple-cardinality Field Widgets (`StaticPropSource`s) to get that done sooner! 🤩

tedbow’s picture

Project: Experience Builder » Drupal Canvas
Version: 0.x-dev » 1.x-dev

Switching the project because I was searching for this issue and couldn't find it.