summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRandy Fay2010-09-05 15:33:00 (GMT)
committer Randy Fay2010-09-05 15:33:00 (GMT)
commit8c6243ca0d19d44570808085553dfba829d55ca4 (patch)
tree03662164100e6ca35991423b8130a3c6df1d6dc3
parent01c0106346df2dbfe8078b583fa6101c3fa6b059 (diff)
#870906 by rfay, DjebbZ: Rewrite element_example moving it into form_example, adding tests, adding more element types.
-rw-r--r--element_example/element_example.info5
-rw-r--r--element_example/element_example.module163
-rw-r--r--form_example/form_example.module74
-rw-r--r--form_example/form_example.test63
-rw-r--r--form_example/form_example_elements.inc389
5 files changed, 493 insertions, 201 deletions
diff --git a/element_example/element_example.info b/element_example/element_example.info
deleted file mode 100644
index 2153793..0000000
--- a/element_example/element_example.info
+++ /dev/null
@@ -1,5 +0,0 @@
-; $Id$
-name = Element example
-description = An example demonstrating how a module can define custom form elements.
-package = Example modules
-core = 6.x
diff --git a/element_example/element_example.module b/element_example/element_example.module
deleted file mode 100644
index 6c799b4..0000000
--- a/element_example/element_example.module
+++ /dev/null
@@ -1,163 +0,0 @@
-<?php
-
-// $Id$
-
-/**
- * @file
- * This is an example demonstrating how a module can define custom form
- * elements.
- *
- * Form elements are already familiar to anyone who uses Forms API. Examples
- * of core form elements are 'textfield', 'checkbox' and 'fieldset'. Drupal
- * utilizes hook_elements() to define these FAPI types, and this occurs in
- * the core function system_elements().
- *
- * Each form element has a #type value that determines how it's treated by
- * the Form API and how it's ultimately rendered into HTML. hook_elements()
- * allows modules to define new element types, and tell the Form API what
- * default values they should automatically be populated with.
- *
- * By implementing hook_elements in your own module, you can create custom
- * form elements with their own properties, validation and theming.
- *
- * In this example, we will define a phone number field that is expanded
- * into several text fields for area code, phone number and extention, each
- * of which is validated.
- */
-
-/**
- * Implementation of hook_menu().
- *
- * This just defines a page that we can use to test our form elements.
- */
-function element_example_menu() {
- $items['example/element'] = array(
- 'title' => 'Example element demo',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('element_example_demo_form'),
- 'access arguments' => array('access content'),
- );
- return $items;
-}
-
-/**
- * Implementation of hook_elements().
- */
-function element_example_elements() {
- $type['phonenumber'] = array(
- '#input' => TRUE,
- '#process' => array('element_example_phonenumber_expand'),
- '#element_validate' => array('element_example_phonenumber_validate'),
- '#default_value' => array('areacode' => '', 'number' => '', 'extension' => ''),
- );
- return $type;
-}
-
-/**
- * Our process callback to expand the control.
- */
-function element_example_phonenumber_expand($element) {
- $element['#tree'] = TRUE;
-
- if (!isset($element['#value'])) {
- $element['#value'] = array('areacode' => '', 'number' => '', 'extension' => '');
- }
-
- $element['areacode'] = array(
- '#type' => 'textfield',
- '#size' => 3,
- '#maxlength' => 3,
- '#value' => $element['#value']['areacode'],
- '#prefix' => '(',
- '#suffix' => ')',
- );
- $element['number'] = array(
- '#type' => 'textfield',
- '#size' => 8,
- '#maxlength' => 8,
- '#required' => TRUE,
- '#value' => $element['#value']['number'],
- );
- $element['extension'] = array(
- '#type' => 'textfield',
- '#size' => 10,
- '#maxlength' => 10,
- '#prefix' => t('ext'),
- '#value' => $element['#value']['extension'],
- );
-
- return $element;
-}
-
-/**
- * Our element's validation function.
- *
- * We check that:
- * - the area code is a three digit number
- * - the number is numeric, with an optional dash
- *
- * Any problems are attached to the form element using form_error().
- */
-function element_example_phonenumber_validate($form, &$form_state) {
- if (isset($form['#value']['areacode'])) {
- if (0 == preg_match('/^\d{3}$/', $form['#value']['areacode'])) {
- form_error($form['areacode'], t('The areacode is invalid.'));
- }
- }
- if (isset($form['#value']['number'])) {
- if (0 == preg_match('/^\d{3}-?\d{4}$/', $form['#value']['number'])) {
- form_error($form['number'], t('The number is invalid.'));
- }
- }
- return $form;
-
-}
-
-/**
- * Implementation of hook_theme().
- *
- * This lets us tell Drupal about our theme functions and their arguments.
- */
-function element_example_theme() {
- return array(
- 'phonenumber' => array(
- 'arguments' => array('element'),
- ),
- );
-}
-
-/**
- * Theme function to format the output.
- *
- * We use the container-inline class so that all three of the HTML elements
- * are placed next to each other, rather than on separate lines.
- */
-function theme_phonenumber($element) {
- return theme('form_element', $element, '<div class="container-inline">' . $element['#children'] . '</div>');
-}
-
-/**
- * This is a simple form to demonstrate how to use the phonenumber element we've
- * defined.
- */
-function element_example_demo_form() {
- $form['element_example_test_1'] = array(
- '#type' => 'phonenumber',
- '#title' => t('Phone number 1'),
- '#default_value' => variable_get('element_example_test_1',
- array('areacode' => '123', 'number' => '456-7890', 'extension' => '')
- ),
- '#description' => t('A phone number.'),
- );
-
- $form['element_example_test_2'] = array(
- '#type' => 'phonenumber',
- '#title' => t('Phone number 2'),
- '#default_value' => variable_get('element_example_test_2',
- array('areacode' => '', 'number' => '456-7890', 'extension' => '23')
- ),
- '#description' => t('Another phone number, a fax perhaps?'),
- );
-
- return system_settings_form($form);
-}
diff --git a/form_example/form_example.module b/form_example/form_example.module
index e8f1e3b..cac352c 100644
--- a/form_example/form_example.module
+++ b/form_example/form_example.module
@@ -6,23 +6,28 @@
* Examples demonstrating the Drupal Form API.
*/
+require_once('form_example_elements.inc');
/**
* Implements hook_menu() to set up the URLs (menu entries) for the
* form examples.
*/
function form_example_menu() {
- $items = array();
- $items['form_example/tutorial'] = array(
- 'title' => t('Form Example: Tutorial'),
+ $items['examples/form_example'] = array(
+ 'title' => t('Form Example'),
+ 'page callback' => 'form_example_info',
+ 'access callback' => TRUE,
+ );
+ $items['examples/form_example/tutorial'] = array(
+ 'title' => t('Tutorial'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_1'),
'access callback' => TRUE,
'description' => t('A set of ten tutorials'),
'file' => 'form_example_tutorial.inc',
- 'type' => MENU_NORMAL_ITEM,
+ 'weight' => -20,
);
- $items['form_example/tutorial/1'] = array(
+ $items['examples/form_example/tutorial/1'] = array(
'title' => t('#1'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_1'),
@@ -31,7 +36,7 @@ function form_example_menu() {
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/2'] = array(
+ $items['examples/form_example/tutorial/2'] = array(
'title' => t('#2'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_2'),
@@ -40,7 +45,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/3'] = array(
+ $items['examples/form_example/tutorial/3'] = array(
'title' => t('#3'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_3'),
@@ -49,7 +54,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/4'] = array(
+ $items['examples/form_example/tutorial/4'] = array(
'title' => t('#4'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_4'),
@@ -58,7 +63,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/5'] = array(
+ $items['examples/form_example/tutorial/5'] = array(
'title' => t('#5'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_5'),
@@ -67,7 +72,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/6'] = array(
+ $items['examples/form_example/tutorial/6'] = array(
'title' => t('#6'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_6'),
@@ -76,7 +81,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/7'] = array(
+ $items['examples/form_example/tutorial/7'] = array(
'title' => t('#7'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_7'),
@@ -85,7 +90,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/8'] = array(
+ $items['examples/form_example/tutorial/8'] = array(
'title' => t('#8'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_8'),
@@ -94,7 +99,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/9'] = array(
+ $items['examples/form_example/tutorial/9'] = array(
'title' => t('#9'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_9'),
@@ -103,7 +108,7 @@ function form_example_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
- $items['form_example/tutorial/10'] = array(
+ $items['examples/form_example/tutorial/10'] = array(
'title' => t('#10'),
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_10'),
@@ -113,20 +118,57 @@ function form_example_menu() {
'file' => 'form_example_tutorial.inc',
'weight' => 10,
);
+ $items['examples/form_example/element_example'] = array(
+ 'title' => 'Element example',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('form_example_element_demo_form'),
+ 'access callback' => TRUE,
+ 'file' => 'form_example_elements.inc',
+ );
return $items;
}
+/**
+ * Simple page callback to give feedback to the user about this example.
+ */
+function form_example_info() {
+ return t('The form example provides <a href="!tutorial_url">tutorial examples</a> tied to the <a href="!handbook_url">handbook</a> and <a href="!element_url">form element examples</a>.', array('!tutorial_url' => url('examples/form_example/tutorial'), '!handbook_url' => 'http://drupal.org/node/262422', '!element_url' => url('examples/form_example/element_example')));
+}
/**
* Implements hook_help() to provide a bit of help.
*/
function form_example_help($path, $arg) {
switch($path) {
- case 'form_example/tutorial':
+ case 'examples/form_example/tutorial':
$help = t('This form example tutorial for Drupal 6 is the code from the <a href="http://drupal.org/node/262422">Handbook 10-step tutorial</a>');
+ break;
+ case 'examples/form_example/element_example':
+ $help = t('The Element Example shows how modules can provide their own Form API element types. Four different element types are demonstrated.');
+ break;
}
if (!empty($help)) {
return '<p>' . $help . '</p>';
}
-} \ No newline at end of file
+}
+
+
+/**
+ * Implementation of form_example_elements().
+ *
+ * To keep the various pieces of the example together, this just returns
+ * _form_example_elements().
+ */
+function form_example_elements() {
+ return _form_example_elements();
+}
+/**
+ * Implementation of hook_theme().
+ *
+ * To keep the various parts of the example together, this actually returns
+ * _form_example_element_theme().
+ */
+function form_example_theme() {
+ return _form_example_element_theme();
+}
diff --git a/form_example/form_example.test b/form_example/form_example.test
index 62a411a..ed34beb 100644
--- a/form_example/form_example.test
+++ b/form_example/form_example.test
@@ -13,8 +13,8 @@ class FormExampleTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
- 'name' => 'Form Example tests',
- 'description' => 'Various tests on the dbtng example module.' ,
+ 'name' => 'Form Example',
+ 'description' => 'Various tests on the form_example module.' ,
'group' => 'Examples',
);
}
@@ -28,33 +28,33 @@ class FormExampleTestCase extends DrupalWebTestCase {
*/
function testTutorials() {
// Tutorial #1
- $this->drupalGet('form_example/tutorial');
+ $this->drupalGet('examples/form_example/tutorial');
$this->assertText(t('#10'));
// #2
- $this->drupalPost('form_example/tutorial/2', array('name' => t('name')), t('Submit'));
+ $this->drupalPost('examples/form_example/tutorial/2', array('name' => t('name')), t('Submit'));
// #4
- $this->drupalPost('form_example/tutorial/4',
+ $this->drupalPost('examples/form_example/tutorial/4',
array('first' => t('firstname'), 'last' => t('lastname')), t('Submit'));
- $this->drupalPost('form_example/tutorial/4', array(), t('Submit'));
+ $this->drupalPost('examples/form_example/tutorial/4', array(), t('Submit'));
$this->assertText(t('First name field is required'));
$this->assertText(t('Last name field is required'));
// #5
- $this->drupalPost('form_example/tutorial/5',
+ $this->drupalPost('examples/form_example/tutorial/5',
array('first' => t('firstname'), 'last' => t('lastname')), t('Submit'));
$this->assertText(t('Please enter your first name'));
- $this->drupalPost('form_example/tutorial/4', array(), t('Submit'));
+ $this->drupalPost('examples/form_example/tutorial/4', array(), t('Submit'));
$this->assertText(t('First name field is required'));
$this->assertText(t('Last name field is required'));
// #6
- $this->drupalPost('form_example/tutorial/6',
+ $this->drupalPost('examples/form_example/tutorial/6',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1955),
t('Submit'));
$this->assertNoText(t('Enter a year between 1900 and 2000'));
- $this->drupalPost('form_example/tutorial/6',
+ $this->drupalPost('examples/form_example/tutorial/6',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1855),
t('Submit'));
@@ -62,11 +62,11 @@ class FormExampleTestCase extends DrupalWebTestCase {
// #7
- $this->drupalPost('form_example/tutorial/7',
+ $this->drupalPost('examples/form_example/tutorial/7',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1955),
t('Submit'));
$this->assertText(t('The form has been submitted. name="firstname lastname", year of birth=1955'));
- $this->drupalPost('form_example/tutorial/7',
+ $this->drupalPost('examples/form_example/tutorial/7',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1855),
t('Submit'));
@@ -74,21 +74,21 @@ class FormExampleTestCase extends DrupalWebTestCase {
// #8
- $this->drupalPost('form_example/tutorial/8',
+ $this->drupalPost('examples/form_example/tutorial/8',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1955),
t('Submit'));
$this->assertText(t('The form has been submitted. name="firstname lastname", year of birth=1955'));
- $this->drupalPost('form_example/tutorial/8',
+ $this->drupalPost('examples/form_example/tutorial/8',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1855),
t('Submit'));
$this->assertText(t('Enter a year between 1900 and 2000'));
- $this->drupalPost('form_example/tutorial/8',
+ $this->drupalPost('examples/form_example/tutorial/8',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1855),
t('Reset form'));
$this->assertNoText(t('Enter a year between 1900 and 2000'));
// #9
- $this->drupalPost('form_example/tutorial/9',
+ $this->drupalPost('examples/form_example/tutorial/9',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1955),
t('Add another name'));
$this->assertText(t('Name #2'));
@@ -102,7 +102,7 @@ class FormExampleTestCase extends DrupalWebTestCase {
// #10
- $this->drupalPost('form_example/tutorial/10',
+ $this->drupalPost('examples/form_example/tutorial/10',
array('first' => t('firstname'), 'last' => t('lastname'), 'year_of_birth' => 1955),
t('Add another name'));
$this->assertText(t('Name #2'));
@@ -119,5 +119,34 @@ class FormExampleTestCase extends DrupalWebTestCase {
$this->assertText(t('And the favorite color is green'));
}
+ /**
+ * Test the element_example form for correct behavior.
+ */
+ function testElementExample() {
+ // Make one basic POST with a set of values and check for correct responses.
+ $edit = array(
+ 'form_example_textfield' => $this->randomName(),
+ 'form_example_checkbox' => TRUE,
+ 'form_example_element_discrete[areacode]' => sprintf('%03d', rand(0,999)),
+ 'form_example_element_discrete[prefix]' => sprintf('%03d', rand(0,999)),
+ 'form_example_element_discrete[extension]' => sprintf('%04d', rand(0,9999)),
+ 'form_example_element_combined[areacode]' => sprintf('%03d', rand(0,999)),
+ 'form_example_element_combined[prefix]' => sprintf('%03d', rand(0,999)),
+ 'form_example_element_combined[extension]' => sprintf('%04d', rand(0,9999)),
+ );
+ $this->drupalPost('examples/form_example/element_example', $edit, t('Submit'));
+ $this->assertText(t('form_example_textfield has value @value', array('@value' => $edit['form_example_textfield'])));
+ $this->assertText(t('form_example_checkbox has value 1'));
+ $this->assertPattern(t('/areacode.*!areacode/', array('!areacode' => $edit['form_example_element_discrete[areacode]'])));
+ $this->assertPattern(t('/prefix.*!prefix/', array('!prefix' => $edit['form_example_element_discrete[prefix]'])));
+ $this->assertPattern(t('/extension.*!extension/', array('!extension' => $edit['form_example_element_discrete[extension]'])));
+
+ $this->assertText(t('form_example_element_combined has value @value', array('@value' => $edit['form_example_element_combined[areacode]'] . $edit['form_example_element_combined[prefix]'] . $edit['form_example_element_combined[extension]'])));
+
+ // Now flip the checkbox and check for correct behavior.
+ $edit['form_example_checkbox'] = FALSE;
+ $this->drupalPost('examples/form_example/element_example', $edit, t('Submit'));
+ $this->assertText(t('form_example_checkbox has value 0'));
+ }
}
diff --git a/form_example/form_example_elements.inc b/form_example/form_example_elements.inc
new file mode 100644
index 0000000..1940b76
--- /dev/null
+++ b/form_example/form_example_elements.inc
@@ -0,0 +1,389 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ * This is an example demonstrating how a module can define custom form
+ * elements.
+ *
+ * Form elements are already familiar to anyone who uses Form API. Examples
+ * of core form elements are 'textfield', 'checkbox' and 'fieldset'. Drupal
+ * utilizes hook_elements() to define these FAPI types, and this occurs in
+ * the core function system_elements().
+ *
+ * Each form element has a #type value that determines how it is treated by
+ * the Form API and how it is ultimately rendered into HTML. hook_elements()
+ * allows modules to define new element types, and tell the Form API what
+ * default values they should automatically be populated with.
+ *
+ * By implementing hook_elements in your own module, you can create custom
+ * form elements with their own properties, validation and theming.
+ *
+ * In this example, we define a series of elements that range from trivial
+ * (a renamed textfield) to more advanced (a telephone number field with each
+ * portion separately validated).
+ *
+ * The @link http://drupal.org/node/169815 Elements handbook page @endlink
+ * has full details on creating elements. See also hook_elements().
+ */
+
+
+/**
+ * Implementation of hook_elements().
+ *
+ * This defines a new form element types.
+ *
+ * - form_example_textfield: This is actually just a textfield, but provides
+ * the new type. If more were to be done with it a theme function could be
+ * provided.
+ * - form_example_checkbox: Nothing more than a regular checkbox, but uses
+ * an alternate theme function provided by this module.
+ * - form_example_phonenumber_discrete: Provides a North-American style
+ * three-part phonenumber where the value of the phonenumber is managed
+ * as an array of three parts.
+ * - form_example_phonenumber_combined: Provides a North-American style
+ * three-part phonenumber where the actual value is managed as a 10-digit
+ * string and only broken up into three parts for the user interface.
+ *
+ * See hook_elements() and the
+ * @link http://drupal.org/node/169815 Creating Custom Elements @endlink
+ * handbook page.
+ */
+function _form_example_elements() {
+ // Simple elements based on textfield require only a definition and a theme
+ // function. In this case we provide the theme function using the default
+ // but it would by default be provided in hook_theme(), probably as
+ // theme_form_example_textfield().
+ $types['form_example_textfield'] = array(
+ // #input tells FAPI that this is an element that will carry a value, even
+ // if it is a hidden value.
+ '#input' => TRUE,
+ '#theme' => array('textfield'),
+ '#autocomplete_path' => FALSE,
+ );
+
+ $types['form_example_checkbox'] = array(
+ '#input' => TRUE,
+ '#return_value' => TRUE,
+ '#process' => array('form_expand_ahah'),
+ // #theme is the default theme('form_example_checkbox'), provided by
+ // this module.
+
+ // This also depends on the existence of
+ // form_type_form_example_checkbox_value(), which is provided by this
+ // module. Since this is not a default textfield-derived element, it
+ // needs its own value callback.
+ );
+
+ // This discrete phonenumber element keeps its values as the separate elements
+ // area code, prefix, extension.
+ $types['form_example_phonenumber_discrete'] = array(
+ '#input' => TRUE,
+
+ // #process is an array of callback functions executed when this element is
+ // processed. Here it provides the child form elements which define
+ // areacode, prefix, and extension.
+ '#process' => array('form_example_phonenumber_discrete_process'),
+
+ // validation handlers for this element
+ '#element_validate' => array('form_example_phonenumber_discrete_validate'),
+ '#autocomplete_path' => FALSE,
+ );
+
+ // Define form_example_phonenumber_combined, which combines the phone
+ // number into a single validated text string.
+ $types['form_example_phonenumber_combined'] = array(
+ '#input' => TRUE,
+ '#process' => array('form_example_phonenumber_combined_process'),
+ '#element_validate' => array('form_example_phonenumber_combined_validate'),
+ '#autocomplete_path' => FALSE,
+ '#default_value' => array(
+ 'areacode' => '',
+ 'prefix' => '',
+ 'extension' => '',
+ ),
+ );
+ return $types;
+}
+
+/**
+ * Helper function to determine the value for an form_example_checkbox.
+ *
+ * Required for the element type 'form_example_checkbox' to work.
+ * Copied from form.inc.
+ *
+ * @param $form
+ * The form element whose value is being populated.
+ * @param $edit
+ * The incoming POST data to populate the form element. If this is FALSE,
+ * the element's default value should be returned.
+ * @return
+ * The data that will appear in the $form_state['values'] collection
+ * for this element. Return nothing to use the default.
+ */
+function form_type_form_example_checkbox_value($form, $edit = FALSE) {
+ if ($edit !== FALSE) {
+ if (empty($form['#disabled'])) {
+ return !empty($edit) ? $form['#return_value'] : 0;
+ }
+ else {
+ return $form['#default_value'];
+ }
+ }
+}
+
+/**
+ * Process callback for the discrete version of phonenumber.
+ */
+function form_example_phonenumber_discrete_process($element, $edit, &$form_state, $complete_form) {
+ // #tree = TRUE means that the values in $form_state['values'] will be stored
+ // hierarchically. In this case, the parts of the element will appear in
+ // $form_state['values'] as
+ // $form_state['values']['<element_name>']['areacode'],
+ // $form_state['values']['<element_name>']['prefix'],
+ // etc. This technique is preferred when an element has member form
+ // elements.
+ $element['#tree'] = TRUE;
+
+ // Normal FAPI field definitions, except that #value is defined.
+ $element['areacode'] = array(
+ '#type' => 'textfield',
+ '#size' => 3,
+ '#maxlength' => 3,
+ '#value' => $element['#value']['areacode'],
+ '#required' => TRUE,
+ '#prefix' => '(',
+ '#suffix' => ')',
+ );
+ $element['prefix'] = array(
+ '#type' => 'textfield',
+ '#size' => 3,
+ '#maxlength' => 3,
+ '#required' => TRUE,
+ '#value' => $element['#value']['prefix'],
+ );
+ $element['extension'] = array(
+ '#type' => 'textfield',
+ '#size' => 4,
+ '#maxlength' => 4,
+ '#value' => $element['#value']['extension'],
+ );
+
+ return $element;
+}
+
+/**
+ * Validation handler for the discrete version of the phone number.
+ *
+ * Using regular expressions, we check that:
+ * - the area code is a three digit number
+ * - the prefix is numeric 3-digit number
+ * - the extension is a numeric 4-digit number
+ *
+ * Any problems are shown on the form element using form_error().
+ */
+function form_example_phonenumber_discrete_validate($element, &$form_state) {
+ if (isset($element['#value']['areacode'])) {
+ if (0 == preg_match('/^\d{3}$/', $element['#value']['areacode'])) {
+ form_error($element['areacode'], t('The area code is invalid.'));
+ }
+ }
+ if (isset($element['#value']['prefix'])) {
+ if (0 == preg_match('/^\d{3}$/', $element['#value']['prefix'])) {
+ form_error($element['prefix'], t('The prefix is invalid.'));
+ }
+ }
+ if (isset($element['#value']['extension'])) {
+ if (0 == preg_match('/^\d{4}$/', $element['#value']['extension'])) {
+ form_error($element['extension'], t('The extension is invalid.'));
+ }
+ }
+ return $element;
+}
+
+
+
+/**
+ * Process callback for the combined version of the phonenumber element.
+ */
+function form_example_phonenumber_combined_process($element, $edit, &$form_state, $complete_form) {
+ // #tree = TRUE means that the values in $form_state['values'] will be stored
+ // hierarchically. In this case, the parts of the element will appear in
+ // $form_state['values'] as
+ // $form_state['values']['<element_name>']['areacode'],
+ // $form_state['values']['<element_name>']['prefix'],
+ // etc. This technique is preferred when an element has member form
+ // elements.
+ $element['#tree'] = TRUE;
+
+
+ // Normal FAPI field definitions, except that #value is defined.
+ $element['areacode'] = array(
+ '#type' => 'textfield',
+ '#size' => 3,
+ '#maxlength' => 3,
+ '#required' => TRUE,
+ '#prefix' => '(',
+ '#suffix' => ')',
+ );
+ $element['prefix'] = array(
+ '#type' => 'textfield',
+ '#size' => 3,
+ '#maxlength' => 3,
+ '#required' => TRUE,
+ );
+ $element['extension'] = array(
+ '#type' => 'textfield',
+ '#size' => 4,
+ '#maxlength' => 4,
+ '#required' => TRUE,
+ );
+
+
+ $matches = array();
+ $match = preg_match('/^(\d{3})(\d{3})(\d{4})$/', $element['#default_value'], $matches);
+ if ($match) {
+ array_shift($matches); // get rid of the "all match" element
+ list($element['areacode']['#default_value'], $element['prefix']['#default_value'], $element['extension']['#default_value']) = $matches;
+ }
+ return $element;
+}
+
+/**
+ * Combined version validation function.
+ *
+ * Using regular expressions, we check that:
+ * - the area code is a three digit number
+ * - the prefix is numeric 3-digit number
+ * - the extension is a numeric 4-digit number
+ *
+ * Any problems are shown on the form element using form_error().
+ *
+ * The combined value is then updated in the element.
+ */
+function form_example_phonenumber_combined_validate($element, &$form_state) {
+ $lengths = array(
+ 'areacode' => 3,
+ 'prefix' => 3,
+ 'extension' => 4,
+ );
+ foreach ($lengths as $member => $length) {
+ $regex = '/^\d{' . $length . '}$/';
+ if (!empty($element['#value'][$member]) && 0 == preg_match($regex, $element['#value'][$member])) {
+ form_error($element[$member], t('@member is invalid', array('@member' => $member)));
+ }
+ }
+
+ // Consolidate into the three parts into one combined value.
+ $value = $element['#value']['areacode'] . $element['#value']['prefix'] . $element['#value']['extension'];
+ form_set_value($element, $value, $form_state);
+ return $element;
+}
+
+/**
+ * Called by form_example_theme() to provide hook_theme().
+ */
+function _form_example_element_theme() {
+ return array(
+ 'form_example_checkbox' => array(
+ 'arguments' => array('element'),
+ 'file' => 'form_example_elements.inc',
+ ),
+ 'form_example_phonenumber_discrete' => array(
+ 'arguments' => array('element'),
+ 'file' => 'form_example_elements.inc',
+ ),
+ 'form_example_phonenumber_combined' => array(
+ 'arguments' => array('element'),
+ 'file' => 'form_example_elements.inc',
+ ),
+ );
+}
+
+
+/**
+ * Theme function for form_example_checkbox type.
+ */
+function theme_form_example_checkbox($element) {
+ return theme('checkbox', $element);
+}
+
+/**
+ * Theme function to format the discrete version.
+ *
+ * We use the container-inline class so that all three of the HTML elements
+ * are placed next to each other, rather than on separate lines.
+ */
+function theme_form_example_phonenumber_discrete($element) {
+ // #children represents all the sublevels elements already rendered in HTML.
+ // Here it contains the three parts of the 'phonenumber' element type ('areacode', 'prefix' and 'extension').
+ return theme('form_element', $element, '<div class="container-inline">' . $element['#children'] . '</div>');
+}
+
+
+/**
+ * Theme function to format the combined version.
+ *
+ * We use the container-inline class so that all three of the HTML elements
+ * are placed next to each other, rather than on separate lines.
+ */
+function theme_form_example_phonenumber_combined($element) {
+ // #children represents all the sublevels elements already rendered in HTML.
+ // Here it contains the three parts of the 'phonenumber' element type ('areacode', 'prefix' and 'extension').
+ return theme('form_element', $element, '<div class="container-inline">' . $element['#children'] . '</div>');
+}
+
+/**
+ * This is a simple form to demonstrate how to use the various new FAPI elements
+ * we've defined.
+ */
+function form_example_element_demo_form() {
+ $form['form_example_textfield'] = array(
+ '#type' => 'form_example_textfield',
+ '#title' => t('Form Example textfield'),
+ '#default_value' => variable_get('form_example_textfield', ''),
+ '#description' => t('form_example_textfield is a new type, but it is actually uses the system-provided functions of textfield'),
+ );
+
+ $form['form_example_checkbox'] = array(
+ '#type' => 'form_example_checkbox',
+ '#title' => t('Form Example checkbox'),
+ '#default_value' => variable_get('form_example_checkbox', FALSE),
+ '#description' => t('Nothing more than a regular checkbox but with a theme provided by this module.')
+ );
+
+ $form['form_example_element_discrete'] = array(
+ '#type' => 'form_example_phonenumber_discrete',
+ '#title' => t('Discrete phone number'),
+ '#default_value' => variable_get('form_example_element_discrete', array('areacode' => '', 'prefix' => '', 'extension' => '')),
+ '#description' => t('A phone number : areacode (XXX), prefix (XXX) and extension (XXXX). This one uses a "discrete" element type, one which stores the three parts of the telephone number separately.'),
+ );
+
+ $form['form_example_element_combined'] = array(
+ '#type' => 'form_example_phonenumber_combined',
+ '#title' => t('Combined phone number'),
+ '#default_value' => variable_get('form_example_element_combined', '0000000000'),
+ '#description' => t('form_example_element_combined one uses a "combined" element type, one with a single 10-digit value which is broken apart when needed.'),
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for form_example_element_demo_form().
+ */
+function form_example_element_demo_form_submit($form, &$form_state) {
+ // Exclude unnecessary elements.
+ unset($form_state['values']['submit'], $form_state['values']['form_id'], $form_state['values']['op'], $form_state['values']['form_token'], $form_state['values']['form_build_id']);
+
+ foreach ($form_state['values'] as $key => $value) {
+ variable_set($key, $value);
+ drupal_set_message(t('%name has value %value', array('%name' => $key, '%value' => print_r($value, TRUE))));
+ }
+}