Jul 26 2018
Jul 26
I had a hard time getting the #default_value to work for the date form field because the examples module and the documentation uses this approach:

    $form['send_date'] = [
      '#type' => 'date',
      '#title' => $this->t("Send Date"),
      '#default_value' => [

        'month' => 2,
        'day' => 15,
        'year' => 2020,
      ],
    ];

After playing around with it for a while, I was able to get it to work by passing in a YYYY-MM-DD value, using date.formatter (the new way to format_date()).

    $date_formatter = \Drupal::service('date.formatter');
    $form['send_date'] = [
      '#type' => 'date',
      '#title' => $this->t("Send Date"),
      '#default_value' => $date_formatter->format(REQUEST_TIME, 'html_date'),
    ];

Looks like this is a known issue that has also caused others lost time. :( Hopefully this will get rolled out soon.

May 15 2015
May 15

In this article, I am going to show you a clean way of using the Drupal 8 Ajax API without writing one line of JavaScript code. To this end, we will go back to the first custom form we built for Drupal 8 in a previous article and Ajaxify some of its behaviour to make it more user friendly.

Drupal 8 logo

An updated version of this form can be found in this repository under the name DemoForm (the demo module). The code we write in this article can also be found there but in a separate branch called ajax. I recommend you clone the repo and install the module in your development environment if you want to follow along.

DemoForm

Although poorly named, the DemoForm was very helpful in illustrating the basics of writing a custom form in Drupal 8. It handles validation, configuration and exemplifies the use of the Form API in general. Of course, it focuses on the basics and has nothing spectacular going on.

If you remember, or check the code, you’ll see that the form presents a single textfield responsible for collecting an email address to be saved as configuration. The form validation is in charge of making sure that the submitted email has a .com ending (a poor attempt at that but enough to illustrate the principle of form validation). So when a user submits the form, they are saving a new email address to the configuration and get a confirmation message printed to the screen.

In this article, we will move the email validation logic to an Ajax callback so that after the user has finished typing the email address, the validation gets automagically triggered and a message printed without submitting the form. Again, there is nothing spectacular about this behaviour and you will see it quite often in forms in the wild (typically to validate usernames). But it’s a good exercise for looking at Ajax in Drupal 8.

Ajax form

The first thing we need to do is move the email validation logic from the general validateForm() to a method that handles only this aspect:

/**
 * Validates that the email field is correct.
 */
protected function validateEmail(array &$form, FormStateInterface $form_state) {
  if (substr($form_state->getValue('email'), -4) !== '.com') {
    return FALSE;
  }
  return TRUE;
}

As you can notice, we’ve also changed the logic a bit to make sure the email address ends with a .com.

Then, we can defer to this logic from the main validation method to make sure our existing behaviour still works:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {
  // Validate email.
  if (!$this->validateEmail($form, $form_state)) {
    $form_state->setErrorByName('email', $this->t('This is not a .com email address.'));
  }
}

This way even if our form gets somehow submitted (programatically or otherwise), the validation will still be run.

Next, we need to turn to our form definition, specifically the email field, and make it trigger ajax requests based on a user interaction. This will be the act of a user changing the value of the field and removing focus from it:

$form['email'] = array(
  '#type' => 'email',
  '#title' => $this->t('Your .com email address.'),
  '#default_value' => $config->get('demo.email_address'),
  '#ajax' => [
    'callback' => array($this, 'validateEmailAjax'),
    'event' => 'change',
    'progress' => array(
      'type' => 'throbber',
      'message' => t('Verifying email...'),
    ),
  ],
  '#suffix' => '<span class="email-valid-message"></span>'
);

What we did new here is add the #ajax key to the array with some of the relevant keys. Additionally, we added a little markup after the form element as a wrapper for a short message regarding the validity of the email.

The callback inside the #ajax array points to a method inside our form class (validateEmailAjax()) while the event adds a javascript binding to this form element for the jQuery change event. Alternatively, you can also specify a path key instead of a callback, but in our case it would mean having to also set up a route and a controller which seems redundant. And we don’t want the wrapper key because we do not intend to fill up an area with returned content but want to fine grain the actions that result from the callback. For that, we will use Ajax commands.

To learn more about all of this, I encourage you to consult the Ajax API page or the Form API entry for Ajax. There are a handful of other options you can use to further customize the Ajax behavior of your form elements.

Now it’s time to write the callback method inside of our form class. This receives the $form array and $form_state object as arguments coming from the form that triggered the Ajax request:

/**
 * Ajax callback to validate the email field.
 */
public function validateEmailAjax(array &$form, FormStateInterface $form_state) {
  $valid = $this->validateEmail($form, $form_state);
  $response = new AjaxResponse();
  if ($valid) {
    $css = ['border' => '1px solid green'];
    $message = $this->t('Email ok.');
  }
  else {
    $css = ['border' => '1px solid red'];
    $message = $this->t('Email not valid.');
  }
  $response->addCommand(new CssCommand('#edit-email', $css));
  $response->addCommand(new HtmlCommand('.email-valid-message', $message));
  return $response;
}

Simply put, in this method, we perform the validation and return an Ajax response with multiple commands that differ depending on the validation result. With the CssCommand we apply some css directly to the email form element while with the HtmlCommand we replace the contents of the specified selector (remember the suffix from our form element?).

These commands pretty much map to jQuery functions so they are quite easy to grasp. You can find a list of all available commands on this page. And since we are using three new classes inside this method, we must remember to also use them at the top:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\HtmlCommand;

And that is pretty much it. If you clear the cache and reload your form, typing into the email field and removing focus will trigger the callback to validate the email address. You’ll notice the little throbber icon there (which can be changed in the definition) and the short message we defined as well. A correct email address should highlight the field in green and print the OK message while on the contrary the color red is used with an opposite message.

If we had specified a wrapper in the form element definition, we could have returned some content (or render array) which would have been placed inside that selector. So you have the option of choosing between returning content or Ajax commands but I recommend the latter for most cases because they offer a more flexible (and consistent) behavior.

Conclusion

In this article we’ve seen an example of using Ajax to improve our form and make it more friendly to end users. And we have written exactly zero lines of javascript to accomplish this.

In our case, it really is a matter of preference or fancification. But if you are dealing with a 20 field form which has validation on multiple fields similar to this, using Ajax really makes sense. It doesn’t annoy users with having to submit the form only to realize their input is invalid.

Although forms are the main area where you’ll see Ajax in Drupal 8, there are a couple of other ways you can leverage it without writing JavaScript.

Once nice way is to add the use-ajax class on any link. This will have Drupal make an Ajax request to the URL in the href attribute whenever the link is clicked. From the callback you can return Ajax commands and perform various actions as needed. But do keep in mind that jQuery and other core scripts are not loaded on all pages for anonymous users (hence Ajax will gracefully degrade to regular link behaviour). So make sure you include these scripts for anonymous users if you need this behavior.

Jun 16 2014
Jun 16

How to Build a Drupal 8 Module

In the first installment of this article series on Drupal 8 module development we started with the basics. We’ve seen what files were needed to let Drupal know about our module, how the routing process works and how to create menu links programatically as configuration.

In this tutorial we are going to go a bit further with our sandbox module found in this repository and look at two new important pieces of functionality: blocks and forms. To this end, we will create a custom block that returns some configurable text. After that, we will create a simple form used to print out user submitted values to the screen.

Drupal 8 blocks

A cool new change to the block API in D8 has been a switch to making blocks more prominent, by making them plugins (a brand new concept). What this means is that they are reusable pieces of functionality (under the hood) as you can now create a block in the UI and reuse it across the site – you are no longer limited to using a block only one time.

Let’s go ahead and create a simple block type that prints to the screen Hello World! by default. All we need to work with is one class file located in the src/Plugin/Block folder of our module’s root directory. Let’s call our new block type DemoBlock, and naturally it needs to reside in a file called DemoBlock.php. Inside this file, we can start with the following:

<?php

namespace Drupal\demo\Plugin\Block;

use Drupal\block\BlockBase;
use Drupal\Core\Session\AccountInterface;

/**
 * Provides a 'Demo' block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo block"),
 * )
 */

class DemoBlock extends BlockBase {
  
  /**
   * {@inheritdoc}
   */
  public function build() {    
    return array(
      '#markup' => $this->t('Hello World!'),
    );
  }
  
  /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account) {
    return $account->hasPermission('access content');
  }  
  
}

