You are here

field_collection_tabs_widget.module in Field collection tabs widget 7

Provides widget for field collections on tabs

File

field_collection_tabs_widget.module
View source
<?php

/**
 * @file
 * Provides widget for field collections on tabs
 */

/**
 * Implements hook_field_formatter_info().
 */
function field_collection_tabs_widget_field_widget_info() {
  return array(
    'field_collection_tabs' => array(
      'label' => t('Tabs'),
      'description' => t('Field collection form elements as tabs'),
      'field types' => array(
        'field_collection',
      ),
      'settings' => array(
        'fctw_title_field' => 0,
        'fctw_title_field_formatter' => 'default',
        'fctw_new_tab_title' => 'New',
        'fctw_empty_tab_title' => '- None -',
        'fctw_add_new_tab_title' => '+',
        'fctw_add_new_tab_hover' => 'Add a new tab',
        'fctw_hide_first_blank_tab' => FALSE,
      ),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function field_collection_tabs_widget_field_widget_settings_form($field, $instance) {

  // The field settings infrastructure is not AJAX enabled by default,
  // because it doesn't pass over the $form_state.
  // Build the whole form into a #process in which we actually have access
  // to the form state. Idea stolen from entityreference.
  $form['settings'] = array(
    '#type' => 'container',
    '#tree' => TRUE,
    '#process' => array(
      '_fctw_form_process_merge_parent',
      '_fctw_widget_settings_process',
    ),
    '#element_validate' => array(
      '_fctw_widget_settings_validate',
    ),
    '#field' => $field,
    '#instance' => $instance,
  );
  return $form;
}

/**
 * Widget settings form process callback.
 */
function _fctw_form_process_merge_parent($element) {
  $parents = $element['#parents'];
  array_pop($parents);
  $element['#parents'] = $parents;
  return $element;
}

/**
 * Widget settings form process callback.
 */
function _fctw_widget_settings_process($element, $form_state) {
  $field = $element['#field'];
  $instance = $element['#instance'];
  $settings = $instance['widget']['settings'];
  $element['fctw_new_tab_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Text to use on a new tab'),
    '#default_value' => $settings['fctw_new_tab_title'],
    '#size' => 10,
    '#maxlength' => 30,
  );
  $element['fctw_add_new_tab_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Text to use on the "add a new tab" tab'),
    '#default_value' => $settings['fctw_add_new_tab_title'],
    '#size' => 10,
    '#maxlength' => 30,
  );
  $element['fctw_add_new_tab_hover'] = array(
    '#type' => 'textfield',
    '#title' => t('Text to use on the "add a new tab" tab hover'),
    '#default_value' => $settings['fctw_add_new_tab_hover'],
    '#size' => 10,
    '#maxlength' => 30,
  );
  $element['fctw_hide_first_blank_tab'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide first blank tab'),
    '#description' => t('If checked, the first blank field collection tab does not appear on form load but only when the "Add a new tab" tab is clicked.'),
    '#default_value' => $settings['fctw_hide_first_blank_tab'],
  );
  $options = array(
    t('No titles'),
  );
  $fields = field_info_instances('field_collection_item', $field['field_name']);
  foreach ($fields as $field_name => $field) {
    $options[$field_name] = $field['label'];
  }
  $id = 'fctw-title-field-formatter';
  $element['fctw_title_field'] = array(
    '#type' => 'select',
    '#title' => t('Field to use for tab titles'),
    '#default_value' => $settings['fctw_title_field'],
    '#options' => $options,
    '#ajax' => array(
      'callback' => 'fctw_title_field_ajax_callback',
      'wrapper' => $id,
      'element' => $element['#array_parents'],
    ),
  );
  $element['fctw_empty_tab_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Text to use if the field is empty'),
    '#default_value' => $settings['fctw_empty_tab_title'],
    '#size' => 10,
    '#maxlength' => 30,
  );
  $element['fctw_title_field']['#ajax']['element'][] = 'fctw_title_field_formatter';
  if (isset($form_state['input']['_triggering_element_name']) && $form_state['input']['_triggering_element_name'] == 'instance[widget][settings][fctw_title_field]') {
    $title_field = $form_state['input']['instance']['widget']['settings']['fctw_title_field'];
  }
  else {
    $title_field = $element['fctw_title_field']['#default_value'];
  }
  if ($title_field) {
    $field_type = field_info_field($title_field);
    $field_type = $field_type['type'];
    $formatter_options = field_ui_formatter_options($field_type);
    $element['fctw_title_field_formatter'] = array(
      '#type' => 'select',
      '#title' => t('Formatter'),
      '#options' => $formatter_options,
      '#prefix' => '<div id="' . $id . '">',
      '#suffix' => '</div>',
    );
    if (empty($form_state['input']['_triggering_element_name']) && empty($form_state['triggering_element'])) {
      $element['fctw_title_field_formatter']['#default_value'] = $settings['fctw_title_field_formatter'];
    }
    else {
      $keys = array_keys($formatter_options);
      $element['fctw_title_field_formatter']['#default_value'] = reset($keys);
      $element['fctw_title_field_formatter']['#value'] = reset($keys);
    }
  }
  else {
    $element['fctw_title_field_formatter'] = array(
      '#prefix' => '<div id="' . $id . '">',
      '#suffix' => '</div>',
    );
  }
  return $element;
}

