fel.module in Form element layout 7
Reorder #title, #description and #children in forms.
Introduces a new Form API attribute: #description_display. Its value can be either 'before' or 'after', relative to #children, or the actual input element. It behaves much like the D8 issue with the same purpose.
The combination of #description_display and #title_display together makes the ordering of element attributes more complex, and in cases where both #title and #description are on the same side of #children, #title always comes first.
See also
File
fel.moduleView source
<?php
/**
* @file
* Reorder #title, #description and #children in forms.
*
* Introduces a new Form API attribute: #description_display. Its value can be
* either 'before' or 'after', relative to #children, or the actual input
* element. It behaves much like the D8 issue with the same purpose.
*
* @see https://www.drupal.org/node/314385
*
* The combination of #description_display and #title_display together makes the
* ordering of element attributes more complex, and in cases where both #title
* and #description are on the same side of #children, #title always comes
* first.
*/
/**
* Implements hook_theme().
*/
function fel_theme($existing, $type, $theme, $path) {
return array(
'fel_form_element' => array(
'render element' => 'element',
),
'fel_form_element_label' => array(
'render element' => 'element',
),
'fel_form_element_description' => array(
'render element' => 'element',
),
'fel_text_format_wrapper' => array(
'render element' => 'element',
),
'fel_fieldset' => array(
'render element' => 'element',
),
);
}
/**
* Implements hook_element_info_alter().
*/
function fel_element_info_alter(&$elements) {
// Replace all #theme_wrapper[] = 'form_element' with our custom form_element
// theme.
$form_element_wrappers = array(
// Core.
'textfield',
'machine_name',
'password',
'password_confirm',
'textarea',
'radio',
'checkbox',
'select',
'date',
'file',
'item',
'managed_file',
// Contrib.
'emailfield',
'hierarchical_select',
'interval',
'link_field',
'media',
'mfw_managed_file',
'numberfield',
'rangefield',
'searchfield',
'select_or_other',
'telfield',
'urlfield',
);
foreach ($form_element_wrappers as $type) {
if (!empty($elements[$type])) {
fel_wrapper_replace('form_element', 'fel_form_element', $elements[$type]);
}
}
// 'radios' and 'checkboxes' are special.
$elements['checkboxes']['#pre_render'][] = 'fel_element_pre_render';
$elements['radios']['#pre_render'][] = 'fel_element_pre_render';
// Fieldsets.
fel_wrapper_replace('fieldset', 'fel_fieldset', $elements['fieldset']);
// 'text_format': Replace #theme_wrapper with our own and reshuffle the title,
// description and element trio.
if (!empty($elements['text_format']) and module_exists('filter')) {
fel_wrapper_replace('text_format_wrapper', 'fel_text_format_wrapper', $elements['text_format']);
$elements['text_format']['#process'][] = 'fel_filter_process_format';
}
// Contrib: Taxonomy Term Reference Tree Widget.
if (!empty($elements['checkbox_tree'])) {
$elements['checkbox_tree']['#pre_render'][] = 'fel_element_pre_render';
}
// Contrib: Autocomplete Deluxe.
if (!empty($elements['autocomplete_deluxe'])) {
$elements['autocomplete_deluxe']['#pre_render'][] = 'fel_element_pre_render';
}
// Contrib: Select (or other).
if (!empty($elements['select_or_other'])) {
$elements['select_or_other']['#process'][] = 'fel_select_or_other_element_process';
}
}
/**
* Replace #theme_wrapper => 'form_element' with our own.
*/
function fel_element_pre_render($element) {
fel_wrapper_replace('form_element', 'fel_form_element', $element);
return $element;
}
/**
* Extra processing for 'text_format' elements.
*
* Filter module groups the title and the form element (#title, #children) into
* the 'value' child of '#type' == 'text_format' element and renders the
* '#description' item outside this, so there is no way to jam #description
* between #title and #children.
*
* This processing is necessary in order to move the description from this
* element to the child element 'value' when '#description_display' == 'before.
*/
function fel_filter_process_format($element) {
if (!empty($element['#description']) and !empty($element['#description_display']) and $element['#description_display'] == 'before') {
$element['value']['#description'] = $element['#description'];
unset($element['#description']);
}
return $element;
}
/**
* Renders the #description of a form element.
*/
function theme_fel_form_element_description($variables) {
$element = $variables['element'];
if (empty($element['#description'])) {
return '';
}
$description_classes = array(
'description',
);
if (!empty($element['#description_classes'])) {
$description_classes = array_merge($description_classes, $element['#description_classes']);
}
return '<div class="' . implode(' ', $description_classes) . '">' . $element['#description'] . "</div>\n";
}
/**
* Replacement theme for 'form_element'.
*
* @see theme_form_element()
*/
function theme_fel_form_element($variables) {
$element =& $variables['element'];
// This function is invoked as theme wrapper, but the rendered form element
// may not necessarily have been processed by form_builder().
$element += array(
'#title_display' => 'before',
);
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
$attributes['id'] = $element['#id'];
}
// Add element's #type and #name as class to aid with JS/CSS selectors.
$attributes['class'] = array(
'form-item',
);
if (!empty($element['#type'])) {
$attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
}
if (!empty($element['#name'])) {
$replacements = array(
' ' => '-',
'_' => '-',
'[' => '-',
']' => '',
);
$attributes['class'][] = 'form-item-' . strtr($element['#name'], $replacements);
}
// Add a class for disabled elements to facilitate cross-browser styling.
if (!empty($element['#attributes']['disabled'])) {
$attributes['class'][] = 'form-disabled';
}
$output = '<div' . drupal_attributes($attributes) . '>' . "\n";
// If #title is not set, we don't display any label or required marker.
if (!isset($element['#title'])) {
$element['#title_display'] = 'none';
}
$prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : '';
$suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : '';
$element_parts = array(
'children' => $prefix . $element['#children'] . $suffix,
);
if ($element['#title_display'] != 'none' and $element['#title_display'] != 'attribute') {
$element_parts['title'] = theme('fel_form_element_label', $variables);
}
if (!empty($element['#description'])) {
$element_parts['description'] = theme('fel_form_element_description', $variables) . "\n";
}
fel_order_output($element, $element_parts);
$output .= implode(' ', $element_parts);
$output .= "\n</div>\n";
return $output;
}
/**
* Replacement theme for 'form_element_label'.
*
* @see theme_form_element_label()
*/
function theme_fel_form_element_label($variables) {
$element = $variables['element'];
// This is also used in the installer, pre-database setup.
$t = get_t();
// If title and required marker are both empty, output no label.
if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
return '';
}
// If the element is required, a required marker is appended to the label.
$required = !empty($element['#required']) ? theme('form_required_marker', array(
'element' => $element,
)) : '';
$title = filter_xss_admin($element['#title']);
$attributes = array();
if ($element['#title_display'] != 'invisible' and !empty($element['#title_classes'])) {
$attributes['class'] = $element['#title_classes'];
}
// Style the label as class option to display inline with the element.
if ($element['#title_display'] == 'after') {
$attributes['class'][] = 'option';
}
elseif ($element['#title_display'] == 'invisible') {
$attributes['class'][] = 'element-invisible';
}
if (!empty($element['#id'])) {
$attributes['for'] = $element['#id'];
}
// The leading whitespace helps visually separate fields from inline labels.
return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array(
'!title' => $title,
'!required' => $required,
)) . "</label>\n";
}
/**
* Replacement theme for 'text_format'.
*
* @see theme_text_format_wrapper()
*/
function theme_fel_text_format_wrapper($variables) {
$element = $variables['element'];
$parts['children'] = $element['#children'];
if (!empty($element['#description'])) {
$parts['description'] = theme('fel_form_element_description', $variables);
}
$output = '<div class="text-format-wrapper">';
$output .= fel_order_output($element, $parts);
$output .= "</div>\n";
return $output;
}
/**
* Replacement theme for 'fieldset'.
*
* @see theme_fieldset()
*/
function theme_fel_fieldset($variables) {
$element = $variables['element'];
element_set_attributes($element, array(
'id',
));
_form_set_class($element, array(
'form-wrapper',
));
$output = '<fieldset' . drupal_attributes($element['#attributes']) . '>';
if (!empty($element['#title'])) {
$title_classes = array(
'fieldset-legend',
);
if (!empty($element['#title_classes'])) {
$title_classes = array_merge($title_classes, $element['#title_classes']);
}
// Always wrap fieldset legends in a SPAN for CSS positioning.
$output .= '<legend><span class="' . implode(' ', $title_classes) . '">' . $element['#title'] . '</span></legend>';
}
$output .= '<div class="fieldset-wrapper">';
$parts['children'] = $element['#children'];
if (isset($element['#value'])) {
$parts['children'] .= $element['#value'];
}
if (!empty($element['#description'])) {
$element['#description_classes'][] = 'fieldset-description';
$parts['description'] = theme('fel_form_element_description', $variables);
if (empty($element['#description_display'])) {
$element['#description_display'] = 'before';
}
}
$output .= fel_order_output($element, $parts);
$output .= '</div>';
$output .= "</fieldset>\n";
return $output;
}
/**
* Replace $search in $element['#theme_wrapper'] with $replace.
*
* @param string $search
* The theme name to search for.
* @param string $replace
* Replacement theme name.
* @param array $element
* The element to search in.
*/
function fel_wrapper_replace($search, $replace, array &$element) {
if (!empty($element['#theme_wrappers'])) {
$key = array_search($search, $element['#theme_wrappers']);
if ($key !== FALSE) {
unset($element['#theme_wrappers'][$key]);
$element['#theme_wrappers'][] = $replace;
}
}
}
/**
* Determine the render order of a form element.
*
* Returns what order #title, #children and #description should be rendered in,
* based on #title_display and #description_display.
*
* @param array $element
* Form element.
*
* @return array
* An array with the three values 'title', 'children' and 'description' in
* various orders.
*/
function fel_get_order(array $element) {
if (!empty($element['#element_order'])) {
return $element['#element_order'];
}
$title_before = (empty($element['#title_display']) or in_array($element['#title_display'], array(
'before',
'invisible',
'inline',
)));
$description_before = empty($element['#description_display']) ? FALSE : $element['#description_display'] == 'before';
if ($title_before) {
if ($description_before) {
$order = !empty($element['#title_display']) && $element['#title_display'] == 'inline' ? array(
'description',
'title',
'children',
) : array(
'title',
'description',
'children',
);
}
else {
$order = array(
'title',
'children',
'description',
);
}
}
else {
$order = $description_before ? array(
'description',
'children',
'title',
) : array(
'children',
'title',
'description',
);
}
return $order;
}
/**
* Return the output of $parts in configured order.
*
* @param array $element
* A form element.
* @param array $parts
* An associative array with the following optional keys:
* - 'title'.
* - 'description'.
* - 'children'.
*
* @return string
* The items in order according to configuration in $element.
*/
function fel_order_output(array $element, array &$parts) {
$element_order = fel_get_order($element);
$parts_ordered = array();
$out = '';
foreach ($element_order as $element_item) {
if (!empty($parts[$element_item])) {
$out .= $parts[$element_item];
$parts_ordered[$element_item] = $parts[$element_item];
}
}
$parts = $parts_ordered;
return $out;
}
/**
* Additional processing for element type 'select_or_other'.
*/
function fel_select_or_other_element_process($element, &$form_state) {
// If the description is set to be before the input element, we have to move
// it from the outside container to the nested select/radios/checkboxes input
// element and apply the FAPI attributes there.
if (!empty($element['#description']) && !empty($element['#description_display']) && $element['#description_display'] === 'before') {
$element['select']['#description'] = $element['#description'];
$element['select']['#description_display'] = $element['#description_display'];
unset($element['#description']);
}
return $element;
}
Functions
Name![]() |
Description |
---|---|
fel_element_info_alter | Implements hook_element_info_alter(). |
fel_element_pre_render | Replace #theme_wrapper => 'form_element' with our own. |
fel_filter_process_format | Extra processing for 'text_format' elements. |
fel_get_order | Determine the render order of a form element. |
fel_order_output | Return the output of $parts in configured order. |
fel_select_or_other_element_process | Additional processing for element type 'select_or_other'. |
fel_theme | Implements hook_theme(). |
fel_wrapper_replace | Replace $search in $element['#theme_wrapper'] with $replace. |
theme_fel_fieldset | Replacement theme for 'fieldset'. |
theme_fel_form_element | Replacement theme for 'form_element'. |
theme_fel_form_element_description | Renders the #description of a form element. |
theme_fel_form_element_label | Replacement theme for 'form_element_label'. |
theme_fel_text_format_wrapper | Replacement theme for 'text_format'. |