Drupal: show/hide location fields with module Conditional Fields


November 2014.
Image

Situation


You're using module Conditional Fields to show/hide a few fields depending on another field value. You notice that everything works except your location fields, which don't respond to any trigger.

Example: Let's say you have the following profile2:









FieldTypeWidgetMisc
field_champ1List(text)Select listValues: "Valeur 1", "Valeur 2" or "Valeur 3"
field_champ2LocationLocation field
field_champ3TextText


You set up the following conditional interactions:

Then, you open an edit form of this profile, and notice that only the second interaction works.

Finding what cause the problem


Let's dig into the code.

In modules/conditional_fields/conditional_fields.module, function conditional_fields_element_after_build(...) seems to be the one that attach the dependencies from the form.

if (isset($dependencies['dependents'][$field['#field_name']])) {
foreach ($dependencies['dependents'][$field['#field_name']] as $id => $dependency) {
if (!isset($form['#conditional_fields'][$field['#field_name']]['dependees'][$id])) {
conditional_fields_attach_dependency($form, array('#field_name' => $dependency['dependee']), $field, $dependency['options'], $id);
}
}
}

// Attach dependee.
// TODO: collect information about every element of the dependee widget, not
// just the first encountered. This bottom-up approach would allow us to
// define per-element sets of dependency values.
if (isset($dependencies['dependees'][$field['#field_name']])) {
foreach ($dependencies['dependees'][$field['#field_name']] as $id => $dependency) {
if (!isset($form['#conditional_fields'][$field['#field_name']]['dependents'][$id])) {
conditional_fields_attach_dependency($form, $field, array('#field_name' => $dependency['dependent']), $dependency['options'], $id);
}
}
}

Debugging shows that our location field does not validate the preceding condition:

// Some fields do not have entity type and bundle properties. In this case we
// try to use the properties from the form. This is not an optimal solution,
// since in case of fields in entities within entities they might not correspond,
// and their dependencies will not be loaded.
if (isset($field['#entity_type'], $field['#bundle'])) {
$entity_type = $field['#entity_type'];
$bundle = $field['#bundle'];
}
elseif (isset($form['#entity_type'], $form['#bundle'])) {
$entity_type = $form['#entity_type'];
$bundle = $form['#bundle'];
}
else {
return $element;
}

This means that our field was not build with a reference to its profile2 entity.

These field form entries are built by field_multiple_value_form(...) in modules/field/field.form.inc:

$element = array(
'#entity_type' => $instance['entity_type'],
'#entity' => $form['#entity'],
'#bundle' => $instance['bundle'],
'#field_name' => $field_name,
'#language' => $langcode,
'#field_parents' => $parents,
'#columns' => array_keys($field['columns']),
// For multiple fields, title and description are handled by the wrapping table.
'#title' => $multiple ? '' : $title,
'#description' => $multiple ? '' : $description,
// Only the first widget should be required.
'#required' => $delta == 0 && $instance['required'],
'#delta' => $delta,
'#weight' => $delta,
);
if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) {
...

Hum, once build, it's passed into another function. Let's find it.

It's location_cck_field_widget_form(...) in modules/location/contrib/location_cck/location_cck.module:

$element = array(
'#type' => 'location_element',
'#has_garbage_value' => TRUE,
'#value' => '',
'#title' => t($instance['label']),
'#description' => t($instance['description']),
'#required' => $instance['required'],
'#location_settings' => $settings,
'#default_value' => $location,
);

return $element;

Since the returned element doesn't care about what was passed to it, the entity elements are indeed missing.

Working around the problem


One solution would be to patch module location.

Another one is to alter the form and add the missing entries manually.

function my_module_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
if ($entity_type == 'profile2' && $entity->type == 'profile_debug') {
$form['field_champ2']['#entity_type'] = $entity_type;
$form['field_champ2']['#bundle'] = $entity->type;

if (isset($form['field_champ2'][LANGUAGE_NONE])) {
$form['field_champ2'][LANGUAGE_NONE]['#entity_type'] = $entity_type;
$form['field_champ2'][LANGUAGE_NONE]['#bundle'] = $entity->type;
}
}
}

That's it: location field field_champ2 now behaves correctly.