Change the text of a clicked Add to Cart button, if the item was successfully added to cart. We could change the text from "Add to Cart" to "In Cart" or something similar, and make it configurable

Comments

torgospizza’s picture

Title: Change text in Add to Cart Form button upon success » Refresh Cart Form button with Ajax after clicking (Patch)
Status: Active » Needs review
StatusFileSize
new588 bytes

Turns out the answer is easy, thanks to Drupal's Ajax API.

Attached is a patch which simply refreshes the cart form, using the Commerce function commerce_cart_add_to_cart_form_attributes_refresh() and merging the resulting commands in with the commands provided by this module's form handlers.

It works really well, and in our case, I am using a hook_form_alter() to check to see if a product is in a user's cart, and if so, disables the button.

The approach used here - by merging an existing ajax cart refresh function - should allow compatibility with other modules like Commerce Bundle, aw well as in custom implementations like myself where we don't necessarily want people adding more than one (digital) item.

It just makes for a better UX.

Please review and consider committing. Thanks!

Status: Needs review » Needs work

The last submitted patch, 1: dc_ajax_add_cart-2446825.patch, failed testing.

torgospizza’s picture

Status: Needs work » Needs review
StatusFileSize
new618 bytes

Whoops, looks like I wasn't using the latest Dev. Attached is a re-roll.

joelpittet’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

The issue summary has not much to do with the patch. But likely a good call because you really can't use the cart refresh feature with this module at the moment, I assume this fixes that?

torgospizza’s picture

I believe so? The patch doesn't refresh the cart (I think that is handled elsewhere in Ajax Cart, if you have the module's Ajax Cart Block enabled).

It's true that the issue changed after I posted this, so I'm open to rewriting the summary. Once I started digging into how the module operates I realized that I could very easily achieved what I wanted by using ajax commands and a small bit of custom code for my custom block.

I didn't necessarily want to make the text that it changed to something hard-coded into the module since modules such as Commerce Stock have their own say with what the cart button says after it has been clicked. (And I wanted to leave that flexibility there, in case admins had options enabled such as the "Qty field" for the cart buttons themselves.)

What the patch essentially does is expose the cart forms to an Ajax command, which allows those other modules to do their thing and not be inhibited by the fact that this module did not refresh the cart button once it had been clicked.

xaffimarc’s picture

Hi torgosPizza!

Thanks for taking the time to look at this, I have a real need for this functionality myself. I have patched my module and can see that it is currently changing the id of the form and clicked submit button.

But I cant see how to customize the button text, i'm sorry if this is a dumb question... It's been a very ... very long day

Could you please point me in the right direction?

Regards

torgospizza’s picture

Ah yes sorry for muddying up the issue with my patch. My patch was only part of that process, after I had fallen down the rabbit hole, so to speak.

Once I discovered that you could easily do this with hook_form_alter() I didn't think it was really necessary for the module itself to do anything, but if we can find a simple way to add this to DC Ajax Cart then I'd be happy to attempt a patch for it.

The hook_alter needed is really simple (apologies for weird spacing):

function MYMODULE_form_commerce_cart_add_to_cart_form_alter(&$form, &$form_state) {
  global $user;

  // Only valid for logged in users, because of Varnish and other caches.
  if (!empty($user->uid)) {
    $line_item = $form_state['line_item'];
    $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);

    $product = $line_item_wrapper->commerce_product->value();
    $product_wrapper = entity_metadata_wrapper('commerce_product', $product);

    // Check if it's in their cart.
    $cart_product_ids = MYMODULE_get_items_in_cart();

    if (!empty($cart_product_ids) && !empty($line_item->commerce_product))   {
      $cart_product_ids = array_keys($cart_product_ids);
 
      if (in_array($line_item_wrapper->commerce_product->product_id->value(), $cart_product_ids)) {
          // Product is already in cart! 
          $form['submit']['#disabled'] = TRUE;
          $form['submit']['#value'] = t('In your cart!');
        }
    }
}


// Helper function to return an array of product_ids of the user's current cart.
function MYMODULE_get_items_in_cart() {
  global $user;
  $cart_product_ids = &drupal_static(__FUNCTION__);

  if (!$cart_product_ids) {
    $order = commerce_cart_order_load($user->uid);

    if (!empty($order)) {
      $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
      foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
        if (!empty($line_item_wrapper->commerce_product)) {
          $product_wrapper = $line_item_wrapper->commerce_product;
          $type = $product_wrapper->type->value();
          $product_id = $product_wrapper->product_id->value();
          $cart_product_ids[$product_id] = $type;
        }
      }
    }
  }

  return $cart_product_ids;
}

