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:

  1. A piece of the form that will be rendered to replace the contents of the wrapper element specified in the #ajax array
  2. 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. Cool

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.

Next Steps for Drupal Commerce

It was my pleasure to demo Commerce Kickstart 2.x at DrupalCon Munich as I presented Next Steps for Drupal Commerce. The relaunched distribution has been a long time coming, and I couldn't be happier with the release our Kickstart team at Commerce Guys has produced. They've proven the idea that we could keep the core of Drupal Commerce a slim eCommerce framework and wrap it in a usability layer that makes it both attractive and easier to administer.

Beyond the sheer amount of polish that has gone into the UI from the installer to the "Shiny" administration theme, its two most exciting features for me are the Inline Entity Form and Views Megarow functionality.


An inline entity form on a product display node with multiple variations.

Inline Entity Form allows us to embed product details forms inside node forms, simplifying the product vs. product display architecture that has made Drupal Commerce notorious (and truly flexible). I could write a whole post about just how we've implemented it, but I'll save the gory details for a later time.

I will at least point out that if this were a single value product reference field, instead of the drag-and-drop table pictured here, the module would simply embed the product related form elements directly into the node form.


A Views Megarow in action after clicking the "Quick Edit" link.

An equally impressive technical implementation, Views Megarow makes it simple to edit groups of products and orders inline in a View through some fancy AJAX row expansion. It gives you easy access to bulk product edit forms and order summaries complete with order activity streams and administrator comments.

Both of these features have been meticulously planned, designed, user tested, and revised to provide a very streamlined user experience for store administrators. With my time mostly focused on other development, I happily applaud our team for putting out such high quality work.

However, even with this release under our belts, we're hardly ready to sit back and relax. We have plenty more to do in the development of Drupal Commerce distributions, contributed modules, and the core framework itself. I spent the final part of my session at DrupalCon proposing the topics and timeline under consideration for a Drupal Commerce 2.x roadmap targeting Drupal 8.

To make sure we're meeting these challenges head on, I proposed a Commerce 2.x planning and development sprint at our offices in Paris in mid-October. The primary goal of the sprint is to research and plan development on Drupal 8, including how to best utilize and work with the various core initiatives, what new modules to add as dependencies or bring into core as new features, and what core code needs refactoring.

This sprint is on the calendar for October 15-19, and while we likely won't be laying down a lot of code, we're still looking for collaborators. We're particularly interested in folks with experience developing for Drupal Commerce on Drupal 7, bonus points awarded to those who have had to work around its limitations.

I'll post more on the topic in the near future, including specific items we intend to address in 2.x development. In the meantime, it's about time for a Commerce 1.4.

On Cats, Bags, and Letting it all out

One of the unexpected challenges in raising money to grow your business is keeping mum about the deal until it's time. Time is likely among the many terms you'll find defined in your contract, and between the day you sign the papers and the day that time actually arrives, you're glowing inside because your investors believe in the potential of your business and want to see you do more.

Investors don't magically make a business plan succeed, nor do they single out the sole source of success behind a business or an idea. This is certainly the case with Commerce Guys' raise announced last week. We know for a fact that our investors get open source as much as they do eCommerce. Even as they evaluated us on our ability to execute our business plan, they evaluated us on how well we work within and alongside the larger Drupal community. When they took a close look, they saw the strengths of the community and the caliber of developers collaborating with us to build Drupal Commerce.

That's what makes it so exciting to share the news - investors and developers who have grown their own businesses and, in the case of the team at Open Ocean with MySQL, their own open source projects have looked closely into both Commerce Guys and Drupal Commerce and felt confident enough to front some serious cash for us to kick our efforts up a notch. Many of these guys have built their own eCommerce systems and understand the challenges we're in a unique position to solve through Drupal 7, Views, Rules, and Commerce, and they're guys who understand the importance of the community in the success of any open source project.

So, we're not crazy after all, and what we've been trying to build with our friends at Commerce Guys and in the Drupal community isn't crazy. Ambitious, sure, but achievable. Our vision for Drupal Commerce remains the same - to see Drupal Commerce become the world's leading open source eCommerce framework. For the last two and a half years, my time has been set aside by Commerce Guys to develop the code (with plenty of help from other brilliant Commerce Guys and community contributors) and grow the community needed to make it happen. Now we've sold the vision to some very smart people with deep pockets outside our normal circles and are eager to see what happens next.

Their affirmation is much appreciated, but so is the money that will let us hire and set aside even more developers to "scale me" out a bit. We need to address immediate concerns pertaining to documentation and community support on DrupalCommerce.org. We'll need to make sure we follow-through on our longstanding 2.x strategy to bring some sanity to the user experience for administrators even as 1.x has privileged developers. All the while, there will be more than enough module maintenance and distribution work to go around!

