1

I'm trying to create a custom widget for my module which will contain a textfield to hold a question(textfield), Question style (select box), potential answers (typed into textarea).

I'd then like an "add another item" button so the client can add as many questions as they like.

I've got a basic ajax widget working but i'm not sure if I've constructed it correctly because when I want to validate the data in hook_field_validate I don't seem to be able to access each question seperately.

I thought i'd be able to validate each question before the next is added by ajax but this doesn't seem to be working.

Asssuming this code (below) is correct, can you please advise how I can validate each time the "add another item" is clicked.

My hook_field_widget_form looks like this:

    function keep_warm_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  $element['question_wrapper'] = array(
    '#type' => 'fieldset',
    '#title' => t('Questions'),
    // Set up the wrapper so that AJAX will be able to replace the fieldset.
    '#prefix' => '<div id="names-fieldset-wrapper">',
    '#suffix' => '</div>',
  );

  if (empty($form_state['num_names'])) {
    $form_state['num_names'] = 1;
  }
  for ($i = 0; $i < $form_state['num_names']; $i++) {


    //other form elements
    $element['question_wrapper'][$i]['question'] = array(
      '#type' => 'textfield',
      '#title' => t('Question number '. $form_state['num_names']),

    );

  }

  $element['question_wrapper']['add_question'] = array(
    '#type' => 'submit',
    '#value' => t('Add another question'),
    '#submit' => array('ajax_example_add_more_add_one'),
    // See the examples in ajax_example.module for more details on the
    // properties of #ajax.
    '#ajax' => array(
      'callback' => 'ajax_example_add_more_callback',
      'wrapper' => 'names-fieldset-wrapper',
    ),
  );
  if ($form_state['num_names'] > 1) {
    $element['question_wrapper']['remove_name'] = array(
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => array('ajax_example_add_more_remove_one'),
      '#ajax' => array(
        'callback' => 'ajax_example_add_more_callback',
        'wrapper' => 'names-fieldset-wrapper',
      ),
    );
  }

return $element;
}

And my ajax callback

function ajax_example_add_more_callback($form, $form_state) {
  $form_state['num_names']++;
  return $form['field_question'];
}

2 Answers 2

1

Looks like you're just missing a #tree property:

Used to allow collections of form elements. Normally applied to the "parent" element, as the #tree property cascades to sub-elements. Use where you previously used ][ in form_ calls. For more information, see #tree and #parents in the handbook.

e.g.

$element['question_wrapper'] = array(
  '#type' => 'fieldset',
  '#title' => t('Questions'),
  // Set up the wrapper so that AJAX will be able to replace the fieldset.
  '#prefix' => '<div id="names-fieldset-wrapper">',
  '#suffix' => '</div>',
  '#tree' => TRUE,
);

And then in the validate handler:

foreach ($form_state['values']['question_wrapper'] as $key => $value) {
  if (is_numeric($key) && something_is_wrong_with($value['question'])) {
    form_set_error("question_wrapper][$key][question", 'Error');
  }
}
5
  • Your answer do the job , but I still think @useyourillusiontoo shouldn't implement ajax widget by his own , I think storing data in DB will raise another mess for him this way. Jul 17, 2015 at 17:27
  • I might be wrong, but I've had to do something like this before too. I needed multiple sets of multiple questions, so a kind of multiple field within a multiple field. You're right, the database storage can be awkward, but since the data was just coming back as text for me I serialised an array of strings and just deserialised again when the field data was loaded. Not the most elegant from best-practice, but without installing the dreaded Field Collection module I don't think there's a nicer way
    – Clive
    Jul 17, 2015 at 17:32
  • Actually you could normalise the questions into their own table, but then you're at the point where you might as well create a custom entity type and use an entity reference field with Inline Entity Form. Typical Drupal, there are quite a few different ways you could solve this
    – Clive
    Jul 17, 2015 at 17:34
  • @Clive Thanks for your advice. When I first created this widget I had assigned the Tree attribute because I though that was the correct approach but still seemed to have the same problem. I.e. each click meant I had to validate all questions again, not just the most recent one entered. I'll give it another shot on Monday and let you know. Jul 18, 2015 at 7:28
  • if you want to validate just the latest field you can use your num_names variable like this : var $last = $form_state['num_names'] and then : $form_state['values']['question_wrapper'][$last] will give you access to your last question wrapper . Jul 19, 2015 at 10:05
0

If you want multiple questions , then you can use UI and define your field as multiple value field when creating an instance of it. This way is easy and drupal will handle the rest. just because I think you have misunderstood I tell you that using field API we use to define a Field type not an Instance , I don't say you are completely wrong but what if your field type wants to be used in a situation that it always used single value?

but if you still persist you can write a validation function and call it in "ajax_example_add_more_callback" function.

to access your fields data you can use this code :

foreach($form_state['values']['question_wrapper'] as $key => $value) {
  //do something with $item
  //Code was stolen from @Clive . just for not representing a false answer
}
4
  • That'll loop through entries in a multiple cardinality field, but the OP is trying to loop through the textfields they've added to the field widget for each of those fields. Slightly different thing
    – Clive
    Jul 17, 2015 at 17:22
  • @Clive you are mentioning all my mistakes today :D Jul 17, 2015 at 17:24
  • Sorry about that ;)
    – Clive
    Jul 17, 2015 at 17:27
  • Thanks for that , I must say ;) (for mentioning my mistakes) Jul 17, 2015 at 17:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.