5

I have a view that has three expose filters (Manufacturer, Type, Model), three options list (term reference to Printer vocabulary) .

Vocabulary: Printer (3 levels)

-- HP           
---- xxx     
------ xxxx  
-- Apple
---- xxxx
------ xxxxxx
-- Dell
---- xxxx
------- xxxxx
-- ...
-- ...
(thousands of terms)

Since Form API AJAX doesn't work on views expose form, I am unable to make the them dependent select for exposed form fields. So I used jQuery to make the exposed form fields "dependent select / cascading select".

enter image description here

Basically, I empty all the options in views_exposed_form_alter() firstly:

$form['manufacturer_id']['#options'] = $form['type_id']['#options'] = $form['model_id']['#options'] = array();

then populate the option lists with my jQuery code accordingly.

However I get "An illegal choice has been detected." on page load, and form submit. Form submission is prevented due to Drupal form validation mechanism .

enter image description here

It's the form validation detect the options generated by jQuery are not come with the original form data, as I removed the options in form alter. Value of a select box is not present in its defined options .

So I tried to remove form validation in form alter as:

unset($form['#validate']);

still doesn't work, the illegal choice error remains.

============ UPDATED =================

Inspired by @kiamlaluno 's answer below, the only solution I came up is hack/patch the core. Here is what I done:

Edited /includes/form.inc : (line 2014)

enter image description here

I removed the code:

$element['#needs_validation'] = TRUE;

and added:

// if not these fields, mark for validation as usual. 
// so if are these fields, validation will be passed. 

if ($element['#name'] != 'manufacturer_id'  && 
    $element['#name'] != 'type_id'          && 
    $element['#name'] != 'model_id' ) {

  $element['#needs_validation'] = TRUE;
}

It works, the exposed form select option bypassed the validation.

But my solution is very ugly which modified the core.

I wonder if there is any other solution without touching the core? How can I bypass the validation of these exposed form fields?

Thanks!

4 Answers 4

9

