#458194 by voxpelli: Add first and last row classes to table style.
[project/views.git] / theme / theme.inc
index 908681c..8ff3292 100644 (file)
@@ -24,6 +24,7 @@ function _views_theme_functions($hook, $view, $display = NULL) {
   if ($display) {
     $themes[] = $hook . '__' . $view->name . '__' . $display->id;
     $themes[] = $hook . '__' . $display->id;
+    $themes[] = $hook . '__' . preg_replace('/[^a-z0-9]/', '-', strtolower($view->tag));
     if ($display->id != $display->display_plugin) {
       $themes[] = $hook . '__' . $view->name . '__' . $display->display_plugin;
       $themes[] = $hook . '__' . $display->display_plugin;
@@ -31,7 +32,6 @@ function _views_theme_functions($hook, $view, $display = NULL) {
   }
   $themes[] = $hook . '__' . $view->name;
   $themes[] = $hook;
-
   return $themes;
 }
 
@@ -39,9 +39,11 @@ function _views_theme_functions($hook, $view, $display = NULL) {
  * Preprocess the primary theme implementation for a view.
  */
 function template_preprocess_views_view(&$vars) {
+  global $base_path;
+
   $view = $vars['view'];
 
-  $vars['rows']       = !empty($view->result) ? $view->style_handler->render($view->result) : '';
+  $vars['rows']       = !empty($view->result) || !empty($view->style_plugin->definition['even empty']) ? $view->style_plugin->render($view->result) : '';
 
   $vars['css_name']   = views_css_safe($view->name);
   $vars['name']       = $view->name;
@@ -75,13 +77,16 @@ function template_preprocess_views_view(&$vars) {
   $vars['attachment_after'] = !empty($view->attachment_after) ? $view->attachment_after : '';
 
   $vars['pager']      = '';
+
+  $exposed_input = isset($view->exposed_data_raw) ? $view->exposed_data_raw : NULL;
   if (!empty($view->pager['use_pager'])) {
     $pager_type = ($view->pager['use_pager'] === 'mini' ? 'views_mini_pager' : 'pager');
-    $vars['pager']    = theme($pager_type, $view->exposed_input, $view->pager['items_per_page'], $view->pager['element']);
+    $pager_theme = views_theme_functions($pager_type, $view, $view->display_handler->display);
+    $vars['pager']    = theme($pager_theme, $exposed_input, $view->pager['items_per_page'], $view->pager['element']);
   }
 
   // if administrator, add some links. These used to be tabs, but this is better.
-  if (user_access('administer views')) {
+  if (user_access('administer views') && module_exists('views_ui') && empty($view->hide_admin_links) && !variable_get('views_no_hover_links', FALSE)) {
     $vars['admin_links_raw'] = array(
       array(
         'title' => t('Edit'),
@@ -101,13 +106,27 @@ function template_preprocess_views_view(&$vars) {
         'href' => "admin/build/views/clone/$view->name",
       ),
     );
+
+    drupal_alter('views_admin_links', $vars['admin_links_raw'], $view);
     $vars['admin_links'] = theme('links', $vars['admin_links_raw']);
-    views_add_css('views');
   }
   else {
     $vars['admin_links'] = '';
     $vars['admin_links_raw'] = array();
   }
+  views_add_css('views');
+
+  // Our JavaScript needs to have some means to find the HTML belonging to this
+  // view.
+  //
+  // It is true that the DIV wrapper has classes denoting the name of the view
+  // and its display ID, but this is not enough to unequivocally match a view
+  // with its HTML, because one view may appear several times on the page. So
+  // we set up a running counter, $dom_id, to issue a "unique" identifier for
+  // each view. This identifier is written to both Drupal.settings and the DIV
+  // wrapper.
+  static $dom_id = 1;
+  $vars['dom_id'] = !empty($view->dom_id) ? $view->dom_id : $dom_id++;
 
   // If using AJAX, send identifying data about this view.
   if ($view->use_ajax) {
@@ -120,6 +139,13 @@ function template_preprocess_views_view(&$vars) {
             'view_display_id' => $view->current_display,
             'view_args' => implode('/', $view->args),
             'view_path' => $_GET['q'],
+            // Pass through URL to ensure we get e.g. language prefixes.
+//            'view_base_path' => isset($view->display['page']) ? substr(url($view->display['page']->display_options['path']), strlen($base_path)) : '',
+            'view_base_path' => $view->get_path(),
+            'view_dom_id' => $vars['dom_id'],
+            // To fit multiple views on a page, the programmer may have
+            // overridden the display's pager_element.
+            'pager_element' => $view->pager['element'],
           ),
         ),
       ),
@@ -137,21 +163,35 @@ function template_preprocess_views_view_fields(&$vars) {
   $view = $vars['view'];
 
   // Loop through the fields for this view.
+  $inline = FALSE;
+  $vars['fields'] = array(); // ensure it's at least an empty array.
   foreach ($view->field as $id => $field) {
-    if (!empty($field['handler']) && is_object($field['handler'])) {
+    // render this even if set to exclude so it can be used elsewhere.
+    $field_output = $view->field[$id]->theme($vars['row']);
+    $empty = $field_output !== 0 && empty($field_output);
+    if (empty($field->options['exclude']) && (!$empty || empty($field->options['hide_empty']))) {
       $object = new stdClass();
 
-      $object->content = $field['handler']->theme($vars['row']);
-      if (isset($field['handler']->field_alias) && isset($vars['row']->{$field['handler']->field_alias})) {
-        $object->raw = $vars['row']->{$field['handler']->field_alias};
+      $object->content = $field_output;
+      if (isset($view->field[$id]->field_alias) && isset($vars['row']->{$view->field[$id]->field_alias})) {
+        $object->raw = $vars['row']->{$view->field[$id]->field_alias};
       }
       else {
         $object->raw = NULL; // make sure it exists to reduce NOTICE
       }
+      $object->inline = !empty($vars['options']['inline'][$id]);
+      $object->inline_html = $object->inline ? 'span' : 'div';
+      if (!empty($vars['options']['separator']) && $inline && $object->inline && $object->content) {
+        $object->separator = filter_xss_admin($vars['options']['separator']);
+      }
+
+      $inline = $object->inline;
+
+      $object->handler = &$view->field[$id];
+      $object->element_type = $object->handler->element_type();
 
-      $object->handler = $field['handler'];
       $object->class = views_css_safe($id);
-      $object->label = check_plain($field['handler']->label());
+      $object->label = check_plain($view->field[$id]->label());
       $vars['fields'][$id] = $object;
     }
   }
@@ -166,7 +206,18 @@ function template_preprocess_views_view_fields(&$vars) {
  * this: @code { $row->{$field->field_alias} @endcode
  */
 function theme_views_view_field($view, $field, $row) {
-  return $field->render($row);
+  return $field->advanced_render($row);
+}
+
+/**
+ * Process a single field within a view.
+ *
+ * This preprocess function isn't normally run, as a function is used by
+ * default, for performance. However, by creating a template, this
+ * preprocess should get picked up.
+ */
+function template_preprocess_views_view_field(&$vars) {
+  $vars['output'] = $vars['field']->advanced_render($vars['row']);
 }
 
 /**
@@ -174,11 +225,48 @@ function theme_views_view_field($view, $field, $row) {
  */
 function template_preprocess_views_view_summary(&$vars) {
   $view     = $vars['view'];
-  $argument = $view->argument[$view->build_info['summary_level']]['handler'];
+  $argument = $view->argument[$view->build_info['summary_level']];
 
+  $url_options = array();
+
+  if (!empty($view->exposed_raw_input)) {
+    $url_options['query'] = $view->exposed_raw_input;
+  }
   foreach ($vars['rows'] as $id => $row) {
     $vars['rows'][$id]->link = $argument->summary_name($row);
-    $vars['rows'][$id]->url = $argument->summary_link($row, $view->get_url());
+    $args = $view->args;
+    $args[$argument->position] = $argument->summary_argument($row);
+
+    $vars['rows'][$id]->url = url($view->get_url($args), $url_options);
+    $vars['rows'][$id]->count = intval($row->{$argument->count_alias});
+  }
+}
+
+/**
+ * Template preprocess theme function to print summary basically
+ * unformatted.
+ */
+function template_preprocess_views_view_summary_unformatted(&$vars) {
+  $view     = $vars['view'];
+  $argument = $view->argument[$view->build_info['summary_level']];
+
+  $url_options = array();
+
+  if (!empty($view->exposed_raw_input)) {
+    $url_options['query'] = $view->exposed_raw_input;
+  }
+
+  $count = 0;
+  foreach ($vars['rows'] as $id => $row) {
+    // only false on first time:
+    if ($count++) {
+      $vars['rows'][$id]->separator = filter_xss_admin($vars['options']['separator']);
+    }
+    $vars['rows'][$id]->link = $argument->summary_name($row);
+    $args = $view->args;
+    $args[$argument->position] = $argument->summary_argument($row);
+
+    $vars['rows'][$id]->url = url($view->get_url($args), $url_options);
     $vars['rows'][$id]->count = intval($row->{$argument->count_alias});
   }
 }
@@ -188,11 +276,19 @@ function template_preprocess_views_view_summary(&$vars) {
  */
 function template_preprocess_views_view_table(&$vars) {
   $view     = $vars['view'];
-  $result   = $view->result;
-  $options  = $view->style_handler->options;
-  $handler  = $view->style_handler;
 
-  $fields   = $view->field;
+  // We need the raw data for this grouping, which is passed in as $vars['rows'].
+  // However, the template also needs to use for the rendered fields.  We
+  // therefore swap the raw data out to a new variable and reset $vars['rows']
+  // so that it can get rebuilt.
+  // Store rows so that they may be used by further preprocess functions.
+  $result   = $vars['result'] = $vars['rows'];
+  $vars['rows'] = array();
+
+  $options  = $view->style_plugin->options;
+  $handler  = $view->style_plugin;
+
+  $fields   = &$view->field;
   $columns  = $handler->sanitize_columns($options['columns'], $fields);
 
   $active   = !empty($handler->active) ? $handler->active : '';
@@ -203,11 +299,24 @@ function template_preprocess_views_view_table(&$vars) {
     $query = '&' . $query;
   }
 
+  // Fields must be rendered in order as of Views 2.3, so we will pre-render
+  // everything.
+  $renders = array();
+  $view->row_index = 0;
+  $keys = array_keys($view->field);
+  foreach ($result as $count => $row) {
+    foreach ($keys as $id) {
+      $renders[$count][$id] = $view->field[$id]->theme($row);
+    }
+    $view->row_index = $count;
+  }
+  unset($view->row_index);
+
   foreach ($columns as $field => $column) {
     // render the header labels
-    if ($field == $column) {
-      $label = check_plain(!empty($fields[$field]['handler']) ? $fields[$field]['handler']->label() : '');
-      if (empty($options['info'][$field]['sortable'])) {
+    if ($field == $column && empty($fields[$field]->options['exclude'])) {
+      $label = check_plain(!empty($fields[$field]) ? $fields[$field]->label() : '');
+      if (empty($options['info'][$field]['sortable']) || !$fields[$field]->click_sortable()) {
         $vars['header'][$field] = $label;
       }
       else {
@@ -218,14 +327,16 @@ function template_preprocess_views_view_table(&$vars) {
           $initial = 'desc';
         }
 
-        $image = theme('tablesort_indicator', $initial);
         $title = t('sort by @s', array('@s' => $label));
+        if ($active == $field) {
+          $label .= theme('tablesort_indicator', $initial);
+        }
         $link_options = array(
           'html' => true,
           'attributes' => array('title' => $title),
           'query' => 'order=' . urlencode($field) . '&sort=' . $initial . $query,
         );
-        $vars['header'][$field] = l($label . $image, $_GET['q'], $link_options);
+        $vars['header'][$field] = l($label, $_GET['q'], $link_options);
       }
     }
 
@@ -238,29 +349,42 @@ function template_preprocess_views_view_table(&$vars) {
 
     // Render each field into its appropriate column.
     foreach ($result as $num => $row) {
-      if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler'])) {
-        $handler = &$fields[$field]['handler'];
-        $field_output = $handler->theme($row);
+      if (!empty($fields[$field]) && empty($fields[$field]->options['exclude'])) {
+        $field_output = $renders[$num][$field];
+
+        if (!isset($vars['rows'][$num][$column])) {
+          $vars['rows'][$num][$column] = '';
+        }
 
         // Don't bother with separators and stuff if the field does not show up.
-        if (!isset($field_output) && isset($vars['rows'][$num][$column])) {
+        if ($field_output === '') {
           continue;
         }
 
         // Place the field into the column, along with an optional separator.
-        if (isset($vars['rows'][$num][$column])) {
+        if ($vars['rows'][$num][$column] !== '') {
           if (!empty($options['info'][$column]['separator'])) {
             $vars['rows'][$num][$column] .= filter_xss_admin($options['info'][$column]['separator']);
           }
         }
-        else {
-          $vars['rows'][$num][$column] = '';
-        }
 
         $vars['rows'][$num][$column] .= $field_output;
       }
     }
   }
+
+  foreach ($vars['rows'] as $num => $row) {
+    $vars['row_classes'][$num][] = ($num % 2 == 0) ? 'odd' : 'even';
+  }
+
+  $vars['row_classes'][0][] = 'views-row-first';
+  $vars['row_classes'][count($vars['row_classes']) - 1][] = 'views-row-last';
+
+  $vars['class'] = 'views-table';
+  if (!empty($options['sticky'])) {
+    drupal_add_js('misc/tableheader.js');
+    $vars['class'] .= " sticky-enabled";
+  }
 }
 
 /**
@@ -269,8 +393,8 @@ function template_preprocess_views_view_table(&$vars) {
 function template_preprocess_views_view_grid(&$vars) {
   $view     = $vars['view'];
   $result   = $view->result;
-  $options  = $view->style_handler->options;
-  $handler  = $view->style_handler;
+  $options  = $view->style_plugin->options;
+  $handler  = $view->style_plugin;
 
   $columns  = $options['columns'];
 
@@ -278,14 +402,21 @@ function template_preprocess_views_view_grid(&$vars) {
 
   if ($options['alignment'] == 'horizontal') {
     $row = array();
+    $row_count = 0;
     foreach ($vars['rows'] as $count => $item) {
       $row[] = $item;
+      $row_count++;
       if (($count + 1) % $columns == 0) {
         $rows[] = $row;
         $row = array();
+        $row_count = 0;
       }
     }
     if ($row) {
+      // Fill up the last line.
+      for ($i = 0; $i < ($columns - $row_count); $i++) {
+        $row[] = '';
+      }
       $rows[] = $row;
     }
   }
@@ -309,11 +440,44 @@ function template_preprocess_views_view_grid(&$vars) {
         $remainders--;
       }
     }
+    for ($i = 0; $i < count($rows[0]); $i++) {
+      // This should be string so that's okay :)
+      if (!isset($rows[count($rows) - 1][$i])) {
+        $rows[count($rows) - 1][$i] = '';
+      }
+    }
   }
   $vars['rows'] = $rows;
 }
 
 /**
+ * Display the simple view of rows one after another
+ */
+function template_preprocess_views_view_unformatted(&$vars) {
+  $view     = $vars['view'];
+  $rows     = $vars['rows'];
+
+  $vars['classes'] = array();
+  // Set up striping values.
+  foreach ($rows as $id => $row) {
+    $vars['classes'][$id] = 'views-row';
+    $vars['classes'][$id] .= ' views-row-' . ($id + 1);
+    $vars['classes'][$id] .= ' views-row-' . ($id % 2 ? 'even' : 'odd');
+    if ($id == 0) {
+      $vars['classes'][$id] .= ' views-row-first';
+    }
+  }
+  $vars['classes'][$id] .= ' views-row-last';
+}
+
+/**
+ * Display the view as an HTML list element
+ */
+function template_preprocess_views_view_list(&$vars) {
+  template_preprocess_views_view_unformatted($vars);
+}
+
+/**
  * Preprocess an RSS feed
  */
 function template_preprocess_views_view_rss(&$vars) {
@@ -324,7 +488,7 @@ function template_preprocess_views_view_rss(&$vars) {
   $options  = &$vars['options'];
   $items    = &$vars['rows'];
 
-  $style    = &$view->style_handler;
+  $style    = &$view->style_plugin;
 
   if (!empty($options['mission_description'])) {
     $description = variable_get('site_mission', '');
@@ -332,24 +496,67 @@ function template_preprocess_views_view_rss(&$vars) {
   else {
     $description = $options['description'];
   }
+  // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
+  // We strip all HTML tags, but need to prevent double encoding from properly
+  // escaped source data (such as &amp becoming &amp;amp;).
+  $vars['description'] = check_plain(decode_entities(strip_tags($description)));
+
+  if ($view->display_handler->get_option('sitename_title')) {
+    $title = variable_get('site_name', 'Drupal');
+    if ($slogan = variable_get('site_slogan', '')) {
+      $title .= ' - ' . $slogan;
+    }
+  }
+  else {
+    $title = $view->get_title();
+  }
+  $vars['title'] = check_plain($title);
 
   // Figure out which display which has a path we're using for this feed. If there isn't
   // one, use the global $base_url
-  $link_display = $view->display_handler->get_link_display();
+  $link_display_id = $view->display_handler->get_link_display();
+  if ($link_display_id && !empty($view->display[$link_display_id])) {
+    $path = $view->display[$link_display_id]->handler->get_path();
+  }
 
+  if ($path) {
+    $path = $view->get_url(NULL, $path);
+    $url_options = array('absolute' => TRUE);
+    if (!empty($view->exposed_raw_input)) {
+      $url_options['query'] = $view->exposed_raw_input;
+    }
+
+    // Compare the link to the default home page; if it's the default home page, just use $base_url.
+    if ($path == variable_get('site_frontpage', 'node')) {
+      $path = '';
+    }
 
-  // Compare the link to the default home page; if it's the default home page, just use $base_url.
-  if (empty($vars['link'])) {
-    $vars['link'] = $base_url;
+    $vars['link'] = check_url(url($path, $url_options));
   }
 
+  $vars['langcode'] = check_plain($language->language);
   $vars['namespaces'] = drupal_attributes($style->namespaces);
-  $vars['channel'] = format_rss_channel($view->get_title(), $vars['link'], $description, $items, $language->language);
+  $vars['items'] = $items;
+  $vars['channel_elements'] = format_xml_elements($style->channel_elements);
 
   drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
 }
 
 /**
+ * Default theme function for all RSS rows.
+ */
+function template_preprocess_views_view_row_rss(&$vars) {
+  $view     = &$vars['view'];
+  $options  = &$vars['options'];
+  $item     = &$vars['row'];
+
+  $vars['title'] = check_plain($item->title);
+  $vars['link'] = check_url($item->link);
+  $vars['description'] = check_plain($item->description);
+  $vars['item_elements'] = empty($item->elements) ? '' : format_xml_elements($item->elements);
+}
+
+/**
  * Default theme function for all filter forms.
  */
 function template_preprocess_views_exposed_form(&$vars) {
@@ -416,7 +623,14 @@ function theme_views_mini_pager($tags = array(), $limit = 10, $element = 0, $par
 
 
   $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹‹')), $limit, $element, 1, $parameters);
+  if (empty($li_previous)) {
+    $li_previous = "&nbsp;";
+  }
+
   $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('››')), $limit, $element, 1, $parameters);
+  if (empty($li_next)) {
+    $li_next = "&nbsp;";
+  }
 
   if ($pager_total[$element] > 1) {
     $items[] = array(
@@ -462,23 +676,23 @@ function theme_views_mini_pager($tags = array(), $limit = 10, $element = 0, $par
  *
  * View, named foobar. Style: unformatted. Row style: Fields. Display: Page.
  *
- * - views-view--page--foobar.tpl.php
+ * - views-view--foobar--page.tpl.php
  * - views-view--page.tpl.php
  * - views-view--foobar.tpl.php
  * - views-view.tpl.php
  *
- * - views-view-unformatted--page--foobar.tpl.php
+ * - views-view-unformatted--foobar--page.tpl.php
  * - views-view-unformatted--page.tpl.php
  * - views-view-unformatted--foobar.tpl.php
  * - views-view-unformatted.tpl.php
  *
- * - views-view-fields--page--foobar.tpl.php
+ * - views-view-fields--foobar--page.tpl.php
  * - views-view-fields--page.tpl.php
  * - views-view-fields--foobar.tpl.php
  * - views-view-fields.tpl.php
  *
  * Important! When adding a new template to your theme, be sure to flush the
- * theme registry cache! Simply visit admin/build/themes.
+ * theme registry cache!
  *
  * @see _views_theme_functions
  * @}