With a unique form id for any purchasable entity's add to cart form, you'd think you could have the same (product's) add to cart form on a page more than once -- and you can, and can successfully submit either, but #ajax replacement of the form and fields does not work as expected--if you change an attribute in form 1, the fields in form 2 will be replaced as well. However, the selected variation in form 2 will not be changed.

I think the issue is that the ajax replacement targets the fields by field class only, and instead should probably target the fields only for the form which triggered the ajax call in the first place - by a form id context.

Issue fork commerce-2832268

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

steveoliver created an issue. See original summary.

pavelculacov’s picture

If you try change second form first, is not working. After change first form si working second form.

abx’s picture

Version: 8.x-2.x-dev » 3.3.10

Problem/Motivation

When the same product appears multiple times on a page, the Add to Cart formatter generates identical lazy-builder arguments. This can result in duplicate form and AJAX wrapper IDs. In my case, the second AJAX Add to Cart button loses its expected drupal-ajax initialization, and clicking it also breaks the theme’s off-canvas menu.

Proposed resolution

Add a unique instance ID to the lazy-builder arguments using Html::getUniqueId(), so repeated instances of the same product are built independently.

Steps to reproduce

1. Display the same product multiple times in a View.
2. Use the AJAX Add to Cart formatter.
3. Click the Add to Cart button on the second instance.
4. Observe that the AJAX behavior fails and the off-canvas menu stops working.

Testing

* Tested multiple instances of the same product on one page.
* Verified with both the core Commerce Add to Cart formatter and the contributed AJAX Add to Cart formatter.
* Tested with Drupal caching enabled. (Both Anonymous and Login User)
* Confirmed that the forms receive different IDs and the original cart-combination behavior is preserved.

abx’s picture

Status: Active » Needs review
jsacksick’s picture

hm... I'm not sure about this, additionally the fix does not respect coding standards.
The proper fix is probably to update the following instead:

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    if (empty($this->formId)) {
      $this->formId = $this->getBaseFormId();
      /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
      $order_item = $this->entity;
      if ($purchased_entity = $order_item->getPurchasedEntity()) {
        $this->formId .= '_' . $purchased_entity->getEntityTypeId() . '_' . $purchased_entity->id();
      }
    }

    return $this->formId;
  }

Though calling getUniqueId() from there isn't the right thing to do... either.

abx’s picture

Thanks for the review. I dropped the Html::getUniqueId() approach.

In 3.3.10 the product add to cart form ID is set in ProductLazyBuilders::addToCartForm() through setFormId(), so the default getFormId() is not used for these forms. I made the change there instead:

AddToCartFormatter numbers repeated instances of the same product in render order and passes the number as a new lazy builder argument. This also stops the placeholders from being merged into one.
ProductLazyBuilders::addToCartForm() appends that number to the form ID when it is greater than 0.

The first instance keeps the current form ID, and the new argument defaults to 0, so already cached render arrays still work. The number does not depend on the order lazy builders run.

Known limits: if one cached render array is reused twice on a page, the placeholder is still rendered once. The static counter also isn’t reset between requests in a long-running process.

(By Claude)

I tested on 3.3.10 with the same product displayed twice on one page: the form IDs are different, and the Add to Cart button on the second form works and adds the item once. I did not test attribute AJAX, because my products don’t use attributes.