There are lots of ways to learn about something, and everyone learns in a different way, having best results with different options: some people get better results with articles, others with videos, or others with examples. My favorite way is with examples. And when trying to learn how to achieve something with Drupal, my favorite examples are Drupal core itself.
If you need to get something done and not sure where to start, try to think: where have I seen that before?
You might even have an example in your codebase already. This article covers a real use-case I faced with Drupal Commerce, just simplifying it a bit for the sake of keeping it short. The user story is something alike: As a customer, I need to refresh the cart page instead of continuing checkout after editing the units of any order line, so I can see any better discounts that apply because of volume.
So in terms of our form, this will mean: when in the Cart form page, disable the Checkout button when the user edits any quantity field, and enable the Update cart one.
But for a better user experience, we want the user to notice which line item they modified. Where have I seen that before? In the locale translations interface.
If you enable the locale module, and add a second language to your site, you have an interface translation page where you can fill the translations of the interface strings.
So we will look at the code and inspect how that's achieved. For me, the easiest way if you are not familiar with this piece of code is searching the UI strings or the url path of that page in my IDE, and dig until I find how that's working. In this case, if you search for "Changes made in this table" you end up in web/core/modules/locale/locale.admin.js, which is part of the drupal.locale.admin library defined at core/modules/locale/locale.libraries.yml. If you search for that library, you end up finding some forms that include the library with:
$form['#attached']['library'][] = 'locale/drupal.locale.admin';
So that's everything you need.
Let's create our module. Fastest way is using drush, so I executed
ddev drush generate module-standard
And picked commerce_cart_forcerefresh as the name. From the next options, I only generated the libraries file, which I edited to look like:
commerce_cart_forcerefresh:
js:
js/commerce-cart-forcerefresh.js: {}
css:
component:
css/commerce-cart-forcerefresh.css: {}
dependencies:
- core/jquery
- core/drupal
- core/drupal.form
- core/jquery.once
Our css will only be
form#views-form-commerce-cart-form-default-1 tr.changed {
background: #ffb;
}
And our JavaScript:
(function ($, Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.cartForceRefresh = {
attach: function (context) {
var $form = $('form#views-form-commerce-cart-form-default-1').once('cartItemDirty');
var $updateSubmit = $('form#views-form-commerce-cart-form-default-1 #edit-submit').prop('disabled', true);
if ($form.length) {
$form.one('formUpdated.cartItemDirty', 'table', function () {
var $marker = $(Drupal.theme('cartItemChangedWarning')).hide();
$(this).addClass('changed').before($marker);
$marker.fadeIn('slow');
});
$form.on('formUpdated.cartItemDirty', 'tr', function () {
var $row = $(this);
var marker = Drupal.theme('cartItemChangedMarker');
$row.addClass('changed');
var $updateSubmit = $('form#views-form-commerce-cart-form-default-1 #edit-submit').prop('disabled', false);
var $checkoutSubmit = $('form#views-form-commerce-cart-form-default-1 #edit-checkout').prop('disabled', true);
if ($row.length) {
$row.find('td:first-child .js-form-item').append(marker);
}
});
}
},
detach: function (context, settings, trigger) {
if (trigger === 'unload') {
var $form = $('form#views-form-commerce-cart-form-default-1').removeOnce('cartItemDirty');
if ($form.length) {
$form.off('formUpdated.cartItemDirty');
}
}
}
};
$.extend(Drupal.theme, {
cartItemChangedMarker: function cartItemChangedMarker() {
return '*';
},
cartItemChangedWarning: function cartItemChangedWarning() {
return '
';
}
});
})(jQuery, Drupal, drupalSettings);
Note that the form id is being used in some parts of that code, and that depends on the view id. Take that into account if you are not using the default view, you have multiple line item types, etc. In my real case, I was able to use a class that my theme was adding to that form.
The last thing is ensuring this behavior is added to our form. The easiest for this demo is using hook_form_FORMID_alter(). Our commerce_cart_forcerefresh.module:
And that produces the desired result:
Hope this helps!