Like with all other class files we start by namespacing our class. Then we use the BlockBase class so that we can extend it, as well as the AccountInterface class so that we can get access to the currently logged in user. Then follows something you definitely have not seen in Drupal 7: annotations.

Annotations are a PHP discovery tool located in the comment block of the same file as the class definition. Using these annotations we let Drupal know that we want to register a new block type (@Block) with the id of demo_block and the admin_label of Demo block (passed through the translation system).

Next, we extend the BlockBase class into our own DemoBlock, inside of which we implement two methods (the most common ones you’ll implement). The build() method is the most important as it returns a renderable array the block will print out. The access() method controls access rights for viewing this block. The parameter passed to it is an instance of the AccountInterface class which will be in this case the current user.

Another interesting thing to note is that we are no longer using the t() function globally for translation but we reference the t() method implemented in the class parent.

And that’s it, you can clear the caches and go to the Block layout configuration page. The cool thing is that you have the block types on the right (that you can filter through) and you can place one or more blocks of those types to various regions on the site.

Drupal 8 block configuration

Now that we’ve seen how to create a new block type to use from the UI, let’s tap further into the API and add a configuration form for it. We will make it so that you can edit the block, specify a name in a textfield and then the block will say hello to that name rather than the world.

First, we’ll need to define the form that contains our textfield. So inside our DemoBlock class we can add a new method called blockForm():

