Follow-up to #2263569: Bypass form caching by default for forms using #ajax..

There, in RenderElement::preRenderAjaxForm() we changed the default URL of an AJAX submission from Url::fromRoute('system.ajax') to Url::fromRoute('<current>'). Most of the time, this is the same as $form['#action'], but not always. For example, comment forms sometimes set the action to a separate page.

For the cases where the action is not the current page, let's discuss which one AJAX should submit to, and then add docs and test coverage for that. In most cases, since the AJAX response is built solely out of what's in $form and $form_state at the end of form processing, which route we submit to probably doesn't result in any difference, if both the current route and action route end up calling $form_builder->buildForm() with the same arguments, and if there aren't alter hooks that modify differently based on the route. But, since it is possible to write routes and alter implementations that result in differences, we should take a stance here.

Reasons for AJAX to submit to $form['#action']:

  • All #ajax elements fall back to submitting to the form action when JS is disabled, so this would bring more consistency for what server-side form processing code runs, regardless of whether JS is enabled in the browser.

Reasons for AJAX to submit to the current page:

  • The purpose of the AJAX response is to update the current page, so shouldn't it do so with content meant for that page? When JS is disabled, an entire new page is returned, so it matters less whether that's the same or different than the current one.
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

nod_’s picture

Reasons for AJAX to submit to the current page:

The purpose of the AJAX response is to update the current page…

I would argue that it is not true. For a form, the purpose is to submit data without changing the page, updating the current page is merely a side-effect. I'm for using $form['#action'], it makes more sense to me.

Wim Leers’s picture

Priority: Normal » Major
Issue tags: +D8 cacheability

Note that this also impacts cacheability. Forms using <current> need to be cached per route. Which means that e.g. the login block actually is only cacheable per route.

So if we continue to do this, we should at least do it using a placeholder.

Wim Leers’s picture

Wim Leers’s picture

Wim Leers’s picture

Wim Leers’s picture

Priority: Major » Normal
tic2000’s picture

In most cases, since the AJAX response is built solely out of what's in $form and $form_state at the end of form processing, which route we submit to probably doesn't result in any difference

Maybe it's just my luck to hit one of those few cases where that is not true.

