0

Note: There is some information here that may be extraneous, because I started with a broader question. I still think the context might help so I'm leaving the detail in. Feel free to skip to The Code and The Question though, and come back for the detail.

The Detail

I have a node form that has a boolean field, and a multivalue field collection field that contains a multivalue field collection field, that contains a boolean field. Since that makes little sense, let me try to diagram it. (There are of course other fields, but these are the relevant ones)

field_mr_home_leave - boolean - single value field_mr_fc_itineraries - field collection - unlimited values |--- field_mr_fc_legs - field collection - unlimited values |--- field_mr_fc_leg_home_leave - boolean - single value

I'm sorry the names here are a little confusing, please note one of the home_leave fields has fc_leg in the prefix. The home leave field deeper in the hierarchy is the ones) I want to be dependent on the value of the home_leave field at the top level.

Because conditional fields does not give me the option of making sub fields of a field collection dependent on the value of a field in the parent form (if it does, please tell me how), I want to write code in my custom module to alter the form so that when the value of field_mr_home_leave is set to 1, field_mr_fc_leg_home_leave is visible, and when field_mr_home_leave is set to 0 field_mr_fc_leg_home_leave is invisible.

In terms of the form, if the parent boolean is set to Yes (1), I want to see any boolean fields in the sub sub field collection. If it is set to No (0) they should not show up.

The Code

The form has an id of mission_request_node_form and my module is called missions. Here is the code I have so far:

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function missions_form_mission_request_node_form_alter(&$form, &$form_state, $form_id)
{
  $form['field_mr_home_leave'][LANGUAGE_NONE]['#ajax'] = array(
    'callback' => 'missions_hide_home_leave_toggle',
    'wrapper' => 'itinerary-section',
    'effect' => 'slide',
  );
  $form['field_mr_fc_itineraries']['#prefix'] = '<div id="itinerary-section">';
  $form['field_mr_fc_itineraries']['#suffix'] = '</div>';
  if ((!empty($form_state['values']['field_mr_home_leave']) && $form_state['values']['field_mr_home_leave'] == 1) || (empty($form_state['values']['field_mr_home_leave']) && $form['field_mr_home_leave'][LANGUAGE_NONE]['#default_value'] == 1)) {
    $home_leave_toggle_access = true;
  } else {
    $home_leave_toggle_access = false;
  }
  foreach ($form['field_mr_fc_itineraries'][LANGUAGE_NONE] as $key => &$itinerary) {
    if (is_integer($key)) {
      foreach ($itinerary['field_mr_fc_legs'][LANGUAGE_NONE] as $key => &$leg) {
        if (is_integer($key)) {
          $leg['field_mr_fc_leg_home_leave']['#access'] = $home_leave_toggle_access;
        }
      }
    }
  }
  $form_state['rebuild'] = TRUE;

}

function missions_hide_home_leave_toggle($form, $form_state) {
  dpm('testing');
  return $form['field_mr_fc_itineraries'];
}

I'm adding the #ajax array to the parent boolean field and then testing to see what the value of the field is. I then set the #access value of any sub booleans present. This works on first run, the parent boolean defaults to 0 and so the initial sub boolean field is hidden. Unfortunately, then I change the value of the parent boolean, the ajax spinner triggers, but with a breakpoint set in my IDE in the callback function I never see any evidence that the callback is called. What can I do to get the change to trigger the callback.

What else I've tried

I thought maybe, since the subfield is not a required field, perhaps I could just hide it using the #states attribute. This worked great until the moment I added another leg to the itinerary, at which point the subfield showed up on each leg, and I had to toggle the value of field_mr_home_leave to get the fields to be hidden again.

Here's the code for that:

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function missions_form_mission_request_node_form_alter(&$form, &$form_state, $form_id)
{
  foreach ($form['field_mr_fc_itineraries'][LANGUAGE_NONE] as $key => &$itinerary) {
    if (is_integer($key)) {
      foreach ($itinerary['field_mr_fc_legs'][LANGUAGE_NONE] as $key => &$leg) {
        if (is_integer($key)) {
          $leg['field_mr_fc_leg_home_leave']['#states'] = array(
            'visible' => array(
              ':input[name="field_mr_home_leave[und]"]' => array('value' => '1'),
            ),
          );
        }
      }
    }
  }
}

I tried deleting dependencies on the same field in conditional fields, then disabled conditional fields entirely to no avail.

I've tracked this down to form_get_cache() failing to return a cached form. I note that nothing is being cached for this form.

Newest Update

I probably need to rewrite this question to be more concise, but here is what I've found: The cache_form table is never written to. I've simplified this as far as I can. I started with a clean drupal install on Acquia Dev Desktop, I installed my module, and created a content type that just contains the field and subfield at the same level so I don't have field collections or anything else to worry about.