The function that shows those errors is _form_validate(), which contains the following code.

  // Validate the current input.
  if (!isset($elements['#validated']) || !$elements['#validated']) {
    // The following errors are always shown.
    if (isset($elements['#needs_validation'])) {

      // ...

      if (isset($elements['#options']) && isset($elements['#value'])) {
        if ($elements['#type'] == 'select') {
          $options = form_options_flatten($elements['#options']);
        }
        else {
          $options = $elements['#options'];
        }
        if (is_array($elements['#value'])) {
          $value = in_array($elements['#type'], array('checkboxes', 'tableselect')) ? array_keys($elements['#value']) : $elements['#value'];
          foreach ($value as $v) {
            if (!isset($options[$v])) {
              form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
              watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
            }
          }
        }

        // ...
      }

The function is always executed, whenever the form has a validation handler, or not.

You could set $form['#needs_validation'] to FALSE, but _form_builder_handle_input_element() sets it to TRUE. The function is called from form_builder(), which is then called from drupal_process_form(), or drupal_rebuild_form().

  // Mark all posted values for validation.
  if (isset($element['#value']) || (!empty($element['#required']))) {
    $element['#needs_validation'] = TRUE;
  }

After _form_builder_handle_input_element() is called, form_builder() calls any #process function set for the form element.

  // Handle input elements.
  if (!empty($element['#input'])) {
    _form_builder_handle_input_element($form_id, $element, $form_state);
  }
  // Allow for elements to expand to multiple elements, e.g., radios,
  // checkboxes and files.
  if (isset($element['#process']) && !$element['#processed']) {
    foreach ($element['#process'] as $process) {
      $element = $process($element, $form_state, $form_state['complete form']);
    }
    $element['#processed'] = TRUE;
  }

It also calls any #after_build functions set for the form element.

  // The #after_build flag allows any piece of a form to be altered
  // after normal input parsing has been completed.
  if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
    foreach ($element['#after_build'] as $function) {
      $element = $function($element, $form_state);
    }
    $element['#after_build_done'] = TRUE;
  }

You could set a #after_build function that executes code similar to the following one.

function mymodule_remove_validation($form_element) {
  unset($form_element['#needs_validation']);

  return $form_element;
}

I am using unset($form_element['#needs_validation']) because the if-statement in _form_validate() checks isset($elements['#needs_validation']), instead of !empty($elements['#needs_validation']).

In this way, the form element would not have #needs_validation set to TRUE, and Drupal would not check the returned value for the form element is one of the possible values it can get, nor is the length of a textfield string longer than required.
It doesn't avoid the form validation handlers are run, thought.

6
  • that means no way to bypass the validation as it will always set to be required?
    – gilzero
    Oct 26, 2012 at 3:47
  • I just tried hacking the core (yes, very ugly solution for me), by commenting out the line "$element['#needs_validation'] = TRUE;" in "_form_builder_handle_input_element()". That would be great, if there is an alter hook for this. :(
    – gilzero
    Oct 26, 2012 at 5:31
  • 1
    I have updated my answer with a solution. #after_build functions set for a form element are called right after the function setting $element['#needs_validation']. It means you could set it to FALSE, and avoid Drupal checks the values for that form element are between the possible values the form element can get.
    – apaderno
    Oct 29, 2012 at 15:13
  • thanks for the detailed updated. Unfortunately, the after_build callback still doesn't help here. Here is what I done: in form alter, I added: '$form['manufacturer_id']['#after_build'] = array('printerglue_remove_validation');' . Within the callback function, 'function printerglue_remove_validation($form_element) { ... }', I have '$form_element['#needs_validation'] = FALSE;', I also dsm($form_element), confirmed that '#needs_validation' has been set to FALSE, the callback was called, 'FALSE' value was set. However, still throw me the illegal choice for that element.
    – gilzero
    Oct 30, 2012 at 15:46
  • I got it working. Instead of using 'form_element['#needs_validation'] = FALSE;' , use 'unset(form_element['#needs_validation'])' in after_build callback made the validation removed. So looks like if form_element['#needs_validation'] exists regardless of its value, field will always be sent to be validated. Anyway, the after_build call made my day. Thanks! @kiamlaluno
    – gilzero
    Oct 30, 2012 at 17:12
4
+50

You need empty '#options' not in form_alter but in theming function. In this case validation will not display 'illegal choice error'.

First in views_exposed_form_alter() add custom theme function to your selects.

$form['manufacturer_id']['#theme'][] = 'select_empty';
$form['type_id']['#theme'][] = 'select_empty';
$form['model_id']['#theme'][] = 'select_empty';

Then in module or template.php file create new theme function.

function THEME_NAME_theme($existing, $type, $theme, $path) {
  return array(
    'select_empty' => array(
      'render element' => 'element',
     ),
  );
}

function THEME_NAME_select_empty($vars) {
  // Empty select values and add one fake option for correct theming.
  $vars['element']['#options'] = array(
    'All' => t('All'),
  );

  // return default select.
  return theme('select', $vars) 
}
3
  • Seems promising. I tried, theme function doesn't get called. In my module file > form alter: "$form['manufacturer_id']['#theme'][] = 'select_empty';", in the active theme's template.php: MYTHEMENAME_select_empty($vars) { dsm('hello world!'); dsm($vars); ... ... } . Cache flushed. Nothing shows. MYTHEMENAME_select_empty() is not called. Did I miss any step? Thanks!
    – gilzero
    Oct 30, 2012 at 16:40
  • 1
    don't forget to declare theme function in hook_theme() - see updatetd answer Oct 30, 2012 at 19:17
  • I have corrected code of THEME_NAME_theme() - please check. Oct 31, 2012 at 10:31
1

IIRC, it's not possible to disable validation on Form API starting from Drupal 6. Instead of emptying out options in Drupal, you can empty them with jQuery. This way, the options still exist on the Drupal side, and the validation error is not triggered.

4
  • Re:"Instead of emptying out options in Drupal, you can empty them with jQuery." . I actually did that before. However, it really slows down the page. Because the each select has thousands options. jQuery only does thing after page is ready, that means the page still populates all options first, then options got empty by jQuery. That makes the page took ages to load.
    – gilzero
    Oct 26, 2012 at 3:24
  • "IIRC, it's not possible to disable validation on Form API starting from Drupal 6." --- have to hack core?
    – gilzero
    Oct 26, 2012 at 5:08
  • >>jQuery only does thing after page is ready, that means the page still populates all options first, then options got empty by jQuery. That makes the page took ages to load - Should be pretty fast to empty options with jquery or JS - e.g $('#id').get(0).innerHTML=''; For the slow load, you could try preg_replace and remove the options in the views theme file
    – vamur
    Oct 26, 2012 at 22:12
  • My printer term list is huge, thousands+. Like I said, the html still loads the options first, and then jquery steps in, remove the options. That means browser still loaded thousands+ select options before jQuery's magic happened.
    – gilzero
    Oct 28, 2012 at 14:45
0

The hard part is updated, available colors, change the shape of the filter through AJAX. We can only tell an exposed filter to update its values on the basis of a AJAX UI through it will be great. Unfortunately, it is not yet possible. Actually, it is not possible to expose the filter to provide dynamic values is logical to consider any kind. When it can not change it because you will need to refresh themselves through AJAX? HPE0-Y53 PDF Dumps

1
  • is this a question or answer? Apr 30, 2017 at 14:05

Your Answer

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

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