I discovered this while working on Field UI AJAX. What I want to do is to make the 2 or 3 forms (depending on if it's an existing field or not) to load through AJAX on the same page, with no full page reloads. Note that this is not actually a multistep form because of how fields works. It's a series of forms loaded one after the other. That's true even in the non-js way.

I try to create a "Dummy Text" text field on the node type article. What I found is this as follows (long read).

1. If I don't set an action on forms. The first form works as expected because it loaded from the link he actually resides at (/admin/structure/types/manage/article/fields/add-field). While the second loads, when you submit through AJAX it "submits" to the same page and loads the first form again. I put "submits" in queotes because it doesn't actually submit the form (the values are not saved, I change the cardinality to 2 to check this).

2. If I add an action to the form the exact same thing happens at I described at point 1. The set action is just ignored.

3. If I set an URL for the ajax to where the form normally resides, instead of a callback. The first form works as expected. The second form submits through ajax at /admin/structure/types/manage/article/fields/node.article.field_dummy_text/storage?_wrapper_format=drupal_ajax. The values is actually saved, but nothing happens. In the background what happens is that on the ajax submission a redirect is triggered to /admin/structure/types/manage/article/fields.
This is because if you notice the URL where the form submitted, it misses the ajax_form=1 part which is added when a callback with no URL is used. This because that is added by Drupal when a callback is provided AND no URL.

4. If I add an URL and a callback the behavior is as in 3 because of the same condition.

5. If I add an URL and

$actions['submit']['#ajax']['options']['query'][FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;

the first forms works as expected, the seconds submits and saves the value, doesn't redirects, but throws a 500 error

*258 FastCGI sent in stderr: "PHP message: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\HttpException: "The specified #ajax callback is empty or not callable." at /home/catalin/projects/d8.1/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php line 73"

Which means it wants a callback.

6. If I add a callback, an URL and

$actions['submit']['#ajax']['options']['query'][FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;

it almost works, but a value I stored in $form_state to know I'm in the "multistep" workflow is not remembered so it gives me the response I have when calling just the 2nd form on the manage fields page.

So the only way to make what I want work is on top of 6 to also add

$form_state->setCached(TRUE);

to any form beside the first form.

Conclusions

The AJAX system is not smart enough to rebuild a form submitted through AJAX if it's not on the page he was intended to work on. The developer has to add the callback he wants to process the ajax submission, the URL to which the form normally submits, set ajax_form = 1 manually, because Drupal decided that that can only be set if you have a callback, but not an URL and enable the cache on the form.

Bonus

Although if you provide an URL, the ajax system converts that to a path, the ajax system also things you are not allowed to provide a path directly and in that case sets the URL to null.

      // Convert \Drupal\Core\Url object to string.
      if (isset($settings['url']) && $settings['url'] instanceof Url) {
        $url = $settings['url']->setOptions($settings['options'])->toString(TRUE);
        BubbleableMetadata::createFromRenderArray($element)
          ->merge($url)
          ->applyTo($element);
        $settings['url'] = $url->getGeneratedUrl();dpm($settings['url']);
      }
      else {
        $settings['url'] = NULL;
      }

Please forgive all my grammar and spelling mistakes and ask for any clarifications. I know that when I have to write that much in English the meaning to what I meant gets lost a lot of times.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

John Pitcairn’s picture

@tic2000: Many thanks for your post. I am modifying an existing non-core form (commerce) to submit via ajax, and I am finding that the first instance of the base form on a page does not call the ajax callback after a cache clear, because the request is missing the ajax query parameter. My workaround in hook_form_alter is to explicitly set that:

    $form['#action'] = Url::fromRoute('<current>')->setOption('query', [FormBuilderInterface::AJAX_FORM_REQUEST => 1])->toString();
joegl’s picture

Priority: Normal » Critical

This and the below issue are proving to be devastating to the app we are attempting to build.

https://www.drupal.org/project/drupal/issues/2504709

We have 4-5 pages where the majority of our functionality takes place, but almost everything happens via forms, modals and AJAX.

For example, let's we have a page with a list of users. Each user has certain actions such as "edit". When you click "edit" it uses AJAX to load a form with the user's data in a modal, with save and cancel buttons. At the top of page is a checkbox called "Display only active users". When the checkbox is clicked, a global variable is set, a new user list is pulled, and the current user list is replaced via AJAX.

The Problem: The new user list was built in the AJAX callback for the checkbox, and is wrapped in all of the AJAX data, query parameters, wrappers, URL's, etc., of the checkbox. This breaks all of the AJAX functionality for the user list, including the aforementioned "edit" link. It also builds all non-AJAX links as AJAX links, which break table headers, pager links, etc.,

I want to make sure I am taking the right approach and not missing anything, because this appears to be a pretty common use-case for AJAX.

joegl’s picture

Priority: Critical » Major
joegl’s picture

Why is the $form[#action] being re-written for AJAX forms?

I wish I could better understand and contribute to this issue but my knowledge of and experience with symphony and drupal core is slim.

It appears the intention is to set submission URL for the form to the page the form resides on when it is being built. This is done by retrieving the <current> request URL, and appending the corresponding queries.

Unfortunately, in an AJAX callback, the <current> request URL is going to be the URL the current AJAX callback is being submitted to, and not the page the AJAX callback originated from. If we build a form IN the AJAX callback, the submission URL's for the form and form's AJAX callbacks will be the AJAX callback URL the form was built inside of, due to pulling from the <current> request URL. Thus, when any AJAX is triggered in the newly built form, it is submitted through and triggers the AJAX callback the form was built inside of.

For example, lets say I am on the URL: mysite.com/users .
The page displays a list of users with basic edit forms for each user, built using \Drupal::formBuilder()->getForm(EditUserForm); .
The submission URL for the edit forms is mysite.com/users because it is the <current> request.
My "Show Active Users Only" checkbox (is actually a link masking as a checkbox) has a route of mysite.com/ajax/Users/ShowActiveOnly .
When I click the checkbox, the AJAX Callback is called, and it rebuilds the user list with only active users, using \Drupal::formBuilder()->getForm(EditUserForm); to rebuild the user edit forms, and a ReplaceCommand(); to replace them.
If I do not click the checkbox, the edit forms work. If I do click the checkbox, the edit forms submission and AJAX Callback URL's are now being sent to mysite.com/ajax/Users/ShowActiveOnly instead of the current page mysite.com/users , because they were built inside of the callback.

joegl’s picture

I've looked at RenderElement::preRenderAjaxForm() and am a bit mystified similar to #7 as to why the url and options are being reset by default on all $form[$element]['#ajax'] arrays.

I've been able to determine if we're already in an AJAX callback by using $drupal_ajax = $current_request->request->get('_drupal_ajax');. However, once I have determined that, I don't know what to do with it; I still don't know where exactly to point the callbacks too or if I can even get that information, but I know I can't submit them to the <current> URL, since it will inherit the wrong URL and break it.

For now, I'm thinking about writing my own AjaxResponse Command to rebuild/replace a form. The command would have all the relevant data to build and replace the form, but it wouldn't build it. Once the AjaxResponse is executed, the command would trigger an additional custom AJAX callback, and trigger the form rebuild and replace. This callback wouldn't be "wrapped" in the first callback's request stack, and wouldn't inherit any AJAX data.

EDIT

Well, I tried to build what I described with no luck. I essentially created a custom command, whose javascript responds by triggering another ajax call for just rebuilding the form. Unfortunately it seems even if I chain ajax responses one after another like this, they still inherit parameters from one another.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joegl’s picture

I'm still stuck; I'm trying to figure out where the URL's should be sent to, when <current> does not point to the current page. Is there a default AJAX URL for a Form Action to submit to, which will route the request to the correct callback? The problem I am encountering is essentially forms built within an AJAX callback use the URL for the current AJAX callback as the Action URL and AJAX Callback URL’s, which causes the newly built form to trigger the AJAX callback it was built in when its own AJAX callbacks are triggered, since they are sent through the same URL.

For example, let's say I have a "use-ajax" link submitting to /ajax/ToggleThing/<id>. When I click the "ToggleThing" link, it toggles a server-side var "on" or "off", builds a form, and either removes or inserts the form depending on whether the toggle is "on" or "off". However, because the form was built using the <current> request, and the <current> request when the form was built was /ajax/ToggleThing/<id>, all of the AJAX in the built form is submitted to /ajax/ToggleThing/<id>, which toggles the var and removes the form in addition to attempting to execute the form AJAX.

I am thinking if I could somehow intercept the form after or while it is built, and change the route from <current> to a default AJAX URL setup for forms, I could get around the issue. I have tried reverse engineering the AJAX handling in the views and views_ui core modules, but I haven't been able to figure out how they get around the issue. Any help is appreciated.

However, to get back to this specific issue in core, I am wondering why we moved away from using a default route for all AJAX form submissions (Url::fromRoute('system.ajax')), and instead changed the default behavior to the <current> request? Again, my understanding of core, routing, and the AJAX system is poor. Thanks!

EDIT: Am I wrong in thinking this is a huge issue? I'm surprised at the lack of traction and others encountering this problem. This and issue #2504709: Prevent _wrapper_format and ajax_form parameters from bleeding through to generated URLs are making it hard for us to support Drupal 8, as they are pretty common AJAX requests (rebuilding table headers, pagers, forms, etc., with AJAX). I feel like I have to be missing something super simple...

joegl’s picture

How are form AJAX callbacks being routed to the correct callback? Is there a way to prevent the bogus route from being triggered? I am starting to lose my mind...thanks.

As far as I can tell, views gets around this by building every form inside of another multi-step form, so it's all considered one form...or something. I also see views_ui is re-writing the url in the ajax.js file, and passing everything through their own JS/Ajax functions.

I don't see any purpose for submitting a form to the current request, instead of a default system.ajax route. Unless...is the request being used because we wanted a unique #action url to cache the form around? I am so lost.

I really appreciate any help as I am banging my head on things, trying to get a form with AJAX callbacks I placed on a page via a 'use-ajax' link to work correctly. I have spent weeks on this now, and if I had one inkling of what I should actually be changing in core, I'd have poured it all into this issue. I promise this is the last time I post my agonizing defeatedness into another redundant comment :P

samuel.mortenson’s picture

I think we're hitting this when working on the Media library field widget in #2962525: Create a field widget for the Media library module.

Replication steps:

1. Install the Standard profile.
2. Apply the combined patch from #2962110: Add the Media Library module to Drupal core comment #3 (I worked around the bug in comment #11).
3. Create some Media.
4. Add a Media field to the Article content type, and choose the "Media library" field widget in the form display tab.
5. Visit /node/add/article and open the Media library.
6. Use the exposed form to filter results.
7. Select a media item then click "Select media".
8. See a 404 error in your browser's console, as the button hit "/views/ajax" instead of the path in the form action.

John Pitcairn’s picture

Version: 8.5.x-dev » 8.6.x-dev
Fabianx’s picture

Title: Should AJAX forms submit to $form['#action'] instead of <current>? » AJAX forms should submit to $form['#action']
Category: Task » Bug report

Re-classifying as a bug based on #20:

- When we fallback to '#action' without JS, it is illogical that we would submit somewhere else in the AJAX case, this could even lead to bugs - e.g. the form not being available on a fresh re-visit of the page.

Fabianx’s picture

Title: AJAX forms should submit to $form['#action'] » AJAX forms should submit to $form['#action'] instead of <current>
Version: 8.6.x-dev » 8.5.x-dev
joegl’s picture

Version: 8.5.x-dev » 8.6.x-dev

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

gun_dose’s picture

See a 404 error in your browser's console, as the button hit "/views/ajax" instead of the path in the form action.

I have the same problem with BEF and dc_ajax_add_to_cart modules. When view changed via ajax, all forms has "/views/ajax" as action.

And the second problem: when form is submitted, Drupal renders full current page, and only when rendering is reached form, it interrupts and form submit processing starts. It is very big penalty to performance. Especially if form is located at the end of render array. So I suppose, we need one special route for all AJAX form, as it was in Drupal 7, because current situation looks like a big regression.

Mingsong’s picture

My site is located in a sub-folder. For example the home page URL is : http://localhost/d8/

The latest version of 8.6.1 doesn't work with my site with following 404 page error:

POST http://localhost/d8/d8/admin/content/media-widget?.... 404

Apparently, there is a duplicated 'd8' which is the sub-directory name of my site added to the beginning of the URL.

The correct URL should be http://localhost/d8/admin/content/media-widget

Line 71 in MediaLibrarySelectForm.php
$url = parse_url($form['#action'], PHP_URL_PATH);

This url has the sub-directory name of my site at the beginning which is not necessary. Because the sub-folder (site base URL) will be added at line 75.

Replace the codes at line 71 with:
$url = parse_url('/' . $this->displayHandler->options['path'], PHP_URL_PATH);

Fixed the bug for me.

The patch is attached.

Mingsong’s picture

Re-patch for Drupal 8.7.x

Mingsong’s picture

samuel.mortenson’s picture

@mingsong Would you mind opening a new issue for this (with the "media" component)? Media Library is already supposed to have a workaround for this more generic core bug, and if its workaround isn't working for you that's something that should be addressed elsewhere.

Ideally when the generic core bug in this issue is addressed, the Media LIbrary's workaround will be removed completely.

Mingsong’s picture

@Samuel, no problem.

The Media module is part of the core with Drupal 8. So should I create a new issue for the core project?

Regarding the workaround you mention above, could you please give more details about it? Configurations?

samuel.mortenson’s picture

@mingsong Yes, please create it for the core project. The workaround in the Media Library is the same code you're patching - ideally manually setting the AJAX URL would not be necessary when this issue is fixed.

jasonawant’s picture

Hi,

With samuel.mortenson's help (thanks Sam!), I used a work around for our usage of a custom form rendered within a view display's header via a views @ViewsArea plugin. Wanted to share here in case someone else comes across this issue.

The custom form included two select elements with ajax callbacks defined. The view included ajax facets, sorts and pagination. When the views' facets, sort or pagination were used, the custom form's ajaxed elements would inherit the /views/ajax url value set in RenderElement::preRenderAjaxForm() here. Also, after view ajax requests, the custom form's submit would not work b/c $form['#action'] would also bet set with /views/ajax.

To work around this, I updated the elements ajax and $form['#action'] like so.

  $form['my_select'] = [
    '#type' => 'select',
    '#title' => $title,
    '#required' => TRUE,
    '#options' => $options,
    '#ajax' => [
      'wrapper' => 'my-wrapper',
      'callback' => [$this, 'updateForm'],
      'url' => Url::fromRoute('my_module.custom_form'),
      'options' => [
        'query' => [
          FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
        ],
      ],
    ],
  ];
  // Add other query parameters as RenderElement::preRenderAjaxForm() does.
  $form['my_select']['#ajax']['options']['query'] += \Drupal::request()->query->all();

  $form['#action'] =  Url::fromRoute('my_module.custom_form')->toString();

This solved the error, but introduces new UX issues where validation fails. When validation fails, the users is redirected to the my_module.custom_form route as technically expected, but takes the site visitor away from the original context. I'm sure there's a way to solve that, but nothing comes to mind right away. I've considered storing the original path where the form is first built in $form_state and then use that value when setting ajax element url and $form['#action'] or with an ajax submit callback.

Berdir’s picture

> I've considered storing the original path where the form is first built in $form_state and then use that value when setting ajax element url and $form['#action'] or with an ajax submit callback.

You can't do anything like that because the form is rebuilt on the first ajax request.

What I did in poll module, which uses a similar approach is to rely on ajax for form submission and therefore also validation, then it only goes to that other page if JS is disabled.

gun_dose’s picture

I think, the best solution is make special route for ajax requests, like 'system/ajax' as it was in Drupal 7. It could solve all issues with form actions and with performance (because in current route, before form submit handling controller tries to render full page, that obviously is a big penalty for performance.

I suppose that current route as ajax-form action is a big regress compared to seventh version.

joegl’s picture

I suppose that current route as ajax-form action is a big regress compared to seventh version.

I personally agree but was not involved in D8 core discussions on the subject, so don't know the nuances. However, I believe this issue sprouted from #2263569: Bypass form caching by default for forms using #ajax., where they started using the <current> route as the form['#action'] to avoid caching issues. The contents of that thread are currently above my head unfortunately.

I would specifically look at this comment and below: https://www.drupal.org/project/drupal/issues/2263569#comment-9953615

gun_dose’s picture

I would specifically look at this comment and below: https://www.drupal.org/project/drupal/issues/2263569#comment-9953615

Too big thread to read it completely, but I noticed, that mentioned comment is about form controller, but it is not the same case when forms are placed in blocks or when forms are rendered in entity teasers as it happen in Commerce.

jasonawant’s picture

Thanks Berdir for pointing out that solution in poll module in #34 above. I think I've found what you are referencing in PollViewForm::actions()

gun_dose’s picture

I think I've found what you are referencing in PollViewForm::actions()

Nice example! But is there any way to do this with forms that already exists and defined in contrib modules? I can set ['#ajax']['url'] at hook_form_alter, but I suppose, that also I need some controller to handle this. I tried to do this, but my controller does not handle form submitting and I can't figure out, what should I to to make this work?

Pancho’s picture

Hide basically unrelated patch.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

davo20019’s picture

Thanks @jasonawant. I ended up using the workaround from comment #33.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

CulacovPavel’s picture

Some new Solution?

Working with commerce2, I create ajax cart and add canonical product url to cart

$url = \Drupal\Core\Url::fromRoute('entity.commerce_product.canonical', [
      "commerce_product" => $productVariant->getProductId()
    ]);
$form['#action'] = $url->toString();

Before this create js script, that replace view/ajax with canonical url

 /**
   * Keep the original beforeSend method to use it later.
   */
  var beforeSend = Drupal.Ajax.prototype.beforeSend;
  var parseQuery = function (queryString) {
    var query = {};
    $.map(queryString.split('&'), function (val) {
      var s = val.split('=');
      query[s[0]] = s[1];
    });
    return query;
  };
  /**
   * Override beforeSend to clean up the Ajax submission URL.
   *
   * @param {XMLHttpRequest} xmlhttprequest
   *   Native Ajax object.
   * @param {object} options
   *   jQuery.ajax options.
   */
  Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
    var data = (typeof options.data === 'string') ? parseQuery(options.data) : {};
    if(data.form_build_id){
      var form = document.getElementsByClassName(data.form_id.replaceAll("_", "-"))[0];
      if(form){
        var action = form.action;
        var url = new URL(action);
        if(options.url.includes('/views/ajax')){
          options.url = options.url.replace('/views/ajax', url.pathname)
        }
      }
    }
    beforeSend.apply(this, arguments);
  }

For me working.

lukasss’s picture

@Utilvideo #46 very good but its has some problems.

When we have the add to cart form with commerce attributes I see that's the widget not right getting view mode.

CulacovPavel’s picture

@lukasss, try add action depending on the view_mode.

lukasss’s picture

@Utilvideo
I got it like this:

in js add:

// Add the right view_mode parameter.
options.data = options.data + '&view_mode=ajax_dialog';

in hook form alter:

  $request = \Drupal::request();

  // Add the right view_mode parameter.
  // @see atc-ajax-dialog.js
  $view_mode = $request->request->get('view_mode');
  if ($view_mode && $view_mode == 'ajax_dialog') {
    $user_input = $form_state->getUserInput();
    if (isset($user_input['product_id'])) {
      $product_id = $user_input['product_id'];
      $url = Url::fromRoute('entity.commerce_product.canonical', [
         "commerce_product" => $product_id,
      ])->setOption('query', [FormBuilderInterface::AJAX_FORM_REQUEST => 1])->toString();
      $form['#action'] = $url;
    }
    // Set view_mode for right render field in
    // \Drupal\commerce_product\Plugin\Field\FieldWidget\ProductVariationWidgetBase::ajaxRefresh
    $form_state->set('view_mode', $view_mode);
  }

This solved the problem of choosing the correct view_mode, but it did not solve the problem if the product is displayed on the page and in the modal dialog. Then the field, for example, price is replaced everywhere.
I think there is a problem in
Drupal\commerce_product\Plugin\Field\FieldWidget\ProductVariationWidgetBase::ajaxRefresh

lukasss’s picture

@Utilvideo
The problem that remained in #49 does not apply to this one.
Described it here https://www.drupal.org/project/commerce/issues/3191731

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.