form_example_elements.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. <?php
  2. /**
  3. * @file
  4. * This is an example demonstrating how a module can define custom form and
  5. * render elements.
  6. *
  7. * Form elements are already familiar to anyone who uses Form API. They share
  8. * history with render elements, which are explained in the
  9. * @link render_example.module Render Example @endlink. Examples
  10. * of core form elements are 'textfield', 'checkbox' and 'fieldset'. Drupal
  11. * utilizes hook_elements() to define these FAPI types, and this occurs in
  12. * the core function system_elements().
  13. *
  14. * Each form element has a #type value that determines how it is treated by
  15. * the Form API and how it is ultimately rendered into HTML.
  16. * hook_element_info() allows modules to define new element types, and tells
  17. * the Form API what default values they should automatically be populated with.
  18. *
  19. * By implementing hook_element_info() in your own module, you can create custom
  20. * form (or render) elements with their own properties, validation and theming.
  21. *
  22. * In this example, we define a series of elements that range from trivial
  23. * (a renamed textfield) to more advanced (a telephone number field with each
  24. * portion separately validated).
  25. *
  26. * Since each element can use arbitrary properties (like #process or #dropthis)
  27. * it can be quite complicated to figure out what all the properties actually
  28. * mean. This example won't undertake the exhaustive task of explaining them
  29. * all, as that would probably be impossible.
  30. */
  31. /**
  32. * @todo: Some additional magic things to explain:
  33. * - #process and process callback (and naming) (in forms)
  34. * - #value and value callback (and naming of the above)
  35. * - #theme and #theme_wrappers
  36. * - What is #return_value?
  37. * - system module provides the standard default elements.
  38. * - What are all the things that can be defined in hook_element_info() and
  39. * where do the defaults come from?
  40. * - Form elements that have a type that has a matching type in the element
  41. * array created by hook_element_info() get those items merged with them.
  42. * - #value_callback is called first by form_builder(). Its job is to figure
  43. * out what the actual value of the element, using #default_value or whatever.
  44. * - #process is then called to allow changes to the whole element (like adding
  45. * child form elements.)
  46. * - #return_value: chx: you need three different values for form API. You need
  47. * the default value (#default_value), the value for the element if it gets
  48. * checked )#return_value) and then #value which is either 0 or the
  49. * #return_value
  50. */
  51. /**
  52. * Utility function providing data for form_example_element_info().
  53. *
  54. * This defines several new form element types.
  55. *
  56. * - form_example_textfield: This is actually just a textfield, but provides
  57. * the new type. If more were to be done with it a theme function could be
  58. * provided.
  59. * - form_example_checkbox: Nothing more than a regular checkbox, but uses
  60. * an alternate theme function provided by this module.
  61. * - form_example_phonenumber_discrete: Provides a North-American style
  62. * three-part phonenumber where the value of the phonenumber is managed
  63. * as an array of three parts.
  64. * - form_example_phonenumber_combined: Provides a North-American style
  65. * three-part phonenumber where the actual value is managed as a 10-digit
  66. * string and only broken up into three parts for the user interface.
  67. *
  68. * form_builder() has significant discussion of #process and #value_callback.
  69. * See also hook_element_info().
  70. *
  71. * system_element_info() contains the Drupal default element types, which can
  72. * also be used as examples.
  73. */
  74. function _form_example_element_info() {
  75. // form_example_textfield is a trivial element based on textfield that
  76. // requires only a definition and a theme function. In this case we provide
  77. // the theme function using the parent "textfield" theme function, but it
  78. // would by default be provided in hook_theme(), by a "form_example_textfield"
  79. // theme implementation, provided by default by the function
  80. // theme_form_example_textfield(). Note that the 'form_example_textfield'
  81. // element type is completely defined here. There is no further code required
  82. // for it.
  83. $types['form_example_textfield'] = array(
  84. // #input = TRUE means that the incoming value will be used to figure out
  85. // what #value will be.
  86. '#input' => TRUE,
  87. // Use theme('textfield') to format this element on output.
  88. '#theme' => array('textfield'),
  89. // Do not provide autocomplete.
  90. '#autocomplete_path' => FALSE,
  91. // Allow theme('form_element') to control the markup surrounding this
  92. // value on output.
  93. '#theme_wrappers' => array('form_element'),
  94. );
  95. // form_example_checkbox is mostly a copy of the system-defined checkbox
  96. // element.
  97. $types['form_example_checkbox'] = array(
  98. // This is an HTML <input>.
  99. '#input' => TRUE,
  100. // @todo: Explain #return_value.
  101. '#return_value' => TRUE,
  102. // Our #process array will use the standard process functions used for a
  103. // regular checkbox.
  104. '#process' => array('form_process_checkbox', 'ajax_process_form'),
  105. // Use theme('form_example_checkbox') to render this element on output.
  106. '#theme' => 'form_example_checkbox',
  107. // Use theme('form_element') to provide HTML wrappers for this element.
  108. '#theme_wrappers' => array('form_element'),
  109. // Place the title after the element (to the right of the checkbox).
  110. // This attribute affects the behavior of theme_form_element().
  111. '#title_display' => 'after',
  112. // We use the default function name for the value callback, so it does not
  113. // have to be listed explicitly. The pattern for the default function name
  114. // is form_type_TYPENAME_value().
  115. // '#value_callback' => 'form_type_form_example_checkbox_value',
  116. );
  117. // This discrete phonenumber element keeps its values as the separate elements
  118. // area code, prefix, extension.
  119. $types['form_example_phonenumber_discrete'] = array(
  120. // #input == TRUE means that the form value here will be used to determine
  121. // what #value will be.
  122. '#input' => TRUE,
  123. // #process is an array of callback functions executed when this element is
  124. // processed. Here it provides the child form elements which define
  125. // areacode, prefix, and extension.
  126. '#process' => array('form_example_phonenumber_discrete_process'),
  127. // Validation handlers for this element. These are in addition to any
  128. // validation handlers that might.
  129. '#element_validate' => array('form_example_phonenumber_discrete_validate'),
  130. '#autocomplete_path' => FALSE,
  131. '#theme_wrappers' => array('form_example_inline_form_element'),
  132. );
  133. // Define form_example_phonenumber_combined, which combines the phone
  134. // number into a single validated text string.
  135. $types['form_example_phonenumber_combined'] = array(
  136. '#input' => TRUE ,
  137. '#process' => array('form_example_phonenumber_combined_process'),
  138. '#element_validate' => array('form_example_phonenumber_combined_validate'),
  139. '#autocomplete_path' => FALSE,
  140. '#value_callback' => 'form_example_phonenumber_combined_value',
  141. '#default_value' => array(
  142. 'areacode' => '',
  143. 'prefix' => '',
  144. 'extension' => '',
  145. ),
  146. '#theme_wrappers' => array('form_example_inline_form_element'),
  147. );
  148. return $types;
  149. }
  150. /**
  151. * Value callback for form_example_phonenumber_combined.
  152. *
  153. * Builds the current combined value of the phone number only when the form
  154. * builder is not processing the input.
  155. *
  156. * @param array $element
  157. * Form element.
  158. * @param array $input
  159. * Input.
  160. * @param array $form_state
  161. * Form state.
  162. *
  163. * @return array
  164. * The modified element.
  165. */
  166. function form_example_phonenumber_combined_value(&$element, $input = FALSE, $form_state = NULL) {
  167. if (!$form_state['process_input']) {
  168. $matches = array();
  169. $match = preg_match('/^(\d{3})(\d{3})(\d{4})$/', $element['#default_value'], $matches);
  170. if ($match) {
  171. // Get rid of the "all match" element.
  172. array_shift($matches);
  173. list($element['areacode'], $element['prefix'], $element['extension']) = $matches;
  174. }
  175. }
  176. return $element;
  177. }
  178. /**
  179. * Value callback for form_example_checkbox element type.
  180. *
  181. * Copied from form_type_checkbox_value().
  182. *
  183. * @param array $element
  184. * The form element whose value is being populated.
  185. * @param mixed $input
  186. * The incoming input to populate the form element. If this is FALSE, meaning
  187. * there is no input, the element's default value should be returned.
  188. *
  189. * @return int
  190. * The value represented by the form element.
  191. */
  192. function form_type_form_example_checkbox_value($element, $input = FALSE) {
  193. if ($input === FALSE) {
  194. return isset($element['#default_value']) ? $element['#default_value'] : 0;
  195. }
  196. else {
  197. return isset($input) ? $element['#return_value'] : 0;
  198. }
  199. }
  200. /**
  201. * Process callback for the discrete version of phonenumber.
  202. */
  203. function form_example_phonenumber_discrete_process($element, &$form_state, $complete_form) {
  204. // #tree = TRUE means that the values in $form_state['values'] will be stored
  205. // hierarchically. In this case, the parts of the element will appear in
  206. // $form_state['values'] as
  207. // $form_state['values']['<element_name>']['areacode'],
  208. // $form_state['values']['<element_name>']['prefix'],
  209. // etc. This technique is preferred when an element has member form
  210. // elements.
  211. $element['#tree'] = TRUE;
  212. // Normal FAPI field definitions, except that #value is defined.
  213. $element['areacode'] = array(
  214. '#type' => 'textfield',
  215. '#size' => 3,
  216. '#maxlength' => 3,
  217. '#value' => $element['#value']['areacode'],
  218. '#required' => TRUE,
  219. '#prefix' => '(',
  220. '#suffix' => ')',
  221. );
  222. $element['prefix'] = array(
  223. '#type' => 'textfield',
  224. '#size' => 3,
  225. '#maxlength' => 3,
  226. '#required' => TRUE,
  227. '#value' => $element['#value']['prefix'],
  228. );
  229. $element['extension'] = array(
  230. '#type' => 'textfield',
  231. '#size' => 4,
  232. '#maxlength' => 4,
  233. '#value' => $element['#value']['extension'],
  234. );
  235. return $element;
  236. }
  237. /**
  238. * Validation handler for the discrete version of the phone number.
  239. *
  240. * Uses regular expressions to check that:
  241. * - the area code is a three digit number.
  242. * - the prefix is numeric 3-digit number.
  243. * - the extension is a numeric 4-digit number.
  244. *
  245. * Any problems are shown on the form element using form_error().
  246. */
  247. function form_example_phonenumber_discrete_validate($element, &$form_state) {
  248. if (isset($element['#value']['areacode'])) {
  249. if (0 == preg_match('/^\d{3}$/', $element['#value']['areacode'])) {
  250. form_error($element['areacode'], t('The area code is invalid.'));
  251. }
  252. }
  253. if (isset($element['#value']['prefix'])) {
  254. if (0 == preg_match('/^\d{3}$/', $element['#value']['prefix'])) {
  255. form_error($element['prefix'], t('The prefix is invalid.'));
  256. }
  257. }
  258. if (isset($element['#value']['extension'])) {
  259. if (0 == preg_match('/^\d{4}$/', $element['#value']['extension'])) {
  260. form_error($element['extension'], t('The extension is invalid.'));
  261. }
  262. }
  263. return $element;
  264. }
  265. /**
  266. * Process callback for the combined version of the phonenumber element.
  267. */
  268. function form_example_phonenumber_combined_process($element, &$form_state, $complete_form) {
  269. // #tree = TRUE means that the values in $form_state['values'] will be stored
  270. // hierarchically. In this case, the parts of the element will appear in
  271. // $form_state['values'] as
  272. // $form_state['values']['<element_name>']['areacode'],
  273. // $form_state['values']['<element_name>']['prefix'],
  274. // etc. This technique is preferred when an element has member form
  275. // elements.
  276. $element['#tree'] = TRUE;
  277. // Normal FAPI field definitions, except that #value is defined.
  278. $element['areacode'] = array(
  279. '#type' => 'textfield',
  280. '#size' => 3,
  281. '#maxlength' => 3,
  282. '#required' => TRUE,
  283. '#prefix' => '(',
  284. '#suffix' => ')',
  285. );
  286. $element['prefix'] = array(
  287. '#type' => 'textfield',
  288. '#size' => 3,
  289. '#maxlength' => 3,
  290. '#required' => TRUE,
  291. );
  292. $element['extension'] = array(
  293. '#type' => 'textfield',
  294. '#size' => 4,
  295. '#maxlength' => 4,
  296. '#required' => TRUE,
  297. );
  298. $matches = array();
  299. $match = preg_match('/^(\d{3})(\d{3})(\d{4})$/', $element['#default_value'], $matches);
  300. if ($match) {
  301. // Get rid of the "all match" element.
  302. array_shift($matches);
  303. list($element['areacode']['#default_value'], $element['prefix']['#default_value'], $element['extension']['#default_value']) = $matches;
  304. }
  305. return $element;
  306. }
  307. /**
  308. * Phone number validation function for the combined phonenumber.
  309. *
  310. * Uses regular expressions to check that:
  311. * - the area code is a three digit number
  312. * - the prefix is numeric 3-digit number
  313. * - the extension is a numeric 4-digit number
  314. *
  315. * Any problems are shown on the form element using form_error().
  316. *
  317. * The combined value is then updated in the element.
  318. */
  319. function form_example_phonenumber_combined_validate($element, &$form_state) {
  320. $lengths = array(
  321. 'areacode' => 3,
  322. 'prefix' => 3,
  323. 'extension' => 4,
  324. );
  325. foreach ($lengths as $member => $length) {
  326. $regex = '/^\d{' . $length . '}$/';
  327. if (!empty($element['#value'][$member]) && 0 == preg_match($regex, $element['#value'][$member])) {
  328. form_error($element[$member], t('@member is invalid', array('@member' => $member)));
  329. }
  330. }
  331. // Consolidate into the three parts into one combined value.
  332. $value = $element['areacode']['#value'] . $element['prefix']['#value'] . $element['extension']['#value'];
  333. form_set_value($element, $value, $form_state);
  334. return $element;
  335. }
  336. /**
  337. * Called by form_example_theme() to provide hook_theme().
  338. *
  339. * This is kept in this file so it can be with the theme functions it presents.
  340. * Otherwise it would get lonely.
  341. */
  342. function _form_example_element_theme() {
  343. return array(
  344. 'form_example_inline_form_element' => array(
  345. 'render element' => 'element',
  346. 'file' => 'form_example_elements.inc',
  347. ),
  348. 'form_example_checkbox' => array(
  349. 'render element' => 'element',
  350. 'file' => 'form_example_elements.inc',
  351. ),
  352. );
  353. }
  354. /**
  355. * Themes a custom checkbox.
  356. *
  357. * This doesn't actually do anything, but is here to show that theming can
  358. * be done here.
  359. */
  360. function theme_form_example_checkbox($variables) {
  361. $element = $variables['element'];
  362. return theme('checkbox', $element);
  363. }
  364. /**
  365. * Formats child form elements as inline elements.
  366. */
  367. function theme_form_example_inline_form_element($variables) {
  368. $element = $variables['element'];
  369. // Add element #id for #type 'item'.
  370. if (isset($element['#markup']) && !empty($element['#id'])) {
  371. $attributes['id'] = $element['#id'];
  372. }
  373. // Add element's #type and #name as class to aid with JS/CSS selectors.
  374. $attributes['class'] = array('form-item');
  375. if (!empty($element['#type'])) {
  376. $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
  377. }
  378. if (!empty($element['#name'])) {
  379. $attributes['class'][] = 'form-item-' . strtr($element['#name'],
  380. array(
  381. ' ' => '-',
  382. '_' => '-',
  383. '[' => '-',
  384. ']' => '',
  385. )
  386. );
  387. }
  388. // Add a class for disabled elements to facilitate cross-browser styling.
  389. if (!empty($element['#attributes']['disabled'])) {
  390. $attributes['class'][] = 'form-disabled';
  391. }
  392. $output = '<div' . drupal_attributes($attributes) . '>' . "\n";
  393. // If #title is not set, we don't display any label or required marker.
  394. if (!isset($element['#title'])) {
  395. $element['#title_display'] = 'none';
  396. }
  397. $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : '';
  398. $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : '';
  399. switch ($element['#title_display']) {
  400. case 'before':
  401. $output .= ' ' . theme('form_element_label', $variables);
  402. $output .= ' ' . '<div class="container-inline">' . $prefix . $element['#children'] . $suffix . "</div>\n";
  403. break;
  404. case 'invisible':
  405. case 'after':
  406. $output .= ' ' . $prefix . $element['#children'] . $suffix;
  407. $output .= ' ' . theme('form_element_label', $variables) . "\n";
  408. break;
  409. case 'none':
  410. case 'attribute':
  411. // Output no label and no required marker, only the children.
  412. $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
  413. break;
  414. }
  415. if (!empty($element['#description'])) {
  416. $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
  417. }
  418. $output .= "</div>\n";
  419. return $output;
  420. }
  421. /**
  422. * Form content for examples/form_example/element_example.
  423. *
  424. * Simple form to demonstrate how to use the various new FAPI elements
  425. * we've defined.
  426. */
  427. function form_example_element_demo_form($form, &$form_state) {
  428. $form['a_form_example_textfield'] = array(
  429. '#type' => 'form_example_textfield',
  430. '#title' => t('Form Example textfield'),
  431. '#default_value' => variable_get('form_example_textfield', ''),
  432. '#description' => t('form_example_textfield is a new type, but it is actually uses the system-provided functions of textfield'),
  433. );
  434. $form['a_form_example_checkbox'] = array(
  435. '#type' => 'form_example_checkbox',
  436. '#title' => t('Form Example checkbox'),
  437. '#default_value' => variable_get('form_example_checkbox', FALSE),
  438. '#description' => t('Nothing more than a regular checkbox but with a theme provided by this module.'),
  439. );
  440. $form['a_form_example_element_discrete'] = array(
  441. '#type' => 'form_example_phonenumber_discrete',
  442. '#title' => t('Discrete phone number'),
  443. '#default_value' => variable_get(
  444. 'form_example_element_discrete',
  445. array(
  446. 'areacode' => '999',
  447. 'prefix' => '999',
  448. 'extension' => '9999',
  449. )
  450. ),
  451. '#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.'),
  452. );
  453. $form['a_form_example_element_combined'] = array(
  454. '#type' => 'form_example_phonenumber_combined',
  455. '#title' => t('Combined phone number'),
  456. '#default_value' => variable_get('form_example_element_combined', '0000000000'),
  457. '#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.'),
  458. );
  459. $form['submit'] = array(
  460. '#type' => 'submit',
  461. '#value' => t('Submit'),
  462. );
  463. return $form;
  464. }
  465. /**
  466. * Submit handler for form_example_element_demo_form().
  467. */
  468. function form_example_element_demo_form_submit($form, &$form_state) {
  469. // Exclude unnecessary elements.
  470. 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']);
  471. foreach ($form_state['values'] as $key => $value) {
  472. variable_set($key, $value);
  473. drupal_set_message(
  474. t('%name has value %value',
  475. array(
  476. '%name' => $key,
  477. '%value' => print_r($value, TRUE),
  478. )
  479. )
  480. );
  481. }
  482. }