Overview

See video:

https://youtu.be/SIbNsMHQQOo

Steps to reproduce

  • The XB layout should have two components that use images. The default layout has this.
  • Click on a component that uses an image, note that the props form opens for it
  • Click on a different component that uses an image, note that the props form does not open - it is stuck loading.
  • Install XB and SDDS per dev guide (use D10 due to components module not having D11 version and us not having time to remove this dependency)
  • Delete all components in the desktop preview using delete button
  • Drag "Four column" component into desktop preview slot
  • Drag "Icon card" into each slot for the four columns
  • Click on each icon card and look at the props form
  • It will be hanging for at least some of them
  • Is it because there are duplicates and have some sort of duplicate id?
  • Delete all components
  • Drag Hero => props form shows up => delete component
  • Drag Stats Card => props form shows up (note the text is white) => delete component
  • Drag Testimonial => props form hangs => delete component
  • Drag CTA 2 WIP => props form shows up => delete component
  • Drag Case Study WIP => props form hangs => delete component
  • Drag button => props form shows up

Note that you will probably get an error on command line and have to run drush cr again to get rid of it (needs its own issue)

Note also that I have some XB hacks in place that may or may not be causing an issue or masking other issues... I needed to add these at some point during testing

My XB codebase:

  1. I'm using this MR branch #3472634: Component List doesn't scroll
  2. I've changed many of the XB components to "obsolete" so it's easier for me to see what to use
  3. I have code hacks below
// not needed to reproduce
// not needed to reproduce
// not needed to reproduce

Proposed resolution

TBD

User interface changes

Prop form doesn't hang and shows props properly :D

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

kristen pol created an issue. See original summary.

kristen pol’s picture

Note that this:

#3472192: Redux support for ImageWidget: `[image] String value found, but an object is required`

is what crashes things when trying to use a props form that has an image.

The hanging props form doesn't seem related.

kristen pol’s picture

I added back one of the asserts (below) and rebuilt and the hanging behavior seems pretty much the same. I'll add an issue for the other assert causing fatal error.

