3

I've a multipage form (where each page is determined by field group) where on the first page user type number of people in text field and on the second one I would like to show X number of field collection instances based on the given number. So if user selects 5 people, I expect to see 5 field collection placeholders to fill for the user and no more Add another item buttons (especially when field cardinality is set to Unlimited).

Is there any solution which can achieve setting up cardinality per form state by a rule or from the code?

6

There are the following hooks which allow to alter the widget form such as hook_field_widget_form_alter and hook_field_widget_WIDGET_TYPE_form_alter, but they only apply to widget form element.

As for workaround, it's possible to set the field to Unlimited and use hook_form_alter to remove add_more element manually like:

$form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE;
$form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;

or overriding/removing #theme:

unset($form['field_foo'][LANGUAGE_NONE][0]['#theme']);

for certain conditions.


Using Rules, it's possible to use Rules Forms Support module which can deal with form elements, so you can change the items_count. Here is sample rule dealing with a multi value field in Entityform:

/**
 * Implements hook_action_info().
 */
function mymodule_entityform_rules_rules_action_info() {
  return array(
    'foo_entityform_rules_set_cardinality_value' => array(
      'label' => t('Change items count of a multi value field'),
      'group' => t('Rules Forms'),
      'parameter' => array(
        'form' => array('type' => 'form', 'label' => t('Form')),
        'form_state' => array('type' => 'form_state', 'label' => t('Form State')),
        'element' => array('type' => 'form_element', 'label' => t('Target Element')),
        'value' => array('type' => 'form_element', 'label' => t('Value from Element')),
      ),
      'base' => 'foo_entityform_rules_set_cardinality_value',
      'callbacks' => array(
        'access' => 'rules_forms_integration_access',
        'form_alter' => 'rules_forms_element_form_alter',
      ),
    ),
  );
}

/**
 * Implementation of rule callback.
 */
function mymodule_entityform_rules_set_cardinality_value($form, $form_state, $element, $value) {
  module_load_include("module", "rules_forms", 'rules_forms');
  $val_elem = &_rules_forms_get_element($form, $value);
  if (!isset($form_state['field'])) {
    return;
  }

  $element = substr($element, strpos($element, ':') + 1);
  $path = explode(':', $element);
  if (count($path) == 1) {
    $path[] = LANGUAGE_NONE;
  }

  $field_state = &drupal_array_get_nested_value($form_state['field'], $path);
  if (!$field_state) {
    return;
  }

  $limit = !empty($val_elem['#value']) ? $val_elem['#value'] : (!empty($val_elem['#default_value']) ? $val_elem['#default_value'] : NULL);
  $field_state['items_count'] = $limit;
  $field_state['field']['cardinality'] = $limit; // Won't work for field_multiple_value_form().
}

This works when field is set to Unlimited, however Add another and Remove buttons still needs to be removed from form_alter as shown below:

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, &$form_state, $form_id) {

    // Check cardinality.
    // If items_count for a field has reach its cardinality limit,
    // then remove Add more and Remove buttons.
    foreach ($form as $key => $item) {
      if (is_array($item) && !empty($item['#type']) && !empty($item['#language']) && $item['#type'] == 'container') {
        $field = &$form[$key];
        $langcode = $field['#language'] ?: LANGUAGE_NONE;
        $field_state = field_form_get_state($field[$langcode]['#field_parents'], $field[$langcode]['#field_name'], $langcode, $form_state);
        if ($field_state['items_count'] == $field_state['field']['cardinality']) {
          $field[$langcode]['add_more']['#access'] = FALSE;
          foreach (array_column($field[$langcode], 'remove_button') as $key2 => $rm_item) {
            $field[$langcode][$key2]['remove_button']['#access'] = FALSE;
          }
        }
      }
    } // end: foreach
  }
}

It's also worth to try some patches from Allow cardinality to be restricted (overridden and reduced) in the field instance settings or Add hook alter to override field_multiple_value_form().

There is also Field instance cardinality module which allows the cardinality of specific instances of fields to be overridden.

Some other proposed workaround from here by @donquixote (without actually hacking Drupal core) could include:

  • a new widget type with FIELD_BEHAVIOR_CUSTOM with widget settings to change a cardinality limit, the name of another ("decorated") field widget type and other settings for the decorated field widget type,
  • add a number field in the instance settings form and use hook_field_widget_properties_alter to replace $instance['widget']['module'] (keep original in $instance['widget']['_module']) and replace override $instance['widget']['type'] with own FIELD_BEHAVIOR_CUSTOM and save the original in $instance['widget']['_type'], so you can do the same decorator trick in hook_field_widget_form as above.

Modules

Related modules:

| improve this answer | |
0

Another approach suggested by @BDuell in his post:

  // In your hook_form_FORM_ID_alter() function...

  // Our field and the number of values that we need to have in it...
  $field_name = 'field_machine_name';
  $items_count = count($array_of_values);

  // If we have an array of values that need to be prepopulated...
  if (!empty($array_of_values) && $form_state['field'][$field_name][LANGUAGE_NONE]['items_count']!=$items_count) {
    $form_state['field'][$field_name][LANGUAGE_NONE]['items_count'] = $items_count;
    $form_theme = $form[$field_name][LANGUAGE_NONE]['#theme'];
    $items = &$form_state['field'][$field_name][LANGUAGE_NONE];
    // Generate required number of fields collection
    if ($items_count > 1 and $items['items_count'] != $items_count) {
      $items['items_count'] = $items_count;
      $items['field']['cardinality'] = $items_count;
      $form[$field_name][LANGUAGE_NONE] = field_multiple_value_form($items['field'], $items['instance'], LANGUAGE_NONE, array(), $form, $form_state);
      // Reset theme function, as field_multiple_value_form hijacks it
      $form[$field_name][LANGUAGE_NONE]['#theme'] = $form_theme;
    }

    // Tell our form that we need to rebuild once we are done...
    $rebuild_p = TRUE;
  }

  // ANY OTHER FORM ALTER STUFF

  // If we need to rebuild, notify the form...
  if ($rebuild_p) $form['#after_build'][] = 'MYMODULE_after_build';

then in custom module you can define hook_after_build like:

/**
 * Rebuild the form if necessary...
 */
function MYMODULE_after_build($form, &$form_state) {
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.