Index: views.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/views.module,v retrieving revision 1.332.2.10 diff -u -p -r1.332.2.10 views.module --- views.module 24 Sep 2009 23:33:40 -0000 1.332.2.10 +++ views.module 7 Nov 2009 14:03:07 -0000 @@ -956,22 +956,45 @@ function views_exposed_form(&$form_state ); } + $exposed_sorts = array('' => t('- Select One -')); + // Go through each handler and let it generate its exposed widget. foreach ($view->display_handler->handlers as $type => $value) { foreach ($view->$type as $id => $handler) { if ($handler->can_expose() && $handler->is_exposed()) { - $handler->exposed_form($form, $form_state); - if ($info = $handler->exposed_info()) { - $form['#info']["$type-$id"] = $info; + if ($type == 'sort') { + if ($info = $handler->exposed_info()) { + $exposed_sorts[$id] = check_plain($info['label']); + } + } + else { + $handler->exposed_form($form, $form_state); + if ($info = $handler->exposed_info()) { + $form['#info']["$type-$id"] = $info; + } } } } } + if (count($exposed_sorts)) { + $form['sort_by'] = array( + '#type' => 'select', + '#options' => $exposed_sorts, + '#title' => t($form_state['exposed_sorts_label']), + ); + + $form['exposed_sort_order'] = array( + '#type' => 'select', + '#options' => array('asc' => t('Asc'), 'desc' => t('Desc')), + '#title' => t('Order'), + ); + } + $form['submit'] = array( '#name' => '', // prevent from showing up in $_GET. '#type' => 'submit', - '#value' => t('Apply'), + '#value' => t($form_state['submit_button']), '#id' => form_clean_id('edit-submit-' . $view->name), ); @@ -1018,7 +1041,7 @@ function views_exposed_form_submit(&$for $form_state['view']->exposed_raw_input = array(); foreach ($form_state['values'] as $key => $value) { - if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token', ''))) { + if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'submit_button', 'exposed_sorts_label',''))) { $form_state['view']->exposed_raw_input[$key] = $value; } } Index: handlers/views_handler_sort.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/handlers/views_handler_sort.inc,v retrieving revision 1.1.2.1 diff -u -p -r1.1.2.1 views_handler_sort.inc --- handlers/views_handler_sort.inc 26 Jun 2009 00:23:37 -0000 1.1.2.1 +++ handlers/views_handler_sort.inc 7 Nov 2009 14:03:08 -0000 @@ -10,6 +10,160 @@ * Base sort handler that has no options and performs a simple sort */ class views_handler_sort extends views_handler { + + /** + * Determine if a sort can be exposed. + */ + function can_expose() { return TRUE; } + + /** + * Provide the basic form which calls through to subforms. + * If overridden, it is best to call through to the parent, + * or to at least make sure all of the functions in this form + * are called. + */ + function options_form(&$form, &$form_state) { + if ($this->can_expose()) { + $this->show_expose_button($form, $form_state); + } + $form['start'] = array('#value' => '
'); + $this->sort_form($form, $form_state); + $form['end'] = array('#value' => '
'); + if ($this->can_expose()) { + $this->show_expose_form($form, $form_state); + } + } + + /** + * Simple validate handler + */ + function options_validate(&$form, &$form_state) { + $this->sort_validate($form, $form_state); + if (!empty($this->options['exposed'])) { + $this->expose_validate($form, $form_state); + } + } + + /** + * Validate the sort form. + */ + function sort_validate($form, &$form_state) { } + + /** + * Perform any necessary changes to the form values prior to storage. + * There is no need for this function to actually store the data. + */ + function sort_submit($form, &$form_state) { } + + /** + * Simple submit handler + */ + function options_submit(&$form, &$form_state) { + unset($form_state['values']['expose_button']); // don't store this. + $this->sort_submit($form, $form_state); + if (!empty($this->options['exposed'])) { + $this->expose_submit($form, $form_state); + } + } + + /** + * Provide a form for setting the sort. + * + * This may be overridden by child classes, and it must + * define $form['order']; + */ + function sort_form(&$form, &$form_state) { + $options = $this->sort_options(); + if (!empty($options)) { + $form['order'] = array( + '#type' => 'radios', + '#options' => $options, + '#default_value' => $this->options['order'], + ); + } + } + + /** + * Provide a list of options for the default sort form. + * Should be overridden by classes that don't override sort_form + */ + function sort_options() { return array( + 'ASC' => t('Sort ascending'), + 'DESC' => t('Sort descending'), + ); + } + + function expose_form_left(&$form, &$form_state) { + $form['expose']['label'] = array( + '#type' => 'textfield', + '#default_value' => $this->options['expose']['label'], + '#title' => t('Label'), + '#size' => 40, + ); + } + + /** + * Handle the 'left' side fo the exposed options form. + */ + function expose_form_right(&$form, &$form_state) { + + $form['expose']['order'] = array( + '#type' => 'value', + '#value' => 'ASC', + ); + + $form['expose']['label'] = array( + '#type' => 'textfield', + '#default_value' => $this->options['expose']['label'], + '#title' => t('Label'), + '#size' => 40, + '#required' => TRUE, + ); + } + + /** + * Provide default options for exposed sorts. + */ + function expose_options() { + $this->options['expose'] = array( + 'order' => $this->options['order'], + 'label' => $this->ui_name(), + ); + } + + /** + * Tell the renderer about our exposed form. This only needs to be + * overridden for particularly complex forms. And maybe not even then. + * + * @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() { + if (empty($this->options['exposed'])) { + return; + } + + return array( + 'order' => $this->options['expose']['order'], + 'label' => $this->options['expose']['label'], + ); + } + + /** + * Check to see if input from the exposed sorts should change + * the behavior of this sort. + */ + function accept_exposed_input($input) { + return TRUE; + } + + function store_exposed_input($input, $status) { + return TRUE; + } + /** * Called to add the sort to a query. */ @@ -22,40 +176,31 @@ class views_handler_sort extends views_h function option_definition() { $options = parent::option_definition(); + $options['exposed'] = array('default' => FALSE); $options['order'] = array('default' => 'ASC'); return $options; } /** - * Display whether or not the sort order is ascending or descending + * Display whether or not the sort sort is ascending or descending */ function admin_summary() { + if (!empty($this->options['exposed'])) { + return t('exposed'); + } switch ($this->options['order']) { case 'ASC': case 'asc': - default: - $type = t('asc'); + return t('asc'); break; case 'DESC'; case 'desc'; - $type = t('desc'); + return t('desc'); break; } - return '' . $type . ''; } - /** - * Basic options for all sort criteria - */ - function options_form(&$form, &$form_state) { - $form['order'] = array( - '#type' => 'radios', - '#title' => t('Sort order'), - '#options' => array('ASC' => t('Ascending'), 'DESC' => t('Descending')), - '#default_value' => $this->options['order'], - ); - } } /** Index: includes/plugins.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v retrieving revision 1.152.2.5 diff -u -p -r1.152.2.5 plugins.inc --- includes/plugins.inc 24 Sep 2009 23:28:23 -0000 1.152.2.5 +++ includes/plugins.inc 7 Nov 2009 14:03:09 -0000 @@ -267,6 +267,25 @@ function views_views_plugins() { 'help topic' => 'cache-time', ), ), + 'exposed_form' => array( + 'parent' => array( + 'no ui' => TRUE, + 'handler' => 'views_plugin_exposed_form', + 'parent' => '', + ), + 'basic' => array( + 'title' => t('Basic'), + 'help' => t('Basic exposed form'), + 'handler' => 'views_plugin_exposed_form_basic', + 'uses options' => TRUE, + ), + 'on_demand' => array( + 'title' => t('On demand'), + 'help' => t('On demand exposed form'), + 'handler' => 'views_plugin_exposed_form_on_demand', + 'uses options' => TRUE, + ), + ), ); } @@ -276,7 +295,7 @@ function views_views_plugins() { * @return Nested array of plugins, grouped by type. */ function views_discover_plugins() { - $cache = array('display' => array(), 'style' => array(), 'row' => array(), 'argument default' => array(), 'argument validator' => array(), 'access' => array(), 'cache' => array()); + $cache = array('display' => array(), 'style' => array(), 'row' => array(), 'argument default' => array(), 'argument validator' => array(), 'access' => array(), 'cache' => array(), 'exposed_form' => array()); // Get plugins from all mdoules. foreach (module_implements('views_plugins') as $module) { $function = $module . '_views_plugins'; Index: includes/view.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/view.inc,v retrieving revision 1.151.2.16 diff -u -p -r1.151.2.16 view.inc --- includes/view.inc 2 Nov 2009 23:34:26 -0000 1.151.2.16 +++ includes/view.inc 7 Nov 2009 14:03:13 -0000 @@ -383,42 +383,6 @@ class view extends views_db_object { } /** - * Render the exposed filter form. - * - * This actually does more than that; because it's using FAPI, the form will - * also assign data to the appropriate handlers for use in building the - * query. - */ - function render_exposed_form($block = FALSE) { - // Deal with any exposed filters we may have, before building. - $form_state = array( - 'view' => &$this, - 'display' => &$this->display_handler->display, - 'method' => 'get', - 'rerender' => TRUE, - 'no_redirect' => TRUE, - ); - - // Some types of displays (eg. attachments) may wish to use the exposed - // filters of their parent displays instead of showing an additional - // exposed filter form for the attachment as well as that for the parent. - if (!$this->display_handler->displays_exposed() || (!$block && $this->display_handler->get_option('exposed_block'))) { - unset($form_state['rerender']); - } - - if (!empty($this->ajax)) { - $form_state['ajax'] = TRUE; - } - - $output = drupal_build_form('views_exposed_form', $form_state); - if (!empty($form_state['js settings'])) { - $this->js_settings = $form_state['js settings']; - } - - return $output; - } - - /** * Build all the arguments. */ function _build_arguments() { @@ -584,7 +548,8 @@ class view extends views_db_object { $this->_pre_query(); if ($this->display_handler->uses_exposed()) { - $this->exposed_widgets = $this->render_exposed_form(); + $exposed_form = $this->display_handler->get_exposed_form_plugin(); + $this->exposed_widgets = $exposed_form->render_exposed_form(); if (form_set_error() || !empty($this->build_info['abort'])) { $this->built = TRUE; return empty($this->build_info['fail']); @@ -633,6 +598,11 @@ class view extends views_db_object { // Allow style handler to affect the query: $this->style_plugin->query(); + + // Allow exposed form to affect the query: + if (isset($exposed_form)) { + $exposed_form->query(); + } if (variable_get('views_sql_signature', FALSE)) { $this->query->add_signature($this); @@ -738,6 +708,9 @@ class view extends views_db_object { $this->start_query_capture(); } + $exposed_form = $this->display_handler->get_exposed_form_plugin(); + $exposed_form->pre_render(); + // Check for already-cached output. if (!empty($this->live_preview)) { $cache = FALSE; @@ -785,6 +758,8 @@ class view extends views_db_object { } } + $exposed_form->post_render($this->display_handler->output); + if ($cache) { $cache->post_render($this->display_handler->output); } Index: plugins/views_plugin_display.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/plugins/views_plugin_display.inc,v retrieving revision 1.20.2.11 diff -u -p -r1.20.2.11 views_plugin_display.inc --- plugins/views_plugin_display.inc 2 Nov 2009 23:58:04 -0000 1.20.2.11 +++ plugins/views_plugin_display.inc 7 Nov 2009 14:03:18 -0000 @@ -180,6 +180,7 @@ class views_plugin_display extends views 'link_display' => array('link_display'), 'distinct' => array('distinct'), 'exposed_block' => array('exposed_block'), + 'exposed_form' => array('exposed_form'), // Force these to cascade properly. 'style_plugin' => array('style_plugin', 'style_options', 'row_plugin', 'row_options'), @@ -254,6 +255,7 @@ class views_plugin_display extends views 'use_more_text' => TRUE, 'distinct' => TRUE, 'exposed_block' => TRUE, + 'exposed_form' => TRUE, 'link_display' => TRUE, @@ -378,6 +380,11 @@ class views_plugin_display extends views 'exposed_block' => array( 'default' => FALSE, ), + 'exposed_form' => array( + 'contains' => array( + 'type' => array('default' => 'basic'), + ), + ), ); if ($this->is_default_display()) { @@ -534,6 +541,23 @@ class views_plugin_display extends views } /** + * Get the exposed form plugin + */ + function get_exposed_form_plugin($name = NULL) { + if (!$name) { + $exposed_form = $this->get_option('exposed_form'); + $name = $exposed_form['type']; + } + + $plugin = views_get_plugin('exposed_form', $name); + $options = $this->get_option($type . '_options'); + if ($plugin) { + $plugin->init($this->view, $this->display, $options); + return $plugin; + } + } + + /** * Get the handler object for a single handler. */ function &get_handler($type, $id) { @@ -800,6 +824,25 @@ class views_plugin_display extends views 'desc' => t('Allow the exposed form to appear in a block instead of the view.'), ); + $exposed_form_plugin = $this->get_exposed_form_plugin(); + if (!$exposed_form_plugin) { + // default to the no cache control plugin. + $exposed_form_plugin = views_get_plugin('exposed_form', 'basic'); + } + + $exposed_form_str = $exposed_form_plugin->summary_title(); + + $options['exposed_form'] = array( + 'category' => 'exposed', + 'title' => t('Exposed form style'), + 'value' => $exposed_form_str, + 'desc' => t('Select the kind of exposed filter to use.'), + ); + + if (!empty($exposed_form_plugin->definition['uses options'])) { + $options['exposed_form']['links']['exposed_form_options'] = t('Exposed form settings for this exposed form style.'); + } + foreach (array('header' => t('Header'), 'footer' => t('Footer'), 'empty' => t('Empty text')) as $type => $name) { if (!$this->get_option($type)) { $field = t('None'); @@ -1375,6 +1418,47 @@ class views_plugin_display extends views '#default_value' => $this->get_option('exposed_block') ? 1 : 0, ); break; + case 'exposed_form': + $form['#title'] .= t('Exposed Form'); + $form['exposed_form'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#tree' => TRUE, + ); + + $exposed_form = $this->get_option('exposed_form'); + $form['exposed_form']['type'] = array( + '#type' => 'radios', + '#options' => views_fetch_plugin_names('exposed_form'), + '#default_value' => $exposed_form['type'], + ); + + $exposed_form_plugin = views_fetch_plugin_data('exposed_form', $exposed_form['type']); + if (!empty($exposed_form_plugin['uses options'])) { + $form['markup'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#value' => t('You may also adjust the !settings for the currently selected style by clicking on the icon.', array('!settings' => $this->option_link(t('settings'), 'exposed_form_options'))), + ); + } + break; + case 'exposed_form_options': + $exposed_form = $this->get_option('exposed_form'); + $plugin = $this->get_exposed_form_plugin(); + $form['#title'] .= t('Exposed form options'); + if ($plugin) { + $form['#help_topic'] = $plugin->definition['help topic']; + + $form['exposed_form_options'] = array( + '#tree' => TRUE, + ); + $form['exposed_form_options']['type'] = array( + '#type' => 'value', + '#value' => $exposed_form['type'], + ); + $plugin->options_form($form['exposed_form_options'], $form_state); + } + break; } } @@ -1441,6 +1525,12 @@ class views_plugin_display extends views $plugin->options_validate($form['cache_options'], $form_state); } break; + case 'exposed_form_options': + $plugin = $this->get_exposed_form_plugin(); + if ($plugin) { + $plugin->options_validate($form['exposed_form_options'], $form_state); + } + break; } } @@ -1579,6 +1669,27 @@ class views_plugin_display extends views case 'exposed_block': $this->set_option($section, (bool) $form_state['values'][$section]); break; + case 'exposed_form': + $exposed_form = $this->get_option('exposed_form'); + if ($exposed_form['type'] != $form_state['values']['exposed_form']['type']) { + $plugin = views_get_plugin('exposed_form', $form_state['values']['exposed_form']['type']); + if ($plugin) { + $exposed_form = array('type' => $form_state['values']['exposed_form']['type']); + $this->set_option('exposed_form', $exposed_form); + if (!empty($plugin->definition['uses options'])) { + views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('exposed_form_options')); + } + } + } + + break; + case 'exposed_form_options': + $plugin = views_get_plugin('exposed_form', $form_state['values'][$section]['type']); + if ($plugin) { + $plugin->options_submit($form['exposed_form_options'], $form_state); + $this->set_option('exposed_form', $form_state['values'][$section]); + } + break; } } Index: plugins/views_plugin_exposed_form.inc =================================================================== RCS file: plugins/views_plugin_exposed_form.inc diff -N plugins/views_plugin_exposed_form.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/views_plugin_exposed_form.inc 7 Nov 2009 14:03:19 -0000 @@ -0,0 +1,124 @@ +view = &$view; + $this->display = &$display; + + $this->options = array(); + + $this->unpack_options($this->options, isset($options) ? $options : $display->handler->get_option('exposed_form')); + } + + /** + * Return a string to display as the clickable title for the + * access control. + */ + function summary_title() { + return t('Unknown'); + } + + function option_definition() { + $options = parent::option_definition(); + $options['submit_button'] = array('default' => 'Apply'); + $options['exposed_sorts'] = array('default' => 'Sort By'); + return $options; + } + + function options_form(&$form, &$form_state) { + $form['submit_button'] = array( + '#type' => 'textfield', + '#title' => t('Submit button text'), + '#description' => t('Text to display in the submit button of the exposed form.'), + '#default_value' => isset($this->options['submit_button'])?$this->options['submit_button']:'Apply', + ); + + $form['exposed_sorts_label'] = array( + '#type' => 'textfield', + '#title' => t('Exposed sorts label'), + '#description' => t('Text to display as the label of the exposed form element.'), + '#default_value' => isset($this->options['exposed_sorts_label'])?$this->options['exposed_sorts_label']:'Sort By', + ); + } + + /** + * Render the exposed filter form. + * + * This actually does more than that; because it's using FAPI, the form will + * also assign data to the appropriate handlers for use in building the + * query. + */ + function render_exposed_form($block = FALSE) { + // Deal with any exposed filters we may have, before building. + $form_state = array( + 'view' => &$this->view, + 'display' => &$this->display, + 'method' => 'get', + 'rerender' => TRUE, + 'no_redirect' => TRUE, + 'submit_button' => isset($this->options['submit_button'])?$this->options['submit_button']:'Apply', + 'exposed_sorts_label' => isset($this->options['exposed_sorts_label'])?$this->options['exposed_sorts_label']:'Sort By', + ); + + // Some types of displays (eg. attachments) may wish to use the exposed + // filters of their parent displays instead of showing an additional + // exposed filter form for the attachment as well as that for the parent. + if (!$this->view->display_handler->displays_exposed() || (!$block && $this->view->display_handler->get_option('exposed_block'))) { + unset($form_state['rerender']); + } + + if (!empty($this->ajax)) { + $form_state['ajax'] = TRUE; + } + + $output = drupal_build_form('views_exposed_form', $form_state); + if (!empty($form_state['js settings'])) { + $this->view->js_settings = $form_state['js settings']; + } + + return $output; + } + + function query() { + $view = $this->view; + $exposed_data = $view->exposed_data; + $sort_by = $exposed_data['sort_by']; + if (!empty($sort_by)) { + $handler = $view->sort[$sort_by]; + if (isset($handler)) { + + $view->query->orderby = array(); + if (isset($exposed_data['exposed_sort_order']) && in_array($exposed_data['exposed_sort_order'], array('asc', 'desc'))) { + $handler->options['order'] = $exposed_data['exposed_sort_order']; + } + $handler->query(); + foreach ($view->sort as $sort) { + if (!$sort->is_exposed()) { + $sort->query(); + } + } + } + } + } + + function pre_render() { } + + function post_render(&$output) { } + + function pre_execute() { } + + function post_execute() { } +} Index: plugins/views_plugin_exposed_form_basic.inc =================================================================== RCS file: plugins/views_plugin_exposed_form_basic.inc diff -N plugins/views_plugin_exposed_form_basic.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/views_plugin_exposed_form_basic.inc 7 Nov 2009 14:03:19 -0000 @@ -0,0 +1,13 @@ + 'Apply'); + $options['text_on_demand'] = array('default' => t('Select any filter and click on Apply to see results')); + $options['text_on_demand_format'] = array('default' => FILTER_FORMAT_DEFAULT); + + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $form['text_on_demand'] = array( + '#type' => 'textarea', + '#title' => t('Text on demand'), + '#description' => t('Text to display instead results until user select and apply an expose filter.'), + '#default_value' => isset($this->options['text_on_demand'])?$this->options['text_on_demand']:t('Select any filter and click on Apply to see results.'), + ); + + $form['text_on_demand_format'] = filter_form($this->options['text_on_demand_format'], NULL, array('text_on_demand_format')); + } + + function options_submit(&$form, &$form_state) { + $form_state['values']['exposed_form_options']['text_on_demand_format'] = $form_state['values']['text_on_demand_format']; + + parent::options_submit($form, $form_state); + } + + function exposed_filter_applied() { + $view = $this->view; + $exposed_data = $view->exposed_data; + if (is_array($view->filter) && count($view->filter)) { + foreach ($view->filter as $filter_id => $filter) { + if ($filter->is_exposed()) { + $filter_value = $exposed_data[$filter->options['expose']['identifier']]; + if (!empty($filter_value) && $filter_value != 'All') { + return TRUE; + } + } + } + } + return FALSE; + } + + function pre_render() { + if (!$this->exposed_filter_applied()) { + $this->view->display_handler->set_option('empty', $this->options['text_on_demand']); + $this->view->display_handler->set_option('empty_format', $this->options['text_on_demand_format']); + } + } + + function query() { + parent::query(); + if (!$this->exposed_filter_applied()) { + // clear WHERE statment to display empty text + $this->view->query->where = array(); + $this->view->query->add_where(0, '1 = 0'); + } + } + +}