/**
 * {@inheritdoc}
 */
public function blockForm($form, &$form_state) {
  
  $form = parent::blockForm($form, $form_state);
  
  $config = $this->getConfiguration();

  $form['demo_block_settings'] = array(
    '#type' => 'textfield',
    '#title' => $this->t('Who'),
    '#description' => $this->t('Who do you want to say hello to?'),
    '#default_value' => isset($config['demo_block_settings']) ? $config['demo_block_settings'] : '',
  );
  
  return $form;
}

This form API implementation should look very familiar from Drupal 7. There are, however, some new things going on here. First, we retrieve the $form array from the parent class (so we are building on the existing form by adding our own field). Standard OOP stuff. Then, we retrieve and store the configuration for this block. The BlockBase class defines the getConfiguration() method that does this for us. And we place the demo_block_settings value as the #default_value in case it has been set already.

Next, it’s time for the submit handler of this form that will process the value of our field and store it in the block’s configuration:

/**
* {@inheritdoc}
*/
public function blockSubmit($form, &$form_state) {
 
 $this->setConfigurationValue('demo_block_settings', $form_state['values']['demo_block_settings']);
 
} 

This method also goes inside the DemoBlock class and all it does is save the value of the demo_block_settings field as a new item in the block’s configuration (keyed by the same name for consistency).

Lastly, we need to adapt our build() method to include the name to say hello to:

 /**
 * {@inheritdoc}
 */
public function build() {
  
  $config = $this->getConfiguration();
  
  if (isset($config['demo_block_settings']) && !empty($config['demo_block_settings'])) {
    $name = $config['demo_block_settings'];
  }
  else {
    $name = $this->t('to no one');
  }
  
  return array(
    '#markup' => $this->t('Hello @name!', array('@name' => $name)),
  );  
}

By now, this should look fairly easy. We are retrieving the block’s configuration and if the value of our field is set, we use it for the printed statement. If not, use use a generic one. You can clear the cache and test it out by editing the block you assigned to a region and add a name to say hello to. One thing to keep in mind is that you are still responsible for sanitizing user input upon printing to the screen. I have not included these steps for brevity.

Drupal 8 forms

The last thing we are going to explore in this tutorial is how to create a simple form. Due to space limitations, I will not cover the configuration management aspect of it (storing configuration values submitted through forms). Rather, I will illustrate a simple form definition, the values submitted being simply printed on the screen to show that it works.

In Drupal 8, form definition functions are all grouped together inside a class. So let’s define our simple DemoForm class inside src/Form/DemoForm.php:

<?php

/**
 * @file
 * Contains \Drupal\demo\Form\DemoForm.
 */

namespace Drupal\demo\Form;

use Drupal\Core\Form\FormBase;

class DemoForm extends FormBase {
  
  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'demo_form';
  }
  
  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, array &$form_state) {
    
    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your .com email address.')
    );
    $form['show'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    );
    
    return $form;
  }
  
  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, array &$form_state) {
    
    if (strpos($form_state['values']['email'], '.com') === FALSE ) {
      $this->setFormError('email', $form_state, $this->t('This is not a .com email address.'));
    } 
  }
  
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {
    
    drupal_set_message($this->t('Your email address is @email', array('@email' => $form_state['values']['email'])));
  }
  
}

Apart from the OOP side of it, everything should look very familiar to Drupal 7. The Form API has remained pretty much unchanged (except for the addition of some new form elements and this class encapsulation). So what happens above?

First, we namespace the class and use the core FormBase class so we can extend it with our own DemoForm class. Then we implement 4 methods, 3 of which should look very familiar. The getFormId() method is new and mandatory, used simply to return the machine name of the form. The buildForm() method is again mandatory and it builds up the form. How? Just like you are used to from Drupal 7. The validateForm() method is optional and its purpose should also be quite clear from D7. And finally, the submitForm() method does the submission handling. Very logical and organised.

