The other day, I had to tweak the “Add to cart” feature which comes out of the box with Drupal Commerce. Adding and tweaking features in Commerce can be a bit daunting. The package contains a fully fledged API with several subsystems to handle orders, products, checkout,… It’s easy to get lost if you are new. This article explores this use case and the particularities I had to go through.
I was involved in a webshop project. The challenge is to place the product price next to the quantity dropdown form element in the “Add to cart” form, separated with a multiplier sign. The end result should look like: price x quantity. Why? Because visually relating price and quantity makes for good UX. Check out this example of how the end result is supposed to look.
One might think it’s easy to build something similar, but don’t get fooled. It’s trickier then things might appear. Let’s take a look at the Field UI in the administration backend and see how things are actually set up by Commerce. Open up the default view mode of the corresponding product display node type for the product type you want to edit. Here, we control the display of the different fields shown on the product page. The Field UI not only shows you the fields from the product display node type, but also those inherited from the product entity. Including “Commerce price” and “Product variations” fields.
The hairy problem here is that price is a separate field. It’s not an actual part of the add to cart form. The HTML rendered by Commerce also makes a structural divide between the form and the price. Even more so, the displayed price can be changed on the fly through an AJAX call! A product display is an aggregate of products. Each product has its’ own SKU and attributes (color, size,…) which determine the price. So you want that skirt in a yellow bubble pattern instead of plain red? Well, you’ll get a more expensive proposition, if you choose that option from the “Pattern” attribute dropdown. These product attributes are part of the form, yet if you change them, the price field will be dynamically updated too! Whatever we do, we don’t want to break this intermingled functionality.
So, how can we nudge the price into the form in a controlled, sensible way?
CSS Without touching any PHP, you could work your way around this with some clever CSS. Since content and style should be separated, and HTML should only define structure in a document (i.e. product detail page), dealing with this through CSS sounds like a good way to go. Getting the price into the form might prove to be a long stretch though. You’ll need to break out your positionising kungfu and you might spend a lot of time debugging in different browsers, devices,…
JQuery Get your JQuery toolbelt out. With some search and replace actions in the DOM we could fit the price right into the cart HTML which makes for easier styling. However, we take a risk here of breaking the attribute based price refresh, if we are not too careful. Besides, it’s a very lazy hack: the Drupal Way is not the fastest road in this case, but it’s still the correct and least error-prone way of doing things.
The Drupal Way So, first some code. I’ll explain in a bit.
In essence, I just alter the commerce_cart_add_to_cart_form() function (Part of the Cart (commerce_cart) module and add the commerce price field as an extra element to the form. There are two hurdles I have to take: (i) Retrieving the price from the referenced product entities (ii) Rebuild the price field in such a way that the AJAX dynamics will not break.
The correct price is retrieved in commerce_product_reference_entity_view() (Product Reference module) which is actually an implementation of hook_entity_view. The logic in the hook implementation does exactly what I want to do. I dissected what I needed to display only one field - the commerce price - and reapplied that code in my own form alter function. Now, I need to muddle with the code to make it all fit
Next up, I need to attach the price to the form and make it work together with the quantity. It’s a two step proces. First, I’ll move the quantity into a container. Like this:
There is a slight problem here. The commerce_cart_add_to_cart_form_validate() function assumes the existence of $form[‘quantity’][‘#datatype’], but I’m breaking the structure. The validator will start to fail because of that. That’s why I’ve added the expliciet $form[‘quantity’][‘#datatype’] = ‘integer’; Yes, it’s a bit of a hack, but it works. If you really want to do it the clean way, then you would have to replace the standard validation callback with a copy of the function changing the reference to the new location of the quantity field.
Rendering the price field and adding it to the form happens over here:
Although I achieved my goal, I’m left with a bit of mixed feelings about this. I’m happy I pulled it off using the Drupal Way. I didn’t reinvent the wheel with lazy hacks in JQuery and CSS. I did it through a nice form alter. Just the way the Drupal API intends it. Yet, I had to reïmplement a lot of existing logic in a separate function to make sure nothing breaks. That’s plain wrong when you live by code reuse. It’s also a consequence of the trade off made by the Commerce architects separating price from form in two separate Field API fields. The flexibility of a toolbox comes before the adaptability towards a very specific use case.