Skip to content

Instantly share code, notes, and snippets.

@AlexSkrypnyk
Last active April 17, 2024 08:37
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save AlexSkrypnyk/94bac012e7d679051bc0 to your computer and use it in GitHub Desktop.
Save AlexSkrypnyk/94bac012e7d679051bc0 to your computer and use it in GitHub Desktop.
Drupal 'add more' and 'remove single' AJAX buttons on multi value custom field using FormAPI
input.form-submit.button-small {
padding: 4px 8px;
font-weight: bold;
}
.container-inline input.form-submit.button-small + .ajax-progress.ajax-progress-throbber .throbber {
position: absolute;
left: 19px;
margin-top: 7px;
}
.container-inline input.form-submit.button-small + .ajax-progress.ajax-progress-throbber .message {
display: none;
}
function mymodule_ajax_example_add_more($form, &$form_state) {
$form['field_container'] = [
'#type' => 'container',
'#weight' => 80,
'#tree' => TRUE,
// Set up the wrapper so that AJAX will be able to replace the fieldset.
'#prefix' => '<div id="js-ajax-elements-wrapper">',
'#suffix' => '</div>',
];
$form_state['field_deltas'] = isset($form_state['field_deltas']) ? $form_state['field_deltas'] : range(0, 3);
$field_count = $form_state['field_deltas'];
foreach ($field_count as $delta) {
$form['field_container'][$delta] = [
'#type' => 'container',
'#attributes' => [
'class' => ['container-inline'],
],
'#tree' => TRUE,
];
$form['field_container'][$delta]['field1'] = [
'#type' => 'textfield',
'#title' => t('Field 1 - ' . $delta),
'#size' => 10,
];
$form['field_container'][$delta]['field2'] = [
'#type' => 'textfield',
'#title' => t('Field 2 - ' . $delta),
'#size' => 10,
];
$form['field_container'][$delta]['remove_name'] = [
'#type' => 'submit',
'#value' => t('-'),
'#submit' => ['mymodule_ajax_example_add_more_remove'],
// See the examples in ajax_example.module for more details on the
// properties of #ajax.
'#ajax' => [
'callback' => 'mymodule_ajax_example_add_more_remove_callback',
'wrapper' => 'js-ajax-elements-wrapper',
],
'#weight' => -50,
'#attributes' => [
'class' => ['button-small'],
],
'#name' => 'remove_name_' . $delta,
];
}
$form['field_container']['add_name'] = [
'#type' => 'submit',
'#value' => t('Add one more'),
'#submit' => ['mymodule_ajax_example_add_more_add_one'],
// See the examples in ajax_example.module for more details on the
// properties of #ajax.
'#ajax' => [
'callback' => 'mymodule_ajax_example_add_more_add_one_callback',
'wrapper' => 'js-ajax-elements-wrapper',
],
'#weight' => 100,
];
$form['other_field'] = [
'#type' => 'textfield',
'#title' => t('Other field'),
];
return $form;
}
function mymodule_ajax_example_add_more_remove($form, &$form_state) {
$delta_remove = $form_state['triggering_element']['#parents'][1];
$k = array_search($delta_remove, $form_state['field_deltas']);
unset($form_state['field_deltas'][$k]);
$form_state['rebuild'] = TRUE;
drupal_get_messages();
}
function mymodule_ajax_example_add_more_remove_callback($form, &$form_state) {
return $form['field_container'];
}
function mymodule_ajax_example_add_more_add_one($form, &$form_state) {
$form_state['field_deltas'][] = count($form_state['field_deltas']) > 0 ? max($form_state['field_deltas']) + 1 : 0;
$form_state['rebuild'] = TRUE;
drupal_get_messages();
}
function mymodule_ajax_example_add_more_add_one_callback($form, $form_state) {
return $form['field_container'];
}
@AlexSkrypnyk
Copy link
Author

drupal_add_button

@jphat
Copy link

jphat commented Feb 26, 2016

hey alex, thanks for sharing the code.
any way of implementing a code that would disable the add button after a certain number?

@elalemanyo
Copy link

Hi,
I am building something similar, but I am having some issue with the form errors. They are being shown just if I don't add any Item.
Do you get it to work also with form errors?

Thanks!

@zvamature
Copy link

hie thanks for the code. But how do you insert data saved in the form into custom table. If you can provide the ajax way and the normal drupal way i will be very much grateful

@acebytes
Copy link

acebytes commented Aug 5, 2016

HOLY THANK YOU - this code rocks!

@alexmtch
Copy link

alexmtch commented Aug 11, 2016

How you deal with required fields ? it block adding and removing fields if they are required. (i used this property in input type submit to fix this : '#limit_validation_errors' => array()).

@hiteshkoli
Copy link

Thanks Alex! Nice clean start up code.

@gabrielpaoli
Copy link

Genius !!

@mkhalid03
Copy link

How to manage this on Edit form ?

@webfroth
Copy link

webfroth commented Jun 8, 2017

THANK YOU! Woo! ;-)