So what are we trying to achieve with this form? We have an email field (a new form element in Drupal 8) we want users to fill out. By default, Drupal checks whether the value input is in fact an email address. But in our validation function we make sure it is a .com email address and if not, we set a form error on the field. Lastly, the submit handler just prints a message on the page.

One last thing we need to do in order to use this form is provide a route for it. So edit the demo.routing.yml file and add the following:

demo.form:
  path: '/demo/form'
  defaults:
    _form: '\Drupal\demo\Form\DemoForm'
    _title: 'Demo Form'
  requirements:
    _permission: 'access content'

This should look familiar from the previous article in which we routed a simple page. The only big difference is that instead of _content under defaults, we use _form to specify that the target is a form class. And the value is therefore the class name we just created.

Clear the caches and navigate to demo/form to see the form and test it out.

If you are familiar with drupal_get_form() and are wondering how to load a form like we used to in Drupal 7, the answer is in the global Drupal class. Thus to retrieve a form, you can use its formBuilder() method and do something like this:

$form = \Drupal::formBuilder()->getForm('Drupal\demo\Form\DemoForm');

Then you can return $form which will be the renderable array of the form.

Conclusion

In this article we’ve continued our exploration of Drupal 8 module development with two new topics: blocks and forms. We’ve seen how to create our own block type we can use to create blocks in the UI. We’ve also learned how to add a custom configuration to it and store the values for later use. On the topic of forms, we’ve seen a simple implementation of the FormBase class that we used to print out to the screen the value submitted by the user.

In the next tutorial we will take a quick look at configuration forms. We will save the values submitted by the user using the Drupal 8 configuration system. Additionally, we will look at the service container and dependency injection and how those work in Drupal 8. See you then.

Feb 07 2013
Feb 07

Episode Number: 

104

The Drupal 7 Conditional Fields module makes it easy to build dynamic and complex forms with Drupal 7 Fields. Any Drupal entity that has fields can pretty much be built into a dynamic and conditional form. The Conditional Fields module essentially lets you set conditions for how specific fields act based on another dependent field on the form. The simplest example is that this module can hide or show a field based on the value of another field. If you are still confused at what this module can do, watch the video to find out more!

In this episode you will learn:

  • The basic Drupal conditional fields terminology
  • How to hide and show Drupal fields based on the values of other form fields
  • How to get the module to work with nested field conditions

Thanks to Drupalize.me for sponsoring this episode!

DDoD Video: 

Oct 13 2011
Oct 13

Theming forms is always a little bit of a handful; designers like to create slick looking forms with rounded corners, drop shadows and custom looking form elements. Unfortunately it's not always an easy task to bring the design to life, but with a bit of css and jquery it is very easy to get your drupal forms looking a little less Drupal-ey.

Overview

We have previously gone over theming the markup of your Drupal forms for easier theming, now let's look at what tools are out there to add a bit of pizzaz to those boring select list and checkboxes. Select lists and checkboxes are incredibly annoying to deal with because unlike text fields you just can do much with css. Without any theming you end up with a very disjointed looking form. I this case we have to look to jquery to save the day. We are going to be using two lightweight jquery scripts to transform those stubborn form element into slick custom checkboxes and select lists.

Preparations

We are going to need these two scripts.

The select box script comes with a .css file as well, you can drop that in your theme's css folder and we will load it like any other css file.

and lastly you will need an empty file to supply the jquery to the theme, I usualy go with the incredibly creative scripts.js.

How-to

First things first you'll want to create a folder in the root of your theme called "scripts" if there isn't one already. Once you have that done you can toss those three files in there. Then we want to load those files through the theme. The way we do that is declare them in your themes .info file like this.

stylesheets[all][] = css/jquery.selectBox.css scripts[] = scripts/jquery.selectBox.js scripts[] = scripts/jquery.checkbox.js scripts[] = scripts/scripts.js

Save your file and give your site's cache a clear at admin/config/development/performance or if you use Administration menu you can flush it through the link they have. Your theme should now be loading your scripts so we should probably start applying them to something.

First we can apply the select box script to all select boxes on the site with this

(function ($) { $(document).ready(function(){ $(function() { /** * Fancy select boxes */ $("SELECT").selectBox(['default']); }); }); })(jQuery);

If you would like something a little more targeted just throw in the css selector you'd like it to apply to.

Under that you will add a similar line for your checkboxes again if you want to target specific ones just add a css selector.

(function ($) { $(document).ready(function(){ $(function() { /** * Fancy select boxes */ $("SELECT").selectBox(['default']); /** * Fancy checkboxes */ $('input:checkbox:not([safari])').checkbox(); }); }); })(jQuery);

What you will get is some nice markup to theme away.

For select boxes you will get this.

<a class="selectBox selectBox-dropdown"> <span class="selectBox-label">Item 15</span> </a>

The ul with all your options will ouput at the bottom of your page and be absolutely positioned below the selectbox.

For checkboxes you will get this.

<label><input style="position: absolute; z-index: -1; visibility: hidden; " type="radio" /> <span class="jquery-checkbox"> <span class="mark"> <img src="empty.png" /> </span> </span> 1st radio button </label>

With these 2 scripts you should be able to get your form looking quite unique and a lot less drupal-ey. I will offer a little disclaimer, it is very easy to get carried away with adding jquery to forms. Having used full jquery form suites in the past I urge you to steer clear of them. I've only created more headaches with them and prefer to bring jquery in to help with only the most stubborn of form elements.

Jul 02 2011
Jul 02

AHAH is a way to allow developers to add new form elements to an existing form (on a page) asynchronously, without reloading the page again. Please refer the detailed fapi on AHAH.

The main steps for creating an AHAH form are:

1. Attach the AHAH property to a form element. (Not all form elements support AHAH)

2. Declare a callback function which should be called 

3. Implement the callback function

Despite what you might feel, or might have read, once you try a simple AHAH form, you will get used to it fairly quickly.

You will the other functions like hook_menu and menu callback just like normal callbacks. Let's assume that the menu callback function invokes the get_AHAH() function by calling drupal_get_form('get_AHAH');

In this example, a select form element will load additional values on the page if something is selected from the drop down.

 

1. Define and attach AHAH to a form element. 

function get_AHAH(){

$form['class_registration']['select_category'] = array(

'#title' => t('I want to ...'),
'#type' => 'select',  
  '#options' =>  array(
'1' => 'Travel to Mars',
'2' => 'Write the best code',
'3' => 'Take a good vacation'
),
'#ahah' => array(
      'path' => 'mywishes',   // The URL path that the hook_menu will trigger the callback on.
      'wrapper' => 'render-mywishes',   // The div id where the returned HTML will be applied.
      'method' => 'replace',
      'effect' => 'fade',
    ),
);
return $form;
}

2. Declare the callback:

When an item is selected from the select control defined in step 1, the menu with URL 'mywishes' will be triggered. The control goes to the hook_menu. In the hook_menu, you need to define the code as follows:

$items['mywishes'] = array(
'title' => 'Show AHAH response to my wishes',
'page callback' => 'react_to_my_wishes',
'access arguments' => 'access content',
    'access callback' => 'user_access',
    'type' => MENU_CALLBACK, 
);

the function 'react_to_my_wishes' will be called for the URL mywishes. And this method will serve as the callback method. The function will look something as follows:

3. Implement the callback function: 

The callback function will be invoked by the hook_menu when the menu request is made. 

function react_to_my_wishes(){

// Make a DB query and fetch some nodes into the $nodes

        foreach($nodes as $node){
    $value_to_display = $node['node']->title;
    $form['myform'][$node['nid']] = array(
     '#type' => 'markup',
     '#value' => '<li><a class="reaction" href=' . $base_url . 'mywishes/' . $node['nid'] . '>' . $value_to_display .  '</a>',
    );
 }
  $output = theme('status_messages') . drupal_render($form);
  drupal_json(array('status' => TRUE, 'data' => $output));
}

This function will fetch some nodes based on the selection in the previous step. We are creating URL links to the fetched nodes. You can create any other control from the fetched values or react in any other way.

Well that is all there is to the AHAH forms.

So you create a AHAH control, define the callback for it and the then implement the callback function. Quite an ahah!! 

I also found this note to be quite useful. 

 

Feb 08 2011
Feb 08

It is fairly trivial and useful to add one or more custom submit handlers in Drupal. in other words, when the user hits the submit button, you can write own handlers to interrupt the normal flow and perform custom actions on the form data. Like what you ask?

You might want to send out mails, preprocess data before submitting it, or store the data in different databases or format (files). There can be seneral usecases where you might want to interfere in the normal code execution flow.

What ever your usecase, here is the way to do it:

1. In the mymodule_form_alter() function (replace mymodule with your module's name), override the #submit property of the form your want to handle.

function mymodule_form_alter(&$form, &$form_state, $form_id){
switch($form_id){
    case 'some_form_id':
      // some code to handle the form. 
       $form['#submit'][] = 'mymodule_mysubmit_handler';
     break;
  }
}

switch($form_id){

    case 'some_form_id':
      // some code to handle the form. 
       $form['#submit'][] = 'mymodule_mysubmit_handler', 'mymodule_second_handler';
     break;
 }


Hopefully you get to use this powerful functionality.

Have fun

Dec 03 2010
Dec 03

Although CCK automatically does some basic validation on your fields that you add to your Drupal content types, there are some cases where you'd like to do some additional validation for your site. One use case that I ran into recently was a basic text field that was being used to house hyperlinks for one of my websites. The text field had already been in place and working perfectly for months. Rather than do something drastic like replacing the field altogether with a field provided by the "Link" module, I decided to do a hook_form_alter to add in my own custom validation function.

Basically you just create a custom module for your site called "form_alterations" or whatever you like. Here's the code for adding in this sort of functionality:

<?php
/**
* Implementation of hook_form_alter().
*/
function form_alterations_form_alter(&$form, &$form_state, $form_id) {
  switch (
$form_id) {
    case
'announcement_node_form':
     
// Simply add an additional link validate handler.
     
$first = array_shift($form['#validate']);
     
array_unshift($form['#validate'], $first, 'form_alterations_link_validate');
      break;
  }
}
/**
* FAPI #validate handler. Make sure the hyperlink they used is correctly formatted.
*/
function form_alterations_link_validate($form, &$form_state) {
  if (!empty(
$form_state['values']['field_web_address'][0]['value']) && !strstr($form_state['values']['field_web_address'][0]['value'], 'http://')) {
   
form_set_error('field_web_address', t('Please enter a full web address starting with "http://".'));
  }
}
?>

hook_form_alter allows you to use the Drupal Form API to make changes to existing forms. In the code above, I'm adding in a call to my custom function called "form_alterations_link_validate" after the basic CCK validation takes place. Then, within the function itself, I check to make sure that the user entered a value and that it contains "http://" at the beginning of what was entered. If you use the code above, please just be sure to change the line with "case 'announcement_node_form':" so that it modifies the form for your node type. My node type was called "announcement".

Sep 20 2010
Sep 20

Today we will talk Form API. The API allows to expand your Drupal installation with highly extendable and secure forms. In this particular post I will show how to create a multi-step survey with file upload ability. Form API is part of Drupal's core and there's no need to download it.

To make it easier to implement multi-step forms we'll use Chaos tool suite, an additional set of API's made to further streamline developing.

All code will reside in a custom module. For the purpose of this tutorial let's call it multi_step_form.

Create a new directory, sites/all/modules/custom/multi_step_form. In it, create multi_step_form.info and multi_step_form.module files, the absolute minimum required for a proper Drupal module.

multi_step_form.info contents:

name = Multi Step Form description = Multi Step Form core = 6.x 

The rest of the code will be done in multi_step_form.module.

The module begins with the implementation of hook_menu().

'Multi Step Form Wizard', 'page callback' => 'multi_step_form_wizard', 'access arguments' => array('access content') ); return $items; } 

Setting up the form with Ctools.

function multi_step_form_wizard() { 
	ctools_include('wizard'); 
	ctools_include('object-cache'); 
	$step = arg(1); 
	$form_info = array( 
		'id' => 'multi_step_form_basic', 
		'path' => "multi_step_form/%step", 
		'show trail' => true, 
		'show back' => true, 
		'show cancel' => true, 
		'show return' => false, 
		'next text' => 'Proceed to next step', 
		'next callback' => 'multi_step_form_basic_add_subtask_next', 
		'finish callback' => 'multi_step_form_basic_add_subtask_finish', 
		'return callback' => 'multi_step_form_basic_add_subtask_finish', 
		'cancel callback' => 'multi_step_form_basic_add_subtask_cancel', 
		'order' => array( 
			'address' => t('Step 1'), 
			'comment' => t('Step 2')
		), 
		'forms' => array( 
			'form_details' => array( 
				'form id' => 'multi_step_form_address'
			), 
			'company_production_facility_address' => array( 
				'form id' => 'multi_step_form_comment' 
			)
		) 
	); 
 
	$form_state = array( 
		'cache name' => NULL
	); 
 
	$multi_step_form = multi_step_form_basic_get_page_cache(NULL); 
 
	if (!$multi_step_form) { 
		$step = current(array_keys($form_info['order'])); 
		$multi_step_form = new stdClass(); 
		ctools_object_cache_set('multi_step_form_basic', $form_state['cache name'], $multi_step_form); 
	};
 
	$form_state['multi_step_form_obj'] = $multi_step_form; $output = ctools_wizard_multistep_form($form_info, $step, $form_state); 
 
	return $output; 
};

