I have a drupal form with repeatable fields allowing to type in as many phone numbers as one wants. Simplifying to the extreme, the code looks like this:
function form_test($form, $form_state) {
$form = array(
'#attributes' => array(
'enctype' => 'multipart/form-data'
),
'phoneNumbers' => array(
'#type' => 'fieldset',
'#attributes' => array(
'class' => array('repeatable_container')
),
'#tree' => true,
0 => array(
'#type' => 'fieldset',
'#attributes' => array(
'class' => array('repeatable')
),
'phoneNumber' => array(
'#type' => 'textfield'
)
)
),
'enregistrer' => array(
'#type' => 'submit',
'#value' => t('Enregistrer'),
'#submit' => array('form_test_enregistrer')
)
);
return $form;
}
function form_test_validate($form, &$form_state) {
drupal_set_message("<pre>" . print_r($form_state['input']['phoneNumbers'], true) . "</pre>", 'warning');
}
function form_test_enregistrer($form, &$form_state) {
// Whatever
}
As you can see, the validation function simply prints out the phone numbers that are submitted through the form. The classes 'repeatable_container' and 'repeatable' are used by the following javascript (simplified here) to allow the addition or reletion of phone numbers:
jQuery.noConflict();
(function ($) {
"use strict";
var getNextIdentifier, repeatButton, deleteButton;
getNextIdentifier = function getNextIdentifier(original) {
var re_lastNumber = /\d+(?!.*\d+)/, // One or more digits NOT followed by anything (.*) plus a number (\d+ : one or more digits). Basically, the last number of the string.
m,
newIdentifier = original;
m = original.match(re_lastNumber);
if (m !== null) {
newIdentifier = original.replace(re_lastNumber, parseInt(m[0], 10) + 1);
}
return newIdentifier;
};
$.prototype.hasAttr = function (attrName) {
var attr = $(this).attr(attrName);
return (typeof(attr) !== 'undefined' && attr !== false);
}
$.prototype.increaseID = function (element) {
return element.attr('id', getNextIdentifier(element.attr('id')));
}
$.prototype.increaseName = function (element) {
return element.attr('name', getNextIdentifier(element.attr('name')));
}
$.prototype.increaseClassNames = function (element) {
var classes = element.attr('class').split(' '),
nbClasses = classes.length,
i;
for (i = 0; i < nbClasses; i += 1) {
classes[i] = getNextIdentifier(classes[i]);
}
return element.attr('class', classes.join(' '));
}
$.prototype.increaseIdentifiers = function () {
var attrs = [
{ attrName: 'id', func: $(this).increaseID},
{ attrName: 'name', func: $(this).increaseName },
{ attrName: 'class', func: $(this).increaseClassNames }
],
nbAttrs = attrs.length,
i;
for (i = 0; i < nbAttrs; i += 1) {
if (this.hasAttr(attrs[i].attrName)) {
attrs[i].func(this);
}
$('[' + attrs[i].attrName + ']', this).each(function () {
attrs[i].func($(this));
});
}
return this;
};
repeatButton = function (eventObject) {
eventObject.preventDefault();
var parent = $(this).prev(),
element = $('.repeatable', parent).last(),
clone = element.clone(true, true).increaseIdentifiers(true).appendTo(parent);
};
deleteButton = function (eventObject) {
eventObject.preventDefault();
$(this).closest('.repeatable').remove();
};
$('document').ready(function () {
$('.repeatable_container').append($('<a href="#" class="repeater">+</a>'));
$('.repeatable').append($('<a href="#" class="deleter">x</a>'));
$('a.repeater').click(repeatButton);
$('a.deleter').click(deleteButton);
});
}(jQuery));
The problem arises when I create multiple phone numbers and I erase the very first one. For example, if I duplicate the phone number field and erase the first one, I would expect the validation function to obtain one phone number with id of 1 (the id 0 having been erased). But that's not what happens. In fact, what I get are two phone number, the first with id 1 (expected) and the second with id 0 (which should have been erased), in that order.
If I inspect the dom, I see that the javascript part works perfectly, so I'm pretty sure drupal is doing something here that I don't understand. Any clues on what's happening here?
$form
argument the form builder receives; you don't initialize$form
to an new array.dpm()
function; for example,dpm($form_state)
in your validate or submit handler will give you a navigable $form_state array that you can browse in the messages area on the next page view. Very helpful!