@mitchellshelton
Copy link

Here is a D8 version if anyone is interested:

<?php
/**
 * @file
 * Contains \Drupal\mymodule\Form\FormTest.
 */
namespace Drupal\mymodule\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class FormTest extends FormBase {
  
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'mymodule_form_test';
  }
  
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $item_id = NULL) {
  
    // Disable caching for the form
    $form['#cache'] = ['max-age' => 0];
    
    // Do not flatten nested form fields
    $form['#tree'] = TRUE;
    
    $form['field_container'] = array(
      '#type' => 'container',
      '#weight' => 80,
      '#tree' => TRUE,
      // Set up the wrapper so that AJAX will be able to replace the fieldset.
      '#prefix' => '<div id="js-ajax-elements-wrapper">',
      '#suffix' => '</div>',
    );
    
    if ($form_state->get('field_deltas') == '') {
      $form_state->set('field_deltas', range(0, 3));
    }
    
    $field_count = $form_state->get('field_deltas');
  
    foreach ($field_count as $delta) {
      $form['field_container'][$delta] = array(
        '#type' => 'container',
        '#attributes' => array(
          'class' => array('container-inline'),
        ),
        '#tree' => TRUE,
      );
    
      $form['field_container'][$delta]['field1'] = array(
        '#type' => 'textfield',
        '#title' => t('Field 1 - ' . $delta),
        '#size' => 10,
      );
    
      $form['field_container'][$delta]['field2'] = array(
        '#type' => 'textfield',
        '#title' => t('Field 2 - ' . $delta),
        '#size' => 10,
      );
    
      $form['field_container'][$delta]['remove_name'] = array(
        '#type' => 'submit',
        '#value' => t('-'),
        '#submit' => array('::mymoduleAjaxExampleAddMoreRemove'),
        '#ajax' => array(
          'callback' => '::mymoduleAjaxExampleAddMoreRemoveCallback',
          'wrapper' => 'js-ajax-elements-wrapper',
        ),
        '#weight' => -50,
        '#attributes' => array(
          'class' => array('button-small'),
        ),
        '#name' => 'remove_name_' . $delta,
      );
    }
  
    $form['field_container']['add_name'] = array(
      '#type' => 'submit',
      '#value' => t('Add one more'),
      '#submit' => array('::mymoduleAjaxExampleAddMoreAddOne'),
      '#ajax' => array(
        'callback' => '::mymoduleAjaxExampleAddMoreAddOneCallback',
        'wrapper' => 'js-ajax-elements-wrapper',
      ),
      '#weight' => 100,
    );
  
    $form['other_field'] = array(
      '#type' => 'textfield',
      '#title' => t('Other field'),
    );

    return $form;
  }
  
  /**
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   */
  function mymoduleAjaxExampleAddMoreRemove(array &$form, FormStateInterface $form_state) {
    // Get the triggering item
    $delta_remove = $form_state->getTriggeringElement()['#parents'][1];
    
    // Store our form state
    $field_deltas_array = $form_state->get('field_deltas');
    
    // Find the key of the item we need to remove
    $key_to_remove = array_search($delta_remove, $field_deltas_array);
    
    // Remove our triggered element
    unset($field_deltas_array[$key_to_remove]);
    
    // Rebuild the field deltas values
    $form_state->set('field_deltas', $field_deltas_array);
    
    // Rebuild the form
    $form_state->setRebuild();
    
    // Return any messages set
    drupal_get_messages();
  }
  
  /**
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *
   * @return mixed
   */
  function mymoduleAjaxExampleAddMoreRemoveCallback(array &$form, FormStateInterface $form_state) {
    return $form['field_container'];
  }
  
  /**
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   */
  function mymoduleAjaxExampleAddMoreAddOne(array &$form, FormStateInterface $form_state) {
  
    // Store our form state
    $field_deltas_array = $form_state->get('field_deltas');
    
    // check to see if there is more than one item in our array
    if (count($field_deltas_array) > 0) {
      // Add a new element to our array and set it to our highest value plus one
      $field_deltas_array[] = max($field_deltas_array) + 1;
    }
    else {
      // Set the new array element to 0
      $field_deltas_array[] = 0;
    }
  
    // Rebuild the field deltas values
    $form_state->set('field_deltas', $field_deltas_array);
  
    // Rebuild the form
    $form_state->setRebuild();
  
    // Return any messages set
    drupal_get_messages();
  }
  
  /**
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *
   * @return mixed
   */
  function mymoduleAjaxExampleAddMoreAddOneCallback(array &$form, FormStateInterface $form_state) {
    return $form['field_container'];
  }
  
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Submit Form
  }
  
}

@dgading
Copy link

dgading commented Dec 3, 2017

Thank you, this was exactly what I needed to figure out dynamic forms in Drupal 8.

@drupix
Copy link

drupix commented Oct 21, 2018

Thank you Alex and Mitchell, you make my day !

@nareshiksula
Copy link

Thank you, Alex and Mitchell, you really helped me a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment