Posted on 2013-07-09 00:41:17+00:00
One of the most cryptic tasks in Drupal is defining a custom form element. That is, defining your own element types to be used and resused in your forms. This post will walk you through the basics. And there's no better way than to learn by example.
For our example element, let's define one that links to a github or bitbucket
repository. These two sites host a lot of open source projects, so it makes
sense to have an element that handles both. Note: this guide applies to Drupal
7. You should be able to make it work on Drupal 6 with some minor modifications.
Let's call our element gitbucket
.
![3]
Part of what makes the Form API in Drupal so powerful is that we can define
compound custom elements. In other words, elements that contain one or more
existing elements. This is quite useful for our use case, where we want our
gitbucket
field to have both, a source
field (github or bitbucket) and a
username/repository
field. So let's get started.
The key to creating a custom element is hook_element_info()
, where we provide
information about our element. So let's define it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
As you can see above, we're providing the Form API with quite a bit of info. Let's walk through this hook step by step.
Our #process
callback is what gets called when we're going to use the element
in a form. This is where we turn our element from a simple definition into the
compound element that we want. So we provide a callback function for it. We'll
get to that in a bit.
We also define a #theme
callback. This is what renders the inside of our
element, including the subelements that we'll define. We have a lot of
flexibility here when rendering. Using this callback, we'll make sure our
element looks nice and readable.
Next we provide #theme_wrappers
. As you'd imagine, this wraps all the elements
after they come back from the #theme
callback. Generally, you can just use
the built-in form_element
and let it do the work for you. That's exactly what
we're doing here.
Last but not least, we define #tree
and #value_callback
. The first makes
sure our nested element structure is preserved for our values, rather than
flattened. The second provides a way to process values before they are returned
to the form submit function. You need to provide a #value_callback
if you
want to do fancy processing on the input.
With all of that declared, we can now implement our #process
function to
construct our very own custom element:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
As you can see, we're nesting elements in the snippet above. Our custom element consists of a select field for the repository source, and a textfield for the actual repository. The actual declaration of these fields is straightforward, and you can use almost anything in the Form API.
When nesting elements, there are a couple of quirks to watch out for. First, you
probably don't want a bunch of nested titles shown. That's why we set
#title_display
to invisible
. But we still declare a title for error
handling purposes. We want the Form API to be able to pinpoint fields required
nested fields and the like.
You also don't want the wrapping HTML structure around each individual nested
element. You want it outside the entire compound element. So for each nested
element, we set #theme_wrappers
to an empty array.
And that's it. We load up default values as necessary, and we're done processing our custom elemenet. Let's move on to theming.
We want a nice inline look for our field, so let's keep the theming simple. First we need to register our function, and then implement it. Like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Now we're ready for the last part: the value callback. As you can see, our custom element has two parts. You should select either or none. But what happens if they just select a source with no repository name? If we marked the field as required, Drupal handles that for us by requesting both fields be populated. The problem appears when our field is not marked as required. In this case, if we fill just one and not the other, we want to treat this as the user having left the field empty. We can use the value callback for this 1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Value callbacks should be divided into three conditions:
In the first part, we handle the edge case of partial input. If only half the field is filled out, we simply treat it as empty. Then we return the modified input.
And that's it. You're done. You've now defined a custom compound field that can be used in any Drupal forms. For example, let's declare a simple system settings form that uses our newly minted element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Try it out. Fill in partial input and hit submit. You'll see how our fields behave properly even with missing input.
Technically, you can also use validation hooks for this. ↩