C H A P T E R  24

images

Writing a Major Module

by Benjamin Melançon

“You need me and I need you / Without each other there ain't nothing people can do.”

—Aretha Franklin (Think)

The dictionary definition of “module” is “one of multiple distinct but interrelated parts that can be used to construct a more complex structure.” That describes how complex web sites are built with Drupal: module by module. (And you didn't think anything in Drupal was sensibly named.)

A module, by definition, never exists on its own but rather in relation to other modules with which it forms a working whole. Drupal's modularity makes it possible for separately written and maintained projects to extend what Drupal can do.

images Note The fact that modules never exist on their own has legal implications by the interpretation given by legal advisors to the Drupal Associations. Drupal is Free Software licensed under the GNU General Public License (GPL). Because a Drupal module is by definition a derivative work, any module we make and distribute must be available for anyone else to see, copy, and modify (that is, modify their own copy!). Modules built for ourselves or a single client are not considered distributed, but it is worth noting that the ethos of sharing and collaboration—with its range of practical benefits—is backed by a legal framework.

In the terms used in this chapter, a basic module is a module that extends Drupal and a major module is additionally meant to be extended itself. A major module is modular (ready to be modified or extended), a mirroring of Drupal's modularity. By this definition, LoginToboggan (drupal.org/project/logintoboggan) is a basic module, as it changes the way Drupal works, and Advanced help (drupal.org/project/advanced_help) is a major module, as it exists for other modules to plug in to. The more common term for this latter type of module is API module, but that term is reserved in this chapter for a pure API module.

A major module has an API for other modules to use. API, recall, stands for Application Programming Interface. It is how code talks to code (when being proper and going through official channels). The hook invocations and implementations (such as the ones described in Chapter 19), plus the utility functions available to our module, are Drupal's API.

The module we will begin to build in this chapter will enable other modules to extend it just as it extends Drupal 7. Called Form messages, its goal is to do immediate inline validation of form elements. (Originally named AJAX form messages, this module was re-named as during development it became clear that only doing AJAX calls was not the best approach.) It will modify forms to have inline notices and errors as provided by an API and accompanying UI. Other modules will be able to use the Form messages API to provide error messages and inline validation routines. This module, therefore, will have APIs coming and going: making use of Drupal's APIs to build on Drupal (like every module must) and defining its own API so other modules can build on it. We won't complete the module or even get close in this chapter (though the work and documentation will continue at dgd7.org/strategy). Instead, the groundwork will be laid for a major module.

This chapter has two main goals:

  • To present a module development strategy (along with a bunch of tips) that will help set you up for building any kind of API-providing module.
  • To cover some concepts core to much of Drupal 7 development: entities and fields.

How Not to Build a Module

This section is not a worst-case scenario module-making disaster story (although negative examples are quite fun and informative—for a classic satire of four Drupal versions old and still applicable, see Nick Lewis' Road to Drupal Hell, drupal.org/node/77487). No, the premise of this section is simpler than that: the best module is one you do not have to build.

When setting out to build a module, you will want to do the due diligence of searching for a module to meet your need. You will want to be certain you are not duplicating someone else's work. If after searching drupal.org and the World Wide Web you haven't found an existing module doing what you want to do, go to groups.drupal.org (often abbreviated g.d.o by Drupalistas) and join the Contributed Module Ideas group, groups.drupal.org/contributed-module-ideas. Here you can post a discussion (at groups.drupal.org/node/add/story?gids[]=5445)—entirely beside the point, but you can see from this URL that the machine name for a g.d.o discussion is story and the node ID of the contributed module ideas group is 5,445. Remember to keep checking back at your post if you don't have notifications set up! Ask IRC, including in #drupal-contribute, which modules exist in the problem space you are addressing.

images Tip If you find a similar project already developed or in progress, join forces with other developers whenever possible. Module duplication not only wastes your time as a developer, but it makes it harder for users and contributors to choose what to use or where to put their effort. See drupal.org/node/23789 for more information.

For the Form Messages module, a search for Drupal AJAX form messages, drupal AJAX form validation, and drupal inline form validation, among other variations, did not come up with a comparable module or effort. I posted the module's goals in the Contributed Module Ideas group and cross-posted it to the Form API and Usability groups (see this post at groups.drupal.org/node/113564).

Nothing came of asking the final attempt, #drupal-contribute: “What work (if any) has been done in Drupal toward giving immediate, inline feedback when filling out forms? (Ideally including for field combinations on node forms.)” You can craft a better question than that, assuredly, but remember the rule of thumb that any question that can be answered with yes or no is not the best one you can ask. (See Chapter 9 for more IRC tips and etiquette.) Any question asked on IRC is a shot in the dark—even if someone knows the answer, they have to be online, see it, and be in a mental state of coherence sufficient to craft an answer. A question about a topic big enough to be a major module, yet that no one is working on already, is even more of a long shot. But that doesn't mean it's not worthwhile.

The mere act of asking—the work of posing the question even if no one joins you in bouncing ideas around—helps you better understand what you are trying to do. You may even be able to begin to answer your own question. I was reminded while asking my questions that there is one place in Drupal core that provides instant feedback when you fill out a form: the password strength indicator. A core model to follow is a very good thing.

Later, as you build your module, report your progress to people who expressed interest (or anyone who might be interested). Your project page and issue queue are good public places to state plans, track progress toward implementing targeted capabilities, and collaborate with others. On the latter point, never count on people participating until long after you've built something useful. See Chapter 37 for how to set up sandbox projects on drupal.org.

Know the Tools Drupal Gives You

On any project, you will get off to a better start (and, subsequently, a better finish) with a thorough knowledge of the tools you have to work with. Some commonly used APIs in Drupal were introduced in Chapter 21. There are many more.

“You can blow a very long weekend trying to walk through [Drupal] with a—what's the opposite of a bird's-eye view?—worm's-eye view.”

—Jeff Eaton

A piece of this worm's-eye view is given in Chapter 34. You can continue the exploration by setting up a debugger and watching Drupal step through its code as it does different things.

Knowing what Drupal provides can help in recognizing if you're trying to make Drupal do something it's not actually good at. That's step one to any project: evaluating potential tools. Just as there are many ways to do something with Drupal, there are many ways to do something without Drupal. If it has anything to do with content, users, permissions, showing things on a web site, and much more, of course, you probably do want Drupal.

Drupal at its literal core is a collection of APIs. Drupal architect Jeff Eaton highlights not Drupal's hook-providing modules but first and foremost all the useful things that live in the includes directory. Drupal's include files define functions to help with:

  • Menus (routing)
  • Database abstraction
  • Session handling
  • Caching
  • File storage and stream wrappers
  • Locale and language
  • Theming (Rendering)
  • Forms and form processing
  • Date handling
  • Image manipulation
  • Paging and table sorting
  • Batch processing
  • Tokens
  • E-mail
  • Entities
  • Module system
  • XMLRPC
  • AJAX
  • Unicode and other common utilities
  • Updates
  • … and more

Nobody automatically remembers all these helper libraries (includes/graph.inc provides a function to do a depth-first search on a directed acyclic graph, by the way), let alone knows how to use them all, but they are our baseline, always-present toolbox before we even begin to enable or download modules. Drupal's core modules, of course, provide additional critical functionality, such as user handling and input filtering. Contributed modules can provide their own utility functions and APIs, and some modules are meant only to provide tools for other modules. CTools and VotingAPI are pure API modules.

Should Your Module Provide an API?

In general, any module you make should provide an API for modules that might want to work with your module or build on it. Also, in general, an API is hard to do well; it will usually need to be revised or expanded as other modules try to use it.

If your module is simple and uses enough of Drupal's APIs, it doesn't need to create its own API. If your module creates its own user interface for settings, though, it should always at the least have an accompanying API for those settings.

Keep API and UI Separate

Anything that can be done in a module by submitting a form (the user interface) should also be doable with a line of code. Indeed, the submit function that handles a form should always use an API function to save the changes.

If you make a database query directly in a submit function, you are doing it wrong. You should never see db_insert(), db_update(), or db_delete(), nor db_ anything, nor drupal_write_record() in a form submission function. This would mean that saving, changing, or deleting the information affected by the form can only be done through the user interface—or by recreating these functions or faking a form submission. This makes it harder and uglier for other modules to work with the services and information your module provides. (Having any database changes in form validation is even worse.) Instead, the form submitting function should be handing data from the form over to an API function that is cleanly abstracted from form submission. (Unfortunately, Drupal core is not yet a consistent model in this regard.)

The ultimate expression of keeping API and UI separate is having the user interface in a separate module in your project, which can be turned off. Drupal core's Fields module is complemented by a Fields UI module, which can be diabled. Views module likewise has a Views UI module, which you can disable.

Use APIs to Hide Complexity

The benefit for another module to build on yours is that the author of that module does not have to think about all the things that your module is doing. This is especially true of modules whose entire point is to provide for common functionality, an API module. Jeff Eaton told the story of the VotingAPI module (drupal.org/project/votingapi) at DrupalCon DC. He was working on one of his first larger sites and needed voting functionality. He found a number of cool voting modules, but none of them worked with each other. “So I took the one with the nicest flash widget and shamelessly ripped out its guts,” he said.

Thus was born VotingAPI, with two functions for getting and setting votes. And after asking other people maintaining voting modules, “Can you make my module a dependency for no good reason?” he had one person take him up on it. Immediately, he had to add three more functions to meet the more refined needs of that module.

“We always get it wrong the first time,” Eaton said. And the second. And the third. The rule of thumb is that APIs need to be tested with at least three implementations—cases in which they are used to do something specific—before you can expect them to be of general utility.

By the time your API can handle three different use cases, it's likely hiding an awful lot of complexity.

images Tip Mercenary module development—developing a module for hire—can be a lot of fun, especially with a good client, because the requirements are figured out and laid out for you. Even with the best requirements, a module, like a web site, will evolve as it is built and tested against real needs. When trying to develop a module that will be useful for many people, consider your client's specification as only one of the use cases—and don't expect all of your development hours to be paid. Making a generally useful module is always more work than a one-off, but you, your client, and the community can benefit in the long run.

Making Your Module Modular

Providing an API for its core functionality is the most important way to make your module extendable by other modules, but not the only way.

There are many ways to make the Form Messages module modular, as there will be in any major module. The common thread for almost all ways is separating functionality into encapsulated pieces so that your module is working with its own APIs internally. When parts of your code go through channels to talk to other parts of your code, these same channels are available to the code in other modules.

Unleashing the Power of Hooking Into Your Module

Just as your module will use the hooks Drupal provides to change its behavior (such as hook_menu() to show a page or do something at a URL), and may very well use hooks that other contributed modules provide (such as hook_views_default() to provide a default view), your module can make hooks available for other modules to implement.

The main API for Form Messages will be offering the ability of modules to define messages. This will probably take the form of defining to allow modules to provide default messages in the same way hook_default_views() allows modules to provide default views. This fundamental hook for Form Messages will be covered in this chapter's online companion, dgd7.org/strategy. It's important to note that when defining hooks, you should document an example implementation of each hook in a modulename.api.php file that you include with your module.

There are lighter-weight ways to allow modules to hook into yours than defining your own complete hook. Whenever your module gathers an array of data, it can give other modules a chance to manipulate that data. The easiest way to do this is the delightful drupal_alter() function. Frequently, a module first defines its own hook to let other modules give it information. Then it uses drupal_alter() to create an alter hook that comes along and lets modules modify this after all the data has been gathered.

images Note A one-line patch adding a drupal_alter() statement to an existing module is frequently the easiest way to get functionality you seek into someone else's module. Adding a big feature that meets others' needs and the module maintainers' approval may be impossible, but convincing a maintainer to commit a change enabling you to build on features from your own module should be much more achievable.

There are, of course, persuasive solidly selfish reasons for opening up our modules to the meddling of the masses. Well-placed hook invocations allow other people to do the hard work for us. The more our module is built this way, the more we can say, as Jeff Eaton did (pausing to straighten his imaginary tie), “Solving that isn't my problem; I just maintain the API.”

When building a pure API module, this focus on maintaining the API and making it possible to address many use cases—but not solving them in your module—is particularly important. Jeff Eaton ended his API module presentation urging us to “stay focused—do one thing really well. Drupal is moving toward things that work together; the key is making them work together really well.” This is the famous Unix philosophy. Doug McIlroy, the inventor of Unix pipes and one of the founders of this Unix tradition, summarized it this way: “Write programs that do one thing and do it well. Write programs to work together.”

images Tip As early in the process as possible, start talking with other people who are doing work in the area in which you are working.

Progressive Enhancement: Making Use of Other Modules If They Are Enabled

If the point of your module is to extend another module or you are calling one of its functions, you have to list it as a dependency. Most of the time, though, your module can be enhanced by another module or your module can enhance another module by providing more choices or power. If your module stores data in any way, for instance, you probably want to expose that data to Views. (In many cases, you can get this for free by using Drupal's Field API.)

Wherever possible, make these module dependencies optional. You don't want to force people to use half a dozen modules in addition to yours. Instead, check if the specific function you want to use exists, and degrade gracefully if it doesn't. In this context, degrading gracefully can mean your module loses some extra functionality but does not break, and it continues to provide its base features. Your goal should be to build your module to have conditional enhancements rather than dependencies.

Many excellent modules are designed to pick up on what other modules offer. This is better thought of as progressive enhancement than graceful degradation because there is no workaround for missing functionality, simply the addition of functionality when modules making it possible are enabled.

For example, Drupal's core Menu module integrates with the Block module but doesn't require it. In the implementation of hook_help(), which takes the path of the current page as an argument (variable piece of information) to act on, there is this code:

  if ($path == 'admin/structure/menu' && module_exists('block')) {
    return '<p>' . t('Each menu has a corresponding block that is managed on the <a
href="@blocks">Blocks administration page</a>.', array('@blocks' =>
url('admin/structure/block'))) . '</p>';
  }

The exciting bit is module_exists('block'). As long as Block module is enabled, Menu module offers each of its menus as blocks. As long as Help module is enabled, you are told about this at the top of the menu administration page. Both uses of other modules are examples of progressive enhancement.

images Note Why would anyone not have Help module enabled? Why would anyone not have Block module enabled? It is not the job of a module author to judge the choices of a site builder. The Block module, for its part, is in fact not (yet) as powerful and flexible as some Drupal sites demand it to be. Some developers use Panels module instead (which includes code to call blocks itself). Others use Context module. Both Panels and Context modules exemplify the modularity of Drupal—to allow replacements of core functionality—and remind us to never make our module require another module if it doesn't absolutely have to.

The use of Drupal's hook system is the easiest way for one module to react to something another module is doing.

You can provide default views and expose your data to Views. You can provide tokens for use in PathAuto and dynamic text areas, and you can provide Drush commands. You can provide basic Drupal core help by implementing hook_help(), another hook provided by a module that no module has to implement. And you can provide pages of Advanced Help documentation by invoking that module's hooks; they will only be used when Advanced Help is enabled.

Getting Started with a Test Environment

Starting a major module warrants setting up a fresh test environment for it. Grab a copy of Drupal, name the installation after the project you are working on, and install it from the command line. See Chapters 2 and 26 for more on Drush, which is what makes this easy. Here are the command-line steps to create a test site using drush site-install (si), naming the site after the module we are developing.

images Note A word on naming—one of the first things you have to do when starting to write a module. Module names, as far as your directory and code, must be only lowercase letters, numbers, and underscores, and must start with a letter. It is recommended that the name be descriptive rather than short, and that if a noun it take the singular form (so as to be compatible with recommended naming practices for classes and database tables). I have violated both recommendations in naming the module formmsgs, to my public disgrace. I don't apologize for avoiding underscores in module names, though; this is based on my paranoia about namespace conflicts (naming the module form_messages and implementing hook_help() would mean that a module named form implementing a potential hook_messages_help() would have a fatal error due to duplicate function names).

cd ~/workspace
cd formmsgs
drush dl drupal --drupal-project-rename=formmsgs
drush si --db-url=mysql://root:rootpass@localhost/formmsgs

By default, drush site-install will use the standard installation profile and give the superuser (user ID 1) the username admin and password admin.

images Tip You can automate these steps even more. For an example of automating the creation of the test site and its installation of Drupal, see dgd7.org/sh.

Next, make a directory for the new module; within sites/default is fine for a test site. (If the modules directory doesn't already exist in sites/default, the -p flag tells mkdir to make it before making the formmsgs directory.) Also, immediately start a Git repository for the module like so:

mkdir -p sites/default/modules/formmsgs
cd sites/default/modules/formmsgs
git init

As you add and edit files, have Git keep track of all of your changes by using the command git add . (used to add both new and changed files, the period signifies adding everything available) and git commit.

Stealing Some Code to Start

I start my .info file by stealing the one from the Unique fields module because I expect to be working closely with this module (or at the very least learning a lot from it), so I can study and borrow at the same time.

drush dl unique_field
cp ../../../all/modules/unique_field/unique_field.info formmsgs.info
gvim formmsgs.info

Modify the contents. Info files are simple, and there won't be much duplicated. But it's nice to start from a template and, if that is also a module you're interested in, that's great. Copying an entire .module file is not such a good idea, but looking at an example and even copying out parts can work nicely.

name = AJAX form messages
description = "[formmsgs] Provides immediate, in-form validation."
core = 7.x

images Note Including the module machine names in the description is a convention (favored by me, if no one else) that helps people searching for the module on their module administration page to find it.

Sharing Your Code in a Sandbox on Drupal.org

All I have is a .info file, but it is great to get in the habit of committing and sharing early and often (see Listing 24–1). The commands for sharing a project are discussed in Chapter 38 and are given to you after you create a project via drupal.org/node/add/project-project.

Listing 24–1. Start Sharing Your Code on Drupal.org

git add .
git commit -m "Initial commit for AJAX form messages module; the .info file."
git remote add origin [email protected]:sandbox/mlncn/910490.git
git push origin master
git checkout -b 7.x-1.x
git push origin 7.x-1.x

The steps in Listing 24–1, tailored to your project, are on your project's Git instructions tab. This is drupal.org/node/910490/git-instructions for Form Messages, and the instructions for your full or sandbox project can be found at the same path (with the node ID of your project substituted in for 910490).

images Tip Sharing, fortunately, works both ways. For best practices in code, look to core and widely worked on and used modules such as Views, Token, Administration Menu, Date, Webform, Devel, Voting API, and others (including those covered in this book such as Commerce in Chapter 25 and Apache Solr in Chapter 31). Know, however, that you may have to go your own path and revise significantly as you learn more. On that note, also sign up for updates to the code and approaches taken by the Form Messages module at dgd7.org/strategy.

Planning Your Approach

I keep saying I'm going to provide an API, but what does that mean? It means a lot more—and in some cases, it means something different—than simply having the user interface in a different module. One way to look at being an API means your module doesn't do anything. It just is. The API for Form Messages would handle interaction with the form, based solely on what it is told to do by other modules.

This means I must distill what's needed to show a message as its essential parts. You can't assume that your module has any way to know on its own when or how it's supposed to do something.

Here are some of the questions Form Messages module must ask, then, of modules that would work with it. Posing this question for your own module will tell you what data the API module must store and process, and how it interacts with these other modules—in short, just what kind of API it needs.

  • What on the form triggers evaluation for a message?
  • What determines if the message will be shown and what information does it need?
  • What does the message say?
  • Does the trigger also run when the form is submitted (normal validation) or only for inline AJAX validation?

Outlining what your application must know is critical to defining what API functions it will need (and the data it must store, discussed in the section “Defining Your Data Model”). However, answering questions like these also helps you figure out what your application must do, and it can definitely be worth making something work before planning out every detail of the API.

Outlining an API

AJAX Form Messages, at its heart, wants to say: “I'm on a form. What do you want me to do when stuff happens to each element?”

The centerpiece of the API must be a hook or other way for modules (including users through a UI module) to register all the information about a message. (For performance reasons, this should probably not be done when loading the form.) I'll get into the details of what information must be registered, expanding on the questions introduced in the “Defining Your Data Model” section.

Once storing and getting this information is taken care of, and a way for modules to alter this information has been created, the fundamental API is complete. Another module could even access the information and replace AJAX Form Messages' implementation of interacting with the form.

Other aspects of the API could include a way for modules to affect the output defaults (such as where messages are placed), but most other potential API functions will be ancillary to what information can be defined about messages and making this information alterable.

Diving Into Doing

If you feel your questions are getting into the realm of the theoretical, it's time to dive into writing some code that produces some result even if you already know you'll want to introduce an API for that. Abstracting out an API can be premature. As noted, however, first asking the kinds of questions the API needs to answer does help you write better code right away, even if you skip implementing some of the API in your first iteration.

In the case of AJAX Form Messages, you're going to want to aggregate and store all the information it needs in a central place, rather than poll all modules each time a form is displayed. But dealing with that is getting in the way of figuring out how the triggers and messages themselves will work. And the questions and answers brought to light by focusing on the API have given rise to lots of ideas and directions to try out.

For instance, what must trigger the error or other message is a function akin to a validation function. Ideally, you'll be able to reuse validation functions to evaluate if an error message should be shown. However, validation functions frequently take an entire form, and I'm talking about AJAX requests that can happen multiple times a form, and that needs to be fast. Efficiency is important. What information has to be sent to the evaluation function?

The whole form is definitely easiest, but maybe it can be made to either expect a form (which could include only the parts you care about, but still be processed by any normal, non-over-zealous validate function) or a single specific element. Upon investigation, it's clear the date validation function date_validate()—see api.drupal.org/date_validate—is one that (despite its parameter name “$form”) clearly expects a form element, not an entire form.

images Note By choosing to use FormAPI, we've already made sure we won't be only limited to node (content) forms, at least not at the fundamental API level.

Investigating Form Elements

AJAX Form Messages is going to be listening in on form elements as people type and then sending messages back to form elements. It will provide a structure for storing a function that is used to check what people type and decide what message to send back. This means it needs a reliable way to identify these elements within and across forms—so you're going to have to take a close look at the structure of forms and the form elements with which they are built. This approach has already been used in Chapter 19, implementing the form alter hook in order to see the structure of site forms.

/**
 * Implements hook_form_alter().
 */
function formmsgs_form_alter(&$form, &$form_state, $form_id) {
  debug($form, $form_id, TRUE);
}

Giving the form ID as the second parameter for debug() is a neat trick to show exactly what form you are dealing with. The third parameter TRUE is a good habit to get into so that debug() uses a less pretty but more robust (less likely to die) function to print the output.

images GotchaIf you have a debug() statement that isn't printing anything at all, check your logging and errors settings at Administration Image Configuration Image Development Image Logging and errors (admin/config/development/logging). Always show messages like this by adding $conf['error_level'] = 2; to your local settings.php file. Show every error by adding four lines to settings.php as described earlier in this book and at dgd7.org/err.

Listing 24–2. Excerpt from Printing the Form Variable on the node/add/article Page

  'title' =>
  array (
    '#type' => 'textfield',
    '#title' => 'Title',
    '#required' => true,
    '#default_value' => NULL,
    '#maxlength' => 255,
    '#weight' => -5,
  ),

All form elements are defined by an array. The title element in Listing 24–2 was an immediate child of the $form array, but it could also have been nested within a group. Its name, ‘title’, is the key to the array shown. Its location (nesting) and name (‘title’), together, are unique on this form but could be used for an entirely different element on another form. Therefore, you have an imprecise instrument with which to target form elements.

Form element location is a bit messy. (Even the phrase “form element location” is awkward.) You have to decide to either try to find form items even if they've been moved inside a fieldset grouping or if you require implementations of form message hooks to say exactly where a form item is in the form array. The more helpful way to find the form element wherever it has moved is done by form_set_error() (api.drupal.org/form_set_error) as used by form_error() (api.drupal.org/form_error) is slower. To simplify things to start, at least, you'll require the full location.

But you're getting ahead of yourself a little bit. Before you figure out how to store the identity of a form element, you should make sure you can do what you need to with a test-case form element.

images Note Investigating the JavaScript that powers the password strength check on user edit pages, you'll discover that it does not use AJAX at all but does the entire password strength check directly in JavaScript. This indicates another possible flexibility for your API: instead of handling the JavaScript yourself, you could allow modules to define custom JavaScript. They might, therefore, not provide a function to call via AJAX at all. (I found the user password JavaScript by looking at the form structure by implementing hook_form_alter() and, seeing that the 'pass' form element was defined as #type 'password_confirm', searching for 'password_confirm'. The password_confirm form type is expanded with the function form_process_password_confirm() in includes/form.inc, which adds the classes 'password-field' and 'password-confirm' to the first and second password fields, respectively. Searching for either class brought me to the JavaScript file modules/user/user.js. There are probably more direct ways to find things, but never hesitate to explore!

Proving a Concept

Time to prove you can take a form element and, when a user types something in it, display a message based on a function in Drupal. But let's raise the difficulty just a little bit; let's experiment with running a validation function.

The way Drupal can assign validation functions to particular form elements is described in the Form API documentation at api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#element_validate (that monstrous URL is linked from dgd7.org/strategy). This would be a perfect anchor for adding AJAX callbacks to an element via a general hook_form_alter() implementation.

Searching Drupal core's modules for 'element_validate' (as with grep -nHR ‘element_validate’ modules/ on the command line from the Drupal web root) brings an example worth trying. On line 77 of modules/image/image.admin.inc the element_validate property is set to use the function image_style_name_validate().

Visiting the administration page for adding image styles (admin/config/media/image-styles/add) with the debug($form, $form_id); in your implementation of hook_form_alter(), you can see the structure of this element, this candidate for inline validation.

  'name' =>
  array (
    '#type' => 'textfield',
    '#size' => '64',
    '#title' => 'Style name',
    '#default_value' => '',
    '#description' => 'The name is used in URLs for generated images. Use only lowercase
alphanumeric characters, underscores (_), and hyphens (-).',
    '#element_validate' =>
    array (
      0 => 'image_style_name_validate',
    ),
    '#required' => true,
  ),

You can't write a module called AJAX Form Messages without a fair amount of asynchronous JavaScript, but this chapter is just covering the basics. See Chapter 17 on JQuery for more about JavaScript, and the online follow-up to this chapter at dgd7.org/strategy for what will surely be a steep learning curve for me.

images Tip If for your module you write a set of JavaScript and/or CSS files that could be considered a package, provide it as a library to other modules by implementing hook_library(), and include it in your own pages by using either #attached['library'] or drupal_add_library(). See api.drupal.org/hook_library and drupal.org/node/756722 for more.

Borrowing directly from the excellent AJAX module in the Examples for Developers project (drupal.org/project/examples), you can use hook_form_alter() to add an AJAX callback to the 'name' form element. None of the examples in Drupal's Form API documentation for AJAX seem to refer to text entry in a form text field as a triggering event, but it does link to api.jquery.com/category/events where there are several options, including keyup().

/**
 * Implements hook_form_alter().
 */
function formmsgs_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'image_style_add_form') {
    $form['name']['#ajax'] = array(
      'callback' => 'formmsgs_image_style_name',
      'event' => 'keyup',
      'wrapper' => 'formmsgs-image-style-name',
    );
    $form['name']['#suffix'] = '<div id="formmsgs-image-style-name">Default message.</div>';
  }
}

Remember, this is all proof of concept and hardcoded to a specific form and element, quite unlike the API we plan to build. It allows you to test that your AJAX callback is in fact called, without having to worry that the failure might be somewhere else.

/**
 * Test callback.
 */
function formmsgs_image_style_name() {
  return 'Change-o presto.';
}

That works! The “Default message.” text under the form is changed to “Change-o presto.” when you started to type anything in the name form field.

images Gotcha When returning a straight string and using the default ‘replace’ method, the entire placeholder element you are matching with the ‘wrapper’ directive is replaced. This includes, for instance, the div with the ID you matched. This means that unless your return text includes the wrapper element and ID again, the AJAX will not be able to find that it to replace or change anything again. The AJAX commands used next are modeled from the Examples project (drupal.org/project/examples) and documented at api.drupal.org/api/group/ajax_commands/7 allow much more.

Independently, you can check what information you have to work with. Here is the same callback function, but this time with both the variables that are passed to it, the form array and the form state array:

function formmsgs_image_style_name($form, $form_state) {
  die(var_export($form,TRUE));
}

From this, you get to see the variables that are passed to your callback via AJAX. See dgd7.org/273 for the full output; the most important thing in there is that we have the current value—what you just typed—of the name element.

function formmsgs_image_style_name($form, $form_state) {
  image_style_name_validate($form['name']);
  $message = form_get_error($form['name']);
  if (!$message) {
    $message = "Default message.";
  }
  $commands = array();
  $commands[] = ajax_command_html('#formmsgs-image-style-name', $message);
  return array('#type' => 'ajax', '#commands' => $commands);
}

This replaces the text “Default message” with the text “Please only use lowercase alphanumeric characters, underscores (_), and hyphens (-) for style names,” as shown in Figure 24–1.

images

Figure 24–1. The validation error message for image style names shown immediately on the form via AJAX.

This looks impressive and fairly clean—run the validation function, fetch the error, and return it using the AJAX html command—but in fact it barely works. It has done its work, however, as a proof of concept inline validation function using a normal validation function. The Please only use lowercase alphanumeric… message, or nothing, is correctly returned each time. Unfortunately, it causes the text area to lose focus when a user is typing. Also, it passes the whole form and form_state variables, not restricting itself to the needed parts, which is too much overhead for per-keystroke validation. Indeed, it's becoming evident that in-line validation for allowed characters like this (as opposed to preventing duplicate names, which would require checking the database) should be done with JQuery alone and no AJAX calls to Drupal.

Remember, this is a proof of concept. Aside from letters lost in the lag while typing, an unwanted progress throbber, and other problems stemming from Drupal's convenient #ajax property (you can take more control using the #path property instead of the #callback property), you're doing unholy things with Drupal's existing validation functions and form_get_error() that may not prove durable. But the concept is proven! You can continue establishing an API while working out performance and implementation details.

Defining Your Data Model

For Form messages to do inline validation, it needs to get the messages it should show, when it should show them, and a whole lot more information. This is part of the API and should be thought of as such, but you're also thinking of it as the data you want to store. Regular form validation is done without a data model per se, but if you are going to be allowing people to both use a UI to define messages and modules, you need a data model where both meet.

This is where you get into a lot of detail about exactly what information AJAX Form Messages needs to know, thinking through what it needs in full detail. Each message can apply to multiple forms— think node forms with the same field, or the search block form and the main search form. It seems legitimate for now for each form message to have a primary perspective from a single form element, even if there are multiple fields relevant to the validation.

Thinking through issue after issue like this, you'll come up with an initial list of data to store. Each message and evaluation rules combination needs to provide the following:

  • A form ID or multiple form IDs or a pattern for matching form IDs.
  • Optional further conditional logic on whether to apply the evaluation and message. (This will frequently be needed, as the form ID is not enough; all content types use the same node form, for instance.)
  • The trigger/receptor form element. This is the place the user is typing or clicking that triggers the message, and also the place that receives the message— where the message should be shown. Perhaps the trigger/receptor can also be multivalue, such as two phone number fields that should receive identical validation and messages.
  • Optional form elements of interest. This can allow the inline validation to take into account the value entered into other form fields, such as requiring a selection in one vocabulary to have a unique combination with a selection in another vocabulary.
  • One evaluation function to run. It receives at least one value: the value in the triggering receptor, and an array of the values in other form elements, if any are specified. A third parameter is also optional, a context array, such as to hold the user ID. If context is provided, it would be an array of callback functions, which are used to create an array of keyed values.
  • A static message to show when the evaluation function returns a hit (anything other than strictly equal to false, “=== FALSE”).
  • A message callback function—an optional replacement to using the message set in the field above, so you can do whatever message you want, but the AJAX Form Messages API doesn't have to care. This callback function would receive the result of the evaluation function and whatever data the evaluation function was given.
  • Is this a warning that allows the form to submit, or an error that will be added to the form's validation routine? (Or an error that is already applied to the form validation routine, which is a much better way of doing things than counting on a module named “Form Messages” to do your form validation for you.)
  • An optional message to show at the default state.
  • An optional message to provide for a successful selection or entry.

And Form Messages module will take care of the rest. Well, once it is coded.

The message will always be shown at the form element that triggers the error. Unless touchpad forms of entry allow some unholy three-finger action, there is only one form element that is truly in play at any time: the one value is being entered or selected in. Call it a rationalization to keep the code simpler, but the user interface should only be showing changes where you are entering text or selecting something, not elsewhere on the form.

How to Store the Data and How to Edit It in the UI

“In theory, theory and practice are the same. In practice, they are not.”

—Yogi Berra

Storing the data and editing it in the UI should be separate questions. They really should. Define the data model. Define its storage. Make things work without a user interface. Build a user interface on top of that.

But the practical matter of re-using the tools Drupal provides changes the approach. It can make sense to think of a complex configuration object, like the one outlined in the previous section, as an entity with fields. This is controversial in Drupal. I personally really like the idea of finally having an administrative interface that tracks who did what, when— and this would be easily possible with revisions on a form message entity.

When it comes to having a user interface that works flawlessly with the same information captured in code, you're asking for the configuration to be exportable, which, if using an entity, means making an entity exportable, CTools style. On the one hand, this is big and scary. On the other hand, it's a natural progression of where Drupal is that some other crazy person must be working on doing the same thing. It's not easy to find, but the indefatigable Wolfgang Ziegler (fago) has documented using his Entity API module to create exportable entities for the very purpose of using them to store configuration. This documentation is at drupal.org/node/1021526. (Unfortunately, that approach to exporting entities is separate from the CTools approach, and the two have not yet developed an integrated method.)

Again, using entities to store configuration is controversial. The upcoming section also serves as an introduction to entities no matter what you use them for! Note that the export I am talking about is not to export entity type definitions (which are in code anyway), but rather to export the content held in entities' fields. This is why this approach is controversial; many feel that data in fields should always be data and never configuration. Is there anything you can do to use a code-based definition of a form message before creating an exportable entity? Maybe, but it seems it would involve coding a storage mechanism when Drupal can provide one.

Ideally, it would be a JSON export because JSON allows safe copy-pasting to import. Allowing people to paste in PHP can't be made safe, and CTools is planning to switch to JSON. Based on playing around with Profile2 and Message modules, which use Entity API, it does indeed export to JSON.

Also ideally, that export would live only in code if the UI were never needed. On this point, unfortunately, Entity API always automatically imports to the database, rather than reading from code at runtime. But on balance, exportable entities using the Entity API module fits your preferences quite well.

Plus, you'll get to use entityFieldQuery(), which all the first-adopter Drupal 7 devs are raging about. EntityFieldQuery (api.drupal.org/EntityFieldQuery) or Views is what to use for displaying entities— avoid creating your own query and display system. See dgd7.org/entities to find examples.

images Tip Make your module exportable. Closely related to good APIs is making your module's configuration exportable. There are two major approaches to aiding the export of configuration in Drupal at the moment: for entities, Entity API, and for anything that you can put in a database table, CTools. The first rule of exportability is not relying on numeric keys, which both these solutions address. CTools is more common and better tested, and it is documented by example in modules such as Views and Panels.

Providing a New Entity Type

With all the caveats that exportable entities are not universally endorsed by Drupal developers, let's go ahead and make one, because it seems to fit our use case well.

While entities are new to Drupal 7, creating them is hardly uncharted territory. The entire node system is now based on entities, as are comments, terms, vocabularies, files, and users. Contributed modules can and do define their own entity types, and so can your module.

When to Create an Entity Type

The most common reason for creating an entity type is to have your own fieldable entities. Always seek to extend existing entities with fields before creating new entities. However, don't use nodes for anything that isn't content—that's the most common time to create your own entities. Commerce module (see Chapter 25) uses entities for products, among other things, because products have distinct needs from content. In the case of products, they in particular have a need for many subtypes that would be, at best, an abuse of content types.

How to Create an Entity Type

Entity types are declared by implementations of hook_entity_info(). See, as usual, the excellent Examples project at drupal.org/project/examples; api.drupal.org/hook_entity_info; and, for an updated list of tutorials and examples online, dgd7.org/entities.

AJAX Form Messages chose to use entities for storing configuration (have I mentioned that is controversial?) in part for the export capability of Entity API (drupal.org/project/entity), a contributed module that adds a lot of capabilities to core entities. This means your entity definition will differ slightly from an implementation based only on core, especially when you take advantage of the capabilities Entity API offers. You can follow the contributed Entity API's online documentation for making an exportable entity at drupal.org/node/1021526.

The first step to using Entity API, as for relying on any module, is to declare it as a dependency—it's easy to forget to do this later, and your users will not thank you when your module breaks their site when they try to enable it.

name = AJAX form messages API
description = "[formmsgs] Provides immediate, in-form notice of validation requirements."
package = AJAX Form Messages
core = 7.x
dependencies[] = entity

There are a few other things to note in this .info file. Because the main module is meant to be an API module and have a UI and several optional supporting modules packaged with it (and possibly more contributed separately), you include API in the name and give it a package directive, which will have all modules given the package “AJAX Form Messages” grouped together on the modules administration page (admin/modules).

When defining an entity, the next step can take place in your .install file. Every entity requires a database table to hold basic information about it. This includes a serial integer ID column (or field, as they are called in the schema definition in Listing 24–3) required for any entity and machine-readable name column, which is critical for the entity being exportable.

Listing 24–3. Implementation of hook_schema() in Form Messages Module's formmsgs.install File

<?php

/**
 * @file
 * DB schema, install, and uninstall functions for AJAX Form Messages.
 *
 * The entity base table is defined here.
 */

/**
 * Implements hook_schema().
 */
function formmsgs_schema() {
  $schema = array();
  $schema['formmsgs'] = array(
    'description' => 'Stores information about all formmsgs entities.',
    'fields' => array(
      'fmid' => array(
        'type' => 'serial',
        'not null' => TRUE,
        'description' => 'Primary Key: Unique form message ID.',
      ),
      'name' => array(
        'description' => 'The machine-readable name of the form message.',
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
      ),
      'label' => array(
        'description' => 'The human-readable name of this form message.',
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
        'default' => '',
      ),
      'status' => array(
        'description' => 'Boolean indicating whether the form message is active.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 1,
      ),
    ) + entity_exportable_schema_fields(),
    'primary key' => array('fmid'),
    'unique keys' => array(
      'name' => array('name'),
    ),
  );
  return $schema;
}

One neat trick to note is + entity_exportable_schema_fields(), which uses a handy function provided by the Entity API module to add a couple more columns to the entity's table. These columns (or fields) are for the exportable status and the name of the providing module, and Entity API needs them for its export capability but saves you the trouble of defining them yourself.

The next step for making a new kind of entity is to implement hook_entity_info() in your module. This is pretty formulaic. A key element is identifyng the base table defined in the implementation of hook_schema() (which we just did in the .install file). A good model for any content-like entity is node_entity_info() in modules/node/node.module, so borrow a little from that and from Entity API's documentation at drupal.org/node/878804. Parts unique to Entity API are the controller class, discussed later, and the ability to set an 'exportable' property to TRUE. See Listing 24–4.

Listing 24–4. Definition of a New Form Message Entity in formmsgs.module

<?php

/**
 * @file
 * Provides immediate, in-form validation requirements.
 */

/**
 * Implements hook_entity_info().
 */
function formmsgs_entity_info() {
  $return = array(
    'formmsgs' => array(
      'label' => t('Form message'),
      'controller class' => 'EntityAPIController',
      'entity class' => 'Formmsgs',
      'base table' => 'formmsgs',
      'fieldable' => TRUE,
      'exportable' => TRUE,
      'entity keys' => array(
        'id' => 'fmid',
        'name' => 'name',
        'label' => 'label',
      ),
      'access callback' => 'formmsgs_entity_access',
      'module' => 'formmsgs',
      'admin ui' => array(
        'path' => 'admin/structure/formmsgs',
        'file' => 'formmsgs.admin.inc',
      ),
      'bundle keys' => array(
        'bundle' => 'name',
      ),
      'bundles' => array(
        'formmsgs' => array(
          'label' => t('Message'),
        ),
      ),
      'view modes' => array(
        'full' => array(
          'label' => t('On form'),
          'custom settings' => FALSE,
        ),
      ),
    ),
  );
  return $return;
}

The 'controller class' is EntityAPIController, which is what makes your entity take advantage of Entity API's capabilities. Defining an ‘entity class’ requires it. In the previous code, the entity class is Formmsgs. This class has to be defined and will be in the next section.

As you want to be able to use Field API to gather and store data for form messages, you set ‘fieldable’ to TRUE. This is core entity functionality, as opposed to the ‘exportable’ property that came from Entity API.

The 'base table' needs to be the name of a table defined in hook_schema(). While the entity type property ‘label’ is simply ‘Form message’ (what to call this type of entity), within the 'entity keys' property, ‘label’ is the column in your base table that holds labels for individual Form messages (also called label). The ID (fmid) and the name (name) columns are likewise matched here in the ‘entity keys’ property.

The entity key 'name' is another feature provided by Entity API; it will allow form messages to be exported with a machine name. Importing and exporting does not work well across deployments or separate sites when trying to use a sequential numeric ID.

All bundle-related properties could be left off because the formmsgs entity will have only one bundle, which in the absence of being told otherwise, Drupal automatically names after itself. The chance to define a single bundle and its label is available and, as with all the code here, follows examples in core, contributed modules, and drupal.org documentation. The links in this chapter (and more) can also be found at dgd7.org/entities.

images Note When creating an entity type without the aid of Entity API, you will likely want to define your own controller class, such as FormmsgsController and extend a Drupal-provided class such as DrupalDefaultEntityController. In it could go a create method and extensions to inherited methods. Classes that aren't used all the time (such as classes used when creating an entity) should live in an outside file identified in .info with the files[] directive. This helps performance on sites not using an opcode cache. In this example, Entity API module is taking care of that for us. Drupal will only load its includes/entity.controller.inc file when it needs to use the EntityAPIController class. EntityAPIController extends DrupalDefaultEntityController and does a whole lot, but should you need more, you can make your own class that extends EntityAPIController.

Setting the property 'entity class' to Formmsgs means you must define this class. If it were more than these few lines it would make sense to put it in an include file referenced from formmsgs.info with the files[] directive.

/**
 * The class used for form message entities.
 */
class Formmsgs extends Entity {

  public $label;
  public $status;

  public function __construct($values = array()) {
    parent:__construct($values, 'formmsgs'),
  }
}

This is a very lightweight extension of Entity API's Entity class; chiefly, it calls the constructor function from the Entity class. It also declares the label and status variables, making them available without errors, even as empty variables when a form message is created.

Defining an Entity Access Callback Function

Entity API requires an access callback function. In the 'access callback' property for the previous implementation of hook_entity_info(), you named the function formmsgs_entity_access(). When listing the names of callback functions, the parenthesis () are left off. This access callback function is adapted from entity_metadata_comment_access() in entity/modules/callbacks.inc.

/**
 * Access callback for Entity API-provided formmsgs administration section.
 *
 * @TODO Patch Entity API to accept hook_menu style 'access arguments' to
 * make this function unnecessary for the straight user_access() case.
 */
function formmsgs_entity_access($op, $entity = NULL, $account = NULL) {
  return user_access('administer formmsgs'),
}

Entity API requires an access callback in return for it providing an entity management and editing user interface. As noted in the @TODO, it seems when all you want to do is determine access based on a simple user permission, you should be able to provide the permission string as an argument and not create your own access callback to wrap user_access(). Either way, for the access callback to work and have permissions specific to the entity, you need to define a permission or two for the new entity.

Defining a Permission

Chapter 21 advised to avoid defining a new permission if not necessary, but you should define a new permission if your module makes something new possible that site administrators should be able to allow or deny depending on users' roles. Listing 24–5 shows permissions being defined.

Listing 24–5. Excerpt from System Module's Implementation of hook_permission()

/**
 * Implements hook_permission().
 */
function system_permission() {
  return array(
    'administer modules' => array(
      'title' => t('Administer modules'),
    ),
    'administer site configuration' => array(
      'title' => t('Administer site configuration'),
      'restrict access' => TRUE,
    ),
// ...
  );
}

The 'restrict access' => TRUE directive instructs Drupal to print a notice under the permission name (after the description, if any) on the Permissions administration page: Warning: Give to trusted roles only; this permission has security implications. It has no other purpose; it is a convenience for module builders that provides a consistent way to alert administrators to permissions that should not be tossed around freely.

If you want to give a more precise warning to an administrator about giving a permission, you can put the message directly in the description. The core Filter module does this for the Filtered and Full HTML formats (and any format that is not configured to be the fallback format). Filter module is also dynamically generating a permission for each text format, which is cool, but the custom warning in the description is the present topic and is emphasized in bold here:

  // Generate permissions for each text format. Warn the administrator that any
  // of them are potentially unsafe.
  foreach (filter_formats() as $format) {
    $permission = filter_permission_name($format);
    if (!empty($permission)) {
      // Only link to the text format configuration page if the user who is
      // viewing this will have access to that page.
      $format_name_replacement = user_access('administer filters') ? l($format->name,
'admin/config/content/formats/' . $format->format) : drupal_placeholder($format->name);
      $perms[$permission] = array(
        'title' => t("Use the !text_format text format", array('!text_format' =>
$format_name_replacement,)),
        'description' => drupal_placeholder(t('Warning: This permission may have security
implications depending on how the text format is configured.')),
      );
    }
  }
  return $perms;

images Note One more thing from the excerpt from Filter module's implementation of hook_permission() that's too cool not to remark upon: The title of each text format links to the configuration page if the administering user has access to configure text formats. It is using a permissions, check with the user_access() function to enhance the usability of its permission definitions!

Form messages' implementation of hook_permissions() is not nearly as exciting. It does have a bypass permission, modeled on Unique Field module.

/**
 * Implements hook_permission().
 */
function formmsgs_permission() {
  return array(
    'administer formmsgs' => array(
      'title' => t('Administer AJAX form messages'),
      'description' => t('Allows administrators to configure errors and warning messages.'),
    ),
    'bypass formmsgs' => array(
      'title' => t('Bypass form message errors'),
      'description' => t('Allows users to ignore errors set through form messages.'),
    ),
  );
}

Giving Your Entities an Administrative Interface

Entity API puts a fair amount of work into providing an administrative UI for managing entities based on it, but there's still some routine setup you have to do yourself. Previously, in Listing 24–4, the definition of the Form messages entity via hook_entity_info(), you defined an administrative user interface path and a separate admin file with the lines:

       'admin ui' => array(
'path' => 'admin/structure/formmsgs',
'file' => 'formmsgs.admin.inc',
),

You make good on this promise with a couple functions in a formmsgs.admin.inc file (see Listing 24–6). Entity API picks up the main administrative form automatically when the function has the name of the entity followed by '_form'. (If you had a module that both defined an Entity API enhanced entity with the same name as the module and an old-style, module-owned node type, this callback would conflict with the node form callback— but that's unlikely.)

Listing 24–6. An Administration UI for Form Messages Entities as Defined in formmsgs.admin.inc

<?php
/**
 * @file
 * Forms and functions only needed on administration pages.
 */

/**
 * Generates the form message entity add/edit form.
 *
 * This form is automatically picked up by the administrative UI provided by
 * Entity API module.
 */
function formmsgs_form($form, &$form_state, $formmsg, $op = 'edit') {

  if ($op == 'clone') {
    $formmsg->label .= ' (cloned)';
    $formmsg->name .= '_clone';
  }

  $form['label'] = array(
    '#title' => t('Label'),
    '#type' => 'textfield',
    '#default_value' => $formmsg->label,
  );
  // Machine-readable form message name.
  $form['name'] = array(
    '#type' => 'machine_name',
    '#default_value' => isset($formmsg->name) ? $formmsg->name : '',
    '#disabled' => ($op === 'edit') ? TRUE : FALSE,
    '#machine_name' => array(
      'exists' => 'formmsgs_load_by_name',
      'source' => array('label'),
    ),
    '#description' => t('A unique machine-readable name for this form message. It can only
contain lowercase letters, numbers, and underscores.'),
  );
  $form['status'] = array(
    '#type' => 'checkbox',
    '#title' => t('Active'),
    '#default_value' => $formmsg->status,
  );

  field_attach_form('formmsgs', $formmsg, $form, $form_state);

  $form['actions'] = array('#type' => 'actions'),
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save form message'),
    '#weight' => 50,
  );
  return $form;
}

/**
 * Form API submit callback for the formmsgs entity add/edit form.
 */
function formmsgs_form_submit(&$form, &$form_state) {
  $formmsg = entity_ui_form_submit_build_entity($form, $form_state);
  // Save and go back.
  $formmsg->save();
  $form_state['redirect'] = 'admin/structure/formmsgs';
}

There is a very key line in formmsgs_form() that you are not using yet: the field_attach_form() function. It will allow fields defined for the Form message entity to be filled out along with the label and machine name. You will programmatically define fields in the next section.

The next function, formmsgs_form_submit(), is a simple implementation of a submit function for the form. With a helper function from Entity API, calling the ->save() method on the form message object is all you need.

The form, though, even with Entity API's help, is not enough for you to be able to list and edit Form messages in the administrative UI yet. You need to define a few loading functions that Entity API draws on first. The code in Listing 24–7 goes in formmsgs.module because it has more general utility, but its immediate need is to support the administrative operations.

These load functions are modeled on Profile2 module (also by fago, the creator of Entity API). The odd one out, formmsgs_load_by_name(), is modeled on profile2_get_types(). It fills a special need of the Entity API provided administrative UI.

The other two functions are directly analogous to node_load() and node_load_multiple(). You will see that, like the node loading functions, formmsgs_load() works by calling formmsgs_load_multiple(). This respects Garfield Law: One is a special case of many. For Larry Garfield (crell)'s current eight aphorisms of API design, see my notes on his presentation (data.agaric.com/aphorisms-api-design) or the DrupalCon Chicago recording the notes are based on (or catch him revisiting this topic at a future DrupalCon or Drupal camp).

Listing 24–7. Entity Load Functions Required for Entity API's Administrative UI to Work as Defined in formmsgs.module

/**
 * Fetches an array of all form messages, keyed by the formmsg machine name.
 *
 * Also used to check if machine name is used for an existing form message.
 *
 * @param $name
 *   If set, the form message with the given name is returned.
 * @return $formmsgs
 *   An array of form messages or, if $name is set, a single one.
 */
function formmsgs_load_by_name($name = NULL) {
  $formmsgs = entity_load('formmsgs', isset($name) ? array($name) : FALSE);
  return isset($name) ? reset($formmsgs) : $formmsgs;
}

/**
 * Fetch a form message object.
 *
 * @param $fmid
 *   Integer specifying the form message id.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   A fully-loaded $formmsg object or FALSE if it cannot be loaded.
 *
 * @see formmsgs_load_multiple()
 */
function formmsgs_load($fmid, $reset = FALSE) {
  $formmsg = formmsgs_load_multiple(array($fmid), array(), $reset);
  return reset($formmsg);
}

/**
 * Load multiple profiles based on certain conditions.
 *
 * @param $fmids
 *   An array of form message IDs.
 * @param $conditions
 *   An array of conditions to match against the {formmsgs} table.
 * @param $reset
 *   A boolean indicating that the internal cache should be reset.
 * @return
 *   An array of form message objects, indexed by fmid.
 *
 * @see entity_load()
 * @see formmsgs_load()
 */
function formmsgs_load_multiple($fmids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('formmsgs', $fmids, $conditions, $reset);
}

All of these load functions ultimately rely on Drupal core's entity_load() function (see api.drupal.org/entity_load), for which EntityAPIController provides its own implementation.

You can now create, list, edit, and delete Form message entities, but each one is only a label, a machine name, and a status. To get the full power and flexibility you were seeking when going the entity route, your module needs to define Drupal 7 fields.

Programmatically Creating and Attaching Fields

Fields can be created and attached to entity bundles (such as content types) through the User Interface. This is indeed a key reason and purpose for fields, but they can also be defined in code. In the unusual case of using fields to store configuration information (a use case which is controversial, it should be noted again), AJAX Form Messages doesn't even want the fields configurable in the user interface. To provide users of Form messages with all the fields to match the data model brainstormed earlier, you most certainly need to create and attach the fields in code.

Finding a Model

Several places in core programmatically attach fields to content types, which is analogous to attaching fields to your own entity and bundle. Node module has a function that encapsulates adding the body field to content types, node_add_body_field(), which you can see in modules/node/node.module or at api.drupal.org/node_add_body_field. The Standard installation profile also attaches a Taxonomy field, which can be seen around line 283 in profiles/standard/standard.profile.

For the message, you want a text field. To see precisely what text fields are available for you to use, look directly at Drupal core's Text module, a submodule of the Field module found at modules/field/modules/text.module.

function text_field_info() {
  return array(
    'text' => array(
      'label' => t('Text'),
      'description' => t('This field stores varchar text in the database.'),
      'settings' => array('max_length' => 255),
      'instance_settings' => array('text_processing' => 0),
      'default_widget' => 'text_textfield',
      'default_formatter' => 'text_default',
    ),
    'text_long' => array(
      'label' => t('Long text'),
      'description' => t('This field stores long text in the database.'),
      'instance_settings' => array('text_processing' => 0),
      'default_widget' => 'text_textarea',
      'default_formatter' => 'text_default',
    ),
// ...
  );
}

The maximum length (max_length) of the text field may appear to be 255 characters, so it would behoove you to choose the text_long format. However, that 255 characters is a setting; it can be set to something different, and much higher, when the field is created. (The longest safe value is about 50,000 bytes, see drupal.org/node/1052248.)

Putting this together, to add a field you return to your .install file. It is a two-step process of defining (creating the field) and attaching to an entity (creating an instance of the field), and both steps can go together in an implementation of hook_install().

/**
 * Implements hook_install().
 */
function formmsgs_install() {
  // Define a field.
  $field = array(
    'field_name' => 'field_message',
    'type' => 'text_long',
    'entity_types' => array('formmsgs'),
    'translatable' => TRUE,
  );
  $field = field_create_field($field);

  // Attach a field.
  $instance = array(
    'field_name' => 'field_message',
    'entity_type' => 'formmsgs',
    'label' => t('Message'),
    'bundle' => 'formmsgs',
    'description' => t('Message to show on error.'),
    'widget' => array(
      'type' => 'text_textarea',
      'weight' => -5,
    ),
  );
  field_create_instance($instance);
}

The process of defining a field is a simple matter of creating an array of information about the field and calling field_create_field() on that array. Attaching a field works analogously. It does not need to be handed the field you created; instead it uses the same field name as the field you just created and also provides the name of the entity type you are attaching to. It then optionally takes instance-level settings. The real trick is looking through core and contrib .install files for examples of different fields. You can also, if necessary, define your own field types. The creation of custom field types for AJAX Form Messages will be documented at dgd7.org/strategy.

images Note Remember that once you have released a beta version of your module (at which point users expect to be able to upgrade safely), you need to define and attach any new fields in implementations of hook_update_N() in addition to hook_install().

Define Done

“Walking on water and developing software from a specification are easy if both are frozen.”

—Edward V. Berard

This section on defining “done” should have come at the beginning, of course. (There's a reason I didn't write Chapter 10 on project planning and management.) But don't let that scare you off some good advice: clearly define goals first.

Almost any project can be almost infinitely extended; the more you work on it, the more cool things you will think about doing. Defining early the minimum criteria needed to ship helps maintain focus. Use your own issue queue to post feature requests, but don't let them get in the way of getting to done. Ship it to meet a first use case, and try to budget time to revisit it later.

images Tip What you do not want to cut corners on is leaving yourself the flexibility to do things differently later. You want to encapsulate functionality and define boundaries and interfaces between parts of your code whenever a decision about how best to do something is best left until later. This also means abiding by your own API and not special-casing the needs of your code; your module should not privilege itself over others. Code as if you will not be able to edit your own code. If you need more flexibility, make that flexibility available to other modules also.

Of course, in open source, anyone can then decide it is not done enough and put some work in themselves. One of the great strengths of the Drupal community is the frequency with which new people coming to a project have made significant contributions, including by taking over maintainership for established modules.

For this module, in its purpose as a case study, done is defined as this book needs to be published. Follow the continuing adventure of the Form message module at dgd7.org/strategy and drupal.org/project/formmsgs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.13.29