Here is my latest code:

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function missions_form_mission_request_node_form_alter(&$form, &$form_state, $form_id)
{
  $form['field_mr_home_leave'][LANGUAGE_NONE]['#ajax'] = array(
    'callback' => 'missions_hide_home_leave_toggle',
    'wrapper' => 'itinerary-section',
    'effect' => 'slide',
  );
  $form['field_mr_fc_leg_home_leave']['#prefix'] = '<div id="itinerary-section">';
  $form['field_mr_fc_leg_home_leave']['#suffix'] = '</div>';
  if ((!empty($form_state['values']['field_mr_home_leave']) && $form_state['values']['field_mr_home_leave'] == 1) || (empty($form_state['values']['field_mr_home_leave']) && $form['field_mr_home_leave'][LANGUAGE_NONE]['#default_value'] == 1)) {
    $home_leave_toggle_access = true;
  } else {
    $home_leave_toggle_access = false;
  }
  dpm($form);
  $form['field_mr_fc_leg_home_leave']['#access'] = !$home_leave_toggle_access;
  $form_state['rebuild'] = TRUE;

}

function missions_hide_home_leave_toggle($form, $form_state) {
  return $form['field_mr_fc_leg_home_leave'];
}

When I click the checkbox on field_mr_home_leave I see the ajax spinner go, and I see a request to /system/ajax go out. Nothing but headers come back. The log shows Invalid form POST data. which is an error that means it couldn't find a cached form. Am I missing a step to make sure the form gets cached? Is #access the wrong thing to manipulate?

  • As a test, what happens if you set $home_leave_toggle_access to TRUE on initial page load? Does the sub-field appear? – Jaypan Feb 28 '17 at 9:18
  • Jaypan, I just watched your video on js in d7 and was going through the tutorial to see if I could identify a spot where I was doing something wrong. If I set $home_leave_toggle_access to true, indeed the field does show up. Once I change the value, the ajax spinner spins, and the IDE tells me the request finished without hitting a breakpoint. – UltraBob Mar 1 '17 at 0:20
  • If you execute the JS with a javascript console open, do you see a response? – Jaypan Mar 1 '17 at 1:04
  • I see the request to /system/ajax, with the request payload. I see a 200 response header, but no actual response coming back. – UltraBob Mar 1 '17 at 5:51
  • What file is your hook_form_alter() implementation in? – Jaypan Mar 1 '17 at 7:15
1

With thanks to @jaypan for trying to help me figure this out. I finally have a solution! Googling for the error message I was getting Invalid form POST data led me to lots of other issues people were having, but not the one I was having. My problem was very simple! I should not have been setting $form_state['rebuild'] to TRUE.

Doing that caused form_set_cache() to never be called, and therefore for drupal to quietly (with a watchdog entry) bail out of the ajax call and not return a response beyond the headers.

After ajax started working I was able to determine that the variable I was using to check the value of the field was wrong too, and I was not checking for cases such as the add another item buttons where some other portion of the form was being submitted. Now that I've fixed that, it all works. Here is the working code:

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function missions_form_mission_request_node_form_alter(&$form, &$form_state, $form_id)
{
  $form['field_mr_home_leave'][LANGUAGE_NONE]['#ajax'] = array(
    'callback' => 'missions_hide_home_leave_toggle',
    'wrapper' => 'itinerary-section',
  );
  $form['field_mr_fc_itineraries']['#prefix'] = '<div id="itinerary-section">';
  $form['field_mr_fc_itineraries']['#suffix'] = '</div>';
  if ((!empty($form_state['values']['field_mr_home_leave']) && $form_state['values']['field_mr_home_leave'][LANGUAGE_NONE][0]['value'] == 1) || (empty($form_state['values']['field_mr_home_leave']) && $form['field_mr_home_leave'][LANGUAGE_NONE]['#default_value'] == 1) || (!empty($form_state['complete form']['field_mr_home_leave']['und']['#value']) && $form_state['complete form']['field_mr_home_leave']['und']['#value'] == 1)) {
    $home_leave_toggle_access = true;
  } else {
    $home_leave_toggle_access = false;
  }
  foreach ($form['field_mr_fc_itineraries'][LANGUAGE_NONE] as $key => &$itinerary) {
    if (is_integer($key)) {
      foreach ($itinerary['field_mr_fc_legs'][LANGUAGE_NONE] as $key => &$leg) {
        if (is_integer($key)) {
          $leg['field_mr_fc_leg_home_leave']['#access'] = $home_leave_toggle_access;
        }
      }
    }
  }

}

function missions_hide_home_leave_toggle($form, $form_state) {
  return $form['field_mr_fc_itineraries'];
}

I hope this helps someone else not spend 3 days trying to debug this. :(

  • Nice, thanks for the follow up. I noticed your call to rebuild, and I'd never seen it in the form generation (always in a submit handler), but I had no idea it would cause the issue it did. That's a good gotcha to remember. – Jaypan Mar 6 '17 at 6:13

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.