Advertisement
Guest User

spower_elements.inc

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