5

I'm trying to create a field widget which is a collection of multiple fields. I'm using hook_field_widget_info() and hook_field_widget_form(), something like this:

function module_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $element += array(
    '#type' => 'container',
    '#element_validate' => array('custom_validation_function'),
  );

  $options = array('some values here');
  $element['existing'] = array(
    '#type' => 'select',
    '#title' => 'Recipient & delivery address',
    '#options' => $options,
    '#ajax' => array(
      'wrapper' => $wrapper_id,
      'callback' => '_my_custom_ajax_callback'
    )
  );
  $element['new'] = array(
    '#prefix' => '<div id="' . $wrapper_id . '">',
    '#suffix' => '</div>',
  );

  $new_entity = entity_create('entity_type', array('type' => 'bundle')); 

  field_attach_form('entity_type', $new_entity, $element['new'], $form_state);

  return $element;
}

function _my_custom_ajax_callback($form, &$form_state) {
  return $form['new'];
}

The problem is that in the ajax callback the $form array is different than the one in the hook_field_widget_form(). In the ajax callback the form array is the entire form structure, not just the one defining that widget...

Any ideas?

2 Answers 2

3

I've tried to do this a few times and never really got very far (for the reasons you've already mentioned).

The way I found to do it instead was to install the Field Collection module (and Field Collection Table for a better UX), and use that to group the fields I need into a single field, which is represented as a single widget.

Then I just hook into the particular form that's showing the field and add the AJAX that way. As coincidence would have it I've just done exactly this a few hours ago. I know this is out of context but here's a (slightly anonymised) dump of the code I used; hopefully it'll give you a place to start if you choose to go down this route:

function MYMODULE_form_directory_listing_node_form_alter(&$form, &$form_state, $form_id) {
  $selected_country_tid = $form['field_address'][LANGUAGE_NONE][0]['field_country'][LANGUAGE_NONE]['#default_value'][0];

  $form['field_address'][LANGUAGE_NONE][0]['field_country'][LANGUAGE_NONE]['#ajax'] = array(
    'wrapper' => 'directory-form-county-wrapper',
    'callback' => 'MYMODULE_form_directory_listing_country_ajax'
  );

  $form['field_address'][LANGUAGE_NONE][0]['field_county'][LANGUAGE_NONE]['#prefix'] = '<div id="directory-form-county-wrapper">';
  $form['field_address'][LANGUAGE_NONE][0]['field_county'][LANGUAGE_NONE]['#suffix'] = '</div>';

  $selected_country_term = taxonomy_term_load($selected_country_tid);

  $options = array();
  foreach (_MYMODULE_get_country_counties($selected_country_term) as $county) {
    $options[$county->tid] = $county->name;
  }

  $form['field_address'][LANGUAGE_NONE][0]['field_county'][LANGUAGE_NONE]['#options'] = $options;
}


function MYMODULE_form_directory_listing_country_ajax(&$form, &$form_state) {
  return $form['field_address'][LANGUAGE_NONE][0]['field_county'][LANGUAGE_NONE];
}

field_address in the above example is the Field Collection field.

