Custom Drupal Elements for Forms

Posted on July 9, 2013

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.

Learning Drupal? Subscribe to my Drupal articles, tips and tutorials.

![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
<?php

function example_element_info() {
  $elements['gitbucket'] = array(
    '#default_value' => '',
    '#input' => TRUE,
    '#process' => array('example_gitbucket_element_process'),
    '#theme' => array('gitbucket_field'),
    '#theme_wrappers' => array('form_element'),
    '#tree' => TRUE,
    '#value_callback' => 'example_gitbucket_element_value_callback',
  );
  return $elements;
}

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
<?php

function example_gitbucket_element_process($element, $form_state, $complete_form) {

  $element['gitbucket']['source'] = array(
    '#type' => 'select',
    '#empty_option' => '- ' . t('Source') . ' -',
    '#options' => array(
      'github' => 'github://',
      'bitbucket' => 'bitbucket://',
    ),
    '#required' => $element['#required'],
    '#title' => t('Repository Source'),
    '#title_display' => 'invisible',
    '#theme_wrappers' => array(),
  );

  if (isset($element['#default_value']['gitbucket']['source'])) {
    $element['gitbucket']['source']['#default_value'] = $element['#default_value']['gitbucket']['source'];
  }

  $element['gitbucket']['address'] = array(
    '#type' => 'textfield',
    '#size' => 50,
    '#required' => $element['#required'],
    '#title' => t('Repository Name'),
    '#title_display' => 'invisible',
    '#theme_wrappers' => array(),
    '#attributes' => array('placeholder' => 'username/repository'),
  );

  if (isset($element['#default_value']['gitbucket']['address'])) {
    $element['gitbucket']['address']['#default_value'] = $element['#default_value']['gitbucket']['address'];
  }

  return $element;
}

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
<?php

function example_theme() {
  return array(
    'gitbucket_field' => array(
      'render element' => 'element',
    ),
  );
}

function theme_gitbucket_field($variables) {
  $element = $variables['element'];
  $output = '';
  $output .= drupal_render($element['gitbucket']['source']);
  $output .= " "; // This space forces our fields to have a little room in between.
  $output .= drupal_render($element['gitbucket']['address']);
  return $output;
}

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
<?php

function example_gitbucket_element_value_callback($element, $input = FALSE, &$form_state) {
  if ($input !== FALSE) {
    if ($input['gitbucket']['source'] && !$input['gitbucket']['address']) {
      $input['gitbucket']['source'] = '';
    }
    if ($input['gitbucket']['address'] && !$input['gitbucket']['source']) {
      $input['gitbucket']['address'] = '';
    }
    return $input;
  }
  elseif (!empty($element['#default_value'])) {
    return $element['#default_value'];
  }

  return;
}

Value callbacks should be divided into three conditions:

  1. Input is being provided directly. This happens when a form is submitted.
  2. No input is provided, but the field definition has a default value.
  3. No input is provided and there is no default value.

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
<?php

function example_form() {
  $form = array();
  $form['required_field'] = array(
    '#type' => 'gitbucket',
    '#title' => t('Your favorite repository'),
    '#required' => TRUE,
    '#default_value' => variable_get('required_field', array()),
  );
  $form['optional_field'] = array(
    '#type' => 'gitbucket',
    '#title' => t('Your least favorite repository'),
    '#default_value' => variable_get('optional_field', array()),
  );
  return system_settings_form($form);
}

Try it out. Fill in partial input and hit submit. You'll see how our fields behave properly even with missing input.


  1. Technically, you can also use validation hooks for this. 

Comments

Martin MaritimMarch 7, 2014, 7:31 a.m.

Very useful, thanks for posting this.

Reply
DacoAug. 4, 2017, 7:42 p.m.

шрифт - глаза сломать можно...

Reply
Cody SquiresMarch 15, 2019, 6:16 a.m.

Forms for the elements have different of the customs which will not be proceeding on the projects of the reused above there. Drupal have modification part of the best thesis editing services that will example of the resused terms.

Reply
dsdsdMay 6, 2019, 5:56 a.m.

Pour les nouveaux problèmes et les nouvelles choses rencontrées dans le processus de vente, nous pouvons analyser sérieusement, montblanc montres oser nous ouvrir et avancer avec audace nos propres idées et solutions; face aux situations d’urgence, nous devons rester calmes et rationnels, et essayer de résoudre les facteurs défavorables, ne paniquez pas. Main et pied, action impulsive. La résilience reflète la confiance en soi, la sagesse et l'optimisme d'une personne. À cause du même problème, les personnes passives et pessimistes choisissent de fuir et d'abandonner, ne prennent jamais l'initiative de réagir, tandis que les personnes positives et optimistes avancent, omega montres n'abandonnent jamais et enfin surmontent les obstacles.

Reply
fggfggfMay 18, 2019, 2:22 p.m.

Il y a beaucoup de vendeurs qui ont des malentendus dans leur compréhension, la plupart aiment toujours parler en fonction de leurs propres idées ou de la façon dont ils aiment. Par exemple, quand un vendeur voit un client, nous continuons à le présenter: nous disons toujours que nos produits sont différents et que nous pouvons apporter beaucoup d’avantages à nos clients, car ils sont plus populaires que les produits similaires. Malheureusement, les clients ne veulent pas écouter cela. Même si vous le dites bien, cela ne jouera pas un rôle passionnant, montre lamborghini replique mais les clients se sentiront ennuyés. Par conséquent, avant que le vendeur n'ouvre la porte, il devrait d'abord se demander si le client est prêt à l'écouter, sinon, le client se sentira heureux, sinon ce sera en vain.

Reply
Walter D. SmithMay 21, 2019, 10:21 a.m.

Hi! I require custom form in my cheap assignment services website. So please propose any module or can without coding conceivable make in custom structure?

Reply
Online Quran academyJune 19, 2019, 7:01 a.m.

We are innovative so we teach online and offer you the best learning practice https://www.iqraqurancenter.com/

Reply
harisJune 27, 2019, 12:25 p.m.

1st

[url="https://www.google.com/"]2nd[/url]

[url=https://www.google.com]3rd[/url]

[https://www.google.com 4th]

5th

"6th":https://www.google.com/

[A=https://www.google.com/]7th[/A]

Reply
muneer ahmedJuly 10, 2019, 7:38 a.m.

I want you to thank for your time of this wonderful read!!! médium téléphone I definitely enjoy every little bit of it and I have you bookmarked to check out new stuff of your blog a must read blog

Reply
muneer ahmedJuly 13, 2019, 6:08 p.m.

I am looking for and I love to post a comment that vraie voyance par sms "The content of your post is awesome" Great work

Reply
muneer ahmedJuly 16, 2019, 2:12 p.m.

Great job for publishing such a beneficial web site. cvv.me Your web log isn’t only useful but it is additionally really creative too. There tend to be not many people who can certainly write not so simple posts that artistically. Continue the nice writing

Reply
digitincJuly 27, 2019, 4:21 p.m.

Its a great pleasure reading your post. The information here Melanotan 2 10MGS surely be of some help to me. I just played upon your blog and wanted to say that I have really enjoyed reading your blog stations. Thanks for sharing.

Reply
PaverAug. 2, 2019, 1:24 p.m.

I really appreciate this excellent post that you Paver have provided for us. I found your website certain for my needs. I truly like what you have acquired here, really like what you’re saying and the way in which you say it. Thank you for sharing this article.

Reply
samAug. 7, 2019, 5:48 a.m.

Good post but I was wondering if you could write a litte more on this subject? I’d be very thankful if you could elaborate a little bit further. Appreciate it..! domino88

Reply
digitincAug. 8, 2019, 1:50 p.m.

This really is educational content material. I expect that differing peruses will in like way sell my house fast Florida encounter how I feel in the wake of examining your essay. In your blog, everything is good. You may remark on the request arrangement of the blog.

Reply
samAug. 9, 2019, 11:35 a.m.

The web site is lovingly serviced and saved as much as date. So it should be, thanks for sharing this with us. pengeluaran sgp

Reply
muneerAug. 14, 2019, 7:22 p.m.

Tot in de 20e eeuw was Harenkarspel ook een plaatsnaam. Rond 1295 werd er een terp opgeworpen ten oosten van Tuitjenhorn. Het land waarop de terp stond was in handen van de graven van Holland, Tuitjenhorn online services maar geschonken aan de abdij van Egmond.

Reply
samAug. 15, 2019, 10:27 a.m.

You have made some decent points there. I looked on the internet for more information about the issue and found most people will go along with your views on this web site. электро отопление для частного дома

Reply

Post New Comment