delta => value) into * array(delta => field_name => value). * * During validation and submission, the field values are restored to * their normal positions. * * FIELDS AND WIDGETS THAT WORK IN REPEATING GROUPS * * All fields that allow the Content module to handle their multiple * values should work here. Fields that handle their own multiple values * will not be allowed into combo groups unless they implement * hook_combofield_allowed_widgets() to add their widgets to the allowed * widget list. * * All fields that allow the Content module to handle their multiple * values should work correctly when a combo group is changed back to * a normal group. Fields that handle their own multiple values * which may store different results in combo and standard groups should * implement hook_combofield_no_remove_widgets() to add their widgets * to the list of widgets that cannot be removed from combo groups. * * If a simple array of widgets is not sufficient to test whether this * action will work, modules can implement hook_combofield_allowed_in() * and hook_combofield_allowed_out() to intervene. * * Custom code and modules that add fields to groups outside of the UI * should use combofield_allowed_in() and combofield_allowed_out() * to test whether fields are allowed in or out of a combo group. * * TODO * * Add other methods of setting the way to display combo sub-groups on the * node, for now they are displayed in fieldsets. */ function combofield_help($section) { switch ($section) { case ('admin/help#combofield'): return t('The fields in a Standard group are independent of each other and each can have either single or multiple values. The fields in a Repeating group are treated as a repeating collection of single value fields.'); } } /** * Implementation of hook_theme(). */ function combofield_theme() { return array( 'combofield_multiple_values' => array( 'arguments' => array('element' => NULL), ), ); } /** * Implementation of hook_fieldgroup_types(). */ function combofield_fieldgroup_types() { return array('combo' => t('Repeating')); } /** * Implementation of hook_fieldgroup_default_settings(). */ function combofield_fieldgroup_default_settings($group_type) { if ($group_type == 'combo') { return array('combo' => array('multiple' => 1)); } } function combofield_multiple_values() { return array( '' => t('N/A'), 1 => t('Unlimited'), 0 => 1) + drupal_map_assoc(range(2, 10)); } /** * Implementation of hook_menu(). */ function combofield_menu() { $items = array(); // Callback for AHAH add more buttons. $items['combofield/js_add_more'] = array( 'page callback' => 'combofield_add_more_js', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_form_alter(). */ function combofield_form_alter(&$form, $form_state, $form_id) { // If this is a field edit form and the field is in a combo group, // override the multiple value settings. if ($form_id == 'content_field_edit_form' && isset($form['widget'])) { $content_type = content_types($form['type_name']['#value']); $groups = fieldgroup_groups($content_type['type']); $group_name = _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']); $group = isset($groups[$group_name]) ? $groups[$group_name] : array(); if (!empty($group) && $group['group_type'] == 'combo') { $form['field']['multiple']['#value'] = $group['settings']['combo']['multiple']; $form['field']['multiple']['#access'] = FALSE; } } elseif ($form_id == 'content_field_overview_form') { combofield_field_overview_form($form, $form_state); $form['#validate'][] = 'combofield_field_overview_form_validate'; } elseif ($form_id == 'fieldgroup_group_edit_form') { return combofield_group_edit_form($form, $form_state, $form_id); } } function combofield_field_overview_form(&$form, &$form_state) { $form['_add_new_group']['group_type'] = array( '#type' => 'select', '#description' => t('Type of group'), '#options' => fieldgroup_types(), '#default_value' => 'standard', ); } /** * Validation for creating/moving fields and groups on the * Manage Fields screen. */ function combofield_field_overview_form_validate($form, &$form_state) { $form_values = $form_state['values']; $type_name = $form['#type_name']; $fields = array(); $groups = array(); if ($group = $form_values['_add_new_group']) { $group['settings'] = field_group_default_settings($form_values['_add_new_group']['group_type']); $group = $form_values['_add_new_group']; $validation = fieldgroup_validate_name($group); // If there's something wrong with the new group, // don't bother doing any more validation, further // processing will be stopped by the fieldgroup module. if (!empty($validation['errors'])) { return; } $group['group_name'] = $validation['group_name']; $new_group_name = $group['group_name']; $groups[$new_group_name] = $group; } // See if we have fields moving into or out of a combo group. // Set any fields to use the new name here so they will get processed // correctly by the fieldgroup module when saved. foreach ($form_values as $key => $values) { if ($values['parent'] == '_add_new_group') { $values['parent'] = $new_group_name; $form_values[$key] = $values; } if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') { // Gather up info about all groups. $group_name = $form_values[$key]['group']['group_name']; $groups[] = $form_values[$key]['group']; } if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') { if ($values['prev_parent'] != $values['parent']) { // Gather up fields that have moved in or out of a group. $fields[$key] = $form_values[$key]['field']; } } } if (!empty($fields)) { foreach ($fields as $field_name => $field) { $new_group = $form_values[$field_name]['parent']; $old_group = $form_values[$field_name]['prev_parent']; if (!empty($new_group) && isset($groups[$new_group])) { $allowed_in = combofield_allowed_in($field, $groups[$new_group]); if (!$allowed_in['allowed']) { form_set_error($field_name, $allowed_in['message']); } elseif (!empty($allowed_in['message'])) { drupal_set_message($allowed_in['message']); } } elseif (!empty($old_group) && isset($groups[$old_group])) { $allowed_out = combofield_allowed_out($field, $groups[$old_group]); if (!$allowed_out['allowed']) { form_set_error($field_name, $allowed_out['message']); } elseif (!empty($allowed_out['message'])) { drupal_set_message($allowed_out['message']); } } } } } /** * Helper function for deciding if a field is * allowed into a combo group. */ function combofield_allowed_in($field, $group) { if ($group['group_type'] != 'combo') { return array('allowed' => TRUE, 'message' => ''); } // We can't allow fields with more multiple values than the group has // to be moved into it. $max_existing = content_max_delta($field['field_name']); $group_max = $group['settings']['combo']['multiple']; if ($max_existing > $group_max) { return array( 'allowed' => FALSE, 'message' => t('The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $group_max)) ); } // Fields that handle their own multiple values may not have the same values // in combo fields and normal fields. We don't know if they will work or not. // Adding a hook here where widgets that handle their own multiple values // that will work correctly in combo groups can allow their fields in. if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) { $allowed_widgets = array('optionwidgets_select', 'optionwidgets_buttons', 'optionwidgets_onoff'); $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('combofield_allowed_widgets')); if (!in_array($field['widget']['type'], $allowed_widgets)) { return array( 'allowed' => FALSE, 'message' => t('The field %field handles multiple values differently than the Content module. Making this change could result in the loss of data.', array('%field' => $field['widget']['label'])) ); } } // Allow other modules to intervene. // Any failure will prevent this action. foreach (module_implements('combofield_allowed_in') as $module) { $function = $module .'_combofield_allowed_in'; $result = $function($field, $group); if ($result['allowed'] === FALSE) { return array('allowed' => FALSE, 'message' => $result['message']); } } $message = t('You are moving field %field into a Repeating group.', array('%field' => $field['widget']['label'])); return array('allowed' => TRUE, 'message' => $message); } /** * Helper function for deciding if a field is * allowed out of a combo group. */ function combofield_allowed_out($field, $group) { if ($group['group_type'] != 'combo') { return array('allowed' => TRUE, 'message' => ''); } // Optionwidgets do not behave the same in a combo field as out of it. // In a combo group the same option can be selected multiple times, // but that is not possible in a normal group. // Adding a hook here where widgets that handle their own multiple values // can indicate their fields should not be removed from combo groups. $max_existing = content_max_delta($field['field_name']); $no_remove_widgets = array('optionwidgets_select', 'optionwidgets_buttons', 'optionwidgets_onoff'); $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('combofield_no_remove_widgets')); if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) { return array( 'allowed' => FALSE, 'message' => t('The field %field already has data created and uses a widget that stores data differently in a Standard group than in a Repeating group. Making this change could result in the loss of data.', array('%field' => $field['widget']['label'])) ); } // Allow other modules to intervene. // Any failure will prevent this action. foreach (module_implements('combofield_allowed_out') as $module) { $function = $module .'_combofield_allowed_out'; $result = $function($field, $group); if ($result['allowed'] === FALSE) { return array('allowed' => FALSE, 'message' => $result['message']); } } $message = t('You are moving field %field out of a Repeating group.', array('%field' => $field['widget']['label'])); return array('allowed' => TRUE, 'message' => $message); } /** * Alter the Fieldgroup edit form * to add Combo group settings. */ function combofield_group_edit_form(&$form, &$form_state) { $type_name = $form['#content_type']['type']; $group_name = $form['group_name']['#default_value']; $content_type = content_types($type_name); $groups = fieldgroup_groups($content_type['type']); $group = $groups[$group_name]; if ($group['group_type'] != 'combo') { return; } module_load_include('inc', 'content', 'includes/content.admin'); module_load_include('inc', 'content', 'includes/content.crud'); $form['group_type'] = array( '#type' => 'hidden', '#value' => $group['group_type'], ); $form['prev_group_type'] = array( '#type' => 'hidden', '#value' => $group['group_type'], ); $form['settings']['combo'] = array( '#type' => 'fieldset', '#title' => t('Other settings'), '#collapsed' => FALSE, '#collapsible' => TRUE, ); $description = t('Number of times to repeat the collection of Repeating group fields.') . ' '; $description .= t("'Unlimited' will provide an 'Add more' button so the users can add repeat it as many times as they like.") . ' '; $description .= t('All fields in this group will automatically be set to allow this number of values.'); $form['settings']['combo']['multiple'] = array( '#tree' => TRUE, '#type' => 'select', '#title' => t('Number of repeats'), '#options' => combofield_multiple_values(), '#default_value' => isset($group['settings']['combo']['multiple']) ? $group['settings']['combo']['multiple'] : '', '#description' => $description, ); $form['#validate'][] = 'combofield_group_edit_form_validate'; return $form; } /** * Validate the Fieldgroup edit form. */ function combofield_group_edit_form_validate($form, &$form_state) { $form_values = $form_state['values']; $group_type = $form_values['group_type']; $prev_group_type = $form_values['prev_group_type']; if ($group_type != 'combo' && $prev_group_type != 'combo') { return; } $content_type = $form['#content_type']; $groups = fieldgroup_groups($content_type['type']); $group = $groups[$form_values['group_name']]; foreach ($group['fields'] as $field_name => $data) { if ($group_type == 'combo') { // Make sure we don't set the multiple values to a number that // would result in lost data. $max_existing = content_max_delta($field_name); if ($form_values['settings']['combo']['multiple'] != 1 && $max_existing > $form_values['settings']['combo']['multiple']) { form_set_error('settings][combo][multiple', t('The field %field in this group already has %multiple values in the database. To prevent the loss of data you cannot set the number of Repeating group values to less than this.', array('%field' => $data['label'], '%multiple' => $max_existing))); } } // Switching to a Standard group, are all the fields safe? if ($prev_group_type == 'combo' && $group_type != 'combo') { $field = $content_type['fields'][$field_name]; $allowed_out = combofield_allowed_out($field, $group); if (!$allowed_out['allowed']) { form_set_error('group_type', $allowed_out['message']); } } // Switching from a Standard group, are all the fields safe? if ($prev_group_type != 'combo') { $field = $content_type['fields'][$field_name]; $allowed_in = combofield_allowed_in($field, $group); if (!$allowed_in['allowed']) { form_set_error('group_type', $allowed_in['message']); } } } } /** * Implementation of hook_fieldgroup_save_group(). * * Additional processing needed when saving a group. */ function combofield_fieldgroup_save_group($group) { if ($group['group_type'] != 'combo') { return; } // If the group was added it is missing the combo settings // because fieldgroup sets them to default settings. if (isset($_SESSION['combofield']['_add_new_group'])) { $group['settings']['combo'] = $_SESSION['combofield']['_add_new_group']['settings']['combo']; unset($_SESSION['combofield']['_add_new_group']); } $types = content_types(); $multiple = $group['settings']['combo']['multiple']; $result = db_query("SELECT field_name, type_name FROM {". fieldgroup_fields_tablename() ."} WHERE group_name = '%s'", $group['group_name']); while ($row = db_fetch_array($result)) { $field = $types[$row['type_name']]['fields'][$row['field_name']]; $field['multiple'] = $multiple; content_field_instance_update($field); } } /** * Implementation of hook_fieldgroup_form(). * * Align the delta values of each field in the combo group. * * Swap the field name and delta for each combo group so we can * d-n-d each collection of fields as a single delta item. */ function combofield_fieldgroup_form(&$form, &$form_state, $form_id, $group) { if ($group['group_type'] != 'combo') { return; } $node = $form['#node']; $fields = $group['fields']; $content_fields = content_fields(); $group_name = $group['group_name']; // Use the first field in the group to get the item counts. $first_field_name = array_shift(array_keys($group['fields'])); $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array(); $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array(); $group['multiple'] = $group['settings']['combo']['multiple']; switch ($group['multiple']) { case 0: $max = 0; break; case 1: $filled_items = content_set_empty($first_field, $first_field_items); $current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : count($first_field_items); // We always want at least one empty icon for the user to fill in. $max = ($current_item_count > count($filled_items)) ? $current_item_count - 1 : $current_item_count; break; default: $max = $group['multiple'] - 1; break; } $form[$group_name]['#theme'] = 'combofield_multiple_values'; $form[$group_name]['#multiple'] = !empty($max); $form[$group_name]['#group_name'] = $group_name; $form[$group_name]['#group_label'] = $group['label']; $form[$group_name]['#element_validate'] = array('combofield_combo_form_validate'); $form[$group_name]['#tree'] = TRUE; for ($delta = 0; $delta <= $max; $delta++) { combofield_group_form($form, $form_state, $group, $delta); } // Reset the form '#node' back to its original value. $form['#node'] = $node; if ($add_more = combofield_add_more($form, $form_state, $group)) { $form[$group_name] += $add_more; } //dsm($form); } /** * Create a new delta value for the group. * * Called in form_alter and by AHAH add more. */ function combofield_group_form(&$form, &$form_state, $group, $delta) { module_load_include('inc', 'content', 'includes/content.node_form'); if ($group['group_type'] != 'combo') { return; } $node = $form['#node']; $fields = $group['fields']; $content_fields = content_fields(); $group_name = $group['group_name']; // Use the first field in the group to get the item counts. $first_field_name = array_shift(array_keys($group['fields'])); $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array(); $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array(); $group['multiple'] = $group['settings']['combo']['multiple']; switch ($group['multiple']) { case 0: $max = 0; break; case 1: $filled_items = content_set_empty($first_field, $first_field_items); $current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : count($first_field_items); // We always want at least one empty icon for the user to fill in. $max = ($current_item_count > count($filled_items)) ? $current_item_count - 1 : $current_item_count; break; default: $max = $group['multiple'] - 1; break; } $form[$group_name]['#theme'] = 'combofield_multiple_values'; $form[$group_name]['#multiple'] = !empty($max); $form[$group_name]['#group_name'] = $group_name; $form[$group_name]['#group_label'] = $group['label']; $form[$group_name]['#element_validate'] = array('combofield_combo_form_validate'); $form[$group_name]['#tree'] = TRUE; for ($delta = 0; $delta < $max; $delta++) { foreach ($fields as $field_name => $group_field) { if (empty($form[$group_name][$delta])) { $form[$group_name] += array($delta => array($field_name => array())); } else { $form[$group_name][$delta][$field_name] = array(); } $form[$group_name][$delta]['_weight'] = array( '#type' => 'weight', '#delta' => $group['multiple'], // this 'delta' is the 'weight' element's property '#default_value' => $delta, '#weight' => 100, ); $field = $content_fields[$field_name]; // Make each field into a pseudo single value field // with the right delta value. $field['multiple'] = FALSE; $form['#field_info'][$field_name] = $field; $node_copy = drupal_clone($node); // Set the form '#node' to the delta value we want so the Content // module will feed the right $items to the field module in // content_field_form(). // There may be missing delta values for fields that were // never created, so check first. if (!empty($node->$field_name) && count($node->$field_name) >= $delta + 1) { $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]); } else { $node_copy->$field_name = array($delta => NULL); } $form['#node'] = $node_copy; $field_form = content_field_form($form, $form_state, $field, $delta); // Place the new $field_form into the $delta position in the group form. if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0; $form[$group_name][$delta][$field_name] = $field_form[$field_name][$value]; } else { $form[$group_name][$delta][$field_name] = $field_form[$field_name]; } $form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight']; // Add in our validation step, and make sure it preceeds other // processing so we can massage the element back to the normal value. if (isset($form[$group_name][$delta][$field_name]['#element_validate'])) { array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'combofield_combo_item_validate'); } else { $form[$group_name][$delta][$field_name]['#element_validate'] = array('combofield_combo_item_validate'); } } } // Reset the form '#node' back to its original value. $form['#node'] = $node; // Unset the original group field values now that we've reset them. foreach ($fields as $field_name => $field) { unset($form[$group_name][$field_name]); } return $form; } /** * Swap transposed field/delta values back to their normal positions. */ function combofield_combo_item_validate($element, &$form_state) { //dsm($form_state); $field_name = array_pop($element['#parents']); $delta = array_pop($element['#parents']); $group = array_pop($element['#parents']); //dsm($group.'>'.$field_name.'>'.$delta.' is '.$element['#value']['value']); // Examine the post values to see what order the new fields // belong in. This is very hackish and should be done better, // but it works for now. $new = array(); foreach ($element['#post'][$group] as $count => $value) { $new[$value['_weight']] = $count; } ksort($new); $count = 0; foreach ($new as $value) { if ($delta == $value) { $delta = $count; break; } $count++; } // We figured out what the new order for the fields is, // so set these values. array_push($element['#parents'], $field_name); array_push($element['#parents'], $delta); form_set_value($element, $element['#value'], $form_state); } /** * Implementation of hook_fieldgroup_view(). */ function combofield_fieldgroup_view(&$node, &$element, $group, $context) { if ($group['group_type'] != 'combo') { return; } // TODO Add a method in to set the combo formats. // Just using fieldset for now. $combo_format = isset($group['settings']['combo']['display'][$context]['format']) ? $group['settings']['combo']['display'][$context]['format'] : 'fieldset'; $group_name = $group['group_name']; $node_copy = drupal_clone($node); $max = $group['settings']['combo']['multiple']; $count = 0; foreach ($group['fields'] as $field_name => $field) { $count = max($count, count($node->$field_name)); } $group['multiple'] = $group['settings']['combo']['multiple']; switch ($group['multiple']) { case 0: $max = 0; break; case 1: $max = $count; break; default: $max = $group['multiple']; break; } for ($delta = 0; $delta < $max; $delta++) { $element[$delta] = array('#weight' => $delta); foreach ($group['fields'] as $field_name => $field) { // Create a pseudo node that only has the value we want // in this group and pass it to the formatter. if (isset($node->content[$field_name])) { $node_copy->content[$field_name]['field']['items'] = array( $delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL, ); $element[$delta][$field_name] = $node_copy->content[$field_name]; $element[$delta][$field_name]['#delta'] = $delta; } } // TODO Add more ways to format the subgroup displays. switch ($combo_format) { case 'table': case 'simple': case 'fieldset': case 'default': $element[$delta]['#type'] = 'fieldset'; break; } } foreach ($group['fields'] as $field_name => $field) { if (isset($element[$field_name])) { unset($element[$field_name]); } } } /** * Theme an individual form element. * * Combine multiple values into a table with drag-n-drop reordering. * * TODO * With a little tweaking, the original theme, content_multiple_values, * could be made to work for the fieldgroup, too, and this could be * eliminated. */ function theme_combofield_multiple_values($element) { $output = ''; if ($element['#multiple'] >= 1) { $table_id = $element['#group_name'] .'_values'; $order_class = $element['#group_name'] .'-delta-order'; $header = array( array( 'data' => '', 'colspan' => 2 ), t('Order'), ); $rows = array(); foreach (element_children($element) as $key) { if ($key !== $element['#group_name'] .'_add_more') { $element[$key]['_weight']['#attributes']['class'] = $order_class; $delta_element = drupal_render($element[$key]['_weight']); $cells = array( array('data' => '', 'class' => 'content-multiple-drag'), drupal_render($element[$key]), array('data' => $delta_element, 'class' => 'delta-order'), ); $rows[] = array( 'data' => $cells, 'class' => 'draggable', ); } } $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table')); $output .= $element['#description'] ? '
'. $element['#description'] .'
' : ''; $output .= drupal_render($element[$element['#group_name'] .'_add_more']); drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); } else { foreach (element_children($element) as $key) { $output .= drupal_render($element[$key]); } } return $output; } function combofield_add_more(&$form, &$form_state, $group) { // Add AHAH add more button, if not working with a programmed form. $multiple = $group['settings']['combo']['multiple']; $form_element = array(); if ($multiple != 1 || !empty($form['#programmed'])) { return $form_element; } else { // Make sure the form is cached so ahah can work. $form['#cache'] = TRUE; $content_type = content_types($group['type_name']); $group_name = $group['group_name']; $group_name_css = str_replace('_', '-', $group_name); $form_element[$group_name .'_add_more'] = array( '#type' => 'submit', '#name' => $group_name .'_add_more', '#value' => t('Add more values'), '#weight' => $multiple + 1, // Submit callback for disabled JavaScript. drupal_get_form() might get // the form from the cache, so we can't rely on content_form_alter() // including this file. Therefore, call a proxy function to do this. '#submit' => array('combofield_add_more_submit_proxy'), '#ahah' => array( 'path' => 'combofield/js_add_more/'. $content_type['url_str'] .'/'. $group_name, 'wrapper' => $group_name_css .'-items', 'method' => 'replace', 'effect' => 'fade', ), // When JS is disabled, the content_add_more_submit handler will find // the relevant field using these entries. '#group_name' => $group_name, '#type_name' => $group['type_name'], ); // Add wrappers for the group and 'more' button. // TODO: could be simplified ? $form_element['#prefix'] = '
'; $form_element[$group_name .'_add_more']['#prefix'] = '
'; $form_element[$group_name .'_add_more']['#suffix'] = '
'; } return $form_element; } /** * Submit handler to add more choices to a content form. This handler is used when * JavaScript is not available. It makes changes to the form state and the * entire form is rebuilt during the page reload. */ function combofield_add_more_submit($form, &$form_state) { // Set the form to rebuild and run submit handlers. node_form_submit_build_node($form, $form_state); $group_name = $form_state['clicked_button']['#group_name']; $type_name = $form_state['clicked_button']['#type_name']; // Make the changes we want to the form state. if ($form_state['values'][$group_name][$group_name .'_add_more']) { $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]); } } /** * Menu callback for AHAH addition of new empty widgets. * * Adapted from content_add_more_js to work with groups instead of fields. */ function combofield_add_more_js($type_name_url, $group_name) { $type = content_types($type_name_url); $groups = fieldgroup_groups($type['type']); $group = $groups[$group_name]; $group['multiple'] = $group['settings']['combo']['multiple']; if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) { // Invalid request. drupal_json(array('data' => '')); exit; } // Retrieve the cached form. $form_state = array('submitted' => FALSE); $form_build_id = $_POST['form_build_id']; $form = form_get_cache($form_build_id, $form_state); if (!$form) { // Invalid form_build_id. drupal_json(array('data' => '')); exit; } // We don't simply return a new empty widget to append to existing ones, because // - ahah.js won't simply let us add a new row to a table // - attaching the 'draggable' behavior won't be easy // So we resort to rebuilding the whole table of widgets including the existing ones, // which makes us jump through a few hoops. // The form that we get from the cache is unbuilt. We need to build it so that // _value callbacks can be executed and $form_state['values'] populated. // We only want to affect $form_state['values'], not the $form itself // (built forms aren't supposed to enter the cache) nor the rest of $form_data, // so we use copies of $form and $form_data. $form_copy = $form; $form_state_copy = $form_state; $form_copy['#post'] = array(); form_builder($_POST['form_id'], $form_copy, $form_state_copy); // Just grab the data we need. $form_state['values'] = $form_state_copy['values']; // Reset cached ids, so that they don't affect the actual form we output. form_clean_id(NULL, TRUE); // Sort the $form_state['values'] we just built *and* the incoming $_POST data // according to d-n-d reordering. unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']); foreach ($_POST[$group_name] as $delta => $item) { $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight']; } $form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]); $_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]); // Build our new form element for the whole group, asking for one more element. $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1); $delta = max(array_keys($_POST[$group_name])) + 1; combofield_group_form($form, $form_state, $group, $delta); // Save the new definition of the form. $form_state['values'] = array(); form_set_cache($form_build_id, $form, $form_state); // Build the new form against the incoming $_POST values so that we can // render the new element. $_POST[$group_name][$delta]['_weight'] = $delta; $form_state = array('submitted' => FALSE); $form += array( '#post' => $_POST, '#programmed' => FALSE, ); $form = form_builder($_POST['form_id'], $form, $form_state); // Render the new output. $group_form = $form[$group_name]; // We add a div around the new content to receive the ahah effect. $group_form[$delta]['#prefix'] = '
'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : ''); $group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'
'; // If a newly inserted widget contains AHAH behaviors, they normally won't // work because AHAH doesn't know about those - it just attaches to the exact // form elements that were initially specified in the Drupal.settings object. // The new ones didn't exist then, so we need to update Drupal.settings // by ourselves in order to let AHAH know about those new form elements. $javascript = drupal_add_js(NULL, NULL); $output_js = isset($javascript['setting']) ? '' : ''; $output = theme('status_messages') . drupal_render($group_form) . $output_js; drupal_json(array('status' => TRUE, 'data' => $output)); exit; }