/**
 * AJAX callback for the widget settings form.
 */
function fctw_title_field_ajax_callback($form, $form_state) {
  $trigger = $form_state['triggering_element'];
  $key_exists = NULL;
  $return = drupal_array_get_nested_value($form, $trigger['#ajax']['element'], $key_exists);
  if ($key_exists) {
    return $return;
  }
  else {
    array_shift($trigger['#ajax']['element']);
    return drupal_array_get_nested_value($form, $trigger['#ajax']['element'], $key_exists);
  }
}

/**
 * Validate callback for the widget settings form.
 */
function _fctw_widget_settings_validate($form, &$form_state) {
  if (!isset($form_state['values']['instance']['widget']['settings']['fctw_title_field_formatter']) && isset($form_state['input']['instance']['widget']['settings']['fctw_title_field_formatter'])) {
    $form_state['values']['instance']['widget']['settings']['fctw_title_field_formatter'] = $form_state['input']['instance']['widget']['settings']['fctw_title_field_formatter'];
  }
}

/**
 * Implements hook_field_widget_form().
 */
function field_collection_tabs_widget_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $instance['widget']['type'] = 'field_collection_embed';
  $element = field_collection_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
  return $element;
}

/**
 * Implements hook_theme_registry_alter().
 *
 * We want our own version of field_multiple_value_form().
 */
function field_collection_tabs_widget_theme_registry_alter(&$theme_registry) {
  $theme_registry['field_multiple_value_form']['function'] = 'theme_field_collection_tabs_widget_field_multiple_value_form';
}

/**
 * Implements hook_theme().
 */