We included the needed Ctools, set basic multi-step form parameters, and added two steps.

Now let's define the actual steps. Each one will require a form defined using Form API format, a function to validate, and a function to submit. Let's create a basic form that captures addresses.

function multi_step_form_address(&$form, &$form_state) { 
	$r = array(
		' ',
		'Alberta',
		'British Columbia',
		'Manitoba',
		'New Brunswick',
		'Newfoundland and Labrador',
		'Nova Scotia',
		'Northwest Territories',
		'Nunavut',
		'Ontario',
		'Prince Edward Island',
		'Quebec',
		'Saskatchewan',
		'Yukon',
		'Alabama',
		'Alaska',
		'Arizona',
		'Arkansas',
		'California',
		'Colorado',
		'Connecticut',
		'Delaware',
		'District of Columbia',
		'Florida',
		'Georgia',
		'Hawaii',
		'Idaho',
		'Illinois',
		'Indiana',
		'Iowa',
		'Kansas',
		'Kentucky',
		'Louisiana',
		'Maine',
		'Maryland',
		'Massachusetts',
		'Michigan',
		'Minnesota',
		'Mississippi',
		'Missouri',
		'Montana',
		'Nebraska',
		'Nevada',
		'New Hampshire',
		'New Jersey',
		'New Mexico',
		'New York',
		'North Carolina',
		'North Dakota',
		'Ohio',
		'Oklahoma',
		'Oregon',
		'Pennsylvania',
		'Rhode Island',
		'South Carolina',
		'South Dakota',
		'Tennessee',
		'Texas',
		'Utah',
		'Vermont',
		'Virginia',
		'Washington',
		'West Virginia',
		'Wisconsin',
		'Wyoming'
	); 
 
	foreach ($r as $state): 
		$states[$state] = $state; 
		$multi_step_form = &$form_state['multi_step_form_obj']; 
		$form['street_address'] = array( 
			'#type' => 'textfield', 
			'#required' => 1, 
			'#title' => 'Address', 
			'#default_value' => $multi_step_form->street_address 
		); 
 
		$form['city'] = array( 
			'#type' => 'textfield', 
			'#required' => 0, 
			'#title' => 'City', 
			'#default_value' => $multi_step_form->city
		); 
 
		$form['province_state'] = array( 
			'#type' => 'select', 
			'#default_value' => $multi_step_form->province_state, 
			'#required' => 0, 
			'#title' => 'Province/State', 
			'#options' => $states 
		); 
 
		$form['postal_code_zip'] = array( 
			'#type' => 'textfield', 
			'#required' => 0, 
			'#title' => 'Postal Code/Zip', 
			'#size' => 6, 
			'#maxlength' => 6, 
			'#default_value' => $multi_step_form->postal_code_zip
		); 
 
		$form_state['no buttons'] = TRUE; 
	endforeach;
}; 
 
function multi_step_form_address_validate(&$from, &$form_state) { 
	if ($form_state['values']['street_address'] == '') { 
		form_set_error('street_address', 'Your street address can\'t be empty!'); 
	}; 
}; 
 
function multi_step_form_address_submit(&$from, &$form_state) { 
	$submitted = $form_state['values']; 
	$save_values = array(
		'street_address',
		'city',
		'province_state',
		'postal_code_zip'
	); 
 
	foreach ($save_values as $value):
		$form_state['multi_step_form_obj']->$value = $submitted[$value];
	endforeach;
}; 

You will surely want to modify the fields. Consult the reference along the way. The form will capture basic address and check whether the street address field is not empty. It then stores the information in the form state object.

Now let's add another step with a basic text area to store comments.

function multi_step_form_comment(&$form, &$form_state) { 
	$multi_step_form = &$form_state['multi_step_form_obj']; 
 
	$form['form_comment'] = array( 
		'#type' => 'textarea',
		'#required' => 1,
		'#title' => 'Comment',
		'#default_value' => $multi_step_form->form_comment
	); 
 
	$form_state['no buttons'] = TRUE; 
}; 
 
function multi_step_form_comment_validate(&$from, &$form_state) { 
	if ($form_state['values']['form_comment'] == '') { 
		form_set_error('form_comment', 'Please leave a comment.'); 
	}; 
};
 