7
  • Ok, I'll try to add ajax later on in a form_alter hook. Maybe this will work :-s Commented May 22, 2012 at 16:32
  • Ah yeah, that should work too. I've never had much luck adding AJAX in a widget form directly
    – Clive
    Commented May 22, 2012 at 16:37
  • Bad luck. Field collection doesn't work for me because I want to attach an existing entity's form to the widget. The Field collection only allows me to add different fields, not existing form :( Commented May 23, 2012 at 9:56
  • Chuck in the Inline Entity Form module, choose that as the entity reference field widget and you'll be good to go :)
    – Clive
    Commented May 23, 2012 at 9:59
  • Doesn't work either. I'll stick to the good old custom code writing. Field collection is ok if you just need some custom fields there but not for what I need. I'm gonna accept this answer though because for a general use case it works. Commented May 23, 2012 at 11:19
0

I know this is an answer to an old question but I was struggling with this issue myself and came to the realisation that for whatever reason, somehow AJAX properties don't seem to work when declared as part of a field widget. What I did instead was built the basic skeleton of the widget then added the AJAX stuff using hook_form_alter. The following isn't a complete example but it should give an idea of how to go about this for anyone looking in.

Firstly, build the basic outline of your form. In my case I had 3 select elements in the one widget, the latter 2 being populated based on the previous select element's values using the AJAX parameters. Note that initially the latter 2 select elements aren't actually declared as select elements until the AJAX kicks in.

/**
 * Implements hook_field_widget_form().
 */
function myfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  // Make the parent element a fieldset.
  $widget = array(
    '#type' => 'fieldset',
    '#title' => t('My field'),
  );
  // The top level myfield select element.
  $widget['myfield_1'] = array(
    '#type' => 'select',
    '#default_value' => $myfield_1_defaults,
    '#multiple' => TRUE,
    '#options' => $myfield_1_options,
    '#delta' => $delta,
  );
  // The second select element.
  $widget['myfield_2'] = array(
    '#prefix' => '<div id="myfield-2-wrapper">',
    '#suffix' => '</div>',
  );
  // The third select element.
  $widget['myfield_3'] = array(
    '#prefix' => '<div id="myfield-3-wrapper">',
    '#suffix' => '</div>',
  );
  // Assign the $widget variable back to the $element variable.
  $element['myfield'] = $widget;
  // I don't know if this is necessary but I added it just in case.
  $element['#tree'] = TRUE;
  // Don't forget to return it!
  return $element;
}

Now using hook_form_alter I added the AJAX parameters and the logic for dealing with it. You might not need to do all the checks I did but I figured it was the best way of finding out if the element could even be present on the form and if it could be then to check if it was actually there.

/**
 * Implements hook_form_alter().
 */
function myfield_form_alter(&$form, &$form_state, $form_id) {
  // Firstly, check if this is an entity form or not.
  if (isset($form['#entity_type'])) {
    // Check if there is a bundle declared.
    if (isset($form['#bundle'])) {
      // If so, get a list of bundles containing the myfield field.
      // Note that myfield_active_bundles is a helper function that utilises field_info_field_map() to return any
      // entities/bundles that contain our field.
      $bundles = myfield_active_bundles();
      // Is the current bundle present in the bundles array?
      if (isset($bundles[$form['#bundle']])) {
        // Get the language.
        $lang = $form['language']['#value'];
        // Add the AJAX attributes to the myfield_1 form element.
        $form['field_myfield'][$lang]['myfield']['myfield_1']['#ajax'] = array(
          'callback' => 'myfield_2_ajax_callback',
          'wrapper' => 'myfield-2-wrapper',
          'event' => 'change',
          'method' => 'replace',
          'effect' => 'fade',
        );
        // Get the values from myfield_1.
        $myfield_1_values = '';
        $myfield_1_values = '';
        if (isset($form_state['values']['field_myfield'][$lang]['myfield']['myfield_1'])) {
          $myfield_1_values = $form_state['values']['field_myfield'][$lang]['myfield']['myfield_1'];
        }
        // If there have been values set on myfield_1, build myfield_2.
        if (!empty($myfield_1_values)) {
          // Build the form element - we rewrite the whole element using a helper function that simply returns a Form
          // API array. I used a helper function to reduce initial overhead and for tidyness.
          $form['field_myfield'][$lang]['myfield']['myfield_2'] = _myfield_2_form_build($form, $form_state, $myfield_1_values);
        }
        // Get the values from myfield_2.
        $myfield_2_values = '';
        if (isset($form_state['values']['field_myfield'][$lang]['myfield']['myfield_2'])) {
          $myfield_2_values = $form_state['values']['field_myfield'][$lang]['myfield']['myfield_2'];
        }
        // If there have been values set on myfield_1, build myfield_2.
        if (!empty($myfield_2_values)) {
          // Build the form element as described above.
          $form['field_myfield'][$lang]['myfield']['myfield_3'] = _myfield_3_form_build($form, $form_state, $myfield_2_values);
        }
      }
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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