--- a/src/Plugin/Field/FieldFormatter/NaiveComponentTreeFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/NaiveComponentTreeFormatter.php
@@ -24,6 +24,8 @@ class NaiveComponentTreeFormatter extends FormatterBase {
    * {@inheritdoc}
    */
   public function viewElements(FieldItemListInterface $items, $langcode) {
+    // @fixme KP
+    //assert($items->count() === 1);
     assert($items->count() === 1);
     assert($items[0] instanceof ComponentTreeItem);
     // This field type is single-cardinality: delta 0 is rendered.
kristen pol’s picture

boulaffasae’s picture

Hi kristen,

Looking at the Network logs, when you drag a new "Icon Card" and you upload a new file (JPG), the preview get updated with no issue. But right after I change the title It throw an issue

The website encountered an unexpected error. Try again later.<br><br><em class="placeholder">Drupal\Core\Render\Component\Exception\InvalidComponentException</em>: [image] String value found, but an object is required in <em class="placeholder">Drupal\Core\Theme\Component\ComponentValidator-&gt;validateProps()</em> (line <em class="placeholder">203</em> of <em class="placeholder">core/lib/Drupal/Core/Theme/Component/ComponentValidator.php</em>). <pre class="backtrace">Drupal\Core\Template\ComponentsTwigExtension-&gt;doValidateProps() (Line: 109)

Payload (after changing title field)

{"name":"Icon Card","image":{"alt":"Alternative text ","width":1280,"height":853,"src":"/sites/default/files/2024-09/banner-4887650_1280_1.jpg"},"title":"User-centric experiences","summary":"Create compelling content on more  devices than ever. With tools to build versatile, structured content and integrate with a wide array of digital marketing channels.","randomProp":"random"}

Payload

{"name":"Icon Card","image":"","title":"User","summary":"","randomProp":"random"}
kristen pol’s picture

Thanks. That’s probably the issue in comment #2 above. This issue is about the prop form hanging

boulaffasae’s picture

Hi @kisten,

I was able to solve some of the issues

1. [image] String value found, but an object is required

setFormState was settings image to '' (empty string) because the prior state had an entry xb_component_props[9acb612e-748c-4f69-b07e-66b95e5d173e][image][media_library_selection]

2. Textarea

Textarea wasn't getting updated, I found out that the onChange (from InputBehaviors) doesn't get triggered. I updated the code (+ added value because textarea have an entry value on the properties level)

This still doesn't resolve the issue with multiple instances of Card Icon. And another interesting issue, image doesn't get updated in the preview -_-

I'm sorry for not moving everything into the same issue, but as you know you need to have the #3472634 fix just to scroll to Icon Card and I don't think we can rebase on 3 differents issues just to have the ability to test and resolve this one.

boulaffasae’s picture

Hi !

It looks like we have an issue on Redux level, after I click on the 2nd Card Item. Redux stuck at status: "pending"

Redux State

boulaffasae’s picture

UPDATE

once I commented the below in createApi the issue is gone

      transformResponse: processResponseAssets,
boulaffasae’s picture

Finally, I was able to trace this (well the issue was there all the time "loadjs")

// JS

when you open a 2nd Icon Card, it try to load the same JS libraries that were loaded before. This throw an error, because we have a function that filter the JS array and only load the new JS - and it send an empty string which break loadjs

// CSS

LoadJS throw the below error (not sure why they couldn't add an explanation -_-)

  // throw error if bundle is already defined
  if (bundleId) {
    if (bundleId in bundleIdCache) {
      throw "LoadJS";
    } else {
      bundleIdCache[bundleId] = true;
    }
  }

Source code: https://github.com/kubetail-org/loadjs/blob/main/src/loadjs.js#L248

boulaffasae’s picture

props are not hanging anymore, but we still need to figure why the Card Item doesn't get updated (Preview)

wim leers’s picture

Title: XB props form hanging for SDDS components » XBEndpointRenderer & processResponseAssets() do not support `ajaxPageState` ⇒ duplicate CSS/JS loading
Category: Support request » Bug report
Issue tags: +Needs tests, +e2e
Parent issue: » #3450592: [META] Front-end Kanban issue tracker
Related issues: +#3454173: Media Library integration (includes introducing a new main content renderer/`_wrapper_format`)

@boulaffasae Nice detective work! 👏👏

Root cause: ajaxPageState is missing

when you open a 2nd Icon Card, it try to load the same JS libraries that were loaded before. This throw an error, because we have a function that filter the JS array and only load the new JS - and it send an empty string which break loadjs

This points to us missing ajaxPageState support (\Drupal\Core\StackMiddleware\AjaxPageState) in the processResponseAssets function that was added in #3454173: Media Library integration (includes introducing a new main content renderer/`_wrapper_format`). That's how Drupal's \Drupal\Core\Render\MainContent\AjaxRenderer and others handles that, and the ajaxPageState processing happens in \Drupal\Core\Asset\AssetResolver.

Alternative solution

But … your alternative solution sure is intriguing — that'd allow us to not send AJAX page state from the client to server and instead have potentially more cacheable responses… interesting! 😄

Next steps

  • 🙏 We should be able to reproduce this using an end-to-end test. Would you be willing to write that, @boulaffasae? 😊
  • @bnjmnm, WDYT about @boulaffasae's alternative solution?
boulaffasae’s picture

Hi @wim leers

I was able to reproduce the issue just by inserting one Icon Card in the existing Two Columns.

I added a simple test, that only check if clicking on that Icon Card will show the right props form / Currently, it show the Image props form instead of the Icon Card props form

kristen pol’s picture

Interesting. This was a hack to get around XB not loading the themes libraries (added an include at top of each component). But maybe that issue was resolved? I can try removing the base.css from the card and see how it looks

boulaffasae’s picture

Hi @wim leers

I tried getting the libraries and running UrlHelper::compressQueryParameter to compress them so we can attach them to the response:

    if ($libraries = $assets->getLibraries()) {
      $libraries = UrlHelper::compressQueryParameter(implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($libraries)));
      $response->headers->set('Attach-Libraries', $libraries);
    }

What do you think ? We should decompress them on the processResponseAssets.ts level, do you think this is possible ?

kristen pol’s picture

kristen pol’s picture

Fyi, I have removed the temporary extra base.css imports from each component but the hanging props form still remains but I'll let you'll with bigger brains figure that out :D

#3473131: Remove base.css import for all SDDS components

kristen pol’s picture

Oh! I missed that you fixed the textareas and the image issue!!!

I just tried this MR branch and both of those work!!!

The image issue is here:

#3472192: Redux support for ImageWidget: `[image] String value found, but an object is required`

kristen pol’s picture

Note that the hanging doesn't happen with the MR branch but it doesn't show the props form now instead... but you are still working on things so not unexpected.

johnwebdev’s picture

#16 UrlHelper::compressQueryParameter uses zlib (gzcompress) which is not supported in the browser, so the frontend would also need to install a dependency like https://github.com/imaya/zlib.js/

That approach should be able to solve #3471974: Remove obsolete message about Media Library widget not working when JS aggregation is enabled as well...

boulaffasae’s picture

Hi @wim leers

I think we will run into a missing piece even after decompressing the libraries / the output will have names likes: core/ajax, ...etc

Redux might not understand those entries.

Can you please direct me into the correct ghin to do ^^

boulaffasae’s picture

Some context //

This issue is not related to redux (redux show the spinner, because the ajax.js stuck)

When you open a component contextual panel, it try to load JS/CSS dependencies (dialog.js, ...etc)

If two components depend on the same JS, what should happen ?

1/ only load the dependencie one-time, but this will not trigger Drupal.attachBehaviors :/
2/ load the dependencie twice, this will result in a JS file being included two times and will break the click trigger
3/ load the dependecie once, and execute the Drupal.attachBehaviors (something doesn't seem right - the contextual panel stuck at the 1st component props form)

kristen pol’s picture

thanks for the thoughts…. this is my biggest blocker at the moment

No idea how to fix but hopefully y’all can squash it soon 🤞

wim leers’s picture

Status: Active » Needs work
Issue tags: -Needs tests

Nice — tests exist now! 👍

If two components depend on the same JS, what should happen ?

1/ only load the dependencie one-time, but this will not trigger Drupal.attachBehaviors :/
2/ load the dependencie twice, this will result in a JS file being included two times and will break the click trigger
3/ load the dependecie once, and execute the Drupal.attachBehaviors (something doesn't seem right - the contextual panel stuck at the 1st component props form)

  1. We must manually invoke Drupal.attachBehaviors() — that's fine! :) That's how Drupal's AJAX system works too.
  2. 👎
  3. This should work fine. That's how Drupal's AJAX system works too.

1+3 are actually already happening — see Drupal.behaviors.jsxAjaxProcess() in ajax.hyperscriptify.js :)

I don't see why @boulaffasae's proposed solution in the MR can't work — i.e. ignore any Attach-Css calls that have already been executed.

Ah … that won't work as soon as we're dealing with aggregated assets! Because then each component with a different set of asset libraries (e.g. A + B + C) than another component but with a different set (e.g. B + C), will cause a different asset URL to be generated, and hence fail.

Conclusion: both the client-side and the server side MUST be updated to use AjaxPageState as explained in #13.

boulaffasae’s picture

A little update

Textarea + Image fix are no more needed (this was resolved in #3473155)

I will update the PR

HOW TO RESOLVE THE ISSUE MANUALLY

  1. Add an Icon Card and upload an Icon
  2. Now add an Icon Card (but don't open the Contextual Panel)
  3. Go to web/modules/contrib/experience_builder/src/Render/MainContent/XBEndpointRenderer.php, both response->headers->set('Attach-Css', ... and response->headers->set('Attach-Js', ... should be commented (else XB will load the ajax.js a 2nd time which will result in ajaxCommands being override and we will lose the openDialog function definition, this will break the Media functionnalities for the new Icon Card)

  4. Now youn can safely open the Contextual Panel (but don't click the Add Media just yet)
  5. Go to web/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php, and update the setAlreadyLoadedLibraries replace the args with an empty array and then you can click Add Media and everything will work fine

          ->setAlreadyLoadedLibraries([])
    

WHAT NOW

I think @wim leers suggestions will resolve the 1st issue, I will work on that. Then we will need to find out how to resolve the 2nd issue

2nd issue explanation: When Ajax load the necessary JS/CSS for the Media Library widget, it try to skip loaded libraries but then If no JS get loaded Ajax skip also the drupalSettings part :/ (Yes he do - ajax return only $response->getCommands() which concatenate js_assets_footer, js_assets_header, and css_assets what about $attachments['drupalSettings'] = $assets->getSettings())

lauriii’s picture

I was able to reproduce this with the default components that are loaded in XB. It seems that the problem is that you can only use a single component with an image – after highlighting a component, something about the state is off and means that other components with images cannot be edited anymore.

bnjmnm’s picture

Issue summary: View changes
bnjmnm’s picture

longwave made their first commit to this issue’s fork.

longwave’s picture

Status: Needs work » Needs review

MR!306 solves the issue for me. What was happening is that the (filtered) set of libraries sent to add_js was empty for the second instance of the element, and loadjs() does not call the success callback when asked to do nothing. The fix is to filter the list of libraries *before* trying to send it to add_js.

This could also be considered a bug in core ajax.js in that the promise never resolves if an empty set of libraries is passed in?

I haven't yet got the Cypress tests working locally so I cherry-picked the new test from the previous branch.

boulaffasae’s picture

Hi @wim leers, I added ajax_page_state as you suggested

        "ajax_page_state[libraries]": drupalSettings.ajaxPageState?.libraries,

I added only libraries (adding theme result in an issue - I don't think we need it)

I added setTimeout (because I found out they use it also for Drupal object + It help trigger the functions inside only after page load = until drupalSettings is defined)

** web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php will decompress the string

and then it's up to XBEndpointRenderer to remove the existing CSS/JS from the Array

I still have an issue, Media Library doesn't open - I think it's related to the #2 issue ('insert' doesn't get triggered in ajax.js)

boulaffasae’s picture

I'm playing manually again, after uploading an Image in the 1st Icon Card, I add a 2nd Icon Card to the page and before adding a media, I go to web/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php:

I comment the code

//    ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])

and I add these at the end

    unset($commands[1]);
    unset($commands[2]);
    return $commands;

this help me load the settings in the Ajax response + Removing the JS/CSS (they were already loaded in the 1st component edit)

and things work just fine.

Now I need to figure out how to do this programmatically

kristen pol’s picture

#33 has:

MR!306 solves the issue for me

I tried both branches but still get the spinning props form. I'll try installing a completely new environment.

@longwave Which component were you using? Steps for your manual testing would be great.

kristen pol’s picture

I'll try the steps in #35 first.

UPDATE: Tried that with Card (previously called Icon Card) and Case Study and form is still hanging. I'll try a completely fresh environment.

longwave’s picture

@kristen pol

  1. Install Experience Builder, Media, Media Library
  2. On /xb/node/1 add a second Image component to the page
  3. Click on one of the image components, the form will display
  4. Click on the other image component, the form just spins

On the 3472900-fix-empty-js branch this works for me, both forms display as expected - however the second form doesn't work properly, but it does display now.

Remember to npm run build and ddev drush cr after switching branches.

kristen pol’s picture

Thanks @longwave (we crossposted so just seeing yours... yes, lots of npm run build and drush cr happening regularly :D)

Progress!!!

I used a fresh environment and 3472900-fix-empty-js branch.

Everything *appears* to be working except for uploading new media. But I can choose existing media and it works!

I tested with Case Study and Card (from SDDS).

I can create a demo with this by upload images ahead of time and choosing the images.

This is a huge step forward! 🎉 🙏 😍

kristen pol’s picture

It took me a few tries, but I made it through all components except the last one in one go... I had to side step some issues (e.g. dragging certain things), but this is SO DEMOABLE if you know what *not* to do :D

https://youtu.be/SAir2OxwMS4

wim leers’s picture

Status: Needs review » Needs work

As I concluded at the end of #25, I think the only correct way forward is using ajaxPageState, which @boulaffasae's MR uses, but @longwave's MR does not 😅

So while @longwave's MR might be sufficient to unblock @lauriii's upcoming DrupalCon Barcelona demo recording (yay!), I do think that it makes more sense to continue with @boulaffasae's MR? 🙈

Reviewed @boulaffasae's MR.

boulaffasae’s picture

Hi everyone, I recorded a demo (Four columns with Cards)

- https://youtu.be/IVyarGnVZOQ

We still have a small issue with Media widget (already addressed in #3473155 https://www.drupal.org/project/experience_builder/issues/3473155 - but they only added a warning about it in console warns "The field media does not yet support updating the preview on change. It will soon.")

I think I have a fix for that, but for now I went with a small trick to skip it (Uploading a new image in the media - Clicking outside the Card - re-click again on the Card and change the title if you want)

boulaffasae’s picture

This is the small piece of code that I added to web/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php

    // Issue #3472900 by kristen pol: XB props form hanging for SDDS components
    $found = false;
    foreach ($commands as $entry) {
      if (isset($entry["command"]) && $entry["command"] === "settings") {
        $found = true; break;
      }
    }
    if (!$found) {
      array_unshift($commands, [
        'command' => 'settings',
        'settings' => $assets->getSettings(),
        'merge' => true,
      ]);
    }
longwave’s picture

FWIW prop-types.cy.js is now failing in both MRs due to #3469855: Emptying a required value through the UI crashes the app (empty <input>, selecting "None" option in <select> …) when testing the <select> enum and setting the value to "None".

kristen pol’s picture

Status: Needs work » Needs review

Okay... so I'm confused... too many comments.

Why do we have both MRs? Should they still both be open?

Let me know if either of these should be be tested again at some point.

kristen pol’s picture

Status: Needs review » Needs work

I didn't mean to change the status. Not sure what happened there.

longwave’s picture

Just two people working on it with slightly different approaches - mine solved exactly the issue in the issue summary but there are wider problems with adding JS when widget forms are loaded in, which the other MR is trying to solve.

I think the issue should be split in two to solve the immediate UI issue here and then fix the bigger Ajax issue separately, but I don't think Wim agrees with this.

boulaffasae’s picture

Hi @kristen, @longwave

The issue is much bigger than just a CSS/JS loading. @longwace MR will help us build a demo for the upcoming event, and in the meantime I will try to continue with the ajax_page_state approche to resolve the whole issue.

1. Media Library duplicate JS/CSS files, which result in overriding definition (Yes! when Media Library load ajax.js, it automatically erase every definition in dialog.ajax.js - therefore openDialog become undefined and nothing happen on ajax)
2. We can filter existing JS, but then LoadJS library does throw an issue when we pass an empty Array.

-- longwave MR resolve these two

3. Wim suggested that we use ajax_page_state, we need it's logic to skip loading an existing library JS/CSS. But this conflict with Radix state.
4. AjaxResponseAttachmentsProcessor return empty Ajax settings after inserting a Media just because all JS/CSS files are already downloaded.
5. Fied media is not yet supported in Experience Builder (uploading an image might result in it not show up in Preview)

and now it seems like we have new issues with Select too (like we did with Textarea before)

wim leers’s picture

Assigned: Unassigned » bnjmnm

@bnjmnm and I pair-reviewed this to determine next steps.

He's going to combine the excellent work in both MRs (👏 @boulaffasae especially! 😄).

bnjmnm changed the visibility of the branch 3472900-fix-empty-js to hidden.

bnjmnm changed the visibility of the branch 0.x to hidden.

bnjmnm’s picture

Assigned: bnjmnm » Unassigned
Status: Needs work » Needs review

New MR combining the prior solutions is ready.

While manual testing, there's still a strong chance you'll encounter things that don't fully work as expected due to this entire project still being in the "under construction" phase.

If they appear to be problems introduced by the solution in the MR, lets address them in this issue

If they are issues not *specific* to the symptoms in the issue summary and not *introduced* by the MR - first see if there is an existing issue filed and if not file one and no need to even mention it here. The changes in this MR are very beneficial and the more we can streamline communication the faster this will land.

wim leers’s picture

Assigned: Unassigned » bnjmnm
Status: Needs review » Needs work

Reviewed! Looks great! There's two bits that I don't understand what they do in there, and I'm hopeful you'll be able to just remove them 🤞

bnjmnm’s picture

Assigned: bnjmnm » Unassigned
Status: Needs work » Needs review

Feeback addressed. Left threads open for anything with an explanation that might require clarification.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

RTBC — but please add one example at https://git.drupalcode.org/project/experience_builder/-/merge_requests/3..., that'll help the next person reading that file 😊

bnjmnm’s picture

but please add one example at https://git.drupalcode.org/project/experience_builder/-/merge_requests/3..., that'll help the next person reading that file

Example added

  • wim leers committed 8ce73188 on 0.x authored by bnjmnm
    Issue #3472900 by boulaffasae, bnjmnm, longwave, wim leers, kristen pol...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

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