Addressing these needs for Drupal Commerce should only require a fraction of the money we've raised, but it's a good start that will have an immediate positive impact on the thousands of people already using Drupal Commerce to power their online businesses. If you think you can stomach working with me on a daily basis and have the chops to help us succeed, be sure to get in touch.

Updating Completed Order Timestamps in Drupal Commerce

One of the things I'll miss about Louisville, KY when I move my family to Greenville, SC next month is the buying club we're a part of that sources raw / organic foods and other products from local farms and other good companies. It was put together by a genius I knew through college and church (John Moody, who writes and speaks about food clubs and co-ops), and last year another college buddy (Kane Holbrook) started working with him to help manage the club and oversee deliveries and pick-ups. Among the many yummy things we get through the club are a variety of raw milk cheeses from Welsh Mountain Farms, an Amish farm in Lancaster County, Pennsylvania.

It just so happens that this farmer, yea, though he has no internet access himself, understands the advantages of selling online and asked Kane last year about starting a website to sell his cheese. Kane floated the idea by me, and it quickly became a business plan and partnership to start selling his cheese using Drupal Commerce. Not only did it help the farmer and open a door for Kane and I to do some business together, but it also finally gave me a chance to experience using Drupal Commerce as a site owner and administrator. After two years of planning and development, I finally started eating my own dog food when we launched RealMilkCheese.com in November.

I'll have more to write about the site in the future, as I learned a lot through it and contributed a lot of code from the project to drupal.org. It became the driving factor behind my development of the flat rate shipping module and UI improvements for on-site order management in Drupal Commerce 1.2. It's also the primary example I use to demonstrate multiple flat rate shipping options with conditional availability (think free shipping on orders over a certain amount) and custom discounts to products through an integration with quicksketch's Facebook OAuth module. It was my second foray into theming with Omega and turned up a few tips that I need to pass on to other theme developers with respect to Drupal Commerce components. Finally, it gave me my first experience launching a site on Acquia Dev Cloud using one of those handy coupons they give out at Drupal events.

But the primary purpose for this blog post is to highlight one specific difficulty we encountered while administering orders and the simple solution I put in place to resolve it. In Drupal Commerce, the shopping cart is simply an order in a special status that indicates it's "in progress" and therefore needs to be continually updated to reflect current product prices and availability. By default an authenticated shopping cart order (i.e. for a logged in user) may exist indefinitely until the user finally completes checkout for the order. (There's an issue to expire them through Rules if you're interested in reviewing it. )

We encountered a scenario where a customer added a product to his shopping cart the first week of December but didn't actually purchase the cheese until we sent out a special offer over the holidays for a free 8 oz. cheddar for orders placed in a certain timeframe. It was great to see the immediate effectiveness of the offer (recovered cart sales are a big deal), but because the Orders View on the back-end sorted orders in reverse order by creation timestamp, his order appeared down the page below orders that had already been shipped and marked as Completed. It's not a huge problem, because a filter on the View to only show Pending orders by default would highlight orders needing attention, but it still isn't an ideal user experience.

The thought occurred to me that for our scenario, it would be fine for the creation timestamp of an order to be reset to the current timestamp on checkout completion. As far as we're concerned, that's when an order has been "created" that we actually need to respond to. It actually existed before then, and we'll retain that data in the order's revision log, but we really want to know when the order was finally submitted through a complete checkout process. I started imagining where I would put code to do this when I realized I wouldn't do that at all... a simple Rule would do the trick!

This Rule reacts to the event "Completing the checkout process" and includes a single "Set a data value" action to update the created timestamp to the current time. Because every order save triggers a new revision, we'll have the historical creation date if we want it, but the date that matters to the customer and to administrators (i.e. the date of checkout completion) will now appear properly. If you need the same behavior, you can import the following Rule:

{ "rules_update_the_created_timestamp_to_now" : {
    "LABEL" : "Update the created timestamp to now",
    "PLUGIN" : "reaction rule",
    "REQUIRES" : [ "rules", "commerce_checkout" ],
    "ON" : [ "commerce_checkout_complete" ],
    "DO" : [
      { "data_set" : { "data" : [ "commerce-order:created" ], "value" : "now" } }
    ]
  }
}

Now, I should be used to the level of flexibility we achieved in Drupal Commerce by now, but it still tickles me to no end that it really is that simple to change even obscure aspects of your e-commerce system through the user interface. When I communicate the strengths of the project as an eCommerce framework, concrete examples like this make the approach come to life for merchants and store administrators. It's a clear case of the software conforming to your business needs instead of forcing you to work within its constraints, and I look forward to seeing how else we can trick it out to grow our little cheese business.

Pages