You are here

function hierarchical_select_process in Hierarchical Select 6.3

Same name and namespace in other branches
  1. 5.3 hierarchical_select.module \hierarchical_select_process()
  2. 5 hierarchical_select.module \hierarchical_select_process()
  3. 5.2 hierarchical_select.module \hierarchical_select_process()

Hierarchical select form element type #process callback.

1 string reference to 'hierarchical_select_process'
hierarchical_select_elements in ./hierarchical_select.module
Implementation of hook_elements().

File

./hierarchical_select.module, line 400
This module defines the "hierarchical_select" form element, which is a greatly enhanced way for letting the user select items in a hierarchy.

Code

function hierarchical_select_process($element, $edit, &$form_state, $form) {
  if (!is_array($element['#value']) || !isset($element['#value']['hsid'])) {
    $hsid = uniqid();
  }
  else {
    $hsid = check_plain($element['#value']['hsid']);
  }
  $element['hsid'] = array(
    '#type' => 'hidden',
    '#value' => $hsid,
  );

  // A hierarchical_select form element expands to multiple items. For example
  // $element['hsid'] got set just above. If #value is not an array, then
  // form_set_value(), which is called by form_builder() will fail, because it
  // assumes that #value is an array, because we are trying to set a child of
  // it.
  if (!is_array($element['#value'])) {
    $element['#value'] = array(
      $element['#value'],
    );
  }

  // Store the #name property of each hierarchical_select form item, this is
  // necessary to find this form item back in an AJAX callback.
  _hierarchical_select_store_name($element, $hsid);

  // Get the config and convert the 'special_items' setting to a more easily
  // accessible format.
  $config = $element['#config'];
  if (isset($config['special_items'])) {
    $special_items['exclusive'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_exclusive'));
    $special_items['none'] = array_keys(array_filter($config['special_items'], '_hierarchical_select_special_item_none'));
  }

  // Set up Javascript and add settings specifically for the current
  // hierarchical select.
  $config = _hierarchical_select_inherit_default_config($element['#config']);
  _hierarchical_select_setup_js();
  _hierarchical_select_setup_js($form_state);
  $settings = array(
    'HierarchicalSelect' => array(
      'settings' => array(
        $hsid => array(
          'animationDelay' => $config['animation_delay'] == 0 ? (int) variable_get('hierarchical_select_animation_delay', 400) : $config['animation_delay'],
          'cacheId' => $config['module'] . '_' . implode('_', is_array($config['params']) ? $config['params'] : array()),
          'renderFlatSelect' => isset($config['render_flat_select']) ? (int) $config['render_flat_select'] : 0,
          'createNewItems' => isset($config['editability']['status']) ? (int) $config['editability']['status'] : 0,
          'createNewLevels' => isset($config['editability']['allow_new_levels']) ? (int) $config['editability']['allow_new_levels'] : 0,
          'resizable' => isset($config['resizable']) ? (int) $config['resizable'] : 0,
          'path' => $config['path'],
        ),
      ),
    ),
  );
  _hierarchical_select_add_js_settings($settings, $form_state);

  // Basic config validation and diagnostics.
  if (HS_DEVELOPER_MODE) {
    $diagnostics = array();
    if (!isset($config['module']) || empty($config['module'])) {
      $diagnostics[] = t("'module is not set!");
    }
    elseif (!module_exists($config['module'])) {
      $diagnostics[] = t('the module that should be used (module) is not installed!', array(
        '%module' => $config['module'],
      ));
    }
    else {
      $required_params = module_invoke($config['module'], 'hierarchical_select_params');
      $missing_params = array_diff($required_params, array_keys($config['params']));
      if (!empty($missing_params)) {
        $diagnostics[] = t("'params' is missing values for: ") . implode(', ', $missing_params) . '.';
      }
    }
    $config_id = isset($config['config_id']) && is_string($config['config_id']) ? $config['config_id'] : 'none';
    if (empty($diagnostics)) {
      _hierarchical_select_log("Config diagnostics (config id: {$config_id}): no problems found!");
    }
    else {
      $diagnostics_string = print_r($diagnostics, TRUE);
      $message = "Config diagnostics (config id: {$config_id}): {$diagnostics_string}";
      _hierarchical_select_log($message);
      $element['#type'] = 'item';
      $element['#value'] = '<p><span style="color:red;">Fix the indicated errors in the #config property first!</span><br />' . nl2br($message) . '</p>';
      return $element;
    }
  }

  // Calculate the selections in both the hierarchical select and the dropbox,
  // we need these before we can render anything.
  list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element);
  if (HS_DEVELOPER_MODE) {
    _hierarchical_select_log("Calculated hierarchical select selection:");
    _hierarchical_select_log($hs_selection);
    if ($config['dropbox']['status']) {
      _hierarchical_select_log("Calculated dropbox selection:");
      _hierarchical_select_log($db_selection);
    }
  }

  // If:
  // - the special_items setting has been configured
  // - at least one special item has the 'exclusive' property
  // - the dropbox is enabled
  // then do the necessary processing to make exclusive lineages possible.
  if (isset($special_items) && count($special_items['exclusive']) && $config['dropbox']['status']) {

    // When the form is first loaded, $db_selection will contain the selection
    // that we should check, but in updates, $hs_selection will.
    $selection = !empty($hs_selection) ? $hs_selection : $db_selection;

    // If the current selection of the hierarchical select matches one of the
    // configured exclusive items, then disable the dropbox (to ensure an
    // exclusive selection).
    $exclusive_item = array_intersect($selection, $special_items['exclusive']);
    if (count($exclusive_item)) {

      // By also updating the configuration stored in $element, we ensure that
      // the validation step, which extracts the configuration again, also gets
      // the updated config.
      $element['#config']['dropbox']['status'] = 0;
      $config = _hierarchical_select_inherit_default_config($element['#config']);

      // Set the hierarchical select to the exclusive item and make the
      // dropbox empty.
      $hs_selection = array(
        0 => reset($exclusive_item),
      );
      $db_selection = array();
    }
  }

  // Generate the $hierarchy and $dropbox objects using the selections that
  // were just calculated.
  $dropbox = !$config['dropbox']['status'] ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
  $hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
  if (HS_DEVELOPER_MODE) {
    _hierarchical_select_log('Generated hierarchy in ' . $hierarchy->build_time['total'] . ' ms:');
    _hierarchical_select_log($hierarchy);
    if ($config['dropbox']['status']) {
      _hierarchical_select_log('Generated dropbox in ' . $dropbox->build_time . ' ms: ');
      _hierarchical_select_log($dropbox);
    }
  }

  // Store the hierarchy object in the element, we'll need this if the user's
  // browser supports the active cache system.
  $element['hierarchy'] = array(
    '#type' => 'value',
    '#value' => $hierarchy,
  );

  // Ensure that #tree is enabled!
  $element['#tree'] = TRUE;

  // If render_flat_select is enabled, render a flat select.
  if ($config['render_flat_select']) {
    $element['flat_select'] = _hierarchical_select_process_render_flat_select($hierarchy, $dropbox, $config);
  }

  // Render the hierarchical select.
  $element['hierarchical_select'] = array(
    '#theme' => 'hierarchical_select_selects_container',
  );
  $element['hierarchical_select']['selects'] = _hierarchical_select_process_render_hs_selects($hsid, $hierarchy);

  // The selects in the hierarchical select should inherit the #size property.
  foreach (element_children($element['hierarchical_select']['selects']) as $depth) {
    $element['hierarchical_select']['selects'][$depth]['#size'] = isset($element['#size']) ? $element['#size'] : 0;
  }

  // Check if a new item is being created.
  $creating_new_item = FALSE;
  if (isset($element['#value']['hierarchical_select']['selects'])) {
    foreach ($element['#value']['hierarchical_select']['selects'] as $depth => $value) {
      if ($value == 'create_new_item' && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
        $creating_new_item = TRUE;

        // We want to override the select in which the "create_new_item"
        // option was selected and hide all selects after that, if they exist.
        for ($i = $depth; $i < count($hierarchy->lineage); $i++) {
          unset($element['hierarchical_select']['selects'][$i]);
        }
        $element['hierarchical_select']['create_new_item'] = array(
          '#prefix' => '<div class="' . str_replace('_', '-', $value) . '">',
          '#suffix' => '</div>',
        );
        $item_type_depth = $value == 'create_new_item' ? $depth : $depth + 1;
        $item_type = count($config['editability']['item_types']) == $item_type_depth ? t($config['editability']['item_types'][$item_type_depth]) : t('item');
        $element['hierarchical_select']['create_new_item']['input'] = array(
          '#type' => 'textfield',
          '#size' => 20,
          '#maxlength' => 255,
          '#default_value' => t('new @item', array(
            '@item' => $item_type,
          )),
          '#attributes' => array(
            'title' => t('new @item', array(
              '@item' => $item_type,
            )),
            'class' => 'create-new-item-input',
          ),
          // Use a #theme callback to prevent the textfield from being wrapped
          // in a div. This simplifies the CSS and JS code.
          '#theme' => 'hierarchical_select_textfield',
        );
        $element['hierarchical_select']['create_new_item']['create'] = array(
          '#type' => 'button',
          '#value' => t('Create'),
          '#attributes' => array(
            'class' => 'create-new-item-create',
          ),
        );
        $element['hierarchical_select']['create_new_item']['cancel'] = array(
          '#type' => 'button',
          '#value' => t('Cancel'),
          '#attributes' => array(
            'class' => 'create-new-item-cancel',
          ),
        );
      }
    }
  }
  if ($config['dropbox']['status']) {
    if (!$creating_new_item) {

      // Append an "Add" button to the selects.
      $element['hierarchical_select']['dropbox_add'] = array(
        '#type' => 'button',
        '#value' => t('Add'),
        '#attributes' => array(
          'class' => 'add-to-dropbox',
        ),
      );
    }
    if ($config['dropbox']['limit'] > 0) {

      // Zero as dropbox limit means no limit.
      if (count($dropbox->lineages) == $config['dropbox']['limit']) {
        $element['dropbox_limit_warning'] = array(
          '#value' => t("You've reached the maximal number of items you can select."),
          '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">',
          '#suffix' => '</p>',
        );

        // Disable all child form elements of $element['hierarchical_select].
        _hierarchical_select_mark_as_disabled($element['hierarchical_select']);
      }
    }

    // Add the hidden part of the dropbox. This will be used to store the
    // currently selected lineages.
    $element['dropbox']['hidden'] = array(
      '#prefix' => '<div class="dropbox-hidden">',
      '#suffix' => '</div>',
    );
    $element['dropbox']['hidden'] = _hierarchical_select_process_render_db_hidden($hsid, $dropbox);

    // Add the dropbox-as-a-table that will be visible to the user.
    $element['dropbox']['visible'] = _hierarchical_select_process_render_db_visible($hsid, $dropbox);
  }

  // This button and accompanying help text will be hidden when Javascript is
  // enabled.
  $element['nojs'] = array(
    '#prefix' => '<div class="nojs">',
    '#suffix' => '</div>',
  );
  $element['nojs']['update_button'] = array(
    '#type' => 'button',
    '#value' => t('Update'),
    '#attributes' => array(
      'class' => 'update-button',
    ),
  );
  $element['nojs']['update_button_help_text'] = array(
    '#value' => _hierarchical_select_nojs_helptext($config['dropbox']['status']),
    '#prefix' => '<div class="help-text">',
    '#suffix' => '</div>',
  );

  // Ensure the render order is correct.
  $element['hierarchical_select']['#weight'] = 0;
  $element['dropbox_limit_warning']['#weight'] = 1;
  $element['dropbox']['#weight'] = 2;
  $element['nojs']['#weight'] = 3;

  // This prevents values from in $element['#post'] to be used instead of the
  // generated default values (#default_value).
  // For example: $element['hierarchical_select']['selects']['0']['#default_value']
  // is set to 'label_0' after an "Add" operation. When $element['#post'] is
  // NOT unset, the corresponding value in $element['#post'] will be used
  // instead of the default value that was set. This is undesired behavior.
  if (isset($element['#post'])) {
    unset($element['#post']);
  }

  // Finally, calculate the return value of this hierarchical_select form
  // element. This will be set in _hierarchical_select_validate(). (If we'd
  // set it now, it would be overridden again.)
  $element['#return_value'] = _hierarchical_select_process_calculate_return_value($hierarchy, $config['dropbox']['status'] ? $dropbox : FALSE, $config['module'], $config['params'], $config['save_lineage']);

  // Add a validate callback, which will:
  // - validate that the dropbox limit was not exceeded.
  // - set the return value of this form element.
  // Also make sure it is the *first* validate callback.
  $element['#element_validate'] = isset($element['#element_validate']) ? $element['#element_validate'] : array();
  $element['#element_validate'] = array_merge(array(
    '_hierarchical_select_validate',
  ), $element['#element_validate']);
  if (HS_DEVELOPER_MODE) {
    $element['log'] = array(
      '#type' => 'value',
      '#value' => _hierarchical_select_log(NULL, TRUE),
    );
    $settings = array(
      'HierarchicalSelect' => array(
        'initialLog' => array(
          $hsid => $element['log']['#value'],
        ),
      ),
    );
    _hierarchical_select_add_js_settings($settings, $form_state);
  }

  // If the form item is marked as disabled, disable all child form items as
  // well.
  if (isset($element['#disabled']) && $element['#disabled']) {
    _hierarchical_select_mark_as_disabled($element);
  }

  // Remove #options in case it's been added.
  if (isset($element['#options'])) {
    unset($element['#options']);
  }
  return $element;
}