So once you have this form_alter() in place, the "refresh" stuff that my patch does will automatically take effect. When the form is refreshed, this function will run, and your refreshed cart button will now say "In your cart".

xaffimarc’s picture

Thanks torgosPizza!

I'm going to try this in a few hours, it looks very very promising for me.... I do however wonder how it will deal with the multiple instances of the add to cart form I am using on each product page.

I essentially have a main product with several variations & I'm rendering a separate add to cart for each one.

then, I have a separate product reference field for "optional extra's" for which i am also rendering individual add to cart forms.

I'll let you know ho I get on! :)

subhojit777’s picture

Component: Code » User interface

Thanks @torgosPizza for the patch. But I am not sure whether to include this feature in the module. I understand this will be good for UX, but does this feature really aligns with the purpose of this module?

Re #5:

Once I started digging into how the module operates

The working of the module is very straightforward. After adding item to cart:
- Using commerce APIs get the latest commerce line items in current order object
- Using Drupal Ajax APIs replace the old items with new items

What the patch essentially does is expose the cart forms to an Ajax command, which allows those other modules to do their thing and not be inhibited by the fact that this module did not refresh the cart button once it had been clicked.

Can you give me an example for this? I mean a custom module that ajax changes the "add to cart" button text when item is added to cart. Or does the code in #7 achieves that thing?

P.S. Very sorry for the late reply.

torgospizza’s picture

Hi subhokit777,

Yes, the code in #7 is how we are handling this on our own production site. We have a form_alter() that checks if a user already owns a product, if it's in their cart, etc. So the form_alter() is of course called whenever an Add to Cart form is displayed.

Without the patch I have created, clicking an Add to Cart button would add the product, but the text of the button would not indicate this fact (disregarding any popup messages, etc.). In order to facilitate this I simply instructed the DC Ajax Add to Cart module to refresh the clicked button. That function commerce_cart_add_to_cart_form_attributes_refresh() is one that is used elsewhere by Commerce to do similar Ajax refreshes, which is why my patch is able to be so small - I'm just reusing some API methods we already have available.

So with the patch, clicking on the Add to Cart form button not only pops up the Ajax popup from your module, but it also refreshes the button that was clicked, which in our cases changes the state of the button and provides additional user feedback which we have found is essential to avoid confusion.

Before clicking:

After click:

subhojit777’s picture

Thank you. Will try to build a custom module and use the patch. This will be a good addition in this module.

subhojit777’s picture

Status: Needs work » Postponed
Parent issue: » #2568855: Write tests

@torgosPizza I used the patch and created a test module using the code in #7. I was randomly checking, and at one moment the cart update functionality was breaking. I then disabled the test module, and then it started working properly. I then again tried to reproduce the abnormality by enabling the module, but was not able to replicate it.

I think we should write automated tests first #2568855: Write tests before committing this into the module.

torgospizza’s picture

Hmm, interesting. I'll admit I did not test much with the cart from the DC Ajax Add to Cart module because we have our own custom block (which I had hooked into using more custom code not shown here). Thanks for taking a look and hopefully your tests will reveal the issue.

subhojit777’s picture

Title: Refresh Cart Form button with Ajax after clicking (Patch) » Refresh Cart Form button with Ajax after clicking
Version: 7.x-1.x-dev » 7.x-2.x-dev

subhojit777’s picture

Hi @torgosPizza the tests are passing for your patch in #3. I remember when I was testing your patch the ajax was breaking when I was adding the same item without browsing to other page. Currently with the tests that I have written it is not possible to close popup that appears when you add an item to cart. I am keeping this issue as "Postponed" until I figure out this shortcoming.

torgospizza’s picture

That's very strange because it works just fine for me. The only thing I am not using from your module is the Ajax Cart block because I have my own custom block for that.

higherform’s picture

+1 for this feature request.

Will try applying to our dev server this week and see what I can provide for test results.

torgospizza’s picture

