Skip to content

Instantly share code, notes, and snippets.

@yann-yinn
Last active June 14, 2018 15:12
Show Gist options
  • Save yann-yinn/2850342 to your computer and use it in GitHub Desktop.
Save yann-yinn/2850342 to your computer and use it in GitHub Desktop.
Creat multiple field (add more buttons) for form api elements
<?php
/**
* @file
* Re-usable stuff.
*
* @FIXME multifield function seems not to work in included several times in the same page.
*/
/**
* Implements hook_theme()
*/
function palpix_toolbox_theme($existing, $type, $theme, $path) {
return array(
// to add drag and drop on our "multifield".
// @see palpix_toolbox_fapi_multifield()
'palpix_toolbox_fapi_multifield_dragandrop' => array(
'render element' => 'element',
),
);
}
/*================================
PALPIX MULTIFIED
generate a multiple values form api element
================================*/
/**
* Transform a form api element (like a textfield) to a "multified", with optionnaly
* "add more" and "remove one" button like field api does with fields with unlimited values.
*
* IMPORTANT ! please note that you MUST put $form and $form state in signature of your form function declaration
* or it *won't work*, as those variables are required when rebuilding form in ajax callback. ! example :
*
* @code
* function my_form($form, $form_state) {
* // define your form here
* }
* @endcode
*
* Example code to transform a simple textfield to a multiple textfield :
*
* @code
* $form['header']['myfield'] = array(
* '#type' => 'textfield',
* '#title' => 'Kant',
* '#autocomplete_path' => 'user/autocomplete',
* '#description' => "Add some users.",
* // from here, properties are specific to our function palpix_toolbox_multifield
* '#number' => 4, // how many fields we want to display by default.
* '#add_more' => TRUE, // set to false if you don't want "add more" button
* '#collapsible' => TRUE, // for the automatically generated fieldset
* '#collapsed' => FALSE, // idem
* '#default_values' => array('test', 'deux'), // default value, one entry per field.
* );
*
* // magic happen here :
* palpix_toolbox_multifield(array('header', 'myfield'), $form, $form_state);
* @endcode
*
* @parents
* position of field. @see drupal_array_get_nested_value() comments for more details.
* @param $form
* full form $form array
* @param $form_state
* full form $form_state
*/
function palpix_toolbox_multifield($parents, &$form, &$form_state) {
// get the field we want to transform to a multifield.
$field = drupal_array_get_nested_value($form, $parents);
// first parent is the key of our field array.
$element_name = end($parents);
// create a id for the fieldset. Will be used as a css id and a form api for fieldset.
$fieldset_id = "fieldset_multifield_$element_name";
// keep in form_state parents for this element, so that we know which field to replace in html / ajax
// We use $element_name as a key in array to store information per field (this is necessary if there is
// several instance of multifield in the same page.). In submit function, we'll be able to retrieve
// information looking at $form_state['triggering_element'] which contains $element_name too.
$form_state['palpix_fapi_multifield'][$element_name]['parents'] = $parents;
// create a fieldset to put our field in.
$form_chunk[$fieldset_id] = array(
'#type' => 'fieldset',
'#prefix' => '<div id="' . $fieldset_id . '">',
'#suffix' => '</div>',
);
// move some properties of our field to our fieldset : $property_name => $default_value.
$additional_properties = array(
'#title' => '',
'#description' => '',
'#collapsible' => TRUE,
'#collapsed' => FALSE
);
foreach ($additional_properties as $property => $default_value) {
// if property is defined, move it from field to fieldset
if (isset($field[$property])) {
$form_chunk[$fieldset_id][$property] = $field[$property];
unset($field[$property]);
}
// if not, set default value for this property.
else {
$form_chunk[$fieldset_id][$property] = $default_value;
}
}
// 'fields_number' contains the number of fields to display to the user for this field instance.
// If empty, we display only number of field specified in $field[#number]; except if previous saved values are superior to default number of fields.
if (empty($form_state['palpix_fapi_multifield'][$element_name]['fields_number'])) {
$fields_number = count($field['#default_values']) > $field['#number'] ? count($field['#default_values']) : $field['#number'];
$form_state['palpix_fapi_multifield'][$element_name]['fields_number'] = $fields_number;
}
// now insert our $field inside the $fieldset. We print as many fields as needed.
// 'fields_number" is incremented or decremented when clicking on "add more" or "remove one" button.
$field_name = "multifield_$element_name";
for ($i = 0; $i < $form_state['palpix_fapi_multifield'][$element_name]['fields_number']; $i++) {
$form_chunk[$fieldset_id][$field_name][$i]['value'] = $field;
$form_chunk[$fieldset_id][$field_name][$i]['value']['#default_value'] = isset($field['#default_values'][$i]) ? $field['#default_values'][$i] : '';
// we need to add weight for drag and drop.
$form_chunk[$fieldset_id][$field_name][$i]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $i,
'#title-display' => 'invisible',
// a class is required by drag and drop.
'#attributes' => array('class' => array('palpix-multifield-item-weight')),
);
}
// set tree to TRUE allow us to put all variables of our field in an handy array in form_state['values'].
$form_chunk[$fieldset_id][$field_name]['#tree'] = TRUE;
// this theme function add drag and drop goodness to our field.
$form_chunk[$fieldset_id][$field_name]['#theme'] = 'palpix_toolbox_fapi_multifield_dragandrop';
// add all ajax stuff to add / remove dynamically fields.
if ($field['#add_more']) {
// add more button
$form_chunk[$fieldset_id]["palpix_fapi_multifield_add_more"] = array(
// #element_name is a fake property, so that we easily retrieve element_name in submit callback.
'#element_name' => $element_name,
'#type' => 'submit',
'#value' => t('Add one more'),
'#name' =>" palpix_fapi_multifield_add_more_$element_name",
'#submit' => array("palpix_fapi_multifield_submit_add_more"),
// this property avoid title complaining about being empty whan adding more values
'#limit_validation_errors' => array(array($fieldset_id, $element_name)),
'#ajax' => array(
'callback' => 'palpix_fapi_multifield_ajax',
'wrapper' => $fieldset_id,
),
);
// remove one button
if ($form_state['palpix_fapi_multifield'][$element_name]['fields_number'] > 1) {
$form_chunk[$fieldset_id]["palpix_fapi_multifield_remove_one"] = array(
// #element_name is a fake property, so that we easily retrieve element_name in submit callback.
'#element_name' => $element_name,
'#type' => 'submit',
'#value' => t('Remove one'),
'#name' =>" palpix_fapi_multifield_remove_one_$element_name",
'#submit' => array('palpix_fapi_multifield_submit_remove_one'),
// only validate datas from our fieldset, our all the form will be validated when clicking "add more".
// This cause fields to complains because they're still blanck at this moment.
'#limit_validation_errors' => array(array($fieldset_id, $element_name)),
'#ajax' => array(
//'path' => 'palpix-multifield-ajax-callback',
'callback' => 'palpix_fapi_multifield_ajax',
'wrapper' => $fieldset_id,
),
);
}
}
// add our fieldset to form. Happy end.
drupal_array_set_nested_value($form, $parents, $form_chunk);
}
/**
* Submit handler for the "add-one-more" button.
*
* Increments the max counter and causes a rebuild.
*/
function palpix_fapi_multifield_submit_add_more($form, &$form_state, $arg) {
// we need to retrieve $element_name to get the right value from $form_state value.
$element_name = $form_state['triggering_element']['#element_name'];
$form_state['palpix_fapi_multifield'][$element_name]['fields_number']++;
$form_state['rebuild'] = TRUE;
}
/**
* Submit handler for the "remove one" button.
*
* Decrements the max counter and causes a form rebuild.
*/
function palpix_fapi_multifield_submit_remove_one($form, &$form_state) {
$element_name = $form_state['triggering_element']['#element_name'];
if ($form_state['palpix_fapi_multifield'][$element_name]['fields_number']) {
$form_state['palpix_fapi_multifield'][$element_name]['fields_number']--;
}
$form_state['rebuild'] = TRUE;
}
/**
* Callback for both ajax-enabled buttons.
*
* Return portion of the form to rebuild, with added or removed fields.
*/
function palpix_fapi_multifield_ajax($form, $form_state) {
// we need to retrieve $element_name to get the right value from $form_state value.
$element_name = $form_state['triggering_element']['#element_name'];
$parents = $form_state['palpix_fapi_multifield'][$element_name]['parents'];
$output = drupal_array_get_nested_value($form, $parents);
return $output;
}
/**
* Theme for make our multifield draggable.
*/
function theme_palpix_toolbox_fapi_multifield_dragandrop($variables) {
$element = $variables['element'];
$output = '';
$rows = array();
foreach (element_children($element) as $id) {
$rows[$id]['data'][] = drupal_render($element[$id]['value']);
$rows[$id]['data'][] = drupal_render($element[$id]['weight']);
$rows[$id]['class'][] = 'draggable';
}
// We now define the table header values. Ensure that the 'header' count
// matches the final column count for your table.
$header = array(t('title'), t('Weight'));
// set a uniq id for this table.
$table_id = 'palpix-multifield-table_' . $element['#id'];
// We can now render our tabledrag table for output.
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => $table_id)));
// just in case...
$output .= drupal_render_children($element);
// include js.
drupal_add_tabledrag($table_id, 'order', 'sibling', 'palpix-multifield-item-weight');
return $output;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment