Adding your own fields to the field palette of form_builder

Matthias Weiss's picture
Matthias Weiss

When you look into the form_builder module you'll find the form_builder.api.php showing you some example code how to use the API. The problem with this example code is, that it implicates the assumption that the new field you want to add is a complete new field type that you will implement yourself including the webform code for it.

Then there is the form_builder_examples module that uses existing webform fields. In this case, a special node is created for this example form and the palette is filled with existing field elements.

But what if you want to use the existing form_builder palette for webforms and add your own fields to this palette? And further, what if you don't want to implement a new field but just set a few default settings for existing form elements like e.g. a textfield?

Using form_builder.api.php or the form_builder_examples.module files as a guideline will get you nowhere, they don't cover this case.

I asked the form_builder folks for a solution but unfortunately got no replies, so I came up with this solution, not really a beauty but it does the job.

The basic idea of this is to use the code from the form_builder_webform.components.inc and form_builder_webform.module files as a guideline, copy code as little as possible and change as little as possible.

In the new drupal module I created, let's call it my_module, I first copied the code from form_builder_webform.module as an implementation of hook_form_builder_types:

/**
 * Implements hook_form_builder_types().
 * define all form elements that we want to have at display
 */
function my_module_builder_form_builder_types() {

  $fields = array();

  foreach (array('textfield', 'select') as $type) {
    if ($additional_fields = my_module_form_builder_webform_component_invoke($type, 'form_builder_types')) {
      if ($map = _form_builder_webform_property_map($type)) {
        foreach ($additional_fields as $field_name => $field_type) {
          foreach ($map['properties'] as $property_name => $property_info) {
            $additional_fields[$field_name]['properties'][] = $property_name;
          }
        }
      }
      $fields = array_merge($fields, $additional_fields);
    }
  }

  return array('webform' => $fields);
}

As you can see in the loop I'm not iterating over all field types but just the 2 types that I want to customize, textfield and select.

Note further that I'm calling my_module_form_builder_webform_component_invoke and not the original function, that means I also had to implement it by copying and modifying the original.

Here is what my_module_form_builder_webform_component_invoke looks like:

function my_module_form_builder_component_invoke($component_type, $callback) {
  $args = func_get_args();

  // First try invoking the callback in the webform component itself.
  $result = call_user_func_array('webform_component_invoke', $args);
  if (isset($result)) {
    return $result;
  }

  // Otherwise look for a default implementation provided by this module.
  $component_type = array_shift($args);
  $callback = array_shift($args);
  $function = '_my_module_' . $callback . '_' . $component_type;
  module_load_include('inc', 'form_builder_webform', 'form_builder_webform.components');
  if (function_exists($function)) {
    return call_user_func_array($function, $args);
  }
}

The important change is in this line:

  $function = '_my_module_' . $callback . '_' . $component_type;

I had to do this in order to get my custom callback functions called. Now finally I can define my custom fields by copying the code from form_builder_webform.components.inc and modifying the existing implementations for textfield and select types.

/**
 * Implements _my_module_form_builder_types_component().
 */
function _my_module_form_builder_types_textfield() {
  $fields = array();

  $fields['first_name'] = array(
    'title' => t('First name'),
    'weight' => -80,
  );
  $fields['first_name']['default'] = _form_builder_webform_default('textfield');
  $fields['first_name']['default']['#title'] = t('First name');

  return $fields;
}

/**
 * Implements _my_module_form_builder_types_component().
 */
function _my_module_form_builder_types_select() {
  $fields = array();

  $fields['title'] = array(
    'title' => t('Title'),
    'properties' => array(
      'default_value',
      'options',
      'multiple',
    ),
    'weight' => -82,
  );
  $fields['title']['default'] = _form_builder_webform_default('select', array('aslist' => TRUE, 'multiple' => FALSE));
  $fields['title']['default']['#options'] = array('1' => t('Mrs'), '2' => t('Mr'));
  $fields['title']['default']['#default_value'] = 1;
  $fields['title']['default']['#title'] = t('Title');
  $fields['title']['default']['#form_builder']['element_type'] = 'select';

  return $fields;
}

There is one important detail in the _my_module_form_builder_types_select function I want to lead your attention to. I'm defining my Title field to be a select element, I'm doing this with this line

  $fields['title']['default'] = _form_builder_webform_default('select', array('aslist' => TRUE, 'multiple' => FALSE));

The aslist key is set to TRUE and hence this should give me a select element. But this alone doesn't do it, so that is why this line is important:

  $fields['title']['default']['#form_builder']['element_type'] = 'select';

If you don't specify it, form_builder will get confused - when you drag & drop this Title field into your webform it will be dropped as a select field, but it will turn into a radio button field after you saved it.

Another small detail of interest is the use of the weight attribute for each field. This attribute defines the ordering of field elements in the palette, fields that have weight attributes with smaller or negative values will move upword in the palette order, fields with larger positive numbers in the weight attribute will be ordered lower. As the attribute name suggests,  heavy (big positive number) weights sink down, light weight weights float up.

Share this!

Comments

I just wanted to say thanks for sharing your solution; you saved me a gazillion hours of time & stress trying to figure this out. I was able to use your code to add a signature field onto the form builder palette with minimal tweaks.

Pages