Some more improvements to exposed filters.
authorEarl Miles
Sun, 24 Feb 2008 22:52:53 +0000 (22:52 +0000)
committerEarl Miles
Sun, 24 Feb 2008 22:52:53 +0000 (22:52 +0000)
css/admin.css
includes/admin.inc
includes/handlers.inc
includes/view.inc
views.module

index 7f63fbb..ea279da 100644 (file)
@@ -324,6 +324,11 @@ html.js #views-ajax-pad {
   width: 50%;
 }
 
+#views-ajax-pad .views-left-75 {
+  float: left;
+  width: 75%;
+}
+
 #views-ajax-pad .views-radio-box {
   overflow: auto;
   max-height: 180px;
index f8b1437..3265bdd 100644 (file)
@@ -1338,10 +1338,15 @@ function views_ui_config_item_form_remove($form, &$form_state) {
  * Override handler for views_ui_edit_display_form
  */
 function views_ui_config_item_form_expose($form, &$form_state) {
-  $item = $form_state['handler']->options;
+  $item = &$form_state['handler']->options;
   // flip
   $item['exposed'] = empty($item['exposed']);
 
+  // If necessary, set new defaults:
+  if ($item['exposed']) {
+    $form_state['handler']->expose_options();
+  }
+
   $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
 
   views_ui_cache_set($form_state['view']);
index e7fb8c7..81195c4 100644 (file)
@@ -209,7 +209,12 @@ class views_handler extends views_object {
   function options_submit($form, &$form_state) { }
 
   /**
-   * render our chunk of the exposed filter form when selecting
+   * Set new exposed option defaults when exposed setting is flipped
+   * on.
+   */
+  function expose_options() { }
+  /**
+   * Render our chunk of the exposed filter form when selecting
    */
   function exposed_form(&$form, &$form_state) { }
 
@@ -224,6 +229,17 @@ class views_handler extends views_object {
   function exposed_submit(&$form, &$form_state) { }
 
   /**
+   * Get information about the exposed form for the form renderer.
+   *
+   * @return
+   *   An array with the following keys:
+   *   - operator: The $form key of the operator. Set to NULL if no operator.
+   *   - value: The $form key of the value. Set to NULL if no value.
+   *   - label: The label to use for this piece.
+   */
+  function exposed_info() { }
+
+  /**
    * Run before the view is built.
    *
    * This gives all the handlers some time to set up before any handler has
@@ -838,22 +854,68 @@ class views_handler_filter extends views_handler {
    * that are nicely tailored to the given filter.
    */
   function expose_form(&$form, &$form_state) {
-    $form['expose']['operator'] = array(
-      '#type' => 'textfield',
-      '#default_value' => $this->options['expose']['operator'],
-      '#title' => t('Operator identifier'),
-      '#description' => t('This will appear in the URL after the ? to identify this operator. Leave blank to not expose the operator.'),
+    $form['expose']['start_left'] = array(
+      '#value' => '<div class="views-left-50">',
     );
+
+    if (!empty($form['operator']['#type'])) {
+      $form['expose']['operator'] = array(
+        '#type' => 'textfield',
+        '#default_value' => $this->options['expose']['operator'],
+        '#title' => t('Operator identifier'),
+        '#size' => 40,
+        '#description' => t('This will appear in the URL after the ? to identify this operator. Leave blank to not expose the operator.'),
+      );
+    }
+    else {
+      $form['expose']['operator'] = array(
+        '#type' => 'value',
+        '#value' => '',
+      );
+    }
     $form['expose']['identifier'] = array(
       '#type' => 'textfield',
       '#default_value' => $this->options['expose']['identifier'],
       '#title' => t('Filter identifier'),
+      '#size' => 40,
       '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
     );
     $form['expose']['label'] = array(
       '#type' => 'textfield',
       '#default_value' => $this->options['expose']['label'],
       '#title' => t('Label'),
+      '#size' => 40,
+    );
+
+    $form['expose']['end_left'] = array(
+      '#value' => '</div>',
+    );
+
+    $form['expose']['start_checkboxes'] = array(
+      '#value' => '<div class="form-checkboxes views-left-40 clear-block">',
+    );
+    $form['expose']['optional'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Optional'),
+      '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
+      '#default_value' => $this->options['expose']['optional'],
+    );
+    if (empty($this->no_single)) {
+      $form['expose']['single'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Force single'),
+        '#description' => t('Force this exposed filter to accept only one option.'),
+        '#default_value' => $this->options['expose']['single'],
+      );
+    }
+    $form['expose']['remember'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Remember'),
+      '#description' => t('Remember the last setting the user gave this filter.'),
+      '#default_value' => $this->options['expose']['remember'],
+    );
+    $form['expose']['end_checkboxes'] = array(
+      '#value' => '</div>',
     );
   }
 
@@ -875,8 +937,20 @@ class views_handler_filter extends views_handler {
    */
   function expose_submit($form, &$form_state) { }
 
+  function expose_options() {
+    $this->options['expose'] = array(
+      'operator' => $this->options['id'] . '_oper',
+      'identifier' => $this->options['id'],
+      'label' => t('@group: @title', array('@group' => $this->definition['group'], '@title' => $this->definition['title'])),
+      'remember' => FALSE,
+      'single' => TRUE,
+      'optional' => TRUE,
+    );
+  }
   /**
-   * render our chunk of the exposed filter form when selecting
+   * Render our chunk of the exposed filter form when selecting
+   *
+   * You can override this if it doesn't do what you expect.
    */
   function exposed_form(&$form, &$form_state) {
     if (empty($this->options['exposed'])) {
@@ -884,21 +958,84 @@ class views_handler_filter extends views_handler {
     }
 
     if (!empty($this->options['expose']['operator'])) {
+      $operator = $this->options['expose']['operator'];
       $this->operator_form($form, $form_state);
-      $form[$this->options['expose']['operator']] = $form['operator'];
-      unset($form[$this->options['expose']['operator']]['#title']);
+      $form[$operator] = $form['operator'];
+
+      if (isset($form[$operator]['#title'])) {
+        unset($form[$operator]['#title']);
+      }
+
+      $this->exposed_translate($form[$operator], 'operator');
+
       unset($form['operator']);
     }
 
     if (!empty($this->options['expose']['identifier'])) {
+      $value = $this->options['expose']['identifier'];
       $this->value_form($form, $form_state);
-      $form[$this->options['expose']['identifier']] = $form['value'];
-      $form[$this->options['expose']['identifier']]['#title'] = $this->options['expose']['label'];
+      $form[$value] = $form['value'];
+
+      if (isset($form[$value]['#title']) && !empty($form[$value]['#type']) && $form[$value]['#type'] != 'checkbox') {
+        unset($form[$value]['#title']);
+      }
+
+      $this->exposed_translate($form[$value], 'value');
+
+      if (!empty($form['#type']) && ($form['#type'] == 'checkboxes' || ($form['#type'] == 'select' && !empty($form['#multiple'])))) {
+        unset($form[$value]['#default_value']);
+      }
+
+      if (!empty($form['#type']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
+        $form[$value]['#default_value'] = 'All';
+      }
+
       unset($form['value']);
     }
   }
 
   /**
+   * Make some translations to a form item to make it more suitable to
+   * exposing.
+   */
+  function exposed_translate(&$form, $type) {
+    if (!isset($form['#type'])) {
+      return;
+    }
+
+    if ($form['#type'] == 'radios') {
+      $form['#type'] = 'select';
+    }
+    if ($form['#type'] == 'checkboxes' && !empty($this->options['expose']['single'])) {
+      $form['#type'] = 'select';
+    }
+    if (!empty($this->options['expose']['single']) && isset($form['#multiple'])) {
+      unset($form['#multiple']);
+    }
+
+    if ($type == 'value' && !empty($this->options['expose']['optional']) && $form['#type'] == 'select' && empty($form['#multiple'])) {
+      $form['#options'] = array('All' => t('<Any>')) + $form['#options'];
+      $form['#default_value'] = 'All';
+    }
+  }
+
+  /**
+   * Tell the renderer about our exposed form. This only needs to be
+   * overridden for particularly complex forms. And maybe not even then.
+   */
+  function exposed_info() {
+    if (empty($this->options['exposed'])) {
+      return;
+    }
+
+    return array(
+      'operator' => $this->options['expose']['operator'],
+      'value' => $this->options['expose']['identifier'],
+      'label' => $this->options['expose']['label'],
+    );
+  }
+
+  /**
    * Check to see if input from the exposed filters should change
    * the behavior if this filter.
    */
@@ -907,13 +1044,33 @@ class views_handler_filter extends views_handler {
       return;
     }
 
-    if (!empty($this->options['expose']['operator']) && !empty($input[$this->options['expose']['operator']])) {
+    if (!empty($this->options['expose']['operator']) && isset($input[$this->options['expose']['operator']])) {
       $this->operator = $input[$this->options['expose']['operator']];
     }
 
-    if (!empty($this->options['expose']['identifier']) && !empty($input[$this->options['expose']['identifier']])) {
-      $this->value = $input[$this->options['expose']['identifier']];
+    if (!empty($this->options['expose']['identifier'])) {
+      $value = $input[$this->options['expose']['identifier']];
+      // Various ways to check for the absence of optional input.
+      if (!empty($this->options['expose']['optional'])) {
+        if ($value == 'All' || $value === array()) {
+          return FALSE;
+        }
+
+        if (!empty($this->no_single) && $value === '') {
+          return FALSE;
+        }
+      }
+
+
+      if (isset($value)) {
+        $this->value = $value;
+      }
+      else {
+        return FALSE;
+      }
     }
+
+    return TRUE;
   }
 
   /**
@@ -933,6 +1090,9 @@ class views_handler_filter extends views_handler {
  * Simple filter to handle equal to / not equal to filters
  */
 class views_handler_filter_equality extends views_handler_filter {
+  // exposed filter options
+  var $no_single = TRUE;
+
   /**
    * Provide basic defaults for the equality operator
    */
@@ -974,6 +1134,9 @@ class views_handler_filter_equality extends views_handler_filter {
  * Simple filter to handle matching of boolean values
  */
 class views_handler_filter_boolean_operator extends views_handler_filter {
+  // exposed filter options
+  var $no_single = TRUE;
+
   function construct() {
     $this->value_value = t('True');
     if (isset($this->definition['label'])) {
@@ -984,36 +1147,53 @@ class views_handler_filter_boolean_operator extends views_handler_filter {
 
   function options(&$options) {
     parent::options($options);
-    $options['operator'] = '=';
+    $options['value'] = FALSE;
   }
 
   function operator_form(&$form, &$form_state) {
-    $form['operator'] = array(
-      '#type' => 'radios',
-      '#default_value' => $this->operator,
-      '#options' => array(
-        '=' => t('Is'),
-        '<>' => t('Is not'),
-      ),
-    );
+    $form['operator'] = array();
   }
+
   function value_form(&$form, &$form_state) {
-    $form['value'] = array(
-      '#type' => 'textfield',
-      '#disabled' => TRUE,
-      '#size' => 20,
-      '#value' => $this->value_value,
-    );
+    if (empty($this->options['exposed'])) {
+      $form['value'] = array(
+        '#type' => 'checkbox',
+        '#title' => $this->value_value,
+        '#default_value' => $this->value,
+      );
+    }
+    else {
+      $form['value'] = array(
+        '#type' => 'select',
+        '#title' => $this->value_value,
+        '#options' => array(1 => t('Yes'), 0 => t('No')),
+        '#default_value' => $this->value,
+      );
+    }
   }
-
   function admin_summary() {
-    return check_plain($this->operator) . ' ' . check_plain($this->value_value);
+    if (!empty($this->options['exposed'])) {
+      return t('exposed');
+    }
+
+    return (empty($this->value) ? t("False") : t('True'));
+  }
+
+  function expose_options() {
+    $this->options['expose'] = array(
+      'operator' => '',
+      'identifier' => $this->options['id'],
+      'label' => $this->value_value,
+      'remember' => FALSE,
+      'single' => TRUE,
+      'optional' => FALSE,
+    );
   }
 
   function query() {
     // @todo this should actually reverse the operator so it can compare against 0.
     $this->ensure_my_table();
-    $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . " 1");
+    $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . (empty($this->value) ? '=' : '<>') . " 0");
   }
 
 }
@@ -1052,7 +1232,6 @@ class views_handler_filter_in_operator extends views_handler_filter {
   function value_form(&$form, &$form_state) {
     $form['value'] = array(
       '#type' => 'checkboxes',
-      '#required' => TRUE,
       '#title' => $this->value_title,
       '#options' => $this->value_options,
       '#default_value' => (array) $this->value,
@@ -1065,6 +1244,10 @@ class views_handler_filter_in_operator extends views_handler_filter {
   }
 
   function admin_summary() {
+    if (!empty($this->options['exposed'])) {
+      return t('exposed');
+    }
+
     if (count($this->value) == 1) {
       // If there is only one, show it as an =.
       $keys = array_keys($this->value);
@@ -1094,6 +1277,9 @@ class views_handler_filter_in_operator extends views_handler_filter {
   }
 
   function query() {
+    if (empty($this->value)) {
+      return;
+    }
     $this->ensure_my_table();
     $replace = array_fill(0, sizeof($this->value), "'%s'");
     $in = ' (' . implode(", ", $replace) . ')';
index dd0db62..a122863 100644 (file)
@@ -435,7 +435,9 @@ class view extends views_db_object {
       if (!empty($array[$id]['handler']) && is_object($array[$id]['handler'])) {
         // Give this handler access to the exposed filter input.
         if (!empty($this->exposed_input)) {
-          $array[$id]['handler']->accept_exposed_input($this->exposed_input);
+          if (!$array[$id]['handler']->accept_exposed_input($this->exposed_input)) {
+            continue;
+          }
         }
         $array[$id]['handler']->query();
       }
index ddeb4cf..901b7d9 100644 (file)
@@ -814,9 +814,14 @@ function views_exposed_form(&$form_state, &$view, &$display, $source = NULL) {
   // Let form plugins know this is for exposed widgets.
   $form_state['exposed'] = TRUE;
 
+  $form['#info'] = array();
+
   // Go through each filter and let it generate its info.
   foreach ($view->filter as $id => $filter) {
     $filter['handler']->exposed_form($form, $form_state);
+    if ($info = $filter['handler']->exposed_info()) {
+      $form['#info']['filter-' . $id] = $info;
+    }
   }
 
   // @todo deal with exposed sorts
@@ -842,7 +847,43 @@ function views_exposed_form(&$form_state, &$view, &$display, $source = NULL) {
  * Default theme function for all filter forms.
  */
 function theme_views_exposed_form(&$form) {
-  return drupal_render($form);
+  views_add_css('views');
+  $output = '';
+  $output .= '<div class="views-exposed-form">';
+  $output .= '<div class="views-exposed-widgets clear-block">';
+
+  // Put all single checkboxes together in the last spot.
+  $checkboxes = '';
+
+  foreach ($form['#info'] as $id => $info) {
+    if ($form[$info['value']]['#type'] == 'checkbox') {
+      $checkboxes = drupal_render($form[$info['value']]);
+      continue;
+    }
+    $output .= '<div class="views-left">';
+    $output .= '<label>' . $info['label'] . '</label>';
+    $label = FALSE;
+    if (!empty($info['operator'])) {
+      $form[$info['operator']]['#prefix'] = '<div class="views-left">';
+      $form[$info['operator']]['#suffix'] = '</div>';
+      $output .= drupal_render($form[$info['operator']]);
+    }
+    $form[$info['value']]['#prefix'] = '<div class="views-left">';
+    $form[$info['value']]['#suffix'] = '</div>';
+    $output .= drupal_render($form[$info['value']]);
+    $output .= '</div>';
+  }
+  if ($checkboxes) {
+    $output .= '<div class="views-left">';
+    $output .= $checkboxes;
+    $output .= '</div>';
+  }
+  $output .= '</div>';
+
+  // This includes the submit button.
+  $output .= drupal_render($form);
+  $output .= '</div>';
+  return $output;
 }
 
 /**