Executing arbitrary JavaScript from a Forms API #ajax callback
Drupal 7's Forms API included several sweet improvements making it easier than ever to build dynamic forms (with or) without writing custom JavaScript. I'm sure there are better summaries than I have time to bake up right now, but a quick list of pertinent properties includes #attached, #states, and #ajax. This post focuses on the #ajax property and how you can use it to execute arbitrary JavaScript during a form refresh.
A form element that uses #ajax to allow address copying in checkout.
Briefly stated, what I'm calling the form refresh is what happens when Drupal finally asks the callback function pictured above what response to send to the client. This can be one of two things:
- A piece of the form that will be rendered to replace the contents of the wrapper element specified in the #ajax array
- An array of AJAX commands to execute in the browser
We do both in Drupal Commerce, sometimes simply refreshing part of the form that has been updated and other times sending back a nice fat batch of commands to update various parts of the page. We use commands arrays to great effect to make attribute widgets on the Add to Cart form dynamically update product fields that have been rendered into node pages. This is how product images change based on the color selected, for example.
To create these commands, you use functions like ajax_command_replace(), which simply swaps out the contents of an element on the page with something else as if you'd used the first type of response. The other commands are mostly similar manipulations of the DOM, with the full list of commands available here.
I personally think we need more modules brave enough to use ajax_command_alert() in their #ajax callbacks.
One thing you won't find in that list is something like ajax_command_eval() to execute some arbitrary JavaScript back at the client. We've had just such a need a couple times now, once in Commerce Shipping where I wanted to trigger a shipping rate recalculation when an address copy button was clicked and another time on a client site where updating attribute widgets on the Add to Cart form needed to trigger updates in a Flash based product customizer.
Our solution was to use ajax_command_invoke(), which executes a jQuery method with whatever arguments you pass it. Your module should first define a jQuery plugin to hold your code, which is surprisingly easy. Your form them needs to use the #attached property to ensure the JavaScript file is included when your #ajax enabled form element is rendered. Then you just have to invoke your custom jQuery method via ajax_command_invoke(), and you're golden.
This diff from the Commerce Shipping module shows this (hack?) in action. Theoretically, ajax_command_invoke() invokes a jQuery method on some element selected from the DOM, but this example currently just triggers another element's click event using a small jQuery plugin that breaks a design rule by returning nothing to chain to. C'est la vie.