Re @subhojit: One option would be to disable the popup. Since that's more of a user-interaction issue that is not handled in the test, I do not really feel that those tests are representative of whether this feature works or not. Perhaps we add +needs tests to the issue, and/or add that as a follow-up issue here. But as far as I'm concerned this module severely needs this patch added to make for a better user experience by default. (And it's only 3 lines!)

Saneesh’s picture

Hello @torgosPizza,
I tried the code in post #7 and I'm able to see the 'In your cart!' on the submit button. But, I want to change this scenario a little. I want to show a "minus button-textfield for cart count-plus button", in the text-box I want show the current cart count of that product. When we press on plus/minus the cart as well as the cart count textfield need to be updated. To do this I have modified the form code as follows:

$form['submit']['#disabled'] = TRUE;
$form['submit']['#value'] = t('In your cart!');
$form['submit']['#type'] = 'hidden';

$form['minus'] = array(
   '#type' => 'button',
   '#value' => t('-')
);

$form['cart_count'] = array(
    '#type' => 'textfield',
    '#title' => t('Cart count'),
    '#default_value' => 1,
);

$form['plus'] = array(
    '#type' => 'button',
    '#value' => t('+'),
    '#ajax' => array(
       'callback' => 'click_on_plus',
    ),
 );

$form['product_id'] = array(
   '#type' => 'hidden',
    '#value' => $line_item_wrapper->commerce_product->product_id->value(),
);

//click_on_plus()

function click_on_plus($form, $form_state) {
 global $user;

  if ($user) {
    $quantity = $form_state['values']['cart_count'];
    $product_id = $form_state['values']['product_id'];

    $line_item = NULL;

    if ($product = commerce_product_load($product_id)) {
      $line_item = commerce_product_line_item_new($product, $quantity);
      $line_item = commerce_cart_product_add($user->uid, $line_item);
      
      // Set the updated quantity for the text-box 
      $form_state['values']['cart_count'] = $line_item->quantity;
    }
  }

  return $form;
}

When I press on the plus button the following AJAX error it shows:

<br />
<b>Warning</b>:  Unknown: Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini. in <b>Unknown</b> on line <b>0</b><br />
[{"command":"settings","settings":{"basePath":"\/","pathPrefix":"","ajaxPageState":{"theme":"mysite","theme_token":"um-hNv8pgItLAXoIkDCs-VDyTIxNmdKr4K5mKqrLtzs","jquery_version":"1.10","css":{"sites\/all\/modules\/date\/date_api\/date.css":1,"modules\/field\/theme\/field.css":1,"modules\/node\/node.css":1,"sites\/all\/modules\/office_hours\/office_hours.css":1,"modules\/search\/search.css":1,"modules\/user\/user.css":1,"sites\/all\/modules\/views\/css\/views.css":1,"sites\/all\/modules\/ctools\/css\/ctools.css":1,"public:\/\/css\/menu_icons.css":1,"sites\/all\/themes\/mysite\/css\/bootstrap.min.css":1,"sites\/all\/themes\/mysite\/css\/font-awesome.min.css":1,"sites\/all\/themes\/mysite\/css\/style.css":1}},"clientsideValidation":{"forms":{"commerce-cart-add-to-cart-form-2--2":{"rules":{"cart_count":{"maxlength":128}},"messages":{"cart_count":{"maxlength":"Cart count field has a maximum length of 128."}},"errorPlacement":"4","general":{"errorClass":"error","wrapper":"li","validateTabs":0,"scrollTo":1,"scrollSpeed":"1000","disableHtml5Validation":"1","validateOnBlur":"1","validateOnBlurAlways":"0","validateOnKeyUp":"1","validateBeforeAjax":"0","validateOnSubmit":"1","showMessages":"0","errorElement":"label"}},"commerce-cart-add-to-cart-form-2":{"errorPlacement":"4","general":{"errorClass":"error","wrapper":"li","validateTabs":0,"scrollTo":1,"scrollSpeed":"1000","disableHtml5Validation":"1","validateOnBlur":"1","validateOnBlurAlways":"0","validateOnKeyUp":"1","validateBeforeAjax":"0","validateOnSubmit":"1","showMessages":"0","errorElement":"label"}}},"general":{"usexregxp":0,"months":{"January":1,"Jan":1,"February":2,"Feb":2,"March":3,"Mar":3,"April":4,"Apr":4,"May":5,"June":6,"Jun":6,"July":7,"Jul":7,"August":8,"Aug":8,"September":9,"Sep":9,"October":10,"Oct":10,"November":11,"Nov":11,"December":12,"Dec":12}},"groups":{"commerce-cart-add-to-cart-form-2--2":{}}},"ajax":{"edit-plus--2":{"callback":"click_on_plus","event":"mousedown","keypress":true,"prevent":"click","url":"\/system\/ajax","submit":{"_triggering_element_name":"op","_triggering_element_value":"+"}}},"urlIsAjaxTrusted":{"\/system\/ajax":true,"\/node\/2":true}},"merge":true},{"command":"add_css","data":"\u003Cstyle type=\u0022text\/css\u0022 media=\u0022all\u0022\u003E\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/modules\/date\/date_api\/date.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/modules\/field\/theme\/field.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/modules\/node\/node.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/modules\/office_hours\/office_hours.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/modules\/search\/search.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/modules\/user\/user.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/modules\/views\/css\/views.css?ok6i6x\u0022);\n\u003C\/style\u003E\n\u003Cstyle type=\u0022text\/css\u0022 media=\u0022all\u0022\u003E\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/modules\/ctools\/css\/ctools.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/default\/files\/css\/menu_icons.css?ok6i6x\u0022);\n\u003C\/style\u003E\n\u003Cstyle type=\u0022text\/css\u0022 media=\u0022all\u0022\u003E\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/themes\/mysite\/css\/bootstrap.min.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/themes\/mysite\/css\/font-awesome.min.css?ok6i6x\u0022);\n@import url(\u0022https:\/\/mysite.dd:8443\/sites\/all\/themes\/mysite\/css\/style.css?ok6i6x\u0022);\n\u003C\/style\u003E\n"},{"command":"insert","method":null,"selector":null,"data":"\u003Cform class=\u0022commerce-add-to-cart commerce-cart-add-to-cart-form-2 in-stock\u0022 action=\u0022\/node\/2\u0022 method=\u0022post\u0022 id=\u0022commerce-cart-add-to-cart-form-2--2\u0022 accept-charset=\u0022UTF-8\u0022\u003E\u003Cdiv\u003E\u003Cinput type=\u0022hidden\u0022 name=\u0022product_id\u0022 value=\u00222\u0022 \/\u003E\n\u003Cinput type=\u0022hidden\u0022 name=\u0022form_build_id\u0022 value=\u0022form-jtYTUkMyPXfnt0WJSyJWnsSJQJjFBoHUJ_A1arcTkak\u0022 \/\u003E\n\u003Cinput type=\u0022hidden\u0022 name=\u0022form_token\u0022 value=\u0022QS-WSAizLVe5BVqPF4JmfXVmT-GZotZhKrxdFwYyPyU\u0022 \/\u003E\n\u003Cinput type=\u0022hidden\u0022 name=\u0022form_id\u0022 value=\u0022commerce_cart_add_to_cart_form_2\u0022 \/\u003E\n\u003Cinput type=\u0022submit\u0022 id=\u0022edit-minus--2\u0022 name=\u0022op\u0022 value=\u0022-\u0022 class=\u0022form-submit\u0022 \/\u003E\u003Cdiv class=\u0022form-item form-type-textfield form-item-cart-count\u0022\u003E\n  \u003Clabel for=\u0022edit-cart-count--2\u0022\u003ECart count \u003C\/label\u003E\n \u003Cinput type=\u0022text\u0022 id=\u0022edit-cart-count--2\u0022 name=\u0022cart_count\u0022 value=\u00221\u0022 size=\u002260\u0022 maxlength=\u0022128\u0022 class=\u0022form-text\u0022 \/\u003E\n\u003C\/div\u003E\n\u003Cinput type=\u0022submit\u0022 id=\u0022edit-plus--2\u0022 name=\u0022op\u0022 value=\u0022+\u0022 class=\u0022form-submit\u0022 \/\u003E\u003Cdiv id=\u0022edit-line-item-fields--2\u0022 class=\u0022form-wrapper\u0022\u003E\u003C\/div\u003E\u003Cinput type=\u0022hidden\u0022 name=\u0022quantity\u0022 value=\u00221\u0022 \/\u003E\n\u003Cinput type=\u0022hidden\u0022 name=\u0022submit\u0022 value=\u0022In your cart!\u0022 \/\u003E\n\u003C\/div\u003E\u003C\/form\u003E","settings":null},{"command":"insert","method":"prepend","selector":null,"data":"\u003Cdiv class=\u0027messages message-alert alert alert-block alert-success\u0027\u003E\u003Ca href=\u0027#\u0027 class=\u0027close\u0027 data-dismiss=\u0027alert\u0027\u003E\u00d7\u003C\/a\u003E\u003Ch2 class=\u0022element-invisible\u0022\u003EStatus message\u003C\/h2\u003E\u003Cem class=\u0022placeholder\u0022\u003EPepsi 1L\u003C\/em\u003E added to \u003Ca href=\u0022\/cart\u0022\u003Eyour cart\u003C\/a\u003E.\u003C\/div\u003E","settings":null}]

What could be the reason? how can I solve this issue? is there any other way to achieve this?

EDIT: The issue soved; Slick Carousel was conflicting with Drupal-AJAX.

subhojit777’s picture

Status: Postponed » Needs work

The patch in #3 implements the feature partially. We still need a way to incorporate the feature in this module.

Remaining tasks:

  • Implement the feature in module
  • Provide a setting so that user can opt whether or not he wants to change "add to cart" button text once item is added to cart
  • Write tests

I'd be glad to have this feature in this module. Any help would be greatly appreciated.

subhojit777’s picture

Title: Refresh Cart Form button with Ajax after clicking » Change "add to cart" button text with ajax on adding item to cart
Issue summary: View changes
Issue tags: -Needs issue summary update