function multi_step_form_comment_submit(&$from, &$form_state) { 
	$submitted = $form_state['values']; 
 
	$save_values = array(
		'form_comment'
	); 
 
	foreach($save_values as $value):
	 $form_state['multi_step_form_obj']->$value = $submitted[$value]; 
	endforeach; 
 
};

And now some finishing touches. I decided to store form contents in a node. In order to do so, I created a new content type with matching CCK fields.

function multi_step_form_basic_add_subtask_finish(&$form_state) { 
	$multi_step_form = &$form_state['multi_step_form_obj']; 
	drupal_set_message('Your multi-step form has been successfully submitted!'); 
	$node = new StdClass(); 
	$node->type = 'application'; 
	$node->status = 1; 
	$node->uid = 1; 
	$node->title = $multi_step_form->street_address; 
	$node->field_street_address[0]['value'] = $multi_step_form->street_address; 
	$node->field_city[0]['value'] = $multi_step_form->city; 
	$node->field_province_state[0]['value'] = $multi_step_form->province_state; 
	$node->field_postal_code_zip[0]['value'] = $multi_step_form->postal_code_zip; 
	$node->field_form_comment[0]['value'] = $multi_step_form->form_comment; 
	node_save($node); 
	ctools_object_cache_clear('multi_step_form_basic', $form_state['cache name']); 
	$form_state['redirect'] = 'multi_step_form'; 
	drupal_goto('thank-you'); 
};
 
function multi_step_form_basic_add_subtask_next(&$form_state) { 
	$multi_step_form = &$form_state['multi_step_form_obj']; 
	$cache = ctools_object_cache_set('multi_step_form_basic', $form_state['cache name'], $multi_step_form); 
};
 
function multi_step_form_basic_add_subtask_cancel(&$form_state) { 
	ctools_object_cache_clear('multi_step_form_basic', $form_state['cache name']); 
	$form_state['redirect'] = 'multi_step_form'; 
	drupal_set_message('Multi-step form cancelled.'); 
};
 
function multi_step_form_basic_clear_page_cache($name) { 
	ctools_object_cache_clear('multi_step_form_basic', $name); 
};
 
function multi_step_form_basic_get_page_cache($name) { 
	$cache = ctools_object_cache_get('multi_step_form_basic', $name); 
	return $cache; 
};

If everything goes smoothly, the user should be forwarded to the thank-you page and the information stored in a new node.

Form API offers great ways to capture user information. Coupled with Ctools, the end result can be improved even further.

May 13 2010
May 13

I'm a huge fan of the Webform module (and building Drupal forms in general), and I just today noticed a feature I hadn't previously taken advantage of. This is the ability to programmatically add what are called "pre-built" option lists that can be used in your webforms.

Webform pre-built options list in action

Let's say that you want the user to select his/her age from a dropdown. Age possibilities range from 1-100. Without a pre-built option list, you'd have to type in 0|0, 1|1, 2|2, etc. into the "options" field. You can now do this using the webform api and a small custom module for your site.

In your custom module, you have to declare your new options list. Here's an example:

<?php
/**
* Implementation of hook_webform_select_options_info().
* See webform/webform_hooks.php for further information on this hook in the Webform API.
*/
function YOURMODULENAME_webform_select_options_info() {
 
$items = array();
 
$items['one_onehundred'] = array(
   
'title' => t('Numbers 1-100'),
   
'options callback' => 'YOURMODULENAME_options_one_onehundred'
 
);
  return
$items;
}
?>

After that, you need to write a function that spits out the options list. For this example, that looks like:

<?php
/**
* Build an options list for use by webforms.
*/
function YOURMODULENAME_options_one_onehundred() {
 
$options = array();
  for (
$x=1; $x <= 100; $x++) {
   
$options[$x] = $x;
  }
  return
$options;
}
?>

After that, just enable your custom module, and you should be able to use your new pre-built options list in your webforms. Check out the webform_hooks.php file (included in the Webform module directory) for more information.

Note: This is for Drupal 6 and Webform 3.0-beta 5.

Jan 10 2006
Jan 10

Just a quick note: Drupal 4.7.0 beta 3 is available now, fixing more than 100 bugs since the last beta. If you have any further issues or suggestions for 4.7 — now is the time to speak up, file bug reports, post patches etc.

I haven't had too much time for Drupal development recently, but I guess I should really start updating the poormanscron module now (finally!) and help with getting the German translation up-to-date...

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web