function field_collection_tabs_widget_theme($existing, $type, $theme, $path) {
  return array(
    'fctw_item_list' => array(
      'variables' => array(
        'items' => array(),
        'title' => NULL,
        'type' => 'ul',
        'attributes' => array(),
      ),
    ),
    'fctw_add_new_tab' => array(
      'variables' => array(
        'instance' => array(),
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form_alter().
 */
function field_collection_tabs_widget_field_widget_form_alter(&$element, &$form_state, $context) {
  $element['#instance'] = $context['instance'];

  // Display Suite forms support.
  if (module_exists('ds_forms')) {
    if ($ds_form = ds_build_load($element, 'field_collection_tabs')) {

      // Use ds_forms_custom_form theme only for field_collections.
      if (isset($element['#instance']['widget']['module']) && $element['#instance']['widget']['module'] == 'field_collection_tabs_widget') {
        if ($layout = ds_get_layout($ds_form->entity_type, $ds_form->bundle, 'form', FALSE)) {

          // Add the theming function and add the layout as a class.
          $element['#theme'] = array(
            'ds_forms_custom_form',
          );
          $element['#form_id'] = 'field_collection_tabs';
          $class = strtr($layout['layout'], '_', '-');
          if (isset($element['#attributes']['class']) && is_array($element['#attributes']['class']) || !isset($element['#attributes']['class'])) {
            $element['#attributes']['class'][] = $class;
          }
          elseif (isset($element['#attributes']['class']) && is_string($element['#attributes']['class'])) {
            $element['#attributes']['class'] .= ' ' . $class . ' ';
          }
        }
      }
    }
  }
}

/**
 * Implements hook_libraries_info().
 */
function field_collection_tabs_widget_libraries_info() {
  $library_path = libraries_get_path('erta');
  $libraries['erta'] = array(
    'name' => 'Easy Responsve Tabs to Accordion plugin',
    'vendor url' => 'https://github.com/samsono/Easy-Responsive-Tabs-to-Accordion',
    'download url' => 'https://github.com/samsono/Easy-Responsive-Tabs-to-Accordion/archive/master.zip',
    'version arguments' => array(
      'file' => 'Easy-Responsive-Tabs-to-Accordion.jquery.json',
      'pattern' => '@"version": "([0-9\\.a-z]+)"@',
      'lines' => 5,
    ),
    'files' => array(
      'js' => array(
        'js/easyResponsiveTabs.js',
      ),
      'css' => array(
        'css/easy-responsive-tabs.css',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_requirements().
 */
function field_collection_tabs_widget_requirements($phase) {
  $t = get_t();
  $requirements = array();
  $requirements['erta'] = array(
    'title' => $t('Easy Responsive Tabs to Accordion version'),
  );
  $erta = libraries_load('erta');
  if (!$erta['installed']) {
    $requirements['erta']['value'] = $t('Not installed');
    $requirements['erta']['description'] = $t('Easy Responsive Tabs to Accordion is not installed.');
    $requirements['erta']['severity'] = REQUIREMENT_ERROR;
  }
  elseif (version_compare($erta['version'], '1.2.1') < 0) {
    $requirements['erta']['value'] = $erta['version'];
    $requirements['erta']['description'] = $t('Easy Responsive Tabs to Accordion version is lower than 1.2.1.');
    $requirements['erta']['severity'] = REQUIREMENT_ERROR;
  }
  else {
    $requirements['erta']['value'] = $erta['version'];
  }
  return $requirements;
}

/**
 * Theme function for generating an item list.
 *
 *  Completely identical to theme_item_list() apart from the
 *  item-list wrapper.
 */
function theme_fctw_item_list($variables) {
  $items = $variables['items'];
  $title = $variables['title'];
  $type = $variables['type'];
  $attributes = $variables['attributes'];
  $output = '';

  // Only output the list container and title, if there are any list items.
  // Check to see whether the block title exists before adding a header.
  // Empty headers are not semantic and present accessibility challenges.
  if (isset($title) && $title !== '') {
    $output .= '<h3>' . $title . '</h3>';
  }
  if (!empty($items)) {
    $output .= "<{$type}" . drupal_attributes($attributes) . '>';
    $num_items = count($items);
    $i = 0;
    foreach ($items as $item) {
      $attributes = array();
      $children = array();
      $data = '';
      $i++;
      if (is_array($item)) {
        foreach ($item as $key => $value) {
          if ($key == 'data') {
            $data = $value;
          }
          elseif ($key == 'children') {
            $children = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $data = $item;
      }
      if (count($children) > 0) {

        // Render nested list.
        $data .= theme_item_list(array(
          'items' => $children,
          'title' => NULL,
          'type' => $type,
          'attributes' => $attributes,
        ));
      }
      if ($i == 1) {
        $attributes['class'][] = 'first';
      }
      if ($i == $num_items) {
        $attributes['class'][] = 'last';
        if (strpos($item, 'add-more_button')) {
          $attributes['class'][] = 'add-more';
        }
      }
      $output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
    }
    $output .= "</{$type}>";
  }
  return $output;
}

/**
 * Implements hook_field_attach_form().
 *
 * Applies field_collection_field_attach_form() to field_collection_tabs and
 * extends it with a modified
 * https://www.drupal.org/files/field_collection-allow-add-another-1788222-12.patch.
 */
function field_collection_tabs_widget_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) {
    $field = field_info_field($field_name);
    if ($field['type'] == 'field_collection' && $instance['widget']['type'] == 'field_collection_tabs' && field_access('edit', $field, $entity_type)) {
      if ($field['settings']['hide_blank_items']) {
        $element_langcode = $form[$field_name]['#language'];
        if ($form[$field_name][$element_langcode]['#max_delta'] > 0) {
          $form[$field_name][$element_langcode]['#max_delta']--;
        }
        $form[$field_name][$element_langcode]['#instance'] = $instance;

        // Force user to explicitly add a fc.
        if (!empty($instance['widget']['settings']['fctw_hide_first_blank_tab']) && !isset($form[$field_name][$element_langcode][0]['#entity']->item_id)) {
          unset($form[$field_name][$element_langcode][0]);
          unset($form_state['field']['#parents'][$field_name][$element_langcode][0]);
        }
      }
      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
        $element_langcode = $form[$field_name]['#language'];
        $element_wrapper = $form[$field_name][$element_langcode]['add_more']['#ajax']['wrapper'];
        for ($i = 0; $i <= $form[$field_name][$element_langcode]['#max_delta']; $i++) {
          if (isset($form[$field_name][$element_langcode][$i]['remove_button'])) {
            $form[$field_name][$element_langcode][$i]['remove_button']['#ajax']['wrapper'] = $element_wrapper;
          }
        }
      }
    }
  }
}

/**
 * Replacement for theme_field_multiple_value_form().
 */
function theme_field_collection_tabs_widget_field_multiple_value_form($variables) {
  $element = $variables['element'];
  $output = '';
  if (isset($element['#instance']) && $element['#instance']['widget']['type'] == 'field_collection_tabs') {
    $instance = $element['#instance'];
    if (isset($element['#cardinality']) && ($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED)) {
      $title_field = $instance['widget']['settings']['fctw_title_field'];
      $id = drupal_html_id($element['#field_name'] . '_values');
      $required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required. ') . '">*</span>' : '';
      $resp_tabs_list = array();
      $resp_tabs_container = array();

      // Sort items according to '_weight' (needed when the form comes back after
      // preview or failed validation)
      $items = array();
      foreach (element_children($element) as $key) {
        if ($key === 'add_more') {
          $add_more_button =& $element[$key];
          $add_more_button['#attributes']['style'] = 'display: none';
        }
        else {
          $items[] =& $element[$key];
        }
      }
      usort($items, '_field_sort_items_value_helper');

      // Add the items as list rows.
      foreach ($items as $key => $item) {
        if (!isset($item['#entity'])) {
          continue;
        }
        $title = field_get_items('field_collection_item', $item['#entity'], $title_field);

        // This is a new field collection
        $string_name = 'fctw:' . $instance['entity_type'] . ':' . $instance['bundle'] . ':' . $instance['field_name'];
        if (!isset($title) || empty($item['#entity']->item_id)) {
          $title = fctw_t($string_name . ':new_tab_title', $instance['widget']['settings']['fctw_new_tab_title']);
        }
        else {
          $display = array(
            'label' => 'hidden',
            'type' => $instance['widget']['settings']['fctw_title_field_formatter'],
          );
          $title = field_view_field($item['#entity_type'], $item['#entity'], $title_field, $display);
          $title = drupal_render($title);
        }

        // If the title field is empty use the default text.
        if (empty($title)) {
          $title = fctw_t($string_name . ':empty_tab_title', $instance['widget']['settings']['fctw_empty_tab_title']);
        }

        // Tab text
        $resp_tabs_list[] = $title;

        // Tab contents
        $item['_weight']['#attributes']['class'][] = 'fctw-weight';
        $resp_tabs_container[] = array(
          'fc_form' => array(
            '#type' => 'container',
            '#attributes' => array(),
            'item' => $item,
          ),
        );
      }

      // Adding the "Add a new tab" tab.
      if (!empty($add_more_button)) {
        $resp_tabs_list[] = theme('fctw_add_new_tab', array(
          'instance' => $instance,
        ));
        $resp_tabs_container[] = array(
          'fc_form' => array(
            '#type' => 'container',
            '#attributes' => array(),
            'item' => $add_more_button,
          ),
        );
      }

      // We add the id of the whole fc widget to Drupal.settings to
      // call easyResponsiveTabs() on them.
      $id = drupal_html_id('fctw');
      drupal_add_js(array(
        'fieldCollectionTabsWidgetIds' => array(
          $id,
        ),
      ), 'setting');

      // Adding necessary javascript to do the magic.
      libraries_load('erta');
      drupal_add_library('system', 'ui.sortable');
      drupal_add_js(drupal_get_path('module', 'field_collection_tabs_widget') . '/field_collection_tabs_widget.js');
      drupal_add_css(drupal_get_path('module', 'field_collection_tabs_widget') . '/field_collection_tabs_widget.css');
      $output = array(
        '#type' => 'container',
        '#attributes' => array(
          'id' => $id,
        ),
        'label' => array(
          '#type' => 'markup',
          '#markup' => '<label>' . t('!title !required', array(
            '!title' => $element['#title'],
            '!required' => $required,
          )) . '</label>',
        ),
        'tabs' => array(
          '#theme' => 'fctw_item_list',
          '#type' => 'ul',
          '#items' => $resp_tabs_list,
          '#attributes' => array(
            'class' => array(
              'resp-tabs-list',
            ),
          ),
        ),
        'resp_tabs_container' => array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'resp-tabs-container',
            ),
          ),
          'rtc' => $resp_tabs_container,
        ),
      );
      $output = drupal_render($output);
    }
    else {
      foreach (element_children($element) as $key) {
        $output .= drupal_render($element[$key]);
      }
    }
  }
  else {

    // Cannot use theme() here because of field_collection_tabs_widget_theme_registry_alter().
    return theme_field_multiple_value_form($variables);
  }
  return $output;
}

/**
 * Theme function to theme the "Add a new tab" tab.
 */
function theme_fctw_add_new_tab(&$variables) {
  $instance = $variables['instance'];
  $name = 'fctw:' . $instance['entity_type'] . ':' . $instance['bundle'] . ':' . $instance['field_name'];
  return '<div class="add-more_button" title="' . fctw_t($name . ':add_new_tab_hover', $instance['widget']['settings']['fctw_add_new_tab_hover']) . '">' . fctw_t($name . ':add_new_tab_title', $instance['widget']['settings']['fctw_add_new_tab_title']) . '</div>';
}

/**
 * Helper function to integrate with the i18n_strings module.
 *
 * @param string $name
 *   Textgroup and context glued with ':'.
 * @param string $string
 *   The raw db value for the given property
 * @param string $langcode
 *   Optional language code to override the page requested one.
 */
function fctw_t($name, $string, $langcode = NULL) {
  $i18n =& drupal_static(__FUNCTION__);
  if (!isset($i18n)) {
    $i18n = module_exists('i18n_string');
  }
  return $i18n ? i18n_string($name, $string, array(
    'langcode' => $langcode,
  )) : $string;
}

/**
 * Implements hook_i18n_string_info().
 */
function field_collection_tabs_widget_i18n_string_info() {
  $groups['fctw'] = array(
    'title' => t('Field collection tabs widget'),
    'description' => t('FCTW strings for localization.'),
    'format' => FALSE,
    'list' => TRUE,
    'refresh callback' => 'field_collection_tabs_widget_i18n_string_refresh',
  );
  return $groups;
}

/**
 * i18n_string callback for refreshing all strings.
 */
function field_collection_tabs_widget_i18n_string_refresh() {
  foreach (field_info_instances() as $entity => $bundles) {
    foreach ($bundles as $bundle => $field_instances) {
      foreach ($field_instances as $field_instance => $settings) {
        if ($settings['widget']['type'] == 'field_collection_tabs') {
          i18n_string_update("fctw:{$entity}:{$bundle}:{$field_instance}:new_tab_title", $settings['widget']['settings']['fctw_new_tab_title']);
          i18n_string_update("fctw:{$entity}:{$bundle}:{$field_instance}:add_new_tab_title", $settings['widget']['settings']['fctw_add_new_tab_title']);
          i18n_string_update("fctw:{$entity}:{$bundle}:{$field_instance}:add_new_tab_hover", $settings['widget']['settings']['fctw_add_new_tab_hover']);
          i18n_string_update("fctw:{$entity}:{$bundle}:{$field_instance}:empty_tab_title", $settings['widget']['settings']['fctw_empty_tab_title']);
        }
      }
    }
  }
  return TRUE;
}

Functions

Namesort descending Description
fctw_t Helper function to integrate with the i18n_strings module.
fctw_title_field_ajax_callback AJAX callback for the widget settings form.
field_collection_tabs_widget_field_attach_form Implements hook_field_attach_form().
field_collection_tabs_widget_field_widget_form Implements hook_field_widget_form().
field_collection_tabs_widget_field_widget_form_alter Implements hook_field_widget_form_alter().
field_collection_tabs_widget_field_widget_info Implements hook_field_formatter_info().
field_collection_tabs_widget_field_widget_settings_form Implements hook_field_formatter_settings_form().
field_collection_tabs_widget_i18n_string_info Implements hook_i18n_string_info().
field_collection_tabs_widget_i18n_string_refresh i18n_string callback for refreshing all strings.
field_collection_tabs_widget_libraries_info Implements hook_libraries_info().
field_collection_tabs_widget_requirements Implements hook_requirements().
field_collection_tabs_widget_theme Implements hook_theme().
field_collection_tabs_widget_theme_registry_alter Implements hook_theme_registry_alter().
theme_fctw_add_new_tab Theme function to theme the "Add a new tab" tab.
theme_fctw_item_list Theme function for generating an item list.
theme_field_collection_tabs_widget_field_multiple_value_form Replacement for theme_field_multiple_value_form().
_fctw_form_process_merge_parent Widget settings form process callback.
_fctw_widget_settings_process Widget settings form process callback.
_fctw_widget_settings_validate Validate callback for the widget settings form.